This commit is contained in:
Sudo-Ivan
2025-01-01 00:40:25 -06:00
parent 785bc7d782
commit 30ea1dd0c7
15 changed files with 1016 additions and 847 deletions

View File

@@ -9,7 +9,7 @@ BINARY_UNIX=$(BINARY_NAME)_unix
BUILD_DIR=bin BUILD_DIR=bin
MAIN_PACKAGES=./cmd/reticulum-go ./cmd/rns-announce MAIN_PACKAGE=./cmd/reticulum-go
ALL_PACKAGES=$$(go list ./... | grep -v /vendor/) ALL_PACKAGES=$$(go list ./... | grep -v /vendor/)
@@ -19,8 +19,7 @@ all: clean deps build test
build: build:
@mkdir -p $(BUILD_DIR) @mkdir -p $(BUILD_DIR)
$(GOBUILD) -o $(BUILD_DIR)/reticulum-go ./cmd/reticulum-go $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME) $(MAIN_PACKAGE)
$(GOBUILD) -o $(BUILD_DIR)/rns-announce ./cmd/rns-announce
clean: clean:
@rm -rf $(BUILD_DIR) @rm -rf $(BUILD_DIR)
@@ -38,24 +37,18 @@ deps:
$(GOMOD) verify $(GOMOD) verify
build-linux: build-linux:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/reticulum-go ./cmd/reticulum-go CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 $(MAIN_PACKAGE)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/rns-announce ./cmd/rns-announce
build-windows: build-windows:
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/reticulum-windows-amd64.exe ./cmd/reticulum-go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe $(MAIN_PACKAGE)
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/rns-announce-windows-amd64.exe ./cmd/rns-announce
build-darwin: build-darwin:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/reticulum-darwin-amd64 ./cmd/reticulum-go CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 $(MAIN_PACKAGE)
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/rns-announce-darwin-amd64 ./cmd/rns-announce
build-all: build-linux build-windows build-darwin build-all: build-linux build-windows build-darwin
run-reticulum: run:
@./$(BUILD_DIR)/reticulum-go @./$(BUILD_DIR)/$(BINARY_NAME)
run-announce:
@./$(BUILD_DIR)/rns-announce
install: install:
$(GOMOD) download $(GOMOD) download
@@ -63,7 +56,7 @@ install:
help: help:
@echo "Available targets:" @echo "Available targets:"
@echo " all - Clean, download dependencies, build and test" @echo " all - Clean, download dependencies, build and test"
@echo " build - Build binaries" @echo " build - Build binary"
@echo " clean - Remove build artifacts" @echo " clean - Remove build artifacts"
@echo " test - Run tests" @echo " test - Run tests"
@echo " coverage - Generate test coverage report" @echo " coverage - Generate test coverage report"
@@ -72,6 +65,5 @@ help:
@echo " build-windows- Build for Windows" @echo " build-windows- Build for Windows"
@echo " build-darwin - Build for MacOS" @echo " build-darwin - Build for MacOS"
@echo " build-all - Build for all platforms" @echo " build-all - Build for all platforms"
@echo " run-reticulum- Run reticulum binary" @echo " run - Run reticulum binary"
@echo " run-announce - Run announce binary"
@echo " install - Install dependencies" @echo " install - Install dependencies"

View File

