mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
259 lines
7.5 KiB
Go
259 lines
7.5 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package node
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"quad4/reticulum-go/pkg/common"
|
|
"quad4/reticulum-go/pkg/debug"
|
|
"quad4/reticulum-go/pkg/interfaces"
|
|
)
|
|
|
|
func sliceEqual(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func floatEqual(a, b float64) bool {
|
|
const eps = 1e-9
|
|
d := a - b
|
|
if d < 0 {
|
|
d = -d
|
|
}
|
|
return d < eps
|
|
}
|
|
|
|
func interfaceConfigsEqualForReload(a, b *common.InterfaceConfig) bool {
|
|
if a == nil && b == nil {
|
|
return true
|
|
}
|
|
if a == nil || b == nil {
|
|
return false
|
|
}
|
|
return a.Type == b.Type &&
|
|
a.Enabled == b.Enabled &&
|
|
a.Address == b.Address &&
|
|
a.TargetHost == b.TargetHost &&
|
|
a.TargetPort == b.TargetPort &&
|
|
a.TargetAddress == b.TargetAddress &&
|
|
a.Port == b.Port &&
|
|
a.KISSFraming == b.KISSFraming &&
|
|
a.I2PTunneled == b.I2PTunneled &&
|
|
a.I2PConnectable == b.I2PConnectable &&
|
|
a.I2PSAMAddress == b.I2PSAMAddress &&
|
|
a.PreferIPv6 == b.PreferIPv6 &&
|
|
a.MaxReconnTries == b.MaxReconnTries &&
|
|
a.Bitrate == b.Bitrate &&
|
|
a.MTU == b.MTU &&
|
|
sliceEqual(a.I2PPeers, b.I2PPeers) &&
|
|
sliceEqual(a.Devices, b.Devices) &&
|
|
sliceEqual(a.IgnoredDevices, b.IgnoredDevices) &&
|
|
a.GroupID == b.GroupID &&
|
|
a.DiscoveryScope == b.DiscoveryScope &&
|
|
a.DiscoveryPort == b.DiscoveryPort &&
|
|
a.DataPort == b.DataPort &&
|
|
a.MulticastAddrType == b.MulticastAddrType &&
|
|
a.Interface == b.Interface &&
|
|
floatEqual(a.AnnounceCap, b.AnnounceCap) &&
|
|
floatEqual(a.AnnounceRateTarget, b.AnnounceRateTarget) &&
|
|
a.AnnounceRateGrace == b.AnnounceRateGrace &&
|
|
floatEqual(a.AnnounceRatePenalty, b.AnnounceRatePenalty) &&
|
|
a.IngressControl == b.IngressControl &&
|
|
a.IngressControlSet == b.IngressControlSet &&
|
|
a.ICNewTime == b.ICNewTime &&
|
|
floatEqual(a.ICBurstFreqNew, b.ICBurstFreqNew) &&
|
|
floatEqual(a.ICBurstFreq, b.ICBurstFreq) &&
|
|
a.ICMaxHeldAnnounces == b.ICMaxHeldAnnounces &&
|
|
a.ICBurstHold == b.ICBurstHold &&
|
|
a.ICBurstPenalty == b.ICBurstPenalty &&
|
|
a.ICHeldReleaseInterval == b.ICHeldReleaseInterval &&
|
|
floatEqual(a.ICPRBurstFreqNew, b.ICPRBurstFreqNew) &&
|
|
floatEqual(a.ICPRBurstFreq, b.ICPRBurstFreq) &&
|
|
floatEqual(a.ECPRFreq, b.ECPRFreq) &&
|
|
a.EgressControl == b.EgressControl &&
|
|
a.EgressControlSet == b.EgressControlSet &&
|
|
a.NetworkName == b.NetworkName &&
|
|
a.Passphrase == b.Passphrase &&
|
|
a.IFACSize == b.IFACSize &&
|
|
a.IFACNetname == b.IFACNetname &&
|
|
a.IFACNetkey == b.IFACNetkey &&
|
|
a.PublishIFAC == b.PublishIFAC &&
|
|
a.Command == b.Command &&
|
|
a.RespawnDelay == b.RespawnDelay &&
|
|
a.SharedInstanceType == b.SharedInstanceType &&
|
|
a.InstanceName == b.InstanceName &&
|
|
a.CertFile == b.CertFile &&
|
|
a.KeyFile == b.KeyFile &&
|
|
a.PeerKey == b.PeerKey &&
|
|
a.SNI == b.SNI &&
|
|
a.Mode == b.Mode &&
|
|
a.RecursivePRs == b.RecursivePRs &&
|
|
a.AnnouncesFromInternal == b.AnnouncesFromInternal &&
|
|
a.AnnouncesFromInternalSet == b.AnnouncesFromInternalSet &&
|
|
a.AnnouncesToInternal == b.AnnouncesToInternal &&
|
|
a.AnnouncesToInternalSet == b.AnnouncesToInternalSet &&
|
|
a.Gravity == b.Gravity &&
|
|
a.GravitySet == b.GravitySet &&
|
|
a.Outgoing == b.Outgoing &&
|
|
a.OutgoingSet == b.OutgoingSet &&
|
|
a.Device == b.Device &&
|
|
a.Speed == b.Speed &&
|
|
a.DataBits == b.DataBits &&
|
|
a.Parity == b.Parity &&
|
|
a.StopBits == b.StopBits &&
|
|
a.RTSCTS == b.RTSCTS &&
|
|
a.DSRDTR == b.DSRDTR &&
|
|
a.XONXOFF == b.XONXOFF &&
|
|
a.SerialFrameIdleMs == b.SerialFrameIdleMs &&
|
|
a.Path == b.Path &&
|
|
a.TransportMode == b.TransportMode &&
|
|
a.Domain == b.Domain &&
|
|
a.ResolveIntervalSec == b.ResolveIntervalSec &&
|
|
a.ContextID == b.ContextID &&
|
|
a.LongPollSec == b.LongPollSec &&
|
|
a.Discoverable == b.Discoverable &&
|
|
a.DiscoveryName == b.DiscoveryName &&
|
|
a.ReachableOn == b.ReachableOn &&
|
|
a.DiscoveryAnnounceIntervalSec == b.DiscoveryAnnounceIntervalSec &&
|
|
a.DiscoveryStampValue == b.DiscoveryStampValue &&
|
|
a.DiscoveryEncrypt == b.DiscoveryEncrypt &&
|
|
a.DiscoveryLocationCmd == b.DiscoveryLocationCmd &&
|
|
floatEqual(a.DiscoveryLatitude, b.DiscoveryLatitude) &&
|
|
floatEqual(a.DiscoveryLongitude, b.DiscoveryLongitude) &&
|
|
floatEqual(a.DiscoveryHeight, b.DiscoveryHeight) &&
|
|
a.HasDiscoveryGeo == b.HasDiscoveryGeo &&
|
|
a.ControlHost == b.ControlHost &&
|
|
a.ControlPort == b.ControlPort &&
|
|
a.MTUOverhead == b.MTUOverhead &&
|
|
a.AutoFragmentation == b.AutoFragmentation &&
|
|
a.AutoFragSet == b.AutoFragSet &&
|
|
a.ShortFrames == b.ShortFrames &&
|
|
a.ShortMTU == b.ShortMTU &&
|
|
a.HandshakeX2 == b.HandshakeX2 &&
|
|
a.ProofX2 == b.ProofX2 &&
|
|
a.AutoBitrate == b.AutoBitrate &&
|
|
a.AutoBitrateSet == b.AutoBitrateSet &&
|
|
a.CSMAOverhead == b.CSMAOverhead &&
|
|
a.CSMAOverheadSet == b.CSMAOverheadSet &&
|
|
floatEqual(a.TimeoutMargin, b.TimeoutMargin) &&
|
|
a.FrequencyHz == b.FrequencyHz &&
|
|
a.SampleRate == b.SampleRate &&
|
|
a.Bandwidth == b.Bandwidth &&
|
|
floatEqual(a.RXGain, b.RXGain) &&
|
|
floatEqual(a.TXGain, b.TXGain) &&
|
|
a.Modem == b.Modem &&
|
|
a.SerialNum == b.SerialNum
|
|
}
|
|
|
|
func (n *Node) tearDownInterface(iface interfaces.Interface) {
|
|
if iface == nil {
|
|
return
|
|
}
|
|
name := iface.GetName()
|
|
n.transport.UnregisterInterface(name)
|
|
n.wiringMu.Lock()
|
|
if buf, ok := n.buffers[name]; ok {
|
|
_ = buf.Close()
|
|
delete(n.buffers, name)
|
|
}
|
|
if ch, ok := n.channels[name]; ok {
|
|
_ = ch.Close()
|
|
delete(n.channels, name)
|
|
}
|
|
n.wiringMu.Unlock()
|
|
_ = iface.Stop()
|
|
}
|
|
|
|
// ReloadInterfaces reconciles network interfaces against newCfg without restarting transport.
|
|
func (n *Node) ReloadInterfaces(newCfg *common.ReticulumConfig) error {
|
|
if newCfg == nil {
|
|
return errors.New("nil config")
|
|
}
|
|
if n.sharedInstance != nil && !n.sharedInstance.OwnsNetworkInterfaces() {
|
|
n.config = newCfg
|
|
n.transport.SetReticulumConfig(newCfg)
|
|
return nil
|
|
}
|
|
if n.transport == nil {
|
|
return errors.New("nil transport")
|
|
}
|
|
|
|
n.reloadMu.Lock()
|
|
defer n.reloadMu.Unlock()
|
|
|
|
oldCfg := n.config
|
|
oldByName := make(map[string]interfaces.Interface, len(n.interfaces))
|
|
for _, x := range n.interfaces {
|
|
oldByName[x.GetName()] = x
|
|
}
|
|
|
|
for name, oldI := range oldByName {
|
|
ic, inNew := newCfg.Interfaces[name]
|
|
if !inNew || !ic.Enabled {
|
|
n.tearDownInterface(oldI)
|
|
delete(oldByName, name)
|
|
continue
|
|
}
|
|
if !interfaceConfigsEqualForReload(oldCfg.Interfaces[name], ic) {
|
|
n.tearDownInterface(oldI)
|
|
delete(oldByName, name)
|
|
}
|
|
}
|
|
|
|
var next []interfaces.Interface
|
|
for name, ic := range newCfg.Interfaces {
|
|
if !ic.Enabled {
|
|
continue
|
|
}
|
|
if oldI, ok := oldByName[name]; ok {
|
|
next = append(next, oldI)
|
|
continue
|
|
}
|
|
niface, err := interfaces.NewFromConfigWithContext(name, ic, n.fromConfigContext())
|
|
if err != nil {
|
|
if newCfg.PanicOnInterfaceErr {
|
|
return fmt.Errorf("interface %s: %w", name, err)
|
|
}
|
|
debug.Log(debug.DebugError, "ReloadInterfaces: skip interface", "name", name, "error", err)
|
|
continue
|
|
}
|
|
if err := niface.Start(); err != nil {
|
|
if newCfg.PanicOnInterfaceErr {
|
|
return fmt.Errorf("start %s: %w", name, err)
|
|
}
|
|
debug.Log(debug.DebugError, "ReloadInterfaces: start failed", "name", name, "error", err)
|
|
continue
|
|
}
|
|
ni, ok := niface.(common.NetworkInterface)
|
|
if !ok {
|
|
_ = niface.Stop()
|
|
return fmt.Errorf("interface %s does not implement common.NetworkInterface", name)
|
|
}
|
|
if err := n.transport.ReplaceInterface(name, ni); err != nil {
|
|
_ = niface.Stop()
|
|
if newCfg.PanicOnInterfaceErr {
|
|
return err
|
|
}
|
|
debug.Log(debug.DebugError, "ReloadInterfaces: ReplaceInterface failed", "name", name, "error", err)
|
|
continue
|
|
}
|
|
n.handleInterface(ni)
|
|
n.wireConnectivityHooks(niface)
|
|
next = append(next, niface)
|
|
}
|
|
|
|
n.interfaces = next
|
|
n.config = newCfg
|
|
n.transport.SetReticulumConfig(newCfg)
|
|
return nil
|
|
}
|