feat(interfaces): update AutoInterface with multicast address generation and duplicate data handling

This commit is contained in:
2026-01-02 17:48:34 -06:00
parent 62b5d6a4d2
commit f28ba4d69e

View File

@@ -5,9 +5,9 @@ package interfaces
import ( import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"fmt" "fmt"
"net" "net"
"strings"
"sync" "sync"
"time" "time"
@@ -34,8 +34,16 @@ const (
MCAST_ADDR_TYPE_PERMANENT = "0" MCAST_ADDR_TYPE_PERMANENT = "0"
MCAST_ADDR_TYPE_TEMPORARY = "1" MCAST_ADDR_TYPE_TEMPORARY = "1"
MULTI_IF_DEQUE_LEN = 48
MULTI_IF_DEQUE_TTL = 750 * time.Millisecond
) )
type DequeEntry struct {
hash [32]byte
timestamp time.Time
}
type AutoInterface struct { type AutoInterface struct {
BaseInterface BaseInterface
groupID []byte groupID []byte
@@ -45,7 +53,6 @@ type AutoInterface struct {
discoveryScope string discoveryScope string
multicastAddrType string multicastAddrType string
mcastDiscoveryAddr string mcastDiscoveryAddr string
ifacNetname string
peers map[string]*Peer peers map[string]*Peer
linkLocalAddrs []string linkLocalAddrs []string
adoptedInterfaces map[string]*AdoptedInterface adoptedInterfaces map[string]*AdoptedInterface
@@ -60,6 +67,7 @@ type AutoInterface struct {
peerJobInterval time.Duration peerJobInterval time.Duration
peeringTimeout time.Duration peeringTimeout time.Duration
mcastEchoTimeout time.Duration mcastEchoTimeout time.Duration
mifDeque []DequeEntry
done chan struct{} done chan struct{}
stopOnce sync.Once stopOnce sync.Once
} }
@@ -76,6 +84,24 @@ type Peer struct {
addr *net.UDPAddr addr *net.UDPAddr
} }
func descopeLinkLocal(addr string) string {
// Drop scope specifier expressed as %ifname (macOS)
if i := strings.Index(addr, "%"); i != -1 {
addr = addr[:i]
}
// Drop embedded scope specifier (NetBSD, OpenBSD)
// Python: re.sub(r"fe80:[0-9a-f]*::","fe80::", link_local_addr)
if strings.HasPrefix(addr, "fe80:") {
parts := strings.Split(addr, ":")
// Check for fe80:[scope]::...
if len(parts) >= 3 && parts[2] == "" && parts[1] != "" {
return "fe80::" + strings.Join(parts[3:], ":")
}
}
return addr
}
func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterface, error) { func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterface, error) {
groupID := DEFAULT_GROUP_ID groupID := DEFAULT_GROUP_ID
if config.GroupID != "" { if config.GroupID != "" {
@@ -88,6 +114,9 @@ func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterfa
} }
multicastAddrType := MCAST_ADDR_TYPE_TEMPORARY multicastAddrType := MCAST_ADDR_TYPE_TEMPORARY
if config.MulticastAddrType != "" {
multicastAddrType = normalizeMulticastType(config.MulticastAddrType)
}
discoveryPort := DEFAULT_DISCOVERY_PORT discoveryPort := DEFAULT_DISCOVERY_PORT
if config.DiscoveryPort != 0 { if config.DiscoveryPort != 0 {
@@ -101,8 +130,13 @@ func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterfa
groupHash := sha256.Sum256([]byte(groupID)) groupHash := sha256.Sum256([]byte(groupID))
ifacNetname := hex.EncodeToString(groupHash[:])[:16] // Python-compatible multicast address generation
mcastAddr := fmt.Sprintf("ff%s%s::%s", discoveryScope, multicastAddrType, ifacNetname) // gt = "0:" + "{:02x}".format(g[3]+(g[2]<<8)) + ":" + ...
gt := "0"
for i := 1; i <= 6; i++ {
gt += fmt.Sprintf(":%02x%02x", groupHash[i*2], groupHash[i*2+1])
}
mcastAddr := fmt.Sprintf("ff%s%s:%s", multicastAddrType, discoveryScope, gt)
ai := &AutoInterface{ ai := &AutoInterface{
BaseInterface: BaseInterface{ BaseInterface: BaseInterface{
@@ -124,7 +158,6 @@ func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterfa
discoveryScope: discoveryScope, discoveryScope: discoveryScope,
multicastAddrType: multicastAddrType, multicastAddrType: multicastAddrType,
mcastDiscoveryAddr: mcastAddr, mcastDiscoveryAddr: mcastAddr,
ifacNetname: ifacNetname,
peers: make(map[string]*Peer), peers: make(map[string]*Peer),
linkLocalAddrs: make([]string, 0), linkLocalAddrs: make([]string, 0),
adoptedInterfaces: make(map[string]*AdoptedInterface), adoptedInterfaces: make(map[string]*AdoptedInterface),
@@ -138,6 +171,7 @@ func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterfa
peerJobInterval: PEER_JOB_INTERVAL, peerJobInterval: PEER_JOB_INTERVAL,
peeringTimeout: PEERING_TIMEOUT, peeringTimeout: PEERING_TIMEOUT,
mcastEchoTimeout: MCAST_ECHO_TIMEOUT, mcastEchoTimeout: MCAST_ECHO_TIMEOUT,
mifDeque: make([]DequeEntry, 0, MULTI_IF_DEQUE_LEN),
done: make(chan struct{}), done: make(chan struct{}),
} }
@@ -272,7 +306,7 @@ func (ai *AutoInterface) configureInterface(iface *net.Interface) error {
for _, addr := range addrs { for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok { if ipnet, ok := addr.(*net.IPNet); ok {
if ipnet.IP.To4() == nil && ipnet.IP.IsLinkLocalUnicast() { if ipnet.IP.To4() == nil && ipnet.IP.IsLinkLocalUnicast() {
linkLocalAddr = ipnet.IP.String() linkLocalAddr = descopeLinkLocal(ipnet.IP.String())
break break
} }
} }
@@ -381,12 +415,17 @@ func (ai *AutoInterface) handleDiscovery(conn *net.UDPConn, ifaceName string) {
return return
} }
if n >= len(ai.groupHash) { // Python: discovery_token = RNS.Identity.full_hash(self.group_id+ipv6_src[0].encode("utf-8"))
receivedHash := buf[:len(ai.groupHash)] peerIP := descopeLinkLocal(remoteAddr.IP.String())
if bytes.Equal(receivedHash, ai.groupHash) { tokenSource := append(ai.groupID, []byte(peerIP)...)
expectedHash := sha256.Sum256(tokenSource)
if n >= len(expectedHash) {
receivedHash := buf[:len(expectedHash)]
if bytes.Equal(receivedHash, expectedHash[:]) {
ai.handlePeerAnnounce(remoteAddr, ifaceName) ai.handlePeerAnnounce(remoteAddr, ifaceName)
} else { } else {
debug.Log(debug.DEBUG_TRACE, "Received discovery with mismatched group hash", "interface", ifaceName) debug.Log(debug.DEBUG_TRACE, "Received discovery with mismatched group hash", "interface", ifaceName, "peer", peerIP)
} }
} }
} }
@@ -405,7 +444,7 @@ func (ai *AutoInterface) handleData(conn *net.UDPConn, ifaceName string) {
default: default:
} }
n, _, err := conn.ReadFromUDP(buf) n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil { if err != nil {
if ai.IsOnline() { if ai.IsOnline() {
debug.Log(debug.DEBUG_ERROR, "Data read error", "interface", ifaceName, "error", err) debug.Log(debug.DEBUG_ERROR, "Data read error", "interface", ifaceName, "error", err)
@@ -413,8 +452,41 @@ func (ai *AutoInterface) handleData(conn *net.UDPConn, ifaceName string) {
return return
} }
data := buf[:n]
dataHash := sha256.Sum256(data)
now := time.Now()
ai.Mutex.Lock()
// Check for duplicate in mifDeque
isDuplicate := false
for i := 0; i < len(ai.mifDeque); i++ {
if ai.mifDeque[i].hash == dataHash && now.Sub(ai.mifDeque[i].timestamp) < MULTI_IF_DEQUE_TTL {
isDuplicate = true
break
}
}
if isDuplicate {
ai.Mutex.Unlock()
continue
}
// Add to deque
ai.mifDeque = append(ai.mifDeque, DequeEntry{hash: dataHash, timestamp: now})
if len(ai.mifDeque) > MULTI_IF_DEQUE_LEN {
ai.mifDeque = ai.mifDeque[1:]
}
// Refresh peer if known
peerIP := descopeLinkLocal(remoteAddr.IP.String())
peerKey := peerIP + "%" + ifaceName
if peer, exists := ai.peers[peerKey]; exists {
peer.lastHeard = now
}
ai.Mutex.Unlock()
if callback := ai.GetPacketCallback(); callback != nil { if callback := ai.GetPacketCallback(); callback != nil {
callback(buf[:n], ai) callback(data, ai)
} }
} }
} }
@@ -485,7 +557,11 @@ func (ai *AutoInterface) sendPeerAnnounce() {
} }
} }
if _, err := ai.outboundConn.WriteToUDP(ai.groupHash, mcastAddr); err != nil { // Python: discovery_token = RNS.Identity.full_hash(self.group_id+link_local_address.encode("utf-8"))
tokenSource := append(ai.groupID, []byte(adoptedIface.linkLocalAddr)...)
token := sha256.Sum256(tokenSource)
if _, err := ai.outboundConn.WriteToUDP(token[:], mcastAddr); err != nil {
debug.Log(debug.DEBUG_VERBOSE, "Failed to send peer announce", "interface", ifaceName, "error", err) debug.Log(debug.DEBUG_VERBOSE, "Failed to send peer announce", "interface", ifaceName, "error", err)
} else { } else {
debug.Log(debug.DEBUG_TRACE, "Sent peer announce", "interface", adoptedIface.name) debug.Log(debug.DEBUG_TRACE, "Sent peer announce", "interface", adoptedIface.name)