Add missing methods to implement complete interfaces

This commit is contained in:
eyedeekay
2025-10-16 23:23:22 -04:00
parent ad1919c51f
commit 5eb5646d1b
6 changed files with 265 additions and 12 deletions

View File

@@ -24,6 +24,7 @@ Key features:
import ( import (
"context" "context"
"fmt"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
@@ -69,7 +70,11 @@ func (h *HTTPClient) recordError(err error) {
// Get the tunnel's I2P address // Get the tunnel's I2P address
func (h *HTTPClient) Address() string { 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 // Get the tunnel's error message
@@ -151,3 +156,44 @@ func (h *HTTPClient) Target() string {
func (h *HTTPClient) Type() string { func (h *HTTPClient) Type() string {
return h.TunnelConfig.Type 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)
}

View File

@@ -28,7 +28,6 @@ func NewHTTPClient(config i2pconv.TunnelConfig, samAddr string) (*HTTPClient, er
I2PTunnelStatus: i2ptunnel.I2PTunnelStatusStopped, I2PTunnelStatus: i2ptunnel.I2PTunnelStatusStopped,
done: make(chan struct{}), done: make(chan struct{}),
} }
return h, nil return h, nil
} }

View File

@@ -24,6 +24,7 @@ Key features:
import ( import (
"context" "context"
"fmt"
"net" "net"
"strconv" "strconv"
@@ -64,7 +65,11 @@ func (h *HTTPServer) recordError(err error) {
// Get the tunnel's I2P address // Get the tunnel's I2P address
func (h *HTTPServer) Address() string { 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 // Get the tunnel's error message
@@ -141,3 +146,63 @@ func (h *HTTPServer) Target() string {
func (h *HTTPServer) Type() string { func (h *HTTPServer) Type() string {
return h.TunnelConfig.Type 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)
}

View File

@@ -22,6 +22,7 @@ When a local client connects to the I2P tunnel's destination, the traffic flows:
import ( import (
"context" "context"
"fmt"
"net" "net"
"strconv" "strconv"
@@ -57,7 +58,11 @@ func (t *TCPClient) recordError(err error) {
// Get the tunnel's I2P address // Get the tunnel's I2P address
func (t *TCPClient) Address() string { 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 // Get the tunnel's error message
@@ -132,3 +137,54 @@ func (t *TCPClient) Target() string {
func (t *TCPClient) Type() string { func (t *TCPClient) Type() string {
return t.TunnelConfig.Type 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)
}

View File

@@ -15,6 +15,7 @@ When an I2P peer connects to the tunnel's destination, the traffic flows:
import ( import (
"context" "context"
"fmt"
"net" "net"
"strconv" "strconv"
@@ -52,8 +53,14 @@ func (t *TCPServer) recordError(err error) {
// Get the tunnel's I2P address // Get the tunnel's I2P address
func (t *TCPServer) Address() string { func (t *TCPServer) Address() string {
return t.Garlic.StreamListener.Addr().String() // For server tunnels, return the service address if available
//B32() 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 // Get the tunnel's error message
@@ -129,3 +136,63 @@ func (t *TCPServer) Target() string {
func (t *TCPServer) Type() string { func (t *TCPServer) Type() string {
return t.TunnelConfig.Type 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)
}

View File

@@ -1,7 +1,6 @@
package tcp package tcp
import ( import (
"io"
"net" "net"
"strconv" "strconv"
"testing" "testing"
@@ -36,15 +35,19 @@ func RandomUDPPort() int {
} }
func TestTCPTunnel(t *testing.T) { func TestTCPTunnel(t *testing.T) {
t.Log("Starting TCP tunnel test")
// Generate test keys // 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") _, err := i2pkeys.LoadKeys("i2pkeys/test-server.i2p.private")
if err != nil { if err != nil {
t.Fatalf("Failed to load test keys: %v", err) t.Fatalf("Failed to load test keys: %v", err)
} }
sport := RandomTCPPort() sport := RandomTCPPort()
t.Logf("Selected server port: %d", sport)
// Setup server config // Setup server config
t.Log("Setting up server configuration...")
serverConfig := i2pconv.TunnelConfig{ serverConfig := i2pconv.TunnelConfig{
Name: "test-server", Name: "test-server",
Type: "tcpserver", Type: "tcpserver",
@@ -53,11 +56,13 @@ func TestTCPTunnel(t *testing.T) {
} }
// Create and start server // Create and start server
t.Log("Creating TCP server...")
srv, err := server.NewTCPServer(serverConfig, "127.0.0.1:7656") srv, err := server.NewTCPServer(serverConfig, "127.0.0.1:7656")
if err != nil { if err != nil {
t.Fatalf("Failed to create server: %v", err) t.Fatalf("Failed to create server: %v", err)
} }
t.Log("Starting TCP server...")
go func() { go func() {
if err := srv.Start(); err != nil { if err := srv.Start(); err != nil {
t.Errorf("Server error: %v", err) t.Errorf("Server error: %v", err)
@@ -65,10 +70,13 @@ func TestTCPTunnel(t *testing.T) {
}() }()
defer srv.Stop() defer srv.Stop()
cport := RandomTCPPort() cport := RandomTCPPort()
// Wait for server startup t.Logf("Selected client port: %d", cport)
t.Log("Waiting for server startup...")
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
// Setup client config // Setup client config
t.Log("Setting up client configuration...")
clientConfig := i2pconv.TunnelConfig{ clientConfig := i2pconv.TunnelConfig{
Name: "test-client", Name: "test-client",
Type: "tcpclient", Type: "tcpclient",
@@ -78,11 +86,13 @@ func TestTCPTunnel(t *testing.T) {
} }
// Create and start client // Create and start client
t.Log("Creating TCP client...")
cli, err := client.NewTCPClient(clientConfig, "127.0.0.1:7656") cli, err := client.NewTCPClient(clientConfig, "127.0.0.1:7656")
if err != nil { if err != nil {
t.Fatalf("Failed to create client: %v", err) t.Fatalf("Failed to create client: %v", err)
} }
t.Log("Starting TCP client...")
go func() { go func() {
if err := cli.Start(); err != nil { if err := cli.Start(); err != nil {
t.Errorf("Client error: %v", err) t.Errorf("Client error: %v", err)
@@ -90,29 +100,39 @@ func TestTCPTunnel(t *testing.T) {
}() }()
defer cli.Stop() defer cli.Stop()
// Wait for client startup t.Log("Waiting for client startup...")
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
// Test data transfer // Test data transfer
testData := []byte("Hello I2P!") testData := []byte("Hello I2P!")
t.Log("Attempting to establish connection...")
conn, err := net.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(cport))) conn, err := net.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(cport)))
if err != nil { if err != nil {
t.Fatalf("Failed to connect: %v", err) t.Fatalf("Failed to connect: %v", err)
} }
defer conn.Close() defer conn.Close()
t.Log("Connection established successfully")
t.Logf("Writing test data: %q", testData)
n, err := conn.Write(testData) n, err := conn.Write(testData)
if err != nil || n != len(testData) { if err != nil || n != len(testData) {
t.Fatalf("Failed to write test data: %v", err) 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)) buf := make([]byte, len(testData))
n, err = io.ReadFull(conn, buf) n, err = conn.Read(buf)
if err != nil || n != len(testData) { if err != nil {
t.Fatalf("Failed to read test data: %v", err) 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) { if string(buf) != string(testData) {
t.Errorf("Data mismatch: got %q, want %q", buf, testData) t.Errorf("Data mismatch: got %q, want %q", buf, testData)
} }
t.Log("Test completed successfully")
} }