diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index ddb02818..39aceb98 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -3,6 +3,7 @@ package yggdrasil import "net" import "os" import "bytes" +import "encoding/hex" import "errors" import "fmt" import "net/url" @@ -104,6 +105,23 @@ func (a *admin) init(c *Core, listenaddr string) { *out = []byte(a.printInfos([]admin_nodeInfo{info})) } }) + a.addHandler("getAllowedBoxPubs", nil, func(out *[]byte, _ ...string) { + *out = []byte(a.getAllowedBoxPubs()) + }) + a.addHandler("addAllowedBoxPub", []string{""}, func(out *[]byte, saddr ...string) { + if a.addAllowedBoxPub(saddr[0]) == nil { + *out = []byte("Adding key: " + saddr[0] + "\n") + } else { + *out = []byte("Failed to add key: " + saddr[0] + "\n") + } + }) + a.addHandler("removeAllowedBoxPub", []string{""}, func(out *[]byte, sport ...string) { + if a.removeAllowedBoxPub(sport[0]) == nil { + *out = []byte("Removing key: " + sport[0] + "\n") + } else { + *out = []byte("Failed to remove key: " + sport[0] + "\n") + } + }) go a.listen() } @@ -347,6 +365,36 @@ func (a *admin) getData_getSessions() []admin_nodeInfo { return infos } +func (a *admin) getAllowedBoxPubs() string { + pubs := a.core.peers.getAllowedBoxPubs() + var out []string + for _, pub := range pubs { + out = append(out, hex.EncodeToString(pub[:])) + } + out = append(out, "") + return strings.Join(out, "\n") +} + +func (a *admin) addAllowedBoxPub(bstr string) (err error) { + boxBytes, err := hex.DecodeString(bstr) + if err == nil { + var box boxPubKey + copy(box[:], boxBytes) + a.core.peers.addAllowedBoxPub(&box) + } + return +} + +func (a *admin) removeAllowedBoxPub(bstr string) (err error) { + boxBytes, err := hex.DecodeString(bstr) + if err == nil { + var box boxPubKey + copy(box[:], boxBytes) + a.core.peers.removeAllowedBoxPub(&box) + } + return +} + func (a *admin) getResponse_dot() []byte { self := a.getData_getSelf().asMap() myAddr := self["IP"] diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 30c8d23d..f2d25f44 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -2,19 +2,20 @@ package config // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { - Listen string - AdminListen string - Peers []string - BoxPub string - BoxPriv string - SigPub string - SigPriv string - Multicast bool - LinkLocal string - IfName string - IfTAPMode bool - IfMTU int - Net NetConfig + Listen string + AdminListen string + Peers []string + AllowedBoxPubs []string + BoxPub string + BoxPriv string + SigPub string + SigPriv string + Multicast bool + LinkLocal string + IfName string + IfTAPMode bool + IfMTU int + Net NetConfig } // NetConfig defines network/proxy related configuration values diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index be0c6aec..838feef0 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -44,6 +44,7 @@ func (c *Core) init(bpub *boxPubKey, c.log = log.New(ioutil.Discard, "", 0) c.boxPub, c.boxPriv = *bpub, *bpriv c.sigPub, c.sigPriv = *spub, *spriv + c.admin.core = c c.sigs.init() c.searches.init(c) c.dht.init(c) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index f87bb75f..32c52e3e 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -397,6 +397,13 @@ func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) { c.ifceExpr = expr } +func (c *Core) DEBUG_addAllowedBoxPub(boxStr string) { + err := c.admin.addAllowedBoxPub(boxStr) + if err != nil { + panic(err) + } +} + //////////////////////////////////////////////////////////////////////////////// func DEBUG_simLinkPeers(p, q *peer) { diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index ea05728e..bec91352 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -34,6 +34,8 @@ type peers struct { mutex sync.Mutex // Synchronize writes to atomic ports atomic.Value //map[Port]*peer, use CoW semantics //ports map[Port]*peer + authMutex sync.RWMutex + allowedBoxPubs map[boxPubKey]struct{} } func (ps *peers) init(c *Core) { @@ -41,6 +43,36 @@ func (ps *peers) init(c *Core) { defer ps.mutex.Unlock() ps.putPorts(make(map[switchPort]*peer)) ps.core = c + ps.allowedBoxPubs = make(map[boxPubKey]struct{}) +} + +func (ps *peers) isAllowedBoxPub(box *boxPubKey) bool { + ps.authMutex.RLock() + defer ps.authMutex.RUnlock() + _, isIn := ps.allowedBoxPubs[*box] + return isIn || len(ps.allowedBoxPubs) == 0 +} + +func (ps *peers) addAllowedBoxPub(box *boxPubKey) { + ps.authMutex.Lock() + defer ps.authMutex.Unlock() + ps.allowedBoxPubs[*box] = struct{}{} +} + +func (ps *peers) removeAllowedBoxPub(box *boxPubKey) { + ps.authMutex.Lock() + defer ps.authMutex.Unlock() + delete(ps.allowedBoxPubs, *box) +} + +func (ps *peers) getAllowedBoxPubs() []boxPubKey { + ps.authMutex.RLock() + defer ps.authMutex.RUnlock() + keys := make([]boxPubKey, 0, len(ps.allowedBoxPubs)) + for key := range ps.allowedBoxPubs { + keys = append(keys, key) + } + return keys } func (ps *peers) getPorts() map[switchPort]*peer { @@ -75,6 +107,8 @@ type peer struct { throttle uint8 // Called when a peer is removed, to close the underlying connection, or via admin api close func() + // To allow the peer to call close if idle for too long + lastAnc time.Time } const peer_Throttle = 1 @@ -99,14 +133,11 @@ func (p *peer) updateBandwidth(bytes int, duration time.Duration) { func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey) *peer { - //in <-chan []byte, - //out chan<- []byte) *peer { p := peer{box: *box, - sig: *sig, - shared: *getSharedKey(&ps.core.boxPriv, box), - //in: in, - //out: out, - core: ps.core} + sig: *sig, + shared: *getSharedKey(&ps.core.boxPriv, box), + lastAnc: time.Now(), + core: ps.core} ps.mutex.Lock() defer ps.mutex.Unlock() oldPorts := ps.getPorts() @@ -158,31 +189,33 @@ func (p *peer) linkLoop(in <-chan []byte) { } p.handleLinkTraffic(packet) case <-ticker.C: - { - p.throttle = 0 - if p.port == 0 { - continue - } // Don't send announces on selfInterface - p.myMsg, p.mySigs = p.core.switchTable.createMessage(p.port) - var update bool - switch { - case p.msgAnc == nil: - update = true - case lastRSeq != p.msgAnc.seq: - update = true - case p.msgAnc.rseq != p.myMsg.seq: - update = true - case counter%4 == 0: - update = true - } - if update { - if p.msgAnc != nil { - lastRSeq = p.msgAnc.seq - } - p.sendSwitchAnnounce() - } - counter = (counter + 1) % 4 + if time.Since(p.lastAnc) > 16*time.Second && p.close != nil { + // Seems to have timed out, try to trigger a close + p.close() } + p.throttle = 0 + if p.port == 0 { + continue + } // Don't send announces on selfInterface + p.myMsg, p.mySigs = p.core.switchTable.createMessage(p.port) + var update bool + switch { + case p.msgAnc == nil: + update = true + case lastRSeq != p.msgAnc.seq: + update = true + case p.msgAnc.rseq != p.myMsg.seq: + update = true + case counter%4 == 0: + update = true + } + if update { + if p.msgAnc != nil { + lastRSeq = p.msgAnc.seq + } + p.sendSwitchAnnounce() + } + counter = (counter + 1) % 4 } } } @@ -210,6 +243,10 @@ func (p *peer) handlePacket(packet []byte, linkIn chan<- []byte) { } func (p *peer) handleTraffic(packet []byte, pTypeLen int) { + if p.port != 0 && p.msgAnc == nil { + // Drop traffic until the peer manages to send us at least one anc + return + } ttl, ttlLen := wire_decode_uint64(packet[pTypeLen:]) ttlBegin := pTypeLen ttlEnd := pTypeLen + ttlLen @@ -292,6 +329,7 @@ func (p *peer) handleSwitchAnnounce(packet []byte) { } p.msgAnc = &anc p.processSwitchMessage() + p.lastAnc = time.Now() } func (p *peer) requestHop(hop uint64) { diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 3a4e9fb4..7aafca31 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -62,7 +62,7 @@ func (iface *tcpInterface) listener() { if err != nil { panic(err) } - go iface.handler(sock) + go iface.handler(sock, true) } } @@ -81,7 +81,7 @@ func (iface *tcpInterface) callWithConn(conn net.Conn) { delete(iface.calls, raddr) iface.mutex.Unlock() }() - iface.handler(conn) + iface.handler(conn, false) } }() } @@ -106,12 +106,12 @@ func (iface *tcpInterface) call(saddr string) { if err != nil { return } - iface.handler(conn) + iface.handler(conn, false) } }() } -func (iface *tcpInterface) handler(sock net.Conn) { +func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { defer sock.Close() // Get our keys keys := []byte{} @@ -150,6 +150,15 @@ func (iface *tcpInterface) handler(sock net.Conn) { if equiv(info.sig[:], iface.core.sigPub[:]) { return } + // Check if we're authorized to connect to this key / IP + if incoming && !iface.core.peers.isAllowedBoxPub(&info.box) { + // Allow unauthorized peers if they're link-local + raddrStr, _, _ := net.SplitHostPort(sock.RemoteAddr().String()) + raddr := net.ParseIP(raddrStr) + if !raddr.IsLinkLocalUnicast() { + return + } + } // Check if we already have a connection to this node, close and block if yes info.localAddr, _, _ = net.SplitHostPort(sock.LocalAddr().String()) info.remoteAddr, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) diff --git a/src/yggdrasil/udp.go b/src/yggdrasil/udp.go index fffda935..03663b6a 100644 --- a/src/yggdrasil/udp.go +++ b/src/yggdrasil/udp.go @@ -204,6 +204,14 @@ func (iface *udpInterface) handleKeys(msg []byte, addr connAddr) { iface.mutex.RUnlock() if !isIn { udpAddr := addr.toUDPAddr() + // Check if we're authorized to connect to this key / IP + // TODO monitor and always allow outgoing connections + if !iface.core.peers.isAllowedBoxPub(&ks.box) { + // Allow unauthorized peers if they're link-local + if !udpAddr.IP.IsLinkLocalUnicast() { + return + } + } themNodeID := getNodeID(&ks.box) themAddr := address_addrForNodeID(themNodeID) themAddrString := net.IP(themAddr[:]).String() diff --git a/yggdrasil.go b/yggdrasil.go index 30c6a798..5e5ba9ce 100644 --- a/yggdrasil.go +++ b/yggdrasil.go @@ -66,6 +66,10 @@ func (n *node) init(cfg *nodeConfig, logger *log.Logger) { logger.Println("Starting admin socket...") n.core.DEBUG_setupAndStartAdminInterface(cfg.AdminListen) logger.Println("Started admin socket") + for _, pBoxStr := range cfg.AllowedBoxPubs { + n.core.DEBUG_addAllowedBoxPub(pBoxStr) + } + go func() { if len(cfg.Peers) == 0 { return @@ -97,6 +101,7 @@ func generateConfig(isAutoconf bool) *nodeConfig { cfg.SigPub = hex.EncodeToString(spub[:]) cfg.SigPriv = hex.EncodeToString(spriv[:]) cfg.Peers = []string{} + cfg.AllowedBoxPubs = []string{} cfg.Multicast = true cfg.LinkLocal = "" cfg.IfName = core.DEBUG_GetTUNDefaultIfName()