This commit is contained in:
Sudo-Ivan
2025-01-01 17:00:11 -06:00
parent 6cdc02346f
commit 0862830431
9 changed files with 673 additions and 125 deletions

170
pkg/transport/announce.go Normal file
View File

@@ -0,0 +1,170 @@
package transport
import (
"crypto/sha256"
"encoding/hex"
"sort"
"sync"
"time"
"github.com/Sudo-Ivan/reticulum-go/pkg/rate"
)
const (
MaxRetries = 3
RetryInterval = 5 * time.Second
MaxQueueSize = 1000
MinPriorityDelta = 0.1
DefaultPropagationRate = 0.02 // 2% of bandwidth for announces
)
type AnnounceEntry struct {
Data []byte
HopCount int
RetryCount int
LastRetry time.Time
SourceIface string
Priority float64
Hash string
}
type AnnounceManager struct {
announces map[string]*AnnounceEntry
announceQueue map[string][]*AnnounceEntry
rateLimiter *rate.Limiter
mutex sync.RWMutex
}
func NewAnnounceManager() *AnnounceManager {
return &AnnounceManager{
announces: make(map[string]*AnnounceEntry),
announceQueue: make(map[string][]*AnnounceEntry),
rateLimiter: rate.NewLimiter(DefaultPropagationRate, 1),
mutex: sync.RWMutex{},
}
}
func (am *AnnounceManager) ProcessAnnounce(data []byte, sourceIface string) error {
hash := sha256.Sum256(data)
hashStr := hex.EncodeToString(hash[:])
am.mutex.Lock()
defer am.mutex.Unlock()
if entry, exists := am.announces[hashStr]; exists {
if entry.HopCount <= int(data[0]) {
return nil
}
entry.HopCount = int(data[0])
entry.Data = data
entry.RetryCount = 0
entry.LastRetry = time.Now()
entry.Priority = calculatePriority(data[0], 0)
return nil
}
entry := &AnnounceEntry{
Data: data,
HopCount: int(data[0]),
RetryCount: 0,
LastRetry: time.Now(),
SourceIface: sourceIface,
Priority: calculatePriority(data[0], 0),
Hash: hashStr,
}
am.announces[hashStr] = entry
for iface := range am.announceQueue {
if iface != sourceIface {
am.queueAnnounce(entry, iface)
}
}
return nil
}
func (am *AnnounceManager) queueAnnounce(entry *AnnounceEntry, iface string) {
queue := am.announceQueue[iface]
if len(queue) >= MaxQueueSize {
// Remove lowest priority announce if queue is full
queue = queue[:len(queue)-1]
}
insertIdx := sort.Search(len(queue), func(i int) bool {
return queue[i].Priority < entry.Priority
})
queue = append(queue[:insertIdx], append([]*AnnounceEntry{entry}, queue[insertIdx:]...)...)
am.announceQueue[iface] = queue
}
func (am *AnnounceManager) GetNextAnnounce(iface string) *AnnounceEntry {
am.mutex.Lock()
defer am.mutex.Unlock()
queue := am.announceQueue[iface]
if len(queue) == 0 {
return nil
}
entry := queue[0]
now := time.Now()
if entry.RetryCount >= MaxRetries {
am.announceQueue[iface] = queue[1:]
delete(am.announces, entry.Hash)
return am.GetNextAnnounce(iface)
}
if now.Sub(entry.LastRetry) < RetryInterval {
return nil
}
if !am.rateLimiter.Allow() {
return nil
}
entry.RetryCount++
entry.LastRetry = now
entry.Priority = calculatePriority(byte(entry.HopCount), entry.RetryCount)
am.announceQueue[iface] = queue[1:]
am.queueAnnounce(entry, iface)
return entry
}
func calculatePriority(hopCount byte, retryCount int) float64 {
basePriority := 1.0 / float64(hopCount)
retryPenalty := float64(retryCount) * MinPriorityDelta
return basePriority - retryPenalty
}
func (am *AnnounceManager) CleanupExpired() {
am.mutex.Lock()
defer am.mutex.Unlock()
now := time.Now()
expiredHashes := make([]string, 0)
for hash, entry := range am.announces {
if entry.RetryCount >= MaxRetries || now.Sub(entry.LastRetry) > RetryInterval*MaxRetries {
expiredHashes = append(expiredHashes, hash)
}
}
for _, hash := range expiredHashes {
delete(am.announces, hash)
for iface, queue := range am.announceQueue {
newQueue := make([]*AnnounceEntry, 0, len(queue))
for _, entry := range queue {
if entry.Hash != hash {
newQueue = append(newQueue, entry)
}
}
am.announceQueue[iface] = newQueue
}
}
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/Sudo-Ivan/reticulum-go/pkg/announce"
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/interfaces"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/pathfinder"
@@ -616,65 +617,82 @@ func (t *Transport) HandlePacket(data []byte, iface common.NetworkInterface) {
}
}
func (t *Transport) handleAnnouncePacket(data []byte, iface common.NetworkInterface) {
// Validate minimum packet size (1 byte hop count + 32 bytes dest + 16 bytes identity + 4 bytes min app data)
if len(data) < 53 {
log.Printf("[DEBUG-3] Announce packet too small: %d bytes", len(data))
return
func (t *Transport) handleAnnouncePacket(data []byte, iface common.NetworkInterface) error {
// Validate minimum packet size (1 byte hop count + 32 bytes dest hash + 16 bytes min identity + 1 byte min app data)
if len(data) < 50 {
return fmt.Errorf("announce packet too small: %d bytes", len(data))
}
announceHash := sha256.Sum256(data)
log.Printf("[DEBUG-3] Processing announce %x from interface %s",
announceHash[:8], iface.GetName())
t.mutex.Lock()
if _, seen := t.seenAnnounces[string(announceHash[:])]; seen {
t.mutex.Unlock()
log.Printf("[DEBUG-4] Ignoring duplicate announce %x", announceHash[:8])
return
}
t.seenAnnounces[string(announceHash[:])] = true
t.mutex.Unlock()
// Don't forward if max hops reached
if data[0] >= MAX_HOPS {
log.Printf("[DEBUG-3] Announce exceeded max hops: %d", data[0])
return
}
// Parse announce fields
// Extract fields
hopCount := data[0]
destHash := data[1:33]
identity := data[33:49]
identityBytes := data[33:49]
appData := data[49:]
// Check for duplicate announces
announceHash := sha256.Sum256(data)
hashStr := string(announceHash[:])
t.mutex.Lock()
if _, seen := t.seenAnnounces[hashStr]; seen {
t.mutex.Unlock()
log.Printf("[DEBUG-7] Ignoring duplicate announce %x", announceHash[:8])
return nil
}
t.seenAnnounces[hashStr] = true
t.mutex.Unlock()
// Validate announce signature and store destination
id := identity.FromPublicKey(identityBytes)
if id == nil || !id.ValidateAnnounce(data, destHash, appData) {
return fmt.Errorf("invalid announce signature")
}
// Don't forward if max hops reached
if hopCount >= MAX_HOPS {
log.Printf("[DEBUG-7] Announce exceeded max hops: %d", hopCount)
return nil
}
// Add random delay before retransmission (0-2 seconds)
delay := time.Duration(rand.Float64() * 2 * float64(time.Second))
time.Sleep(delay)
// Check bandwidth allocation for announces
if !t.announceRate.Allow() {
log.Printf("[DEBUG-3] Announce rate limit exceeded, dropping")
return
log.Printf("[DEBUG-7] Announce rate limit exceeded, dropping")
return nil
}
// Increment hop count for forwarding
data[0] = hopCount + 1
forwardData := make([]byte, len(data))
copy(forwardData, data)
forwardData[0] = hopCount + 1
// Forward to other interfaces
var lastErr error
for name, outIface := range t.interfaces {
if outIface == iface || !outIface.IsEnabled() {
continue
}
// Check interface mode restrictions
// if outIface.GetMode() == interfaces.ModeAccessPoint {
// log.Printf("[DEBUG-7] Blocking announce broadcast on %s due to AP mode", name)
// continue
// }
log.Printf("[DEBUG-7] Forwarding announce on interface %s", name)
if err := outIface.Send(data, ""); err != nil {
if err := outIface.Send(forwardData, ""); err != nil {
log.Printf("[DEBUG-3] Failed to forward announce on %s: %v", name, err)
lastErr = err
}
}
// Notify announce handlers
t.notifyAnnounceHandlers(destHash, identity, appData)
t.notifyAnnounceHandlers(destHash, identityBytes, appData)
return lastErr
}
func (t *Transport) handleLinkPacket(data []byte, iface common.NetworkInterface) {