From fbd0a0371f50b454865d47352dbddbbe433ed0de Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Fri, 3 May 2024 18:32:12 +1000 Subject: [PATCH] generalise NBP FwdReq handling --- atalk/nbp/nbp.go | 6 ++-- main.go | 25 +++++++++----- router/nbp.go | 86 +++++++++++++++++++++++++++++++++++++++------- router/nbp_aurp.go | 50 ++------------------------- router/port.go | 9 ++--- router/router.go | 1 + 6 files changed, 101 insertions(+), 76 deletions(-) diff --git a/atalk/nbp/nbp.go b/atalk/nbp/nbp.go index 423533d..c2a948c 100644 --- a/atalk/nbp/nbp.go +++ b/atalk/nbp/nbp.go @@ -101,7 +101,7 @@ func Unmarshal(data []byte) (*Packet, error) { t := Tuple{ Network: ddp.Network(binary.BigEndian.Uint16(data[:2])), Node: ddp.Node(data[2]), - Socket: data[3], + Socket: ddp.Socket(data[3]), Enumerator: data[4], } data = data[5:] @@ -128,7 +128,7 @@ func Unmarshal(data []byte) (*Packet, error) { type Tuple struct { Network ddp.Network Node ddp.Node - Socket uint8 + Socket ddp.Socket Enumerator uint8 Object string // length-prefixed Type string // length-prefixed @@ -147,7 +147,7 @@ func (t *Tuple) writeTo(b *bytes.Buffer) error { } write16(b, t.Network) b.WriteByte(byte(t.Node)) - b.WriteByte(t.Socket) + b.WriteByte(byte(t.Socket)) b.WriteByte(t.Enumerator) b.WriteByte(byte(len(t.Object))) b.WriteString(t.Object) diff --git a/main.go b/main.go index f7e17ff..2fb4cc3 100644 --- a/main.go +++ b/main.go @@ -358,6 +358,7 @@ func main() { RTMPMachine: rtmpMachine, Router: rooter, } + rooter.Ports = append(rooter.Ports, etherTalkPort) routes.InsertEtherTalkDirect(etherTalkPort) for _, az := range etherTalkPort.AvailableZones { zones.Upsert(etherTalkPort.NetStart, az, etherTalkPort) @@ -468,26 +469,34 @@ func main() { log.Printf("AURP: Couldn't unmarshal encapsulated DDP packet: %v", err) continue } - log.Printf("DDP/AURP: Got %d.%d.%d -> %d.%d.%d proto %d data len %d", - ddpkt.SrcNet, ddpkt.SrcNode, ddpkt.SrcSocket, - ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket, - ddpkt.Proto, len(ddpkt.Data)) + // log.Printf("DDP/AURP: Got %d.%d.%d -> %d.%d.%d proto %d data len %d", + // ddpkt.SrcNet, ddpkt.SrcNode, ddpkt.SrcSocket, + // ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket, + // ddpkt.Proto, len(ddpkt.Data)) // 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. if ddpkt.DstSocket != 2 { // Something else?? TODO log.Printf("DDP/AURP: I don't have anything 'listening' on socket %d", ddpkt.DstSocket) continue } - // It's NBP - if err := rooter.HandleNBPInAURP(ctx, pr, ddpkt); err != nil { + // It's NBP, specifically it should be a FwdReq + if err := rooter.HandleNBPFromAURP(ctx, ddpkt); err != nil { log.Printf("NBP/DDP/AURP: %v", err) } + continue } - // Route the packet + // Route the packet! if err := rooter.Forward(ctx, ddpkt); err != nil { log.Printf("DDP/AURP: Couldn't route packet: %v", err) } diff --git a/router/nbp.go b/router/nbp.go index d97c9ff..5af2afc 100644 --- a/router/nbp.go +++ b/router/nbp.go @@ -20,12 +20,58 @@ import ( "context" "fmt" "log" + "slices" "gitea.drjosh.dev/josh/jrouter/atalk" "gitea.drjosh.dev/josh/jrouter/atalk/nbp" "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 { if ddpkt.Proto != ddp.ProtoNBP { 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 { case nbp.FunctionLkUp: // 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 { return err } @@ -50,7 +96,7 @@ func (port *EtherTalkPort) HandleNBP(ctx context.Context, ddpkt *ddp.ExtPacket) return port.Send(ctx, outDDP) case nbp.FunctionFwdReq: - // TODO: handle FwdReq input + return port.Router.handleNBPFwdReq(ctx, ddpkt, nbpkt) case nbp.FunctionBrRq: return port.handleNBPBrRq(ctx, ddpkt, nbpkt) @@ -58,16 +104,20 @@ func (port *EtherTalkPort) HandleNBP(ctx context.Context, ddpkt *ddp.ExtPacket) default: 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 { + // 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! tuple := &nbpkt.Tuples[0] - if tuple.Zone == "" || tuple.Zone == "*" { - tuple.Zone = port.DefaultZoneName - } + // This logic would be required on a non-extended network: + // if tuple.Zone == "" || tuple.Zone == "*" { + // tuple.Zone = port.DefaultZoneName + // } 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 // This uses the *input* port information. - outDDP2, err := port.helloWorldThisIsMe(ddpkt, nbpkt.NBPID, tuple) + outDDP2, err := port.helloWorldThisIsMe(nbpkt.NBPID, tuple) if err != nil { return err } @@ -117,8 +167,8 @@ func (port *EtherTalkPort) handleNBPBrRq(ctx context.Context, ddpkt *ddp.ExtPack continue } 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 - // routed + // Can reply to this BrRq on the same port we got it, because it + // wasn't routed if err := port.Send(ctx, outDDP2); err != nil { 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. -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 != "=" { return nil, nil } @@ -187,13 +237,23 @@ func (port *EtherTalkPort) helloWorldThisIsMe(ddpkt *ddp.ExtPacket, nbpID uint8, if err != nil { 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{ ExtHeader: ddp.ExtHeader{ Size: uint16(len(respRaw)) + atalk.DDPExtHeaderSize, Cksum: 0, - DstNet: ddpkt.SrcNet, - DstNode: ddpkt.SrcNode, - DstSocket: ddpkt.SrcSocket, + DstNet: tuple.Network, + DstNode: tuple.Node, + DstSocket: tuple.Socket, SrcNet: port.MyAddr.Network, SrcNode: port.MyAddr.Node, SrcSocket: 2, diff --git a/router/nbp_aurp.go b/router/nbp_aurp.go index eb84b48..31b46af 100644 --- a/router/nbp_aurp.go +++ b/router/nbp_aurp.go @@ -19,13 +19,12 @@ package router import ( "context" "fmt" - "log" "gitea.drjosh.dev/josh/jrouter/atalk/nbp" "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 { 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?? return fmt.Errorf("can't handle %v", nbpkt.Function) } - - 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 + return rtr.handleNBPFwdReq(ctx, ddpkt, nbpkt) } diff --git a/router/port.go b/router/port.go index 26e3e3c..39ac386 100644 --- a/router/port.go +++ b/router/port.go @@ -84,10 +84,10 @@ func (port *EtherTalkPort) Serve(ctx context.Context) { log.Printf("Couldn't unmarshal DDP packet: %v", err) continue } - 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.DstNet, ddpkt.DstNode, ddpkt.DstSocket, - ddpkt.Proto, len(ddpkt.Data)) + // 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.DstNet, ddpkt.DstNode, ddpkt.DstSocket, + // ddpkt.Proto, len(ddpkt.Data)) // 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 @@ -103,6 +103,7 @@ func (port *EtherTalkPort) Serve(ctx context.Context) { if !ok { continue } + port.MyAddr = myAddr.Proto // Our network? // "The network number 0 is reserved to mean unknown; by default diff --git a/router/router.go b/router/router.go index 784147d..e7afdc5 100644 --- a/router/router.go +++ b/router/router.go @@ -27,6 +27,7 @@ type Router struct { Config *Config RouteTable *RouteTable ZoneTable *ZoneTable + Ports []*EtherTalkPort } // Forward routes a packet towards the right destination.