jrouter/main.go

292 lines
7.1 KiB
Go
Raw Normal View History

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-03-30 14:13:34 +11:00
"context"
"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"
"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/ddp"
2024-04-06 16:02:30 +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-06 17:46:00 +11:00
// AppleTalk packet loops
iface, err := net.InterfaceByName(cfg.EtherTalk.Device)
if err != nil {
log.Fatalf("Couldn't find interface named %q: %v", cfg.EtherTalk.Device, err)
}
myHWAddr := ethernet.Addr(iface.HardwareAddr)
2024-04-05 13:18:22 +11:00
2024-04-06 17:46:00 +11:00
pcapHandle, err := atalk.StartPcap(cfg.EtherTalk.Device)
if err != nil {
log.Fatalf("Couldn't open network device for AppleTalk: %v", err)
}
defer pcapHandle.Close()
2024-04-06 16:02:30 +11:00
2024-04-06 20:11:15 +11:00
aarpMachine := NewAARPMachine(cfg, pcapHandle, myHWAddr)
2024-04-06 17:46:00 +11:00
aarpCh := make(chan *ethertalk.Packet, 1024)
go aarpMachine.Run(ctx, aarpCh)
2024-04-06 16:02:30 +11:00
2024-04-06 17:46:00 +11:00
go func() {
2024-04-05 13:18:22 +11:00
for {
2024-04-06 17:46:00 +11:00
rawPkt, _, err := pcapHandle.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-06 17:46:00 +11:00
ethFrame := new(ethertalk.Packet)
if err := ethertalk.Unmarshal(rawPkt, ethFrame); 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-06 17:46:00 +11:00
// Ignore if sent by me
if ethFrame.Src == myHWAddr {
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:
2024-04-06 17:46:00 +11:00
aarpCh <- ethFrame
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}
2024-04-06 17:46:00 +11:00
aarpMachine.Learn(srcAddr, ethFrame.Src)
2024-04-05 18:03:26 +11:00
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-04-06 16:02:30 +11:00
// AURP packet loop
2024-03-15 15:17:21 +11:00
for {
2024-03-30 14:56:04 +11:00
if ctx.Err() != nil {
return
}
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)
var operr *net.OpError
if errors.As(readErr, &operr) && operr.Timeout() {
continue
}
2024-03-15 16:15:24 +11:00
2024-04-06 16:02:30 +11:00
log.Printf("AURP: Received packet of length %d from %v", pktlen, raddr)
2024-03-24 18:01:24 +11:00
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 {
2024-04-06 16:02:30 +11:00
log.Printf("AURP: 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 {
2024-04-06 16:02:30 +11:00
log.Printf("AURP: 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-04-06 16:02:30 +11:00
log.Printf("AURP: The packet parsed succesfully as a %T", pkt)
2024-03-30 14:37:41 +11:00
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),
}
}