diff --git a/atalk/strings.go b/atalk/strings.go new file mode 100644 index 0000000..ae800cf --- /dev/null +++ b/atalk/strings.go @@ -0,0 +1,102 @@ +/* + 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 atalk + +import ( + "math/bits" + + "github.com/sfiera/multitalk/pkg/ethernet" +) + +// Inside AppleTalk, appendix D +var toUpperMap = []byte{ + // The alphabet + 0x61: 0x41, + 0x62: 0x42, + 0x63: 0x43, + 0x64: 0x44, + 0x65: 0x45, + 0x66: 0x46, + 0x67: 0x47, + 0x68: 0x48, + 0x69: 0x49, + 0x6A: 0x4A, + 0x6B: 0x4B, + 0x6C: 0x4C, + 0x6D: 0x4D, + 0x6E: 0x4E, + 0x6F: 0x4F, + 0x70: 0x50, + 0x71: 0x51, + 0x72: 0x52, + 0x73: 0x53, + 0x74: 0x54, + 0x75: 0x55, + 0x76: 0x56, + 0x77: 0x57, + 0x78: 0x58, + 0x79: 0x59, + 0x7A: 0x5A, + + // Letters with diacritics, etc + 0x88: 0xCB, + 0x8A: 0x80, + 0x8B: 0xCC, + 0x8C: 0x81, + 0x8D: 0x82, + 0x8E: 0x83, + 0x96: 0x84, + 0x9A: 0x85, + 0x9B: 0xCD, + 0x9F: 0x86, + 0xBE: 0xAE, + 0xBF: 0xAF, + 0xCF: 0xCE, +} + +func Checksum(s string) uint16 { + // Inside AppleTalk, pp 4-17 and pp 8-18 + var cksum uint16 + for _, b := range []byte(s) { + cksum += uint16(b) + cksum = bits.RotateLeft16(cksum, -1) + } + if cksum == 0 { + cksum = 0xFFFF + } + return cksum +} + +func ToUpper(s string) string { + // Inside Appletalk, appendix D + sb := []byte(s) + out := make([]byte, len(sb)) + for i, b := range sb { + if u := toUpperMap[b]; u != 0 { + out[i] = u + } else { + out[i] = b + } + } + return string(out) +} + +func MulticastAddr(zone string) ethernet.Addr { + // Inside AppleTalk, pp 3-10 and pp 8-18 + h := Checksum(ToUpper(zone)) + return ethernet.Addr{0x09, 0x00, 0x07, 0x00, 0x00, byte(h % 0xFD)} +} diff --git a/atalk/zip/getnetinfo.go b/atalk/zip/getnetinfo.go index b320c60..7814eeb 100644 --- a/atalk/zip/getnetinfo.go +++ b/atalk/zip/getnetinfo.go @@ -57,9 +57,9 @@ type GetNetInfoReplyPacket struct { // DDP type = 6 // --- // ZIP command = 6 - ZoneInvalid bool // 0x80 - UseBroadcast bool // 0x40 - OnlyOneZone bool // 0x20 + ZoneInvalid bool // 0x80 - "set if the zone name in the request is invalid for the network from which the request was sent" + UseBroadcast bool // 0x40 - "set for data links that do not support multicast" + OnlyOneZone bool // 0x20 - "set if the network's zone list contains only one zone name" // Remainder of flags reserved NetStart ddp.Network NetEnd ddp.Network diff --git a/main.go b/main.go index 531e188..205d5de 100644 --- a/main.go +++ b/main.go @@ -205,6 +205,9 @@ func main() { continue } + // TODO: filter ethFrame.Dst to myHWAddr, the broadcast address, + // or the relevant zone multicast address + switch ethFrame.SNAPProto { case ethertalk.AARPProto: // log.Print("Got an AARP frame") diff --git a/nbp.go b/nbp.go index 3491e61..2e7680d 100644 --- a/nbp.go +++ b/nbp.go @@ -100,6 +100,10 @@ func handleNBP(pcapHandle *pcap.Handle, myHWAddr, srcHWAddr ethernet.Addr, myAdd case nbp.FunctionBrRq: // There must be 1! tuple := &nbpkt.Tuples[0] + ethDst := ethertalk.AppleTalkBroadcast + if tuple.Zone != "*" && tuple.Zone != "" { + ethDst = atalk.MulticastAddr(tuple.Zone) + } zones := zoneTable.LookupName(tuple.Zone) for _, z := range zones { @@ -112,7 +116,6 @@ func handleNBP(pcapHandle *pcap.Handle, myHWAddr, srcHWAddr ethernet.Addr, myAdd // 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 { @@ -128,6 +131,7 @@ func handleNBP(pcapHandle *pcap.Handle, myHWAddr, srcHWAddr ethernet.Addr, myAdd if err != nil { return err } + outFrame.Dst = ethDst outFrameRaw, err := ethertalk.Marshal(*outFrame) if err != nil { return err diff --git a/nbp_aurp.go b/nbp_aurp.go index a566dda..10087f2 100644 --- a/nbp_aurp.go +++ b/nbp_aurp.go @@ -20,6 +20,7 @@ import ( "fmt" "log" + "gitea.drjosh.dev/josh/jrouter/atalk" "gitea.drjosh.dev/josh/jrouter/atalk/nbp" "github.com/google/gopacket/pcap" "github.com/sfiera/multitalk/pkg/ddp" @@ -43,11 +44,11 @@ func handleNBPInAURP(pcapHandle *pcap.Handle, myHWAddr ethernet.Addr, ddpkt *ddp if len(nbpkt.Tuples) < 1 { return fmt.Errorf("no tuples in NBP packet") } + tuple := &nbpkt.Tuples[0] - log.Printf("NBP/DDP/AURP: Converting FwdReq to LkUp (%v)", nbpkt.Tuples[0]) + log.Printf("NBP/DDP/AURP: Converting FwdReq to LkUp (%v)", tuple) // 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 { @@ -66,7 +67,9 @@ func handleNBPInAURP(pcapHandle *pcap.Handle, myHWAddr ethernet.Addr, ddpkt *ddp if err != nil { return err } - // TODO: outFrame.Dst = zone-specific multicast address + if tuple.Zone != "*" && tuple.Zone != "" { + outFrame.Dst = atalk.MulticastAddr(tuple.Zone) + } outFrameRaw, err := ethertalk.Marshal(*outFrame) if err != nil { return err diff --git a/zip.go b/zip.go index 2407958..0354458 100644 --- a/zip.go +++ b/zip.go @@ -126,12 +126,12 @@ func handleZIP(pcapHandle *pcap.Handle, srcHWAddr, myHWAddr ethernet.Addr, myAdd // Only running a network with one zone for now. gnir := &zip.GetNetInfoReplyPacket{ ZoneInvalid: zipkt.ZoneName != cfg.EtherTalk.ZoneName, - UseBroadcast: true, // TODO: add multicast addr computation + UseBroadcast: false, OnlyOneZone: true, NetStart: cfg.EtherTalk.NetStart, NetEnd: cfg.EtherTalk.NetEnd, ZoneName: zipkt.ZoneName, // has to match request - MulticastAddr: ethertalk.AppleTalkBroadcast, + MulticastAddr: atalk.MulticastAddr(cfg.EtherTalk.ZoneName), DefaultZoneName: cfg.EtherTalk.ZoneName, } log.Printf("ZIP: Replying with GetNetInfo-Reply: %+v", gnir)