@@ -1,28 +1,44 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"log" "log"
"math/rand"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"github.com/Sudo-Ivan/reticulum-go/internal/config" "github.com/Sudo-Ivan/reticulum-go/internal/config"
"github.com/Sudo-Ivan/reticulum-go/pkg/announce"
"github.com/Sudo-Ivan/reticulum-go/pkg/buffer" "github.com/Sudo-Ivan/reticulum-go/pkg/buffer"
"github.com/Sudo-Ivan/reticulum-go/pkg/channel" "github.com/Sudo-Ivan/reticulum-go/pkg/channel"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/interfaces" "github.com/Sudo-Ivan/reticulum-go/pkg/interfaces"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet" "github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport" "github.com/Sudo-Ivan/reticulum-go/pkg/transport"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
) )
var (
debugLevel = flag.Int("debug", 4, "Debug level (0-7)")
)
func debugLog(level int, format string, v ...interface{}) {
if *debugLevel >= level {
log.Printf("[DEBUG-%d] %s", level, fmt.Sprintf(format, v...))
}
}
type Reticulum struct { type Reticulum struct {
config *common.ReticulumConfig config *common.ReticulumConfig
transport *transport.Transport transport *transport.Transport
interfaces []interfaces.Interface interfaces []interfaces.Interface
channels map[string]*channel.Channel channels map[string]*channel.Channel
buffers map[string]*buffer.Buffer buffers map[string]*buffer.Buffer
announceHandlers map[string][]announce.AnnounceHandler
pathRequests map[string]*common.PathRequest
} }
func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) { func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
@@ -30,218 +46,163 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
cfg = config.DefaultConfig() cfg = config.DefaultConfig()
} }
t, err := transport.NewTransport(cfg) if err := initializeDirectories(); err != nil {
if err != nil { return nil, fmt.Errorf("failed to initialize directories: %v", err)
return nil, fmt.Errorf("failed to initialize transport: %v", err)
} }
debugLog(3, "Directories initialized")
t := transport.NewTransport(cfg)
debugLog(3, "Transport initialized")
return &Reticulum{ return &Reticulum{
config: cfg, config: cfg,
transport: t, transport: t,
interfaces: make([]interfaces.Interface, 0), interfaces: make([]interfaces.Interface, 0),
channels: make(map[string]*channel.Channel), channels: make(map[string]*channel.Channel),
buffers: make(map[string]*buffer.Buffer), buffers: make(map[string]*buffer.Buffer),
announceHandlers: make(map[string][]announce.AnnounceHandler),
pathRequests: make(map[string]*common.PathRequest),
}, nil }, nil
} }
func (r *Reticulum) handleInterface(iface common.NetworkInterface) { func (r *Reticulum) handleInterface(iface common.NetworkInterface) {
// Create channel using transport wrapper debugLog(2, "Setting up interface %s", iface.GetName())
ch := channel.NewChannel(&transportWrapper{r.transport}) ch := channel.NewChannel(&transportWrapper{r.transport})
r.channels[iface.GetName()] = ch r.channels[iface.GetName()] = ch
debugLog(3, "Created channel for interface %s", iface.GetName())
// Create bidirectional buffer
rw := buffer.CreateBidirectionalBuffer( rw := buffer.CreateBidirectionalBuffer(
1, // Receive stream ID 1,
2, // Send stream ID 2,
ch, ch,
func(size int) { func(size int) {
// Handle data ready callback
data := make([]byte, size) data := make([]byte, size)
iface.ProcessIncoming(data) iface.ProcessIncoming(data)
r.transport.HandlePacket(data, iface)
if len(data) > 0 && data[0] == announce.PACKET_TYPE_ANNOUNCE {
r.handleAnnounce(data, iface)
} else {
r.transport.HandlePacket(data, iface)
}
debugLog(5, "Processed %d bytes from interface %s", size, iface.GetName())
}, },
) )
// Store the buffer
r.buffers[iface.GetName()] = &buffer.Buffer{ r.buffers[iface.GetName()] = &buffer.Buffer{
ReadWriter: rw, ReadWriter: rw,
} }
// Set up packet callback
iface.SetPacketCallback(func(data []byte, ni common.NetworkInterface) { iface.SetPacketCallback(func(data []byte, ni common.NetworkInterface) {
if buf, ok := r.buffers[ni.GetName()]; ok { if buf, ok := r.buffers[ni.GetName()]; ok {
if _, err := buf.Write(data); err != nil { if _, err := buf.Write(data); err != nil {
log.Printf("Error writing to buffer for interface %s: %v", ni.GetName(), err) debugLog(1, "Error writing to buffer for interface %s: %v", ni.GetName(), err)
} }
debugLog(6, "Written %d bytes to interface %s buffer", len(data), ni.GetName())
} }
r.transport.HandlePacket(data, ni) r.transport.HandlePacket(data, ni)
}) })
} }
func (r *Reticulum) Start() error { func (r *Reticulum) monitorInterfaces() {
log.Printf("Starting Reticulum...") ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
if err := r.transport.Start(); err != nil { for range ticker.C {
return fmt.Errorf("failed to start transport: %v", err) for _, iface := range r.interfaces {
} if tcpClient, ok := iface.(*interfaces.TCPClientInterface); ok {
log.Printf("Transport started successfully") debugLog(4, "Interface %s status - Connected: %v, RTT: %v",
iface.GetName(),
for name, ifaceConfig := range r.config.Interfaces { tcpClient.IsConnected(),
if !ifaceConfig.Enabled { tcpClient.GetRTT(),
log.Printf("Skipping disabled interface %s", name) )
continue
}
log.Printf("Configuring interface %s (type=%s)...", name, ifaceConfig.Type)
var iface interfaces.Interface
switch ifaceConfig.Type {
case "TCPClientInterface":
log.Printf("Creating TCP client interface %s -> %s:%d", name, ifaceConfig.TargetHost, ifaceConfig.TargetPort)
client, err := interfaces.NewTCPClient(
ifaceConfig.Name,
ifaceConfig.TargetHost,
ifaceConfig.TargetPort,
ifaceConfig.KISSFraming,
ifaceConfig.I2PTunneled,
ifaceConfig.Enabled,
)
if err != nil {
if r.config.PanicOnInterfaceErr {
return fmt.Errorf("failed to create TCP client interface %s: %v", name, err)
}
log.Printf("Failed to create TCP client interface %s: %v", name, err)
continue
} }
iface = client
case "TCPServerInterface":
log.Printf("Creating TCP server interface %s on %s:%d", name, ifaceConfig.Address, ifaceConfig.Port)
server, err := interfaces.NewTCPServer(
ifaceConfig.Name,
ifaceConfig.Address,
ifaceConfig.Port,
ifaceConfig.KISSFraming,
ifaceConfig.I2PTunneled,
ifaceConfig.PreferIPv6,
)
if err != nil {
if r.config.PanicOnInterfaceErr {
return fmt.Errorf("failed to create TCP server interface %s: %v", name, err)
}
log.Printf("Failed to create TCP server interface %s: %v", name, err)
continue
}
iface = server
case "UDPInterface":
addr := fmt.Sprintf("%s:%d", ifaceConfig.Address, ifaceConfig.Port)
target := ""
if ifaceConfig.TargetAddress != "" {
target = fmt.Sprintf("%s:%d", ifaceConfig.TargetHost, ifaceConfig.TargetPort)
}
log.Printf("Creating UDP interface %s on %s -> %s", name, addr, target)
udp, err := interfaces.NewUDPInterface(
ifaceConfig.Name,
addr,
target,
ifaceConfig.Enabled,
)
if err != nil {
if r.config.PanicOnInterfaceErr {
return fmt.Errorf("failed to create UDP interface %s: %v", name, err)
}
log.Printf("Failed to create UDP interface %s: %v", name, err)
continue
}
iface = udp
case "AutoInterface":
log.Printf("Creating Auto interface %s (group=%s, discovery=%d, data=%d)",
name, ifaceConfig.GroupID, ifaceConfig.DiscoveryPort, ifaceConfig.DataPort)
auto, err := interfaces.NewAutoInterface(
ifaceConfig.Name,
ifaceConfig,
)
if err != nil {
if r.config.PanicOnInterfaceErr {
return fmt.Errorf("failed to create Auto interface %s: %v", name, err)
}
log.Printf("Failed to create Auto interface %s: %v", name, err)
continue
}
iface = auto
default:
log.Printf("Unknown interface type: %s", ifaceConfig.Type)
continue
}
if iface != nil {
log.Printf("Starting interface %s...", name)
if err := iface.Start(); err != nil {
if r.config.PanicOnInterfaceErr {
return fmt.Errorf("failed to start interface %s: %v", name, err)
}
log.Printf("Failed to start interface %s: %v", name, err)
continue
}
netIface := iface.(common.NetworkInterface)
r.handleInterface(netIface)
r.interfaces = append(r.interfaces, iface)
log.Printf("Created and started interface %s (type=%v, enabled=%v)",
iface.GetName(), iface.GetType(), iface.IsEnabled())
log.Printf("Interface %s started successfully", name)
} }
} }
log.Printf("Reticulum initialized with config at: %s", r.config.ConfigPath)
log.Printf("Press Ctrl+C to stop...")
return nil
}
func (r *Reticulum) Stop() error {
// Close all buffers
for _, buf := range r.buffers {
if err := buf.Close(); err != nil {
log.Printf("Error closing buffer: %v", err)
}
}
// Close all channels
for _, ch := range r.channels {
if err := ch.Close(); err != nil {
log.Printf("Error closing channel: %v", err)
}
}
// Stop interfaces
for _, iface := range r.interfaces {
if err := iface.Stop(); err != nil {
log.Printf("Error stopping interface %s: %v", iface.GetName(), err)
}
}
if err := r.transport.Close(); err != nil {
return fmt.Errorf("failed to close transport: %v", err)
}
return nil
} }
func main() { func main() {
log.Printf("Initializing Reticulum...") flag.Parse()
debugLog(1, "Initializing Reticulum (Debug Level: %d)...", *debugLevel)
cfg, err := config.InitConfig() cfg, err := config.InitConfig()
if err != nil { if err != nil {
log.Fatalf("Failed to initialize config: %v", err) log.Fatalf("Failed to initialize config: %v", err)
} }
debugLog(2, "Configuration loaded from: %s", cfg.ConfigPath)
// Add default TCP interfaces if none configured
if len(cfg.Interfaces) == 0 {
debugLog(2, "No interfaces configured, adding default TCP interfaces")
cfg.Interfaces = make(map[string]*common.InterfaceConfig)
cfg.Interfaces["amsterdam"] = &common.InterfaceConfig{
Type: "TCPClientInterface",
Enabled: true,
TargetHost: "amsterdam.connect.reticulum.network",
TargetPort: 4965,
Name: "amsterdam",
}
cfg.Interfaces["btb"] = &common.InterfaceConfig{
Type: "TCPClientInterface",
Enabled: true,
TargetHost: "reticulum.betweentheborders.com",
TargetPort: 4242,
Name: "btb",
}
}
r, err := NewReticulum(cfg) r, err := NewReticulum(cfg)
if err != nil { if err != nil {
log.Fatalf("Failed to create Reticulum instance: %v", err) log.Fatalf("Failed to create Reticulum instance: %v", err)
} }
go r.monitorInterfaces()
// Register announce handler using the instance's transport
handler := &AnnounceHandler{
aspectFilter: []string{"*"}, // Handle all aspects
}
r.transport.RegisterAnnounceHandler(handler)
// Create a destination to announce
dest, err := identity.NewIdentity()
if err != nil {
log.Fatalf("Failed to create identity: %v", err)
}
// Create announce for the destination
announce, err := announce.NewAnnounce(
dest,
[]byte("Reticulum-Go"), // App data
nil, // No ratchet ID
false, // Not a path response
)
if err != nil {
log.Fatalf("Failed to create announce: %v", err)
}
// Propagate announce to all interfaces periodically
go func() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
for _, iface := range r.interfaces {
if netIface, ok := iface.(common.NetworkInterface); ok {
if err := announce.Propagate([]common.NetworkInterface{netIface}); err != nil {
debugLog(1, "Failed to propagate announce: %v", err)
}
}
}
}
}
}()
if err := r.Start(); err != nil { if err := r.Start(); err != nil {
log.Fatalf("Failed to start Reticulum: %v", err) log.Fatalf("Failed to start Reticulum: %v", err)
} }
@@ -250,11 +211,11 @@ func main() {
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan <-sigChan
log.Printf("\nShutting down...") debugLog(1, "Shutting down...")
if err := r.Stop(); err != nil { if err := r.Stop(); err != nil {
log.Printf("Error during shutdown: %v", err) debugLog(1, "Error during shutdown: %v", err)
} }
log.Printf("Goodbye!") debugLog(1, "Goodbye!")
} }
// Update transportWrapper to use packet.Packet // Update transportWrapper to use packet.Packet
@@ -306,3 +267,188 @@ func (tw *transportWrapper) SetPacketTimeout(packet interface{}, callback func(i
func (tw *transportWrapper) SetPacketDelivered(packet interface{}, callback func(interface{})) { func (tw *transportWrapper) SetPacketDelivered(packet interface{}, callback func(interface{})) {
callback(packet) callback(packet)
} }
func initializeDirectories() error {
dirs := []string{
".reticulum-go",
".reticulum-go/storage",
".reticulum-go/storage/destinations",
".reticulum-go/storage/identities",
".reticulum-go/storage/ratchets",
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %v", dir, err)
}
}
return nil
}
func (r *Reticulum) Start() error {
debugLog(2, "Starting Reticulum...")
if err := r.transport.Start(); err != nil {
return fmt.Errorf("failed to start transport: %v", err)
}
debugLog(3, "Transport started successfully")
for name, ifaceConfig := range r.config.Interfaces {
if !ifaceConfig.Enabled {
debugLog(2, "Skipping disabled interface %s", name)
continue
}
debugLog(2, "Configuring interface %s (type=%s)...", name, ifaceConfig.Type)
var iface interfaces.Interface
switch ifaceConfig.Type {
case "TCPClientInterface":
client, err := interfaces.NewTCPClientInterface(
ifaceConfig.Name,
ifaceConfig.TargetHost,
ifaceConfig.TargetPort,
ifaceConfig.KISSFraming,
ifaceConfig.I2PTunneled,
ifaceConfig.Enabled,
)
if err != nil {
if r.config.PanicOnInterfaceErr {
return fmt.Errorf("failed to create TCP client interface %s: %v", name, err)
}
debugLog(1, "Failed to create TCP client interface %s: %v", name, err)
continue
}
iface = client
case "TCPServerInterface":
server, err := interfaces.NewTCPServerInterface(
ifaceConfig.Name,
ifaceConfig.Address,
ifaceConfig.Port,
ifaceConfig.KISSFraming,
ifaceConfig.I2PTunneled,
ifaceConfig.PreferIPv6,
)
if err != nil {
if r.config.PanicOnInterfaceErr {
return fmt.Errorf("failed to create TCP server interface %s: %v", name, err)
}
debugLog(1, "Failed to create TCP server interface %s: %v", name, err)
continue
}
iface = server
}
if iface != nil {
debugLog(2, "Starting interface %s...", name)
if err := iface.Start(); err != nil {
if r.config.PanicOnInterfaceErr {
return fmt.Errorf("failed to start interface %s: %v", name, err)
}
debugLog(1, "Failed to start interface %s: %v", name, err)
continue
}
netIface := iface.(common.NetworkInterface)
r.handleInterface(netIface)
r.interfaces = append(r.interfaces, iface)
debugLog(3, "Interface %s started successfully", name)
}
}
debugLog(2, "Reticulum started successfully")
return nil
}
func (r *Reticulum) Stop() error {
debugLog(2, "Stopping Reticulum...")
for _, buf := range r.buffers {
if err := buf.Close(); err != nil {
debugLog(1, "Error closing buffer: %v", err)
}
}
for _, ch := range r.channels {
if err := ch.Close(); err != nil {
debugLog(1, "Error closing channel: %v", err)
}
}
for _, iface := range r.interfaces {
if err := iface.Stop(); err != nil {
debugLog(1, "Error stopping interface %s: %v", iface.GetName(), err)
}
}
if err := r.transport.Close(); err != nil {
return fmt.Errorf("failed to close transport: %v", err)
}
debugLog(2, "Reticulum stopped successfully")
return nil
}
func (r *Reticulum) handleAnnounce(data []byte, iface common.NetworkInterface) {
a := &announce.Announce{}
if err := a.HandleAnnounce(data); err != nil {
debugLog(1, "Error handling announce: %v", err)
return
}
// Add random delay before propagation (0-2 seconds)
delay := time.Duration(rand.Float64() * 2 * float64(time.Second))
time.Sleep(delay)
// Check interface modes and propagate according to RNS rules
for _, otherIface := range r.interfaces {
if otherIface.GetName() == iface.GetName() {
continue
}
srcMode := iface.GetMode()
dstMode := otherIface.GetMode()
// Skip propagation based on interface modes
if srcMode == common.IF_MODE_ACCESS_POINT && dstMode != common.IF_MODE_FULL {
debugLog(4, "Skipping announce propagation from AP to non-full mode interface")
continue
}
if srcMode == common.IF_MODE_ROAMING && dstMode == common.IF_MODE_ACCESS_POINT {
debugLog(4, "Skipping announce propagation from roaming to AP interface")
continue
}
// Check if interface has bandwidth available for announces
if err := a.Propagate([]common.NetworkInterface{otherIface}); err != nil {
debugLog(1, "Error propagating announce: %v", err)
}
}
}
type AnnounceHandler struct {
aspectFilter []string
}
func (h *AnnounceHandler) AspectFilter() []string {
return h.aspectFilter
}
func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, identity interface{}, appData []byte) error {
debugLog(3, "Received announce from %x", destHash)
if len(appData) > 0 {
debugLog(3, "Announce contained app data: %s", string(appData))
}
if id, ok := identity.([]byte); ok {
debugLog(4, "Identity: %x", id)
}
return nil
}
func (h *AnnounceHandler) ReceivePathResponses() bool {
return true
}

