jrouter/main.go

229 lines
5.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
"gitea.drjosh.dev/josh/jrouter/aurp"
)
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
// Incoming packet loop
for {
2024-03-30 14:56:04 +11:00
if ctx.Err() != nil {
return
}
ln.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
2024-03-30 14:24:16 +11:00
pktbuf := make([]byte, 65536)
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-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-03-30 16:14:18 +11:00
if _, ok := pkt.(*aurp.AppleTalkPacket); ok {
// Probably something like:
//
// * parse the DDP header
// * check that this is headed for our local network
// * write the packet out in an EtherTalk frame
//
// or maybe if we were implementing a "central hub"
//
// * parse the DDP header
// * see if we know the network
// * forward to the peer with that network and lowest metric
log.Print("TODO: handle AppleTalk packets")
}
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),
}
}