generalise NBP FwdReq handling

This commit is contained in:
Josh Deprez 2024-05-03 18:32:12 +10:00
parent 7b496a0519
commit fbd0a0371f
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw
6 changed files with 101 additions and 76 deletions

View file

@ -101,7 +101,7 @@ func Unmarshal(data []byte) (*Packet, error) {
t := Tuple{ t := Tuple{
Network: ddp.Network(binary.BigEndian.Uint16(data[:2])), Network: ddp.Network(binary.BigEndian.Uint16(data[:2])),
Node: ddp.Node(data[2]), Node: ddp.Node(data[2]),
Socket: data[3], Socket: ddp.Socket(data[3]),
Enumerator: data[4], Enumerator: data[4],
} }
data = data[5:] data = data[5:]
@ -128,7 +128,7 @@ func Unmarshal(data []byte) (*Packet, error) {
type Tuple struct { type Tuple struct {
Network ddp.Network Network ddp.Network
Node ddp.Node Node ddp.Node
Socket uint8 Socket ddp.Socket
Enumerator uint8 Enumerator uint8
Object string // length-prefixed Object string // length-prefixed
Type string // length-prefixed Type string // length-prefixed
@ -147,7 +147,7 @@ func (t *Tuple) writeTo(b *bytes.Buffer) error {
} }
write16(b, t.Network) write16(b, t.Network)
b.WriteByte(byte(t.Node)) b.WriteByte(byte(t.Node))
b.WriteByte(t.Socket) b.WriteByte(byte(t.Socket))
b.WriteByte(t.Enumerator) b.WriteByte(t.Enumerator)
b.WriteByte(byte(len(t.Object))) b.WriteByte(byte(len(t.Object)))
b.WriteString(t.Object) b.WriteString(t.Object)

25
main.go
View file

@ -358,6 +358,7 @@ func main() {
RTMPMachine: rtmpMachine, RTMPMachine: rtmpMachine,
Router: rooter, Router: rooter,
} }
rooter.Ports = append(rooter.Ports, etherTalkPort)
routes.InsertEtherTalkDirect(etherTalkPort) routes.InsertEtherTalkDirect(etherTalkPort)
for _, az := range etherTalkPort.AvailableZones { for _, az := range etherTalkPort.AvailableZones {
zones.Upsert(etherTalkPort.NetStart, az, etherTalkPort) zones.Upsert(etherTalkPort.NetStart, az, etherTalkPort)
@ -468,26 +469,34 @@ func main() {
log.Printf("AURP: Couldn't unmarshal encapsulated DDP packet: %v", err) log.Printf("AURP: Couldn't unmarshal encapsulated DDP packet: %v", err)
continue continue
} }
log.Printf("DDP/AURP: Got %d.%d.%d -> %d.%d.%d proto %d data len %d", // log.Printf("DDP/AURP: Got %d.%d.%d -> %d.%d.%d proto %d data len %d",
ddpkt.SrcNet, ddpkt.SrcNode, ddpkt.SrcSocket, // ddpkt.SrcNet, ddpkt.SrcNode, ddpkt.SrcSocket,
ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket, // ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket,
ddpkt.Proto, len(ddpkt.Data)) // ddpkt.Proto, len(ddpkt.Data))
// Is it addressed to me? // Is it addressed to me?
if ddpkt.DstNode == 0 { // Node 0 = any router for the network = me var localPort *router.EtherTalkPort
for _, port := range rooter.Ports {
if ddpkt.DstNet >= port.NetStart && ddpkt.DstNet <= port.NetEnd {
localPort = port
break
}
}
if ddpkt.DstNode == 0 && localPort != nil { // Node 0 = any router for the network = me
// Is it NBP? FwdReq needs translating. // Is it NBP? FwdReq needs translating.
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)
continue continue
} }
// It's NBP // It's NBP, specifically it should be a FwdReq
if err := rooter.HandleNBPInAURP(ctx, pr, ddpkt); err != nil { if err := rooter.HandleNBPFromAURP(ctx, ddpkt); err != nil {
log.Printf("NBP/DDP/AURP: %v", err) log.Printf("NBP/DDP/AURP: %v", err)
} }
continue
} }
// Route the packet // Route the packet!
if err := rooter.Forward(ctx, ddpkt); err != nil { if err := rooter.Forward(ctx, ddpkt); err != nil {
log.Printf("DDP/AURP: Couldn't route packet: %v", err) log.Printf("DDP/AURP: Couldn't route packet: %v", err)
} }

View file

@ -20,12 +20,58 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"slices"
"gitea.drjosh.dev/josh/jrouter/atalk" "gitea.drjosh.dev/josh/jrouter/atalk"
"gitea.drjosh.dev/josh/jrouter/atalk/nbp" "gitea.drjosh.dev/josh/jrouter/atalk/nbp"
"github.com/sfiera/multitalk/pkg/ddp" "github.com/sfiera/multitalk/pkg/ddp"
) )
func (rtr *Router) handleNBPFwdReq(ctx context.Context, ddpkt *ddp.ExtPacket, nbpkt *nbp.Packet) error {
// A FwdReq was addressed to us. That means a remote router thinks the
// zone is available on one or more of our local networks.
// There must be 1!
tuple := &nbpkt.Tuples[0]
for _, outPort := range rtr.Ports {
if !slices.Contains(outPort.AvailableZones, tuple.Zone) {
continue
}
log.Printf("NBP: Converting FwdReq to LkUp (%v)", tuple)
// Convert it to a LkUp and broadcast on the corresponding port
nbpkt.Function = nbp.FunctionLkUp
nbpRaw, err := nbpkt.Marshal()
if err != nil {
return fmt.Errorf("couldn't marshal LkUp: %v", err)
}
// Inside AppleTalk SE, pp 8-20:
// "If the destination network is extended, however, the router must also
// change the destination network number to $0000, so that the packet is
// received by all nodes on the network (within the correct zone multicast
// address)."
ddpkt.DstNet = 0x0000
ddpkt.DstNode = 0xFF // Broadcast node address within the dest network
ddpkt.Data = nbpRaw
if err := outPort.ZoneMulticast(tuple.Zone, ddpkt); err != nil {
return err
}
// But also... if it matches us, reply directly with a LkUp-Reply of our own
outDDP, err := outPort.helloWorldThisIsMe(nbpkt.NBPID, tuple)
if err != nil || outDDP == nil {
return err
}
if err := rtr.Forward(ctx, outDDP); err != nil {
return err
}
}
return nil
}
func (port *EtherTalkPort) HandleNBP(ctx context.Context, ddpkt *ddp.ExtPacket) error { func (port *EtherTalkPort) HandleNBP(ctx context.Context, 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)
@ -41,7 +87,7 @@ func (port *EtherTalkPort) HandleNBP(ctx context.Context, ddpkt *ddp.ExtPacket)
switch nbpkt.Function { switch nbpkt.Function {
case nbp.FunctionLkUp: case nbp.FunctionLkUp:
// when in AppleTalk, do as Apple Internet Router does... // when in AppleTalk, do as Apple Internet Router does...
outDDP, err := port.helloWorldThisIsMe(ddpkt, nbpkt.NBPID, &nbpkt.Tuples[0]) outDDP, err := port.helloWorldThisIsMe(nbpkt.NBPID, &nbpkt.Tuples[0])
if err != nil || outDDP == nil { if err != nil || outDDP == nil {
return err return err
} }
@ -50,7 +96,7 @@ func (port *EtherTalkPort) HandleNBP(ctx context.Context, ddpkt *ddp.ExtPacket)
return port.Send(ctx, outDDP) return port.Send(ctx, outDDP)
case nbp.FunctionFwdReq: case nbp.FunctionFwdReq:
// TODO: handle FwdReq input return port.Router.handleNBPFwdReq(ctx, ddpkt, nbpkt)
case nbp.FunctionBrRq: case nbp.FunctionBrRq:
return port.handleNBPBrRq(ctx, ddpkt, nbpkt) return port.handleNBPBrRq(ctx, ddpkt, nbpkt)
@ -58,16 +104,20 @@ func (port *EtherTalkPort) HandleNBP(ctx context.Context, ddpkt *ddp.ExtPacket)
default: default:
return fmt.Errorf("TODO: handle function %v", nbpkt.Function) return fmt.Errorf("TODO: handle function %v", nbpkt.Function)
} }
return nil
} }
func (port *EtherTalkPort) handleNBPBrRq(ctx context.Context, ddpkt *ddp.ExtPacket, nbpkt *nbp.Packet) error { func (port *EtherTalkPort) handleNBPBrRq(ctx context.Context, ddpkt *ddp.ExtPacket, nbpkt *nbp.Packet) error {
// A BrRq was addressed to us. The sender (on a local network) is aware that
// the network is extended and routed, and instead of broadcasting a LkUp
// itself, is asking us to do it.
// There must be 1! // There must be 1!
tuple := &nbpkt.Tuples[0] tuple := &nbpkt.Tuples[0]
if tuple.Zone == "" || tuple.Zone == "*" { // This logic would be required on a non-extended network:
tuple.Zone = port.DefaultZoneName // if tuple.Zone == "" || tuple.Zone == "*" {
} // tuple.Zone = port.DefaultZoneName
// }
zones := port.Router.ZoneTable.LookupName(tuple.Zone) zones := port.Router.ZoneTable.LookupName(tuple.Zone)
@ -109,7 +159,7 @@ func (port *EtherTalkPort) handleNBPBrRq(ctx context.Context, ddpkt *ddp.ExtPack
// But also...if we match the query, reply as though it was a LkUp // But also...if we match the query, reply as though it was a LkUp
// This uses the *input* port information. // This uses the *input* port information.
outDDP2, err := port.helloWorldThisIsMe(ddpkt, nbpkt.NBPID, tuple) outDDP2, err := port.helloWorldThisIsMe(nbpkt.NBPID, tuple)
if err != nil { if err != nil {
return err return err
} }
@ -117,8 +167,8 @@ func (port *EtherTalkPort) handleNBPBrRq(ctx context.Context, ddpkt *ddp.ExtPack
continue continue
} }
log.Print("NBP: Replying to BrRq directly with LkUp-Reply for myself") log.Print("NBP: Replying to BrRq directly with LkUp-Reply for myself")
// Can reply to BrRq on the same port we got it, because it wasn't // Can reply to this BrRq on the same port we got it, because it
// routed // wasn't routed
if err := port.Send(ctx, outDDP2); err != nil { if err := port.Send(ctx, outDDP2); err != nil {
return err return err
} }
@ -158,7 +208,7 @@ func (port *EtherTalkPort) handleNBPBrRq(ctx context.Context, ddpkt *ddp.ExtPack
} }
// Returns an NBP LkUp-Reply for the router itself, with the address from this port. // Returns an NBP LkUp-Reply for the router itself, with the address from this port.
func (port *EtherTalkPort) helloWorldThisIsMe(ddpkt *ddp.ExtPacket, nbpID uint8, tuple *nbp.Tuple) (*ddp.ExtPacket, error) { func (port *EtherTalkPort) helloWorldThisIsMe(nbpID uint8, tuple *nbp.Tuple) (*ddp.ExtPacket, error) {
if tuple.Object != "jrouter" && tuple.Object != "=" { if tuple.Object != "jrouter" && tuple.Object != "=" {
return nil, nil return nil, nil
} }
@ -187,13 +237,23 @@ func (port *EtherTalkPort) helloWorldThisIsMe(ddpkt *ddp.ExtPacket, nbpID uint8,
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't marshal LkUp-Reply: %v", err) return nil, fmt.Errorf("couldn't marshal LkUp-Reply: %v", err)
} }
// Inside AppleTalk SE, pp 7-16:
// "In BrRq, FwdReq, and LkUp packets, which carry only a single tuple, the
// address field contains the internet address of the requester, allowing
// the responder to address the LkUp-Reply datagram."
// Inside AppleTalk SE, pp 8-20:
// "Note: NBP is defined so that the router's NBP process does not
// participate in the NBP response process; the response is sent directly to
// the original requester through DDP. It is important that the original
// requester's field be obtained from the address field of the NBP tuple."
return &ddp.ExtPacket{ return &ddp.ExtPacket{
ExtHeader: ddp.ExtHeader{ ExtHeader: ddp.ExtHeader{
Size: uint16(len(respRaw)) + atalk.DDPExtHeaderSize, Size: uint16(len(respRaw)) + atalk.DDPExtHeaderSize,
Cksum: 0, Cksum: 0,
DstNet: ddpkt.SrcNet, DstNet: tuple.Network,
DstNode: ddpkt.SrcNode, DstNode: tuple.Node,
DstSocket: ddpkt.SrcSocket, DstSocket: tuple.Socket,
SrcNet: port.MyAddr.Network, SrcNet: port.MyAddr.Network,
SrcNode: port.MyAddr.Node, SrcNode: port.MyAddr.Node,
SrcSocket: 2, SrcSocket: 2,

View file

@ -19,13 +19,12 @@ package router
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"gitea.drjosh.dev/josh/jrouter/atalk/nbp" "gitea.drjosh.dev/josh/jrouter/atalk/nbp"
"github.com/sfiera/multitalk/pkg/ddp" "github.com/sfiera/multitalk/pkg/ddp"
) )
func (rtr *Router) HandleNBPInAURP(ctx context.Context, peer *AURPPeer, ddpkt *ddp.ExtPacket) error { func (rtr *Router) HandleNBPFromAURP(ctx context.Context, 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)
} }
@ -37,50 +36,5 @@ func (rtr *Router) HandleNBPInAURP(ctx context.Context, peer *AURPPeer, ddpkt *d
// It's something else?? // It's something else??
return fmt.Errorf("can't handle %v", nbpkt.Function) return fmt.Errorf("can't handle %v", nbpkt.Function)
} }
return rtr.handleNBPFwdReq(ctx, ddpkt, nbpkt)
if len(nbpkt.Tuples) < 1 {
return fmt.Errorf("no tuples in NBP packet")
}
tuple := &nbpkt.Tuples[0]
zones := rtr.ZoneTable.LookupName(tuple.Zone)
for _, z := range zones {
if z.LocalPort == nil {
continue
}
port := z.LocalPort
log.Printf("NBP/DDP/AURP: Converting FwdReq to LkUp (%v)", tuple)
// Convert it to a LkUp and broadcast on EtherTalk
nbpkt.Function = nbp.FunctionLkUp
nbpRaw, err := nbpkt.Marshal()
if err != nil {
return fmt.Errorf("couldn't marshal LkUp: %v", err)
}
// "If the destination network is extended, however, the router must also
// change the destination network number to $0000, so that the packet is
// received by all nodes on the network (within the correct zone multicast
// address)."
ddpkt.DstNet = 0x0000
ddpkt.DstNode = 0xFF // Broadcast node address within the dest network
ddpkt.Data = nbpRaw
if err := port.ZoneMulticast(tuple.Zone, ddpkt); err != nil {
return err
}
// But also... if it matches us, reply directly with a LkUp-Reply of our own
outDDP, err := port.helloWorldThisIsMe(ddpkt, nbpkt.NBPID, tuple)
if err != nil || outDDP == nil {
return err
}
log.Print("NBP/DDP/AURP: Replying to BrRq with LkUp-Reply for myself")
if err := rtr.Forward(ctx, outDDP); err != nil {
return err
}
}
return nil
} }

