diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 5b75690..d93f5d2 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -12,6 +12,7 @@ import ( "os" "os/signal" "regexp" + "strings" "syscall" "time" @@ -188,6 +189,35 @@ func main() { } } } + // Check to see if the peers are in a parsable format, if not then default + // them to the TCP scheme + if peers, ok := dat["Peers"].([]interface{}); ok { + for index, peer := range peers { + uri := peer.(string) + if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { + continue + } + if strings.HasPrefix(uri, "tcp:") { + uri = uri[4:] + } + (dat["Peers"].([]interface{}))[index] = "tcp://" + uri + } + } + // Now do the same with the interface peers + if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok { + for intf, peers := range interfacepeers { + for index, peer := range peers.([]interface{}) { + uri := peer.(string) + if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") { + continue + } + if strings.HasPrefix(uri, "tcp:") { + uri = uri[4:] + } + ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri + } + } + } // Overlay our newly mapped configuration onto the autoconf node config that // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index b37c256..8135815 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -87,6 +87,7 @@ func main() { logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) } } else { + endpoint = *server logger.Println("Using endpoint", endpoint, "from command line") } diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 618f00f..0eb2188 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -71,29 +71,39 @@ etc/systemd/system/*.service etc/systemd/system EOF cat > /tmp/$PKGNAME/debian/postinst << EOF #!/bin/sh + +if ! getent group yggdrasil 2>&1 > /dev/null; then + addgroup --system --quiet yggdrasil +fi + if [ -f /etc/yggdrasil.conf ]; then mkdir -p /var/backups echo "Backing up configuration file to /var/backups/yggdrasil.conf.`date +%Y%m%d`" cp /etc/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d` - echo "Normalising /etc/yggdrasil.conf" + echo "Normalising and updating /etc/yggdrasil.conf" /usr/bin/yggdrasil -useconffile /var/backups/yggdrasil.conf.`date +%Y%m%d` -normaliseconf > /etc/yggdrasil.conf -fi -if command -v systemctl >/dev/null; then - systemctl daemon-reload >/dev/null || true - if [ -f /etc/systemd/system/multi-user.target.wants/yggdrasil.service ]; then - echo "Starting Yggdrasil" + chgrp yggdrasil /etc/yggdrasil.conf + + if command -v systemctl >/dev/null; then + systemctl daemon-reload >/dev/null || true + systemctl enable yggdrasil || true systemctl start yggdrasil || true fi +else + echo "Generating initial configuration file /etc/yggdrasil.conf" + echo "Please familiarise yourself with this file before starting Yggdrasil" + /usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf + chgrp yggdrasil /etc/yggdrasil.conf fi EOF cat > /tmp/$PKGNAME/debian/prerm << EOF #!/bin/sh if command -v systemctl >/dev/null; then if systemctl is-active --quiet yggdrasil; then - echo "Stopping Yggdrasil" systemctl stop yggdrasil || true fi + systemctl disable yggdrasil || true fi EOF diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service index 9ae4a07..1b6f1f0 100644 --- a/contrib/systemd/yggdrasil.service +++ b/contrib/systemd/yggdrasil.service @@ -4,6 +4,7 @@ Wants=network.target After=network.target [Service] +Group=yggdrasil ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil @@ -12,7 +13,7 @@ ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \ yggdrasil -genconf > /etc/yggdrasil.conf; \ echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \ fi" -ExecStart=/bin/sh -c "exec yggdrasil -useconf < /etc/yggdrasil.conf" +ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf Restart=always [Install] diff --git a/src/config/config.go b/src/config/config.go index 9671eca..b5a1f89 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -2,23 +2,24 @@ package config // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { - Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` - AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."` - Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` - InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."` - ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."` - AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` - EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` - EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` - SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` - SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` - MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` - IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` - IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` - IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` - SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` - TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` - SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` + Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` + AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."` + Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` + InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."` + ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."` + AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` + EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` + EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` + SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` + SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` + MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` + IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` + IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` + IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` + TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` + SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` + NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go new file mode 100644 index 0000000..4a43209 --- /dev/null +++ b/src/yggdrasil/adapter.go @@ -0,0 +1,25 @@ +package yggdrasil + +// Defines the minimum required functions for an adapter type. +type AdapterInterface interface { + init(core *Core, send chan<- []byte, recv <-chan []byte) + read() error + write() error + close() error +} + +// Defines the minimum required struct members for an adapter type (this is +// now the base type for tunAdapter in tun.go) +type Adapter struct { + AdapterInterface + core *Core + send chan<- []byte + recv <-chan []byte +} + +// Initialises the adapter. +func (adapter *Adapter) init(core *Core, send chan<- []byte, recv <-chan []byte) { + adapter.core = core + adapter.send = send + adapter.recv = recv +} diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 266d5a8..f3b5998 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -160,9 +160,9 @@ func (a *admin) init(c *Core, listenaddr string) { }() return admin_info{ - a.core.tun.iface.Name(): admin_info{ - "tap_mode": a.core.tun.iface.IsTAP(), - "mtu": a.core.tun.mtu, + a.core.router.tun.iface.Name(): admin_info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), + "mtu": a.core.router.tun.mtu, }, }, nil }) @@ -185,8 +185,8 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{}, errors.New("Failed to configure adapter") } else { return admin_info{ - a.core.tun.iface.Name(): admin_info{ - "tap_mode": a.core.tun.iface.IsTAP(), + a.core.router.tun.iface.Name(): admin_info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), "mtu": ifmtu, }, }, nil @@ -322,6 +322,23 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{}, err } }) + a.addHandler("getNodeInfo", []string{"box_pub_key", "coords", "[nocache]"}, func(in admin_info) (admin_info, error) { + var nocache bool + if in["nocache"] != nil { + nocache = in["nocache"].(string) == "true" + } + result, err := a.admin_getNodeInfo(in["box_pub_key"].(string), in["coords"].(string), nocache) + if err == nil { + var m map[string]interface{} + if err = json.Unmarshal(result, &m); err == nil { + return admin_info{"nodeinfo": m}, nil + } else { + return admin_info{}, err + } + } else { + return admin_info{}, err + } + }) } // start runs the admin API socket to listen for / respond to admin API calls. @@ -515,13 +532,7 @@ func (a *admin) addPeer(addr string, sintf string) error { return errors.New("invalid peer: " + addr) } } else { - // no url scheme provided - addr = strings.ToLower(addr) - if strings.HasPrefix(addr, "tcp:") { - addr = addr[4:] - } - a.core.tcp.connect(addr, "") - return nil + return errors.New("invalid peer: " + addr) } return nil } @@ -539,12 +550,12 @@ func (a *admin) removePeer(p string) error { // startTunWithMTU creates the tun/tap device, sets its address, and sets the MTU to the provided value. func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error { // Close the TUN first if open - _ = a.core.tun.close() + _ = a.core.router.tun.close() // Then reconfigure and start it addr := a.core.router.addr straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address_prefix)-1) if ifname != "none" { - err := a.core.tun.setup(ifname, iftapmode, straddr, ifmtu) + err := a.core.router.tun.setup(ifname, iftapmode, straddr, ifmtu) if err != nil { return err } @@ -559,9 +570,9 @@ func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error a.core.sessions.sendPingPong(sinfo, false) } // Aaaaand... go! - go a.core.tun.read() + go a.core.router.tun.read() } - go a.core.tun.write() + go a.core.router.tun.write() return nil } @@ -806,6 +817,52 @@ func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtR return dhtRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString)) } +func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) { + var key boxPubKey + if keyBytes, err := hex.DecodeString(keyString); err != nil { + return nodeinfoPayload{}, err + } else { + copy(key[:], keyBytes) + } + if !nocache { + if response, err := a.core.nodeinfo.getCachedNodeInfo(key); err == nil { + return response, nil + } + } + var coords []byte + for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { + if cstr == "" { + // Special case, happens if trimmed is the empty string, e.g. this is the root + continue + } + if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { + return nodeinfoPayload{}, err + } else { + coords = append(coords, uint8(u64)) + } + } + response := make(chan *nodeinfoPayload, 1) + sendNodeInfoRequest := func() { + a.core.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) { + defer func() { recover() }() + select { + case response <- nodeinfo: + default: + } + }) + a.core.nodeinfo.sendNodeInfo(key, coords, false) + } + a.core.router.doAdmin(sendNodeInfoRequest) + go func() { + time.Sleep(6 * time.Second) + close(response) + }() + for res := range response { + return *res, nil + } + return nodeinfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) +} + // getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network. // This is color-coded and labeled, and includes the self node, switch peers, nodes known to the DHT, and nodes with open sessions. // The graph is structured as a tree with directed links leading away from the root. diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 5ab91a7..66c6964 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -28,10 +28,10 @@ type Core struct { sessions sessions router router dht dht - tun tunDevice admin admin searches searches multicast multicast + nodeinfo nodeinfo tcp tcpInterface log *log.Logger ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this @@ -59,7 +59,6 @@ func (c *Core) init(bpub *boxPubKey, c.peers.init(c) c.router.init(c) c.switchTable.init(c, c.sigPub) // TODO move before peers? before router? - c.tun.init(c) } // Get the current build name. This is usually injected if built from git, @@ -124,6 +123,9 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) c.admin.init(c, nc.AdminListen) + c.nodeinfo.init(c) + c.nodeinfo.setNodeInfo(nc.NodeInfo) + if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { c.log.Println("Failed to start TCP interface") return err @@ -188,7 +190,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { } ip := net.IP(c.router.addr[:]).String() - if err := c.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address_prefix)-1), nc.IfMTU); err != nil { + if err := c.router.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address_prefix)-1), nc.IfMTU); err != nil { c.log.Println("Failed to start TUN/TAP") return err } @@ -200,7 +202,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { // Stops the Yggdrasil node. func (c *Core) Stop() { c.log.Println("Stopping...") - c.tun.close() + c.router.tun.close() c.admin.close() } @@ -239,6 +241,16 @@ func (c *Core) GetSubnet() *net.IPNet { return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } +// Gets the nodeinfo. +func (c *Core) GetNodeInfo() nodeinfoPayload { + return c.nodeinfo.getNodeInfo() +} + +// Sets the nodeinfo. +func (c *Core) SetNodeInfo(nodeinfo interface{}) { + c.nodeinfo.setNodeInfo(nodeinfo) +} + // Sets the output logger of the Yggdrasil node after startup. This may be // useful if you want to redirect the output later. func (c *Core) SetLogger(log *log.Logger) { @@ -293,10 +305,10 @@ func (c *Core) GetTUNDefaultIfTAPMode() bool { // Gets the current TUN/TAP interface name. func (c *Core) GetTUNIfName() string { - return c.tun.iface.Name() + return c.router.tun.iface.Name() } // Gets the current TUN/TAP interface MTU. func (c *Core) GetTUNIfMTU() int { - return c.tun.mtu + return c.router.tun.mtu } diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index e463518..e5bceee 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -68,11 +68,11 @@ func (c *Core) DEBUG_getEncryptionPublicKey() boxPubKey { } func (c *Core) DEBUG_getSend() chan<- []byte { - return c.tun.send + return c.router.tun.send } func (c *Core) DEBUG_getRecv() <-chan []byte { - return c.tun.recv + return c.router.tun.recv } // Peer @@ -304,18 +304,18 @@ func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) { addr := c.DEBUG_getAddr() straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address_prefix)) if ifname != "none" { - err := c.tun.setup(ifname, iftapmode, straddr, mtu) + err := c.router.tun.setup(ifname, iftapmode, straddr, mtu) if err != nil { panic(err) } - c.log.Println("Setup TUN/TAP:", c.tun.iface.Name(), straddr) - go func() { panic(c.tun.read()) }() + c.log.Println("Setup TUN/TAP:", c.router.tun.iface.Name(), straddr) + go func() { panic(c.router.tun.read()) }() } - go func() { panic(c.tun.write()) }() + go func() { panic(c.router.tun.write()) }() } func (c *Core) DEBUG_stopTun() { - c.tun.close() + c.router.tun.close() } //////////////////////////////////////////////////////////////////////////////// @@ -546,7 +546,7 @@ func DEBUG_simLinkPeers(p, q *peer) { } func (c *Core) DEBUG_simFixMTU() { - c.tun.mtu = 65535 + c.router.tun.mtu = 65535 } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go index 957b192..6e9a265 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/yggdrasil/icmpv6.go @@ -24,7 +24,7 @@ type macAddress [6]byte const len_ETHER = 14 type icmpv6 struct { - tun *tunDevice + tun *tunAdapter mylladdr net.IP mymac macAddress peermacs map[address]neighbor @@ -57,7 +57,7 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // Initialises the ICMPv6 module by assigning our link-local IPv6 address and // our MAC address. ICMPv6 messages will always appear to originate from these // addresses. -func (i *icmpv6) init(t *tunDevice) { +func (i *icmpv6) init(t *tunAdapter) { i.tun = t i.peermacs = make(map[address]neighbor) diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go new file mode 100644 index 0000000..2146b27 --- /dev/null +++ b/src/yggdrasil/nodeinfo.go @@ -0,0 +1,175 @@ +package yggdrasil + +import ( + "encoding/json" + "errors" + "runtime" + "sync" + "time" +) + +type nodeinfo struct { + core *Core + myNodeInfo nodeinfoPayload + myNodeInfoMutex sync.RWMutex + callbacks map[boxPubKey]nodeinfoCallback + callbacksMutex sync.Mutex + cache map[boxPubKey]nodeinfoCached + cacheMutex sync.RWMutex +} + +type nodeinfoPayload []byte + +type nodeinfoCached struct { + payload nodeinfoPayload + created time.Time +} + +type nodeinfoCallback struct { + call func(nodeinfo *nodeinfoPayload) + created time.Time +} + +// Represents a session nodeinfo packet. +type nodeinfoReqRes struct { + SendPermPub boxPubKey // Sender's permanent key + SendCoords []byte // Sender's coords + IsResponse bool + NodeInfo nodeinfoPayload +} + +// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep +// the cache/callback maps clean of stale entries +func (m *nodeinfo) init(core *Core) { + m.core = core + m.callbacks = make(map[boxPubKey]nodeinfoCallback) + m.cache = make(map[boxPubKey]nodeinfoCached) + + go func() { + for { + m.callbacksMutex.Lock() + for boxPubKey, callback := range m.callbacks { + if time.Since(callback.created) > time.Minute { + delete(m.callbacks, boxPubKey) + } + } + m.callbacksMutex.Unlock() + m.cacheMutex.Lock() + for boxPubKey, cache := range m.cache { + if time.Since(cache.created) > time.Hour { + delete(m.cache, boxPubKey) + } + } + m.cacheMutex.Unlock() + time.Sleep(time.Second * 30) + } + }() +} + +// Add a callback for a nodeinfo lookup +func (m *nodeinfo) addCallback(sender boxPubKey, call func(nodeinfo *nodeinfoPayload)) { + m.callbacksMutex.Lock() + defer m.callbacksMutex.Unlock() + m.callbacks[sender] = nodeinfoCallback{ + created: time.Now(), + call: call, + } +} + +// Handles the callback, if there is one +func (m *nodeinfo) callback(sender boxPubKey, nodeinfo nodeinfoPayload) { + m.callbacksMutex.Lock() + defer m.callbacksMutex.Unlock() + if callback, ok := m.callbacks[sender]; ok { + callback.call(&nodeinfo) + delete(m.callbacks, sender) + } +} + +// Get the current node's nodeinfo +func (m *nodeinfo) getNodeInfo() nodeinfoPayload { + m.myNodeInfoMutex.RLock() + defer m.myNodeInfoMutex.RUnlock() + return m.myNodeInfo +} + +// Set the current node's nodeinfo +func (m *nodeinfo) setNodeInfo(given interface{}) error { + m.myNodeInfoMutex.Lock() + defer m.myNodeInfoMutex.Unlock() + newnodeinfo := map[string]interface{}{ + "buildname": GetBuildName(), + "buildversion": GetBuildVersion(), + "buildplatform": runtime.GOOS, + "buildarch": runtime.GOARCH, + } + if nodeinfomap, ok := given.(map[string]interface{}); ok { + for key, value := range nodeinfomap { + if _, ok := newnodeinfo[key]; ok { + continue + } + newnodeinfo[key] = value + } + } + if newjson, err := json.Marshal(newnodeinfo); err == nil { + if len(newjson) > 16384 { + return errors.New("NodeInfo exceeds max length of 16384 bytes") + } + m.myNodeInfo = newjson + return nil + } else { + return err + } +} + +// Add nodeinfo into the cache for a node +func (m *nodeinfo) addCachedNodeInfo(key boxPubKey, payload nodeinfoPayload) { + m.cacheMutex.Lock() + defer m.cacheMutex.Unlock() + m.cache[key] = nodeinfoCached{ + created: time.Now(), + payload: payload, + } +} + +// Get a nodeinfo entry from the cache +func (m *nodeinfo) getCachedNodeInfo(key boxPubKey) (nodeinfoPayload, error) { + m.cacheMutex.RLock() + defer m.cacheMutex.RUnlock() + if nodeinfo, ok := m.cache[key]; ok { + return nodeinfo.payload, nil + } + return nodeinfoPayload{}, errors.New("No cache entry found") +} + +// Handles a nodeinfo request/response - called from the router +func (m *nodeinfo) handleNodeInfo(nodeinfo *nodeinfoReqRes) { + if nodeinfo.IsResponse { + m.callback(nodeinfo.SendPermPub, nodeinfo.NodeInfo) + m.addCachedNodeInfo(nodeinfo.SendPermPub, nodeinfo.NodeInfo) + } else { + m.sendNodeInfo(nodeinfo.SendPermPub, nodeinfo.SendCoords, true) + } +} + +// Send nodeinfo request or response - called from the router +func (m *nodeinfo) sendNodeInfo(key boxPubKey, coords []byte, isResponse bool) { + table := m.core.switchTable.table.Load().(lookupTable) + nodeinfo := nodeinfoReqRes{ + SendCoords: table.self.getCoords(), + IsResponse: isResponse, + NodeInfo: m.core.nodeinfo.getNodeInfo(), + } + bs := nodeinfo.encode() + shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key) + payload, nonce := boxSeal(shared, bs, nil) + p := wire_protoTrafficPacket{ + Coords: coords, + ToKey: key, + FromKey: m.core.boxPub, + Nonce: *nonce, + Payload: payload, + } + packet := p.encode() + m.core.router.out(packet) +} diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index e4382a8..d7a0b37 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -79,20 +79,19 @@ type peer struct { bytesSent uint64 // To track bandwidth usage for getPeers bytesRecvd uint64 // To track bandwidth usage for getPeers // BUG: sync/atomic, 32 bit platforms need the above to be the first element - core *Core - port switchPort - box boxPubKey - sig sigPubKey - shared boxSharedKey - linkShared boxSharedKey - endpoint string - friendlyName string - firstSeen time.Time // To track uptime for getPeers - linkOut (chan []byte) // used for protocol traffic (to bypass queues) - doSend (chan struct{}) // tell the linkLoop to send a switchMsg - dinfo (chan *dhtInfo) // used to keep the DHT working - out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes - close func() // Called when a peer is removed, to close the underlying connection, or via admin api + core *Core + port switchPort + box boxPubKey + sig sigPubKey + shared boxSharedKey + linkShared boxSharedKey + endpoint string + firstSeen time.Time // To track uptime for getPeers + linkOut (chan []byte) // used for protocol traffic (to bypass queues) + doSend (chan struct{}) // tell the linkLoop to send a switchMsg + dinfo (chan *dhtInfo) // used to keep the DHT working + out func([]byte) // Set up by whatever created the peers struct, used to send packets to other nodes + close func() // Called when a peer is removed, to close the underlying connection, or via admin api } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number. diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index b482476..1067067 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -39,6 +39,8 @@ type router struct { in <-chan []byte // packets we received from the network, link to peer's "out" out func([]byte) // packets we're sending to the network, link to peer's "in" toRecv chan router_recvPacket // packets to handle via recvPacket() + tun tunAdapter // TUN/TAP adapter + adapters []Adapter // Other adapters recv chan<- []byte // place where the tun pulls received packets from send <-chan []byte // place where the tun puts outgoing packets reset chan struct{} // signal that coords changed (re-init sessions/dht) @@ -75,12 +77,10 @@ func (r *router) init(core *Core) { send := make(chan []byte, 32) r.recv = recv r.send = send - r.core.tun.recv = recv - r.core.tun.send = send r.reset = make(chan struct{}, 1) r.admin = make(chan func(), 32) r.cryptokey.init(r.core) - // go r.mainLoop() + r.tun.init(r.core, send, recv) } // Starts the mainLoop goroutine. @@ -267,25 +267,6 @@ func (r *router) sendPacket(bs []byte) { // Drop packets if the session MTU is 0 - this means that one or other // side probably has their TUN adapter disabled if sinfo.getMTU() == 0 { - // Get the size of the oversized payload, up to a max of 900 bytes - window := 900 - if len(bs) < window { - window = len(bs) - } - - // Create the Destination Unreachable response - ptb := &icmp.DstUnreach{ - Data: bs[:window], - } - - // Create the ICMPv6 response from it - icmpv6Buf, err := r.core.tun.icmpv6.create_icmpv6_tun( - bs[8:24], bs[24:40], - ipv6.ICMPTypeDestinationUnreachable, 1, ptb) - if err == nil { - r.recv <- icmpv6Buf - } - // Don't continue - drop the packet return } @@ -304,7 +285,7 @@ func (r *router) sendPacket(bs []byte) { } // Create the ICMPv6 response from it - icmpv6Buf, err := r.core.tun.icmpv6.create_icmpv6_tun( + icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun( bs[8:24], bs[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb) if err == nil { @@ -428,6 +409,10 @@ func (r *router) handleProto(packet []byte) { r.handlePing(bs, &p.FromKey) case wire_SessionPong: r.handlePong(bs, &p.FromKey) + case wire_NodeInfoRequest: + fallthrough + case wire_NodeInfoResponse: + r.handleNodeInfo(bs, &p.FromKey) case wire_DHTLookupRequest: r.handleDHTReq(bs, &p.FromKey) case wire_DHTLookupResponse: @@ -472,6 +457,16 @@ func (r *router) handleDHTRes(bs []byte, fromKey *boxPubKey) { r.core.dht.handleRes(&res) } +// Decodes nodeinfo request +func (r *router) handleNodeInfo(bs []byte, fromKey *boxPubKey) { + req := nodeinfoReqRes{} + if !req.decode(bs) { + return + } + req.SendPermPub = *fromKey + r.core.nodeinfo.handleNodeInfo(&req) +} + // Passed a function to call. // This will send the function to r.admin and block until it finishes. // It's used by the admin socket to ask the router mainLoop goroutine about information in the session or dht structs, which cannot be read safely from outside that goroutine. diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 92ae262..4f2bedf 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -273,7 +273,7 @@ func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *newBoxNonce() sinfo.theirMTU = 1280 - sinfo.myMTU = uint16(ss.core.tun.mtu) + sinfo.myMTU = uint16(ss.core.router.tun.mtu) now := time.Now() sinfo.time = now sinfo.mtuTime = now diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index e462502..26d32c0 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -17,11 +17,9 @@ const tun_IPv6_HEADER_LENGTH = 40 const tun_ETHER_HEADER_LENGTH = 14 // Represents a running TUN/TAP interface. -type tunDevice struct { - core *Core +type tunAdapter struct { + Adapter icmpv6 icmpv6 - send chan<- []byte - recv <-chan []byte mtu int iface *water.Interface } @@ -36,14 +34,14 @@ func getSupportedMTU(mtu int) int { } // Initialises the TUN/TAP adapter. -func (tun *tunDevice) init(core *Core) { - tun.core = core +func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) { + tun.Adapter.init(core, send, recv) tun.icmpv6.init(tun) } // Starts the setup process for the TUN/TAP adapter, and if successful, starts // the read/write goroutines to handle packets on that interface. -func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int) error { if ifname == "none" { return nil } @@ -75,7 +73,7 @@ func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) // Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP // mode then additional ethernet encapsulation is added for the benefit of the // host operating system. -func (tun *tunDevice) write() error { +func (tun *tunAdapter) write() error { for { data := <-tun.recv if tun.iface == nil { @@ -164,7 +162,7 @@ func (tun *tunDevice) write() error { // is running in TAP mode then the ethernet headers will automatically be // processed and stripped if necessary. If an ICMPv6 packet is found, then // the relevant helper functions in icmpv6.go are called. -func (tun *tunDevice) read() error { +func (tun *tunAdapter) read() error { mtu := tun.mtu if tun.iface.IsTAP() { mtu += tun_ETHER_HEADER_LENGTH @@ -201,7 +199,7 @@ func (tun *tunDevice) read() error { // Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil // process stops. Typically this operation will happen quickly, but on macOS // it can block until a read operation is completed. -func (tun *tunDevice) close() error { +func (tun *tunAdapter) close() error { if tun.iface == nil { return nil } diff --git a/src/yggdrasil/tun_bsd.go b/src/yggdrasil/tun_bsd.go index ca5eaea..620c79d 100644 --- a/src/yggdrasil/tun_bsd.go +++ b/src/yggdrasil/tun_bsd.go @@ -77,7 +77,7 @@ type in6_ifreq_lifetime struct { // a system socket and making syscalls to the kernel. This is not refined though // and often doesn't work (if at all), therefore if a call fails, it resorts // to calling "ifconfig" instead. -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if ifname[:4] == "auto" { ifname = "/dev/tap0" @@ -103,7 +103,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) return tun.setupAddress(addr) } -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { var sfd int var err error diff --git a/src/yggdrasil/tun_darwin.go b/src/yggdrasil/tun_darwin.go index e49ab52..943468e 100644 --- a/src/yggdrasil/tun_darwin.go +++ b/src/yggdrasil/tun_darwin.go @@ -14,7 +14,7 @@ import ( ) // Configures the "utun" adapter with the correct IPv6 address and MTU. -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if iftapmode { tun.core.log.Printf("TAP mode is not supported on this platform, defaulting to TUN") } @@ -62,7 +62,7 @@ type ifreq struct { // Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using // a system socket and making direct syscalls to the kernel. -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { var fd int var err error diff --git a/src/yggdrasil/tun_linux.go b/src/yggdrasil/tun_linux.go index aa9e791..7a7c9cb 100644 --- a/src/yggdrasil/tun_linux.go +++ b/src/yggdrasil/tun_linux.go @@ -13,7 +13,7 @@ import ( ) // Configures the TAP adapter with the correct IPv6 address and MTU. -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if iftapmode { config = water.Config{DeviceType: water.TAP} @@ -48,7 +48,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) // is used to do this, so there is not a hard requirement on "ip" or "ifconfig" // to exist on the system, but this will fail if Netlink is not present in the // kernel (it nearly always is). -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { // Set address var netIF *net.Interface ifces, err := net.Interfaces() diff --git a/src/yggdrasil/tun_other.go b/src/yggdrasil/tun_other.go index 1a3721a..625f9cd 100644 --- a/src/yggdrasil/tun_other.go +++ b/src/yggdrasil/tun_other.go @@ -9,7 +9,7 @@ import water "github.com/yggdrasil-network/water" // Creates the TUN/TAP adapter, if supported by the Water library. Note that // no guarantees are made at this point on an unsupported platform. -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { var config water.Config if iftapmode { config = water.Config{DeviceType: water.TAP} @@ -27,7 +27,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { tun.core.log.Println("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr) return nil } diff --git a/src/yggdrasil/tun_windows.go b/src/yggdrasil/tun_windows.go index d3420df..150a976 100644 --- a/src/yggdrasil/tun_windows.go +++ b/src/yggdrasil/tun_windows.go @@ -13,7 +13,7 @@ import ( // Configures the TAP adapter with the correct IPv6 address and MTU. On Windows // we don't make use of a direct operating system API to do this - we instead // delegate the hard work to "netsh". -func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) error { +func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error { if !iftapmode { tun.core.log.Printf("TUN mode is not supported on this platform, defaulting to TAP") } @@ -65,7 +65,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) } // Sets the MTU of the TAP adapter. -func (tun *tunDevice) setupMTU(mtu int) error { +func (tun *tunAdapter) setupMTU(mtu int) error { // Set MTU cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface", fmt.Sprintf("interface=%s", tun.iface.Name()), @@ -82,7 +82,7 @@ func (tun *tunDevice) setupMTU(mtu int) error { } // Sets the IPv6 address of the TAP adapter. -func (tun *tunDevice) setupAddress(addr string) error { +func (tun *tunAdapter) setupAddress(addr string) error { // Set address cmd := exec.Command("netsh", "interface", "ipv6", "add", "address", fmt.Sprintf("interface=%s", tun.iface.Name()), diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index d05624e..5e87784 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -16,6 +16,8 @@ const ( wire_SessionPong // inside protocol traffic header wire_DHTLookupRequest // inside protocol traffic header wire_DHTLookupResponse // inside protocol traffic header + wire_NodeInfoRequest // inside protocol traffic header + wire_NodeInfoResponse // inside protocol traffic header ) // Calls wire_put_uint64 on a nil slice. @@ -353,6 +355,47 @@ func (p *sessionPing) decode(bs []byte) bool { //////////////////////////////////////////////////////////////////////////////// +// Encodes a nodeinfoReqRes into its wire format. +func (p *nodeinfoReqRes) encode() []byte { + var pTypeVal uint64 + if p.IsResponse { + pTypeVal = wire_NodeInfoResponse + } else { + pTypeVal = wire_NodeInfoRequest + } + bs := wire_encode_uint64(pTypeVal) + bs = wire_put_coords(p.SendCoords, bs) + if pTypeVal == wire_NodeInfoResponse { + bs = append(bs, p.NodeInfo...) + } + return bs +} + +// Decodes an encoded nodeinfoReqRes into the struct, returning true if successful. +func (p *nodeinfoReqRes) decode(bs []byte) bool { + var pType uint64 + switch { + case !wire_chop_uint64(&pType, &bs): + return false + case pType != wire_NodeInfoRequest && pType != wire_NodeInfoResponse: + return false + case !wire_chop_coords(&p.SendCoords, &bs): + return false + } + if p.IsResponse = pType == wire_NodeInfoResponse; p.IsResponse { + if len(bs) == 0 { + return false + } + p.NodeInfo = make(nodeinfoPayload, len(bs)) + if !wire_chop_slice(p.NodeInfo[:], &bs) { + return false + } + } + return true +} + +//////////////////////////////////////////////////////////////////////////////// + // Encodes a dhtReq into its wire format. func (r *dhtReq) encode() []byte { coords := wire_encode_coords(r.Coords)