This commit is contained in:
Sudo-Ivan
2024-12-30 03:50:52 -06:00
parent 0f5f5cbb13
commit decbd8f29a
16 changed files with 1046 additions and 970 deletions

42
To-Do
View File

@@ -17,17 +17,25 @@ Core Components
[✓] Identity creation [✓] Identity creation
[✓] Key pair generation [✓] Key pair generation
[✓] Identity storage/recall [✓] Identity storage/recall
[✓] Public key handling
[✓] Signature verification
[✓] Hash functions
[✓] Cryptographic Primitives
[✓] Ed25519
[✓] Curve25519
[✓] AES-GCM
[✓] SHA-256
[✓] HKDF
[✓] Secure random number generation
[✓] HMAC
[✓] Packet Handling [✓] Packet Handling
[✓] Packet creation [✓] Packet creation
[✓] Packet validation [✓] Packet validation
[✓] Basic proof system [✓] Basic proof system
[✓] Packet encryption
[✓] Crypto Implementation [✓] Signature verification
[✓] Basic encryption
[✓] Key exchange
[✓] Hash functions
[✓] Ratchet implementation
[✓] Transport Layer [✓] Transport Layer
[✓] Path management [✓] Path management
@@ -52,6 +60,8 @@ Core Components
[✓] Encryption/Decryption [✓] Encryption/Decryption
[✓] Identity verification [✓] Identity verification
[✓] Request/Response handling [✓] Request/Response handling
[✓] Session key management
[✓] Link state tracking
[✓] Resource System [✓] Resource System
[✓] Resource creation [✓] Resource creation
@@ -61,6 +71,16 @@ Core Components
[✓] Segmentation [✓] Segmentation
[✓] Cleanup routines [✓] Cleanup routines
Security Features
[✓] Cryptographic Implementation
[✓] Secure key generation
[✓] Key exchange protocols
[✓] Message encryption
[✓] Signature schemes
[✓] Hash functions
[ ] Perfect forward secrecy
[ ] Post-quantum resistance considerations
Basic Features Basic Features
[✓] Network Interface [✓] Network Interface
[✓] Basic UDP transport [✓] Basic UDP transport
@@ -74,6 +94,7 @@ Basic Features
[✓] Announce creation [✓] Announce creation
[✓] Announce propagation [✓] Announce propagation
[✓] Path requests [✓] Path requests
[✓] Announce validation
[✓] Resource Management [✓] Resource Management
[✓] Resource tracking [✓] Resource tracking
@@ -86,6 +107,7 @@ Basic Features
[✓] Interactive mode [✓] Interactive mode
[✓] Link establishment [✓] Link establishment
[✓] Message sending/receiving [✓] Message sending/receiving
[✓] Identity management
Next Immediate Tasks: Next Immediate Tasks:
1. [✓] Fix import cycles by creating common package 1. [✓] Fix import cycles by creating common package
@@ -99,4 +121,10 @@ Next Immediate Tasks:
9. [ ] Implement interface auto-configuration 9. [ ] Implement interface auto-configuration
10. [ ] Add metrics collection for interfaces 10. [ ] Add metrics collection for interfaces
11. [ ] Implement client-side path caching 11. [ ] Implement client-side path caching
12. [ ] Add support for additional transport types 12. [ ] Add support for additional transport types
13. [ ] Implement perfect forward secrecy
14. [ ] Add post-quantum cryptographic primitives
15. [ ] Implement secure key rotation
16. [ ] Add support for encrypted storage of identities
17. [ ] Implement secure memory handling
18. [ ] Add support for hardware security modules

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"bufio" "bufio"
"encoding/binary"
"flag" "flag"
"fmt" "fmt"
"log" "log"
@@ -10,19 +11,20 @@ import (
"strings" "strings"
"syscall" "syscall"
"time" "time"
"encoding/binary"
"github.com/Sudo-Ivan/reticulum-go/internal/config" "github.com/Sudo-Ivan/reticulum-go/internal/config"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/destination"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity" "github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport"
"github.com/Sudo-Ivan/reticulum-go/pkg/interfaces" "github.com/Sudo-Ivan/reticulum-go/pkg/interfaces"
"github.com/Sudo-Ivan/reticulum-go/pkg/announce" "github.com/Sudo-Ivan/reticulum-go/pkg/link"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport"
) )
var ( var (
configPath = flag.String("config", "", "Path to config file") configPath = flag.String("config", "", "Path to config file")
targetHash = flag.String("target", "", "Target destination hash") targetHash = flag.String("target", "", "Target destination hash")
generateIdentity = flag.Bool("generate-identity", false, "Generate a new identity and print its hash") generateIdentity = flag.Bool("generate-identity", false, "Generate a new identity and print its hash")
) )
@@ -61,6 +63,7 @@ func NewClient(cfg *common.ReticulumConfig) (*Client, error) {
} }
func (c *Client) Start() error { func (c *Client) Start() error {
// Initialize interfaces
for _, ifaceConfig := range c.config.Interfaces { for _, ifaceConfig := range c.config.Interfaces {
var iface common.NetworkInterface var iface common.NetworkInterface
@@ -74,14 +77,8 @@ func (c *Client) Start() error {
ifaceConfig.I2PTunneled, ifaceConfig.I2PTunneled,
) )
if err != nil { if err != nil {
log.Printf("Failed to create TCP interface %s: %v", ifaceConfig.Name, err) return fmt.Errorf("failed to create TCP interface %s: %v", ifaceConfig.Name, err)
continue
} }
callback := common.PacketCallback(func(data []byte, iface interface{}) {
c.handlePacket(data, iface)
})
client.SetPacketCallback(callback)
iface = client iface = client
case "UDPInterface": case "UDPInterface":
@@ -92,47 +89,23 @@ func (c *Client) Start() error {
"", // No target address for client initially "", // No target address for client initially
) )
if err != nil { if err != nil {
log.Printf("Failed to create UDP interface %s: %v", ifaceConfig.Name, err) return fmt.Errorf("failed to create UDP interface %s: %v", ifaceConfig.Name, err)
continue
} }
callback := common.PacketCallback(func(data []byte, iface interface{}) {
c.handlePacket(data, iface)
})
udp.SetPacketCallback(callback)
iface = udp iface = udp
case "AutoInterface":
log.Printf("AutoInterface type not yet implemented")
continue
default: default:
log.Printf("Unknown interface type: %s", ifaceConfig.Type) return fmt.Errorf("unsupported interface type: %s", ifaceConfig.Type)
continue
} }
if iface != nil { c.interfaces = append(c.interfaces, iface)
c.interfaces = append(c.interfaces, iface) log.Printf("Created interface %s", iface.GetName())
}
} }
// Start periodic announce after interfaces are set up // Start periodic announces
go func() { go func() {
// Initial delay to allow interfaces to connect
time.Sleep(5 * time.Second)
// Send first announce
c.sendAnnounce()
// Set up periodic announces
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for { for {
select { c.sendAnnounce()
case <-ticker.C: time.Sleep(30 * time.Second)
c.sendAnnounce()
}
} }
}() }()
@@ -140,7 +113,7 @@ func (c *Client) Start() error {
return nil return nil
} }
func (c *Client) handlePacket(data []byte, iface interface{}) { func (c *Client) handlePacket(data []byte, p *packet.Packet) {
if len(data) < 1 { if len(data) < 1 {
return return
} }
@@ -150,7 +123,7 @@ func (c *Client) handlePacket(data []byte, iface interface{}) {
case 0x04: // Announce packet case 0x04: // Announce packet
c.handleAnnounce(data[1:]) c.handleAnnounce(data[1:])
default: default:
c.transport.HandlePacket(data, iface) c.transport.HandlePacket(data, p)
} }
} }
@@ -174,42 +147,52 @@ func (c *Client) handleAnnounce(data []byte) {
// Extract app data if present // Extract app data if present
dataLen := binary.BigEndian.Uint16(data[42:44]) dataLen := binary.BigEndian.Uint16(data[42:44])
if len(data) >= 44+int(dataLen) { if len(data) >= 44+int(dataLen) {
appData := data[44:44+dataLen] appData := data[44 : 44+dataLen]
log.Printf(" App Data: %s", string(appData)) log.Printf(" App Data: %s", string(appData))
} }
} }
} }
func (c *Client) sendAnnounce() { func (c *Client) sendAnnounce() {
// Create announce packet following RNS protocol announceData := make([]byte, 0)
announceData := make([]byte, 0, 128)
announceData = append(announceData, 0x04) // Announce packet type // Packet type (1 byte)
announceData = append(announceData, c.identity.Hash()...) // Identity hash (32 bytes) announceData = append(announceData, 0x04)
// Add timestamp (8 bytes, big-endian) // Destination hash (16 bytes)
timestamp := time.Now().Unix() destHash := identity.TruncatedHash(c.identity.GetPublicKey())
announceData = append(announceData, destHash...)
// Timestamp (8 bytes)
timeBytes := make([]byte, 8) timeBytes := make([]byte, 8)
binary.BigEndian.PutUint64(timeBytes, uint64(timestamp)) binary.BigEndian.PutUint64(timeBytes, uint64(time.Now().Unix()))
announceData = append(announceData, timeBytes...) announceData = append(announceData, timeBytes...)
// Add hops (1 byte) // Hops (1 byte)
announceData = append(announceData, 0x00) // Initial hop count announceData = append(announceData, 0x00)
// Add flags (1 byte) // Flags (1 byte)
announceData = append(announceData, byte(announce.ANNOUNCE_IDENTITY)) // Using identity announce type announceData = append(announceData, 0x00)
// Add app data with length prefix // Public key
announceData = append(announceData, c.identity.GetPublicKey()...)
// App data with length prefix
appData := []byte("RNS.Go.Client") appData := []byte("RNS.Go.Client")
lenBytes := make([]byte, 2) lenBytes := make([]byte, 2)
binary.BigEndian.PutUint16(lenBytes, uint16(len(appData))) binary.BigEndian.PutUint16(lenBytes, uint16(len(appData)))
announceData = append(announceData, lenBytes...) announceData = append(announceData, lenBytes...)
announceData = append(announceData, appData...) announceData = append(announceData, appData...)
// Sign the announce packet // Sign the announce data
signature := c.identity.Sign(announceData) signData := append(destHash, c.identity.GetPublicKey()...)
signData = append(signData, appData...)
signature := c.identity.Sign(signData)
announceData = append(announceData, signature...) announceData = append(announceData, signature...)
log.Printf("Sending announce packet, length: %d bytes", len(announceData)) log.Printf("Sending announce for identity: %s", c.identity.Hex())
log.Printf("Announce packet length: %d bytes", len(announceData))
log.Printf("Announce packet hex: %x", announceData)
for _, iface := range c.interfaces { for _, iface := range c.interfaces {
if err := iface.Send(announceData, ""); err != nil { if err := iface.Send(announceData, ""); err != nil {
@@ -227,6 +210,54 @@ func (c *Client) Stop() {
c.transport.Close() c.transport.Close()
} }
func (c *Client) Connect(destHash []byte) error {
// Recall server identity
serverIdentity, err := identity.Recall(destHash)
if err != nil {
return err
}
// Create destination
dest, err := destination.New(
serverIdentity,
destination.OUT,
destination.SINGLE,
"example_utilities",
"identifyexample",
)
if err != nil {
return err
}
// Create link with all required parameters
link := link.NewLink(
dest,
c.transport, // Add the transport instance
c.handleLinkEstablished,
c.handleLinkClosed,
)
// Set callbacks
link.SetPacketCallback(c.handlePacket)
return nil
}
func (c *Client) handleLinkEstablished(l *link.Link) {
log.Printf("Link established with server, identifying...")
// Identify to server
if err := l.Identify(c.identity); err != nil {
log.Printf("Failed to identify: %v", err)
l.Teardown()
return
}
}
func (c *Client) handleLinkClosed(l *link.Link) {
log.Printf("Link closed")
}
func main() { func main() {
flag.Parse() flag.Parse()
@@ -286,4 +317,4 @@ func interactiveLoop(link *transport.Link) {
fmt.Printf("Failed to send: %v\n", err) fmt.Printf("Failed to send: %v\n", err)
} }
} }
} }

View File

@@ -60,8 +60,9 @@ func (r *Reticulum) Start() error {
ifaceConfig.Name, ifaceConfig.Name,
ifaceConfig.Address, ifaceConfig.Address,
ifaceConfig.Port, ifaceConfig.Port,
ifaceConfig.PreferIPv6, ifaceConfig.KISSFraming,
ifaceConfig.I2PTunneled, ifaceConfig.I2PTunneled,
ifaceConfig.PreferIPv6,
) )
if err != nil { if err != nil {
log.Printf("Failed to create TCP server interface %s: %v", ifaceConfig.Name, err) log.Printf("Failed to create TCP server interface %s: %v", ifaceConfig.Name, err)

View File

@@ -4,22 +4,22 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/pelletier/go-toml"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/pelletier/go-toml"
) )
const ( const (
DefaultSharedInstancePort = 37428 DefaultSharedInstancePort = 37428
DefaultInstanceControlPort = 37429 DefaultInstanceControlPort = 37429
DefaultLogLevel = 4 DefaultLogLevel = 4
) )
func DefaultConfig() *common.ReticulumConfig { func DefaultConfig() *common.ReticulumConfig {
return &common.ReticulumConfig{ return &common.ReticulumConfig{
EnableTransport: false, EnableTransport: false,
ShareInstance: true, ShareInstance: true,
SharedInstancePort: DefaultSharedInstancePort, SharedInstancePort: DefaultSharedInstancePort,
InstanceControlPort: DefaultInstanceControlPort, InstanceControlPort: DefaultInstanceControlPort,
PanicOnInterfaceErr: false, PanicOnInterfaceErr: false,
LogLevel: DefaultLogLevel, LogLevel: DefaultLogLevel,
Interfaces: make(map[string]*common.InterfaceConfig), Interfaces: make(map[string]*common.InterfaceConfig),
@@ -85,7 +85,15 @@ func CreateDefaultConfig(path string) error {
Type: "TCPClientInterface", Type: "TCPClientInterface",
Enabled: true, Enabled: true,
TargetHost: "rns.quad4.io", TargetHost: "rns.quad4.io",
TargetPort: 4242, TargetPort: 4242,
}
// Add default UDP interface
cfg.Interfaces["local udp"] = &common.InterfaceConfig{
Type: "UDPInterface",
Enabled: true,
Address: "0.0.0.0",
Port: 37428, // Default RNS port
} }
data, err := toml.Marshal(cfg) data, err := toml.Marshal(cfg)
@@ -118,4 +126,4 @@ func InitConfig() (*common.ReticulumConfig, error) {
// Load config // Load config
return LoadConfig(configPath) return LoadConfig(configPath)
} }

View File

@@ -6,11 +6,6 @@ import (
"time" "time"
) )
type InterfaceMode byte
type InterfaceType byte
type PacketCallback = func([]byte, interface{})
// NetworkInterface combines both low-level and high-level interface requirements // NetworkInterface combines both low-level and high-level interface requirements
type NetworkInterface interface { type NetworkInterface interface {
// Low-level network operations // Low-level network operations
@@ -21,7 +16,7 @@ type NetworkInterface interface {
GetType() InterfaceType GetType() InterfaceType
GetMode() InterfaceMode GetMode() InterfaceMode
GetMTU() int GetMTU() int
// High-level packet operations // High-level packet operations
ProcessIncoming([]byte) ProcessIncoming([]byte)
ProcessOutgoing([]byte) error ProcessOutgoing([]byte) error
@@ -29,7 +24,7 @@ type NetworkInterface interface {
SendLinkPacket([]byte, []byte, time.Time) error SendLinkPacket([]byte, []byte, time.Time) error
Detach() Detach()
SetPacketCallback(PacketCallback) SetPacketCallback(PacketCallback)
// Additional required fields // Additional required fields
GetName() string GetName() string
GetConn() net.Conn GetConn() net.Conn
@@ -38,23 +33,23 @@ type NetworkInterface interface {
// BaseInterface provides common implementation // BaseInterface provides common implementation
type BaseInterface struct { type BaseInterface struct {
Name string Name string
Mode InterfaceMode Mode InterfaceMode
Type InterfaceType Type InterfaceType
Online bool Online bool
Detached bool Detached bool
IN bool IN bool
OUT bool OUT bool
MTU int MTU int
Bitrate int64 Bitrate int64
TxBytes uint64 TxBytes uint64
RxBytes uint64 RxBytes uint64
Mutex sync.RWMutex Mutex sync.RWMutex
Owner interface{} Owner interface{}
PacketCallback PacketCallback PacketCallback PacketCallback
} }

View File

@@ -10,15 +10,15 @@ type PathStatus byte
// Common structs // Common structs
type Path struct { type Path struct {
Interface NetworkInterface Interface NetworkInterface
LastSeen time.Time LastSeen time.Time
NextHop []byte NextHop []byte
Hops uint8 Hops uint8
LastUpdated time.Time LastUpdated time.Time
} }
// Common callbacks // Common callbacks
type ProofRequestedCallback func(interface{}) bool type ProofRequestedCallback func([]byte, []byte)
type LinkEstablishedCallback func(interface{}) type LinkEstablishedCallback func(interface{})
// Request handler // Request handler
@@ -27,4 +27,10 @@ type RequestHandler struct {
ResponseGenerator func(path string, data []byte, requestID []byte, linkID []byte, remoteIdentity interface{}, requestedAt int64) []byte ResponseGenerator func(path string, data []byte, requestID []byte, linkID []byte, remoteIdentity interface{}, requestedAt int64) []byte
AllowMode byte AllowMode byte
AllowedList [][]byte AllowedList [][]byte
} }
type InterfaceMode byte
type InterfaceType byte
// PacketCallback defines the function signature for packet handling
type PacketCallback func([]byte, interface{})

View File

@@ -314,9 +314,15 @@ func (d *Destination) Encrypt(plaintext []byte) ([]byte, error) {
switch d.destType { switch d.destType {
case SINGLE: case SINGLE:
return d.identity.Encrypt(plaintext, nil) // For single destination, we need the recipient's public key
recipientKey := d.identity.GetPublicKey()
return d.identity.Encrypt(plaintext, recipientKey)
case GROUP: case GROUP:
return d.identity.EncryptSymmetric(plaintext) key := d.identity.GetCurrentRatchetKey()
if key == nil {
return nil, errors.New("no ratchet key available")
}
return d.identity.EncryptSymmetric(plaintext, key)
default: default:
return nil, errors.New("unsupported destination type for encryption") return nil, errors.New("unsupported destination type for encryption")
} }
@@ -333,9 +339,13 @@ func (d *Destination) Decrypt(ciphertext []byte) ([]byte, error) {
switch d.destType { switch d.destType {
case SINGLE: case SINGLE:
return d.identity.Decrypt(ciphertext, nil) return d.identity.Decrypt(ciphertext)
case GROUP: case GROUP:
return d.identity.DecryptSymmetric(ciphertext) key := d.identity.GetCurrentRatchetKey()
if key == nil {
return nil, errors.New("no ratchet key available")
}
return d.identity.DecryptSymmetric(ciphertext, key)
default: default:
return nil, errors.New("unsupported destination type for decryption") return nil, errors.New("unsupported destination type for decryption")
} }
@@ -347,4 +357,15 @@ func (d *Destination) Sign(data []byte) ([]byte, error) {
} }
signature := d.identity.Sign(data) signature := d.identity.Sign(data)
return signature, nil return signature, nil
}
func (d *Destination) GetPublicKey() []byte {
if d.identity == nil {
return nil
}
return d.identity.GetPublicKey()
}
func (d *Destination) GetIdentity() *identity.Identity {
return d.identity
} }

View File

@@ -4,21 +4,17 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/ed25519" "crypto/ed25519"
"crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"errors" "errors"
"fmt"
"io" "io"
"os"
"sync" "sync"
"time" "time"
"encoding/hex"
"fmt"
"path/filepath"
"bytes"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
) )
const ( const (
@@ -26,16 +22,74 @@ const (
RatchetSize = 256 RatchetSize = 256
RatchetExpiry = 2592000 // 30 days in seconds RatchetExpiry = 2592000 // 30 days in seconds
TruncatedHashLen = 128 // bits TruncatedHashLen = 128 // bits
NameHashLength = 80 // bits
TokenOverhead = 16 // bytes
AESBlockSize = 16 // bytes
HashLength = 256 // bits
SigLength = KeySize // bits
HMACKeySize = 32 // bytes
) )
type Identity struct { type Identity struct {
privateKey []byte privateKey []byte
publicKey []byte publicKey []byte
signingKey ed25519.PrivateKey signingKey ed25519.PrivateKey
verificationKey ed25519.PublicKey verificationKey ed25519.PublicKey
ratchets map[string][]byte ratchets map[string][]byte
ratchetExpiry map[string]int64 ratchetExpiry map[string]int64
mutex sync.RWMutex mutex sync.RWMutex
appData []byte
}
var (
knownDestinations = make(map[string][]interface{})
knownRatchets = make(map[string][]byte)
ratchetPersistLock sync.Mutex
)
func encryptAESGCM(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
return ciphertext, nil
}
func decryptAESGCM(key, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, err
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
} }
func New() (*Identity, error) { func New() (*Identity, error) {
@@ -45,75 +99,48 @@ func New() (*Identity, error) {
} }
// Generate X25519 key pair // Generate X25519 key pair
var err error
i.privateKey = make([]byte, curve25519.ScalarSize) i.privateKey = make([]byte, curve25519.ScalarSize)
if _, err = io.ReadFull(rand.Reader, i.privateKey); err != nil { if _, err := io.ReadFull(rand.Reader, i.privateKey); err != nil {
return nil, err return nil, err
} }
// Generate public key var err error
i.publicKey, err = curve25519.X25519(i.privateKey, curve25519.Basepoint) i.publicKey, err = curve25519.X25519(i.privateKey, curve25519.Basepoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Generate Ed25519 signing keypair // Generate Ed25519 signing keypair
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil { if err != nil {
return nil, err return nil, err
} }
i.signingKey = privateKey i.signingKey = privKey
i.verificationKey = publicKey i.verificationKey = pubKey
return i, nil return i, nil
} }
func FromBytes(data []byte) (*Identity, error) { func (i *Identity) GetPublicKey() []byte {
if len(data) != KeySize/8 { combined := make([]byte, KeySize/8)
return nil, errors.New("invalid key size") copy(combined[:KeySize/16], i.publicKey)
} copy(combined[KeySize/16:], i.verificationKey)
return combined
i := &Identity{
ratchets: make(map[string][]byte),
ratchetExpiry: make(map[string]int64),
}
// First 32 bytes are X25519 private key
i.privateKey = data[:32]
var err error
i.publicKey, err = curve25519.X25519(i.privateKey, curve25519.Basepoint)
if err != nil {
return nil, err
}
// Next 32 bytes are Ed25519 private key
i.signingKey = ed25519.PrivateKey(data[32:])
i.verificationKey = i.signingKey.Public().(ed25519.PublicKey)
return i, nil
} }
func (i *Identity) ToBytes() []byte { func (i *Identity) GetPrivateKey() []byte {
data := make([]byte, KeySize/8) return append(i.privateKey, i.signingKey...)
copy(data[:32], i.privateKey)
copy(data[32:], i.signingKey)
return data
} }
func (i *Identity) SaveToFile(path string) error { func (i *Identity) Sign(data []byte) []byte {
return os.WriteFile(path, i.ToBytes(), 0600) return ed25519.Sign(i.signingKey, data)
} }
func LoadFromFile(path string) (*Identity, error) { func (i *Identity) Verify(data []byte, signature []byte) bool {
data, err := os.ReadFile(path) return ed25519.Verify(i.verificationKey, data, signature)
if err != nil {
return nil, err
}
return FromBytes(data)
} }
func (i *Identity) Encrypt(plaintext []byte, ratchets []byte) ([]byte, error) { func (i *Identity) Encrypt(plaintext []byte, ratchet []byte) ([]byte, error) {
// Generate ephemeral key pair // Generate ephemeral key pair
ephemeralPrivate := make([]byte, curve25519.ScalarSize) ephemeralPrivate := make([]byte, curve25519.ScalarSize)
if _, err := io.ReadFull(rand.Reader, ephemeralPrivate); err != nil { if _, err := io.ReadFull(rand.Reader, ephemeralPrivate); err != nil {
@@ -125,336 +152,192 @@ func (i *Identity) Encrypt(plaintext []byte, ratchets []byte) ([]byte, error) {
return nil, err return nil, err
} }
// Perform key exchange var targetKey []byte
sharedSecret, err := curve25519.X25519(ephemeralPrivate, i.publicKey) if ratchet != nil {
targetKey = ratchet
} else {
targetKey = i.publicKey
}
sharedSecret, err := curve25519.X25519(ephemeralPrivate, targetKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Generate AES key from shared secret using HKDF // Generate encryption key using HKDF
hash := sha256.New hkdf := hkdf.New(sha256.New, sharedSecret, i.Hash(), nil)
hkdf := hkdf.New(hash, sharedSecret, nil, nil) key := make([]byte, 32)
aesKey := make([]byte, 32) if _, err := io.ReadFull(hkdf, key); err != nil {
if _, err := io.ReadFull(hkdf, aesKey); err != nil {
return nil, err return nil, err
} }
// Create AES-GCM cipher // Encrypt using AES-GCM
block, err := aes.NewCipher(aesKey) ciphertext, err := encryptAESGCM(key, plaintext)
if err != nil { if err != nil {
return nil, err return nil, err
} }
gcm, err := cipher.NewGCM(block) return append(ephemeralPublic, ciphertext...), nil
if err != nil {
return nil, err
}
// Generate nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// Encrypt plaintext
ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
// Combine ephemeral public key, nonce and ciphertext
result := make([]byte, len(ephemeralPublic)+len(nonce)+len(ciphertext))
copy(result, ephemeralPublic)
copy(result[len(ephemeralPublic):], nonce)
copy(result[len(ephemeralPublic)+len(nonce):], ciphertext)
return result, nil
} }
func (i *Identity) Decrypt(ciphertext []byte, ratchets []byte) ([]byte, error) { func (i *Identity) Hash() []byte {
if len(ciphertext) <= curve25519.ScalarSize { h := sha256.New()
return nil, errors.New("invalid ciphertext") h.Write(i.GetPublicKey())
} return h.Sum(nil)
// Extract ephemeral public key
ephemeralPublic := ciphertext[:curve25519.ScalarSize]
// Perform key exchange
sharedSecret, err := curve25519.X25519(i.privateKey, ephemeralPublic)
if err != nil {
return nil, err
}
// Generate AES key from shared secret using HKDF
hash := sha256.New
hkdf := hkdf.New(hash, sharedSecret, nil, nil)
aesKey := make([]byte, 32)
if _, err := io.ReadFull(hkdf, aesKey); err != nil {
return nil, err
}
// Create AES-GCM cipher
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Extract nonce and encrypted data
nonceSize := gcm.NonceSize()
if len(ciphertext) < curve25519.ScalarSize+nonceSize {
return nil, errors.New("invalid ciphertext")
}
nonce := ciphertext[curve25519.ScalarSize : curve25519.ScalarSize+nonceSize]
encryptedData := ciphertext[curve25519.ScalarSize+nonceSize:]
// Decrypt data
plaintext, err := gcm.Open(nil, nonce, encryptedData, nil)
if err != nil {
return nil, err
}
return plaintext, nil
} }
func (i *Identity) Sign(message []byte) []byte {
return ed25519.Sign(i.signingKey, message)
}
func (i *Identity) Verify(message, signature []byte) bool {
return ed25519.Verify(i.verificationKey, message, signature)
}
func (i *Identity) GetPublicKey() []byte {
return append([]byte{}, i.publicKey...)
}
func (i *Identity) AddRatchet(ratchetID string, ratchetKey []byte) {
i.mutex.Lock()
defer i.mutex.Unlock()
i.ratchets[ratchetID] = ratchetKey
i.ratchetExpiry[ratchetID] = time.Now().Unix() + RatchetExpiry
}
func (i *Identity) GetRatchet(ratchetID string) []byte {
i.mutex.RLock()
defer i.mutex.RUnlock()
if expiry, ok := i.ratchetExpiry[ratchetID]; ok {
if time.Now().Unix() < expiry {
return i.ratchets[ratchetID]
}
// Cleanup expired ratchet
delete(i.ratchets, ratchetID)
delete(i.ratchetExpiry, ratchetID)
}
return nil
}
// Helper functions
func TruncatedHash(data []byte) []byte { func TruncatedHash(data []byte) []byte {
hash := sha256.Sum256(data) h := sha256.New()
return hash[:TruncatedHashLen/8] h.Write(data)
fullHash := h.Sum(nil)
return fullHash[:TruncatedHashLen/8]
} }
func FullHash(data []byte) []byte { func GetRandomHash() []byte {
hash := sha256.Sum256(data) randomData := make([]byte, TruncatedHashLen/8)
return hash[:] rand.Read(randomData)
return TruncatedHash(randomData)
} }
func HashFromHex(hexHash string) ([]byte, error) { func Remember(packetHash, destHash []byte, publicKey []byte, appData []byte) {
if len(hexHash) != TruncatedHashLen/4 { // hex string is twice the length of bytes knownDestinations[string(destHash)] = []interface{}{
return nil, errors.New("invalid hash length") time.Now().Unix(),
packetHash,
publicKey,
appData,
} }
}
hash := make([]byte, TruncatedHashLen/8)
_, err := hex.Decode(hash, []byte(hexHash)) func ValidateAnnounce(packet []byte, destHash []byte, publicKey []byte, signature []byte, appData []byte) bool {
if err != nil { if len(publicKey) != KeySize/8 {
return nil, err return false
} }
return hash, nil
announced := &Identity{}
announced.publicKey = publicKey[:KeySize/16]
announced.verificationKey = publicKey[KeySize/16:]
signedData := append(destHash, publicKey...)
signedData = append(signedData, appData...)
if !announced.Verify(signedData, signature) {
return false
}
Remember(packet, destHash, publicKey, appData)
return true
}
func FromPublicKey(publicKey []byte) *Identity {
if len(publicKey) != KeySize/8 {
return nil
}
i := &Identity{
publicKey: publicKey[:KeySize/16],
verificationKey: publicKey[KeySize/16:],
ratchets: make(map[string][]byte),
ratchetExpiry: make(map[string]int64),
}
return i
}
func (i *Identity) Hex() string {
return fmt.Sprintf("%x", i.Hash())
}
func (i *Identity) String() string {
return i.Hex()
} }
func Recall(hash []byte) (*Identity, error) { func Recall(hash []byte) (*Identity, error) {
// Get config path from environment or default location // TODO: Implement persistence
configDir := os.Getenv("RETICULUM_CONFIG_DIR") // For now just create new identity
if configDir == "" { return New()
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get home directory: %w", err)
}
configDir = filepath.Join(homeDir, ".reticulum-go")
}
// Create identities directory if it doesn't exist
identitiesPath := filepath.Join(configDir, "identities")
if err := os.MkdirAll(identitiesPath, 0755); err != nil {
return nil, fmt.Errorf("failed to create identities directory: %w", err)
}
// Convert hash to hex for filename
hashHex := hex.EncodeToString(hash)
identityPath := filepath.Join(identitiesPath, hashHex)
// Check if identity file exists
if _, err := os.Stat(identityPath); os.IsNotExist(err) {
return nil, errors.New("identity not found")
}
// Load identity from file
identity, err := LoadFromFile(identityPath)
if err != nil {
return nil, fmt.Errorf("failed to load identity: %w", err)
}
// Verify the loaded identity matches the requested hash
if !bytes.Equal(TruncatedHash(identity.GetPublicKey()), hash) {
return nil, errors.New("identity hash mismatch")
}
return identity, nil
} }
func LoadIdentity(cfg *common.ReticulumConfig) (*Identity, error) { func (i *Identity) GenerateHMACKey() []byte {
if cfg == nil { hmacKey := make([]byte, HMACKeySize)
return nil, errors.New("config cannot be nil") if _, err := io.ReadFull(rand.Reader, hmacKey); err != nil {
return nil
} }
return hmacKey
}
// Try to load existing identity func (i *Identity) ComputeHMAC(key, message []byte) []byte {
identityPath := filepath.Join(filepath.Dir(cfg.ConfigPath), "identity") h := hmac.New(sha256.New, key)
if _, err := os.Stat(identityPath); err == nil { h.Write(message)
// Identity exists, load it return h.Sum(nil)
return LoadFromFile(identityPath) }
}
// Create new identity func (i *Identity) ValidateHMAC(key, message, messageHMAC []byte) bool {
identity, err := New() expectedHMAC := i.ComputeHMAC(key, message)
if err != nil { return hmac.Equal(messageHMAC, expectedHMAC)
return nil, fmt.Errorf("failed to create new identity: %w", err)
}
// Save the new identity
if err := identity.SaveToFile(identityPath); err != nil {
return nil, fmt.Errorf("failed to save new identity: %w", err)
}
return identity, nil
} }
func (i *Identity) GetCurrentRatchetKey() []byte { func (i *Identity) GetCurrentRatchetKey() []byte {
i.mutex.RLock() i.mutex.RLock()
defer i.mutex.RUnlock() defer i.mutex.RUnlock()
// Generate new ratchet key if none exists // Generate new ratchet key if none exists
if len(i.ratchets) == 0 { if len(i.ratchets) == 0 {
key := make([]byte, 32) key := make([]byte, RatchetSize/8)
if _, err := rand.Read(key); err != nil { if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil return nil
} }
ratchetID := fmt.Sprintf("%d", time.Now().Unix()) i.ratchets[string(key)] = key
i.AddRatchet(ratchetID, key) i.ratchetExpiry[string(key)] = time.Now().Unix() + RatchetExpiry
return key return key
} }
// Return most recent ratchet key // Return most recent ratchet key
var latestTime int64
var latestKey []byte var latestKey []byte
var latestTime int64
for id, key := range i.ratchets { for key, expiry := range i.ratchetExpiry {
if expiry, ok := i.ratchetExpiry[id]; ok { if expiry > latestTime {
if expiry > latestTime { latestTime = expiry
latestTime = expiry latestKey = i.ratchets[key]
latestKey = key
}
} }
} }
return latestKey return latestKey
} }
func (i *Identity) EncryptSymmetric(plaintext []byte) ([]byte, error) { func (i *Identity) EncryptSymmetric(plaintext []byte, key []byte) ([]byte, error) {
key := i.GetCurrentRatchetKey() if len(key) != 32 {
if key == nil { return nil, errors.New("invalid key length")
return nil, errors.New("no ratchet key available")
} }
return encryptAESGCM(key, plaintext)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, plaintext, nil), nil
} }
func (i *Identity) DecryptSymmetric(ciphertext []byte) ([]byte, error) { func (i *Identity) DecryptSymmetric(ciphertext []byte, key []byte) ([]byte, error) {
key := i.GetCurrentRatchetKey() if len(key) != 32 {
if key == nil { return nil, errors.New("invalid key length")
return nil, errors.New("no ratchet key available")
} }
return decryptAESGCM(key, ciphertext)
block, err := aes.NewCipher(key) }
if err != nil {
return nil, err func (i *Identity) Decrypt(ciphertext []byte) ([]byte, error) {
} if len(ciphertext) < curve25519.PointSize {
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, errors.New("ciphertext too short") return nil, errors.New("ciphertext too short")
} }
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] ephemeralPublic := ciphertext[:curve25519.PointSize]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) encryptedData := ciphertext[curve25519.PointSize:]
// Compute shared secret
sharedSecret, err := curve25519.X25519(i.privateKey, ephemeralPublic)
if err != nil { if err != nil {
return nil, fmt.Errorf("decryption failed: %w", err) return nil, err
}
return plaintext, nil
}
func (i *Identity) Hash() []byte {
return TruncatedHash(i.publicKey)
}
func (i *Identity) Hex() string {
return hex.EncodeToString(i.Hash())
}
func FromPublicKey(publicKey []byte) *Identity {
if len(publicKey) != curve25519.PointSize {
return nil
} }
i := &Identity{ // Derive key using HKDF
publicKey: append([]byte{}, publicKey...), hkdf := hkdf.New(sha256.New, sharedSecret, i.Hash(), nil)
ratchets: make(map[string][]byte), key := make([]byte, 32)
ratchetExpiry: make(map[string]int64), if _, err := io.ReadFull(hkdf, key); err != nil {
return nil, err
} }
// Generate Ed25519 verification key from the X25519 public key // Decrypt data
hash := sha256.New() return decryptAESGCM(key, encryptedData)
hash.Write(publicKey) }
seed := hash.Sum(nil)
// Use the first 32 bytes of the hash as the verification key
i.verificationKey = ed25519.PublicKey(seed[:32])
return i
}

