0.3.2
This commit is contained in:
26
Makefile
26
Makefile
@@ -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"
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -290,3 +335,60 @@ func NewAnnouncePacket(pubKey []byte, appData []byte, announceID []byte) *Announ
|
|||||||
|
|
||||||
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
5
pkg/announce/handler.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package announce
|
||||||
|
|
||||||
|
type Handler interface {
|
||||||
|
ReceivedAnnounce(destHash []byte, identity interface{}, appData []byte) error
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -46,9 +46,9 @@ type Identity struct {
|
|||||||
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 (
|
||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
34
pkg/pathfinder/pathfinder.go
Normal file
34
pkg/pathfinder/pathfinder.go
Normal 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
44
pkg/rate/rate.go
Normal 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
74
pkg/resolver/resolver.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user