0.2.4
This commit is contained in:
42
To-Do
42
To-Do
@@ -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
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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{})
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
277
pkg/link/link.go
277
pkg/link/link.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user