This commit is contained in:
Josh Deprez 2024-03-24 18:01:24 +11:00
parent 1c69eb5f77
commit 4ecef25867
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw
5 changed files with 187 additions and 19 deletions

56
config.go Normal file
View file

@ -0,0 +1,56 @@
package main
import (
"os"
"gopkg.in/yaml.v3"
)
type config struct {
// Optional: default is 387.
ListenPort uint16 `yaml:"listen_port"`
// Sets the Domain Identifier used by this router.
// Note: this does not "bind" the IP side of the router to a particular
// interface; it will listen on all interfaces with IP addresses.
// Optional: defaults to the first global unicast address on any local
// network interface.
LocalIP string `yaml:"local_ip"`
// Required for routing a local EtherTalk network.
EtherTalk struct {
ZoneName string `yaml:"zone_name"`
NetStart uint16 `yaml:"net_start"`
NetEnd uint16 `yaml:"net_end"`
} `yaml:"ethertalk"`
// LocalTalk struct {
// ZoneName string `yaml:"zone_name"`
// Network uint16 `yaml:"network"`
// } `yaml:"localtalk"`
// Allow routers other than those listed under peers?
OpenPeering bool `yaml:"open_peering"`
// List of peer routers.
Peers []string `yaml:"peers"`
}
func loadConfig(cfgPath string) (*config, error) {
f, err := os.Open(cfgPath)
if err != nil {
return nil, err
}
defer f.Close()
c := new(config)
if err := yaml.NewDecoder(f).Decode(c); err != nil {
return nil, err
}
if c.ListenPort == 0 {
c.ListenPort = 387
}
return c, nil
}

2
go.mod
View file

@ -1,3 +1,5 @@
module gitea.drjosh.dev/josh/jrouter
go 1.22.0
require gopkg.in/yaml.v3 v3.0.1

3
go.sum Normal file
View file

@ -0,0 +1,3 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

8
jrouter.yaml Normal file
View file

@ -0,0 +1,8 @@
ethertalk:
zone: The Twilight Zone
net_start: 100
net_end: 109
open_peering: true
peers:
- 192.168.86.21

137
main.go
View file