View file

@ -84,10 +84,10 @@ func (port *EtherTalkPort) Serve(ctx context.Context) {
log.Printf("Couldn't unmarshal DDP packet: %v", err) log.Printf("Couldn't unmarshal DDP packet: %v", err)
continue continue
} }
log.Printf("DDP: src (%d.%d s %d) dst (%d.%d s %d) proto %d data len %d", // log.Printf("DDP: src (%d.%d s %d) dst (%d.%d s %d) proto %d data len %d",
ddpkt.SrcNet, ddpkt.SrcNode, ddpkt.SrcSocket, // ddpkt.SrcNet, ddpkt.SrcNode, ddpkt.SrcSocket,
ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket, // ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket,
ddpkt.Proto, len(ddpkt.Data)) // ddpkt.Proto, len(ddpkt.Data))
// Glean address info for AMT, but only if SrcNet is our net // Glean address info for AMT, but only if SrcNet is our net
// (If it's not our net, then it was routed from elsewhere, and // (If it's not our net, then it was routed from elsewhere, and
@ -103,6 +103,7 @@ func (port *EtherTalkPort) Serve(ctx context.Context) {
if !ok { if !ok {
continue continue
} }
port.MyAddr = myAddr.Proto
// Our network? // Our network?
// "The network number 0 is reserved to mean unknown; by default // "The network number 0 is reserved to mean unknown; by default

View file

@ -27,6 +27,7 @@ type Router struct {
Config *Config Config *Config
RouteTable *RouteTable RouteTable *RouteTable
ZoneTable *ZoneTable ZoneTable *ZoneTable
Ports []*EtherTalkPort
} }
// Forward routes a packet towards the right destination. // Forward routes a packet towards the right destination.