View File

@@ -1,298 +0,0 @@
package main
import (
"encoding/binary"
"errors"
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/Sudo-Ivan/reticulum-go/internal/config"
"github.com/Sudo-Ivan/reticulum-go/pkg/announce"
"github.com/Sudo-Ivan/reticulum-go/pkg/buffer"
"github.com/Sudo-Ivan/reticulum-go/pkg/channel"
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport"
)
type AnnounceClient struct {
config *common.ReticulumConfig
identity *identity.Identity
interval time.Duration
announceID []byte
data string
transport *transport.Transport
channel *channel.Channel
buffer *buffer.RawChannelWriter
done chan struct{}
}
func NewAnnounceClient(cfg *common.ReticulumConfig, interval time.Duration, data string) (*AnnounceClient, error) {
id, err := identity.New()
if err != nil {
return nil, err
}
t, err := transport.NewTransport(cfg)
if err != nil {
return nil, err
}
// Create transport wrapper that implements LinkInterface
tw := &transportWrapper{t}
// Create channel using transport wrapper
ch := channel.NewChannel(tw)
// Create buffer writer for streaming data
writer := buffer.NewRawChannelWriter(1, ch)
announceID := identity.GetRandomHash()
client := &AnnounceClient{
config: cfg,
identity: id,
interval: interval,
announceID: announceID,
data: data,
transport: t,
channel: ch,
buffer: writer,
done: make(chan struct{}),
}
// Register announce handler
handler := &AnnounceHandler{
aspectFilter: []string{"*"},
}
t.RegisterAnnounceHandler(handler)
return client, nil
}
func (c *AnnounceClient) handlePacket(data []byte) error {
if len(data) < 2 {
return errors.New("packet too short")
}
header := data[0]
packetType := header & 0x03 // Extract packet type from header
switch packetType {
case announce.PACKET_TYPE_ANNOUNCE:
log.Printf("Processing announce packet")
return c.processAnnounce(data[1:])
}
return nil
}
func (c *AnnounceClient) processAnnounce(data []byte) error {
if len(data) < 16 {
return errors.New("invalid announce packet length")
}
destHash := data[:16]
announceType := data[16]
if announceType == announce.ANNOUNCE_IDENTITY {
pubKey := data[17:81] // Ed25519 public key is 32 bytes
appDataLen := binary.BigEndian.Uint16(data[81:83])
appData := data[83 : 83+appDataLen]
log.Printf("Received announce from %x", destHash)
log.Printf("Public key: %x", pubKey)
log.Printf("App data: %s", string(appData))
}
return nil
}
func (c *AnnounceClient) Start() error {
if err := c.transport.Start(); err != nil {
return err
}
log.Printf("Starting announce client with interval: %v", c.interval)
log.Printf("Announce data: %s", c.data)
log.Printf("Announce ID: %x", c.announceID)
ticker := time.NewTicker(c.interval)
defer ticker.Stop()
// Initial announce
log.Printf("Sending initial announce...")
if err := c.announce(); err != nil {
return err
}
log.Printf("Initial announce sent successfully")
for {
select {
case <-ticker.C:
log.Printf("Sending periodic announce...")
if err := c.announce(); err != nil {
log.Printf("Failed to send announce: %v", err)
} else {
log.Printf("Announce sent successfully")
}
case <-c.done:
return nil
}
}
}
func (c *AnnounceClient) announce() error {
// Create announce packet
announceData := []byte(c.data)
packet := announce.NewAnnouncePacket(
c.identity.GetPublicKey(),
announceData,
c.announceID,
)
// Write through buffer system
_, err := c.buffer.Write(packet.Data)
if err != nil {
return fmt.Errorf("failed to write announce: %v", err)
}
return nil
}
func (c *AnnounceClient) Stop() {
close(c.done)
c.buffer.Close()
if err := c.transport.Close(); err != nil {
log.Printf("Error closing transport: %v", err)
}
}
// Add AnnounceHandler type
type AnnounceHandler struct {
aspectFilter []string
}
func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, announcedIdentity interface{}, appData []byte) error {
// Type assert the identity if needed
if id, ok := announcedIdentity.(*identity.Identity); ok {
log.Printf("Received announce from %x (Identity: %x)", destHash, id.GetPublicKey())
} else {
log.Printf("Received announce from %x", destHash)
}
log.Printf("App data: %s", string(appData))
return nil
}
func (h *AnnounceHandler) ReceivePathResponses() bool {
return true
}
// Add AspectFilter method to satisfy the interface
func (h *AnnounceHandler) AspectFilter() []string {
return h.aspectFilter
}
// Add transportWrapper implementation
type transportWrapper struct {
*transport.Transport
}
func (tw *transportWrapper) GetRTT() float64 {
return 0.1 // Default value for now
}
func (tw *transportWrapper) RTT() float64 {
return tw.GetRTT()
}
func (tw *transportWrapper) GetStatus() int {
return transport.STATUS_ACTIVE
}
func (tw *transportWrapper) Send(data []byte) interface{} {
p := &packet.Packet{
Header: [2]byte{
packet.PacketTypeData,
0,
},
Data: data,
Addresses: make([]byte, packet.AddressSize),
Context: 0,
Timestamp: time.Now(),
}
err := tw.Transport.SendPacket(p)
if err != nil {
return nil
}
return p
}
func (tw *transportWrapper) Resend(p interface{}) error {
if pkt, ok := p.(*packet.Packet); ok {
return tw.Transport.SendPacket(pkt)
}
return fmt.Errorf("invalid packet type")
}
func (tw *transportWrapper) SetPacketTimeout(p interface{}, callback func(interface{}), timeout time.Duration) {
time.AfterFunc(timeout, func() {
callback(p)
})
}
func (tw *transportWrapper) SetPacketDelivered(p interface{}, callback func(interface{})) {
callback(p)
}
func main() {
interval := flag.Int("interval", 600, "Announce interval in seconds")
announceData := flag.String("data", "Hello Reticulum", "Data to announce")
configPath := flag.String("config", "", "Path to config file")
flag.Parse()
log.Printf("Initializing announce client...")
log.Printf("Config path: %s", *configPath)
log.Printf("Interval: %d seconds", *interval)
log.Printf("Data: %s", *announceData)
cfg, err := config.InitConfig()
if err != nil {
log.Fatalf("Failed to initialize config: %v", err)
}
if *configPath != "" {
cfg.ConfigPath = *configPath
log.Printf("Using custom config path: %s", *configPath)
}
client, err := NewAnnounceClient(cfg, time.Duration(*interval)*time.Second, *announceData)
if err != nil {
log.Fatalf("Failed to create announce client: %v", err)
}
log.Printf("Client created successfully")
log.Printf("Press Ctrl+C to stop...")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
log.Printf("\nShutting down...")
client.Stop()
os.Exit(0)
}()
if err := client.Start(); err != nil {
log.Fatalf("Error running announce client: %v", err)
}
}

View File

