WIP: routers on ethertalk

This commit is contained in:
Josh Deprez 2024-04-29 09:32:57 +10:00
parent da4c729912
commit 7f8b0cb451
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw
7 changed files with 197 additions and 64 deletions

80
main.go
View file

@ -224,18 +224,18 @@ func main() {
// -------------------------------- Peers --------------------------------- // -------------------------------- Peers ---------------------------------
var peersMu sync.Mutex var peersMu sync.Mutex
peers := make(map[udpAddr]*router.Peer) peers := make(map[udpAddr]*router.AURPPeer)
status.AddItem(ctx, "AURP Peers", peerTableTemplate, func(context.Context) (any, error) { status.AddItem(ctx, "AURP Peers", peerTableTemplate, func(context.Context) (any, error) {
var peerInfo []*router.Peer var peerInfo []*router.AURPPeer
func() { func() {
peersMu.Lock() peersMu.Lock()
defer peersMu.Unlock() defer peersMu.Unlock()
peerInfo = make([]*router.Peer, 0, len(peers)) peerInfo = make([]*router.AURPPeer, 0, len(peers))
for _, p := range peers { for _, p := range peers {
peerInfo = append(peerInfo, p) peerInfo = append(peerInfo, p)
} }
}() }()
slices.SortFunc(peerInfo, func(pa, pb *router.Peer) int { slices.SortFunc(peerInfo, func(pa, pb *router.AURPPeer) int {
return cmp.Or( return cmp.Or(
-cmp.Compare( -cmp.Compare(
bool2Int(pa.ReceiverState() == router.ReceiverConnected), bool2Int(pa.ReceiverState() == router.ReceiverConnected),
@ -257,7 +257,7 @@ func main() {
} }
var wg sync.WaitGroup var wg sync.WaitGroup
goPeerHandler := func(p *router.Peer) { goPeerHandler := func(p *router.AURPPeer) {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
@ -308,7 +308,7 @@ func main() {
continue continue
} }
peer := &router.Peer{ peer := &router.AURPPeer{
Config: cfg, Config: cfg,
Transport: &aurp.Transport{ Transport: &aurp.Transport{
LocalDI: localDI, LocalDI: localDI,
@ -434,18 +434,29 @@ func main() {
// TODO: more generic routing // TODO: more generic routing
if ddpkt.DstNet != 0 && !(ddpkt.DstNet >= cfg.EtherTalk.NetStart && ddpkt.DstNet <= cfg.EtherTalk.NetEnd) { if ddpkt.DstNet != 0 && !(ddpkt.DstNet >= cfg.EtherTalk.NetStart && ddpkt.DstNet <= cfg.EtherTalk.NetEnd) {
// Is it for a network in the routing table? // Is it for a network in the routing table?
rt := routes.LookupRoute(ddpkt.DstNet) route := routes.LookupRoute(ddpkt.DstNet)
if rt == nil { if route == nil {
log.Printf("DDP: no route for network %d", ddpkt.DstNet) log.Printf("DDP: no route for network %d", ddpkt.DstNet)
continue continue
} }
switch {
case route.AURPPeer != nil:
// Encap ethPacket.Payload into an AURP packet // Encap ethPacket.Payload into an AURP packet
log.Printf("DDP: forwarding to AURP peer %v", rt.Peer.RemoteAddr) log.Printf("DDP: forwarding to AURP peer %v", route.AURPPeer.RemoteAddr)
if _, err := rt.Peer.Send(rt.Peer.Transport.NewAppleTalkPacket(ethFrame.Payload)); err != nil { if _, err := route.AURPPeer.Send(route.AURPPeer.Transport.NewAppleTalkPacket(ethFrame.Payload)); err != nil {
log.Printf("DDP: Couldn't forward packet to AURP peer: %v", err) log.Printf("DDP: Couldn't forward packet to AURP peer: %v", err)
} }
case route.EtherTalkPeer != nil:
// Route payload to another router over EtherTalk
// TODO: this is unlikely because we currenly only support 1 EtherTalk port
log.Printf("DDP: forwarding to EtherTalk peer %v", route.EtherTalkPeer.PeerAddr)
// Note: resolving AARP can block
if err := route.EtherTalkPeer.Forward(ctx, ddpkt); err != nil {
log.Printf("DDP: Couldn't forward packet to EtherTalk peer: %v", err)
}
}
continue continue
} }
@ -534,7 +545,7 @@ func main() {
continue continue
} }
// New peer! // New peer!
pr = &router.Peer{ pr = &router.AURPPeer{
Config: cfg, Config: cfg,
Transport: &aurp.Transport{ Transport: &aurp.Transport{
LocalDI: localDI, LocalDI: localDI,
@ -584,14 +595,7 @@ func main() {
ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket, ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket,
ddpkt.Proto, len(ddpkt.Data)) ddpkt.Proto, len(ddpkt.Data))
// "Route" the packet // Route the packet
// Since for now there's only one local network, the routing
// decision is pretty easy
// TODO: Fix this to support other AppleTalk routers
if ddpkt.DstNet < cfg.EtherTalk.NetStart || ddpkt.DstNet > cfg.EtherTalk.NetEnd {
log.Print("DDP/AURP: dropping packet not addressed to our EtherTalk range")
continue
}
// Check and adjust the Hop Count // Check and adjust the Hop Count
// Note the ddp package doesn't make this simple // Note the ddp package doesn't make this simple
@ -604,8 +608,44 @@ func main() {
ddpkt.Size &^= 0x3C00 ddpkt.Size &^= 0x3C00
ddpkt.Size |= hopCount << 10 ddpkt.Size |= hopCount << 10
if ddpkt.DstNet < cfg.EtherTalk.NetStart || ddpkt.DstNet > cfg.EtherTalk.NetEnd {
// Is it a network in the routing table?
route := routes.LookupRoute(ddpkt.DstNet)
if route == nil {
log.Printf("DDP/AURP: no route for packet (dstnet %d); dropping packet", ddpkt.DstNet)
break
}
switch {
case route.AURPPeer != nil:
// Routing between AURP peers... bit weird but OK
log.Printf("DDP/AURP: forwarding to AURP peer %v", route.AURPPeer.RemoteAddr)
outPkt, err := ddp.ExtMarshal(*ddpkt)
if err != nil {
log.Printf("DDP/AURP: Couldn't re-marshal packet: %v", err)
break
}
if _, err := route.AURPPeer.Send(route.AURPPeer.Transport.NewAppleTalkPacket(outPkt)); err != nil {
log.Printf("DDP/AURP: Couldn't forward packet to AURP peer: %v", err)
}
case route.EtherTalkPeer != nil:
// AURP peer -> EtherTalk peer
// Note: resolving AARP can block
log.Printf("DDP/AURP: forwarding to EtherTalk peer %v", route.EtherTalkPeer.PeerAddr)
if err := route.EtherTalkPeer.Forward(ctx, ddpkt); err != nil {
log.Printf("DDP/AURP: Couldn't forward packet to EtherTalk peer: %v", err)
}
default:
log.Print("DDP/AURP: no forwarding mechanism for route; dropping packet")
}
continue
}
// Is it addressed to me? Is it NBP? // Is it addressed to me? Is it NBP?
if ddpkt.DstNode == 0 { // Node 0 = the router for the network if ddpkt.DstNode == 0 { // Node 0 = any router for the network = me
if ddpkt.DstSocket != 2 { if ddpkt.DstSocket != 2 {
// Something else?? TODO // Something else?? TODO
log.Printf("DDP/AURP: I don't have anything 'listening' on socket %d", ddpkt.DstSocket) log.Printf("DDP/AURP: I don't have anything 'listening' on socket %d", ddpkt.DstSocket)

View file

@ -110,7 +110,7 @@ func (rtr *Router) HandleNBP(srcHWAddr ethernet.Addr, ddpkt *ddp.ExtPacket) erro
if route == nil { if route == nil {
return fmt.Errorf("no route for network %d", z.Network) return fmt.Errorf("no route for network %d", z.Network)
} }
peer := route.Peer peer := route.AURPPeer
if peer == nil { if peer == nil {
return fmt.Errorf("nil peer for route for network %d", z.Network) return fmt.Errorf("nil peer for route for network %d", z.Network)
} }

View file

@ -24,7 +24,7 @@ import (
"github.com/sfiera/multitalk/pkg/ddp" "github.com/sfiera/multitalk/pkg/ddp"
) )
func (rtr *Router) HandleNBPInAURP(peer *Peer, ddpkt *ddp.ExtPacket) error { func (rtr *Router) HandleNBPInAURP(peer *AURPPeer, ddpkt *ddp.ExtPacket) error {
if ddpkt.Proto != ddp.ProtoNBP { if ddpkt.Proto != ddp.ProtoNBP {
return fmt.Errorf("invalid DDP type %d on socket 2", ddpkt.Proto) return fmt.Errorf("invalid DDP type %d on socket 2", ddpkt.Proto)
} }

View file

@ -92,8 +92,8 @@ func (ss SenderState) String() string {
} }
} }
// Peer handles the peering with a peer AURP router. // AURPPeer handles the peering with a peer AURP router.
type Peer struct { type AURPPeer struct {
// Whole router config. // Whole router config.
Config *Config Config *Config
@ -125,31 +125,31 @@ type Peer struct {
sstate SenderState sstate SenderState
} }
func (p *Peer) ReceiverState() ReceiverState { func (p *AURPPeer) ReceiverState() ReceiverState {
p.mu.RLock() p.mu.RLock()
defer p.mu.RUnlock() defer p.mu.RUnlock()
return p.rstate return p.rstate
} }
func (p *Peer) SenderState() SenderState { func (p *AURPPeer) SenderState() SenderState {
p.mu.RLock() p.mu.RLock()
defer p.mu.RUnlock() defer p.mu.RUnlock()
return p.sstate return p.sstate
} }
func (p *Peer) setRState(rstate ReceiverState) { func (p *AURPPeer) setRState(rstate ReceiverState) {
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()
p.rstate = rstate p.rstate = rstate
} }
func (p *Peer) setSState(sstate SenderState) { func (p *AURPPeer) setSState(sstate SenderState) {
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()
p.sstate = sstate p.sstate = sstate
} }
func (p *Peer) disconnect() { func (p *AURPPeer) disconnect() {
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()
p.rstate = ReceiverUnconnected p.rstate = ReceiverUnconnected
@ -157,7 +157,7 @@ func (p *Peer) disconnect() {
} }
// Send encodes and sends pkt to the remote host. // Send encodes and sends pkt to the remote host.
func (p *Peer) Send(pkt aurp.Packet) (int, error) { func (p *AURPPeer) Send(pkt aurp.Packet) (int, error) {
var b bytes.Buffer var b bytes.Buffer
if _, err := pkt.WriteTo(&b); err != nil { if _, err := pkt.WriteTo(&b); err != nil {
return 0, err return 0, err
@ -166,7 +166,7 @@ func (p *Peer) Send(pkt aurp.Packet) (int, error) {
return p.UDPConn.WriteToUDP(b.Bytes(), p.RemoteAddr) return p.UDPConn.WriteToUDP(b.Bytes(), p.RemoteAddr)
} }
func (p *Peer) Handle(ctx context.Context) error { func (p *AURPPeer) Handle(ctx context.Context) error {
rticker := time.NewTicker(1 * time.Second) rticker := time.NewTicker(1 * time.Second)
defer rticker.Stop() defer rticker.Stop()
sticker := time.NewTicker(1 * time.Second) sticker := time.NewTicker(1 * time.Second)
@ -244,7 +244,7 @@ func (p *Peer) Handle(ctx context.Context) error {
if sendRetries >= tickleRetryLimit { if sendRetries >= tickleRetryLimit {
log.Printf("AURP Peer: Send retry limit reached while waiting for Tickle-Ack, closing connection") log.Printf("AURP Peer: Send retry limit reached while waiting for Tickle-Ack, closing connection")
p.setRState(ReceiverUnconnected) p.setRState(ReceiverUnconnected)
p.RoutingTable.DeletePeer(p) p.RoutingTable.DeleteAURPPeer(p)
break break
} }
@ -263,7 +263,7 @@ func (p *Peer) Handle(ctx context.Context) error {
if sendRetries >= sendRetryLimit { if sendRetries >= sendRetryLimit {
log.Printf("AURP Peer: Send retry limit reached while waiting for RI-Rsp, closing connection") log.Printf("AURP Peer: Send retry limit reached while waiting for RI-Rsp, closing connection")
p.setRState(ReceiverUnconnected) p.setRState(ReceiverUnconnected)
p.RoutingTable.DeletePeer(p) p.RoutingTable.DeleteAURPPeer(p)
break break
} }
@ -458,7 +458,7 @@ func (p *Peer) Handle(ctx context.Context) error {
log.Printf("AURP Peer: Learned about these networks: %v", pkt.Networks) log.Printf("AURP Peer: Learned about these networks: %v", pkt.Networks)
for _, nt := range pkt.Networks { for _, nt := range pkt.Networks {
p.RoutingTable.InsertRoute( p.RoutingTable.InsertAURPRoute(
p, p,
nt.Extended, nt.Extended,
ddp.Network(nt.RangeStart), ddp.Network(nt.RangeStart),
@ -534,7 +534,7 @@ func (p *Peer) Handle(ctx context.Context) error {
// Do nothing except respond with RI-Ack // Do nothing except respond with RI-Ack
case aurp.EventCodeNA: case aurp.EventCodeNA:
if err := p.RoutingTable.InsertRoute( if err := p.RoutingTable.InsertAURPRoute(
p, p,
et.Extended, et.Extended,
et.RangeStart, et.RangeStart,
@ -546,10 +546,10 @@ func (p *Peer) Handle(ctx context.Context) error {
ackFlag = aurp.RoutingFlagSendZoneInfo ackFlag = aurp.RoutingFlagSendZoneInfo
case aurp.EventCodeND: case aurp.EventCodeND:
p.RoutingTable.DeletePeerNetwork(p, et.RangeStart) p.RoutingTable.DeleteAURPPeerNetwork(p, et.RangeStart)
case aurp.EventCodeNDC: case aurp.EventCodeNDC:
p.RoutingTable.UpdateRouteDistance(p, et.RangeStart, et.Distance+1) p.RoutingTable.UpdateAURPRouteDistance(p, et.RangeStart, et.Distance+1)
case aurp.EventCodeNRC: case aurp.EventCodeNRC:
// "An exterior router sends a Network Route Change // "An exterior router sends a Network Route Change
@ -557,7 +557,7 @@ func (p *Peer) Handle(ctx context.Context) error {
// through its local internet changes to a path through // through its local internet changes to a path through
// a tunneling port, causing split-horizoned processing // a tunneling port, causing split-horizoned processing
// to eliminate that networks routing information." // to eliminate that networks routing information."
p.RoutingTable.DeletePeerNetwork(p, et.RangeStart) p.RoutingTable.DeleteAURPPeerNetwork(p, et.RangeStart)
case aurp.EventCodeZC: case aurp.EventCodeZC:
// "This event is reserved for future use." // "This event is reserved for future use."
@ -575,7 +575,7 @@ func (p *Peer) Handle(ctx context.Context) error {
} }
log.Printf("AURP Peer: Router Down: error code %d %s", pkt.ErrorCode, pkt.ErrorCode) log.Printf("AURP Peer: Router Down: error code %d %s", pkt.ErrorCode, pkt.ErrorCode)
p.RoutingTable.DeletePeer(p) p.RoutingTable.DeleteAURPPeer(p)
// Respond with RI-Ack // Respond with RI-Ack
if _, err := p.Send(p.Transport.NewRIAckPacket(pkt.ConnectionID, pkt.Sequence, 0)); err != nil { if _, err := p.Send(p.Transport.NewRIAckPacket(pkt.ConnectionID, pkt.Sequence, 0)); err != nil {

54
router/peer_eth.go Normal file
View file

@ -0,0 +1,54 @@
/*
Copyright 2024 Josh Deprez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package router
import (
"context"
"github.com/google/gopacket/pcap"
"github.com/sfiera/multitalk/pkg/ddp"
"github.com/sfiera/multitalk/pkg/ethernet"
"github.com/sfiera/multitalk/pkg/ethertalk"
)
// EtherTalkPeer holds data needed to exchange routes and zones with another
// router on the EtherTalk network.
type EtherTalkPeer struct {
PcapHandle *pcap.Handle
MyHWAddr ethernet.Addr
AARP *AARPMachine
PeerAddr ddp.Addr
}
// Forward forwards a DDP packet to the next router.
func (p *EtherTalkPeer) Forward(ctx context.Context, pkt *ddp.ExtPacket) error {
// TODO: AARP resolution can block
de, err := p.AARP.Resolve(ctx, p.PeerAddr)
if err != nil {
return err
}
outFrame, err := ethertalk.AppleTalk(p.MyHWAddr, *pkt)
if err != nil {
return err
}
outFrame.Dst = de
outFrameRaw, err := ethertalk.Marshal(*outFrame)
if err != nil {
return err
}
return p.PcapHandle.WritePacketData(outFrameRaw)
}

View file

@ -24,15 +24,18 @@ import (
"github.com/sfiera/multitalk/pkg/ddp" "github.com/sfiera/multitalk/pkg/ddp"
) )
// const maxRouteAge = 10 * time.Minute // TODO: confirm const maxRouteAge = 10 * time.Minute // TODO: confirm
type Route struct { type Route struct {
Extended bool Extended bool
NetStart ddp.Network NetStart ddp.Network
NetEnd ddp.Network NetEnd ddp.Network
Peer *Peer
Distance uint8 Distance uint8
LastSeen time.Time LastSeen time.Time
// Exactly one of the following should be set
AURPPeer *AURPPeer
EtherTalkPeer *EtherTalkPeer
} }
func (r Route) LastSeenAgo() string { func (r Route) LastSeenAgo() string {
@ -70,15 +73,13 @@ func (rt *RoutingTable) LookupRoute(network ddp.Network) *Route {
var bestRoute *Route var bestRoute *Route
for r := range rt.routes { for r := range rt.routes {
if r.Peer == nil {
continue
}
if network < r.NetStart || network > r.NetEnd { if network < r.NetStart || network > r.NetEnd {
continue continue
} }
// if time.Since(r.LastSeen) > maxRouteAge { // Exclude EtherTalk routes that are too old
// continue if r.EtherTalkPeer != nil && time.Since(r.LastSeen) > maxRouteAge {
// } continue
}
if bestRoute == nil { if bestRoute == nil {
bestRoute = r bestRoute = r
continue continue
@ -90,41 +91,41 @@ func (rt *RoutingTable) LookupRoute(network ddp.Network) *Route {
return bestRoute return bestRoute
} }
func (rt *RoutingTable) DeletePeer(peer *Peer) { func (rt *RoutingTable) DeleteAURPPeer(peer *AURPPeer) {
rt.mu.Lock() rt.mu.Lock()
defer rt.mu.Unlock() defer rt.mu.Unlock()
for route := range rt.routes { for route := range rt.routes {
if route.Peer == peer { if route.AURPPeer == peer {
delete(rt.routes, route) delete(rt.routes, route)
} }
} }
} }
func (rt *RoutingTable) DeletePeerNetwork(peer *Peer, network ddp.Network) { func (rt *RoutingTable) DeleteAURPPeerNetwork(peer *AURPPeer, network ddp.Network) {
rt.mu.Lock() rt.mu.Lock()
defer rt.mu.Unlock() defer rt.mu.Unlock()
for route := range rt.routes { for route := range rt.routes {
if route.Peer == peer && route.NetStart == network { if route.AURPPeer == peer && route.NetStart == network {
delete(rt.routes, route) delete(rt.routes, route)
} }
} }
} }
func (rt *RoutingTable) UpdateRouteDistance(peer *Peer, network ddp.Network, distance uint8) { func (rt *RoutingTable) UpdateAURPRouteDistance(peer *AURPPeer, network ddp.Network, distance uint8) {
rt.mu.Lock() rt.mu.Lock()
defer rt.mu.Unlock() defer rt.mu.Unlock()
for route := range rt.routes { for route := range rt.routes {
if route.Peer == peer && route.NetStart == network { if route.AURPPeer == peer && route.NetStart == network {
route.Distance = distance route.Distance = distance
route.LastSeen = time.Now() route.LastSeen = time.Now()
} }
} }
} }
func (rt *RoutingTable) InsertRoute(peer *Peer, extended bool, netStart, netEnd ddp.Network, metric uint8) error { func (rt *RoutingTable) UpsertEthRoute(peer *EtherTalkPeer, extended bool, netStart, netEnd ddp.Network, metric uint8) error {
if netStart > netEnd { if netStart > netEnd {
return fmt.Errorf("invalid network range [%d, %d]", netStart, netEnd) return fmt.Errorf("invalid network range [%d, %d]", netStart, netEnd)
} }
@ -136,9 +137,33 @@ func (rt *RoutingTable) InsertRoute(peer *Peer, extended bool, netStart, netEnd
Extended: extended, Extended: extended,
NetStart: netStart, NetStart: netStart,
NetEnd: netEnd, NetEnd: netEnd,
Peer: peer,
Distance: metric, Distance: metric,
LastSeen: time.Now(), LastSeen: time.Now(),
EtherTalkPeer: peer,
}
rt.mu.Lock()
defer rt.mu.Unlock()
// TODO: update if present rather than insert
rt.routes[r] = struct{}{}
return nil
}
func (rt *RoutingTable) InsertAURPRoute(peer *AURPPeer, extended bool, netStart, netEnd ddp.Network, metric uint8) error {
if netStart > netEnd {
return fmt.Errorf("invalid network range [%d, %d]", netStart, netEnd)
}
if netStart != netEnd && !extended {
return fmt.Errorf("invalid network range [%d, %d] for nonextended network", netStart, netEnd)
}
r := &Route{
Extended: extended,
NetStart: netStart,
NetEnd: netEnd,
Distance: metric,
LastSeen: time.Now(),
AURPPeer: peer,
} }
rt.mu.Lock() rt.mu.Lock()
@ -152,12 +177,10 @@ func (rt *RoutingTable) ValidRoutes() []*Route {
defer rt.mu.Unlock() defer rt.mu.Unlock()
valid := make([]*Route, 0, len(rt.routes)) valid := make([]*Route, 0, len(rt.routes))
for r := range rt.routes { for r := range rt.routes {
if r.Peer == nil { // Exclude EtherTalk routes that are too old
if r.EtherTalkPeer != nil && time.Since(r.LastSeen) > maxRouteAge {
continue continue
} }
// if time.Since(r.LastSeen) > maxRouteAge {
// continue
// }
valid = append(valid, r) valid = append(valid, r)
} }
return valid return valid

View file

@ -159,14 +159,30 @@ func (m *RTMPMachine) Run(ctx context.Context, incomingCh <-chan *ddp.ExtPacket)
} }
case rtmp.FunctionLoopProbe: case rtmp.FunctionLoopProbe:
log.Printf("RTMP: TODO: handle Loop Probes") log.Print("RTMP: TODO: handle Loop Probes")
} }
case ddp.ProtoRTMPResp: case ddp.ProtoRTMPResp:
// It's a peer router on the AppleTalk network! // It's a peer router on the AppleTalk network!
// TODO: integrate this information with the routing table?
log.Print("RTMP: Got Response or Data") log.Print("RTMP: Got Response or Data")
dataPkt, err := rtmp.UnmarshalDataPacket(pkt.Data)
if err != nil {
log.Printf("RTMP: Couldn't unmarshal RTMP Data packet: %v", err)
break
}
peer := &EtherTalkPeer{
PcapHandle: m.PcapHandle,
MyHWAddr: m.AARP.myAddr.Hardware,
AARP: m.AARP,
PeerAddr: dataPkt.RouterAddr,
}
for _, rt := range dataPkt.NetworkTuples {
if err := m.RoutingTable.UpsertEthRoute(peer, rt.Extended, rt.RangeStart, rt.RangeEnd, rt.Distance+1); err != nil {
log.Printf("RTMP: Couldn't upsert EtherTalk route: %v", err)
}
}
default: default:
log.Printf("RTMP: invalid DDP type %d on socket 1", pkt.Proto) log.Printf("RTMP: invalid DDP type %d on socket 1", pkt.Proto)