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"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
|
@ -32,12 +31,8 @@ import (
|
|||
"time"
|
||||
|
||||
"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"
|
||||
"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"
|
||||
|
@ -130,8 +125,10 @@ func main() {
|
|||
}()
|
||||
}
|
||||
|
||||
// ----------------------------- Routing table ----------------------------
|
||||
// -------------------------------- Tables --------------------------------
|
||||
routing := NewRoutingTable()
|
||||
zones := NewZoneTable()
|
||||
zones.Upsert(cfg.EtherTalk.NetStart, cfg.EtherTalk.ZoneName, true)
|
||||
|
||||
// ------------------------- Configured peer setup ------------------------
|
||||
for _, peerStr := range cfg.Peers {
|
||||
|
@ -156,6 +153,7 @@ func main() {
|
|||
raddr: raddr,
|
||||
recv: make(chan aurp.Packet, 1024),
|
||||
routingTable: routing,
|
||||
zoneTable: zones,
|
||||
}
|
||||
aurp.Inc(&nextConnID)
|
||||
peers[udpAddrFromNet(raddr)] = peer
|
||||
|
@ -285,7 +283,7 @@ func main() {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -414,6 +412,7 @@ func main() {
|
|||
raddr: raddr,
|
||||
recv: make(chan aurp.Packet, 1024),
|
||||
routingTable: routing,
|
||||
zoneTable: zones,
|
||||
}
|
||||
aurp.Inc(&nextConnID)
|
||||
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
|
||||
type udpAddr struct {
|
||||
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
|
||||
|
||||
routingTable *RoutingTable
|
||||
zoneTable *ZoneTable
|
||||
}
|
||||
|
||||
// send encodes and sends pkt to the remote host.
|
||||
|
@ -367,8 +368,10 @@ func (p *peer) handle(ctx context.Context) error {
|
|||
}
|
||||
|
||||
case *aurp.ZIRspPacket:
|
||||
// TODO: Integrate info into zone table
|
||||
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:
|
||||
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