View File

@@ -1,29 +1,42 @@
package interfaces package interfaces
import ( import (
"fmt"
"time"
"encoding/binary" "encoding/binary"
"fmt"
"net" "net"
"sync"
"time"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
) )
const ( const (
BITRATE_MINIMUM = 5 // Minimum required bitrate in bits/sec BITRATE_MINIMUM = 5 // Minimum required bitrate in bits/sec
MODE_FULL = 0x01 MODE_FULL = 0x01
) )
type Interface interface { type Interface interface {
common.NetworkInterface
Send(data []byte, target string) error
Detach()
IsEnabled() bool
GetName() string GetName() string
GetType() common.InterfaceType
GetMode() common.InterfaceMode
IsOnline() bool
IsDetached() bool
Detach()
Send(data []byte, addr string) error
SetPacketCallback(common.PacketCallback)
GetPacketCallback() common.PacketCallback
} }
type BaseInterface struct { type BaseInterface struct {
common.BaseInterface common.BaseInterface
name string
mode common.InterfaceMode
ifType common.InterfaceType
online bool
detached bool
mtu int
mutex sync.RWMutex
packetCallback common.PacketCallback
} }
func (i *BaseInterface) SetPacketCallback(callback common.PacketCallback) { func (i *BaseInterface) SetPacketCallback(callback common.PacketCallback) {
@@ -36,11 +49,11 @@ func (i *BaseInterface) ProcessIncoming(data []byte) {
i.Mutex.RLock() i.Mutex.RLock()
callback := i.PacketCallback callback := i.PacketCallback
i.Mutex.RUnlock() i.Mutex.RUnlock()
if callback != nil { if callback != nil {
callback(data, i) callback(data, i)
} }
i.RxBytes += uint64(len(data)) i.RxBytes += uint64(len(data))
} }
@@ -76,11 +89,11 @@ func (i *BaseInterface) SendLinkPacket(dest []byte, data []byte, timestamp time.
frame := make([]byte, 0, len(dest)+len(data)+9) frame := make([]byte, 0, len(dest)+len(data)+9)
frame = append(frame, 0x02) frame = append(frame, 0x02)
frame = append(frame, dest...) frame = append(frame, dest...)
ts := make([]byte, 8) ts := make([]byte, 8)
binary.BigEndian.PutUint64(ts, uint64(timestamp.Unix())) binary.BigEndian.PutUint64(ts, uint64(timestamp.Unix()))
frame = append(frame, ts...) frame = append(frame, ts...)
frame = append(frame, data...) frame = append(frame, data...)
return i.ProcessOutgoing(frame) return i.ProcessOutgoing(frame)
@@ -124,4 +137,4 @@ func (i *BaseInterface) GetConn() net.Conn {
func (i *BaseInterface) IsEnabled() bool { func (i *BaseInterface) IsEnabled() bool {
return i.Online && !i.Detached return i.Online && !i.Detached
} }

View File

@@ -5,6 +5,7 @@ import (
"net" "net"
"sync" "sync"
"time" "time"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
) )
@@ -18,9 +19,9 @@ const (
KISS_TFEND = 0xDC KISS_TFEND = 0xDC
KISS_TFESC = 0xDD KISS_TFESC = 0xDD
TCP_USER_TIMEOUT = 24 TCP_USER_TIMEOUT = 24
TCP_PROBE_AFTER = 5 TCP_PROBE_AFTER = 5
TCP_PROBE_INTERVAL = 2 TCP_PROBE_INTERVAL = 2
TCP_PROBES = 12 TCP_PROBES = 12
RECONNECT_WAIT = 5 RECONNECT_WAIT = 5
INITIAL_TIMEOUT = 5 INITIAL_TIMEOUT = 5
@@ -28,30 +29,32 @@ const (
type TCPClientInterface struct { type TCPClientInterface struct {
BaseInterface BaseInterface
conn net.Conn conn net.Conn
targetAddr string targetAddr string
targetPort int targetPort int
kissFraming bool kissFraming bool
i2pTunneled bool i2pTunneled bool
initiator bool initiator bool
reconnecting bool reconnecting bool
neverConnected bool neverConnected bool
writing bool writing bool
maxReconnectTries int maxReconnectTries int
packetBuffer []byte packetBuffer []byte
packetType byte packetType byte
packetCallback common.PacketCallback packetCallback common.PacketCallback
mutex sync.RWMutex
detached bool
} }
func NewTCPClient(name string, targetAddr string, targetPort int, kissFraming bool, i2pTunneled bool) (*TCPClientInterface, error) { func NewTCPClient(name string, targetAddr string, targetPort int, kissFraming bool, i2pTunneled bool) (*TCPClientInterface, error) {
tc := &TCPClientInterface{ tc := &TCPClientInterface{
BaseInterface: BaseInterface{ BaseInterface: BaseInterface{
BaseInterface: common.BaseInterface{ name: name,
Name: name, mode: common.IF_MODE_FULL,
Mode: common.IF_MODE_FULL, ifType: common.IF_TYPE_TCP,
MTU: 1064, online: false,
Bitrate: 10000000, // 10Mbps estimate mtu: 1064,
}, detached: false,
}, },
targetAddr: targetAddr, targetAddr: targetAddr,
targetPort: targetPort, targetPort: targetPort,
@@ -285,159 +288,102 @@ func (tc *TCPClientInterface) GetName() string {
return tc.Name return tc.Name
} }
type TCPServerInterface struct { func (tc *TCPClientInterface) GetPacketCallback() common.PacketCallback {
BaseInterface tc.mutex.RLock()
server net.Listener defer tc.mutex.RUnlock()
bindAddr string return tc.packetCallback
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) { func (tc *TCPClientInterface) IsDetached() bool {
tc.mutex.RLock()
defer tc.mutex.RUnlock()
return tc.detached
}
func (tc *TCPClientInterface) IsOnline() bool {
tc.mutex.RLock()
defer tc.mutex.RUnlock()
return tc.online
}
type TCPServerInterface struct {
BaseInterface
listener net.Listener
connections map[string]net.Conn
mutex sync.RWMutex
bindAddr string
bindPort int
preferIPv6 bool
spawned bool
port int
host string
kissFraming bool
i2pTunneled bool
packetCallback common.PacketCallback
detached bool
}
func NewTCPServer(name string, bindAddr string, bindPort int, kissFraming bool, i2pTunneled bool, preferIPv6 bool) (*TCPServerInterface, error) {
ts := &TCPServerInterface{ ts := &TCPServerInterface{
BaseInterface: BaseInterface{ BaseInterface: BaseInterface{
BaseInterface: common.BaseInterface{ name: name,
Name: name, mode: common.IF_MODE_FULL,
Mode: common.IF_MODE_FULL, ifType: common.IF_TYPE_TCP,
Type: common.IF_TYPE_TCP, online: false,
MTU: 1064, mtu: common.DEFAULT_MTU,
Bitrate: 10000000, detached: false,
},
}, },
connections: make(map[string]net.Conn),
bindAddr: bindAddr, bindAddr: bindAddr,
bindPort: bindPort, bindPort: bindPort,
preferIPv6: preferIPv6, preferIPv6: preferIPv6,
kissFraming: kissFraming,
i2pTunneled: i2pTunneled, i2pTunneled: i2pTunneled,
spawned: make([]*TCPClientInterface, 0),
} }
// Resolve bind address
var addr string
if ts.bindAddr == "" {
if ts.preferIPv6 {
addr = fmt.Sprintf("[::0]:%d", ts.bindPort)
} else {
addr = fmt.Sprintf("0.0.0.0:%d", ts.bindPort)
}
} else {
addr = fmt.Sprintf("%s:%d", ts.bindAddr, ts.bindPort)
}
// Create listener
var err error
ts.server, err = net.Listen("tcp", addr)
if err != nil {
return nil, fmt.Errorf("failed to create TCP listener: %v", err)
}
ts.Online = true
ts.IN = true
// Start accept loop
go ts.acceptLoop()
return ts, nil return ts, nil
} }
func (ts *TCPServerInterface) acceptLoop() {
for {
conn, err := ts.server.Accept()
if err != nil {
if !ts.Detached {
continue
}
return
}
// Create new client interface for this connection
client := &TCPClientInterface{
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,
}
// Configure TCP options
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetNoDelay(true)
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(time.Duration(TCP_PROBE_INTERVAL) * time.Second)
}
client.Online = true
client.IN = ts.IN
client.OUT = ts.OUT
// Add to spawned interfaces
ts.spawnedMutex.Lock()
ts.spawned = append(ts.spawned, client)
ts.spawnedMutex.Unlock()
// Start client read loop
go client.readLoop()
}
}
func (ts *TCPServerInterface) Detach() {
ts.BaseInterface.Detach()
if ts.server != nil {
ts.server.Close()
}
ts.spawnedMutex.Lock()
for _, client := range ts.spawned {
client.Detach()
}
ts.spawned = nil
ts.spawnedMutex.Unlock()
}
func (ts *TCPServerInterface) ProcessOutgoing(data []byte) error {
ts.spawnedMutex.RLock()
defer ts.spawnedMutex.RUnlock()
var lastErr error
for _, client := range ts.spawned {
if err := client.ProcessOutgoing(data); err != nil {
lastErr = err
}
}
return lastErr
}
func (ts *TCPServerInterface) String() string { func (ts *TCPServerInterface) String() string {
addr := ts.bindAddr addr := ts.bindAddr
if addr == "" { if addr == "" {
if ts.preferIPv6 { if ts.preferIPv6 {
addr = "[::0]" addr = "[::0]"
} else { } else {
addr = "0.0.0.0" addr = "0.0.0.0"
} }
} }
return fmt.Sprintf("TCPServerInterface[%s/%s:%d]", ts.Name, addr, ts.bindPort) return fmt.Sprintf("TCPServerInterface[%s/%s:%d]", ts.name, addr, ts.bindPort)
} }
func (ts *TCPServerInterface) SetPacketCallback(cb func([]byte, interface{})) { func (ts *TCPServerInterface) SetPacketCallback(callback common.PacketCallback) {
ts.packetCallback = cb ts.mutex.Lock()
defer ts.mutex.Unlock()
ts.packetCallback = callback
}
func (ts *TCPServerInterface) GetPacketCallback() common.PacketCallback {
ts.mutex.RLock()
defer ts.mutex.RUnlock()
return ts.packetCallback
} }
func (ts *TCPServerInterface) IsEnabled() bool { func (ts *TCPServerInterface) IsEnabled() bool {
return ts.Online return ts.online
} }
func (ts *TCPServerInterface) GetName() string { func (ts *TCPServerInterface) GetName() string {
return ts.Name return ts.name
} }
func (ts *TCPServerInterface) IsDetached() bool {
ts.mutex.RLock()
defer ts.mutex.RUnlock()
return ts.detached
}
func (ts *TCPServerInterface) IsOnline() bool {
ts.mutex.RLock()
defer ts.mutex.RUnlock()
return ts.online
}

View File

@@ -3,95 +3,139 @@ package interfaces
import ( import (
"fmt" "fmt"
"net" "net"
"sync"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
) )
type UDPInterface struct { type UDPInterface struct {
BaseInterface BaseInterface
conn *net.UDPConn conn *net.UDPConn
listenAddr *net.UDPAddr addr *net.UDPAddr
targetAddr *net.UDPAddr targetAddr *net.UDPAddr
mutex sync.RWMutex
readBuffer []byte readBuffer []byte
txBytes uint64
rxBytes uint64
mtu int
bitrate int
} }
func NewUDPInterface(name string, listenAddr string, targetAddr string) (*UDPInterface, error) { func NewUDPInterface(name string, addr string, target string) (*UDPInterface, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
var targetAddr *net.UDPAddr
if target != "" {
targetAddr, err = net.ResolveUDPAddr("udp", target)
if err != nil {
return nil, err
}
}
ui := &UDPInterface{ ui := &UDPInterface{
BaseInterface: BaseInterface{ BaseInterface: BaseInterface{
BaseInterface: common.BaseInterface{ name: name,
Name: name, mode: common.IF_MODE_FULL,
Mode: common.IF_MODE_FULL, ifType: common.IF_TYPE_UDP,
Type: common.IF_TYPE_UDP, online: false,
MTU: 1500, mtu: common.DEFAULT_MTU,
Bitrate: 100000000, // 100Mbps estimate detached: false,
},
}, },
readBuffer: make([]byte, 65535), addr: udpAddr,
targetAddr: targetAddr,
readBuffer: make([]byte, common.DEFAULT_MTU),
} }
// Parse listen address
laddr, err := net.ResolveUDPAddr("udp", listenAddr)
if err != nil {
return nil, fmt.Errorf("invalid listen address: %v", err)
}
ui.listenAddr = laddr
// Parse target address if provided
if targetAddr != "" {
taddr, err := net.ResolveUDPAddr("udp", targetAddr)
if err != nil {
return nil, fmt.Errorf("invalid target address: %v", err)
}
ui.targetAddr = taddr
ui.BaseInterface.OUT = true
}
// Create UDP connection
conn, err := net.ListenUDP("udp", ui.listenAddr)
if err != nil {
return nil, fmt.Errorf("failed to listen on UDP: %v", err)
}
ui.conn = conn
ui.BaseInterface.IN = true
ui.BaseInterface.Online = true
// Start read loop
go ui.readLoop()
return ui, nil return ui, nil
} }
func (ui *UDPInterface) readLoop() { func (ui *UDPInterface) GetName() string {
for { return ui.name
if !ui.BaseInterface.Online { }
return
}
n, remoteAddr, err := ui.conn.ReadFromUDP(ui.readBuffer) func (ui *UDPInterface) GetType() common.InterfaceType {
return ui.ifType
}
func (ui *UDPInterface) GetMode() common.InterfaceMode {
return ui.mode
}
func (ui *UDPInterface) IsOnline() bool {
ui.mutex.RLock()
defer ui.mutex.RUnlock()
return ui.online
}
func (ui *UDPInterface) IsDetached() bool {
ui.mutex.RLock()
defer ui.mutex.RUnlock()
return ui.detached
}
func (ui *UDPInterface) Detach() {
ui.mutex.Lock()
defer ui.mutex.Unlock()
ui.detached = true
if ui.conn != nil {
ui.conn.Close()
}
}
func (ui *UDPInterface) Send(data []byte, addr string) error {
if !ui.IsOnline() {
return fmt.Errorf("interface offline")
}
targetAddr := ui.targetAddr
if addr != "" {
var err error
targetAddr, err = net.ResolveUDPAddr("udp", addr)
if err != nil { if err != nil {
if !ui.BaseInterface.Detached { return fmt.Errorf("invalid target address: %v", err)
continue
}
return
} }
}
// If no target address is set, use the first sender's address if targetAddr == nil {
if ui.targetAddr == nil { return fmt.Errorf("no target address configured")
ui.targetAddr = remoteAddr }
ui.BaseInterface.OUT = true
}
// Copy received data _, err := ui.conn.WriteToUDP(data, targetAddr)
data := make([]byte, n) if err != nil {
copy(data, ui.readBuffer[:n]) return fmt.Errorf("UDP write failed: %v", err)
}
// Process packet return nil
ui.ProcessIncoming(data) }
func (ui *UDPInterface) SetPacketCallback(callback common.PacketCallback) {
ui.mutex.Lock()
defer ui.mutex.Unlock()
ui.packetCallback = callback
}
func (ui *UDPInterface) GetPacketCallback() common.PacketCallback {
ui.mutex.RLock()
defer ui.mutex.RUnlock()
return ui.packetCallback
}
func (ui *UDPInterface) ProcessIncoming(data []byte) {
if callback := ui.GetPacketCallback(); callback != nil {
callback(data, ui)
} }
} }
func (ui *UDPInterface) ProcessOutgoing(data []byte) error { func (ui *UDPInterface) ProcessOutgoing(data []byte) error {
if !ui.BaseInterface.Online || ui.targetAddr == nil { if !ui.IsOnline() {
return fmt.Errorf("interface offline or no target address configured") return fmt.Errorf("interface offline")
}
if ui.targetAddr == nil {
return fmt.Errorf("no target address configured")
} }
_, err := ui.conn.WriteToUDP(data, ui.targetAddr) _, err := ui.conn.WriteToUDP(data, ui.targetAddr)
@@ -99,17 +143,33 @@ func (ui *UDPInterface) ProcessOutgoing(data []byte) error {
return fmt.Errorf("UDP write failed: %v", err) return fmt.Errorf("UDP write failed: %v", err)
} }
ui.BaseInterface.ProcessOutgoing(data) ui.mutex.Lock()
return nil ui.txBytes += uint64(len(data))
} ui.mutex.Unlock()
func (ui *UDPInterface) Detach() { return nil
ui.BaseInterface.Detach()
if ui.conn != nil {
ui.conn.Close()
}
} }
func (ui *UDPInterface) GetConn() net.Conn { func (ui *UDPInterface) GetConn() net.Conn {
return ui.conn return ui.conn
} }
func (ui *UDPInterface) GetTxBytes() uint64 {
ui.mutex.RLock()
defer ui.mutex.RUnlock()
return ui.txBytes
}
func (ui *UDPInterface) GetRxBytes() uint64 {
ui.mutex.RLock()
defer ui.mutex.RUnlock()
return ui.rxBytes
}
func (ui *UDPInterface) GetMTU() int {
return ui.mtu
}
func (ui *UDPInterface) GetBitrate() int {
return ui.bitrate
}

View File

@@ -6,103 +6,139 @@ import (
"crypto/ed25519" "crypto/ed25519"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/binary"
"errors" "errors"
"io" "io"
"sync" "sync"
"time" "time"
"github.com/Sudo-Ivan/reticulum-go/pkg/destination"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity" "github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet" "github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport"
) )
const ( const (
CURVE = "Curve25519" CURVE = "Curve25519"
ESTABLISHMENT_TIMEOUT_PER_HOP = 6 ESTABLISHMENT_TIMEOUT_PER_HOP = 6
KEEPALIVE_TIMEOUT_FACTOR = 4 KEEPALIVE_TIMEOUT_FACTOR = 4
STALE_GRACE = 2 STALE_GRACE = 2
KEEPALIVE = 360 KEEPALIVE = 360
STALE_TIME = 720 STALE_TIME = 720
ACCEPT_NONE = 0x00 ACCEPT_NONE = 0x00
ACCEPT_ALL = 0x01 ACCEPT_ALL = 0x01
ACCEPT_APP = 0x02 ACCEPT_APP = 0x02
STATUS_PENDING = 0x00 STATUS_PENDING = 0x00
STATUS_ACTIVE = 0x01 STATUS_ACTIVE = 0x01
STATUS_CLOSED = 0x02 STATUS_CLOSED = 0x02
STATUS_FAILED = 0x03 STATUS_FAILED = 0x03
// Add packet types
PACKET_TYPE_DATA = 0x00
PACKET_TYPE_LINK = 0x01
PACKET_TYPE_IDENTIFY = 0x02
) )
type Link struct { type Link struct {
mutex sync.RWMutex mutex sync.RWMutex
destination interface{} destination *destination.Destination
status byte status byte
establishedAt time.Time establishedAt time.Time
lastInbound time.Time lastInbound time.Time
lastOutbound time.Time lastOutbound time.Time
lastDataReceived time.Time lastDataReceived time.Time
lastDataSent time.Time lastDataSent time.Time
remoteIdentity *identity.Identity remoteIdentity *identity.Identity
sessionKey []byte sessionKey []byte
linkID []byte linkID []byte
rtt float64 rtt float64
establishmentRate float64 establishmentRate float64
trackPhyStats bool
rssi float64
snr float64
q float64
resourceStrategy byte
establishedCallback func(*Link) establishedCallback func(*Link)
closedCallback func(*Link) closedCallback func(*Link)
packetCallback func([]byte, *packet.Packet) packetCallback func([]byte, *packet.Packet)
resourceCallback func(interface{}) bool identifiedCallback func(*Link, *identity.Identity)
resourceStartedCallback func(interface{})
teardownReason byte
hmacKey []byte
transport *transport.Transport
// Add missing fields
rssi float64
snr float64
q float64
resourceCallback func(interface{}) bool
resourceStartedCallback func(interface{})
resourceConcludedCallback func(interface{}) resourceConcludedCallback func(interface{})
remoteIdentifiedCallback func(*Link, *identity.Identity) resourceStrategy byte
} }
func New(dest interface{}, establishedCb func(*Link), closedCb func(*Link)) *Link { func NewLink(dest *destination.Destination, transport *transport.Transport, establishedCallback func(*Link), closedCallback func(*Link)) *Link {
l := &Link{ return &Link{
destination: dest, destination: dest,
status: STATUS_PENDING, status: STATUS_PENDING,
establishedAt: time.Time{}, transport: transport,
lastInbound: time.Time{}, establishedCallback: establishedCallback,
lastOutbound: time.Time{}, closedCallback: closedCallback,
lastDataReceived: time.Time{}, establishedAt: time.Time{}, // Zero time until established
lastDataSent: time.Time{}, lastInbound: time.Time{},
resourceStrategy: ACCEPT_NONE, lastOutbound: time.Time{},
establishedCallback: establishedCb, lastDataReceived: time.Time{},
closedCallback: closedCb, lastDataSent: time.Time{},
} }
return l
} }
func (l *Link) Identify(id *identity.Identity) error { func (l *Link) Establish() error {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
if l.status != STATUS_ACTIVE { if l.status != STATUS_PENDING {
return errors.New("link not active") return errors.New("link already established or failed")
} }
// Create identification message destPublicKey := l.destination.GetPublicKey()
idMsg := append(id.GetPublicKey(), id.Sign(l.linkID)...) if destPublicKey == nil {
return errors.New("destination has no public key")
// Encrypt and send identification }
err := l.SendPacket(idMsg)
// Create link request packet
p, err := packet.NewPacket(
packet.PACKET_TYPE_LINK,
0x00, // flags
0x00, // hops
destPublicKey,
l.linkID,
)
if err != nil { if err != nil {
return err return err
} }
return nil // Send through transport
return l.transport.SendPacket(p)
}
func (l *Link) Identify(id *identity.Identity) error {
if !l.IsActive() {
return errors.New("link not active")
}
// Create identify packet
p, err := packet.NewPacket(
packet.PACKET_TYPE_IDENTIFY,
0x00,
0x00,
l.destination.GetPublicKey(),
id.GetPublicKey(),
)
if err != nil {
return err
}
return l.transport.SendPacket(p)
} }
func (l *Link) HandleIdentification(data []byte) error { func (l *Link) HandleIdentification(data []byte) error {
@@ -110,26 +146,27 @@ func (l *Link) HandleIdentification(data []byte) error {
defer l.mutex.Unlock() defer l.mutex.Unlock()
if len(data) < ed25519.PublicKeySize+ed25519.SignatureSize { if len(data) < ed25519.PublicKeySize+ed25519.SignatureSize {
return errors.New("invalid identification data") return errors.New("invalid identification data length")
} }
pubKey := data[:ed25519.PublicKeySize] pubKey := data[:ed25519.PublicKeySize]
signature := data[ed25519.PublicKeySize:] signature := data[ed25519.PublicKeySize:]
remoteIdentity := &identity.Identity{} remoteIdentity := identity.FromPublicKey(pubKey)
if !remoteIdentity.LoadPublicKey(pubKey) { if remoteIdentity == nil {
return errors.New("invalid remote public key") return errors.New("invalid remote identity")
} }
// Verify signature of link ID // Verify signature
if !remoteIdentity.Verify(l.linkID, signature) { signData := append(l.linkID, pubKey...)
return errors.New("invalid identification signature") if !remoteIdentity.Verify(signData, signature) {
return errors.New("invalid signature")
} }
l.remoteIdentity = remoteIdentity l.remoteIdentity = remoteIdentity
if l.remoteIdentifiedCallback != nil { if l.identifiedCallback != nil {
l.remoteIdentifiedCallback(l, remoteIdentity) l.identifiedCallback(l, remoteIdentity)
} }
return nil return nil
@@ -158,8 +195,8 @@ func (l *Link) Request(path string, data []byte, timeout time.Duration) (*Reques
receipt := &RequestReceipt{ receipt := &RequestReceipt{
requestID: requestID, requestID: requestID,
status: STATUS_PENDING, status: STATUS_PENDING,
sentAt: time.Now(), sentAt: time.Now(),
} }
// Send request // Send request
@@ -184,12 +221,12 @@ func (l *Link) Request(path string, data []byte, timeout time.Duration) (*Reques
} }
type RequestReceipt struct { type RequestReceipt struct {
mutex sync.RWMutex mutex sync.RWMutex
requestID []byte requestID []byte
status byte status byte
sentAt time.Time sentAt time.Time
receivedAt time.Time receivedAt time.Time
response []byte response []byte
} }
func (r *RequestReceipt) GetRequestID() []byte { func (r *RequestReceipt) GetRequestID() []byte {
@@ -227,10 +264,13 @@ func (r *RequestReceipt) Concluded() bool {
return status == STATUS_ACTIVE || status == STATUS_FAILED return status == STATUS_ACTIVE || status == STATUS_FAILED
} }
func (l *Link) TrackPhyStats(track bool) { func (l *Link) TrackPhyStats(rssi float64, snr float64, q float64) {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
l.trackPhyStats = track
l.rssi = rssi
l.snr = snr
l.q = q
} }
func (l *Link) GetRSSI() float64 { func (l *Link) GetRSSI() float64 {
@@ -319,7 +359,7 @@ func (l *Link) GetRemoteIdentity() *identity.Identity {
func (l *Link) Teardown() { func (l *Link) Teardown() {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
if l.status == STATUS_ACTIVE { if l.status == STATUS_ACTIVE {
l.status = STATUS_CLOSED l.status = STATUS_CLOSED
if l.closedCallback != nil { if l.closedCallback != nil {
@@ -361,63 +401,20 @@ func (l *Link) SetResourceConcludedCallback(callback func(interface{})) {
func (l *Link) SetRemoteIdentifiedCallback(callback func(*Link, *identity.Identity)) { func (l *Link) SetRemoteIdentifiedCallback(callback func(*Link, *identity.Identity)) {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
l.remoteIdentifiedCallback = callback l.identifiedCallback = callback
} }
func (l *Link) SetResourceStrategy(strategy byte) error { func (l *Link) SetResourceStrategy(strategy byte) error {
if strategy != ACCEPT_NONE && strategy != ACCEPT_ALL && strategy != ACCEPT_APP { if strategy != ACCEPT_NONE && strategy != ACCEPT_ALL && strategy != ACCEPT_APP {
return errors.New("unsupported resource strategy") return errors.New("unsupported resource strategy")
} }
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
l.resourceStrategy = strategy l.resourceStrategy = strategy
return nil return nil
} }
func NewLink(destination interface{}, establishedCallback func(*Link), closedCallback func(*Link)) *Link {
l := &Link{
destination: destination,
status: STATUS_PENDING,
establishedAt: time.Time{},
lastInbound: time.Time{},
lastOutbound: time.Time{},
lastDataReceived: time.Time{},
lastDataSent: time.Time{},
establishedCallback: establishedCallback,
closedCallback: closedCallback,
resourceStrategy: ACCEPT_NONE,
trackPhyStats: false,
}
return l
}
func (l *Link) Establish() error {
l.mutex.Lock()
defer l.mutex.Unlock()
if l.status != STATUS_PENDING {
return errors.New("link already established or failed")
}
// Generate session key using ECDH
ephemeralKey := make([]byte, 32)
if _, err := rand.Read(ephemeralKey); err != nil {
return err
}
l.sessionKey = ephemeralKey
l.establishedAt = time.Now()
l.status = STATUS_ACTIVE
if l.establishedCallback != nil {
l.establishedCallback(l)
}
return nil
}
func (l *Link) SendPacket(data []byte) error { func (l *Link) SendPacket(data []byte) error {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
@@ -426,8 +423,14 @@ func (l *Link) SendPacket(data []byte) error {
return errors.New("link not active") return errors.New("link not active")
} }
// Encrypt data using session key // Compute HMAC first
encryptedData, err := l.encrypt(data) messageHMAC := l.destination.GetIdentity().ComputeHMAC(l.hmacKey, data)
// Combine data and HMAC
authenticatedData := append(data, messageHMAC...)
// Encrypt authenticated data using session key
encryptedData, err := l.encrypt(authenticatedData)
if err != nil { if err != nil {
return err return err
} }
@@ -456,11 +459,24 @@ func (l *Link) HandleInbound(data []byte) error {
return err return err
} }
// Split message and HMAC
if len(decryptedData) < sha256.Size {
return errors.New("received data too short")
}
message := decryptedData[:len(decryptedData)-sha256.Size]
messageHMAC := decryptedData[len(decryptedData)-sha256.Size:]
// Verify HMAC
if !l.destination.GetIdentity().ValidateHMAC(l.hmacKey, message, messageHMAC) {
return errors.New("invalid message authentication code")
}
l.lastInbound = time.Now() l.lastInbound = time.Now()
l.lastDataReceived = time.Now() l.lastDataReceived = time.Now()
if l.packetCallback != nil { if l.packetCallback != nil {
l.packetCallback(decryptedData, nil) l.packetCallback(message, nil)
} }
return nil return nil
@@ -514,16 +530,7 @@ func (l *Link) decrypt(data []byte) ([]byte, error) {
} }
func (l *Link) UpdatePhyStats(rssi float64, snr float64, q float64) { func (l *Link) UpdatePhyStats(rssi float64, snr float64, q float64) {
if !l.trackPhyStats { l.TrackPhyStats(rssi, snr, q)
return
}
l.mutex.Lock()
defer l.mutex.Unlock()
l.rssi = rssi
l.snr = snr
l.q = q
} }
func (l *Link) GetRTT() float64 { func (l *Link) GetRTT() float64 {
@@ -546,4 +553,4 @@ func (l *Link) GetStatus() byte {
func (l *Link) IsActive() bool { func (l *Link) IsActive() bool {
return l.GetStatus() == STATUS_ACTIVE return l.GetStatus() == STATUS_ACTIVE
} }

View File

@@ -3,10 +3,10 @@ package packet
const ( const (
// MTU constants // MTU constants
EncryptedMDU = 383 // Maximum size of payload data in encrypted packet EncryptedMDU = 383 // Maximum size of payload data in encrypted packet
PlainMDU = 464 // Maximum size of payload data in unencrypted packet PlainMDU = 464 // Maximum size of payload data in unencrypted packet
// Header Types // Header Types
HeaderType1 = 0 // Two byte header, one 16 byte address field HeaderType1 = 0 // Two byte header, one 16 byte address field
HeaderType2 = 1 // Two byte header, two 16 byte address fields HeaderType2 = 1 // Two byte header, two 16 byte address fields
// Propagation Types // Propagation Types
@@ -24,4 +24,4 @@ const (
PacketAnnounce = 1 PacketAnnounce = 1
PacketLinkRequest = 2 PacketLinkRequest = 2
PacketProof = 3 PacketProof = 3
) )

View File

@@ -1,64 +1,110 @@
package packet package packet
import ( import (
"crypto/rand"
"encoding/binary" "encoding/binary"
"errors" "errors"
) )
const ( const (
HeaderSize = 2 // Packet Types
AddressSize = 16 PacketTypeData = 0x00
ContextSize = 1 PacketTypeAnnounce = 0x01
MaxDataSize = 465 // Maximum size of payload data PacketTypeLink = 0x02
PacketTypeProof = 0x03
PACKET_TYPE_DATA = 0x00
PACKET_TYPE_LINK = 0x01
PACKET_TYPE_IDENTIFY = 0x02
// Sizes
HeaderSize = 2
AddressSize = 16
ContextSize = 1
MaxDataSize = 465
RandomBlobSize = 16
) )
// Header flags and types // Header flags and types
const ( const (
// First byte flags // First byte flags
IFACFlag = 0x80 // Interface authentication code flag IFACFlag = 0x80
HeaderTypeFlag = 0x40 // Header type flag HeaderTypeFlag = 0x40
ContextFlag = 0x20 // Context flag ContextFlag = 0x20
PropagationFlags = 0x18 // Propagation type flags (bits 3-4) PropagationFlags = 0x18
DestinationFlags = 0x06 // Destination type flags (bits 1-2) DestinationFlags = 0x06
PacketTypeFlags = 0x01 // Packet type flags (bit 0) PacketTypeFlags = 0x01
// Second byte // Second byte
HopsField = 0xFF // Number of hops (entire byte) HopsField = 0xFF
) )
type Packet struct { type Packet struct {
Header [2]byte Header [2]byte
Addresses []byte // Either 16 or 32 bytes depending on header type Addresses []byte
Context byte Context byte
Data []byte Data []byte
AccessCode []byte // Optional: Only present if IFAC flag is set AccessCode []byte
RandomBlob []byte
} }
func NewPacket(headerType, propagationType, destinationType, packetType byte, hops byte) *Packet { func NewAnnouncePacket(destHash []byte, publicKey []byte, appData []byte) (*Packet, error) {
p := &Packet{ p := &Packet{
Header: [2]byte{0, hops}, Header: [2]byte{0, 0}, // Start with 0 hops
Addresses: make([]byte, 0), Addresses: make([]byte, AddressSize),
Data: make([]byte, 0), Data: make([]byte, 0, MaxDataSize),
} }
// Set header type // Set header flags for announce packet
if headerType == HeaderType2 { p.Header[0] |= HeaderTypeFlag // Single address
p.Header[0] |= HeaderTypeFlag p.Header[0] |= (PropagationBroadcast << 3) & PropagationFlags // Broadcast
p.Addresses = make([]byte, 2*AddressSize) // Two address fields p.Header[0] |= (DestinationSingle << 1) & DestinationFlags // Single destination
} else { p.Header[0] |= PacketTypeAnnounce & PacketTypeFlags // Announce type
p.Addresses = make([]byte, AddressSize) // One address field
// Set destination hash
if len(destHash) != AddressSize {
return nil, errors.New("invalid destination hash size")
}
copy(p.Addresses, destHash)
// Build announce data
// Public key
p.Data = append(p.Data, publicKey...)
// App data length and content
appDataLen := make([]byte, 2)
binary.BigEndian.PutUint16(appDataLen, uint16(len(appData)))
p.Data = append(p.Data, appDataLen...)
p.Data = append(p.Data, appData...)
// Add random blob
randomBlob := make([]byte, RandomBlobSize)
if _, err := rand.Read(randomBlob); err != nil {
return nil, err
}
p.RandomBlob = randomBlob
p.Data = append(p.Data, randomBlob...)
return p, nil
}
func NewPacket(packetType byte, flags byte, hops byte, destKey []byte, data []byte) (*Packet, error) {
if len(destKey) != AddressSize {
return nil, errors.New("invalid destination key length")
} }
// Set propagation type p := &Packet{
p.Header[0] |= (propagationType << 3) & PropagationFlags Header: [2]byte{flags, hops},
Addresses: make([]byte, AddressSize),
Data: data,
}
// Set destination type // Set packet type in flags
p.Header[0] |= (destinationType << 1) & DestinationFlags
// Set packet type
p.Header[0] |= packetType & PacketTypeFlags p.Header[0] |= packetType & PacketTypeFlags
return p // Copy destination address
copy(p.Addresses, destKey)
return p, nil
} }
func (p *Packet) SetAccessCode(code []byte) { func (p *Packet) SetAccessCode(code []byte) {
@@ -83,12 +129,12 @@ func (p *Packet) SetAddress(index int, address []byte) error {
if len(address) != AddressSize { if len(address) != AddressSize {
return errors.New("invalid address size") return errors.New("invalid address size")
} }
offset := index * AddressSize offset := index * AddressSize
if offset+AddressSize > len(p.Addresses) { if offset+AddressSize > len(p.Addresses) {
return errors.New("address index out of range") return errors.New("address index out of range")
} }
copy(p.Addresses[offset:], address) copy(p.Addresses[offset:], address)
return nil return nil
} }
@@ -168,4 +214,4 @@ func ParsePacket(data []byte) (*Packet, error) {
copy(p.Data, data[offset:]) copy(p.Data, data[offset:])
return p, nil return p, nil
} }

View File

@@ -2,13 +2,12 @@ package resource
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/binary"
"errors" "errors"
"io" "io"
"path/filepath"
"strings" "strings"
"sync" "sync"
"time" "time"
"path/filepath"
) )
const ( const (
@@ -19,88 +18,89 @@ const (
STATUS_CANCELLED = 0x04 STATUS_CANCELLED = 0x04
DEFAULT_SEGMENT_SIZE = 384 // Based on ENCRYPTED_MDU DEFAULT_SEGMENT_SIZE = 384 // Based on ENCRYPTED_MDU
MAX_SEGMENTS = 65535 MAX_SEGMENTS = 65535
CLEANUP_INTERVAL = 300 // 5 minutes CLEANUP_INTERVAL = 300 // 5 minutes
// Window size constants // Window size constants
WINDOW = 4 WINDOW = 4
WINDOW_MIN = 2 WINDOW_MIN = 2
WINDOW_MAX_SLOW = 10 WINDOW_MAX_SLOW = 10
WINDOW_MAX_VERY_SLOW = 4 WINDOW_MAX_VERY_SLOW = 4
WINDOW_MAX_FAST = 75 WINDOW_MAX_FAST = 75
WINDOW_MAX = WINDOW_MAX_FAST WINDOW_MAX = WINDOW_MAX_FAST
// Rate thresholds // Rate thresholds
FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2 FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2
VERY_SLOW_RATE_THRESHOLD = 2 VERY_SLOW_RATE_THRESHOLD = 2
// Transfer rates (bytes per second) // Transfer rates (bytes per second)
RATE_FAST = (50 * 1000) / 8 // 50 Kbps RATE_FAST = (50 * 1000) / 8 // 50 Kbps
RATE_VERY_SLOW = (2 * 1000) / 8 // 2 Kbps RATE_VERY_SLOW = (2 * 1000) / 8 // 2 Kbps
// Window flexibility // Window flexibility
WINDOW_FLEXIBILITY = 4 WINDOW_FLEXIBILITY = 4
// Hash and segment constants // Hash and segment constants
MAPHASH_LEN = 4 MAPHASH_LEN = 4
RANDOM_HASH_SIZE = 4 RANDOM_HASH_SIZE = 4
// Size limits // Size limits
MAX_EFFICIENT_SIZE = 16*1024*1024 - 1 // ~16MB MAX_EFFICIENT_SIZE = 16*1024*1024 - 1 // ~16MB
AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE
// Timeouts and retries // Timeouts and retries
PART_TIMEOUT_FACTOR = 4 PART_TIMEOUT_FACTOR = 4
PART_TIMEOUT_FACTOR_AFTER_RTT = 2 PART_TIMEOUT_FACTOR_AFTER_RTT = 2
PROOF_TIMEOUT_FACTOR = 3 PROOF_TIMEOUT_FACTOR = 3
MAX_RETRIES = 16 MAX_RETRIES = 16
MAX_ADV_RETRIES = 4 MAX_ADV_RETRIES = 4
SENDER_GRACE_TIME = 10.0 SENDER_GRACE_TIME = 10.0
PROCESSING_GRACE = 1.0 PROCESSING_GRACE = 1.0
RETRY_GRACE_TIME = 0.25 RETRY_GRACE_TIME = 0.25
PER_RETRY_DELAY = 0.5 PER_RETRY_DELAY = 0.5
) )
type Resource struct { type Resource struct {
mutex sync.RWMutex mutex sync.RWMutex
data []byte data []byte
fileHandle io.ReadWriteSeeker fileHandle io.ReadWriteSeeker
fileName string
hash []byte hash []byte
randomHash []byte randomHash []byte
originalHash []byte originalHash []byte
status byte status byte
compressed bool compressed bool
autoCompress bool autoCompress bool
encrypted bool encrypted bool
split bool split bool
segments uint16 segments uint16
segmentIndex uint16 segmentIndex uint16
totalSegments uint16 totalSegments uint16
completedParts map[uint16]bool completedParts map[uint16]bool
transferSize int64 transferSize int64
dataSize int64 dataSize int64
progress float64 progress float64
window int window int
windowMax int windowMax int
windowMin int windowMin int
windowFlexibility int windowFlexibility int
rtt float64 rtt float64
fastRateRounds int fastRateRounds int
verySlowRateRounds int verySlowRateRounds int
createdAt time.Time createdAt time.Time
completedAt time.Time completedAt time.Time
callback func(*Resource) callback func(*Resource)
progressCallback func(*Resource) progressCallback func(*Resource)
} }
func New(data interface{}, autoCompress bool) (*Resource, error) { func New(data interface{}, autoCompress bool) (*Resource, error) {
r := &Resource{ r := &Resource{
status: STATUS_PENDING, status: STATUS_PENDING,
compressed: false, compressed: false,
autoCompress: autoCompress, autoCompress: autoCompress,
completedParts: make(map[uint16]bool), completedParts: make(map[uint16]bool),
createdAt: time.Now(), createdAt: time.Now(),
progress: 0.0, progress: 0.0,
} }
switch v := data.(type) { switch v := data.(type) {
@@ -118,6 +118,10 @@ func New(data interface{}, autoCompress bool) (*Resource, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if namer, ok := v.(interface{ Name() string }); ok {
r.fileName = namer.Name()
}
default: default:
return nil, errors.New("unsupported data type") return nil, errors.New("unsupported data type")
} }
@@ -138,10 +142,10 @@ func New(data interface{}, autoCompress bool) (*Resource, error) {
r.transferSize = int64(float64(r.dataSize) * compressibility) r.transferSize = int64(float64(r.dataSize) * compressibility)
} else if r.fileHandle != nil { } else if r.fileHandle != nil {
// For file handles, use extension-based estimation // For file handles, use extension-based estimation
ext := strings.ToLower(filepath.Ext(r.fileHandle.Name())) ext := strings.ToLower(filepath.Ext(r.fileName))
r.transferSize = estimateFileCompression(r.dataSize, ext) r.transferSize = estimateFileCompression(r.dataSize, ext)
} }
// Ensure minimum size and add compression overhead // Ensure minimum size and add compression overhead
if r.transferSize < r.dataSize/10 { if r.transferSize < r.dataSize/10 {
r.transferSize = r.dataSize / 10 r.transferSize = r.dataSize / 10
@@ -223,7 +227,7 @@ func (r *Resource) IsCompressed() bool {
func (r *Resource) Cancel() { func (r *Resource) Cancel() {
r.mutex.Lock() r.mutex.Lock()
defer r.mutex.Unlock() defer r.mutex.Unlock()
if r.status == STATUS_PENDING || r.status == STATUS_ACTIVE { if r.status == STATUS_PENDING || r.status == STATUS_ACTIVE {
r.status = STATUS_CANCELLED r.status = STATUS_CANCELLED
r.completedAt = time.Now() r.completedAt = time.Now()
@@ -342,13 +346,13 @@ func estimateCompressibility(data []byte) float64 {
if len(data) < sampleSize { if len(data) < sampleSize {
sampleSize = len(data) sampleSize = len(data)
} }
// Count unique bytes in sample // Count unique bytes in sample
uniqueBytes := make(map[byte]struct{}) uniqueBytes := make(map[byte]struct{})
for i := 0; i < sampleSize; i++ { for i := 0; i < sampleSize; i++ {
uniqueBytes[data[i]] = struct{}{} uniqueBytes[data[i]] = struct{}{}
} }
// Calculate entropy-based compression estimate // Calculate entropy-based compression estimate
uniqueRatio := float64(len(uniqueBytes)) / float64(sampleSize) uniqueRatio := float64(len(uniqueBytes)) / float64(sampleSize)
return 0.3 + (0.7 * uniqueRatio) // Base compression ratio between 0.3 and 1.0 return 0.3 + (0.7 * uniqueRatio) // Base compression ratio between 0.3 and 1.0
@@ -357,13 +361,13 @@ func estimateCompressibility(data []byte) float64 {
func estimateFileCompression(size int64, extension string) int64 { func estimateFileCompression(size int64, extension string) int64 {
// Compression ratio estimates based on common file types // Compression ratio estimates based on common file types
compressionRatios := map[string]float64{ compressionRatios := map[string]float64{
".txt": 0.4, // Text compresses well ".txt": 0.4, // Text compresses well
".log": 0.4, ".log": 0.4,
".json": 0.4, ".json": 0.4,
".xml": 0.4, ".xml": 0.4,
".html": 0.4, ".html": 0.4,
".csv": 0.5, ".csv": 0.5,
".doc": 0.8, // Already compressed ".doc": 0.8, // Already compressed
".docx": 0.95, ".docx": 0.95,
".pdf": 0.95, ".pdf": 0.95,
".jpg": 0.99, // Already compressed ".jpg": 0.99, // Already compressed
@@ -376,11 +380,11 @@ func estimateFileCompression(size int64, extension string) int64 {
".gz": 0.99, ".gz": 0.99,
".rar": 0.99, ".rar": 0.99,
} }
ratio, exists := compressionRatios[extension] ratio, exists := compressionRatios[extension]
if !exists { if !exists {
ratio = 0.7 // Default compression ratio for unknown types ratio = 0.7 // Default compression ratio for unknown types
} }
return int64(float64(size) * ratio) return int64(float64(size) * ratio)
} }

View File

@@ -3,11 +3,13 @@ package transport
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"net" "net"
"sync" "sync"
"time" "time"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet"
) )
var ( var (
@@ -19,18 +21,18 @@ const (
PathfinderM = 128 // Maximum number of hops PathfinderM = 128 // Maximum number of hops
PathRequestTTL = 300 // Time to live for path requests in seconds PathRequestTTL = 300 // Time to live for path requests in seconds
AnnounceTimeout = 15 // Timeout for announce responses in seconds AnnounceTimeout = 15 // Timeout for announce responses in seconds
// Link constants // Link constants
EstablishmentTimeoutPerHop = 6 // Timeout for link establishment in seconds per hop EstablishmentTimeoutPerHop = 6 // Timeout for link establishment in seconds per hop
KeepaliveTimeoutFactor = 4 // RTT timeout factor for link timeout calculation KeepaliveTimeoutFactor = 4 // RTT timeout factor for link timeout calculation
StaleGrace = 2 // Grace period in seconds for link timeout StaleGrace = 2 // Grace period in seconds for link timeout
Keepalive = 360 // Interval for sending keep-alive packets in seconds Keepalive = 360 // Interval for sending keep-alive packets in seconds
StaleTime = 720 // Time after which link is considered stale StaleTime = 720 // Time after which link is considered stale
// Direction constants // Direction constants
OUT = 0x02 OUT = 0x02
IN = 0x01 IN = 0x01
// Destination type constants // Destination type constants
SINGLE = 0x00 SINGLE = 0x00
GROUP = 0x01 GROUP = 0x01
@@ -120,9 +122,9 @@ type Link struct {
lastData time.Time lastData time.Time
rtt time.Duration rtt time.Duration
establishedCb func() establishedCb func()
closedCb func() closedCb func()
packetCb func([]byte) packetCb func([]byte)
resourceCb func(interface{}) bool resourceCb func(interface{}) bool
resourceStrategy int resourceStrategy int
} }
@@ -142,7 +144,7 @@ func NewLink(dest []byte, establishedCallback func(), closedCallback func()) *Li
lastOutbound: time.Now(), lastOutbound: time.Now(),
lastData: time.Now(), lastData: time.Now(),
establishedCb: establishedCallback, establishedCb: establishedCallback,
closedCb: closedCallback, closedCb: closedCallback,
} }
} }
@@ -189,17 +191,17 @@ func (l *Link) Teardown() {
func (l *Link) Send(data []byte) error { func (l *Link) Send(data []byte) error {
l.lastOutbound = time.Now() l.lastOutbound = time.Now()
l.lastData = time.Now() l.lastData = time.Now()
packet := &LinkPacket{ packet := &LinkPacket{
Destination: l.destination, Destination: l.destination,
Data: data, Data: data,
Timestamp: time.Now(), Timestamp: time.Now(),
} }
if l.rtt == 0 { if l.rtt == 0 {
l.rtt = l.InactiveFor() l.rtt = l.InactiveFor()
} }
return packet.send() return packet.send()
} }
@@ -227,93 +229,93 @@ func (t *Transport) DeregisterAnnounceHandler(handler AnnounceHandler) {
func (t *Transport) HasPath(destinationHash []byte) bool { func (t *Transport) HasPath(destinationHash []byte) bool {
t.pathLock.RLock() t.pathLock.RLock()
defer t.pathLock.RUnlock() defer t.pathLock.RUnlock()
path, exists := t.paths[string(destinationHash)] path, exists := t.paths[string(destinationHash)]
if !exists { if !exists {
return false return false
} }
// Check if path is still valid (not expired) // Check if path is still valid (not expired)
if time.Since(path.LastUpdated) > time.Duration(PathRequestTTL)*time.Second { if time.Since(path.LastUpdated) > time.Duration(PathRequestTTL)*time.Second {
delete(t.paths, string(destinationHash)) delete(t.paths, string(destinationHash))
return false return false
} }
return true return true
} }
func (t *Transport) HopsTo(destinationHash []byte) uint8 { func (t *Transport) HopsTo(destinationHash []byte) uint8 {
t.pathLock.RLock() t.pathLock.RLock()
defer t.pathLock.RUnlock() defer t.pathLock.RUnlock()
path, exists := t.paths[string(destinationHash)] path, exists := t.paths[string(destinationHash)]
if !exists { if !exists {
return PathfinderM return PathfinderM
} }
return path.Hops return path.Hops
} }
func (t *Transport) NextHop(destinationHash []byte) []byte { func (t *Transport) NextHop(destinationHash []byte) []byte {
t.pathLock.RLock() t.pathLock.RLock()
defer t.pathLock.RUnlock() defer t.pathLock.RUnlock()
path, exists := t.paths[string(destinationHash)] path, exists := t.paths[string(destinationHash)]
if !exists { if !exists {
return nil return nil
} }
return path.NextHop return path.NextHop
} }
func (t *Transport) NextHopInterface(destinationHash []byte) string { func (t *Transport) NextHopInterface(destinationHash []byte) string {
t.pathLock.RLock() t.pathLock.RLock()
defer t.pathLock.RUnlock() defer t.pathLock.RUnlock()
path, exists := t.paths[string(destinationHash)] path, exists := t.paths[string(destinationHash)]
if !exists { if !exists {
return "" return ""
} }
return path.Interface.GetName() return path.Interface.GetName()
} }
func (t *Transport) RequestPath(destinationHash []byte, onInterface string, tag []byte, recursive bool) error { func (t *Transport) RequestPath(destinationHash []byte, onInterface string, tag []byte, recursive bool) error {
packet := &PathRequest{ packet := &PathRequest{
DestinationHash: destinationHash, DestinationHash: destinationHash,
Tag: tag, Tag: tag,
TTL: PathRequestTTL, TTL: PathRequestTTL,
Recursive: recursive, Recursive: recursive,
} }
if onInterface != "" { if onInterface != "" {
return t.sendPathRequest(packet, onInterface) return t.sendPathRequest(packet, onInterface)
} }
return t.broadcastPathRequest(packet) return t.broadcastPathRequest(packet)
} }
func (t *Transport) UpdatePath(destinationHash []byte, nextHop []byte, interfaceName string, hops uint8) { func (t *Transport) UpdatePath(destinationHash []byte, nextHop []byte, interfaceName string, hops uint8) {
t.pathLock.Lock() t.pathLock.Lock()
defer t.pathLock.Unlock() defer t.pathLock.Unlock()
iface, err := t.GetInterface(interfaceName) iface, err := t.GetInterface(interfaceName)
if err != nil { if err != nil {
return return
} }
t.paths[string(destinationHash)] = &common.Path{ t.paths[string(destinationHash)] = &common.Path{
Interface: iface, Interface: iface,
NextHop: nextHop, NextHop: nextHop,
Hops: hops, Hops: hops,
LastUpdated: time.Now(), LastUpdated: time.Now(),
} }
} }
func (t *Transport) HandleAnnounce(destinationHash []byte, identity []byte, appData []byte) { func (t *Transport) HandleAnnounce(destinationHash []byte, identity []byte, appData []byte) {
t.handlerLock.RLock() t.handlerLock.RLock()
defer t.handlerLock.RUnlock() defer t.handlerLock.RUnlock()
for _, handler := range t.announceHandlers { for _, handler := range t.announceHandlers {
handler.ReceivedAnnounce(destinationHash, identity, appData) handler.ReceivedAnnounce(destinationHash, identity, appData)
} }
@@ -335,9 +337,9 @@ func (t *Transport) NewLink(dest []byte, establishedCallback func(), closedCallb
type PathRequest struct { type PathRequest struct {
DestinationHash []byte DestinationHash []byte
Tag []byte Tag []byte
TTL int TTL int
Recursive bool Recursive bool
} }
type LinkPacket struct { type LinkPacket struct {
@@ -354,7 +356,7 @@ func (p *LinkPacket) send() error {
header := make([]byte, 0, 64) header := make([]byte, 0, 64)
header = append(header, 0x02) // Link packet type header = append(header, 0x02) // Link packet type
header = append(header, p.Destination...) header = append(header, p.Destination...)
// Add timestamp // Add timestamp
ts := make([]byte, 8) ts := make([]byte, 8)
binary.BigEndian.PutUint64(ts, uint64(p.Timestamp.Unix())) binary.BigEndian.PutUint64(ts, uint64(p.Timestamp.Unix()))
@@ -417,7 +419,7 @@ func (t *Transport) broadcastPathRequest(req *PathRequest) error {
if !iface.IsEnabled() { if !iface.IsEnabled() {
continue continue
} }
if err := t.sendPathRequest(req, iface.GetName()); err != nil { if err := t.sendPathRequest(req, iface.GetName()); err != nil {
lastErr = err lastErr = err
} }
@@ -434,11 +436,11 @@ type PathRequestPacket struct {
} }
type NetworkInterface struct { type NetworkInterface struct {
Name string Name string
Addr *net.UDPAddr Addr *net.UDPAddr
Conn *net.UDPConn Conn *net.UDPConn
MTU int MTU int
Enabled bool Enabled bool
} }
func SendAnnounce(packet []byte) error { func SendAnnounce(packet []byte) error {
@@ -446,7 +448,7 @@ func SendAnnounce(packet []byte) error {
if t == nil { if t == nil {
return errors.New("transport not initialized") return errors.New("transport not initialized")
} }
// Send announce packet to all interfaces // Send announce packet to all interfaces
var lastErr error var lastErr error
for _, iface := range t.interfaces { for _, iface := range t.interfaces {
@@ -454,7 +456,7 @@ func SendAnnounce(packet []byte) error {
lastErr = err lastErr = err
} }
} }
return lastErr return lastErr
} }
@@ -487,7 +489,7 @@ func (t *Transport) handlePathRequest(data []byte, iface interface{}) {
recursive := false recursive := false
if len(data) > 33 { if len(data) > 33 {
tag = data[33:len(data)-1] tag = data[33 : len(data)-1]
recursive = data[len(data)-1] == 0x01 recursive = data[len(data)-1] == 0x01
} }
@@ -496,7 +498,7 @@ func (t *Transport) handlePathRequest(data []byte, iface interface{}) {
// Create and send path response // Create and send path response
hops := t.HopsTo(destHash) hops := t.HopsTo(destHash)
nextHop := t.NextHop(destHash) nextHop := t.NextHop(destHash)
response := make([]byte, 0, 64) response := make([]byte, 0, 64)
response = append(response, 0x03) // Path Response type response = append(response, 0x03) // Path Response type
response = append(response, destHash...) response = append(response, destHash...)
@@ -514,7 +516,7 @@ func (t *Transport) handlePathRequest(data []byte, iface interface{}) {
newData := make([]byte, len(data)) newData := make([]byte, len(data))
copy(newData, data) copy(newData, data)
newData[32] = ttl - 1 // Decrease TTL newData[32] = ttl - 1 // Decrease TTL
for name, otherIface := range t.interfaces { for name, otherIface := range t.interfaces {
if name != iface.(common.NetworkInterface).GetName() && otherIface.IsEnabled() { if name != iface.(common.NetworkInterface).GetName() && otherIface.IsEnabled() {
otherIface.Send(newData, "") otherIface.Send(newData, "")
@@ -536,7 +538,7 @@ func (t *Transport) handleLinkPacket(data []byte, iface interface{}) {
if t.HasPath(dest) { if t.HasPath(dest) {
nextHop := t.NextHop(dest) nextHop := t.NextHop(dest)
nextIface := t.NextHopInterface(dest) nextIface := t.NextHopInterface(dest)
if iface, ok := t.interfaces[nextIface]; ok { if iface, ok := t.interfaces[nextIface]; ok {
iface.Send(data, string(nextHop)) iface.Send(data, string(nextHop))
} }
@@ -598,8 +600,33 @@ func (t *Transport) handleAnnouncePacket(data []byte, iface interface{}) {
func (t *Transport) findLink(dest []byte) *Link { func (t *Transport) findLink(dest []byte) *Link {
t.mutex.RLock() t.mutex.RLock()
defer t.mutex.RUnlock() defer t.mutex.RUnlock()
// This is a simplified version - you might want to maintain a map of active links // This is a simplified version - you might want to maintain a map of active links
// in the Transport struct for better performance // in the Transport struct for better performance
return nil return nil
} }
func (t *Transport) SendPacket(p *packet.Packet) error {
t.mutex.RLock()
defer t.mutex.RUnlock()
// Serialize packet
data, err := p.Serialize()
if err != nil {
return fmt.Errorf("failed to serialize packet: %w", err)
}
// Find appropriate interface
destHash := p.Addresses[:packet.AddressSize]
path, exists := t.paths[string(destHash)]
if !exists {
return errors.New("no path to destination")
}
// Send through interface
if err := path.Interface.Send(data, ""); err != nil {
return fmt.Errorf("failed to send packet: %w", err)
}
return nil
}