WIP
This commit is contained in:
parent
1c69eb5f77
commit
4ecef25867
5 changed files with 187 additions and 19 deletions
56
config.go
Normal file
56
config.go
Normal 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
2
go.mod
|
@ -1,3 +1,5 @@
|
||||||
module gitea.drjosh.dev/josh/jrouter
|
module gitea.drjosh.dev/josh/jrouter
|
||||||
|
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|
3
go.sum
Normal file
3
go.sum
Normal 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
8
jrouter.yaml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
ethertalk:
|
||||||
|
zone: The Twilight Zone
|
||||||
|
net_start: 100
|
||||||
|
net_end: 109
|
||||||
|
|
||||||
|
open_peering: true
|
||||||
|
peers:
|
||||||
|
- 192.168.86.21
|
131
main.go
131
main.go
|
@ -2,21 +2,51 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"gitea.drjosh.dev/josh/jrouter/aurp"
|
"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() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
log.Println("jrouter")
|
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 {
|
if localIP == nil {
|
||||||
iaddrs, err := net.InterfaceAddrs()
|
iaddrs, err := net.InterfaceAddrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -36,19 +66,56 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if localIP == nil {
|
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)
|
log.Printf("Using %v as local domain identifier", localIP)
|
||||||
|
|
||||||
peers := make(map[uint32]*aurp.Transport)
|
peers := make(map[udpAddr]*peer)
|
||||||
var nextConnID uint16
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Couldn't listen on udp4:387: %v", err)
|
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
|
// Incoming packet loop
|
||||||
pb := make([]byte, 65536)
|
pb := make([]byte, 65536)
|
||||||
|
@ -57,6 +124,8 @@ func main() {
|
||||||
// net.PacketConn.ReadFrom: "Callers should always process
|
// net.PacketConn.ReadFrom: "Callers should always process
|
||||||
// the n > 0 bytes returned before considering the error err."
|
// 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])
|
dh, _, parseErr := aurp.ParseDomainHeader(pb[:pktlen])
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
log.Printf("Failed to parse domain header: %v", err)
|
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("Failed to parse packet: %v", parseErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("The packet parsed succesfully as a %T", pkt)
|
||||||
|
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
log.Printf("Failed to read packet: %v", readErr)
|
log.Printf("Failed to read packet: %v", readErr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Existing peer?
|
// Existing peer?
|
||||||
rip := binary.BigEndian.Uint32(raddr.IP)
|
ra := udpAddrFromNet(raddr)
|
||||||
tr := peers[rip]
|
pr := peers[ra]
|
||||||
if tr == nil {
|
if pr == nil {
|
||||||
// New peer!
|
// New peer!
|
||||||
tr = &aurp.Transport{
|
nextConnID++
|
||||||
|
pr = &peer{
|
||||||
|
tr: &aurp.Transport{
|
||||||
LocalDI: aurp.IPDomainIdentifier(localIP),
|
LocalDI: aurp.IPDomainIdentifier(localIP),
|
||||||
RemoteDI: dh.SourceDI, // platinum rule
|
RemoteDI: dh.SourceDI, // platinum rule
|
||||||
LocalConnID: nextConnID,
|
LocalConnID: nextConnID,
|
||||||
|
},
|
||||||
|
conn: ln,
|
||||||
|
raddr: raddr,
|
||||||
}
|
}
|
||||||
nextConnID++
|
peers[ra] = pr
|
||||||
peers[rip] = tr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch p := pkt.(type) {
|
switch p := pkt.(type) {
|
||||||
|
@ -102,24 +177,26 @@ func main() {
|
||||||
|
|
||||||
case *aurp.OpenReqPacket:
|
case *aurp.OpenReqPacket:
|
||||||
// The peer tells us their connection ID in Open-Req.
|
// The peer tells us their connection ID in Open-Req.
|
||||||
tr.RemoteConnID = p.ConnectionID
|
pr.tr.RemoteConnID = p.ConnectionID
|
||||||
|
|
||||||
// Formulate a response.
|
// Formulate a response.
|
||||||
var rp *aurp.OpenRspPacket
|
var rp *aurp.OpenRspPacket
|
||||||
switch {
|
switch {
|
||||||
case p.Version != 1:
|
case p.Version != 1:
|
||||||
// Respond with Open-Rsp with unknown version error.
|
// 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:
|
case len(p.Options) > 0:
|
||||||
// Options? OPTIONS? We don't accept no stinkin' _options_
|
// 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:
|
default:
|
||||||
// Accept it I guess.
|
// 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
|
// Write an Open-Rsp packet
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if _, err := rp.WriteTo(&b); err != nil {
|
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)
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue