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 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
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 )
}
var pkt ethertalk . Packet
if err := ethertalk . Unmarshal ( rawPkt , & pkt ) ; err != nil {
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
if bytes . Equal ( pkt . Src [ : ] , localMAC ) {
continue
}
2024-04-05 14:31:36 +11:00
switch pkt . SNAPProto {
case ethertalk . AARPProto :
var aapkt aarp . Packet
if err := aarp . Unmarshal ( pkt . Payload , & aapkt ) ; err != nil {
log . Printf ( "Couldn't unmarshal AARP packet: %v" , err )
continue
}
2024-04-05 14:38:27 +11:00
log . Printf ( "Read AARP packet with opcode %d src %+v dst %+v" , aapkt . Opcode , aapkt . Src , aapkt . Dst )
2024-04-05 14:31:36 +11:00
case ethertalk . AppleTalkProto :
var ddpkt ddp . ExtPacket
if err := ddp . ExtUnmarshal ( pkt . Payload , & ddpkt ) ; err != nil {
log . Printf ( "Couldn't unmarshal DDP packet: %v" , err )
continue
}
2024-04-05 14:38:27 +11:00
log . Printf ( "Read AppleTalk packet with src net %d node %d socket %d dst net %d node %d socket %d data len %d" , ddpkt . SrcNet , ddpkt . SrcNode , ddpkt . SrcSocket , ddpkt . DstNet , ddpkt . DstNode , ddpkt . DstSocket , len ( ddpkt . Data ) )
default :
log . Printf ( "Read unknown packet %s -> %s with payload %x" , pkt . Src , pkt . Dst , pkt . 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-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 ) ,
}
}