Implement configuration loading for HTTP, IRC, TCP, and UDP tunnels using go-i2ptunnel-config

This commit is contained in:
eyedeekay
2025-10-18 22:35:38 -04:00
parent da3277425a
commit ea7bec0fd4
10 changed files with 398 additions and 29 deletions

4
go.mod
View File

@@ -8,8 +8,9 @@ require (
github.com/elazarl/goproxy v1.7.0
github.com/go-i2p/go-connfilter v0.0.0-20250205023438-0f2b889a80f6
github.com/go-i2p/go-forward v0.0.0-20250202052226-ee8a43dcb664
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250209030407-ba90db65df97
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20251019021515-10ef90f2473b
github.com/go-i2p/go-limit v0.0.0-20250203203118-210616857c15
github.com/go-i2p/go-sam-go v0.0.0-20251016194809-b6ead96fdc39
github.com/go-i2p/i2pkeys v0.33.92
github.com/go-i2p/onramp v0.33.93-0.20251016200402-d3ac8f5353c5
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
@@ -20,7 +21,6 @@ require (
github.com/cretz/bine v0.2.0 // indirect
github.com/go-i2p/common v0.0.0-20250819203334-e5459df35789 // indirect
github.com/go-i2p/crypto v0.0.0-20250822224541-85015740db11 // indirect
github.com/go-i2p/go-sam-go v0.0.0-20251016194809-b6ead96fdc39 // indirect
github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect

View File

@@ -27,6 +27,7 @@ import (
"fmt"
"net"
"net/http"
"os"
"strconv"
"sync"
@@ -192,8 +193,44 @@ func (h *HTTPClient) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
//
// Why: HTTP proxies need dynamic configuration updates for production deployments.
// Design: Uses go-i2ptunnel-config library for parsing. Preserves SAM connection and I2P keys.
func (h *HTTPClient) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if h.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
h.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", h.I2PTunnelStatus)
}
// Parse config file using the converter library
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "httpclient" {
return fmt.Errorf("config file contains %s tunnel, expected httpclient", newConfig.Type)
}
// Update mutable configuration fields
// The Garlic connection (SAM) is preserved to maintain tunnel identity and keys
h.TunnelConfig = *newConfig
return nil
}

View File

@@ -26,6 +26,7 @@ import (
"context"
"fmt"
"net"
"os"
"strconv"
httpinspector "github.com/go-i2p/go-connfilter/http"
@@ -201,8 +202,50 @@ func (h *HTTPServer) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
//
// Why: HTTP reverse proxies need dynamic configuration updates for production deployments.
// Design: Uses go-i2ptunnel-config library for parsing. Preserves SAM connection and I2P keys.
func (h *HTTPServer) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if h.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
h.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", h.I2PTunnelStatus)
}
// Parse config file using the converter library
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "httpserver" {
return fmt.Errorf("config file contains %s tunnel, expected httpserver", newConfig.Type)
}
// Validate target address (local service) before applying changes
targetAddr, err := net.ResolveTCPAddr("tcp", newConfig.Target)
if err != nil {
return fmt.Errorf("invalid target address in config: %w", err)
}
// Update mutable configuration fields
h.TunnelConfig = *newConfig
h.Addr = targetAddr
return nil
}

View File

@@ -16,6 +16,7 @@ import (
"context"
"fmt"
"net"
"os"
"strconv"
ircinspector "github.com/go-i2p/go-connfilter/irc"
@@ -180,8 +181,47 @@ func (i *IRCClient) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
func (i *IRCClient) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if i.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
i.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", i.I2PTunnelStatus)
}
// Parse config file using the converter library
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "ircclient" {
return fmt.Errorf("config file contains %s tunnel, expected ircclient", newConfig.Type)
}
// Validate target address before applying changes
addr, err := i2pkeys.Lookup(newConfig.Target)
if err != nil {
return fmt.Errorf("invalid target address in config: %w", err)
}
// Update mutable configuration fields
i.TunnelConfig = *newConfig
i.I2PAddr = addr
return nil
}

View File

@@ -16,6 +16,7 @@ import (
"context"
"fmt"
"net"
"os"
"strconv"
ircinspector "github.com/go-i2p/go-connfilter/irc"
@@ -191,8 +192,47 @@ func (i *IRCServer) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
func (i *IRCServer) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if i.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
i.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", i.I2PTunnelStatus)
}
// Parse config file using the converter library
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "ircserver" {
return fmt.Errorf("config file contains %s tunnel, expected ircserver", newConfig.Type)
}
// Validate target address (local service) before applying changes
targetAddr, err := net.ResolveTCPAddr("tcp", newConfig.Target)
if err != nil {
return fmt.Errorf("invalid target address in config: %w", err)
}
// Update mutable configuration fields
i.TunnelConfig = *newConfig
i.Addr = targetAddr
return nil
}

View File

@@ -43,6 +43,7 @@ import (
"context"
"fmt"
"net"
"os"
"strconv"
"sync"
@@ -204,8 +205,44 @@ func (s *SOCKS) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
//
// Why: SOCKS proxies need dynamic configuration for production deployments.
// Design: Uses go-i2ptunnel-config library for parsing. Preserves SAM connection and I2P keys.
func (s *SOCKS) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if s.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
s.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", s.I2PTunnelStatus)
}
// Parse config file using the converter library
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "socks" {
return fmt.Errorf("config file contains %s tunnel, expected socks", newConfig.Type)
}
// Update mutable configuration fields
// The Garlic connection (SAM) is preserved to maintain tunnel identity and keys
s.TunnelConfig = *newConfig
return nil
}

View File

@@ -24,6 +24,7 @@ import (
"context"
"fmt"
"net"
"os"
"strconv"
"github.com/go-i2p/go-forward/config"
@@ -183,8 +184,56 @@ func (t *TCPClient) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
//
// Why: Production deployments need to reload configuration without recreating tunnel objects.
// This enables configuration management tools and web UIs to persist changes.
//
// Design: Uses the go-i2ptunnel-config library to parse config files in multiple formats,
// then updates only the mutable fields. SAM connection is preserved to maintain tunnel identity.
// The Garlic (I2P connection) is NOT reloaded - it maintains the existing keys and SAM session.
func (t *TCPClient) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if t.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
t.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", t.I2PTunnelStatus)
}
// Parse config file using the converter library
// This handles format detection and validation for .properties, .ini, .yaml
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "tcpclient" {
return fmt.Errorf("config file contains %s tunnel, expected tcpclient", newConfig.Type)
}
// Validate target address before applying changes
addr, err := i2pkeys.Lookup(newConfig.Target)
if err != nil {
return fmt.Errorf("invalid target address in config: %w", err)
}
// Update mutable configuration fields
// The Garlic connection (SAM) is preserved to maintain tunnel identity and keys
t.TunnelConfig = *newConfig
t.I2PAddr = addr
return nil
}

View File

@@ -17,6 +17,7 @@ import (
"context"
"fmt"
"net"
"os"
"strconv"
"github.com/go-i2p/go-forward/config"
@@ -191,8 +192,50 @@ func (t *TCPServer) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
//
// Why: Production deployments need to reload configuration without recreating tunnel objects.
// Design: Uses go-i2ptunnel-config library for parsing. Preserves SAM connection and I2P keys.
func (t *TCPServer) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if t.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
t.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", t.I2PTunnelStatus)
}
// Parse config file using the converter library
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "tcpserver" {
return fmt.Errorf("config file contains %s tunnel, expected tcpserver", newConfig.Type)
}
// Validate target address (local service) before applying changes
targetAddr, err := net.ResolveTCPAddr("tcp", newConfig.Target)
if err != nil {
return fmt.Errorf("invalid target address in config: %w", err)
}
// Update mutable configuration fields
t.TunnelConfig = *newConfig
t.Addr = targetAddr
return nil
}

View File

@@ -26,6 +26,7 @@ import (
"context"
"fmt"
"net"
"os"
"strconv"
"github.com/go-i2p/go-forward/config"
@@ -184,8 +185,47 @@ func (u *UDPClient) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
func (u *UDPClient) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if u.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
u.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", u.I2PTunnelStatus)
}
// Parse config file using the converter library
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "udpclient" {
return fmt.Errorf("config file contains %s tunnel, expected udpclient", newConfig.Type)
}
// Validate target address before applying changes
addr, err := i2pkeys.Lookup(newConfig.Target)
if err != nil {
return fmt.Errorf("invalid target address in config: %w", err)
}
// Update mutable configuration fields
u.TunnelConfig = *newConfig
u.I2PAddr = addr
return nil
}

View File

@@ -26,6 +26,7 @@ import (
"context"
"fmt"
"net"
"os"
"strconv"
"github.com/go-i2p/go-forward/config"
@@ -176,8 +177,47 @@ func (u *UDPServer) SetOptions(opts map[string]string) error {
return nil
}
// Load the tunnel config from file
// LoadConfig loads tunnel configuration from a file and updates the tunnel settings.
// The tunnel must be stopped before calling LoadConfig to prevent inconsistent state.
// Supported formats: .properties, .ini, .yaml/.yml
func (u *UDPServer) LoadConfig(path string) error {
// For now, return an error indicating this method needs configuration file support
return fmt.Errorf("LoadConfig not yet implemented: would load configuration from %s", path)
// Prevent config changes while tunnel is running to avoid race conditions
if u.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusRunning ||
u.I2PTunnelStatus == i2ptunnel.I2PTunnelStatusStarting {
return fmt.Errorf("cannot load config while tunnel is %s - stop tunnel first", u.I2PTunnelStatus)
}
// Parse config file using the converter library
conv := i2pconv.Converter{}
format, err := conv.DetectFormat(path)
if err != nil {
return fmt.Errorf("failed to detect config format: %w", err)
}
bytes, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newConfig, err := conv.ParseInput(bytes, format)
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
// Type safety: ensure loaded config matches expected tunnel type
if newConfig.Type != "udpserver" {
return fmt.Errorf("config file contains %s tunnel, expected udpserver", newConfig.Type)
}
// Validate target address (local service) before applying changes
targetAddr, err := net.ResolveUDPAddr("udp", newConfig.Target)
if err != nil {
return fmt.Errorf("invalid target address in config: %w", err)
}
// Update mutable configuration fields
u.TunnelConfig = *newConfig
u.Addr = targetAddr
return nil
}