@@ -1,31 +0,0 @@
enable_transport = true
share_instance = true
shared_instance_port = 37428
instance_control_port = 37429
panic_on_interface_error = false
loglevel = 4
[interfaces]
[[Local UDP]]
type = "UDPInterface"
interface_enabled = true
address = "0.0.0.0"
port = 37697
[[Auto Discovery]]
type = "AutoInterface"
interface_enabled = true
discovery_port = 29717
data_port = 42672
[interfaces."RNS Testnet Amsterdam"]
type = "TCPClientInterface"
enabled = true
target_host = "amsterdam.connect.reticulum.network"
target_port = 4965
[interfaces."RNS Testnet BetweenTheBorders"]
type = "TCPClientInterface"
enabled = true
target_host = "reticulum.betweentheborders.com"
target_port = 4242

View File

@@ -1,31 +0,0 @@
enable_transport = true
share_instance = true
shared_instance_port = 37430
instance_control_port = 37431
panic_on_interface_error = false
loglevel = 4
[interfaces]
[interfaces."RNS Testnet Amsterdam"]
type = "TCPClientInterface"
enabled = true
target_host = "amsterdam.connect.reticulum.network"
target_port = 4965
[interfaces."RNS Testnet BetweenTheBorders"]
type = "TCPClientInterface"
enabled = true
target_host = "reticulum.betweentheborders.com"
target_port = 4242
[[Local UDP]]
type = "UDPInterface"
interface_enabled = true
address = "0.0.0.0"
port = 37698
[[Auto Discovery]]
type = "AutoInterface"
interface_enabled = true
discovery_port = 29718
data_port = 42673

View File

