Some checks failed
Bearer / scan (push) Successful in 9s
Go Build Multi-Platform / build (amd64, linux) (push) Successful in 42s
Go Build Multi-Platform / build (amd64, darwin) (push) Successful in 44s
Go Build Multi-Platform / build (arm, freebsd) (push) Successful in 41s
Go Build Multi-Platform / build (arm, windows) (push) Successful in 39s
Go Build Multi-Platform / build (arm64, windows) (push) Successful in 1m8s
Go Build Multi-Platform / build (wasm, js) (push) Successful in 1m6s
TinyGo Build / tinygo-build (tinygo-wasm, tinygo-wasm, reticulum-go.wasm, wasm) (pull_request) Failing after 1m2s
TinyGo Build / tinygo-build (tinygo-build, tinygo-default, reticulum-go-tinygo, ) (pull_request) Failing after 1m4s
Go Revive Lint / lint (push) Successful in 1m4s
Go Test Multi-Platform / Test (ubuntu-latest, arm64) (push) Successful in 1m24s
Run Gosec / tests (push) Successful in 1m29s
Go Test Multi-Platform / Test (ubuntu-latest, amd64) (push) Successful in 2m31s
Go Build Multi-Platform / build (amd64, freebsd) (push) Successful in 9m28s
Go Build Multi-Platform / build (arm, linux) (push) Successful in 9m28s
Go Build Multi-Platform / build (amd64, windows) (push) Successful in 9m30s
Go Build Multi-Platform / build (arm64, darwin) (push) Successful in 9m27s
Go Build Multi-Platform / build (arm64, linux) (push) Successful in 9m26s
Go Build Multi-Platform / build (arm64, freebsd) (push) Successful in 9m29s
Go Build Multi-Platform / Create Release (push) Has been skipped
308 lines
7.2 KiB
Go
308 lines
7.2 KiB
Go
// SPDX-License-Identifier: 0BSD
|
|
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
|
package interfaces
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
|
)
|
|
|
|
const (
|
|
BITRATE_MINIMUM = 1200 // Minimum bitrate in bits/second
|
|
MODE_FULL = 0x01
|
|
|
|
// Interface modes
|
|
MODE_GATEWAY = 0x02
|
|
MODE_ACCESS_POINT = 0x03
|
|
MODE_ROAMING = 0x04
|
|
MODE_BOUNDARY = 0x05
|
|
|
|
// Interface types
|
|
TYPE_UDP = 0x01
|
|
TYPE_TCP = 0x02
|
|
|
|
PROPAGATION_RATE = 0.02 // 2% of interface bandwidth
|
|
)
|
|
|
|
type Interface interface {
|
|
GetName() string
|
|
GetType() common.InterfaceType
|
|
GetMode() common.InterfaceMode
|
|
IsOnline() bool
|
|
IsDetached() bool
|
|
IsEnabled() bool
|
|
Detach()
|
|
Enable()
|
|
Disable()
|
|
Send(data []byte, addr string) error
|
|
SetPacketCallback(common.PacketCallback)
|
|
GetPacketCallback() common.PacketCallback
|
|
ProcessIncoming([]byte)
|
|
ProcessOutgoing([]byte) error
|
|
SendPathRequest([]byte) error
|
|
SendLinkPacket([]byte, []byte, time.Time) error
|
|
Start() error
|
|
Stop() error
|
|
GetMTU() int
|
|
GetConn() net.Conn
|
|
GetBandwidthAvailable() bool
|
|
common.NetworkInterface
|
|
}
|
|
|
|
type BaseInterface struct {
|
|
Name string
|
|
Mode common.InterfaceMode
|
|
Type common.InterfaceType
|
|
Online bool
|
|
Enabled bool
|
|
Detached bool
|
|
IN bool
|
|
OUT bool
|
|
MTU int
|
|
Bitrate int64
|
|
TxBytes uint64
|
|
RxBytes uint64
|
|
lastTx time.Time
|
|
lastRx time.Time
|
|
|
|
Mutex sync.RWMutex
|
|
packetCallback common.PacketCallback
|
|
}
|
|
|
|
func NewBaseInterface(name string, ifType common.InterfaceType, enabled bool) BaseInterface {
|
|
return BaseInterface{
|
|
Name: name,
|
|
Mode: common.IF_MODE_FULL,
|
|
Type: ifType,
|
|
Online: false,
|
|
Enabled: enabled,
|
|
Detached: false,
|
|
IN: false,
|
|
OUT: false,
|
|
MTU: common.DEFAULT_MTU,
|
|
Bitrate: BITRATE_MINIMUM,
|
|
lastTx: time.Now(),
|
|
lastRx: time.Now(),
|
|
}
|
|
}
|
|
|
|
func (i *BaseInterface) SetPacketCallback(callback common.PacketCallback) {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
i.packetCallback = callback
|
|
}
|
|
|
|
func (i *BaseInterface) GetPacketCallback() common.PacketCallback {
|
|
i.Mutex.RLock()
|
|
defer i.Mutex.RUnlock()
|
|
return i.packetCallback
|
|
}
|
|
|
|
func (i *BaseInterface) ProcessIncoming(data []byte) {
|
|
i.Mutex.Lock()
|
|
i.RxBytes += uint64(len(data))
|
|
i.Mutex.Unlock()
|
|
|
|
i.Mutex.RLock()
|
|
callback := i.packetCallback
|
|
i.Mutex.RUnlock()
|
|
|
|
if callback != nil {
|
|
callback(data, i)
|
|
}
|
|
}
|
|
|
|
func (i *BaseInterface) ProcessOutgoing(data []byte) error {
|
|
if !i.Online || i.Detached {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Interface cannot process outgoing packet - interface offline or detached", "name", i.Name)
|
|
return fmt.Errorf("interface offline or detached")
|
|
}
|
|
|
|
i.Mutex.Lock()
|
|
i.TxBytes += uint64(len(data))
|
|
i.Mutex.Unlock()
|
|
|
|
debug.Log(debug.DEBUG_VERBOSE, "Interface processed outgoing packet", "name", i.Name, "bytes", len(data), "total_tx", i.TxBytes)
|
|
return nil
|
|
}
|
|
|
|
func (i *BaseInterface) SendPathRequest(packet []byte) error {
|
|
if !i.Online || i.Detached {
|
|
return fmt.Errorf("interface offline or detached")
|
|
}
|
|
|
|
frame := make([]byte, 0, len(packet)+1)
|
|
frame = append(frame, common.HEX_0x01)
|
|
frame = append(frame, packet...)
|
|
|
|
return i.ProcessOutgoing(frame)
|
|
}
|
|
|
|
func (i *BaseInterface) SendLinkPacket(dest []byte, data []byte, timestamp time.Time) error {
|
|
if !i.Online || i.Detached {
|
|
return fmt.Errorf("interface offline or detached")
|
|
}
|
|
|
|
frame := make([]byte, 0, len(dest)+len(data)+9)
|
|
frame = append(frame, common.HEX_0x02)
|
|
frame = append(frame, dest...)
|
|
|
|
ts := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(ts, uint64(timestamp.Unix())) // #nosec G115
|
|
frame = append(frame, ts...)
|
|
frame = append(frame, data...)
|
|
|
|
return i.ProcessOutgoing(frame)
|
|
}
|
|
|
|
func (i *BaseInterface) Detach() {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
i.Detached = true
|
|
i.Online = false
|
|
}
|
|
|
|
func (i *BaseInterface) IsEnabled() bool {
|
|
i.Mutex.RLock()
|
|
defer i.Mutex.RUnlock()
|
|
return i.Enabled && i.Online && !i.Detached
|
|
}
|
|
|
|
func (i *BaseInterface) Enable() {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
|
|
prevState := i.Enabled
|
|
i.Enabled = true
|
|
i.Online = true
|
|
|
|
debug.Log(debug.DEBUG_INFO, "Interface state changed", "name", i.Name, "enabled_prev", prevState, "enabled", i.Enabled, "online_prev", !i.Online, "online", i.Online)
|
|
}
|
|
|
|
func (i *BaseInterface) Disable() {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
i.Enabled = false
|
|
i.Online = false
|
|
debug.Log(debug.DEBUG_ERROR, "Interface disabled and offline", "name", i.Name)
|
|
}
|
|
|
|
func (i *BaseInterface) GetName() string {
|
|
return i.Name
|
|
}
|
|
|
|
func (i *BaseInterface) GetType() common.InterfaceType {
|
|
return i.Type
|
|
}
|
|
|
|
func (i *BaseInterface) GetMode() common.InterfaceMode {
|
|
return i.Mode
|
|
}
|
|
|
|
func (i *BaseInterface) GetMTU() int {
|
|
return i.MTU
|
|
}
|
|
|
|
func (i *BaseInterface) IsOnline() bool {
|
|
i.Mutex.RLock()
|
|
defer i.Mutex.RUnlock()
|
|
return i.Online
|
|
}
|
|
|
|
func (i *BaseInterface) IsDetached() bool {
|
|
i.Mutex.RLock()
|
|
defer i.Mutex.RUnlock()
|
|
return i.Detached
|
|
}
|
|
|
|
func (i *BaseInterface) Start() error {
|
|
return nil
|
|
}
|
|
|
|
func (i *BaseInterface) Stop() error {
|
|
return nil
|
|
}
|
|
|
|
func (i *BaseInterface) Send(data []byte, address string) error {
|
|
debug.Log(debug.DEBUG_VERBOSE, "Interface sending bytes", "name", i.Name, "bytes", len(data), "address", address)
|
|
|
|
err := i.ProcessOutgoing(data)
|
|
if err != nil {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Interface failed to send data", "name", i.Name, "error", err)
|
|
return err
|
|
}
|
|
|
|
i.updateBandwidthStats(uint64(len(data)))
|
|
return nil
|
|
}
|
|
|
|
func (i *BaseInterface) GetConn() net.Conn {
|
|
return nil
|
|
}
|
|
|
|
func (i *BaseInterface) GetBandwidthAvailable() bool {
|
|
i.Mutex.RLock()
|
|
defer i.Mutex.RUnlock()
|
|
|
|
now := time.Now()
|
|
timeSinceLastTx := now.Sub(i.lastTx)
|
|
|
|
if timeSinceLastTx > time.Second {
|
|
debug.Log(debug.DEBUG_VERBOSE, "Interface bandwidth available", "name", i.Name, "idle_seconds", timeSinceLastTx.Seconds())
|
|
return true
|
|
}
|
|
|
|
bytesPerSec := float64(i.TxBytes) / timeSinceLastTx.Seconds()
|
|
currentUsage := bytesPerSec * 8
|
|
maxUsage := float64(i.Bitrate) * PROPAGATION_RATE
|
|
|
|
available := currentUsage < maxUsage
|
|
debug.Log(debug.DEBUG_VERBOSE, "Interface bandwidth stats", "name", i.Name, "current_bps", currentUsage, "max_bps", maxUsage, "usage_percent", (currentUsage/maxUsage)*100, "available", available)
|
|
|
|
return available
|
|
}
|
|
|
|
func (i *BaseInterface) updateBandwidthStats(bytes uint64) {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
|
|
i.TxBytes += bytes
|
|
i.lastTx = time.Now()
|
|
|
|
debug.Log(debug.DEBUG_VERBOSE, "Interface updated bandwidth stats", "name", i.Name, "tx_bytes", i.TxBytes, "last_tx", i.lastTx)
|
|
}
|
|
|
|
type InterceptedInterface struct {
|
|
Interface
|
|
interceptor func([]byte, common.NetworkInterface) error
|
|
originalSend func([]byte, string) error
|
|
}
|
|
|
|
// Create constructor for intercepted interface
|
|
func NewInterceptedInterface(base Interface, interceptor func([]byte, common.NetworkInterface) error) *InterceptedInterface {
|
|
return &InterceptedInterface{
|
|
Interface: base,
|
|
interceptor: interceptor,
|
|
originalSend: base.Send,
|
|
}
|
|
}
|
|
|
|
// Implement Send method for intercepted interface
|
|
func (i *InterceptedInterface) Send(data []byte, addr string) error {
|
|
// Call interceptor if provided
|
|
if i.interceptor != nil && len(data) > 0 {
|
|
if err := i.interceptor(data, i); err != nil {
|
|
debug.Log(debug.DEBUG_ERROR, "Failed to intercept outgoing packet", "error", err)
|
|
}
|
|
}
|
|
|
|
// Call original send
|
|
return i.originalSend(data, addr)
|
|
}
|