diff --git a/lib/http/client/httpclient.go b/lib/http/client/httpclient.go index d02a07e..5d259c2 100644 --- a/lib/http/client/httpclient.go +++ b/lib/http/client/httpclient.go @@ -24,6 +24,7 @@ Key features: import ( "context" + "fmt" "net" "net/http" "strconv" @@ -69,7 +70,11 @@ func (h *HTTPClient) recordError(err error) { // Get the tunnel's I2P address func (h *HTTPClient) Address() string { - return h.Garlic.B32() + // For HTTP client proxy, return the service address if available + if h.Garlic != nil && h.Garlic.ServiceKeys != nil { + return h.Garlic.ServiceKeys.Addr().Base32() + } + return "" } // Get the tunnel's error message @@ -151,3 +156,44 @@ func (h *HTTPClient) Target() string { func (h *HTTPClient) Type() string { return h.TunnelConfig.Type } + +// Get the tunnel's ID +func (h *HTTPClient) ID() string { + return i2ptunnel.Clean(h.Name()) +} + +// Get the tunnel's options +func (h *HTTPClient) Options() map[string]string { + // Return basic configuration options as a map + options := make(map[string]string) + options["name"] = h.TunnelConfig.Name + options["type"] = h.TunnelConfig.Type + options["interface"] = h.TunnelConfig.Interface + options["port"] = strconv.Itoa(h.TunnelConfig.Port) + return options +} + +// Set the tunnel's options +func (h *HTTPClient) SetOptions(opts map[string]string) error { + // Apply configuration options from the map + if name, ok := opts["name"]; ok { + h.TunnelConfig.Name = name + } + if iface, ok := opts["interface"]; ok { + h.TunnelConfig.Interface = iface + } + if portStr, ok := opts["port"]; ok { + if port, err := strconv.Atoi(portStr); err == nil { + h.TunnelConfig.Port = port + } else { + return fmt.Errorf("invalid port value: %s", portStr) + } + } + return nil +} + +// Load the tunnel config from file +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) +} diff --git a/lib/http/client/new.go b/lib/http/client/new.go index bb7fe23..efe9ea4 100644 --- a/lib/http/client/new.go +++ b/lib/http/client/new.go @@ -28,7 +28,6 @@ func NewHTTPClient(config i2pconv.TunnelConfig, samAddr string) (*HTTPClient, er I2PTunnelStatus: i2ptunnel.I2PTunnelStatusStopped, done: make(chan struct{}), } - return h, nil } diff --git a/lib/http/server/httpserver.go b/lib/http/server/httpserver.go index c7e4753..a8bf267 100644 --- a/lib/http/server/httpserver.go +++ b/lib/http/server/httpserver.go @@ -24,6 +24,7 @@ Key features: import ( "context" + "fmt" "net" "strconv" @@ -64,7 +65,11 @@ func (h *HTTPServer) recordError(err error) { // Get the tunnel's I2P address func (h *HTTPServer) Address() string { - return h.Garlic.B32() + // For HTTP server, return the service address if available + if h.Garlic != nil && h.Garlic.ServiceKeys != nil { + return h.Garlic.ServiceKeys.Addr().Base32() + } + return "" } // Get the tunnel's error message @@ -141,3 +146,63 @@ func (h *HTTPServer) Target() string { func (h *HTTPServer) Type() string { return h.TunnelConfig.Type } + +// Get the tunnel's ID +func (h *HTTPServer) ID() string { + return i2ptunnel.Clean(h.Name()) +} + +// Get the tunnel's options +func (h *HTTPServer) Options() map[string]string { + // Return basic configuration options as a map + options := make(map[string]string) + options["name"] = h.TunnelConfig.Name + options["type"] = h.TunnelConfig.Type + options["interface"] = h.TunnelConfig.Interface + options["port"] = strconv.Itoa(h.TunnelConfig.Port) + options["maxconns"] = strconv.Itoa(h.LimitedConfig.MaxConns) + options["ratelimit"] = strconv.FormatFloat(h.LimitedConfig.RateLimit, 'f', -1, 64) + if h.Addr != nil { + options["target"] = h.Addr.String() + } + return options +} + +// Set the tunnel's options +func (h *HTTPServer) SetOptions(opts map[string]string) error { + // Apply configuration options from the map + if name, ok := opts["name"]; ok { + h.TunnelConfig.Name = name + } + if iface, ok := opts["interface"]; ok { + h.TunnelConfig.Interface = iface + } + if portStr, ok := opts["port"]; ok { + if port, err := strconv.Atoi(portStr); err == nil { + h.TunnelConfig.Port = port + } else { + return fmt.Errorf("invalid port value: %s", portStr) + } + } + if maxconnsStr, ok := opts["maxconns"]; ok { + if maxconns, err := strconv.Atoi(maxconnsStr); err == nil { + h.LimitedConfig.MaxConns = maxconns + } else { + return fmt.Errorf("invalid maxconns value: %s", maxconnsStr) + } + } + if ratelimitStr, ok := opts["ratelimit"]; ok { + if ratelimit, err := strconv.ParseFloat(ratelimitStr, 64); err == nil { + h.LimitedConfig.RateLimit = ratelimit + } else { + return fmt.Errorf("invalid ratelimit value: %s", ratelimitStr) + } + } + return nil +} + +// Load the tunnel config from file +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) +} diff --git a/lib/tcp/client/tcpclient.go b/lib/tcp/client/tcpclient.go index 04bd5ac..5be619c 100644 --- a/lib/tcp/client/tcpclient.go +++ b/lib/tcp/client/tcpclient.go @@ -22,6 +22,7 @@ When a local client connects to the I2P tunnel's destination, the traffic flows: import ( "context" + "fmt" "net" "strconv" @@ -57,7 +58,11 @@ func (t *TCPClient) recordError(err error) { // Get the tunnel's I2P address func (t *TCPClient) Address() string { - return t.Garlic.StreamSession.Addr().Base32() + // Return the target I2P address for client tunnels + if t.I2PAddr != nil { + return t.I2PAddr.Base32() + } + return "" } // Get the tunnel's error message @@ -132,3 +137,54 @@ func (t *TCPClient) Target() string { func (t *TCPClient) Type() string { return t.TunnelConfig.Type } + +// Get the tunnel's ID +func (t *TCPClient) ID() string { + return i2ptunnel.Clean(t.Name()) +} + +// Get the tunnel's options +func (t *TCPClient) Options() map[string]string { + // Return basic configuration options as a map + options := make(map[string]string) + options["name"] = t.TunnelConfig.Name + options["type"] = t.TunnelConfig.Type + options["interface"] = t.TunnelConfig.Interface + options["port"] = strconv.Itoa(t.TunnelConfig.Port) + if t.I2PAddr != nil { + options["target"] = t.I2PAddr.Base32() + } + return options +} + +// Set the tunnel's options +func (t *TCPClient) SetOptions(opts map[string]string) error { + // Apply configuration options from the map + if name, ok := opts["name"]; ok { + t.TunnelConfig.Name = name + } + if iface, ok := opts["interface"]; ok { + t.TunnelConfig.Interface = iface + } + if portStr, ok := opts["port"]; ok { + if port, err := strconv.Atoi(portStr); err == nil { + t.TunnelConfig.Port = port + } else { + return fmt.Errorf("invalid port value: %s", portStr) + } + } + if target, ok := opts["target"]; ok { + addr, err := i2pkeys.Lookup(target) + if err != nil { + return fmt.Errorf("invalid target address: %w", err) + } + t.I2PAddr = addr + } + return nil +} + +// Load the tunnel config from file +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) +} diff --git a/lib/tcp/server/tcpserver.go b/lib/tcp/server/tcpserver.go index 9b9c9ea..656cb08 100644 --- a/lib/tcp/server/tcpserver.go +++ b/lib/tcp/server/tcpserver.go @@ -15,6 +15,7 @@ When an I2P peer connects to the tunnel's destination, the traffic flows: import ( "context" + "fmt" "net" "strconv" @@ -52,8 +53,14 @@ func (t *TCPServer) recordError(err error) { // Get the tunnel's I2P address func (t *TCPServer) Address() string { - return t.Garlic.StreamListener.Addr().String() - //B32() + // For server tunnels, return the service address if available + if t.Garlic != nil { + // Use service keys to identify the tunnel's I2P address + if t.Garlic.ServiceKeys != nil { + return t.Garlic.ServiceKeys.Addr().Base32() + } + } + return "" } // Get the tunnel's error message @@ -129,3 +136,63 @@ func (t *TCPServer) Target() string { func (t *TCPServer) Type() string { return t.TunnelConfig.Type } + +// Get the tunnel's ID +func (t *TCPServer) ID() string { + return i2ptunnel.Clean(t.Name()) +} + +// Get the tunnel's options +func (t *TCPServer) Options() map[string]string { + // Return basic configuration options as a map + options := make(map[string]string) + options["name"] = t.TunnelConfig.Name + options["type"] = t.TunnelConfig.Type + options["interface"] = t.TunnelConfig.Interface + options["port"] = strconv.Itoa(t.TunnelConfig.Port) + options["maxconns"] = strconv.Itoa(t.LimitedConfig.MaxConns) + options["ratelimit"] = strconv.FormatFloat(t.LimitedConfig.RateLimit, 'f', -1, 64) + if t.Addr != nil { + options["target"] = t.Addr.String() + } + return options +} + +// Set the tunnel's options +func (t *TCPServer) SetOptions(opts map[string]string) error { + // Apply configuration options from the map + if name, ok := opts["name"]; ok { + t.TunnelConfig.Name = name + } + if iface, ok := opts["interface"]; ok { + t.TunnelConfig.Interface = iface + } + if portStr, ok := opts["port"]; ok { + if port, err := strconv.Atoi(portStr); err == nil { + t.TunnelConfig.Port = port + } else { + return fmt.Errorf("invalid port value: %s", portStr) + } + } + if maxconnsStr, ok := opts["maxconns"]; ok { + if maxconns, err := strconv.Atoi(maxconnsStr); err == nil { + t.LimitedConfig.MaxConns = maxconns + } else { + return fmt.Errorf("invalid maxconns value: %s", maxconnsStr) + } + } + if ratelimitStr, ok := opts["ratelimit"]; ok { + if ratelimit, err := strconv.ParseFloat(ratelimitStr, 64); err == nil { + t.LimitedConfig.RateLimit = ratelimit + } else { + return fmt.Errorf("invalid ratelimit value: %s", ratelimitStr) + } + } + return nil +} + +// Load the tunnel config from file +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) +} diff --git a/lib/tcp/tcp_test.go b/lib/tcp/tcp_test.go index 951bac1..23972f1 100644 --- a/lib/tcp/tcp_test.go +++ b/lib/tcp/tcp_test.go @@ -1,7 +1,6 @@ package tcp import ( - "io" "net" "strconv" "testing" @@ -36,15 +35,19 @@ func RandomUDPPort() int { } func TestTCPTunnel(t *testing.T) { + t.Log("Starting TCP tunnel test") + // Generate test keys - //keys, err := i2pkeys.LoadKeys("i2pkeys/test-server.i2p.private") + t.Log("Loading test keys...") _, err := i2pkeys.LoadKeys("i2pkeys/test-server.i2p.private") if err != nil { t.Fatalf("Failed to load test keys: %v", err) } sport := RandomTCPPort() + t.Logf("Selected server port: %d", sport) // Setup server config + t.Log("Setting up server configuration...") serverConfig := i2pconv.TunnelConfig{ Name: "test-server", Type: "tcpserver", @@ -53,11 +56,13 @@ func TestTCPTunnel(t *testing.T) { } // Create and start server + t.Log("Creating TCP server...") srv, err := server.NewTCPServer(serverConfig, "127.0.0.1:7656") if err != nil { t.Fatalf("Failed to create server: %v", err) } + t.Log("Starting TCP server...") go func() { if err := srv.Start(); err != nil { t.Errorf("Server error: %v", err) @@ -65,10 +70,13 @@ func TestTCPTunnel(t *testing.T) { }() defer srv.Stop() cport := RandomTCPPort() - // Wait for server startup + t.Logf("Selected client port: %d", cport) + + t.Log("Waiting for server startup...") time.Sleep(2 * time.Second) // Setup client config + t.Log("Setting up client configuration...") clientConfig := i2pconv.TunnelConfig{ Name: "test-client", Type: "tcpclient", @@ -78,11 +86,13 @@ func TestTCPTunnel(t *testing.T) { } // Create and start client + t.Log("Creating TCP client...") cli, err := client.NewTCPClient(clientConfig, "127.0.0.1:7656") if err != nil { t.Fatalf("Failed to create client: %v", err) } + t.Log("Starting TCP client...") go func() { if err := cli.Start(); err != nil { t.Errorf("Client error: %v", err) @@ -90,29 +100,39 @@ func TestTCPTunnel(t *testing.T) { }() defer cli.Stop() - // Wait for client startup + t.Log("Waiting for client startup...") time.Sleep(2 * time.Second) // Test data transfer testData := []byte("Hello I2P!") + t.Log("Attempting to establish connection...") conn, err := net.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(cport))) if err != nil { t.Fatalf("Failed to connect: %v", err) } defer conn.Close() + t.Log("Connection established successfully") + t.Logf("Writing test data: %q", testData) n, err := conn.Write(testData) if err != nil || n != len(testData) { t.Fatalf("Failed to write test data: %v", err) } + t.Logf("Successfully wrote %d bytes", n) + t.Log("Reading response...") buf := make([]byte, len(testData)) - n, err = io.ReadFull(conn, buf) - if err != nil || n != len(testData) { + n, err = conn.Read(buf) + if err != nil { t.Fatalf("Failed to read test data: %v", err) } + if n != len(testData) { + t.Fatalf("Read wrong number of bytes: got %d, want %d", n, len(testData)) + } + t.Logf("Successfully read %d bytes", n) if string(buf) != string(testData) { t.Errorf("Data mismatch: got %q, want %q", buf, testData) } + t.Log("Test completed successfully") }