handle more ZIP
This commit is contained in:
parent
8771139a4f
commit
2afb879573
7 changed files with 554 additions and 264 deletions
66
aep.go
Normal file
66
aep.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.drjosh.dev/josh/jrouter/atalk/aep"
|
||||||
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethernet"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethertalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleAEP(pcapHandle *pcap.Handle, src, dst ethernet.Addr, ddpkt *ddp.ExtPacket) error {
|
||||||
|
if ddpkt.Proto != ddp.ProtoAEP {
|
||||||
|
return fmt.Errorf("invalid DDP type %d on socket 4", ddpkt.Proto)
|
||||||
|
}
|
||||||
|
ep, err := aep.Unmarshal(ddpkt.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch ep.Function {
|
||||||
|
case aep.EchoReply:
|
||||||
|
// we didn't send a request? I don't think?
|
||||||
|
// we shouldn't be sending them from this socket
|
||||||
|
return fmt.Errorf("echo reply received at socket 4 why?")
|
||||||
|
|
||||||
|
case aep.EchoRequest:
|
||||||
|
// Uno Reverso the packet
|
||||||
|
// "The client can send the Echo Request datagram through any socket
|
||||||
|
// the client has open, and the Echo Reply will come back to this socket."
|
||||||
|
ddpkt.DstNet, ddpkt.SrcNet = ddpkt.SrcNet, ddpkt.DstNet
|
||||||
|
ddpkt.DstNode, ddpkt.SrcNode = ddpkt.SrcNode, ddpkt.DstNode
|
||||||
|
ddpkt.DstSocket, ddpkt.SrcSocket = ddpkt.SrcSocket, ddpkt.DstSocket
|
||||||
|
ddpkt.Data[0] = byte(aep.EchoReply)
|
||||||
|
|
||||||
|
ethFrame, err := ethertalk.AppleTalk(src, *ddpkt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ethFrame.Dst = dst
|
||||||
|
ethFrameRaw, err := ethertalk.Marshal(*ethFrame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pcapHandle.WritePacketData(ethFrameRaw)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid AEP function %d", ep.Function)
|
||||||
|
}
|
||||||
|
}
|
269
main.go
269
main.go
|
@ -20,7 +20,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
|
@ -32,12 +31,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.drjosh.dev/josh/jrouter/atalk"
|
"gitea.drjosh.dev/josh/jrouter/atalk"
|
||||||
"gitea.drjosh.dev/josh/jrouter/atalk/aep"
|
|
||||||
"gitea.drjosh.dev/josh/jrouter/atalk/nbp"
|
|
||||||
"gitea.drjosh.dev/josh/jrouter/atalk/zip"
|
|
||||||
"gitea.drjosh.dev/josh/jrouter/aurp"
|
"gitea.drjosh.dev/josh/jrouter/aurp"
|
||||||
"github.com/google/gopacket/pcap"
|
"github.com/google/gopacket/pcap"
|
||||||
"github.com/sfiera/multitalk/pkg/aarp"
|
|
||||||
"github.com/sfiera/multitalk/pkg/ddp"
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
"github.com/sfiera/multitalk/pkg/ethernet"
|
"github.com/sfiera/multitalk/pkg/ethernet"
|
||||||
"github.com/sfiera/multitalk/pkg/ethertalk"
|
"github.com/sfiera/multitalk/pkg/ethertalk"
|
||||||
|
@ -130,8 +125,10 @@ func main() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------- Routing table ----------------------------
|
// -------------------------------- Tables --------------------------------
|
||||||
routing := NewRoutingTable()
|
routing := NewRoutingTable()
|
||||||
|
zones := NewZoneTable()
|
||||||
|
zones.Upsert(cfg.EtherTalk.NetStart, cfg.EtherTalk.ZoneName, true)
|
||||||
|
|
||||||
// ------------------------- Configured peer setup ------------------------
|
// ------------------------- Configured peer setup ------------------------
|
||||||
for _, peerStr := range cfg.Peers {
|
for _, peerStr := range cfg.Peers {
|
||||||
|
@ -156,6 +153,7 @@ func main() {
|
||||||
raddr: raddr,
|
raddr: raddr,
|
||||||
recv: make(chan aurp.Packet, 1024),
|
recv: make(chan aurp.Packet, 1024),
|
||||||
routingTable: routing,
|
routingTable: routing,
|
||||||
|
zoneTable: zones,
|
||||||
}
|
}
|
||||||
aurp.Inc(&nextConnID)
|
aurp.Inc(&nextConnID)
|
||||||
peers[udpAddrFromNet(raddr)] = peer
|
peers[udpAddrFromNet(raddr)] = peer
|
||||||
|
@ -285,7 +283,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 6: // The ZIS (zone information socket / ZIP socket)
|
case 6: // The ZIS (zone information socket / ZIP socket)
|
||||||
if err := handleZIP(pcapHandle, ethFrame.Src, myHWAddr, myAddr, cfg, ddpkt); err != nil {
|
if err := handleZIP(pcapHandle, ethFrame.Src, myHWAddr, myAddr, cfg, zones, ddpkt); err != nil {
|
||||||
log.Printf("ZIP: couldn't handle: %v", err)
|
log.Printf("ZIP: couldn't handle: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,6 +412,7 @@ func main() {
|
||||||
raddr: raddr,
|
raddr: raddr,
|
||||||
recv: make(chan aurp.Packet, 1024),
|
recv: make(chan aurp.Packet, 1024),
|
||||||
routingTable: routing,
|
routingTable: routing,
|
||||||
|
zoneTable: zones,
|
||||||
}
|
}
|
||||||
aurp.Inc(&nextConnID)
|
aurp.Inc(&nextConnID)
|
||||||
peers[ra] = pr
|
peers[ra] = pr
|
||||||
|
@ -431,262 +430,6 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNBP(pcapHandle *pcap.Handle, myHWAddr, srcHWAddr ethernet.Addr, myAddr aarp.AddrPair, cfg *config, ddpkt *ddp.ExtPacket) error {
|
|
||||||
if ddpkt.Proto != ddp.ProtoNBP {
|
|
||||||
return fmt.Errorf("invalid DDP type %d on socket 2", ddpkt.Proto)
|
|
||||||
}
|
|
||||||
|
|
||||||
nbpkt, err := nbp.Unmarshal(ddpkt.Data)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid packet: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("NBP: Got %v id %d with tuples %v", nbpkt.Function, nbpkt.NBPID, nbpkt.Tuples)
|
|
||||||
|
|
||||||
switch nbpkt.Function {
|
|
||||||
case nbp.FunctionLkUp:
|
|
||||||
// when in AppleTalk, do as Apple Internet Router does...
|
|
||||||
tuple := nbpkt.Tuples[0]
|
|
||||||
if tuple.Object != "jrouter" && tuple.Object != "=" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if tuple.Type != "AppleRouter" && tuple.Type != "=" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if tuple.Zone != cfg.EtherTalk.ZoneName && tuple.Zone != "*" && tuple.Zone != "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
respPkt := &nbp.Packet{
|
|
||||||
Function: nbp.FunctionLkUpReply,
|
|
||||||
NBPID: nbpkt.NBPID,
|
|
||||||
Tuples: []nbp.Tuple{
|
|
||||||
{
|
|
||||||
Network: myAddr.Proto.Network,
|
|
||||||
Node: myAddr.Proto.Node,
|
|
||||||
Socket: 253,
|
|
||||||
Enumerator: 0,
|
|
||||||
Object: "jrouter",
|
|
||||||
Type: "AppleRouter",
|
|
||||||
Zone: cfg.EtherTalk.ZoneName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
respRaw, err := respPkt.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't marshal LkUp-Reply: %v", err)
|
|
||||||
}
|
|
||||||
ddpkt.DstNet = ddpkt.SrcNet
|
|
||||||
ddpkt.DstNode = ddpkt.SrcNode
|
|
||||||
ddpkt.DstSocket = ddpkt.SrcSocket
|
|
||||||
ddpkt.SrcNet = myAddr.Proto.Network
|
|
||||||
ddpkt.SrcNode = myAddr.Proto.Node
|
|
||||||
ddpkt.SrcSocket = 2
|
|
||||||
ddpkt.Data = respRaw
|
|
||||||
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
outFrame.Dst = srcHWAddr
|
|
||||||
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pcapHandle.WritePacketData(outFrameRaw)
|
|
||||||
|
|
||||||
case nbp.FunctionBrRq:
|
|
||||||
// There must be 1!
|
|
||||||
tuple := nbpkt.Tuples[0]
|
|
||||||
|
|
||||||
if tuple.Zone != cfg.EtherTalk.ZoneName {
|
|
||||||
// TODO: Translate it into a FwdReq and route it to the
|
|
||||||
// routers with the appropriate zone(s).
|
|
||||||
return errors.New("TODO: BrRq-FwdReq translation")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's for the local zone, translate it to a LkUp and broadcast it back
|
|
||||||
// out the EtherTalk port.
|
|
||||||
// "Note: On an internet, nodes on extended networks performing lookups in
|
|
||||||
// their own zone must replace a zone name of asterisk (*) with their actual
|
|
||||||
// zone name before sending the packet to A-ROUTER. All nodes performing
|
|
||||||
// lookups in their own zone will receive LkUp packets from themselves
|
|
||||||
// (actually sent by a router). The node's NBP process should expect to
|
|
||||||
// receive these packets and must reply to them."
|
|
||||||
// TODO: use zone-specific multicast
|
|
||||||
nbpkt.Function = nbp.FunctionLkUp
|
|
||||||
nbpRaw, err := nbpkt.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't marshal LkUp: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ddpkt.DstNode = 0xFF // Broadcast node address within the dest network
|
|
||||||
ddpkt.Data = nbpRaw
|
|
||||||
|
|
||||||
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pcapHandle.WritePacketData(outFrameRaw)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("TODO: handle function %v", nbpkt.Function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleZIP(pcapHandle *pcap.Handle, srcHWAddr, myHWAddr ethernet.Addr, myAddr aarp.AddrPair, cfg *config, ddpkt *ddp.ExtPacket) error {
|
|
||||||
switch ddpkt.Proto {
|
|
||||||
case 3: // ATP
|
|
||||||
return errors.New("TODO implement ATP-based ZIP requests")
|
|
||||||
|
|
||||||
case 6: // ZIP
|
|
||||||
zipkt, err := zip.UnmarshalPacket(ddpkt.Data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch zipkt := zipkt.(type) {
|
|
||||||
case *zip.GetNetInfoPacket:
|
|
||||||
// Only running a network with one zone for now.
|
|
||||||
resp := &zip.GetNetInfoReplyPacket{
|
|
||||||
ZoneInvalid: zipkt.ZoneName != cfg.EtherTalk.ZoneName,
|
|
||||||
UseBroadcast: true, // TODO: add multicast addr computation
|
|
||||||
OnlyOneZone: true,
|
|
||||||
NetStart: cfg.EtherTalk.NetStart,
|
|
||||||
NetEnd: cfg.EtherTalk.NetEnd,
|
|
||||||
ZoneName: zipkt.ZoneName, // has to match request
|
|
||||||
MulticastAddr: ethertalk.AppleTalkBroadcast,
|
|
||||||
DefaultZoneName: cfg.EtherTalk.ZoneName,
|
|
||||||
}
|
|
||||||
respRaw, err := resp.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't marshal GetNetInfoReplyPacket: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: fix
|
|
||||||
// "In cases where a node's provisional address is
|
|
||||||
// invalid, routers will not be able to respond to
|
|
||||||
// the node in a directed manner. An address is
|
|
||||||
// invalid if the network number is neither in the
|
|
||||||
// startup range nor in the network number range
|
|
||||||
// assigned to the node's network. In these cases,
|
|
||||||
// if the request was sent via a broadcast, the
|
|
||||||
// routers should respond with a broadcast."
|
|
||||||
ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket = 0x0000, 0xFF, ddpkt.SrcSocket
|
|
||||||
ddpkt.SrcNet = myAddr.Proto.Network
|
|
||||||
ddpkt.SrcNode = myAddr.Proto.Node
|
|
||||||
ddpkt.SrcSocket = 6
|
|
||||||
ddpkt.Data = respRaw
|
|
||||||
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't create EtherTalk frame: %w", err)
|
|
||||||
}
|
|
||||||
outFrame.Dst = srcHWAddr
|
|
||||||
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("couldn't marshal EtherTalk frame: %w", err)
|
|
||||||
}
|
|
||||||
if err := pcapHandle.WritePacketData(outFrameRaw); err != nil {
|
|
||||||
return fmt.Errorf("couldn't write packet data: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("TODO: handle type %T", zipkt)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid DDP type %d on socket 6", ddpkt.Proto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleNBPInAURP(pcapHandle *pcap.Handle, myHWAddr ethernet.Addr, ddpkt *ddp.ExtPacket) error {
|
|
||||||
if ddpkt.Proto != ddp.ProtoNBP {
|
|
||||||
return fmt.Errorf("invalid DDP type %d on socket 2", ddpkt.Proto)
|
|
||||||
}
|
|
||||||
nbpkt, err := nbp.Unmarshal(ddpkt.Data)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid NBP packet: %v", err)
|
|
||||||
}
|
|
||||||
if nbpkt.Function != nbp.FunctionFwdReq {
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("NBP/DDP/AURP: Converting FwdReq to LkUp (%v)", nbpkt.Tuples[0])
|
|
||||||
|
|
||||||
// Convert it to a LkUp and broadcast on EtherTalk
|
|
||||||
// TODO: use zone-specific multicast
|
|
||||||
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
|
|
||||||
|
|
||||||
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// TODO: outFrame.Dst = zone-specific multicast address
|
|
||||||
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pcapHandle.WritePacketData(outFrameRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleAEP(pcapHandle *pcap.Handle, src, dst ethernet.Addr, ddpkt *ddp.ExtPacket) error {
|
|
||||||
if ddpkt.Proto != ddp.ProtoAEP {
|
|
||||||
return fmt.Errorf("invalid DDP type %d on socket 4", ddpkt.Proto)
|
|
||||||
}
|
|
||||||
ep, err := aep.Unmarshal(ddpkt.Data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch ep.Function {
|
|
||||||
case aep.EchoReply:
|
|
||||||
// we didn't send a request? I don't think?
|
|
||||||
// we shouldn't be sending them from this socket
|
|
||||||
return fmt.Errorf("echo reply received at socket 4 why?")
|
|
||||||
|
|
||||||
case aep.EchoRequest:
|
|
||||||
// Uno Reverso the packet
|
|
||||||
// "The client can send the Echo Request datagram through any socket
|
|
||||||
// the client has open, and the Echo Reply will come back to this socket."
|
|
||||||
ddpkt.DstNet, ddpkt.SrcNet = ddpkt.SrcNet, ddpkt.DstNet
|
|
||||||
ddpkt.DstNode, ddpkt.SrcNode = ddpkt.SrcNode, ddpkt.DstNode
|
|
||||||
ddpkt.DstSocket, ddpkt.SrcSocket = ddpkt.SrcSocket, ddpkt.DstSocket
|
|
||||||
ddpkt.Data[0] = byte(aep.EchoReply)
|
|
||||||
|
|
||||||
ethFrame, err := ethertalk.AppleTalk(src, *ddpkt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ethFrame.Dst = dst
|
|
||||||
ethFrameRaw, err := ethertalk.Marshal(*ethFrame)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pcapHandle.WritePacketData(ethFrameRaw)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid AEP function %d", ep.Function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hashable net.UDPAddr
|
// Hashable net.UDPAddr
|
||||||
type udpAddr struct {
|
type udpAddr struct {
|
||||||
ipv4 [4]byte
|
ipv4 [4]byte
|
||||||
|
|
135
nbp.go
Normal file
135
nbp.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gitea.drjosh.dev/josh/jrouter/atalk/nbp"
|
||||||
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/sfiera/multitalk/pkg/aarp"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethernet"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethertalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleNBP(pcapHandle *pcap.Handle, myHWAddr, srcHWAddr ethernet.Addr, myAddr aarp.AddrPair, cfg *config, ddpkt *ddp.ExtPacket) error {
|
||||||
|
if ddpkt.Proto != ddp.ProtoNBP {
|
||||||
|
return fmt.Errorf("invalid DDP type %d on socket 2", ddpkt.Proto)
|
||||||
|
}
|
||||||
|
|
||||||
|
nbpkt, err := nbp.Unmarshal(ddpkt.Data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid packet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("NBP: Got %v id %d with tuples %v", nbpkt.Function, nbpkt.NBPID, nbpkt.Tuples)
|
||||||
|
|
||||||
|
switch nbpkt.Function {
|
||||||
|
case nbp.FunctionLkUp:
|
||||||
|
// when in AppleTalk, do as Apple Internet Router does...
|
||||||
|
tuple := nbpkt.Tuples[0]
|
||||||
|
if tuple.Object != "jrouter" && tuple.Object != "=" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if tuple.Type != "AppleRouter" && tuple.Type != "=" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if tuple.Zone != cfg.EtherTalk.ZoneName && tuple.Zone != "*" && tuple.Zone != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
respPkt := &nbp.Packet{
|
||||||
|
Function: nbp.FunctionLkUpReply,
|
||||||
|
NBPID: nbpkt.NBPID,
|
||||||
|
Tuples: []nbp.Tuple{
|
||||||
|
{
|
||||||
|
Network: myAddr.Proto.Network,
|
||||||
|
Node: myAddr.Proto.Node,
|
||||||
|
Socket: 253,
|
||||||
|
Enumerator: 0,
|
||||||
|
Object: "jrouter",
|
||||||
|
Type: "AppleRouter",
|
||||||
|
Zone: cfg.EtherTalk.ZoneName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
respRaw, err := respPkt.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't marshal LkUp-Reply: %v", err)
|
||||||
|
}
|
||||||
|
ddpkt.DstNet = ddpkt.SrcNet
|
||||||
|
ddpkt.DstNode = ddpkt.SrcNode
|
||||||
|
ddpkt.DstSocket = ddpkt.SrcSocket
|
||||||
|
ddpkt.SrcNet = myAddr.Proto.Network
|
||||||
|
ddpkt.SrcNode = myAddr.Proto.Node
|
||||||
|
ddpkt.SrcSocket = 2
|
||||||
|
ddpkt.Data = respRaw
|
||||||
|
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outFrame.Dst = srcHWAddr
|
||||||
|
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pcapHandle.WritePacketData(outFrameRaw)
|
||||||
|
|
||||||
|
case nbp.FunctionBrRq:
|
||||||
|
// There must be 1!
|
||||||
|
tuple := nbpkt.Tuples[0]
|
||||||
|
|
||||||
|
if tuple.Zone != cfg.EtherTalk.ZoneName {
|
||||||
|
// TODO: Translate it into a FwdReq and route it to the
|
||||||
|
// routers with the appropriate zone(s).
|
||||||
|
return errors.New("TODO: BrRq-FwdReq translation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's for the local zone, translate it to a LkUp and broadcast it back
|
||||||
|
// out the EtherTalk port.
|
||||||
|
// "Note: On an internet, nodes on extended networks performing lookups in
|
||||||
|
// their own zone must replace a zone name of asterisk (*) with their actual
|
||||||
|
// zone name before sending the packet to A-ROUTER. All nodes performing
|
||||||
|
// lookups in their own zone will receive LkUp packets from themselves
|
||||||
|
// (actually sent by a router). The node's NBP process should expect to
|
||||||
|
// receive these packets and must reply to them."
|
||||||
|
// TODO: use zone-specific multicast
|
||||||
|
nbpkt.Function = nbp.FunctionLkUp
|
||||||
|
nbpRaw, err := nbpkt.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't marshal LkUp: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ddpkt.DstNode = 0xFF // Broadcast node address within the dest network
|
||||||
|
ddpkt.Data = nbpRaw
|
||||||
|
|
||||||
|
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pcapHandle.WritePacketData(outFrameRaw)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("TODO: handle function %v", nbpkt.Function)
|
||||||
|
}
|
||||||
|
}
|
75
nbp_aurp.go
Normal file
75
nbp_aurp.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gitea.drjosh.dev/josh/jrouter/atalk/nbp"
|
||||||
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethernet"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethertalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleNBPInAURP(pcapHandle *pcap.Handle, myHWAddr ethernet.Addr, ddpkt *ddp.ExtPacket) error {
|
||||||
|
if ddpkt.Proto != ddp.ProtoNBP {
|
||||||
|
return fmt.Errorf("invalid DDP type %d on socket 2", ddpkt.Proto)
|
||||||
|
}
|
||||||
|
nbpkt, err := nbp.Unmarshal(ddpkt.Data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid NBP packet: %v", err)
|
||||||
|
}
|
||||||
|
if nbpkt.Function != nbp.FunctionFwdReq {
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("NBP/DDP/AURP: Converting FwdReq to LkUp (%v)", nbpkt.Tuples[0])
|
||||||
|
|
||||||
|
// Convert it to a LkUp and broadcast on EtherTalk
|
||||||
|
// TODO: use zone-specific multicast
|
||||||
|
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
|
||||||
|
|
||||||
|
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO: outFrame.Dst = zone-specific multicast address
|
||||||
|
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pcapHandle.WritePacketData(outFrameRaw)
|
||||||
|
}
|
5
peer.go
5
peer.go
|
@ -83,6 +83,7 @@ type peer struct {
|
||||||
recv chan aurp.Packet
|
recv chan aurp.Packet
|
||||||
|
|
||||||
routingTable *RoutingTable
|
routingTable *RoutingTable
|
||||||
|
zoneTable *ZoneTable
|
||||||
}
|
}
|
||||||
|
|
||||||
// send encodes and sends pkt to the remote host.
|
// send encodes and sends pkt to the remote host.
|
||||||
|
@ -367,8 +368,10 @@ func (p *peer) handle(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case *aurp.ZIRspPacket:
|
case *aurp.ZIRspPacket:
|
||||||
// TODO: Integrate info into zone table
|
|
||||||
log.Printf("Learned about these zones: %v", pkt.Zones)
|
log.Printf("Learned about these zones: %v", pkt.Zones)
|
||||||
|
for _, zt := range pkt.Zones {
|
||||||
|
p.zoneTable.Upsert(ddp.Network(zt.Network), zt.Name, false)
|
||||||
|
}
|
||||||
|
|
||||||
case *aurp.GDZLReqPacket:
|
case *aurp.GDZLReqPacket:
|
||||||
if _, err := p.send(p.tr.NewGDZLRspPacket(-1, nil)); err != nil {
|
if _, err := p.send(p.tr.NewGDZLRspPacket(-1, nil)); err != nil {
|
||||||
|
|
156
zip.go
Normal file
156
zip.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.drjosh.dev/josh/jrouter/atalk/atp"
|
||||||
|
"gitea.drjosh.dev/josh/jrouter/atalk/zip"
|
||||||
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/sfiera/multitalk/pkg/aarp"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethernet"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethertalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleZIP(pcapHandle *pcap.Handle, srcHWAddr, myHWAddr ethernet.Addr, myAddr aarp.AddrPair, cfg *config, zones *ZoneTable, ddpkt *ddp.ExtPacket) error {
|
||||||
|
switch ddpkt.Proto {
|
||||||
|
case 3: // ATP
|
||||||
|
atpkt, err := atp.UnmarshalPacket(ddpkt.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch atpkt := atpkt.(type) {
|
||||||
|
case *atp.TReq:
|
||||||
|
gzl, err := zip.UnmarshalTReq(atpkt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO: handle this in a more transactiony way
|
||||||
|
resp := &zip.GetZonesReplyPacket{
|
||||||
|
TID: gzl.TID,
|
||||||
|
LastFlag: true, // TODO: support multiple response packets
|
||||||
|
}
|
||||||
|
switch gzl.Function {
|
||||||
|
case zip.FunctionGetZoneList:
|
||||||
|
resp.Zones = zones.AllNames()
|
||||||
|
|
||||||
|
case zip.FunctionGetLocalZones:
|
||||||
|
resp.Zones = zones.LocalNames()
|
||||||
|
|
||||||
|
case zip.FunctionGetMyZone:
|
||||||
|
return fmt.Errorf("TODO: support GetMyZone?")
|
||||||
|
}
|
||||||
|
|
||||||
|
respATP, err := resp.MarshalTResp()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ddpBody, err := respATP.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
respDDP := ddp.ExtPacket{
|
||||||
|
ExtHeader: ddp.ExtHeader{
|
||||||
|
DstNet: ddpkt.SrcNet,
|
||||||
|
DstNode: ddpkt.SrcNode,
|
||||||
|
DstSocket: ddpkt.SrcSocket,
|
||||||
|
SrcNet: myAddr.Proto.Network,
|
||||||
|
SrcNode: myAddr.Proto.Node,
|
||||||
|
SrcSocket: 6,
|
||||||
|
Proto: ddp.ProtoATP,
|
||||||
|
},
|
||||||
|
Data: ddpBody,
|
||||||
|
}
|
||||||
|
outFrame, err := ethertalk.AppleTalk(myHWAddr, respDDP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outFrame.Dst = srcHWAddr
|
||||||
|
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pcapHandle.WritePacketData(outFrameRaw)
|
||||||
|
|
||||||
|
case *atp.TResp:
|
||||||
|
return fmt.Errorf("TODO: support handling ZIP ATP replies?")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported ATP packet type %T for ZIP", atpkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 6: // ZIP
|
||||||
|
zipkt, err := zip.UnmarshalPacket(ddpkt.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch zipkt := zipkt.(type) {
|
||||||
|
case *zip.GetNetInfoPacket:
|
||||||
|
// Only running a network with one zone for now.
|
||||||
|
resp := &zip.GetNetInfoReplyPacket{
|
||||||
|
ZoneInvalid: zipkt.ZoneName != cfg.EtherTalk.ZoneName,
|
||||||
|
UseBroadcast: true, // TODO: add multicast addr computation
|
||||||
|
OnlyOneZone: true,
|
||||||
|
NetStart: cfg.EtherTalk.NetStart,
|
||||||
|
NetEnd: cfg.EtherTalk.NetEnd,
|
||||||
|
ZoneName: zipkt.ZoneName, // has to match request
|
||||||
|
MulticastAddr: ethertalk.AppleTalkBroadcast,
|
||||||
|
DefaultZoneName: cfg.EtherTalk.ZoneName,
|
||||||
|
}
|
||||||
|
respRaw, err := resp.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't marshal GetNetInfoReplyPacket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fix
|
||||||
|
// "In cases where a node's provisional address is
|
||||||
|
// invalid, routers will not be able to respond to
|
||||||
|
// the node in a directed manner. An address is
|
||||||
|
// invalid if the network number is neither in the
|
||||||
|
// startup range nor in the network number range
|
||||||
|
// assigned to the node's network. In these cases,
|
||||||
|
// if the request was sent via a broadcast, the
|
||||||
|
// routers should respond with a broadcast."
|
||||||
|
ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket = 0x0000, 0xFF, ddpkt.SrcSocket
|
||||||
|
ddpkt.SrcNet = myAddr.Proto.Network
|
||||||
|
ddpkt.SrcNode = myAddr.Proto.Node
|
||||||
|
ddpkt.SrcSocket = 6
|
||||||
|
ddpkt.Data = respRaw
|
||||||
|
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't create EtherTalk frame: %w", err)
|
||||||
|
}
|
||||||
|
outFrame.Dst = srcHWAddr
|
||||||
|
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't marshal EtherTalk frame: %w", err)
|
||||||
|
}
|
||||||
|
if err := pcapHandle.WritePacketData(outFrameRaw); err != nil {
|
||||||
|
return fmt.Errorf("couldn't write packet data: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("TODO: handle type %T", zipkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid DDP type %d on socket 6", ddpkt.Proto)
|
||||||
|
}
|
||||||
|
}
|
112
zones.go
Normal file
112
zones.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxZoneAge = 10 * time.Minute // TODO: confirm
|
||||||
|
|
||||||
|
type Zone struct {
|
||||||
|
Network ddp.Network
|
||||||
|
Name string
|
||||||
|
Local bool
|
||||||
|
LastSeen time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type zoneKey struct {
|
||||||
|
network ddp.Network
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZoneTable struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
zones map[zoneKey]*Zone
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewZoneTable() *ZoneTable {
|
||||||
|
return &ZoneTable{
|
||||||
|
zones: make(map[zoneKey]*Zone),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zt *ZoneTable) Upsert(network ddp.Network, name string, local bool) {
|
||||||
|
zt.mu.Lock()
|
||||||
|
defer zt.mu.Unlock()
|
||||||
|
key := zoneKey{network, name}
|
||||||
|
z := zt.zones[key]
|
||||||
|
if z != nil {
|
||||||
|
z.Local = local
|
||||||
|
z.LastSeen = time.Now()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zt.zones[key] = &Zone{
|
||||||
|
Network: network,
|
||||||
|
Name: name,
|
||||||
|
Local: local,
|
||||||
|
LastSeen: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zt *ZoneTable) LocalNames() []string {
|
||||||
|
zt.mu.Lock()
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
zs := make([]string, 0, len(zt.zones))
|
||||||
|
for _, z := range zt.zones {
|
||||||
|
if time.Since(z.LastSeen) > maxZoneAge {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !z.Local {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, s := seen[z.Name]; s {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[z.Name] = struct{}{}
|
||||||
|
zs = append(zs, z.Name)
|
||||||
|
|
||||||
|
}
|
||||||
|
zt.mu.Unlock()
|
||||||
|
|
||||||
|
sort.Strings(zs)
|
||||||
|
return zs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (zt *ZoneTable) AllNames() []string {
|
||||||
|
zt.mu.Lock()
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
zs := make([]string, 0, len(zt.zones))
|
||||||
|
for _, z := range zt.zones {
|
||||||
|
if time.Since(z.LastSeen) > maxZoneAge {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, s := seen[z.Name]; s {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[z.Name] = struct{}{}
|
||||||
|
zs = append(zs, z.Name)
|
||||||
|
}
|
||||||
|
zt.mu.Unlock()
|
||||||
|
|
||||||
|
sort.Strings(zs)
|
||||||
|
return zs
|
||||||
|
}
|
Loading…
Reference in a new issue