0.3.0
This commit is contained in:
@@ -24,24 +24,38 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
|
|||||||
cfg = config.DefaultConfig()
|
cfg = config.DefaultConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize transport
|
|
||||||
t, err := transport.NewTransport(cfg)
|
t, err := transport.NewTransport(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to initialize transport: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Reticulum{
|
return &Reticulum{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
transport: t,
|
transport: t,
|
||||||
|
interfaces: make([]interfaces.Interface, 0),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reticulum) Start() error {
|
func (r *Reticulum) Start() error {
|
||||||
for _, ifaceConfig := range r.config.Interfaces {
|
log.Printf("Starting Reticulum...")
|
||||||
|
|
||||||
|
if err := r.transport.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start transport: %v", err)
|
||||||
|
}
|
||||||
|
log.Printf("Transport started successfully")
|
||||||
|
|
||||||
|
for name, ifaceConfig := range r.config.Interfaces {
|
||||||
|
if !ifaceConfig.Enabled {
|
||||||
|
log.Printf("Skipping disabled interface %s", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Configuring interface %s (type=%s)...", name, ifaceConfig.Type)
|
||||||
var iface interfaces.Interface
|
var iface interfaces.Interface
|
||||||
|
|
||||||
switch ifaceConfig.Type {
|
switch ifaceConfig.Type {
|
||||||
case "TCPClientInterface":
|
case "TCPClientInterface":
|
||||||
|
log.Printf("Creating TCP client interface %s -> %s:%d", name, ifaceConfig.TargetHost, ifaceConfig.TargetPort)
|
||||||
client, err := interfaces.NewTCPClient(
|
client, err := interfaces.NewTCPClient(
|
||||||
ifaceConfig.Name,
|
ifaceConfig.Name,
|
||||||
ifaceConfig.TargetHost,
|
ifaceConfig.TargetHost,
|
||||||
@@ -51,18 +65,16 @@ func (r *Reticulum) Start() error {
|
|||||||
ifaceConfig.Enabled,
|
ifaceConfig.Enabled,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to create TCP interface %s: %v", ifaceConfig.Name, err)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.Start(); err != nil {
|
|
||||||
log.Printf("Failed to start TCP interface %s: %v", ifaceConfig.Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
iface = client
|
iface = client
|
||||||
|
|
||||||
case "TCPServerInterface":
|
case "TCPServerInterface":
|
||||||
|
log.Printf("Creating TCP server interface %s on %s:%d", name, ifaceConfig.Address, ifaceConfig.Port)
|
||||||
server, err := interfaces.NewTCPServer(
|
server, err := interfaces.NewTCPServer(
|
||||||
ifaceConfig.Name,
|
ifaceConfig.Name,
|
||||||
ifaceConfig.Address,
|
ifaceConfig.Address,
|
||||||
@@ -72,41 +84,51 @@ func (r *Reticulum) Start() error {
|
|||||||
ifaceConfig.PreferIPv6,
|
ifaceConfig.PreferIPv6,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to create TCP server interface %s: %v", ifaceConfig.Name, err)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := server.Start(); err != nil {
|
|
||||||
log.Printf("Failed to start TCP server interface %s: %v", ifaceConfig.Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
iface = server
|
iface = server
|
||||||
|
|
||||||
case "UDPInterface":
|
case "UDPInterface":
|
||||||
addr := fmt.Sprintf("%s:%d", ifaceConfig.Address, ifaceConfig.Port)
|
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(
|
udp, err := interfaces.NewUDPInterface(
|
||||||
ifaceConfig.Name,
|
ifaceConfig.Name,
|
||||||
addr,
|
addr,
|
||||||
"", // No target address for server initially
|
target,
|
||||||
ifaceConfig.Enabled,
|
ifaceConfig.Enabled,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to create UDP interface %s: %v", ifaceConfig.Name, err)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := udp.Start(); err != nil {
|
|
||||||
log.Printf("Failed to start UDP interface %s: %v", ifaceConfig.Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
iface = udp
|
iface = udp
|
||||||
|
|
||||||
case "AutoInterface":
|
case "AutoInterface":
|
||||||
log.Printf("AutoInterface type not yet implemented")
|
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
|
continue
|
||||||
|
}
|
||||||
|
iface = auto
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Printf("Unknown interface type: %s", ifaceConfig.Type)
|
log.Printf("Unknown interface type: %s", ifaceConfig.Type)
|
||||||
@@ -114,50 +136,71 @@ func (r *Reticulum) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if iface != nil {
|
if iface != nil {
|
||||||
// Set packet callback to transport
|
log.Printf("Starting interface %s...", name)
|
||||||
iface.SetPacketCallback(r.transport.HandlePacket)
|
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)
|
||||||
|
|
||||||
|
callback := func(data []byte, ni common.NetworkInterface) {
|
||||||
|
r.transport.HandlePacket(data, ni)
|
||||||
|
}
|
||||||
|
|
||||||
|
netIface.SetPacketCallback(callback)
|
||||||
r.interfaces = append(r.interfaces, iface)
|
r.interfaces = append(r.interfaces, iface)
|
||||||
log.Printf("Created and started interface %s (type=%v, enabled=%v)",
|
log.Printf("Created and started interface %s (type=%v, enabled=%v)",
|
||||||
iface.GetName(), iface.GetType(), iface.IsEnabled())
|
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("Reticulum initialized with config at: %s", r.config.ConfigPath)
|
||||||
|
log.Printf("Press Ctrl+C to stop...")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reticulum) Stop() error {
|
func (r *Reticulum) Stop() error {
|
||||||
|
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 {
|
if err := r.transport.Close(); err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to close transport: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Initialize configuration
|
log.Printf("Initializing Reticulum...")
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new reticulum instance
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start reticulum
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for interrupt signal
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-sigChan
|
<-sigChan
|
||||||
|
|
||||||
// Clean shutdown
|
log.Printf("\nShutting down...")
|
||||||
if err := r.Stop(); err != nil {
|
if err := r.Stop(); err != nil {
|
||||||
log.Printf("Error during shutdown: %v", err)
|
log.Printf("Error during shutdown: %v", err)
|
||||||
}
|
}
|
||||||
|
log.Printf("Goodbye!")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
|
|
||||||
func DefaultConfig() *common.ReticulumConfig {
|
func DefaultConfig() *common.ReticulumConfig {
|
||||||
return &common.ReticulumConfig{
|
return &common.ReticulumConfig{
|
||||||
EnableTransport: false,
|
EnableTransport: true,
|
||||||
ShareInstance: true,
|
ShareInstance: true,
|
||||||
SharedInstancePort: DefaultSharedInstancePort,
|
SharedInstancePort: DefaultSharedInstancePort,
|
||||||
InstanceControlPort: DefaultInstanceControlPort,
|
InstanceControlPort: DefaultInstanceControlPort,
|
||||||
@@ -74,26 +74,38 @@ func SaveConfig(cfg *common.ReticulumConfig) error {
|
|||||||
func CreateDefaultConfig(path string) error {
|
func CreateDefaultConfig(path string) error {
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
// Add default interface
|
// Add Auto Interface
|
||||||
cfg.Interfaces["Default Interface"] = &common.InterfaceConfig{
|
cfg.Interfaces["Auto Discovery"] = &common.InterfaceConfig{
|
||||||
Type: "AutoInterface",
|
Type: "AutoInterface",
|
||||||
Enabled: false,
|
Enabled: true,
|
||||||
|
GroupID: "reticulum",
|
||||||
|
DiscoveryScope: "link",
|
||||||
|
DiscoveryPort: 29716,
|
||||||
|
DataPort: 42671,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add default quad4net interface
|
// Add RNS Amsterdam Testnet interface
|
||||||
cfg.Interfaces["quad4net tcp"] = &common.InterfaceConfig{
|
cfg.Interfaces["RNS Testnet Amsterdam"] = &common.InterfaceConfig{
|
||||||
Type: "TCPClientInterface",
|
Type: "TCPClientInterface",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
TargetHost: "rns.quad4.io",
|
TargetHost: "amsterdam.connect.reticulum.network",
|
||||||
|
TargetPort: 4965,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add RNS BetweenTheBorders Testnet interface
|
||||||
|
cfg.Interfaces["RNS Testnet BetweenTheBorders"] = &common.InterfaceConfig{
|
||||||
|
Type: "TCPClientInterface",
|
||||||
|
Enabled: true,
|
||||||
|
TargetHost: "reticulum.betweentheborders.com",
|
||||||
TargetPort: 4242,
|
TargetPort: 4242,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add default UDP interface
|
// Add local UDP interface
|
||||||
cfg.Interfaces["local udp"] = &common.InterfaceConfig{
|
cfg.Interfaces["Local UDP"] = &common.InterfaceConfig{
|
||||||
Type: "UDPInterface",
|
Type: "UDPInterface",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Address: "0.0.0.0",
|
Address: "0.0.0.0",
|
||||||
Port: 37696, // Default RNS port
|
Port: 37696,
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := toml.Marshal(cfg)
|
data, err := toml.Marshal(cfg)
|
||||||
@@ -101,7 +113,6 @@ func CreateDefaultConfig(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create config directory if it doesn't exist
|
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const (
|
|||||||
|
|
||||||
type AnnounceHandler interface {
|
type AnnounceHandler interface {
|
||||||
AspectFilter() []string
|
AspectFilter() []string
|
||||||
ReceivedAnnounce(destinationHash []byte, announcedIdentity *identity.Identity, appData []byte) error
|
ReceivedAnnounce(destinationHash []byte, announcedIdentity interface{}, appData []byte) error
|
||||||
ReceivePathResponses() bool
|
ReceivePathResponses() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ type InterfaceConfig struct {
|
|||||||
MaxReconnTries int `toml:"max_reconnect_tries"`
|
MaxReconnTries int `toml:"max_reconnect_tries"`
|
||||||
Bitrate int64 `toml:"bitrate"`
|
Bitrate int64 `toml:"bitrate"`
|
||||||
MTU int `toml:"mtu"`
|
MTU int `toml:"mtu"`
|
||||||
|
GroupID string
|
||||||
|
DiscoveryScope string
|
||||||
|
DiscoveryPort int
|
||||||
|
DataPort int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReticulumConfig represents the main configuration structure
|
// ReticulumConfig represents the main configuration structure
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const (
|
|||||||
IF_TYPE_I2P
|
IF_TYPE_I2P
|
||||||
IF_TYPE_BLUETOOTH
|
IF_TYPE_BLUETOOTH
|
||||||
IF_TYPE_SERIAL
|
IF_TYPE_SERIAL
|
||||||
|
IF_TYPE_AUTO
|
||||||
|
|
||||||
// Interface Modes
|
// Interface Modes
|
||||||
IF_MODE_FULL InterfaceMode = iota
|
IF_MODE_FULL InterfaceMode = iota
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"encoding/binary"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NetworkInterface defines the interface for all network communication methods
|
// NetworkInterface defines the interface for all network communication methods
|
||||||
@@ -68,6 +69,7 @@ func NewBaseInterface(name string, ifaceType InterfaceType, enabled bool) BaseIn
|
|||||||
Mode: IF_MODE_FULL,
|
Mode: IF_MODE_FULL,
|
||||||
Enabled: enabled,
|
Enabled: enabled,
|
||||||
MTU: DEFAULT_MTU,
|
MTU: DEFAULT_MTU,
|
||||||
|
Bitrate: BITRATE_MINIMUM,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,3 +140,51 @@ func (i *BaseInterface) Disable() {
|
|||||||
i.Enabled = false
|
i.Enabled = false
|
||||||
i.Online = false
|
i.Online = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default implementations that should be overridden by specific interfaces
|
||||||
|
func (i *BaseInterface) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseInterface) Stop() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseInterface) GetConn() net.Conn {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseInterface) Send(data []byte, address string) error {
|
||||||
|
return i.ProcessOutgoing(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseInterface) ProcessIncoming(data []byte) {
|
||||||
|
if i.PacketCallback != nil {
|
||||||
|
i.PacketCallback(data, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseInterface) ProcessOutgoing(data []byte) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseInterface) SendPathRequest(data []byte) error {
|
||||||
|
return i.Send(data, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseInterface) SendLinkPacket(dest []byte, data []byte, timestamp time.Time) error {
|
||||||
|
// Create link packet
|
||||||
|
packet := make([]byte, 0, len(dest)+len(data)+9) // 1 byte type + dest + 8 byte timestamp
|
||||||
|
packet = append(packet, 0x02) // Link packet type
|
||||||
|
packet = append(packet, dest...)
|
||||||
|
|
||||||
|
// Add timestamp
|
||||||
|
ts := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(ts, uint64(timestamp.Unix()))
|
||||||
|
packet = append(packet, ts...)
|
||||||
|
|
||||||
|
// Add data
|
||||||
|
packet = append(packet, data...)
|
||||||
|
|
||||||
|
return i.Send(packet, "")
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ type Path struct {
|
|||||||
// Common callbacks
|
// Common callbacks
|
||||||
type ProofRequestedCallback func([]byte, []byte)
|
type ProofRequestedCallback func([]byte, []byte)
|
||||||
type LinkEstablishedCallback func(interface{})
|
type LinkEstablishedCallback func(interface{})
|
||||||
type PacketCallback func([]byte, interface{})
|
type PacketCallback func([]byte, NetworkInterface)
|
||||||
|
|
||||||
// RequestHandler manages path requests and responses
|
// RequestHandler manages path requests and responses
|
||||||
type RequestHandler struct {
|
type RequestHandler struct {
|
||||||
|
|||||||
277
pkg/interfaces/auto.go
Normal file
277
pkg/interfaces/auto.go
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_DISCOVERY_PORT = 29716
|
||||||
|
DEFAULT_DATA_PORT = 42671
|
||||||
|
BITRATE_GUESS = 10 * 1000 * 1000
|
||||||
|
PEERING_TIMEOUT = 7500 * time.Millisecond
|
||||||
|
SCOPE_LINK = "2"
|
||||||
|
SCOPE_ADMIN = "4"
|
||||||
|
SCOPE_SITE = "5"
|
||||||
|
SCOPE_ORGANISATION = "8"
|
||||||
|
SCOPE_GLOBAL = "e"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AutoInterface struct {
|
||||||
|
BaseInterface
|
||||||
|
groupID []byte
|
||||||
|
discoveryPort int
|
||||||
|
dataPort int
|
||||||
|
discoveryScope string
|
||||||
|
peers map[string]*Peer
|
||||||
|
linkLocalAddrs []string
|
||||||
|
adoptedInterfaces map[string]string
|
||||||
|
interfaceServers map[string]*net.UDPConn
|
||||||
|
multicastEchoes map[string]time.Time
|
||||||
|
mutex sync.RWMutex
|
||||||
|
outboundConn *net.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type Peer struct {
|
||||||
|
ifaceName string
|
||||||
|
lastHeard time.Time
|
||||||
|
conn *net.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterface, error) {
|
||||||
|
base := &BaseInterface{
|
||||||
|
Name: name,
|
||||||
|
Mode: common.IF_MODE_FULL,
|
||||||
|
Type: common.IF_TYPE_AUTO,
|
||||||
|
Online: false,
|
||||||
|
Enabled: config.Enabled,
|
||||||
|
Detached: false,
|
||||||
|
IN: false,
|
||||||
|
OUT: false,
|
||||||
|
MTU: common.DEFAULT_MTU,
|
||||||
|
Bitrate: BITRATE_MINIMUM,
|
||||||
|
}
|
||||||
|
|
||||||
|
ai := &AutoInterface{
|
||||||
|
BaseInterface: *base,
|
||||||
|
discoveryPort: DEFAULT_DISCOVERY_PORT,
|
||||||
|
dataPort: DEFAULT_DATA_PORT,
|
||||||
|
discoveryScope: SCOPE_LINK,
|
||||||
|
peers: make(map[string]*Peer),
|
||||||
|
linkLocalAddrs: make([]string, 0),
|
||||||
|
adoptedInterfaces: make(map[string]string),
|
||||||
|
interfaceServers: make(map[string]*net.UDPConn),
|
||||||
|
multicastEchoes: make(map[string]time.Time),
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Port != 0 {
|
||||||
|
ai.discoveryPort = config.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.GroupID != "" {
|
||||||
|
ai.groupID = []byte(config.GroupID)
|
||||||
|
} else {
|
||||||
|
ai.groupID = []byte("reticulum")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ai, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) Start() error {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list interfaces: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
if err := ai.configureInterface(&iface); err != nil {
|
||||||
|
log.Printf("Failed to configure interface %s: %v", iface.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ai.adoptedInterfaces) == 0 {
|
||||||
|
return fmt.Errorf("no suitable interfaces found")
|
||||||
|
}
|
||||||
|
|
||||||
|
go ai.peerJobs()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) configureInterface(iface *net.Interface) error {
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.IsLinkLocalUnicast() {
|
||||||
|
ai.adoptedInterfaces[iface.Name] = ipnet.IP.String()
|
||||||
|
ai.multicastEchoes[iface.Name] = time.Now()
|
||||||
|
|
||||||
|
if err := ai.startDiscoveryListener(iface); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ai.startDataListener(iface); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) startDiscoveryListener(iface *net.Interface) error {
|
||||||
|
addr := &net.UDPAddr{
|
||||||
|
IP: net.ParseIP(fmt.Sprintf("ff%s%s::1", ai.discoveryScope, SCOPE_LINK)),
|
||||||
|
Port: ai.discoveryPort,
|
||||||
|
Zone: iface.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.ListenMulticastUDP("udp6", iface, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go ai.handleDiscovery(conn, iface.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) startDataListener(iface *net.Interface) error {
|
||||||
|
addr := &net.UDPAddr{
|
||||||
|
IP: net.IPv6zero,
|
||||||
|
Port: ai.dataPort,
|
||||||
|
Zone: iface.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.ListenUDP("udp6", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.interfaceServers[iface.Name] = conn
|
||||||
|
go ai.handleData(conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) handleDiscovery(conn *net.UDPConn, ifaceName string) {
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
n, remoteAddr, err := conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Discovery read error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.handlePeerAnnounce(remoteAddr, buf[:n], ifaceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) handleData(conn *net.UDPConn) {
|
||||||
|
buf := make([]byte, ai.GetMTU())
|
||||||
|
for {
|
||||||
|
n, _, err := conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
if !ai.IsDetached() {
|
||||||
|
log.Printf("Data read error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if callback := ai.GetPacketCallback(); callback != nil {
|
||||||
|
callback(buf[:n], ai)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) handlePeerAnnounce(addr *net.UDPAddr, data []byte, ifaceName string) {
|
||||||
|
ai.mutex.Lock()
|
||||||
|
defer ai.mutex.Unlock()
|
||||||
|
|
||||||
|
peerAddr := addr.IP.String()
|
||||||
|
|
||||||
|
for _, localAddr := range ai.linkLocalAddrs {
|
||||||
|
if peerAddr == localAddr {
|
||||||
|
ai.multicastEchoes[ifaceName] = time.Now()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := ai.peers[peerAddr]; !exists {
|
||||||
|
ai.peers[peerAddr] = &Peer{
|
||||||
|
ifaceName: ifaceName,
|
||||||
|
lastHeard: time.Now(),
|
||||||
|
}
|
||||||
|
log.Printf("Added peer %s on %s", peerAddr, ifaceName)
|
||||||
|
} else {
|
||||||
|
ai.peers[peerAddr].lastHeard = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) peerJobs() {
|
||||||
|
ticker := time.NewTicker(PEERING_TIMEOUT)
|
||||||
|
for range ticker.C {
|
||||||
|
ai.mutex.Lock()
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for addr, peer := range ai.peers {
|
||||||
|
if now.Sub(peer.lastHeard) > PEERING_TIMEOUT {
|
||||||
|
delete(ai.peers, addr)
|
||||||
|
log.Printf("Removed timed out peer %s", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.mutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) Send(data []byte, address string) error {
|
||||||
|
ai.mutex.RLock()
|
||||||
|
defer ai.mutex.RUnlock()
|
||||||
|
|
||||||
|
for _, peer := range ai.peers {
|
||||||
|
addr := &net.UDPAddr{
|
||||||
|
IP: net.ParseIP(address),
|
||||||
|
Port: ai.dataPort,
|
||||||
|
Zone: peer.ifaceName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ai.outboundConn == nil {
|
||||||
|
var err error
|
||||||
|
ai.outboundConn, err = net.ListenUDP("udp6", &net.UDPAddr{Port: 0})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ai.outboundConn.WriteToUDP(data, addr); err != nil {
|
||||||
|
log.Printf("Failed to send to peer %s: %v", address, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) Stop() error {
|
||||||
|
ai.mutex.Lock()
|
||||||
|
defer ai.mutex.Unlock()
|
||||||
|
|
||||||
|
for _, server := range ai.interfaceServers {
|
||||||
|
server.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ai.outboundConn != nil {
|
||||||
|
ai.outboundConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -789,3 +789,9 @@ func (l *Link) HandleResource(resource interface{}) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Start() error {
|
||||||
|
t.mutex.Lock()
|
||||||
|
defer t.mutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user