2024-03-31 09:31:50 +11:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2024-03-10 11:57:03 +11:00
|
|
|
package main
|
|
|
|
|
2024-03-15 15:17:21 +11:00
|
|
|
import (
|
2024-04-05 14:07:16 +11:00
|
|
|
"bytes"
|
2024-03-30 14:13:34 +11:00
|
|
|
"context"
|
2024-03-30 17:13:13 +11:00
|
|
|
"errors"
|
2024-03-22 16:14:55 +11:00
|
|
|
"flag"
|
2024-03-15 15:17:21 +11:00
|
|
|
"log"
|
2024-04-05 10:43:29 +11:00
|
|
|
"math/rand/v2"
|
2024-03-15 15:17:21 +11:00
|
|
|
"net"
|
2024-03-30 14:24:16 +11:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
2024-03-24 18:01:24 +11:00
|
|
|
"regexp"
|
2024-03-30 14:49:18 +11:00
|
|
|
"sync"
|
2024-03-30 17:13:13 +11:00
|
|
|
"time"
|
2024-03-15 15:17:21 +11:00
|
|
|
|
2024-04-05 13:18:22 +11:00
|
|
|
"gitea.drjosh.dev/josh/jrouter/atalk"
|
2024-03-15 15:17:21 +11:00
|
|
|
"gitea.drjosh.dev/josh/jrouter/aurp"
|
2024-04-05 14:31:36 +11:00
|
|
|
"github.com/sfiera/multitalk/pkg/aarp"
|
|
|
|
"github.com/sfiera/multitalk/pkg/ddp"
|
2024-04-05 17:59:47 +11:00
|
|
|
"github.com/sfiera/multitalk/pkg/ethernet"
|
2024-04-05 14:07:16 +11:00
|
|
|
"github.com/sfiera/multitalk/pkg/ethertalk"
|
2024-03-15 15:17:21 +11:00
|
|
|
)
|
|
|
|
|
2024-03-24 18:01:24 +11:00
|
|
|
var hasPortRE = regexp.MustCompile(`:\d+$`)
|
|
|
|
|
|
|
|
var configFilePath = flag.String("config", "jrouter.yaml", "Path to configuration file to use")
|
|
|
|
|
2024-03-10 11:57:03 +11:00
|
|
|
func main() {
|
2024-03-22 16:14:55 +11:00
|
|
|
flag.Parse()
|
2024-03-15 15:17:21 +11:00
|
|
|
log.Println("jrouter")
|
|
|
|
|
2024-03-24 18:01:24 +11:00
|
|
|
cfg, err := loadConfig(*configFilePath)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Couldn't load configuration file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
localIP := net.ParseIP(cfg.LocalIP).To4()
|
2024-03-22 16:14:55 +11:00
|
|
|
if localIP == nil {
|
|
|
|
iaddrs, err := net.InterfaceAddrs()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Couldn't read network interface addresses: %v", err)
|
|
|
|
}
|
|
|
|
for _, iaddr := range iaddrs {
|
|
|
|
inet, ok := iaddr.(*net.IPNet)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !inet.IP.IsGlobalUnicast() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
localIP = inet.IP.To4()
|
|
|
|
if localIP != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if localIP == nil {
|
2024-03-24 18:01:24 +11:00
|
|
|
log.Fatalf("No global unicast IPv4 addresses on any network interfaces, and no valid local_ip address in configuration")
|
2024-03-22 16:14:55 +11:00
|
|
|
}
|
|
|
|
}
|
2024-03-24 21:10:24 +11:00
|
|
|
localDI := aurp.IPDomainIdentifier(localIP)
|
2024-03-22 16:14:55 +11:00
|
|
|
|
|
|
|
log.Printf("Using %v as local domain identifier", localIP)
|
|
|
|
|
2024-03-30 20:47:18 +11:00
|
|
|
log.Printf("EtherTalk configuration: %+v", cfg.EtherTalk)
|
|
|
|
|
2024-03-24 18:01:24 +11:00
|
|
|
peers := make(map[udpAddr]*peer)
|
2024-04-05 10:43:29 +11:00
|
|
|
var nextConnID uint16
|
|
|
|
for nextConnID == 0 {
|
|
|
|
nextConnID = uint16(rand.IntN(0x10000))
|
|
|
|
}
|
2024-03-22 16:14:55 +11:00
|
|
|
|
2024-03-24 18:01:24 +11:00
|
|
|
ln, err := net.ListenUDP("udp4", &net.UDPAddr{Port: int(cfg.ListenPort)})
|
2024-03-15 15:17:21 +11:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Couldn't listen on udp4:387: %v", err)
|
|
|
|
}
|
2024-03-24 18:01:24 +11:00
|
|
|
log.Printf("Listening on %v", ln.LocalAddr())
|
|
|
|
|
2024-03-30 14:24:16 +11:00
|
|
|
log.Println("Press ^C or send SIGINT to stop the router gracefully")
|
2024-03-30 14:37:41 +11:00
|
|
|
cctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
ctx, _ := signal.NotifyContext(cctx, os.Interrupt)
|
2024-03-30 14:49:18 +11:00
|
|
|
|
|
|
|
// Wait until all peer handlers have finished before closing the port
|
|
|
|
var handlersWG sync.WaitGroup
|
|
|
|
defer func() {
|
2024-03-30 17:04:27 +11:00
|
|
|
log.Print("Waiting for handlers to return...")
|
2024-03-30 14:49:18 +11:00
|
|
|
handlersWG.Wait()
|
2024-03-30 14:30:58 +11:00
|
|
|
ln.Close()
|
|
|
|
}()
|
2024-03-30 14:49:18 +11:00
|
|
|
goHandler := func(p *peer) {
|
|
|
|
handlersWG.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer handlersWG.Done()
|
|
|
|
p.handle(ctx)
|
|
|
|
}()
|
|
|
|
}
|
2024-03-30 14:24:16 +11:00
|
|
|
|
2024-03-24 18:01:24 +11:00
|
|
|
for _, peerStr := range cfg.Peers {
|
|
|
|
if !hasPortRE.MatchString(peerStr) {
|
|
|
|
peerStr += ":387"
|
|
|
|
}
|
|
|
|
|
|
|
|
raddr, err := net.ResolveUDPAddr("udp4", peerStr)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Invalid UDP address: %v", err)
|
|
|
|
}
|
|
|
|
log.Printf("resolved %q to %v", peerStr, raddr)
|
|
|
|
|
|
|
|
peer := &peer{
|
2024-03-30 20:27:24 +11:00
|
|
|
cfg: cfg,
|
|
|
|
tr: &aurp.Transport{
|
|
|
|
LocalDI: localDI,
|
|
|
|
RemoteDI: aurp.IPDomainIdentifier(raddr.IP),
|
|
|
|
LocalConnID: nextConnID,
|
|
|
|
},
|
2024-03-24 18:01:24 +11:00
|
|
|
conn: ln,
|
|
|
|
raddr: raddr,
|
2024-03-24 21:10:24 +11:00
|
|
|
recv: make(chan aurp.Packet, 1024),
|
2024-03-24 18:01:24 +11:00
|
|
|
}
|
2024-04-05 10:43:29 +11:00
|
|
|
aurp.Inc(&nextConnID)
|
2024-03-24 18:01:24 +11:00
|
|
|
peers[udpAddrFromNet(raddr)] = peer
|
2024-04-05 10:43:29 +11:00
|
|
|
goHandler(peer)
|
2024-03-24 18:01:24 +11:00
|
|
|
}
|
2024-03-15 15:17:21 +11:00
|
|
|
|
2024-04-05 13:18:22 +11:00
|
|
|
// AppleTalk packet loop
|
2024-04-05 17:59:47 +11:00
|
|
|
type amtEntry struct {
|
|
|
|
hwAddr ethernet.Addr
|
|
|
|
last time.Time
|
|
|
|
}
|
|
|
|
amt := make(map[ddp.Addr]amtEntry)
|
2024-04-05 13:18:22 +11:00
|
|
|
go func() {
|
2024-04-05 14:07:16 +11:00
|
|
|
iface, err := net.InterfaceByName(cfg.EtherTalk.Device)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Couldn't find interface named %q: %v", cfg.EtherTalk.Device, err)
|
|
|
|
}
|
|
|
|
localMAC := iface.HardwareAddr
|
|
|
|
|
2024-04-05 13:18:22 +11:00
|
|
|
handle, err := atalk.StartPcap(cfg.EtherTalk.Device)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Couldn't open network device for AppleTalk: %v", err)
|
|
|
|
}
|
|
|
|
defer handle.Close()
|
|
|
|
|
|
|
|
for {
|
2024-04-05 14:07:16 +11:00
|
|
|
rawPkt, _, err := handle.ReadPacketData()
|
2024-04-05 13:18:22 +11:00
|
|
|
if err != nil {
|
2024-04-05 14:07:16 +11:00
|
|
|
log.Fatalf("Couldn't read AppleTalk / AARP packet data: %v", err)
|
|
|
|
}
|
|
|
|
|
2024-04-05 18:03:26 +11:00
|
|
|
var ethFrame ethertalk.Packet
|
|
|
|
if err := ethertalk.Unmarshal(rawPkt, ðFrame); err != nil {
|
2024-04-05 14:07:16 +11:00
|
|
|
log.Printf("Couldn't unmarshal EtherTalk frame: %v", err)
|
|
|
|
continue
|
2024-04-05 13:18:22 +11:00
|
|
|
}
|
2024-04-05 14:07:16 +11:00
|
|
|
|
2024-04-05 18:03:26 +11:00
|
|
|
if bytes.Equal(ethFrame.Src[:], localMAC) {
|
2024-04-05 14:07:16 +11:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-04-05 18:03:26 +11:00
|
|
|
switch ethFrame.SNAPProto {
|
2024-04-05 14:31:36 +11:00
|
|
|
case ethertalk.AARPProto:
|
|
|
|
var aapkt aarp.Packet
|
2024-04-05 18:03:26 +11:00
|
|
|
if err := aarp.Unmarshal(ethFrame.Payload, &aapkt); err != nil {
|
2024-04-05 14:31:36 +11:00
|
|
|
log.Printf("Couldn't unmarshal AARP packet: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
2024-04-05 17:59:47 +11:00
|
|
|
|
|
|
|
switch aapkt.Opcode {
|
|
|
|
case aarp.RequestOp:
|
|
|
|
log.Printf("AARP: Who has %v? Tell %v", aapkt.Dst.Proto, aapkt.Src.Proto)
|
|
|
|
// Glean that aapkt.Src.Proto -> aapkt.Src.Hardware
|
|
|
|
amt[aapkt.Src.Proto] = amtEntry{
|
|
|
|
hwAddr: aapkt.Src.Hardware,
|
|
|
|
last: time.Now(),
|
|
|
|
}
|
|
|
|
log.Printf("AARP: Gleaned that %v -> %v", aapkt.Src.Proto, aapkt.Src.Hardware)
|
|
|
|
|
|
|
|
case aarp.ResponseOp:
|
|
|
|
log.Printf("AARP: %v is at %v", aapkt.Dst.Proto, aapkt.Dst.Hardware)
|
|
|
|
amt[aapkt.Dst.Proto] = amtEntry{
|
|
|
|
hwAddr: aapkt.Dst.Hardware,
|
|
|
|
last: time.Now(),
|
|
|
|
}
|
|
|
|
|
|
|
|
case aarp.ProbeOp:
|
|
|
|
log.Printf("AARP: %v probing to see if %v is available", aapkt.Src.Hardware, aapkt.Src.Proto)
|
|
|
|
// AMT should not be updated, because the address is tentative
|
|
|
|
}
|
2024-04-05 14:31:36 +11:00
|
|
|
|
|
|
|
case ethertalk.AppleTalkProto:
|
|
|
|
var ddpkt ddp.ExtPacket
|
2024-04-05 18:03:26 +11:00
|
|
|
if err := ddp.ExtUnmarshal(ethFrame.Payload, &ddpkt); err != nil {
|
2024-04-05 14:31:36 +11:00
|
|
|
log.Printf("Couldn't unmarshal DDP packet: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
2024-04-05 21:09:55 +11:00
|
|
|
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))
|
2024-04-05 18:03:26 +11:00
|
|
|
// Glean address info for AMT
|
|
|
|
srcAddr := ddp.Addr{Network: ddpkt.SrcNet, Node: ddpkt.SrcNode}
|
|
|
|
amt[srcAddr] = amtEntry{
|
|
|
|
hwAddr: ethFrame.Src,
|
|
|
|
last: time.Now(),
|
|
|
|
}
|
|
|
|
log.Printf("DDP: Gleaned that %v -> %v", srcAddr, ethFrame.Src)
|
2024-04-05 14:38:27 +11:00
|
|
|
|
|
|
|
default:
|
2024-04-05 18:03:26 +11:00
|
|
|
log.Printf("Read unknown packet %s -> %s with payload %x", ethFrame.Src, ethFrame.Dst, ethFrame.Payload)
|
2024-04-05 14:31:36 +11:00
|
|
|
|
|
|
|
}
|
2024-04-05 13:18:22 +11:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2024-03-15 15:17:21 +11:00
|
|
|
// Incoming packet loop
|
|
|
|
for {
|
2024-03-30 14:56:04 +11:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-03-30 17:13:13 +11:00
|
|
|
ln.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
2024-04-05 14:07:16 +11:00
|
|
|
pktbuf := make([]byte, 4096)
|
2024-03-30 14:24:16 +11:00
|
|
|
pktlen, raddr, readErr := ln.ReadFromUDP(pktbuf)
|
2024-03-30 17:13:13 +11:00
|
|
|
|
|
|
|
var operr *net.OpError
|
|
|
|
if errors.As(readErr, &operr) && operr.Timeout() {
|
|
|
|
continue
|
|
|
|
}
|
2024-03-15 16:15:24 +11:00
|
|
|
|
2024-03-24 18:01:24 +11:00
|
|
|
log.Printf("Received packet of length %d from %v", pktlen, raddr)
|
|
|
|
|
2024-03-30 14:24:16 +11:00
|
|
|
dh, pkt, parseErr := aurp.ParsePacket(pktbuf[:pktlen])
|
2024-03-15 16:15:24 +11:00
|
|
|
if parseErr != nil {
|
|
|
|
log.Printf("Failed to parse packet: %v", parseErr)
|
2024-03-15 15:17:21 +11:00
|
|
|
}
|
2024-03-15 16:15:24 +11:00
|
|
|
if readErr != nil {
|
|
|
|
log.Printf("Failed to read packet: %v", readErr)
|
2024-03-30 14:30:58 +11:00
|
|
|
return
|
2024-03-15 15:17:21 +11:00
|
|
|
}
|
2024-03-22 16:14:55 +11:00
|
|
|
|
2024-03-30 14:37:41 +11:00
|
|
|
log.Printf("The packet parsed succesfully as a %T", pkt)
|
|
|
|
|
2024-04-06 10:34:54 +11:00
|
|
|
if apkt, ok := pkt.(*aurp.AppleTalkPacket); ok {
|
|
|
|
var ddpkt ddp.ExtPacket
|
|
|
|
if err := ddp.ExtUnmarshal(apkt.Data, &ddpkt); err != nil {
|
|
|
|
log.Printf("AURP: Couldn't unmarshal encapsulated DDP packet: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
log.Printf("AURP encapsulated 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))
|
|
|
|
continue
|
2024-03-30 16:14:18 +11:00
|
|
|
}
|
|
|
|
|
2024-03-22 16:14:55 +11:00
|
|
|
// Existing peer?
|
2024-03-24 18:01:24 +11:00
|
|
|
ra := udpAddrFromNet(raddr)
|
|
|
|
pr := peers[ra]
|
|
|
|
if pr == nil {
|
2024-03-22 16:14:55 +11:00
|
|
|
// New peer!
|
2024-03-24 18:01:24 +11:00
|
|
|
pr = &peer{
|
2024-03-30 20:27:24 +11:00
|
|
|
cfg: cfg,
|
2024-03-24 18:01:24 +11:00
|
|
|
tr: &aurp.Transport{
|
2024-03-24 21:10:24 +11:00
|
|
|
LocalDI: localDI,
|
2024-03-24 18:01:24 +11:00
|
|
|
RemoteDI: dh.SourceDI, // platinum rule
|
|
|
|
LocalConnID: nextConnID,
|
|
|
|
},
|
|
|
|
conn: ln,
|
|
|
|
raddr: raddr,
|
2024-03-24 21:10:24 +11:00
|
|
|
recv: make(chan aurp.Packet, 1024),
|
2024-03-24 18:01:24 +11:00
|
|
|
}
|
2024-04-05 10:43:29 +11:00
|
|
|
aurp.Inc(&nextConnID)
|
2024-03-24 18:01:24 +11:00
|
|
|
peers[ra] = pr
|
2024-03-30 14:49:18 +11:00
|
|
|
goHandler(pr)
|
2024-03-22 16:14:55 +11:00
|
|
|
}
|
|
|
|
|
2024-03-24 21:10:24 +11:00
|
|
|
// Pass the packet to the goroutine in charge of this peer.
|
2024-03-30 14:30:58 +11:00
|
|
|
select {
|
|
|
|
case pr.recv <- pkt:
|
|
|
|
// That's it for us.
|
|
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
2024-03-24 21:10:24 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-24 18:01:24 +11:00
|
|
|
// Hashable net.UDPAddr
|
|
|
|
type udpAddr struct {
|
|
|
|
ipv4 [4]byte
|
|
|
|
port uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
func udpAddrFromNet(a *net.UDPAddr) udpAddr {
|
|
|
|
return udpAddr{
|
|
|
|
ipv4: [4]byte(a.IP.To4()),
|
|
|
|
port: uint16(a.Port),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u udpAddr) toNet() *net.UDPAddr {
|
|
|
|
return &net.UDPAddr{
|
|
|
|
IP: u.ipv4[:],
|
|
|
|
Port: int(u.port),
|
|
|
|
}
|
|
|
|
}
|