From 2e01fa565d6d935151330f60a5da904123b82943 Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Mon, 30 Dec 2024 02:26:51 -0600 Subject: [PATCH] update 0.2.0 --- README.md | 1 + To-Do | 22 ++--- cmd/client/client.go | 177 ++++++++++++++++++++++-------------- cmd/reticulum/main.go | 5 +- internal/config/config.go | 8 +- pkg/common/config.go | 22 +++-- pkg/common/interface.go | 60 ++++++++++++ pkg/common/interfaces.go | 57 ------------ pkg/common/types.go | 6 -- pkg/identity/identity.go | 8 ++ pkg/interfaces/interface.go | 68 ++++++++++++-- pkg/interfaces/tcp.go | 97 +++++++++++++------- pkg/interfaces/udp.go | 49 ++++++---- pkg/transport/transport.go | 146 +++++++++++++++++++++++++++++ 14 files changed, 509 insertions(+), 217 deletions(-) create mode 100644 pkg/common/interface.go delete mode 100644 pkg/common/interfaces.go diff --git a/README.md b/README.md index 2a6a778..215b321 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,4 @@ Reticulum Network Stack in Go. +See [To-Do](./To-Do.md) for the current state of the project. \ No newline at end of file diff --git a/To-Do b/To-Do index 813d2dc..fe66428 100644 --- a/To-Do +++ b/To-Do @@ -66,7 +66,7 @@ Basic Features [✓] Basic UDP transport [✓] TCP transport [ ] Interface discovery - [ ] Connection management + [✓] Connection management [✓] Packet framing [✓] Transport integration @@ -89,14 +89,14 @@ Basic Features Next Immediate Tasks: 1. [✓] Fix import cycles by creating common package -2. [ ] Implement Interface discovery -3. [ ] Implement Connection management -4. [ ] Test network layer integration end-to-end -5. [ ] Add error handling for network failures -6. [ ] Implement interface auto-configuration -7. [ ] Complete NetworkInterface implementation -8. [ ] Add comprehensive interface tests -9. [ ] Implement connection retry logic +2. [✓] Complete NetworkInterface implementation +3. [✓] Add comprehensive interface tests +4. [✓] Implement connection retry logic +5. [✓] Add client reconnection handling +6. [ ] Implement Interface discovery +7. [ ] Test network layer integration end-to-end +8. [ ] Add error handling for network failures +9. [ ] Implement interface auto-configuration 10. [ ] Add metrics collection for interfaces -11. [ ] Add client reconnection handling -12. [ ] Implement client-side path caching \ No newline at end of file +11. [ ] Implement client-side path caching +12. [ ] Add support for additional transport types \ No newline at end of file diff --git a/cmd/client/client.go b/cmd/client/client.go index aa05f97..08e9508 100644 --- a/cmd/client/client.go +++ b/cmd/client/client.go @@ -14,14 +14,104 @@ import ( "github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/identity" "github.com/Sudo-Ivan/reticulum-go/pkg/transport" - "github.com/Sudo-Ivan/reticulum-go/pkg/destination" + "github.com/Sudo-Ivan/reticulum-go/pkg/interfaces" ) var ( configPath = flag.String("config", "", "Path to config file") targetHash = flag.String("target", "", "Target destination hash") + generateIdentity = flag.Bool("generate-identity", false, "Generate a new identity and print its hash") ) +type Client struct { + config *common.ReticulumConfig + transport *transport.Transport + interfaces []common.NetworkInterface +} + +func NewClient(cfg *common.ReticulumConfig) (*Client, error) { + if cfg == nil { + var err error + cfg, err = config.InitConfig() + if err != nil { + return nil, fmt.Errorf("failed to initialize config: %v", err) + } + } + + t, err := transport.NewTransport(cfg) + if err != nil { + return nil, fmt.Errorf("failed to initialize transport: %v", err) + } + + return &Client{ + config: cfg, + transport: t, + interfaces: make([]common.NetworkInterface, 0), + }, nil +} + +func (c *Client) Start() error { + for _, ifaceConfig := range c.config.Interfaces { + var iface common.NetworkInterface + + switch ifaceConfig.Type { + case "tcp": + client, err := interfaces.NewTCPClient( + ifaceConfig.Name, + ifaceConfig.Address, + ifaceConfig.Port, + ifaceConfig.KISSFraming, + ifaceConfig.I2PTunneled, + ) + if err != nil { + log.Printf("Failed to create TCP interface %s: %v", ifaceConfig.Name, err) + continue + } + + // Convert callback type to match interface + callback := func(data []byte, iface interface{}) { + c.transport.HandlePacket(data, iface) + } + client.SetPacketCallback(common.PacketCallback(callback)) + iface = client + + case "udp": + addr := fmt.Sprintf("%s:%d", ifaceConfig.Address, ifaceConfig.Port) + udp, err := interfaces.NewUDPInterface( + ifaceConfig.Name, + addr, + "", // No target address for client initially + ) + if err != nil { + log.Printf("Failed to create UDP interface %s: %v", ifaceConfig.Name, err) + continue + } + + // Convert callback type to match interface + callback := func(data []byte, iface interface{}) { + c.transport.HandlePacket(data, iface) + } + udp.SetPacketCallback(common.PacketCallback(callback)) + iface = udp + + default: + log.Printf("Unknown interface type: %s", ifaceConfig.Type) + continue + } + + c.interfaces = append(c.interfaces, iface) + } + + return nil +} + +func (c *Client) Stop() { + for _, iface := range c.interfaces { + iface.Detach() + } + c.transport.Close() +} + func main() { flag.Parse() @@ -30,85 +120,32 @@ func main() { if *configPath == "" { cfg, err = config.InitConfig() - if err != nil { - log.Fatalf("Failed to initialize config: %v", err) - } } else { cfg, err = config.LoadConfig(*configPath) - if err != nil { - log.Fatalf("Failed to load config: %v", err) - } } - - // Enable transport by default for client - cfg.EnableTransport = true - - // Initialize transport - transport, err := transport.NewTransport(cfg) if err != nil { - log.Fatalf("Failed to initialize transport: %v", err) + log.Fatalf("Failed to load config: %v", err) } - defer transport.Close() - // If target specified, establish connection - if *targetHash != "" { - destHash, err := identity.HashFromHex(*targetHash) + if *generateIdentity { + id, err := identity.New() if err != nil { - log.Fatalf("Invalid destination hash: %v", err) + log.Fatalf("Failed to generate identity: %v", err) } - - // Request path if needed - if !transport.HasPath(destHash) { - fmt.Println("Requesting path to destination...") - if err := transport.RequestPath(destHash, "", nil, true); err != nil { - log.Fatalf("Failed to request path: %v", err) - } - } - - // Get destination identity - destIdentity, err := identity.Recall(destHash) - if err != nil { - log.Fatalf("Failed to recall identity: %v", err) - } - - // Create destination - dest, err := destination.New( - destIdentity, - destination.OUT, - destination.SINGLE, - "client", - "direct", - ) - if err != nil { - log.Fatalf("Failed to create destination: %v", err) - } - - // Enable and configure ratchets - dest.SetRetainedRatchets(destination.RATCHET_COUNT) - dest.SetRatchetInterval(destination.RATCHET_INTERVAL) - dest.EnforceRatchets() - - // Create link - link := transport.NewLink(dest.Hash(), func() { - fmt.Println("Link established") - }, func() { - fmt.Println("Link closed") - }) - - defer link.Teardown() - - // Set packet callback - link.SetPacketCallback(func(data []byte) { - fmt.Printf("Received: %s\n", string(data)) - }) - - // Start interactive loop - go interactiveLoop(link) - } else { - fmt.Println("No target specified. Use -target to connect to a destination") + fmt.Printf("Identity hash: %s\n", id.Hex()) return } + client, err := NewClient(cfg) + if err != nil { + log.Fatalf("Failed to create client: %v", err) + } + defer client.Stop() + + if err := client.Start(); err != nil { + log.Fatalf("Failed to start client: %v", err) + } + // Wait for interrupt sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) @@ -134,4 +171,4 @@ func interactiveLoop(link *transport.Link) { fmt.Printf("Failed to send: %v\n", err) } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/cmd/reticulum/main.go b/cmd/reticulum/main.go index b00e5d9..b5b13bb 100644 --- a/cmd/reticulum/main.go +++ b/cmd/reticulum/main.go @@ -6,17 +6,18 @@ import ( "os/signal" "syscall" + "github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/internal/config" "github.com/Sudo-Ivan/reticulum-go/pkg/transport" "github.com/Sudo-Ivan/reticulum-go/pkg/interfaces" ) type Reticulum struct { - config *config.ReticulumConfig + config *common.ReticulumConfig transport *transport.Transport } -func NewReticulum(cfg *config.ReticulumConfig) (*Reticulum, error) { +func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) { if cfg == nil { cfg = config.DefaultConfig() } diff --git a/internal/config/config.go b/internal/config/config.go index 8cd05bb..a8a4f6f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -22,7 +22,7 @@ func DefaultConfig() *common.ReticulumConfig { InstanceControlPort: DefaultInstanceControlPort, PanicOnInterfaceErr: false, LogLevel: DefaultLogLevel, - Interfaces: make(map[string]common.InterfaceConfig), + Interfaces: make(map[string]*common.InterfaceConfig), } } @@ -75,17 +75,17 @@ func CreateDefaultConfig(path string) error { cfg := DefaultConfig() // Add default interface - cfg.Interfaces["Default Interface"] = common.InterfaceConfig{ + cfg.Interfaces["Default Interface"] = &common.InterfaceConfig{ Type: "AutoInterface", Enabled: false, } // Add default quad4net interface - cfg.Interfaces["quad4net tcp"] = common.InterfaceConfig{ + cfg.Interfaces["quad4net tcp"] = &common.InterfaceConfig{ Type: "TCPClientInterface", Enabled: true, TargetHost: "rns.quad4.io", - TargetPort: 4242, + TargetPort: 4242, } data, err := toml.Marshal(cfg) diff --git a/pkg/common/config.go b/pkg/common/config.go index 7f96f88..6c35843 100644 --- a/pkg/common/config.go +++ b/pkg/common/config.go @@ -10,20 +10,26 @@ type ConfigProvider interface { // InterfaceConfig represents interface configuration type InterfaceConfig struct { Type string `toml:"type"` + Name string `toml:"name"` Enabled bool `toml:"enabled"` TargetHost string `toml:"target_host,omitempty"` TargetPort int `toml:"target_port,omitempty"` Interface string `toml:"interface,omitempty"` + Address string `toml:"address,omitempty"` + Port int `toml:"port,omitempty"` + KISSFraming bool `toml:"kiss_framing,omitempty"` + I2PTunneled bool `toml:"i2p_tunneled,omitempty"` + PreferIPv6 bool `toml:"prefer_ipv6,omitempty"` } // ReticulumConfig represents the main configuration structure type ReticulumConfig struct { - EnableTransport bool `toml:"enable_transport"` - ShareInstance bool `toml:"share_instance"` - SharedInstancePort int `toml:"shared_instance_port"` - InstanceControlPort int `toml:"instance_control_port"` - PanicOnInterfaceErr bool `toml:"panic_on_interface_error"` - LogLevel int `toml:"loglevel"` - ConfigPath string `toml:"-"` - Interfaces map[string]InterfaceConfig + ConfigPath string `toml:"-"` + EnableTransport bool `toml:"enable_transport"` + ShareInstance bool `toml:"share_instance"` + SharedInstancePort int `toml:"shared_instance_port"` + InstanceControlPort int `toml:"instance_control_port"` + PanicOnInterfaceErr bool `toml:"panic_on_interface_error"` + LogLevel int `toml:"loglevel"` + Interfaces map[string]*InterfaceConfig `toml:"interfaces"` } \ No newline at end of file diff --git a/pkg/common/interface.go b/pkg/common/interface.go new file mode 100644 index 0000000..1f7ccf6 --- /dev/null +++ b/pkg/common/interface.go @@ -0,0 +1,60 @@ +package common + +import ( + "net" + "sync" + "time" +) + +type InterfaceMode byte +type InterfaceType byte + +type PacketCallback func([]byte, interface{}) + +// NetworkInterface combines both low-level and high-level interface requirements +type NetworkInterface interface { + // Low-level network operations + Start() error + Stop() error + Send(data []byte, address string) error + Receive() ([]byte, string, error) + GetType() InterfaceType + GetMode() InterfaceMode + GetMTU() int + + // High-level packet operations + ProcessIncoming([]byte) + ProcessOutgoing([]byte) error + SendPathRequest([]byte) error + SendLinkPacket([]byte, []byte, time.Time) error + Detach() + SetPacketCallback(PacketCallback) + + // Additional required fields + GetName() string + GetConn() net.Conn + IsEnabled() bool +} + +// BaseInterface provides common implementation +type BaseInterface struct { + Name string + Mode InterfaceMode + Type InterfaceType + + Online bool + Detached bool + + IN bool + OUT bool + + MTU int + Bitrate int64 + + TxBytes uint64 + RxBytes uint64 + + Mutex sync.RWMutex + Owner interface{} + PacketCallback PacketCallback +} \ No newline at end of file diff --git a/pkg/common/interfaces.go b/pkg/common/interfaces.go deleted file mode 100644 index 9b769fb..0000000 --- a/pkg/common/interfaces.go +++ /dev/null @@ -1,57 +0,0 @@ -package common - -import ( - "net" - "sync" - "time" -) - -// NetworkInterface combines both low-level and high-level interface requirements -type NetworkInterface interface { - // Low-level network operations - Start() error - Stop() error - Send(data []byte, address string) error - Receive() ([]byte, string, error) - GetType() InterfaceType - GetMode() InterfaceMode - GetMTU() int - - // High-level packet operations - ProcessIncoming([]byte) - ProcessOutgoing([]byte) error - SendPathRequest([]byte) error - SendLinkPacket([]byte, []byte, time.Time) error - Detach() - SetPacketCallback(PacketCallback) - - // Additional required fields - GetName() string - GetConn() net.Conn - IsEnabled() bool -} - -type PacketCallback func([]byte, interface{}) - -// BaseInterface provides common implementation -type BaseInterface struct { - Name string - Mode InterfaceMode - Type InterfaceType - - Online bool - Detached bool - - IN bool - OUT bool - - MTU int - Bitrate int64 - - TxBytes uint64 - RxBytes uint64 - - mutex sync.RWMutex - owner interface{} - packetCallback PacketCallback -} \ No newline at end of file diff --git a/pkg/common/types.go b/pkg/common/types.go index 4dc38d3..1755b1c 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -4,10 +4,6 @@ import ( "time" ) -// Interface related types -type InterfaceMode byte -type InterfaceType byte - // Transport related types type TransportMode byte type PathStatus byte @@ -15,8 +11,6 @@ type PathStatus byte // Common structs type Path struct { Interface NetworkInterface - Address string - Status PathStatus LastSeen time.Time NextHop []byte Hops uint8 diff --git a/pkg/identity/identity.go b/pkg/identity/identity.go index 54c17e6..63637cf 100644 --- a/pkg/identity/identity.go +++ b/pkg/identity/identity.go @@ -427,4 +427,12 @@ func (i *Identity) DecryptSymmetric(ciphertext []byte) ([]byte, error) { } return plaintext, nil +} + +func (i *Identity) Hash() []byte { + return TruncatedHash(i.publicKey) +} + +func (i *Identity) Hex() string { + return hex.EncodeToString(i.Hash()) } \ No newline at end of file diff --git a/pkg/interfaces/interface.go b/pkg/interfaces/interface.go index a61a987..b479340 100644 --- a/pkg/interfaces/interface.go +++ b/pkg/interfaces/interface.go @@ -2,32 +2,40 @@ package interfaces import ( "fmt" - "sync" "time" "encoding/binary" + "net" "github.com/Sudo-Ivan/reticulum-go/pkg/common" ) const ( BITRATE_MINIMUM = 5 // Minimum required bitrate in bits/sec + MODE_FULL = 0x01 ) -// BaseInterface embeds common.BaseInterface and implements common.Interface +type Interface interface { + common.NetworkInterface + Send(data []byte, target string) error + Detach() + IsEnabled() bool + GetName() string +} + type BaseInterface struct { common.BaseInterface } func (i *BaseInterface) SetPacketCallback(callback common.PacketCallback) { - i.mutex.Lock() - defer i.mutex.Unlock() - i.packetCallback = callback + i.Mutex.Lock() + defer i.Mutex.Unlock() + i.PacketCallback = callback } func (i *BaseInterface) ProcessIncoming(data []byte) { - i.mutex.RLock() - callback := i.packetCallback - i.mutex.RUnlock() + i.Mutex.RLock() + callback := i.PacketCallback + i.Mutex.RUnlock() if callback != nil { callback(data, i) @@ -42,8 +50,8 @@ func (i *BaseInterface) ProcessOutgoing(data []byte) error { } func (i *BaseInterface) Detach() { - i.mutex.Lock() - defer i.mutex.Unlock() + i.Mutex.Lock() + defer i.Mutex.Unlock() i.Detached = true i.Online = false } @@ -76,4 +84,44 @@ func (i *BaseInterface) SendLinkPacket(dest []byte, data []byte, timestamp time. frame = append(frame, data...) return i.ProcessOutgoing(frame) +} + +func (i *BaseInterface) Start() error { + return nil +} + +func (i *BaseInterface) Stop() error { + return nil +} + +func (i *BaseInterface) Send(data []byte, address string) error { + return i.ProcessOutgoing(data) +} + +func (i *BaseInterface) Receive() ([]byte, string, error) { + return nil, "", nil +} + +func (i *BaseInterface) GetType() common.InterfaceType { + return i.Type +} + +func (i *BaseInterface) GetMode() common.InterfaceMode { + return i.Mode +} + +func (i *BaseInterface) GetMTU() int { + return i.MTU +} + +func (i *BaseInterface) GetName() string { + return i.Name +} + +func (i *BaseInterface) GetConn() net.Conn { + return nil +} + +func (i *BaseInterface) IsEnabled() bool { + return i.Online && !i.Detached } \ No newline at end of file diff --git a/pkg/interfaces/tcp.go b/pkg/interfaces/tcp.go index 00ad7be..45a1c85 100644 --- a/pkg/interfaces/tcp.go +++ b/pkg/interfaces/tcp.go @@ -5,6 +5,7 @@ import ( "net" "sync" "time" + "github.com/Sudo-Ivan/reticulum-go/pkg/common" ) const ( @@ -26,7 +27,7 @@ const ( ) type TCPClientInterface struct { - Interface + BaseInterface conn net.Conn targetAddr string targetPort int @@ -39,15 +40,18 @@ type TCPClientInterface struct { maxReconnectTries int packetBuffer []byte packetType byte + packetCallback func([]byte, interface{}) } func NewTCPClient(name string, targetAddr string, targetPort int, kissFraming bool, i2pTunneled bool) (*TCPClientInterface, error) { tc := &TCPClientInterface{ - Interface: Interface{ - Name: name, - Mode: MODE_FULL, - MTU: 1064, - Bitrate: 10000000, // 10Mbps estimate + BaseInterface: BaseInterface{ + BaseInterface: common.BaseInterface{ + Name: name, + Mode: common.IF_MODE_FULL, + MTU: 1064, + Bitrate: 10000000, // 10Mbps estimate + }, }, targetAddr: targetAddr, targetPort: targetPort, @@ -195,12 +199,12 @@ func (tc *TCPClientInterface) handlePacket(data []byte) { switch packetType { case 0x01: // Path request - tc.Interface.ProcessIncoming(payload) + tc.BaseInterface.ProcessIncoming(payload) case 0x02: // Link packet if len(payload) < 40 { // minimum size for link packet return } - tc.Interface.ProcessIncoming(payload) + tc.BaseInterface.ProcessIncoming(payload) default: // Unknown packet type return @@ -229,7 +233,7 @@ func (tc *TCPClientInterface) ProcessOutgoing(data []byte) error { return fmt.Errorf("write failed: %v", err) } - tc.Interface.ProcessOutgoing(data) + tc.BaseInterface.ProcessOutgoing(data) return nil } @@ -269,29 +273,45 @@ func escapeKISS(data []byte) []byte { return escaped } -type TCPServerInterface struct { - Interface - server net.Listener - bindAddr string - bindPort int - i2pTunneled bool - preferIPv6 bool - spawned []*TCPClientInterface - spawnedMutex sync.RWMutex +func (tc *TCPClientInterface) SetPacketCallback(cb func([]byte, interface{})) { + tc.packetCallback = cb } -func NewTCPServer(name string, bindAddr string, bindPort int, i2pTunneled bool, preferIPv6 bool) (*TCPServerInterface, error) { +func (tc *TCPClientInterface) IsEnabled() bool { + return tc.Online +} + +func (tc *TCPClientInterface) GetName() string { + return tc.Name +} + +type TCPServerInterface struct { + BaseInterface + server net.Listener + bindAddr string + bindPort int + preferIPv6 bool + i2pTunneled bool + spawned []*TCPClientInterface + spawnedMutex sync.RWMutex + packetCallback func([]byte, interface{}) +} + +func NewTCPServer(name string, bindAddr string, bindPort int, preferIPv6 bool, i2pTunneled bool) (*TCPServerInterface, error) { ts := &TCPServerInterface{ - Interface: Interface{ - Name: name, - Mode: MODE_FULL, - MTU: 1064, - Bitrate: 10000000, // 10Mbps estimate + BaseInterface: BaseInterface{ + BaseInterface: common.BaseInterface{ + Name: name, + Mode: common.IF_MODE_FULL, + Type: common.IF_TYPE_TCP, + MTU: 1064, + Bitrate: 10000000, + }, }, bindAddr: bindAddr, bindPort: bindPort, - i2pTunneled: i2pTunneled, preferIPv6: preferIPv6, + i2pTunneled: i2pTunneled, spawned: make([]*TCPClientInterface, 0), } @@ -328,7 +348,6 @@ func (ts *TCPServerInterface) acceptLoop() { conn, err := ts.server.Accept() if err != nil { if !ts.Detached { - // Log error and continue accepting continue } return @@ -336,10 +355,14 @@ func (ts *TCPServerInterface) acceptLoop() { // Create new client interface for this connection client := &TCPClientInterface{ - Interface: Interface{ - Name: fmt.Sprintf("Client-%s-%s", ts.Name, conn.RemoteAddr()), - Mode: ts.Mode, - MTU: ts.MTU, + BaseInterface: BaseInterface{ + BaseInterface: common.BaseInterface{ + Name: fmt.Sprintf("Client-%s-%s", ts.Name, conn.RemoteAddr()), + Mode: ts.Mode, + Type: common.IF_TYPE_TCP, + MTU: ts.MTU, + Bitrate: ts.Bitrate, + }, }, conn: conn, i2pTunneled: ts.i2pTunneled, @@ -367,7 +390,7 @@ func (ts *TCPServerInterface) acceptLoop() { } func (ts *TCPServerInterface) Detach() { - ts.Interface.Detach() + ts.BaseInterface.Detach() if ts.server != nil { ts.server.Close() @@ -405,4 +428,16 @@ func (ts *TCPServerInterface) String() string { } } return fmt.Sprintf("TCPServerInterface[%s/%s:%d]", ts.Name, addr, ts.bindPort) +} + +func (ts *TCPServerInterface) SetPacketCallback(cb func([]byte, interface{})) { + ts.packetCallback = cb +} + +func (ts *TCPServerInterface) IsEnabled() bool { + return ts.Online +} + +func (ts *TCPServerInterface) GetName() string { + return ts.Name } \ No newline at end of file diff --git a/pkg/interfaces/udp.go b/pkg/interfaces/udp.go index 09347da..f3a0faf 100644 --- a/pkg/interfaces/udp.go +++ b/pkg/interfaces/udp.go @@ -3,11 +3,11 @@ package interfaces import ( "fmt" "net" - "sync" + "github.com/Sudo-Ivan/reticulum-go/pkg/common" ) type UDPInterface struct { - Interface + BaseInterface conn *net.UDPConn listenAddr *net.UDPAddr targetAddr *net.UDPAddr @@ -16,11 +16,14 @@ type UDPInterface struct { func NewUDPInterface(name string, listenAddr string, targetAddr string) (*UDPInterface, error) { ui := &UDPInterface{ - Interface: Interface{ - Name: name, - Mode: MODE_FULL, - MTU: 1500, - Bitrate: 100000000, // 100Mbps estimate for UDP + BaseInterface: BaseInterface{ + BaseInterface: common.BaseInterface{ + Name: name, + Mode: common.IF_MODE_FULL, + Type: common.IF_TYPE_UDP, + MTU: 1500, + Bitrate: 100000000, // 100Mbps estimate + }, }, readBuffer: make([]byte, 65535), } @@ -39,7 +42,7 @@ func NewUDPInterface(name string, listenAddr string, targetAddr string) (*UDPInt return nil, fmt.Errorf("invalid target address: %v", err) } ui.targetAddr = taddr - ui.OUT = true + ui.BaseInterface.OUT = true } // Create UDP connection @@ -48,8 +51,8 @@ func NewUDPInterface(name string, listenAddr string, targetAddr string) (*UDPInt return nil, fmt.Errorf("failed to listen on UDP: %v", err) } ui.conn = conn - ui.IN = true - ui.Online = true + ui.BaseInterface.IN = true + ui.BaseInterface.Online = true // Start read loop go ui.readLoop() @@ -59,16 +62,22 @@ func NewUDPInterface(name string, listenAddr string, targetAddr string) (*UDPInt func (ui *UDPInterface) readLoop() { for { - if !ui.Online { + if !ui.BaseInterface.Online { return } - n, addr, err := ui.conn.ReadFromUDP(ui.readBuffer) + n, remoteAddr, err := ui.conn.ReadFromUDP(ui.readBuffer) if err != nil { - if !ui.Detached { - // Log error + if !ui.BaseInterface.Detached { + continue } - continue + return + } + + // If no target address is set, use the first sender's address + if ui.targetAddr == nil { + ui.targetAddr = remoteAddr + ui.BaseInterface.OUT = true } // Copy received data @@ -81,7 +90,7 @@ func (ui *UDPInterface) readLoop() { } func (ui *UDPInterface) ProcessOutgoing(data []byte) error { - if !ui.Online || ui.targetAddr == nil { + if !ui.BaseInterface.Online || ui.targetAddr == nil { return fmt.Errorf("interface offline or no target address configured") } @@ -90,13 +99,17 @@ func (ui *UDPInterface) ProcessOutgoing(data []byte) error { return fmt.Errorf("UDP write failed: %v", err) } - ui.Interface.ProcessOutgoing(data) + ui.BaseInterface.ProcessOutgoing(data) return nil } func (ui *UDPInterface) Detach() { - ui.Interface.Detach() + ui.BaseInterface.Detach() if ui.conn != nil { ui.conn.Close() } +} + +func (ui *UDPInterface) GetConn() net.Conn { + return ui.conn } \ No newline at end of file diff --git a/pkg/transport/transport.go b/pkg/transport/transport.go index ece6a8d..259b0bf 100644 --- a/pkg/transport/transport.go +++ b/pkg/transport/transport.go @@ -456,4 +456,150 @@ func SendAnnounce(packet []byte) error { } return lastErr +} + +func (t *Transport) HandlePacket(data []byte, iface interface{}) { + if len(data) < 1 { + return + } + + packetType := data[0] + switch packetType { + case 0x01: // Path Request + t.handlePathRequest(data[1:], iface) + case 0x02: // Link Packet + t.handleLinkPacket(data[1:], iface) + case 0x03: // Path Response + t.handlePathResponse(data[1:], iface) + case 0x04: // Announce + t.handleAnnouncePacket(data[1:], iface) + } +} + +func (t *Transport) handlePathRequest(data []byte, iface interface{}) { + if len(data) < 33 { // 32 bytes hash + 1 byte TTL minimum + return + } + + destHash := data[:32] + ttl := data[32] + var tag []byte + recursive := false + + if len(data) > 33 { + tag = data[33:len(data)-1] + recursive = data[len(data)-1] == 0x01 + } + + // Check if we have a path to the destination + if t.HasPath(destHash) { + // Create and send path response + hops := t.HopsTo(destHash) + nextHop := t.NextHop(destHash) + + response := make([]byte, 0, 64) + response = append(response, 0x03) // Path Response type + response = append(response, destHash...) + response = append(response, byte(hops)) + response = append(response, nextHop...) + if len(tag) > 0 { + response = append(response, tag...) + } + + if i, ok := iface.(common.NetworkInterface); ok { + i.Send(response, "") + } + } else if recursive && ttl > 0 { + // Forward path request to other interfaces + newData := make([]byte, len(data)) + copy(newData, data) + newData[32] = ttl - 1 // Decrease TTL + + for name, otherIface := range t.interfaces { + if name != iface.(common.NetworkInterface).GetName() && otherIface.IsEnabled() { + otherIface.Send(newData, "") + } + } + } +} + +func (t *Transport) handleLinkPacket(data []byte, iface interface{}) { + if len(data) < 40 { // 32 bytes dest + 8 bytes timestamp minimum + return + } + + dest := data[:32] + timestamp := binary.BigEndian.Uint64(data[32:40]) + payload := data[40:] + + // Check if we're the destination + if t.HasPath(dest) { + nextHop := t.NextHop(dest) + nextIface := t.NextHopInterface(dest) + + if iface, ok := t.interfaces[nextIface]; ok { + iface.Send(data, string(nextHop)) + } + } + + // Update timing information + if link := t.findLink(dest); link != nil { + link.lastInbound = time.Unix(int64(timestamp), 0) + if link.packetCb != nil { + link.packetCb(payload) + } + } +} + +func (t *Transport) handlePathResponse(data []byte, iface interface{}) { + if len(data) < 33 { // 32 bytes hash + 1 byte hops minimum + return + } + + destHash := data[:32] + hops := data[32] + var nextHop []byte + + if len(data) > 33 { + nextHop = data[33:] + } + + // Update path information + if i, ok := iface.(common.NetworkInterface); ok { + t.UpdatePath(destHash, nextHop, i.GetName(), hops) + } +} + +func (t *Transport) handleAnnouncePacket(data []byte, iface interface{}) { + if len(data) < 32 { // 32 bytes minimum for hash + return + } + + destHash := data[:32] + var identity, appData []byte + + if len(data) > 32 { + splitPoint := 32 + for i := 32; i < len(data); i++ { + if data[i] == 0x00 { + splitPoint = i + break + } + } + identity = data[32:splitPoint] + if splitPoint < len(data)-1 { + appData = data[splitPoint+1:] + } + } + + t.HandleAnnounce(destHash, identity, appData) +} + +func (t *Transport) findLink(dest []byte) *Link { + t.mutex.RLock() + defer t.mutex.RUnlock() + + // This is a simplified version - you might want to maintain a map of active links + // in the Transport struct for better performance + return nil } \ No newline at end of file