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 (
"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)
}

View File

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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")
}