feat: enhance AutoInterface with improved configuration and multicast handling
This commit is contained in:
@@ -1,49 +1,106 @@
|
|||||||
package interfaces
|
package interfaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
|
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
|
||||||
|
"github.com/Sudo-Ivan/reticulum-go/pkg/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
HW_MTU = 1196
|
||||||
DEFAULT_DISCOVERY_PORT = 29716
|
DEFAULT_DISCOVERY_PORT = 29716
|
||||||
DEFAULT_DATA_PORT = 42671
|
DEFAULT_DATA_PORT = 42671
|
||||||
|
DEFAULT_GROUP_ID = "reticulum"
|
||||||
BITRATE_GUESS = 10 * 1000 * 1000
|
BITRATE_GUESS = 10 * 1000 * 1000
|
||||||
PEERING_TIMEOUT = 7500 * time.Millisecond
|
PEERING_TIMEOUT = 22 * time.Second
|
||||||
SCOPE_LINK = "2"
|
ANNOUNCE_INTERVAL = 1600 * time.Millisecond
|
||||||
SCOPE_ADMIN = "4"
|
PEER_JOB_INTERVAL = 4 * time.Second
|
||||||
SCOPE_SITE = "5"
|
MCAST_ECHO_TIMEOUT = 6500 * time.Millisecond
|
||||||
SCOPE_ORGANISATION = "8"
|
|
||||||
SCOPE_GLOBAL = "e"
|
SCOPE_LINK = "2"
|
||||||
|
SCOPE_ADMIN = "4"
|
||||||
|
SCOPE_SITE = "5"
|
||||||
|
SCOPE_ORGANISATION = "8"
|
||||||
|
SCOPE_GLOBAL = "e"
|
||||||
|
|
||||||
|
MCAST_ADDR_TYPE_PERMANENT = "0"
|
||||||
|
MCAST_ADDR_TYPE_TEMPORARY = "1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AutoInterface struct {
|
type AutoInterface struct {
|
||||||
BaseInterface
|
BaseInterface
|
||||||
groupID []byte
|
groupID []byte
|
||||||
discoveryPort int
|
groupHash []byte
|
||||||
dataPort int
|
discoveryPort int
|
||||||
discoveryScope string
|
dataPort int
|
||||||
peers map[string]*Peer
|
discoveryScope string
|
||||||
linkLocalAddrs []string
|
multicastAddrType string
|
||||||
adoptedInterfaces map[string]string
|
mcastDiscoveryAddr string
|
||||||
interfaceServers map[string]*net.UDPConn
|
ifacNetname string
|
||||||
multicastEchoes map[string]time.Time
|
peers map[string]*Peer
|
||||||
mutex sync.RWMutex
|
linkLocalAddrs []string
|
||||||
outboundConn *net.UDPConn
|
adoptedInterfaces map[string]*AdoptedInterface
|
||||||
|
interfaceServers map[string]*net.UDPConn
|
||||||
|
discoveryServers map[string]*net.UDPConn
|
||||||
|
multicastEchoes map[string]time.Time
|
||||||
|
timedOutInterfaces map[string]time.Time
|
||||||
|
allowedInterfaces []string
|
||||||
|
ignoredInterfaces []string
|
||||||
|
mutex sync.RWMutex
|
||||||
|
outboundConn *net.UDPConn
|
||||||
|
announceInterval time.Duration
|
||||||
|
peerJobInterval time.Duration
|
||||||
|
peeringTimeout time.Duration
|
||||||
|
mcastEchoTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdoptedInterface struct {
|
||||||
|
name string
|
||||||
|
linkLocalAddr string
|
||||||
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
ifaceName string
|
ifaceName string
|
||||||
lastHeard time.Time
|
lastHeard time.Time
|
||||||
conn *net.UDPConn
|
addr *net.UDPAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterface, error) {
|
func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterface, error) {
|
||||||
|
groupID := DEFAULT_GROUP_ID
|
||||||
|
if config.GroupID != "" {
|
||||||
|
groupID = config.GroupID
|
||||||
|
}
|
||||||
|
|
||||||
|
discoveryScope := SCOPE_LINK
|
||||||
|
if config.DiscoveryScope != "" {
|
||||||
|
discoveryScope = normalizeScope(config.DiscoveryScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
multicastAddrType := MCAST_ADDR_TYPE_TEMPORARY
|
||||||
|
|
||||||
|
discoveryPort := DEFAULT_DISCOVERY_PORT
|
||||||
|
if config.DiscoveryPort != 0 {
|
||||||
|
discoveryPort = config.DiscoveryPort
|
||||||
|
}
|
||||||
|
|
||||||
|
dataPort := DEFAULT_DATA_PORT
|
||||||
|
if config.DataPort != 0 {
|
||||||
|
dataPort = config.DataPort
|
||||||
|
}
|
||||||
|
|
||||||
|
groupHash := sha256.Sum256([]byte(groupID))
|
||||||
|
|
||||||
|
ifacNetname := hex.EncodeToString(groupHash[:])[:16]
|
||||||
|
mcastAddr := fmt.Sprintf("ff%s%s::%s", discoveryScope, multicastAddrType, ifacNetname)
|
||||||
|
|
||||||
ai := &AutoInterface{
|
ai := &AutoInterface{
|
||||||
BaseInterface: BaseInterface{
|
BaseInterface: BaseInterface{
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -52,34 +109,66 @@ func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterfa
|
|||||||
Online: false,
|
Online: false,
|
||||||
Enabled: config.Enabled,
|
Enabled: config.Enabled,
|
||||||
Detached: false,
|
Detached: false,
|
||||||
IN: false,
|
IN: true,
|
||||||
OUT: false,
|
OUT: false,
|
||||||
MTU: common.DEFAULT_MTU,
|
MTU: HW_MTU,
|
||||||
Bitrate: BITRATE_MINIMUM,
|
Bitrate: BITRATE_GUESS,
|
||||||
},
|
},
|
||||||
discoveryPort: DEFAULT_DISCOVERY_PORT,
|
groupID: []byte(groupID),
|
||||||
dataPort: DEFAULT_DATA_PORT,
|
groupHash: groupHash[:],
|
||||||
discoveryScope: SCOPE_LINK,
|
discoveryPort: discoveryPort,
|
||||||
peers: make(map[string]*Peer),
|
dataPort: dataPort,
|
||||||
linkLocalAddrs: make([]string, 0),
|
discoveryScope: discoveryScope,
|
||||||
adoptedInterfaces: make(map[string]string),
|
multicastAddrType: multicastAddrType,
|
||||||
interfaceServers: make(map[string]*net.UDPConn),
|
mcastDiscoveryAddr: mcastAddr,
|
||||||
multicastEchoes: make(map[string]time.Time),
|
ifacNetname: ifacNetname,
|
||||||
}
|
peers: make(map[string]*Peer),
|
||||||
|
linkLocalAddrs: make([]string, 0),
|
||||||
if config.Port != 0 {
|
adoptedInterfaces: make(map[string]*AdoptedInterface),
|
||||||
ai.discoveryPort = config.Port
|
interfaceServers: make(map[string]*net.UDPConn),
|
||||||
}
|
discoveryServers: make(map[string]*net.UDPConn),
|
||||||
|
multicastEchoes: make(map[string]time.Time),
|
||||||
if config.GroupID != "" {
|
timedOutInterfaces: make(map[string]time.Time),
|
||||||
ai.groupID = []byte(config.GroupID)
|
allowedInterfaces: make([]string, 0),
|
||||||
} else {
|
ignoredInterfaces: make([]string, 0),
|
||||||
ai.groupID = []byte("reticulum")
|
announceInterval: ANNOUNCE_INTERVAL,
|
||||||
|
peerJobInterval: PEER_JOB_INTERVAL,
|
||||||
|
peeringTimeout: PEERING_TIMEOUT,
|
||||||
|
mcastEchoTimeout: MCAST_ECHO_TIMEOUT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug.Log(debug.DEBUG_INFO, "AutoInterface configured", "name", name, "group", groupID, "mcast_addr", mcastAddr)
|
||||||
return ai, nil
|
return ai, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeScope(scope string) string {
|
||||||
|
switch scope {
|
||||||
|
case "link", "2":
|
||||||
|
return SCOPE_LINK
|
||||||
|
case "admin", "4":
|
||||||
|
return SCOPE_ADMIN
|
||||||
|
case "site", "5":
|
||||||
|
return SCOPE_SITE
|
||||||
|
case "organisation", "organization", "8":
|
||||||
|
return SCOPE_ORGANISATION
|
||||||
|
case "global", "e":
|
||||||
|
return SCOPE_GLOBAL
|
||||||
|
default:
|
||||||
|
return SCOPE_LINK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeMulticastType(mtype string) string {
|
||||||
|
switch mtype {
|
||||||
|
case "permanent", "0":
|
||||||
|
return MCAST_ADDR_TYPE_PERMANENT
|
||||||
|
case "temporary", "1":
|
||||||
|
return MCAST_ADDR_TYPE_TEMPORARY
|
||||||
|
default:
|
||||||
|
return MCAST_ADDR_TYPE_TEMPORARY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ai *AutoInterface) Start() error {
|
func (ai *AutoInterface) Start() error {
|
||||||
interfaces, err := net.Interfaces()
|
interfaces, err := net.Interfaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,8 +176,18 @@ func (ai *AutoInterface) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, iface := range interfaces {
|
for _, iface := range interfaces {
|
||||||
|
if ai.shouldIgnoreInterface(iface.Name) {
|
||||||
|
debug.Log(debug.DEBUG_TRACE, "Ignoring interface", "name", iface.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ai.allowedInterfaces) > 0 && !ai.isAllowedInterface(iface.Name) {
|
||||||
|
debug.Log(debug.DEBUG_TRACE, "Interface not in allowed list", "name", iface.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := ai.configureInterface(&iface); err != nil {
|
if err := ai.configureInterface(&iface); err != nil {
|
||||||
log.Printf("Failed to configure interface %s: %v", iface.Name, err)
|
debug.Log(debug.DEBUG_VERBOSE, "Failed to configure interface", "name", iface.Name, "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,43 +196,97 @@ func (ai *AutoInterface) Start() error {
|
|||||||
return fmt.Errorf("no suitable interfaces found")
|
return fmt.Errorf("no suitable interfaces found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark interface as online
|
|
||||||
ai.Online = true
|
ai.Online = true
|
||||||
ai.Enabled = true
|
ai.IN = true
|
||||||
|
ai.OUT = true
|
||||||
|
|
||||||
go ai.peerJobs()
|
go ai.peerJobs()
|
||||||
|
go ai.announceLoop()
|
||||||
|
|
||||||
|
debug.Log(debug.DEBUG_INFO, "AutoInterface started", "adopted", len(ai.adoptedInterfaces))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) shouldIgnoreInterface(name string) bool {
|
||||||
|
ignoreList := []string{"lo", "lo0", "tun0", "awdl0", "llw0", "en5", "dummy0"}
|
||||||
|
|
||||||
|
for _, ignored := range ai.ignoredInterfaces {
|
||||||
|
if name == ignored {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ignored := range ignoreList {
|
||||||
|
if name == ignored {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) isAllowedInterface(name string) bool {
|
||||||
|
for _, allowed := range ai.allowedInterfaces {
|
||||||
|
if name == allowed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (ai *AutoInterface) configureInterface(iface *net.Interface) error {
|
func (ai *AutoInterface) configureInterface(iface *net.Interface) error {
|
||||||
|
if iface.Flags&net.FlagUp == 0 {
|
||||||
|
return fmt.Errorf("interface is down")
|
||||||
|
}
|
||||||
|
|
||||||
|
if iface.Flags&net.FlagLoopback != 0 {
|
||||||
|
return fmt.Errorf("loopback interface")
|
||||||
|
}
|
||||||
|
|
||||||
addrs, err := iface.Addrs()
|
addrs, err := iface.Addrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var linkLocalAddr string
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.IsLinkLocalUnicast() {
|
if ipnet, ok := addr.(*net.IPNet); ok {
|
||||||
ai.adoptedInterfaces[iface.Name] = ipnet.IP.String()
|
if ipnet.IP.To4() == nil && ipnet.IP.IsLinkLocalUnicast() {
|
||||||
ai.multicastEchoes[iface.Name] = time.Now()
|
linkLocalAddr = ipnet.IP.String()
|
||||||
|
break
|
||||||
if err := ai.startDiscoveryListener(iface); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ai.startDataListener(iface); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if linkLocalAddr == "" {
|
||||||
|
return fmt.Errorf("no link-local IPv6 address found")
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.mutex.Lock()
|
||||||
|
ai.adoptedInterfaces[iface.Name] = &AdoptedInterface{
|
||||||
|
name: iface.Name,
|
||||||
|
linkLocalAddr: linkLocalAddr,
|
||||||
|
index: iface.Index,
|
||||||
|
}
|
||||||
|
ai.linkLocalAddrs = append(ai.linkLocalAddrs, linkLocalAddr)
|
||||||
|
ai.multicastEchoes[iface.Name] = time.Now()
|
||||||
|
ai.mutex.Unlock()
|
||||||
|
|
||||||
|
if err := ai.startDiscoveryListener(iface); err != nil {
|
||||||
|
return fmt.Errorf("failed to start discovery listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ai.startDataListener(iface); err != nil {
|
||||||
|
return fmt.Errorf("failed to start data listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug.Log(debug.DEBUG_INFO, "Configured interface", "name", iface.Name, "addr", linkLocalAddr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ai *AutoInterface) startDiscoveryListener(iface *net.Interface) error {
|
func (ai *AutoInterface) startDiscoveryListener(iface *net.Interface) error {
|
||||||
addr := &net.UDPAddr{
|
addr := &net.UDPAddr{
|
||||||
IP: net.ParseIP(fmt.Sprintf("ff%s%s::1", ai.discoveryScope, SCOPE_LINK)),
|
IP: net.ParseIP(ai.mcastDiscoveryAddr),
|
||||||
Port: ai.discoveryPort,
|
Port: ai.discoveryPort,
|
||||||
Zone: iface.Name,
|
Zone: iface.Name,
|
||||||
}
|
}
|
||||||
@@ -143,47 +296,79 @@ func (ai *AutoInterface) startDiscoveryListener(iface *net.Interface) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := conn.SetReadBuffer(1024); err != nil {
|
||||||
|
debug.Log(debug.DEBUG_ERROR, "Failed to set discovery read buffer", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.mutex.Lock()
|
||||||
|
ai.discoveryServers[iface.Name] = conn
|
||||||
|
ai.mutex.Unlock()
|
||||||
|
|
||||||
go ai.handleDiscovery(conn, iface.Name)
|
go ai.handleDiscovery(conn, iface.Name)
|
||||||
|
debug.Log(debug.DEBUG_VERBOSE, "Discovery listener started", "interface", iface.Name, "addr", ai.mcastDiscoveryAddr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ai *AutoInterface) startDataListener(iface *net.Interface) error {
|
func (ai *AutoInterface) startDataListener(iface *net.Interface) error {
|
||||||
|
adoptedIface, exists := ai.adoptedInterfaces[iface.Name]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("interface not adopted")
|
||||||
|
}
|
||||||
|
|
||||||
addr := &net.UDPAddr{
|
addr := &net.UDPAddr{
|
||||||
IP: net.IPv6zero,
|
IP: net.ParseIP(adoptedIface.linkLocalAddr),
|
||||||
Port: ai.dataPort,
|
Port: ai.dataPort,
|
||||||
Zone: iface.Name,
|
Zone: iface.Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := net.ListenUDP("udp6", addr)
|
conn, err := net.ListenUDP("udp6", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
debug.Log(debug.DEBUG_ERROR, "Failed to listen on data port", "addr", addr, "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := conn.SetReadBuffer(ai.MTU); err != nil {
|
||||||
|
debug.Log(debug.DEBUG_ERROR, "Failed to set data read buffer", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ai.mutex.Lock()
|
||||||
ai.interfaceServers[iface.Name] = conn
|
ai.interfaceServers[iface.Name] = conn
|
||||||
go ai.handleData(conn)
|
ai.mutex.Unlock()
|
||||||
|
|
||||||
|
go ai.handleData(conn, iface.Name)
|
||||||
|
debug.Log(debug.DEBUG_VERBOSE, "Data listener started", "interface", iface.Name, "addr", addr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ai *AutoInterface) handleDiscovery(conn *net.UDPConn, ifaceName string) {
|
func (ai *AutoInterface) handleDiscovery(conn *net.UDPConn, ifaceName string) {
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
_, remoteAddr, err := conn.ReadFromUDP(buf)
|
n, remoteAddr, err := conn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Discovery read error: %v", err)
|
if ai.IsOnline() {
|
||||||
continue
|
debug.Log(debug.DEBUG_ERROR, "Discovery read error", "interface", ifaceName, "error", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ai.handlePeerAnnounce(remoteAddr, ifaceName)
|
if n >= len(ai.groupHash) {
|
||||||
|
receivedHash := buf[:len(ai.groupHash)]
|
||||||
|
if bytes.Equal(receivedHash, ai.groupHash) {
|
||||||
|
ai.handlePeerAnnounce(remoteAddr, ifaceName)
|
||||||
|
} else {
|
||||||
|
debug.Log(debug.DEBUG_TRACE, "Received discovery with mismatched group hash", "interface", ifaceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ai *AutoInterface) handleData(conn *net.UDPConn) {
|
func (ai *AutoInterface) handleData(conn *net.UDPConn, ifaceName string) {
|
||||||
buf := make([]byte, ai.GetMTU())
|
buf := make([]byte, ai.GetMTU())
|
||||||
for {
|
for {
|
||||||
n, _, err := conn.ReadFromUDP(buf)
|
n, _, err := conn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !ai.IsDetached() {
|
if ai.IsOnline() {
|
||||||
log.Printf("Data read error: %v", err)
|
debug.Log(debug.DEBUG_ERROR, "Data read error", "interface", ifaceName, "error", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -198,36 +383,98 @@ func (ai *AutoInterface) handlePeerAnnounce(addr *net.UDPAddr, ifaceName string)
|
|||||||
ai.mutex.Lock()
|
ai.mutex.Lock()
|
||||||
defer ai.mutex.Unlock()
|
defer ai.mutex.Unlock()
|
||||||
|
|
||||||
peerAddr := addr.IP.String()
|
peerIP := addr.IP.String()
|
||||||
|
|
||||||
for _, localAddr := range ai.linkLocalAddrs {
|
for _, localAddr := range ai.linkLocalAddrs {
|
||||||
if peerAddr == localAddr {
|
if peerIP == localAddr {
|
||||||
ai.multicastEchoes[ifaceName] = time.Now()
|
ai.multicastEchoes[ifaceName] = time.Now()
|
||||||
|
debug.Log(debug.DEBUG_TRACE, "Received own multicast echo", "interface", ifaceName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := ai.peers[peerAddr]; !exists {
|
peerKey := peerIP + "%" + ifaceName
|
||||||
ai.peers[peerAddr] = &Peer{
|
|
||||||
|
if peer, exists := ai.peers[peerKey]; exists {
|
||||||
|
peer.lastHeard = time.Now()
|
||||||
|
debug.Log(debug.DEBUG_TRACE, "Updated peer", "peer", peerIP, "interface", ifaceName)
|
||||||
|
} else {
|
||||||
|
ai.peers[peerKey] = &Peer{
|
||||||
ifaceName: ifaceName,
|
ifaceName: ifaceName,
|
||||||
lastHeard: time.Now(),
|
lastHeard: time.Now(),
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
debug.Log(debug.DEBUG_INFO, "Discovered new peer", "peer", peerIP, "interface", ifaceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) announceLoop() {
|
||||||
|
ticker := time.NewTicker(ai.announceInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
if !ai.IsOnline() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ai.sendPeerAnnounce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ai *AutoInterface) sendPeerAnnounce() {
|
||||||
|
ai.mutex.RLock()
|
||||||
|
defer ai.mutex.RUnlock()
|
||||||
|
|
||||||
|
for ifaceName, adoptedIface := range ai.adoptedInterfaces {
|
||||||
|
mcastAddr := &net.UDPAddr{
|
||||||
|
IP: net.ParseIP(ai.mcastDiscoveryAddr),
|
||||||
|
Port: ai.discoveryPort,
|
||||||
|
Zone: ifaceName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ai.outboundConn == nil {
|
||||||
|
var err error
|
||||||
|
ai.outboundConn, err = net.ListenUDP("udp6", &net.UDPAddr{Port: 0})
|
||||||
|
if err != nil {
|
||||||
|
debug.Log(debug.DEBUG_ERROR, "Failed to create outbound socket", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ai.outboundConn.WriteToUDP(ai.groupHash, mcastAddr); err != nil {
|
||||||
|
debug.Log(debug.DEBUG_VERBOSE, "Failed to send peer announce", "interface", ifaceName, "error", err)
|
||||||
|
} else {
|
||||||
|
debug.Log(debug.DEBUG_TRACE, "Sent peer announce", "interface", adoptedIface.name)
|
||||||
}
|
}
|
||||||
log.Printf("Added peer %s on %s", peerAddr, ifaceName)
|
|
||||||
} else {
|
|
||||||
ai.peers[peerAddr].lastHeard = time.Now()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ai *AutoInterface) peerJobs() {
|
func (ai *AutoInterface) peerJobs() {
|
||||||
ticker := time.NewTicker(PEERING_TIMEOUT)
|
ticker := time.NewTicker(ai.peerJobInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
|
if !ai.IsOnline() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ai.mutex.Lock()
|
ai.mutex.Lock()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
for addr, peer := range ai.peers {
|
for peerKey, peer := range ai.peers {
|
||||||
if now.Sub(peer.lastHeard) > PEERING_TIMEOUT {
|
if now.Sub(peer.lastHeard) > ai.peeringTimeout {
|
||||||
delete(ai.peers, addr)
|
delete(ai.peers, peerKey)
|
||||||
log.Printf("Removed timed out peer %s", addr)
|
debug.Log(debug.DEBUG_VERBOSE, "Removed timed out peer", "peer", peerKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ifaceName, echoTime := range ai.multicastEchoes {
|
||||||
|
if now.Sub(echoTime) > ai.mcastEchoTimeout {
|
||||||
|
if _, exists := ai.timedOutInterfaces[ifaceName]; !exists {
|
||||||
|
debug.Log(debug.DEBUG_INFO, "Interface timed out", "interface", ifaceName)
|
||||||
|
ai.timedOutInterfaces[ifaceName] = now
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete(ai.timedOutInterfaces, ifaceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,28 +483,43 @@ func (ai *AutoInterface) peerJobs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ai *AutoInterface) Send(data []byte, address string) error {
|
func (ai *AutoInterface) Send(data []byte, address string) error {
|
||||||
|
if !ai.IsOnline() {
|
||||||
|
return fmt.Errorf("interface offline")
|
||||||
|
}
|
||||||
|
|
||||||
ai.mutex.RLock()
|
ai.mutex.RLock()
|
||||||
defer ai.mutex.RUnlock()
|
defer ai.mutex.RUnlock()
|
||||||
|
|
||||||
|
if len(ai.peers) == 0 {
|
||||||
|
debug.Log(debug.DEBUG_TRACE, "No peers available for sending")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ai.outboundConn == nil {
|
||||||
|
var err error
|
||||||
|
ai.outboundConn, err = net.ListenUDP("udp6", &net.UDPAddr{Port: 0})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create outbound socket: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sentCount := 0
|
||||||
for _, peer := range ai.peers {
|
for _, peer := range ai.peers {
|
||||||
addr := &net.UDPAddr{
|
targetAddr := &net.UDPAddr{
|
||||||
IP: net.ParseIP(address),
|
IP: peer.addr.IP,
|
||||||
Port: ai.dataPort,
|
Port: ai.dataPort,
|
||||||
Zone: peer.ifaceName,
|
Zone: peer.ifaceName,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ai.outboundConn == nil {
|
if _, err := ai.outboundConn.WriteToUDP(data, targetAddr); err != nil {
|
||||||
var err error
|
debug.Log(debug.DEBUG_VERBOSE, "Failed to send to peer", "interface", peer.ifaceName, "error", err)
|
||||||
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
|
continue
|
||||||
}
|
}
|
||||||
|
sentCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if sentCount > 0 {
|
||||||
|
debug.Log(debug.DEBUG_TRACE, "Sent data to peers", "count", sentCount, "bytes", len(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -267,13 +529,22 @@ func (ai *AutoInterface) Stop() error {
|
|||||||
ai.mutex.Lock()
|
ai.mutex.Lock()
|
||||||
defer ai.mutex.Unlock()
|
defer ai.mutex.Unlock()
|
||||||
|
|
||||||
|
ai.Online = false
|
||||||
|
ai.IN = false
|
||||||
|
ai.OUT = false
|
||||||
|
|
||||||
for _, server := range ai.interfaceServers {
|
for _, server := range ai.interfaceServers {
|
||||||
server.Close() // #nosec G104
|
server.Close() // #nosec G104
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, server := range ai.discoveryServers {
|
||||||
|
server.Close() // #nosec G104
|
||||||
|
}
|
||||||
|
|
||||||
if ai.outboundConn != nil {
|
if ai.outboundConn != nil {
|
||||||
ai.outboundConn.Close() // #nosec G104
|
ai.outboundConn.Close() // #nosec G104
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug.Log(debug.DEBUG_INFO, "AutoInterface stopped")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,10 @@ func TestNewAutoInterface(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("CustomConfig", func(t *testing.T) {
|
t.Run("CustomConfig", func(t *testing.T) {
|
||||||
config := &common.InterfaceConfig{
|
config := &common.InterfaceConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Port: 12345, // Custom discovery port
|
DiscoveryPort: 12345,
|
||||||
GroupID: "customGroup",
|
DataPort: 54321,
|
||||||
|
GroupID: "customGroup",
|
||||||
}
|
}
|
||||||
ai, err := NewAutoInterface("autoCustom", config)
|
ai, err := NewAutoInterface("autoCustom", config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -59,6 +60,9 @@ func TestNewAutoInterface(t *testing.T) {
|
|||||||
if ai.discoveryPort != 12345 {
|
if ai.discoveryPort != 12345 {
|
||||||
t.Errorf("discoveryPort = %d; want 12345", ai.discoveryPort)
|
t.Errorf("discoveryPort = %d; want 12345", ai.discoveryPort)
|
||||||
}
|
}
|
||||||
|
if ai.dataPort != 54321 {
|
||||||
|
t.Errorf("dataPort = %d; want 54321", ai.dataPort)
|
||||||
|
}
|
||||||
if string(ai.groupID) != "customGroup" {
|
if string(ai.groupID) != "customGroup" {
|
||||||
t.Errorf("groupID = %s; want customGroup", string(ai.groupID))
|
t.Errorf("groupID = %s; want customGroup", string(ai.groupID))
|
||||||
}
|
}
|
||||||
@@ -79,9 +83,11 @@ func newMockAutoInterface(name string, config *common.InterfaceConfig) (*mockAut
|
|||||||
// Initialize maps that would normally be initialized in Start()
|
// Initialize maps that would normally be initialized in Start()
|
||||||
ai.peers = make(map[string]*Peer)
|
ai.peers = make(map[string]*Peer)
|
||||||
ai.linkLocalAddrs = make([]string, 0)
|
ai.linkLocalAddrs = make([]string, 0)
|
||||||
ai.adoptedInterfaces = make(map[string]string)
|
ai.adoptedInterfaces = make(map[string]*AdoptedInterface)
|
||||||
ai.interfaceServers = make(map[string]*net.UDPConn)
|
ai.interfaceServers = make(map[string]*net.UDPConn)
|
||||||
|
ai.discoveryServers = make(map[string]*net.UDPConn)
|
||||||
ai.multicastEchoes = make(map[string]time.Time)
|
ai.multicastEchoes = make(map[string]time.Time)
|
||||||
|
ai.timedOutInterfaces = make(map[string]time.Time)
|
||||||
|
|
||||||
return &mockAutoInterface{AutoInterface: ai}, nil
|
return &mockAutoInterface{AutoInterface: ai}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user