@@ -54,7 +54,7 @@ type AnnounceHandler interface {
} }
type Announce struct { type Announce struct {
mutex sync.RWMutex mutex *sync.RWMutex
destinationHash []byte destinationHash []byte
identity *identity.Identity identity *identity.Identity
appData []byte appData []byte
@@ -65,6 +65,7 @@ type Announce struct {
retries int retries int
handlers []AnnounceHandler handlers []AnnounceHandler
ratchetID []byte ratchetID []byte
packet []byte
} }
func New(dest *identity.Identity, appData []byte, pathResponse bool) (*Announce, error) { func New(dest *identity.Identity, appData []byte, pathResponse bool) (*Announce, error) {
@@ -73,24 +74,30 @@ func New(dest *identity.Identity, appData []byte, pathResponse bool) (*Announce,
} }
a := &Announce{ a := &Announce{
identity: dest, mutex: &sync.RWMutex{},
appData: appData, identity: dest,
hops: 0, appData: appData,
timestamp: time.Now().Unix(), hops: 0,
pathResponse: pathResponse, timestamp: time.Now().Unix(),
retries: 0, pathResponse: pathResponse,
handlers: make([]AnnounceHandler, 0), retries: 0,
handlers: make([]AnnounceHandler, 0),
} }
// Generate truncated hash // Generate truncated hash from public key
hash := sha256.New() pubKey := dest.GetPublicKey()
hash.Write(dest.GetPublicKey()) hash := sha256.Sum256(pubKey)
a.destinationHash = hash.Sum(nil)[:identity.TRUNCATED_HASHLENGTH/8] a.destinationHash = hash[:identity.TRUNCATED_HASHLENGTH/8]
// Get current ratchet ID if enabled
currentRatchet := dest.GetCurrentRatchetKey()
if currentRatchet != nil {
a.ratchetID = dest.GetRatchetID(currentRatchet)
}
// Sign announce data // Sign announce data
signData := append(a.destinationHash, a.appData...) signData := append(a.destinationHash, a.appData...)
if dest.GetRatchetID(nil) != nil { if a.ratchetID != nil {
a.ratchetID = dest.GetRatchetID(nil)
signData = append(signData, a.ratchetID...) signData = append(signData, a.ratchetID...)
} }
a.signature = dest.Sign(signData) a.signature = dest.Sign(signData)
@@ -147,22 +154,53 @@ func (a *Announce) HandleAnnounce(data []byte) error {
a.mutex.Lock() a.mutex.Lock()
defer a.mutex.Unlock() defer a.mutex.Unlock()
if len(data) < identity.TRUNCATED_HASHLENGTH/8+identity.KEYSIZE/8+1 { // Minimum packet size validation (2 header + 16 hash + 32 pubkey + 1 hops + 2 appdata len + 64 sig)
if len(data) < 117 {
return errors.New("invalid announce data length") return errors.New("invalid announce data length")
} }
destHash := data[:identity.TRUNCATED_HASHLENGTH/8] // Parse header
publicKey := data[identity.TRUNCATED_HASHLENGTH/8 : identity.TRUNCATED_HASHLENGTH/8+identity.KEYSIZE/8] header := data[:2]
hopCount := data[identity.TRUNCATED_HASHLENGTH/8+identity.KEYSIZE/8] hopCount := header[1]
if hopCount > MAX_HOPS { if hopCount > MAX_HOPS {
return errors.New("announce exceeded maximum hop count") return errors.New("announce exceeded maximum hop count")
} }
// Extract app data and signature // Extract fields
dataStart := identity.TRUNCATED_HASHLENGTH/8 + identity.KEYSIZE/8 + 1 destHash := data[2:18]
appData := data[dataStart : len(data)-ed25519.SignatureSize] publicKey := data[18:50]
signature := data[len(data)-ed25519.SignatureSize:] hopsByte := data[50]
// Validate hop count matches header
if hopsByte != hopCount {
return errors.New("inconsistent hop count in packet")
}
// Extract app data length and content
appDataLen := binary.BigEndian.Uint16(data[51:53])
appDataEnd := 53 + int(appDataLen)
if appDataEnd > len(data) {
return errors.New("invalid app data length")
}
appData := data[53:appDataEnd]
// Handle ratchet ID if present
var ratchetID []byte
signatureStart := appDataEnd
remainingBytes := len(data) - appDataEnd
if remainingBytes > ed25519.SignatureSize {
ratchetID = data[appDataEnd : len(data)-ed25519.SignatureSize]
signatureStart = len(data) - ed25519.SignatureSize
}
if signatureStart+ed25519.SignatureSize > len(data) {
return errors.New("invalid signature position")
}
signature := data[signatureStart:]
// Create announced identity // Create announced identity
announcedIdentity := identity.FromPublicKey(publicKey) announcedIdentity := identity.FromPublicKey(publicKey)
@@ -170,10 +208,9 @@ func (a *Announce) HandleAnnounce(data []byte) error {
return errors.New("invalid identity public key") return errors.New("invalid identity public key")
} }
// Verify signature including ratchet if present // Verify signature
signData := append(destHash, appData...) signData := append(destHash, appData...)
if len(appData) > 32 { // Check for ratchet if ratchetID != nil {
ratchetID := appData[len(appData)-32:]
signData = append(signData, ratchetID...) signData = append(signData, ratchetID...)
} }
@@ -227,37 +264,45 @@ func CreateHeader(ifacFlag byte, headerType byte, contextFlag byte, propType byt
func (a *Announce) CreatePacket() []byte { func (a *Announce) CreatePacket() []byte {
packet := make([]byte, 0) packet := make([]byte, 0)
// Create header for announce packet // Create header according to spec
header := CreateHeader( header := CreateHeader(
IFAC_NONE, // No interface authentication IFAC_NONE, // No interface auth
HEADER_TYPE_1, // One address field HEADER_TYPE_1, // One address field
0x00, // Context flag unset 0x00, // Context flag unset
PROP_TYPE_BROADCAST, // Broadcast propagation PROP_TYPE_BROADCAST, // Broadcast propagation
DEST_TYPE_SINGLE, // Single destination DEST_TYPE_SINGLE, // Single destination
PACKET_TYPE_ANNOUNCE, // Announce packet type PACKET_TYPE_ANNOUNCE, // Announce packet type
byte(a.hops), // Current hop count a.hops, // Current hop count
) )
packet = append(packet, header...) packet = append(packet, header...)
// Add destination hash (16 bytes) // Add destination hash (16 bytes)
packet = append(packet, a.destinationHash...) packet = append(packet, a.destinationHash...)
// Add context byte
packet = append(packet, ANNOUNCE_IDENTITY)
// Add public key // Add public key
packet = append(packet, a.identity.GetPublicKey()...) packet = append(packet, a.identity.GetPublicKey()...)
// Add hop count byte
packet = append(packet, byte(a.hops))
// Add app data with length prefix // Add app data with length prefix
if a.appData != nil { appDataLen := make([]byte, 2)
lenBytes := make([]byte, 2) binary.BigEndian.PutUint16(appDataLen, uint16(len(a.appData)))
binary.BigEndian.PutUint16(lenBytes, uint16(len(a.appData))) packet = append(packet, appDataLen...)
packet = append(packet, lenBytes...) packet = append(packet, a.appData...)
packet = append(packet, a.appData...)
// Add ratchet ID if present
if a.ratchetID != nil {
packet = append(packet, a.ratchetID...)
} }
// Add signature // Add signature
packet = append(packet, a.signature...) signData := append(a.destinationHash, a.appData...)
if a.ratchetID != nil {
signData = append(signData, a.ratchetID...)
}
signature := a.identity.Sign(signData)
packet = append(packet, signature...)
return packet return packet
} }
@@ -268,25 +313,82 @@ type AnnouncePacket struct {
func NewAnnouncePacket(pubKey []byte, appData []byte, announceID []byte) *AnnouncePacket { func NewAnnouncePacket(pubKey []byte, appData []byte, announceID []byte) *AnnouncePacket {
packet := &AnnouncePacket{} packet := &AnnouncePacket{}
// Build packet data // Build packet data
packet.Data = make([]byte, 0, len(pubKey)+len(appData)+len(announceID)+4) packet.Data = make([]byte, 0, len(pubKey)+len(appData)+len(announceID)+4)
// Add header // Add header
packet.Data = append(packet.Data, PACKET_TYPE_ANNOUNCE) packet.Data = append(packet.Data, PACKET_TYPE_ANNOUNCE)
packet.Data = append(packet.Data, ANNOUNCE_IDENTITY) packet.Data = append(packet.Data, ANNOUNCE_IDENTITY)
// Add public key // Add public key
packet.Data = append(packet.Data, pubKey...) packet.Data = append(packet.Data, pubKey...)
// Add app data length and content // Add app data length and content
appDataLen := make([]byte, 2) appDataLen := make([]byte, 2)
binary.BigEndian.PutUint16(appDataLen, uint16(len(appData))) binary.BigEndian.PutUint16(appDataLen, uint16(len(appData)))
packet.Data = append(packet.Data, appDataLen...) packet.Data = append(packet.Data, appDataLen...)
packet.Data = append(packet.Data, appData...) packet.Data = append(packet.Data, appData...)
// Add announce ID // Add announce ID
packet.Data = append(packet.Data, announceID...) packet.Data = append(packet.Data, announceID...)
return packet return packet
} }
// NewAnnounce creates a new announce packet for a destination
func NewAnnounce(identity *identity.Identity, appData []byte, ratchetID []byte, pathResponse bool) (*Announce, error) {
if identity == nil {
return nil, errors.New("identity cannot be nil")
}
a := &Announce{
identity: identity,
appData: appData,
ratchetID: ratchetID,
pathResponse: pathResponse,
destinationHash: identity.Hash(),
hops: 0,
mutex: &sync.RWMutex{},
handlers: make([]AnnounceHandler, 0),
}
// Create announce packet
packet := make([]byte, 0)
// Add header (2 bytes)
packet = append(packet, PACKET_TYPE_ANNOUNCE)
packet = append(packet, byte(a.hops))
// Add destination hash (16 bytes)
packet = append(packet, a.destinationHash...)
// Add public key (32 bytes)
packet = append(packet, identity.GetPublicKey()...)
// Add hop count (1 byte)
packet = append(packet, byte(a.hops))
// Add app data with length prefix (2 bytes + data)
appDataLen := make([]byte, 2)
binary.BigEndian.PutUint16(appDataLen, uint16(len(appData)))
packet = append(packet, appDataLen...)
packet = append(packet, appData...)
// Add ratchet ID if present
if ratchetID != nil {
packet = append(packet, ratchetID...)
}
// Add signature
signData := append(a.destinationHash, appData...)
if ratchetID != nil {
signData = append(signData, ratchetID...)
}
signature := identity.Sign(signData)
packet = append(packet, signature...)
a.packet = packet
return a, nil
}

5
pkg/announce/handler.go Normal file
View File

@@ -0,0 +1,5 @@
package announce
type Handler interface {
ReceivedAnnounce(destHash []byte, identity interface{}, appData []byte) error
}

View File

@@ -15,6 +15,7 @@ type Path struct {
NextHop []byte NextHop []byte
Hops uint8 Hops uint8
LastUpdated time.Time LastUpdated time.Time
HopCount uint8
} }
// Common callbacks // Common callbacks

View File

@@ -17,24 +17,24 @@ import (
"encoding/hex" "encoding/hex"
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
"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 (
CURVE = "Curve25519" CURVE = "Curve25519"
KEYSIZE = 512 // Combined length of encryption key (256) and signing key (256) KEYSIZE = 512 // Combined length of encryption key (256) and signing key (256)
RATCHETSIZE = 256 RATCHETSIZE = 256
RATCHET_EXPIRY = 2592000 // 30 days in seconds RATCHET_EXPIRY = 2592000 // 30 days in seconds
TRUNCATED_HASHLENGTH = 128 TRUNCATED_HASHLENGTH = 128
NAME_HASH_LENGTH = 80 NAME_HASH_LENGTH = 80
// Token constants for Fernet-like spec // Token constants for Fernet-like spec
TOKEN_OVERHEAD = 16 // AES block size TOKEN_OVERHEAD = 16 // AES block size
AES128_BLOCKSIZE = 16 AES128_BLOCKSIZE = 16
HASHLENGTH = 256 HASHLENGTH = 256
SIGLENGTH = KEYSIZE SIGLENGTH = KEYSIZE
) )
type Identity struct { type Identity struct {
@@ -45,10 +45,10 @@ type Identity struct {
hash []byte hash []byte
hexHash string hexHash string
appData []byte appData []byte
ratchets map[string][]byte ratchets map[string][]byte
ratchetExpiry map[string]int64 ratchetExpiry map[string]int64
mutex sync.RWMutex mutex *sync.RWMutex
} }
var ( var (
@@ -170,7 +170,7 @@ func (i *Identity) Encrypt(plaintext []byte, ratchet []byte) ([]byte, error) {
if _, err := io.ReadFull(rand.Reader, ephemeralPrivKey); err != nil { if _, err := io.ReadFull(rand.Reader, ephemeralPrivKey); err != nil {
return nil, err return nil, err
} }
ephemeralPubKey, err := curve25519.X25519(ephemeralPrivKey, curve25519.Basepoint) ephemeralPubKey, err := curve25519.X25519(ephemeralPrivKey, curve25519.Basepoint)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -183,7 +183,7 @@ func (i *Identity) Encrypt(plaintext []byte, ratchet []byte) ([]byte, error) {
} }
// Generate shared secret // Generate shared secret
sharedSecret, err := curve25519.X25519(ephemeralPrivKey, targetKey) sharedSecret, err := curve25519.X25519(ephemeralPrivKey, targetKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -255,15 +255,15 @@ func GetRandomHash() []byte {
return TruncatedHash(randomData) return TruncatedHash(randomData)
} }
func Remember(packetHash, destHash []byte, publicKey []byte, appData []byte) { func Remember(packet []byte, destHash []byte, publicKey []byte, appData []byte) {
if len(destHash) > TRUNCATED_HASHLENGTH/8 { hashStr := hex.EncodeToString(destHash)
destHash = destHash[:TRUNCATED_HASHLENGTH/8]
}
knownDestinations[string(destHash)] = []interface{}{ // Store destination data as [packet, destHash, identity, appData]
time.Now().Unix(), id := FromPublicKey(publicKey)
packetHash, knownDestinations[hashStr] = []interface{}{
publicKey, packet,
destHash,
id,
appData, appData,
} }
} }
@@ -464,7 +464,7 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
return nil, nil, err return nil, nil, err
} }
ratchetID := i.GetRatchetID(ratchetPubBytes) ratchetID := i.GetRatchetID(ratchetPubBytes)
// Generate shared key // Generate shared key
sharedKey, err := curve25519.X25519(ratchetPriv, peerPubBytes) sharedKey, err := curve25519.X25519(ratchetPriv, peerPubBytes)
if err != nil { if err != nil {
@@ -618,3 +618,71 @@ func (i *Identity) GetRatchetID(ratchetPubBytes []byte) []byte {
hash := sha256.Sum256(ratchetPubBytes) hash := sha256.Sum256(ratchetPubBytes)
return hash[:NAME_HASH_LENGTH/8] return hash[:NAME_HASH_LENGTH/8]
} }
func GetKnownDestination(hash string) ([]interface{}, bool) {
if data, exists := knownDestinations[hash]; exists {
return data, true
}
return nil, false
}
func (i *Identity) GetHexHash() string {
if i.hexHash == "" {
i.hexHash = hex.EncodeToString(i.Hash())
}
return i.hexHash
}
func (i *Identity) GetRatchetKey(id string) ([]byte, bool) {
ratchetPersistLock.Lock()
defer ratchetPersistLock.Unlock()
key, exists := knownRatchets[id]
return key, exists
}
func (i *Identity) SetRatchetKey(id string, key []byte) {
ratchetPersistLock.Lock()
defer ratchetPersistLock.Unlock()
knownRatchets[id] = key
}
// NewIdentity creates a new Identity instance with fresh keys
func NewIdentity() (*Identity, error) {
// Generate Ed25519 signing keypair
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate Ed25519 keypair: %v", err)
}
// Generate X25519 encryption keypair
var encPrivKey [32]byte
if _, err := io.ReadFull(rand.Reader, encPrivKey[:]); err != nil {
return nil, fmt.Errorf("failed to generate X25519 private key: %v", err)
}
encPubKey, err := curve25519.X25519(encPrivKey[:], curve25519.Basepoint)
if err != nil {
return nil, fmt.Errorf("failed to generate X25519 public key: %v", err)
}
i := &Identity{
privateKey: encPrivKey[:],
publicKey: encPubKey,
signingKey: privKey,
verificationKey: pubKey,
ratchets: make(map[string][]byte),
ratchetExpiry: make(map[string]int64),
mutex: &sync.RWMutex{},
}
// Generate hash
combinedPub := make([]byte, KEYSIZE/8)
copy(combinedPub[:KEYSIZE/16], i.publicKey)
copy(combinedPub[KEYSIZE/16:], i.verificationKey)
hash := sha256.Sum256(combinedPub)
i.hash = hash[:]
return i, nil
}

View File

@@ -4,7 +4,9 @@ import (
"fmt" "fmt"
"net" "net"
"sync" "sync"
"syscall"
"time" "time"
"unsafe"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
) )
@@ -25,6 +27,8 @@ const (
TCP_PROBES = 12 TCP_PROBES = 12
RECONNECT_WAIT = 5 RECONNECT_WAIT = 5
INITIAL_TIMEOUT = 5 INITIAL_TIMEOUT = 5
INITIAL_BACKOFF = time.Second
MAX_BACKOFF = time.Minute * 5
) )
type TCPClientInterface struct { type TCPClientInterface struct {
@@ -45,15 +49,18 @@ type TCPClientInterface struct {
enabled bool enabled bool
} }
func NewTCPClient(name string, targetHost string, targetPort int, kissFraming bool, i2pTunneled bool, enabled bool) (*TCPClientInterface, error) { func NewTCPClientInterface(name string, targetHost string, targetPort int, kissFraming bool, i2pTunneled bool, enabled bool) (*TCPClientInterface, error) {
tc := &TCPClientInterface{ tc := &TCPClientInterface{
BaseInterface: NewBaseInterface(name, common.IF_TYPE_TCP, enabled), BaseInterface: NewBaseInterface(name, common.IF_TYPE_TCP, enabled),
targetAddr: targetHost, targetAddr: targetHost,
targetPort: targetPort, targetPort: targetPort,
kissFraming: kissFraming, kissFraming: kissFraming,
i2pTunneled: i2pTunneled, i2pTunneled: i2pTunneled,
initiator: true, initiator: true,
enabled: enabled, enabled: enabled,
maxReconnectTries: TCP_PROBES,
packetBuffer: make([]byte, 0),
neverConnected: true,
} }
if enabled { if enabled {
@@ -64,6 +71,7 @@ func NewTCPClient(name string, targetHost string, targetPort int, kissFraming bo
} }
tc.conn = conn tc.conn = conn
tc.Online = true tc.Online = true
go tc.readLoop()
} }
return tc, nil return tc, nil
@@ -79,6 +87,7 @@ func (tc *TCPClientInterface) Start() error {
if tc.conn != nil { if tc.conn != nil {
tc.Online = true tc.Online = true
go tc.readLoop()
return nil return nil
} }
@@ -89,6 +98,7 @@ func (tc *TCPClientInterface) Start() error {
} }
tc.conn = conn tc.conn = conn
tc.Online = true tc.Online = true
go tc.readLoop()
return nil return nil
} }
@@ -166,17 +176,26 @@ func (tc *TCPClientInterface) handlePacket(data []byte) {
return return
} }
packetType := data[0] tc.mutex.Lock()
tc.packetType = data[0]
tc.mutex.Unlock()
payload := data[1:] payload := data[1:]
switch packetType { switch tc.packetType {
case 0x01: // Path request case 0x01: // Announce packet
tc.BaseInterface.ProcessIncoming(payload) if len(payload) >= 53 { // Minimum announce size
tc.BaseInterface.ProcessIncoming(payload)
}
case 0x02: // Link packet case 0x02: // Link packet
if len(payload) < 40 { // minimum size for link packet if len(payload) < 40 { // minimum size for link packet
return return
} }
tc.BaseInterface.ProcessIncoming(payload) tc.BaseInterface.ProcessIncoming(payload)
case 0x03: // Announce packet
tc.BaseInterface.ProcessIncoming(payload)
case 0x04: // Transport packet
tc.BaseInterface.ProcessIncoming(payload)
default: default:
// Unknown packet type // Unknown packet type
return return
@@ -286,38 +305,53 @@ func (tc *TCPClientInterface) reconnect() {
tc.reconnecting = true tc.reconnecting = true
tc.mutex.Unlock() tc.mutex.Unlock()
backoff := time.Second
maxBackoff := time.Minute * 5
retries := 0 retries := 0
for retries < tc.maxReconnectTries { for retries < tc.maxReconnectTries {
tc.teardown() tc.teardown()
addr := fmt.Sprintf("%s:%d", tc.targetAddr, tc.targetPort) addr := fmt.Sprintf("%s:%d", tc.targetAddr, tc.targetPort)
conn, err := net.Dial("tcp", addr) conn, err := net.Dial("tcp", addr)
if err == nil { if err == nil {
tc.mutex.Lock() tc.mutex.Lock()
tc.conn = conn tc.conn = conn
tc.Online = true tc.Online = true
tc.neverConnected = false tc.neverConnected = false
tc.reconnecting = false tc.reconnecting = false
tc.mutex.Unlock() tc.mutex.Unlock()
// Restart read loop
go tc.readLoop() go tc.readLoop()
return return
} }
retries++ // Log reconnection attempt
// Wait before retrying fmt.Printf("Failed to reconnect to %s (attempt %d/%d): %v\n",
select { addr, retries+1, tc.maxReconnectTries, err)
case <-time.After(RECONNECT_WAIT * time.Second):
continue // Wait with exponential backoff
time.Sleep(backoff)
// Increase backoff time exponentially
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
} }
retries++
} }
// Failed to reconnect after max retries
tc.mutex.Lock() tc.mutex.Lock()
tc.reconnecting = false tc.reconnecting = false
tc.mutex.Unlock() tc.mutex.Unlock()
// If we've exhausted all retries, perform final teardown
tc.teardown() tc.teardown()
fmt.Printf("Failed to reconnect to %s after %d attempts\n",
fmt.Sprintf("%s:%d", tc.targetAddr, tc.targetPort), tc.maxReconnectTries)
} }
func (tc *TCPClientInterface) Enable() { func (tc *TCPClientInterface) Enable() {
@@ -332,6 +366,55 @@ func (tc *TCPClientInterface) Disable() {
tc.Online = false tc.Online = false
} }
func (tc *TCPClientInterface) IsConnected() bool {
tc.mutex.RLock()
defer tc.mutex.RUnlock()
return tc.conn != nil && tc.Online && !tc.reconnecting
}
func getRTTFromSocket(fd uintptr) time.Duration {
var info syscall.TCPInfo
size := uint32(syscall.SizeofTCPInfo)
_, _, err := syscall.Syscall6(
syscall.SYS_GETSOCKOPT,
fd,
syscall.SOL_TCP,
syscall.TCP_INFO,
uintptr(unsafe.Pointer(&info)),
uintptr(unsafe.Pointer(&size)),
0,
)
if err != 0 {
return 0
}
// RTT is in microseconds, convert to Duration
return time.Duration(info.Rtt) * time.Microsecond
}
func (tc *TCPClientInterface) GetRTT() time.Duration {
tc.mutex.RLock()
defer tc.mutex.RUnlock()
if !tc.IsConnected() {
return 0
}
if tcpConn, ok := tc.conn.(*net.TCPConn); ok {
var rtt time.Duration
if info, err := tcpConn.SyscallConn(); err == nil {
info.Control(func(fd uintptr) {
rtt = getRTTFromSocket(fd)
})
return rtt
}
}
return 0
}
type TCPServerInterface struct { type TCPServerInterface struct {
BaseInterface BaseInterface
connections map[string]net.Conn connections map[string]net.Conn
@@ -344,7 +427,7 @@ type TCPServerInterface struct {
packetCallback common.PacketCallback packetCallback common.PacketCallback
} }
func NewTCPServer(name string, bindAddr string, bindPort int, kissFraming bool, i2pTunneled bool, preferIPv6 bool) (*TCPServerInterface, error) { func NewTCPServerInterface(name string, bindAddr string, bindPort int, kissFraming bool, i2pTunneled bool, preferIPv6 bool) (*TCPServerInterface, error) {
ts := &TCPServerInterface{ ts := &TCPServerInterface{
BaseInterface: BaseInterface{ BaseInterface: BaseInterface{
Name: name, Name: name,

View File

@@ -1,9 +1,8 @@
package packet package packet
import ( import (
"crypto/rand"
"encoding/binary"
"errors" "errors"
"time"
) )
const ( const (
@@ -38,6 +37,7 @@ const (
HopsField = 0xFF HopsField = 0xFF
) )
// Packet represents a network packet in the Reticulum protocol
type Packet struct { type Packet struct {
Header [2]byte Header [2]byte
Addresses []byte Addresses []byte
@@ -45,48 +45,10 @@ type Packet struct {
Data []byte Data []byte
AccessCode []byte AccessCode []byte
RandomBlob []byte RandomBlob []byte
Timestamp time.Time
} }
func NewAnnouncePacket(destHash []byte, publicKey []byte, appData []byte) (*Packet, error) { // NewPacket creates a new packet with the specified parameters
p := &Packet{
Header: [2]byte{0, 0}, // Start with 0 hops
Addresses: make([]byte, AddressSize),
Data: make([]byte, 0, MaxDataSize),
}
// Set header flags for announce packet
p.Header[0] |= HeaderTypeFlag // Single address
p.Header[0] |= (PropagationBroadcast << 3) & PropagationFlags // Broadcast
p.Header[0] |= (DestinationSingle << 1) & DestinationFlags // Single destination
p.Header[0] |= PacketTypeAnnounce & PacketTypeFlags // Announce type
// 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) { func NewPacket(packetType byte, flags byte, hops byte, destKey []byte, data []byte) (*Packet, error) {
if len(destKey) != AddressSize { if len(destKey) != AddressSize {
return nil, errors.New("invalid destination key length") return nil, errors.New("invalid destination key length")
@@ -96,6 +58,7 @@ func NewPacket(packetType byte, flags byte, hops byte, destKey []byte, data []by
Header: [2]byte{flags, hops}, Header: [2]byte{flags, hops},
Addresses: make([]byte, AddressSize), Addresses: make([]byte, AddressSize),
Data: data, Data: data,
Timestamp: time.Now(),
} }
// Set packet type in flags // Set packet type in flags
@@ -107,38 +70,7 @@ func NewPacket(packetType byte, flags byte, hops byte, destKey []byte, data []by
return p, nil return p, nil
} }
func (p *Packet) SetAccessCode(code []byte) { // Serialize converts the packet into a byte slice
p.AccessCode = code
p.Header[0] |= IFACFlag
}
func (p *Packet) SetContext(context byte) {
p.Context = context
p.Header[0] |= ContextFlag
}
func (p *Packet) SetData(data []byte) error {
if len(data) > MaxDataSize {
return errors.New("data exceeds maximum allowed size")
}
p.Data = data
return nil
}
func (p *Packet) SetAddress(index int, address []byte) error {
if len(address) != AddressSize {
return errors.New("invalid address size")
}
offset := index * AddressSize
if offset+AddressSize > len(p.Addresses) {
return errors.New("address index out of range")
}
copy(p.Addresses[offset:], address)
return nil
}
func (p *Packet) Serialize() ([]byte, error) { func (p *Packet) Serialize() ([]byte, error) {
totalSize := HeaderSize + len(p.Addresses) + ContextSize + len(p.Data) totalSize := HeaderSize + len(p.Addresses) + ContextSize + len(p.Data)
if p.AccessCode != nil { if p.AccessCode != nil {
@@ -172,46 +104,13 @@ func (p *Packet) Serialize() ([]byte, error) {
return buffer, nil return buffer, nil
} }
func ParsePacket(data []byte) (*Packet, error) { type AnnouncePacket struct {
if len(data) < HeaderSize { Header [2]byte
return nil, errors.New("packet data too short") DestHash []byte
} PublicKey []byte
AppData []byte
p := &Packet{ RandomBlob []byte
Header: [2]byte{data[0], data[1]}, Signature []byte
} HopCount byte
Timestamp time.Time
offset := HeaderSize
// Handle access code if present
if p.Header[0]&IFACFlag != 0 {
// Access code handling would go here
// For now, we'll assume no access code
return nil, errors.New("access code handling not implemented")
}
// Determine address size based on header type
addrLen := AddressSize
if p.Header[0]&HeaderTypeFlag != 0 {
addrLen = 2 * AddressSize
}
if len(data[offset:]) < addrLen+ContextSize {
return nil, errors.New("packet data too short for addresses and context")
}
// Copy addresses
p.Addresses = make([]byte, addrLen)
copy(p.Addresses, data[offset:offset+addrLen])
offset += addrLen
// Copy context
p.Context = data[offset]
offset++
// Copy remaining data
p.Data = make([]byte, len(data)-offset)
copy(p.Data, data[offset:])
return p, nil
} }

View File

@@ -0,0 +1,34 @@
package pathfinder
import "time"
type PathFinder struct {
paths map[string]Path
}
type Path struct {
NextHop []byte
Interface string
HopCount byte
LastUpdated int64
}
func NewPathFinder() *PathFinder {
return &PathFinder{
paths: make(map[string]Path),
}
}
func (p *PathFinder) AddPath(destHash string, nextHop []byte, iface string, hops byte) {
p.paths[destHash] = Path{
NextHop: nextHop,
Interface: iface,
HopCount: hops,
LastUpdated: time.Now().Unix(),
}
}
func (p *PathFinder) GetPath(destHash string) (Path, bool) {
path, exists := p.paths[destHash]
return path, exists
}

44
pkg/rate/rate.go Normal file
View File

@@ -0,0 +1,44 @@
package rate
import (
"sync"
"time"
)
type Limiter struct {
rate float64
interval time.Duration
lastUpdate time.Time
allowance float64
mutex sync.Mutex
}
func NewLimiter(rate float64, interval time.Duration) *Limiter {
return &Limiter{
rate: rate,
interval: interval,
lastUpdate: time.Now(),
allowance: rate,
}
}
func (l *Limiter) Allow() bool {
l.mutex.Lock()
defer l.mutex.Unlock()
now := time.Now()
elapsed := now.Sub(l.lastUpdate)
l.lastUpdate = now
l.allowance += elapsed.Seconds() * l.rate
if l.allowance > l.rate {
l.allowance = l.rate
}
if l.allowance < 1.0 {
return false
}
l.allowance -= 1.0
return true
}

74
pkg/resolver/resolver.go Normal file
View File

@@ -0,0 +1,74 @@
package resolver
import (
"crypto/sha256"
"encoding/hex"
"errors"
"strings"
"sync"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
)
type Resolver struct {
cache map[string]*identity.Identity
cacheLock sync.RWMutex
}
func New() *Resolver {
return &Resolver{
cache: make(map[string]*identity.Identity),
}
}
func (r *Resolver) ResolveIdentity(fullName string) (*identity.Identity, error) {
if fullName == "" {
return nil, errors.New("empty identity name")
}
r.cacheLock.RLock()
if cachedIdentity, exists := r.cache[fullName]; exists {
r.cacheLock.RUnlock()
return cachedIdentity, nil
}
r.cacheLock.RUnlock()
// Hash the full name to create a deterministic identity
h := sha256.New()
h.Write([]byte(fullName))
nameHash := h.Sum(nil)[:identity.NAME_HASH_LENGTH/8]
hashStr := hex.EncodeToString(nameHash)
// Check if this identity is known
if knownData, exists := identity.GetKnownDestination(hashStr); exists {
if id, ok := knownData[2].(*identity.Identity); ok {
r.cacheLock.Lock()
r.cache[fullName] = id
r.cacheLock.Unlock()
return id, nil
}
}
// Split name into parts for hierarchical resolution
parts := strings.Split(fullName, ".")
if len(parts) < 2 {
return nil, errors.New("invalid identity name format")
}
// Create new identity if not found
id, err := identity.New()
if err != nil {
return nil, err
}
r.cacheLock.Lock()
r.cache[fullName] = id
r.cacheLock.Unlock()
return id, nil
}
func ResolveIdentity(fullName string) (*identity.Identity, error) {
r := New()
return r.ResolveIdentity(fullName)
}

View File

@@ -1,16 +1,21 @@
package transport package transport
import ( import (
"crypto/sha256"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"log"
"math/rand"
"net" "net"
"sync" "sync"
"time" "time"
"github.com/Sudo-Ivan/reticulum-go/pkg/announce"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
"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/pathfinder"
"github.com/Sudo-Ivan/reticulum-go/pkg/rate"
) )
var ( var (
@@ -56,6 +61,13 @@ const (
STATUS_ACTIVE = 1 STATUS_ACTIVE = 1
STATUS_CLOSED = 2 STATUS_CLOSED = 2
STATUS_FAILED = 3 STATUS_FAILED = 3
AnnounceRatePercent = 2.0 // 2% of bandwidth for announces
PATHFINDER_M = 8 // Maximum hop count
AnnounceRateKbps = 20.0 // 20 Kbps for announces
MAX_HOPS = 128 // Default m value for announce propagation
PROPAGATION_RATE = 0.02 // 2% bandwidth cap for announces
) )
type PathInfo struct { type PathInfo struct {
@@ -66,29 +78,35 @@ type PathInfo struct {
} }
type Transport struct { type Transport struct {
mutex sync.RWMutex
config *common.ReticulumConfig config *common.ReticulumConfig
interfaces map[string]common.NetworkInterface interfaces map[string]common.NetworkInterface
paths map[string]*common.Path
announceHandlers []AnnounceHandler
mutex sync.RWMutex
handlerLock sync.RWMutex
pathLock sync.RWMutex
links map[string]*Link links map[string]*Link
announceRate *rate.Limiter
seenAnnounces map[string]bool
pathfinder *pathfinder.PathFinder
announceHandlers []announce.Handler
paths map[string]*common.Path
} }
func NewTransport(config *common.ReticulumConfig) (*Transport, error) { type Path struct {
NextHop []byte
Interface common.NetworkInterface
HopCount byte
}
func NewTransport(cfg *common.ReticulumConfig) *Transport {
t := &Transport{ t := &Transport{
config: config, interfaces: make(map[string]common.NetworkInterface),
interfaces: make(map[string]common.NetworkInterface), paths: make(map[string]*common.Path),
paths: make(map[string]*common.Path), seenAnnounces: make(map[string]bool),
links: make(map[string]*Link), announceRate: rate.NewLimiter(PROPAGATION_RATE, 1),
mutex: sync.RWMutex{},
config: cfg,
links: make(map[string]*Link),
pathfinder: pathfinder.NewPathFinder(),
} }
return t
transportMutex.Lock()
transportInstance = t
transportMutex.Unlock()
return t, nil
} }
// Add GetTransportInstance function // Add GetTransportInstance function
@@ -249,40 +267,39 @@ func (l *Link) Send(data []byte) interface{} {
return packet return packet
} }
type AnnounceHandler interface { func (t *Transport) RegisterAnnounceHandler(handler announce.Handler) {
AspectFilter() []string t.mutex.Lock()
ReceivedAnnounce(destinationHash []byte, announcedIdentity interface{}, appData []byte) error defer t.mutex.Unlock()
ReceivePathResponses() bool
}
func (t *Transport) RegisterAnnounceHandler(handler AnnounceHandler) {
t.handlerLock.Lock()
defer t.handlerLock.Unlock()
// Check for duplicate handlers
for _, h := range t.announceHandlers {
if h == handler {
return
}
}
t.announceHandlers = append(t.announceHandlers, handler) t.announceHandlers = append(t.announceHandlers, handler)
} }
func (t *Transport) DeregisterAnnounceHandler(handler AnnounceHandler) { func (t *Transport) UnregisterAnnounceHandler(handler announce.Handler) {
t.handlerLock.Lock() t.mutex.Lock()
defer t.handlerLock.Unlock() defer t.mutex.Unlock()
for i, h := range t.announceHandlers { for i, h := range t.announceHandlers {
if h == handler { if h == handler {
t.announceHandlers = append(t.announceHandlers[:i], t.announceHandlers[i+1:]...) t.announceHandlers = append(t.announceHandlers[:i], t.announceHandlers[i+1:]...)
return break
}
}
}
func (t *Transport) notifyAnnounceHandlers(destHash []byte, identity interface{}, appData []byte) {
t.mutex.RLock()
handlers := make([]announce.Handler, len(t.announceHandlers))
copy(handlers, t.announceHandlers)
t.mutex.RUnlock()
for _, handler := range handlers {
if err := handler.ReceivedAnnounce(destHash, identity, appData); err != nil {
log.Printf("Error in announce handler: %v", err)
} }
} }
} }
func (t *Transport) HasPath(destinationHash []byte) bool { func (t *Transport) HasPath(destinationHash []byte) bool {
t.pathLock.RLock() t.mutex.RLock()
defer t.pathLock.RUnlock() defer t.mutex.RUnlock()
path, exists := t.paths[string(destinationHash)] path, exists := t.paths[string(destinationHash)]
if !exists { if !exists {
@@ -299,20 +316,20 @@ func (t *Transport) HasPath(destinationHash []byte) bool {
} }
func (t *Transport) HopsTo(destinationHash []byte) uint8 { func (t *Transport) HopsTo(destinationHash []byte) uint8 {
t.pathLock.RLock() t.mutex.RLock()
defer t.pathLock.RUnlock() defer t.mutex.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.HopCount
} }
func (t *Transport) NextHop(destinationHash []byte) []byte { func (t *Transport) NextHop(destinationHash []byte) []byte {
t.pathLock.RLock() t.mutex.RLock()
defer t.pathLock.RUnlock() defer t.mutex.RUnlock()
path, exists := t.paths[string(destinationHash)] path, exists := t.paths[string(destinationHash)]
if !exists { if !exists {
@@ -323,8 +340,8 @@ func (t *Transport) NextHop(destinationHash []byte) []byte {
} }
func (t *Transport) NextHopInterface(destinationHash []byte) string { func (t *Transport) NextHopInterface(destinationHash []byte) string {
t.pathLock.RLock() t.mutex.RLock()
defer t.pathLock.RUnlock() defer t.mutex.RUnlock()
path, exists := t.paths[string(destinationHash)] path, exists := t.paths[string(destinationHash)]
if !exists { if !exists {
@@ -350,8 +367,8 @@ func (t *Transport) RequestPath(destinationHash []byte, onInterface string, tag
} }
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.mutex.Lock()
defer t.pathLock.Unlock() defer t.mutex.Unlock()
iface, err := t.GetInterface(interfaceName) iface, err := t.GetInterface(interfaceName)
if err != nil { if err != nil {
@@ -359,22 +376,18 @@ func (t *Transport) UpdatePath(destinationHash []byte, nextHop []byte, interface
} }
t.paths[string(destinationHash)] = &common.Path{ t.paths[string(destinationHash)] = &common.Path{
Interface: iface,
NextHop: nextHop, NextHop: nextHop,
Interface: iface,
Hops: hops, Hops: hops,
LastUpdated: time.Now(), LastUpdated: time.Now(),
} }
} }
func (t *Transport) HandleAnnounce(destinationHash []byte, identity []byte, appData []byte, announceHash []byte) { func (t *Transport) HandleAnnounce(destinationHash []byte, identity []byte, appData []byte, announceHash []byte) {
t.handlerLock.RLock() t.mutex.RLock()
defer t.handlerLock.RUnlock() defer t.mutex.RUnlock()
for _, handler := range t.announceHandlers { t.notifyAnnounceHandlers(destinationHash, identity, appData)
if handler.ReceivePathResponses() || announceHash != nil {
handler.ReceivedAnnounce(destinationHash, identity, appData)
}
}
} }
func (t *Transport) NewDestination(identity interface{}, direction int, destType int, appName string, aspects ...string) *Destination { func (t *Transport) NewDestination(identity interface{}, direction int, destType int, appName string, aspects ...string) *Destination {
@@ -639,30 +652,53 @@ func (t *Transport) handleAnnouncePacket(data []byte, iface common.NetworkInterf
return return
} }
destHash := data[:32] p := &packet.Packet{
var identityData, appData []byte Data: data,
Header: [2]byte{
if len(data) > 32 { 0x04, // Announce packet type
splitPoint := 32 0x00, // Initial hop count
for i := 32; i < len(data); i++ { },
if data[i] == 0x00 {
splitPoint = i
break
}
}
identityData = data[32:splitPoint]
if splitPoint < len(data)-1 {
appData = data[splitPoint+1:]
}
} }
// Use identity package's GetRandomHash announceHash := sha256.Sum256(data)
announceHash := identity.GetRandomHash() if t.seenAnnounces[string(announceHash[:])] {
return
// Use interface name in announce handling
if iface != nil {
t.HandleAnnounce(destHash, identityData, appData, announceHash)
} }
// Record this announce
t.seenAnnounces[string(announceHash[:])] = true
// Process the announce
if err := t.handleAnnounce(p); err != nil {
log.Printf("Error handling announce: %v", err)
return
}
// Broadcast to other interfaces based on interface mode
t.mutex.RLock()
for name, otherIface := range t.interfaces {
// Skip the interface we received from
if name == iface.GetName() {
continue
}
// Check interface modes for propagation rules
srcMode := iface.GetMode()
dstMode := otherIface.GetMode()
// Skip propagation based on interface modes
if srcMode == common.IF_MODE_ACCESS_POINT && dstMode != common.IF_MODE_FULL {
continue
}
if srcMode == common.IF_MODE_ROAMING && dstMode == common.IF_MODE_ACCESS_POINT {
continue
}
if err := otherIface.Send(p.Data, ""); err != nil {
log.Printf("Error broadcasting announce to %s: %v", name, err)
}
}
t.mutex.RUnlock()
} }
func (t *Transport) findLink(dest []byte) *Link { func (t *Transport) findLink(dest []byte) *Link {
@@ -831,32 +867,31 @@ func (l *Link) RTT() float64 {
return l.GetRTT() return l.GetRTT()
} }
func (l *Link) Resend(packet interface{}) error { func (l *Link) Resend(p interface{}) error {
if p, ok := packet.(*LinkPacket); ok { if pkt, ok := p.(*packet.Packet); ok {
p.Timestamp = time.Now() t := GetTransportInstance()
return p.send() if t == nil {
return fmt.Errorf("transport not initialized")
}
return t.SendPacket(pkt)
} }
return errors.New("invalid packet type") return fmt.Errorf("invalid packet type")
} }
func (l *Link) SetPacketTimeout(packet interface{}, callback func(interface{}), timeout time.Duration) { func (l *Link) SetPacketTimeout(p interface{}, callback func(interface{}), timeout time.Duration) {
if p, ok := packet.(*LinkPacket); ok { if pkt, ok := p.(*packet.Packet); ok {
// Start timeout timer
time.AfterFunc(timeout, func() { time.AfterFunc(timeout, func() {
callback(p) callback(pkt)
}) })
} }
} }
func (l *Link) SetPacketDelivered(packet interface{}, callback func(interface{})) { func (l *Link) SetPacketDelivered(p interface{}, callback func(interface{})) {
if p, ok := packet.(*LinkPacket); ok { if pkt, ok := p.(*packet.Packet); ok {
// Update RTT
l.mutex.Lock() l.mutex.Lock()
l.rtt = time.Since(p.Timestamp) l.rtt = time.Since(time.Now())
l.mutex.Unlock() l.mutex.Unlock()
callback(pkt)
// Call delivery callback
callback(p)
} }
} }
@@ -865,3 +900,49 @@ func (l *Link) GetStatus() int {
defer l.mutex.RUnlock() defer l.mutex.RUnlock()
return l.status return l.status
} }
func (t *Transport) handleAnnounce(p *packet.Packet) error {
// Skip if we've seen this announce before
announceHash := sha256.Sum256(p.Data)
if t.seenAnnounces[string(announceHash[:])] {
return nil
}
// Record this announce
t.seenAnnounces[string(announceHash[:])] = true
// Extract announce fields
if len(p.Data) < 53 { // Minimum size for announce packet
return errors.New("invalid announce packet size")
}
// Don't forward if max hops reached
if p.Header[1] >= MAX_HOPS {
return nil
}
// Add random delay before retransmission (0-2 seconds)
delay := time.Duration(rand.Float64() * 2 * float64(time.Second))
time.Sleep(delay)
// Check bandwidth allocation for announces
if !t.announceRate.Allow() {
return nil
}
// Increment hop count and retransmit
p.Header[1]++
return t.broadcastAnnouncePacket(p)
}
func (t *Transport) broadcastAnnouncePacket(p *packet.Packet) error {
t.mutex.RLock()
defer t.mutex.RUnlock()
for _, iface := range t.interfaces {
if err := iface.Send(p.Data, ""); err != nil {
return fmt.Errorf("failed to broadcast announce: %w", err)
}
}
return nil
}