@ -2,21 +2,51 @@ package main
import (
"bytes"
"encoding/binary"
"flag"
"log"
"net"
"regexp"
"gitea.drjosh.dev/josh/jrouter/aurp"
)
var localIPAddr = flag.String("local-ip", "", "IPv4 address to use as the Source Domain Identifier")
var hasPortRE = regexp.MustCompile(`:\d+$`)
var configFilePath = flag.String("config", "jrouter.yaml", "Path to configuration file to use")
type peer struct {
tr *aurp.Transport
conn *net.UDPConn
raddr *net.UDPAddr
}
func (p *peer) dataReceiver() {
// Write an Open-Req packet
oreq := p.tr.NewOpenReqPacket(nil)
var b bytes.Buffer
if _, err := oreq.WriteTo(&b); err != nil {
log.Printf("Couldn't write Open-Req packet to buffer: %v", err)
return
}
n, err := p.conn.WriteToUDP(b.Bytes(), p.raddr)
if err != nil {
log.Printf("Couldn't write packet to peer: %v", err)
return
}
log.Printf("Sent Open-Req (len %d) to peer %v", n, p.raddr)
}
func main() {
flag.Parse()
log.Println("jrouter")
localIP := net.ParseIP(*localIPAddr).To4()
cfg, err := loadConfig(*configFilePath)
if err != nil {
log.Fatalf("Couldn't load configuration file: %v", err)
}
localIP := net.ParseIP(cfg.LocalIP).To4()
if localIP == nil {
iaddrs, err := net.InterfaceAddrs()
if err != nil {
@ -36,19 +66,56 @@ func main() {
}
}
if localIP == nil {
log.Fatalf("No global unicast IPv4 addresses on any network interfaces, and no valid address passed with --local-ip")
log.Fatalf("No global unicast IPv4 addresses on any network interfaces, and no valid local_ip address in configuration")
}
}
log.Printf("Using %v as local domain identifier", localIP)
peers := make(map[uint32]*aurp.Transport)
peers := make(map[udpAddr]*peer)
var nextConnID uint16
ln, err := net.ListenUDP("udp4", &net.UDPAddr{Port: 387})
ln, err := net.ListenUDP("udp4", &net.UDPAddr{Port: int(cfg.ListenPort)})
if err != nil {
log.Fatalf("Couldn't listen on udp4:387: %v", err)
}
defer ln.Close()
log.Printf("Listening on %v", ln.LocalAddr())
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)
tr := &aurp.Transport{
LocalDI: aurp.IPDomainIdentifier(localIP),
RemoteDI: aurp.IPDomainIdentifier(raddr.IP),
LocalConnID: nextConnID,
}
nextConnID++
// conn, err := net.DialUDP("udp4", nil, raddr)
// if err != nil {
// log.Printf("Couldn't dial %v->%v: %v", nil, raddr, err)
// continue
// }
// log.Printf("conn.LocalAddr = %v", conn.LocalAddr())
peer := &peer{
tr: tr,
conn: ln,
raddr: raddr,
}
go peer.dataReceiver()
peers[udpAddrFromNet(raddr)] = peer
}
// Incoming packet loop
pb := make([]byte, 65536)
@ -57,6 +124,8 @@ func main() {
// net.PacketConn.ReadFrom: "Callers should always process
// the n > 0 bytes returned before considering the error err."
log.Printf("Received packet of length %d from %v", pktlen, raddr)
dh, _, parseErr := aurp.ParseDomainHeader(pb[:pktlen])
if parseErr != nil {
log.Printf("Failed to parse domain header: %v", err)
@ -67,23 +136,29 @@ func main() {
log.Printf("Failed to parse packet: %v", parseErr)
}
log.Printf("The packet parsed succesfully as a %T", pkt)
if readErr != nil {
log.Printf("Failed to read packet: %v", readErr)
continue
}
// Existing peer?
rip := binary.BigEndian.Uint32(raddr.IP)
tr := peers[rip]
if tr == nil {
ra := udpAddrFromNet(raddr)
pr := peers[ra]
if pr == nil {
// New peer!
tr = &aurp.Transport{
LocalDI: aurp.IPDomainIdentifier(localIP),
RemoteDI: dh.SourceDI, // platinum rule
LocalConnID: nextConnID,
}
nextConnID++
peers[rip] = tr
pr = &peer{
tr: &aurp.Transport{
LocalDI: aurp.IPDomainIdentifier(localIP),
RemoteDI: dh.SourceDI, // platinum rule
LocalConnID: nextConnID,
},
conn: ln,
raddr: raddr,
}
peers[ra] = pr
}
switch p := pkt.(type) {
@ -102,24 +177,26 @@ func main() {
case *aurp.OpenReqPacket:
// The peer tells us their connection ID in Open-Req.
tr.RemoteConnID = p.ConnectionID
pr.tr.RemoteConnID = p.ConnectionID
// Formulate a response.
var rp *aurp.OpenRspPacket
switch {
case p.Version != 1:
// Respond with Open-Rsp with unknown version error.
rp = tr.NewOpenRspPacket(0, aurp.ErrCodeInvalidVersion, nil)
rp = pr.tr.NewOpenRspPacket(0, aurp.ErrCodeInvalidVersion, nil)
case len(p.Options) > 0:
// Options? OPTIONS? We don't accept no stinkin' _options_
rp = tr.NewOpenRspPacket(0, aurp.ErrCodeOptionNegotiation, nil)
rp = pr.tr.NewOpenRspPacket(0, aurp.ErrCodeOptionNegotiation, nil)
default:
// Accept it I guess.
rp = tr.NewOpenRspPacket(0, 1, nil)
rp = pr.tr.NewOpenRspPacket(0, 1, nil)
}
log.Printf("Responding with %T", rp)
// Write an Open-Rsp packet
var b bytes.Buffer
if _, err := rp.WriteTo(&b); err != nil {
@ -136,6 +213,28 @@ func main() {
log.Printf("Open-Rsp error code from peer %v: %d", raddr.IP, p.RateOrErrCode)
}
// TODO
}
}
}
// 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),
}
}