From 4ecef258672d93719b39ec926d41c51d212e69c5 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 24 Mar 2024 18:01:24 +1100 Subject: [PATCH] WIP --- config.go | 56 +++++++++++++++++++++ go.mod | 2 + go.sum | 3 ++ jrouter.yaml | 8 +++ main.go | 137 ++++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 187 insertions(+), 19 deletions(-) create mode 100644 config.go create mode 100644 go.sum create mode 100644 jrouter.yaml diff --git a/config.go b/config.go new file mode 100644 index 0000000..f2974c8 --- /dev/null +++ b/config.go @@ -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 +} diff --git a/go.mod b/go.mod index 3706a21..16aa3c1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module gitea.drjosh.dev/josh/jrouter go 1.22.0 + +require gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4bc0337 --- /dev/null +++ b/go.sum @@ -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= diff --git a/jrouter.yaml b/jrouter.yaml new file mode 100644 index 0000000..540e521 --- /dev/null +++ b/jrouter.yaml @@ -0,0 +1,8 @@ +ethertalk: + zone: The Twilight Zone + net_start: 100 + net_end: 109 + +open_peering: true +peers: +- 192.168.86.21 \ No newline at end of file diff --git a/main.go b/main.go index cdb8293..970ebdc 100644 --- a/main.go +++ b/main.go @@ -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), + } +}