update 0.2.0
This commit is contained in:
@@ -2,3 +2,4 @@
|
||||
|
||||
Reticulum Network Stack in Go.
|
||||
|
||||
See [To-Do](./To-Do.md) for the current state of the project.
|
||||
22
To-Do
22
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
|
||||
11. [ ] Implement client-side path caching
|
||||
12. [ ] Add support for additional transport types
|
||||
@@ -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 *generateIdentity {
|
||||
id, err := identity.New()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize transport: %v", err)
|
||||
log.Fatalf("Failed to generate identity: %v", err)
|
||||
}
|
||||
defer transport.Close()
|
||||
|
||||
// If target specified, establish connection
|
||||
if *targetHash != "" {
|
||||
destHash, err := identity.HashFromHex(*targetHash)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid destination hash: %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 <hash> 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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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,13 +75,13 @@ 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",
|
||||
|
||||
@@ -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 {
|
||||
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"`
|
||||
ConfigPath string `toml:"-"`
|
||||
Interfaces map[string]InterfaceConfig
|
||||
Interfaces map[string]*InterfaceConfig `toml:"interfaces"`
|
||||
}
|
||||
60
pkg/common/interface.go
Normal file
60
pkg/common/interface.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -428,3 +428,11 @@ 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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -77,3 +85,43 @@ func (i *BaseInterface) SendLinkPacket(dest []byte, data []byte, timestamp time.
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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,16 +40,19 @@ 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{
|
||||
BaseInterface: BaseInterface{
|
||||
BaseInterface: common.BaseInterface{
|
||||
Name: name,
|
||||
Mode: MODE_FULL,
|
||||
Mode: common.IF_MODE_FULL,
|
||||
MTU: 1064,
|
||||
Bitrate: 10000000, // 10Mbps estimate
|
||||
},
|
||||
},
|
||||
targetAddr: targetAddr,
|
||||
targetPort: targetPort,
|
||||
kissFraming: kissFraming,
|
||||
@@ -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
|
||||
}
|
||||
|
||||
func (tc *TCPClientInterface) SetPacketCallback(cb func([]byte, interface{})) {
|
||||
tc.packetCallback = cb
|
||||
}
|
||||
|
||||
func (tc *TCPClientInterface) IsEnabled() bool {
|
||||
return tc.Online
|
||||
}
|
||||
|
||||
func (tc *TCPClientInterface) GetName() string {
|
||||
return tc.Name
|
||||
}
|
||||
|
||||
type TCPServerInterface struct {
|
||||
Interface
|
||||
BaseInterface
|
||||
server net.Listener
|
||||
bindAddr string
|
||||
bindPort int
|
||||
i2pTunneled bool
|
||||
preferIPv6 bool
|
||||
i2pTunneled bool
|
||||
spawned []*TCPClientInterface
|
||||
spawnedMutex sync.RWMutex
|
||||
packetCallback func([]byte, interface{})
|
||||
}
|
||||
|
||||
func NewTCPServer(name string, bindAddr string, bindPort int, i2pTunneled bool, preferIPv6 bool) (*TCPServerInterface, error) {
|
||||
func NewTCPServer(name string, bindAddr string, bindPort int, preferIPv6 bool, i2pTunneled bool) (*TCPServerInterface, error) {
|
||||
ts := &TCPServerInterface{
|
||||
Interface: Interface{
|
||||
BaseInterface: BaseInterface{
|
||||
BaseInterface: common.BaseInterface{
|
||||
Name: name,
|
||||
Mode: MODE_FULL,
|
||||
Mode: common.IF_MODE_FULL,
|
||||
Type: common.IF_TYPE_TCP,
|
||||
MTU: 1064,
|
||||
Bitrate: 10000000, // 10Mbps estimate
|
||||
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{
|
||||
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()
|
||||
@@ -406,3 +429,15 @@ 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
|
||||
}
|
||||
@@ -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{
|
||||
BaseInterface: BaseInterface{
|
||||
BaseInterface: common.BaseInterface{
|
||||
Name: name,
|
||||
Mode: MODE_FULL,
|
||||
Mode: common.IF_MODE_FULL,
|
||||
Type: common.IF_TYPE_UDP,
|
||||
MTU: 1500,
|
||||
Bitrate: 100000000, // 100Mbps estimate for UDP
|
||||
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,17 +62,23 @@ 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
|
||||
}
|
||||
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
|
||||
data := make([]byte, n)
|
||||
@@ -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
|
||||
}
|
||||
@@ -457,3 +457,149 @@ 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
|
||||
}
|
||||
Reference in New Issue
Block a user