From 8844dedb8a780dcf96c79e7e5dc99e2a8bdaa12c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Oct 2018 11:55:01 +0100 Subject: [PATCH 001/145] Add create-pkg.sh for creating macOS installers --- .circleci/config.yml | 7 +++ contrib/macos/create-pkg.sh | 107 ++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100755 contrib/macos/create-pkg.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 036c97e3..fa5ebcac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,6 +50,13 @@ jobs: GOOS=darwin GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64; GOOS=darwin GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-i386; + - run: + name: Build for macOS (.pkg format) + command: | + rm -rf {yggdrasil,yggdrasilctl} + GOOS=darwin GOARCH=amd64 ./build && PKGARCH=amd64 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/ + GOOS=darwin GOARCH=386 ./build && PKGARCH=i386 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/ + - run: name: Build for OpenBSD command: | diff --git a/contrib/macos/create-pkg.sh b/contrib/macos/create-pkg.sh new file mode 100755 index 00000000..59647364 --- /dev/null +++ b/contrib/macos/create-pkg.sh @@ -0,0 +1,107 @@ +#!/bin/sh + +# Check if xar and mkbom are available +command -v xar >/dev/null 2>&1 || ( + echo "Building xar" + sudo apt-get install libxml2-dev libssl1.0-dev zlib1g-dev -y + mkdir -p /tmp/xar && cd /tmp/xar + git clone https://github.com/mackyle/xar && cd xar/xar + (sh autogen.sh && make && sudo make install) || (echo "Failed to build xar"; exit 1) +) +command -v mkbom >/dev/null 2>&1 || ( + echo "Building mkbom" + mkdir -p /tmp/mkbom && cd /tmp/mkbom + git clone https://github.com/hogliux/bomutils && cd bomutils + sudo make install || (echo "Failed to build mkbom"; exit 1) +) + +# Check if we can find the files we need - they should +# exist if you are running this script from the root of +# the yggdrasil-go repo and you have ran ./build +test -f yggdrasil || (echo "yggdrasil binary not found"; exit 1) +test -f yggdrasilctl || (echo "yggdrasilctl binary not found"; exit 1) +test -f contrib/macos/yggdrasil.plist || (echo "contrib/macos/yggdrasil.plist not found"; exit 1) +test -f contrib/semver/version.sh || (echo "contrib/semver/version.sh not found"; exit 1) + +# Delete the pkgbuild folder if it already exists +test -d pkgbuild && rm -rf pkgbuild + +# Create our folder structure +mkdir -p pkgbuild/scripts +mkdir -p pkgbuild/flat/base.pkg +mkdir -p pkgbuild/flat/Resources/en.lproj +mkdir -p pkgbuild/root/usr/local/bin +mkdir -p pkgbuild/root/Library/LaunchDaemons + +# Copy package contents into the pkgbuild root +cp yggdrasil pkgbuild/root/usr/local/bin +cp yggdrasilctl pkgbuild/root/usr/local/bin +cp contrib/macos/yggdrasil.plist pkgbuild/root/Library/LaunchDaemons + +# Create the postinstall script +cat > pkgbuild/scripts/postinstall << EOF +#!/bin/sh +test -f /etc/yggdrasil.conf || /usr/local/bin/yggdrasil -genconf > /etc/yggdrasil.conf +test -f /Library/LaunchDaemons/yggdrasil.plist && launchctl unload /Library/LaunchDaemons/yggdrasil.plist +launchctl load /Library/LaunchDaemons/yggdrasil.plist +EOF + +# Set execution permissions +chmod +x pkgbuild/scripts/postinstall +chmod +x pkgbuild/root/usr/local/bin/yggdrasil +chmod +x pkgbuild/root/usr/local/bin/yggdrasilctl + +# Pack payload and scripts +( cd pkgbuild/scripts && find . | cpio -o --format odc --owner 0:80 | gzip -c ) > pkgbuild/flat/base.pkg/Scripts +( cd pkgbuild/root && find . | cpio -o --format odc --owner 0:80 | gzip -c ) > pkgbuild/flat/base.pkg/Payload + +# Work out metadata for the package info +PKGNAME=$(sh contrib/semver/name.sh) +PKGVERSION=$(sh contrib/semver/version.sh | cut -c 2-) +PKGARCH=${PKGARCH-amd64} +PAYLOADSIZE=$(( $(wc -c pkgbuild/flat/base.pkg/Payload | awk '{ print $1 }') / 1024 )) + +# Create the PackageInfo file +cat > pkgbuild/flat/base.pkg/PackageInfo << EOF + + + + + + +EOF + +# Create the BOM +( cd pkgbuild && mkbom root flat/base.pkg/Bom ) + +# Create the Distribution file +cat > pkgbuild/flat/Distribution << EOF + + + Yggdrasil ${PKGVERSION} + + + + + + + + + + + #base.pkg + +EOF + +# Finally pack the .pkg +( cd pkgbuild/flat && xar --compression none -cf "../../${PKGNAME}-${PKGVERSION}-macos-${PKGARCH}.pkg" * ) From 9f129bc7b085309c58f67f2dcc2ef5fb0bebc676 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 17 Oct 2018 12:48:54 +0100 Subject: [PATCH 002/145] Backup and normalise config if needed --- contrib/macos/create-pkg.sh | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/contrib/macos/create-pkg.sh b/contrib/macos/create-pkg.sh index 59647364..167184e3 100755 --- a/contrib/macos/create-pkg.sh +++ b/contrib/macos/create-pkg.sh @@ -41,8 +41,23 @@ cp contrib/macos/yggdrasil.plist pkgbuild/root/Library/LaunchDaemons # Create the postinstall script cat > pkgbuild/scripts/postinstall << EOF #!/bin/sh -test -f /etc/yggdrasil.conf || /usr/local/bin/yggdrasil -genconf > /etc/yggdrasil.conf -test -f /Library/LaunchDaemons/yggdrasil.plist && launchctl unload /Library/LaunchDaemons/yggdrasil.plist + +# Normalise the config if it exists, generate it if it doesn't +if [ -f /etc/yggdrasil.conf ]; +then + mkdir -p /Library/Preferences/Yggdrasil + echo "Backing up configuration file to /Library/Preferences/Yggdrasil/yggdrasil.conf.`date +%Y%m%d`" + cp /etc/yggdrasil.conf /Library/Preferences/Yggdrasil/yggdrasil.conf.`date +%Y%m%d` + echo "Normalising /etc/yggdrasil.conf" + /usr/local/bin/yggdrasil -useconffile /Library/Preferences/Yggdrasil/yggdrasil.conf.`date +%Y%m%d` -normaliseconf > /etc/yggdrasil.conf +else + /usr/local/bin/yggdrasil -genconf > /etc/yggdrasil.conf +fi + +# Unload existing Yggdrasil launchd service, if possible +test -f /Library/LaunchDaemons/yggdrasil.plist && (launchctl unload /Library/LaunchDaemons/yggdrasil.plist || true) + +# Load Yggdrasil launchd service and start Yggdrasil launchctl load /Library/LaunchDaemons/yggdrasil.plist EOF @@ -78,7 +93,7 @@ EOF cat > pkgbuild/flat/Distribution << EOF - Yggdrasil ${PKGVERSION} + Yggdrasil (${PKGNAME}-${PKGVERSION}) From 03a88fe30420b616022f0fd96495a126922cb572 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 14:48:07 -0500 Subject: [PATCH 003/145] Try using a chord-like DHT instead of a kad-like one, work in progress, but it compiles at least --- misc/sim/treesim.go | 2 +- src/yggdrasil/admin.go | 36 +-- src/yggdrasil/debug.go | 2 + src/yggdrasil/dht.go | 637 ++++++++++----------------------------- src/yggdrasil/router.go | 3 +- src/yggdrasil/search.go | 10 +- src/yggdrasil/session.go | 2 +- 7 files changed, 182 insertions(+), 510 deletions(-) diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index c5c67e79..793ef219 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -386,7 +386,7 @@ func (n *Node) startTCP(listen string) { } func (n *Node) connectTCP(remoteAddr string) { - n.core.AddPeer(remoteAddr) + n.core.AddPeer(remoteAddr, remoteAddr) } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 9d3866f8..2446be51 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -554,26 +554,28 @@ func (a *admin) getData_getSwitchQueues() admin_nodeInfo { // getData_getDHT returns info from Core.dht for an admin response. func (a *admin) getData_getDHT() []admin_nodeInfo { var infos []admin_nodeInfo - now := time.Now() getDHT := func() { - for i := 0; i < a.core.dht.nBuckets(); i++ { - b := a.core.dht.getBucket(i) - getInfo := func(vs []*dhtInfo, isPeer bool) { - for _, v := range vs { - addr := *address_addrForNodeID(v.getNodeID()) - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"coords", fmt.Sprint(v.coords)}, - {"bucket", i}, - {"peer_only", isPeer}, - {"last_seen", int(now.Sub(v.recv).Seconds())}, + /* TODO fix this + now := time.Now() + for i := 0; i < a.core.dht.nBuckets(); i++ { + b := a.core.dht.getBucket(i) + getInfo := func(vs []*dhtInfo, isPeer bool) { + for _, v := range vs { + addr := *address_addrForNodeID(v.getNodeID()) + info := admin_nodeInfo{ + {"ip", net.IP(addr[:]).String()}, + {"coords", fmt.Sprint(v.coords)}, + {"bucket", i}, + {"peer_only", isPeer}, + {"last_seen", int(now.Sub(v.recv).Seconds())}, + } + infos = append(infos, info) + } } - infos = append(infos, info) + getInfo(b.other, false) + getInfo(b.peers, true) } - } - getInfo(b.other, false) - getInfo(b.peers, true) - } + */ } a.core.router.doAdmin(getDHT) return infos diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 892529b6..f614fece 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -230,11 +230,13 @@ func DEBUG_wire_encode_coords(coords []byte) []byte { func (c *Core) DEBUG_getDHTSize() int { total := 0 + /* FIXME for bidx := 0; bidx < c.dht.nBuckets(); bidx++ { b := c.dht.getBucket(bidx) total += len(b.peers) total += len(b.other) } + */ return total } diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 955ef839..068a4f37 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -1,38 +1,10 @@ package yggdrasil -/* - -This part has the (kademlia-like) distributed hash table - -It's used to look up coords for a NodeID - -Every node participates in the DHT, and the DHT stores no real keys/values -(Only the peer relationships / lookups are needed) - -This version is intentionally fragile, by being recursive instead of iterative -(it's also not parallel, as a result) -This is to make sure that DHT black holes are visible if they exist -(the iterative parallel approach tends to get around them sometimes) -I haven't seen this get stuck on blackholes, but I also haven't proven it can't -Slight changes *do* make it blackhole hard, bootstrapping isn't an easy problem - -*/ - import ( "sort" "time" ) -// Number of DHT buckets, equal to the number of bits in a NodeID. -// Note that, in practice, nearly all of these will be empty. -const dht_bucket_number = 8 * NodeIDLen - -// Number of nodes to keep in each DHT bucket. -// Additional entries may be kept for peers, for bootstrapping reasons, if they don't already have an entry in the bucket. -const dht_bucket_size = 2 - -// Number of responses to include in a lookup. -// If extras are given, they will be truncated from the response handler to prevent abuse. const dht_lookup_size = 16 // dhtInfo represents everything we know about a node in the DHT. @@ -41,11 +13,11 @@ type dhtInfo struct { nodeID_hidden *NodeID key boxPubKey coords []byte - send time.Time // When we last sent a message - recv time.Time // When we last received a message - pings int // Decide when to drop - throttle time.Duration // Time to wait before pinging a node to bootstrap buckets, increases exponentially from 1 second to 1 minute - bootstrapSend time.Time // The time checked/updated as part of throttle checks + send time.Time // When we last sent a message + recv time.Time // When we last received a message + //pings int // Decide when to drop + //throttle time.Duration // Time to wait before pinging a node to bootstrap buckets, increases exponentially from 1 second to 1 minute + //bootstrapSend time.Time // The time checked/updated as part of throttle checks } // Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. @@ -56,12 +28,6 @@ func (info *dhtInfo) getNodeID() *NodeID { return info.nodeID_hidden } -// The nodes we known in a bucket (a region of keyspace with a matching prefix of some length). -type bucket struct { - peers []*dhtInfo - other []*dhtInfo -} - // Request for a node to do a lookup. // Includes our key and coords so they can send a response back, and the destination NodeID we want to ask about. type dhtReq struct { @@ -74,42 +40,94 @@ type dhtReq struct { // Includes the key and coords of the node that's responding, and the destination they were asked about. // The main part is Infos []*dhtInfo, the lookup response. type dhtRes struct { - Key boxPubKey // key to respond to - Coords []byte // coords to respond to + Key boxPubKey // key of the sender + Coords []byte // coords of the sender Dest NodeID Infos []*dhtInfo // response } -// Information about a node, either taken from our table or from a lookup response. -// Used to schedule pings at a later time (they're throttled to 1/second for background maintenance traffic). -type dht_rumor struct { - info *dhtInfo - target *NodeID -} - // The main DHT struct. -// Includes a slice of buckets, to organize known nodes based on their region of keyspace. -// Also includes information about outstanding DHT requests and the rumor mill of nodes to ping at some point. type dht struct { - core *Core - nodeID NodeID - buckets_hidden [dht_bucket_number]bucket // Extra is for the self-bucket - peers chan *dhtInfo // other goroutines put incoming dht updates here - reqs map[boxPubKey]map[NodeID]time.Time - offset int - rumorMill []dht_rumor + core *Core + nodeID NodeID + table map[NodeID]*dhtInfo + peers chan *dhtInfo // other goroutines put incoming dht updates here + reqs map[boxPubKey]map[NodeID]time.Time + //rumorMill []dht_rumor } -// Initializes the DHT. func (t *dht) init(c *Core) { + // TODO t.core = c t.nodeID = *t.core.GetNodeID() t.peers = make(chan *dhtInfo, 1024) - t.reqs = make(map[boxPubKey]map[NodeID]time.Time) + t.reset() +} + +func (t *dht) reset() { + t.table = make(map[NodeID]*dhtInfo) +} + +func (t *dht) lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { + return nil + var results []*dhtInfo + var successor *dhtInfo + sTarget := t.nodeID.next() + for infoID, info := range t.table { + if allowWorse || dht_ordered(&t.nodeID, &infoID, nodeID) { + results = append(results, info) + } else { + if successor == nil || dht_ordered(&sTarget, &infoID, successor.getNodeID()) { + successor = info + } + } + } + sort.SliceStable(results, func(i, j int) bool { + return dht_ordered(results[j].getNodeID(), results[i].getNodeID(), nodeID) + }) + if successor != nil { + results = append([]*dhtInfo{successor}, results...) + } + if len(results) > dht_lookup_size { + results = results[:dht_lookup_size] + } + return results +} + +// Insert into table, preserving the time we last sent a packet if the node was already in the table, otherwise setting that time to now +func (t *dht) insert(info *dhtInfo) { + info.recv = time.Now() + if oldInfo, isIn := t.table[*info.getNodeID()]; isIn { + info.send = oldInfo.send + } else { + info.send = info.recv + } + t.table[*info.getNodeID()] = info +} + +// Return true if first/second/third are (partially) ordered correctly +// FIXME? maybe total ordering makes more sense +func dht_ordered(first, second, third *NodeID) bool { + var ordered bool + for idx := 0; idx < NodeIDLen; idx++ { + f, s, t := first[idx], second[idx], third[idx] + switch { + case f == s && s == t: + continue + case f <= s && s <= t: + ordered = true // nothing wrapped around 0 + case t <= f && f <= s: + ordered = true // 0 is between second and third + case s <= t && t <= f: + ordered = true // 0 is between first and second + } + break + } + return ordered } // Reads a request, performs a lookup, and responds. -// If the node that sent the request isn't in our DHT, but should be, then we add them. +// Update info about the node that sent the request. func (t *dht) handleReq(req *dhtReq) { // Send them what they asked for loc := t.core.switchTable.getLocator() @@ -129,11 +147,50 @@ func (t *dht) handleReq(req *dhtReq) { // For bootstrapping to work, we need to add these nodes to the table // Using insertIfNew, they can lie about coords, but searches will route around them // Using the mill would mean trying to block off the mill becomes an attack vector - t.insertIfNew(&info, false) + t.insert(&info) +} + +// Sends a lookup response to the specified node. +func (t *dht) sendRes(res *dhtRes, req *dhtReq) { + // Send a reply for a dhtReq + bs := res.encode() + shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &req.Key) + payload, nonce := boxSeal(shared, bs, nil) + p := wire_protoTrafficPacket{ + Coords: req.Coords, + ToKey: req.Key, + FromKey: t.core.boxPub, + Nonce: *nonce, + Payload: payload, + } + packet := p.encode() + t.core.router.out(packet) +} + +// Returns nodeID + 1 +func (nodeID NodeID) next() NodeID { + for idx := len(nodeID); idx >= 0; idx-- { + nodeID[idx] += 1 + if nodeID[idx] != 0 { + break + } + } + return nodeID +} + +// Returns nodeID - 1 +func (nodeID NodeID) prev() NodeID { + for idx := len(nodeID); idx >= 0; idx-- { + nodeID[idx] -= 1 + if nodeID[idx] != 0xff { + break + } + } + return nodeID } // Reads a lookup response, checks that we had sent a matching request, and processes the response info. -// This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and adding the response info to the rumor mill. +// This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses func (t *dht) handleRes(res *dhtRes) { t.core.searches.handleDHTRes(res) reqs, isIn := t.reqs[res.Key] @@ -145,223 +202,36 @@ func (t *dht) handleRes(res *dhtRes) { return } delete(reqs, res.Dest) - now := time.Now() rinfo := dhtInfo{ - key: res.Key, - coords: res.Coords, - send: now, // Technically wrong but should be OK... - recv: now, - throttle: time.Second, - bootstrapSend: now, + key: res.Key, + coords: res.Coords, } - // If they're already in the table, then keep the correct send time - bidx, isOK := t.getBucketIndex(rinfo.getNodeID()) - if !isOK { - return - } - b := t.getBucket(bidx) - for _, oldinfo := range b.peers { - if oldinfo.key == rinfo.key { - rinfo.send = oldinfo.send - rinfo.throttle = oldinfo.throttle - rinfo.bootstrapSend = oldinfo.bootstrapSend + t.insert(&rinfo) // Or at the end, after checking successor/predecessor? + var successor *dhtInfo + var predecessor *dhtInfo + for infoID, info := range t.table { + // Get current successor and predecessor + if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { + successor = info } - } - for _, oldinfo := range b.other { - if oldinfo.key == rinfo.key { - rinfo.send = oldinfo.send - rinfo.throttle = oldinfo.throttle - rinfo.bootstrapSend = oldinfo.bootstrapSend + if predecessor == nil || dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { + predecessor = info } } - // Insert into table - t.insert(&rinfo, false) - if res.Dest == *rinfo.getNodeID() { - return - } // No infinite recursions - if len(res.Infos) > dht_lookup_size { - // Ignore any "extra" lookup results - res.Infos = res.Infos[:dht_lookup_size] - } for _, info := range res.Infos { - if dht_firstCloserThanThird(info.getNodeID(), &res.Dest, rinfo.getNodeID()) { - t.addToMill(info, info.getNodeID()) - } - } -} - -// Does a DHT lookup and returns the results, sorted in ascending order of distance from the destination. -func (t *dht) lookup(nodeID *NodeID, allowCloser bool) []*dhtInfo { - // FIXME this allocates a bunch, sorts, and keeps the part it likes - // It would be better to only track the part it likes to begin with - addInfos := func(res []*dhtInfo, infos []*dhtInfo) []*dhtInfo { - for _, info := range infos { - if info == nil { - panic("Should never happen!") - } - if allowCloser || dht_firstCloserThanThird(info.getNodeID(), nodeID, &t.nodeID) { - res = append(res, info) - } - } - return res - } - var res []*dhtInfo - for bidx := 0; bidx < t.nBuckets(); bidx++ { - b := t.getBucket(bidx) - res = addInfos(res, b.peers) - res = addInfos(res, b.other) - } - doSort := func(infos []*dhtInfo) { - less := func(i, j int) bool { - return dht_firstCloserThanThird(infos[i].getNodeID(), - nodeID, - infos[j].getNodeID()) - } - sort.SliceStable(infos, less) - } - doSort(res) - if len(res) > dht_lookup_size { - res = res[:dht_lookup_size] - } - return res -} - -// Gets the bucket for a specified matching prefix length. -func (t *dht) getBucket(bidx int) *bucket { - return &t.buckets_hidden[bidx] -} - -// Lists the number of buckets. -func (t *dht) nBuckets() int { - return len(t.buckets_hidden) -} - -// Inserts a node into the DHT if they meet certain requirements. -// In particular, they must either be a peer that's not already in the DHT, or else be someone we should insert into the DHT (see: shouldInsert). -func (t *dht) insertIfNew(info *dhtInfo, isPeer bool) { - // Insert if no "other" entry already exists - nodeID := info.getNodeID() - bidx, isOK := t.getBucketIndex(nodeID) - if !isOK { - return - } - b := t.getBucket(bidx) - if (isPeer && !b.containsOther(info)) || t.shouldInsert(info) { - // We've never heard this node before - // TODO is there a better time than "now" to set send/recv to? - // (Is there another "natural" choice that bootstraps faster?) - info.send = time.Now() - info.recv = info.send - t.insert(info, isPeer) - } -} - -// Adds a node to the DHT, possibly removing another node in the process. -func (t *dht) insert(info *dhtInfo, isPeer bool) { - // First update the time on this info - info.recv = time.Now() - // Get the bucket for this node - nodeID := info.getNodeID() - bidx, isOK := t.getBucketIndex(nodeID) - if !isOK { - return - } - b := t.getBucket(bidx) - if !isPeer && !b.containsOther(info) { - // This is a new entry, give it an old age so it's pinged sooner - // This speeds up bootstrapping - info.recv = info.recv.Add(-time.Hour) - } - if isPeer || info.throttle > time.Minute { - info.throttle = time.Minute - } - // First drop any existing entry from the bucket - b.drop(&info.key) - // Now add to the *end* of the bucket - if isPeer { - // TODO make sure we don't duplicate peers in b.other too - b.peers = append(b.peers, info) - return - } - b.other = append(b.other, info) - // Shrink from the *front* to requied size - for len(b.other) > dht_bucket_size { - b.other = b.other[1:] - } -} - -// Gets the bucket index for the bucket where we would put the given NodeID. -func (t *dht) getBucketIndex(nodeID *NodeID) (int, bool) { - for bidx := 0; bidx < t.nBuckets(); bidx++ { - them := nodeID[bidx/8] & (0x80 >> byte(bidx%8)) - me := t.nodeID[bidx/8] & (0x80 >> byte(bidx%8)) - if them != me { - return bidx, true - } - } - return t.nBuckets(), false -} - -// Helper called by containsPeer, containsOther, and contains. -// Returns true if a node with the same ID *and coords* is already in the given part of the bucket. -func dht_bucket_check(newInfo *dhtInfo, infos []*dhtInfo) bool { - // Compares if key and coords match - if newInfo == nil { - panic("Should never happen") - } - for _, info := range infos { - if info == nil { - panic("Should never happen") - } - if info.key != newInfo.key { + if *info.getNodeID() == t.nodeID { continue + } // Skip self + // Send a request to all better successors or predecessors + // We could try sending to only the best, but then packet loss matters more + if successor == nil || dht_ordered(&t.nodeID, info.getNodeID(), successor.getNodeID()) { + // ping } - if len(info.coords) != len(newInfo.coords) { - continue - } - match := true - for idx := 0; idx < len(info.coords); idx++ { - if info.coords[idx] != newInfo.coords[idx] { - match = false - break - } - } - if match { - return true + if predecessor == nil || dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { + // ping } } - return false -} - -// Calls bucket_check over the bucket's peers infos. -func (b *bucket) containsPeer(info *dhtInfo) bool { - return dht_bucket_check(info, b.peers) -} - -// Calls bucket_check over the bucket's other info. -func (b *bucket) containsOther(info *dhtInfo) bool { - return dht_bucket_check(info, b.other) -} - -// returns containsPeer || containsOther -func (b *bucket) contains(info *dhtInfo) bool { - return b.containsPeer(info) || b.containsOther(info) -} - -// Removes a node with the corresponding key, if any, from a bucket. -func (b *bucket) drop(key *boxPubKey) { - clean := func(infos []*dhtInfo) []*dhtInfo { - cleaned := infos[:0] - for _, info := range infos { - if info.key == *key { - continue - } - cleaned = append(cleaned, info) - } - return cleaned - } - b.peers = clean(b.peers) - b.other = clean(b.other) + // TODO add everyting else to a rumor mill for later use? (when/how?) } // Sends a lookup request to the specified node. @@ -390,75 +260,10 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { reqsToDest[req.Dest] = time.Now() } -// Sends a lookup response to the specified node. -func (t *dht) sendRes(res *dhtRes, req *dhtReq) { - // Send a reply for a dhtReq - bs := res.encode() - shared := t.core.sessions.getSharedKey(&t.core.boxPriv, &req.Key) - payload, nonce := boxSeal(shared, bs, nil) - p := wire_protoTrafficPacket{ - Coords: req.Coords, - ToKey: req.Key, - FromKey: t.core.boxPub, - Nonce: *nonce, - Payload: payload, - } - packet := p.encode() - t.core.router.out(packet) -} - -// Returns true of a bucket contains no peers and no other nodes. -func (b *bucket) isEmpty() bool { - return len(b.peers)+len(b.other) == 0 -} - -// Gets the next node that should be pinged from the bucket. -// There's a cooldown of 6 seconds between ping attempts for each node, to give them time to respond. -// It returns the least recently pinged node, subject to that send cooldown. -func (b *bucket) nextToPing() *dhtInfo { - // Check the nodes in the bucket - // Return whichever one responded least recently - // Delay of 6 seconds between pinging the same node - // Gives them time to respond - // And time between traffic loss from short term congestion in the network - var toPing *dhtInfo - update := func(infos []*dhtInfo) { - for _, next := range infos { - if time.Since(next.send) < 6*time.Second { - continue - } - if toPing == nil || next.recv.Before(toPing.recv) { - toPing = next - } - } - } - update(b.peers) - update(b.other) - return toPing -} - -// Returns a useful target address to ask about for pings. -// Equal to the our node's ID, except for exactly 1 bit at the bucket index. -func (t *dht) getTarget(bidx int) *NodeID { - targetID := t.nodeID - targetID[bidx/8] ^= 0x80 >> byte(bidx%8) - return &targetID -} - -// Sends a ping to a node, or removes the node if it has failed to respond to too many pings. -// If target is nil, we will ask the node about our own NodeID. func (t *dht) ping(info *dhtInfo, target *NodeID) { - if info.pings > 2 { - bidx, isOK := t.getBucketIndex(info.getNodeID()) - if !isOK { - panic("This should never happen") - } - b := t.getBucket(bidx) - b.drop(&info.key) - return - } + // Creates a req for the node at dhtInfo, asking them about the target (if one is given) or themself (if no target is given) if target == nil { - target = &t.nodeID + target = info.getNodeID() } loc := t.core.switchTable.getLocator() coords := loc.getCoords() @@ -467,160 +272,24 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { Coords: coords, Dest: *target, } - info.pings++ info.send = time.Now() t.sendReq(&req, info) } -// Adds a node info and target to the rumor mill. -// The node will be asked about the target at a later point, if doing so would still be useful at the time. -func (t *dht) addToMill(info *dhtInfo, target *NodeID) { - rumor := dht_rumor{ - info: info, - target: target, - } - t.rumorMill = append(t.rumorMill, rumor) -} - -// Regular periodic maintenance. -// If the mill is empty, it adds two pings to the rumor mill. -// The first is to the node that responded least recently, provided that it's been at least 1 minute, to make sure we eventually detect and remove unresponsive nodes. -// The second is used for bootstrapping, and attempts to fill some bucket, iterating over buckets and resetting after it hits the last non-empty one. -// If the mill is not empty, it pops nodes from the mill until it finds one that would be useful to ping (see: shouldInsert), and then pings it. func (t *dht) doMaintenance() { - // First clean up reqs - for key, reqs := range t.reqs { - for target, timeout := range reqs { - if time.Since(timeout) > time.Minute { - delete(reqs, target) - } - } - if len(reqs) == 0 { - delete(t.reqs, key) + // Ping successor, asking for their predecessor, and clean up old/expired info + var successor *dhtInfo + now := time.Now() + for infoID, info := range t.table { + if now.Sub(info.recv) > time.Minute { + delete(t.table, infoID) + } else if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { + successor = info } } - if len(t.rumorMill) == 0 { - // Ping the least recently contacted node - // This is to make sure we eventually notice when someone times out - var oldest *dhtInfo - last := 0 - for bidx := 0; bidx < t.nBuckets(); bidx++ { - b := t.getBucket(bidx) - if !b.isEmpty() { - last = bidx - toPing := b.nextToPing() - if toPing == nil { - continue - } // We've recently pinged everyone in b - if oldest == nil || toPing.recv.Before(oldest.recv) { - oldest = toPing - } - } - } - if oldest != nil && time.Since(oldest.recv) > time.Minute { - // Ping the oldest node in the DHT, but don't ping nodes that have been checked within the last minute - t.addToMill(oldest, nil) - } - // Refresh buckets - if t.offset > last { - t.offset = 0 - } - target := t.getTarget(t.offset) - func() { - closer := t.lookup(target, false) - for _, info := range closer { - // Throttled ping of a node that's closer to the destination - if time.Since(info.recv) > info.throttle { - t.addToMill(info, target) - t.offset++ - info.bootstrapSend = time.Now() - info.throttle *= 2 - if info.throttle > time.Minute { - info.throttle = time.Minute - } - return - } - } - if len(closer) == 0 { - // If we don't know of anyone closer at all, then there's a hole in our dht - // Ping the closest node we know and ignore the throttle, to try to fill it - for _, info := range t.lookup(target, true) { - t.addToMill(info, target) - t.offset++ - return - } - } - }() - //t.offset++ - } - for len(t.rumorMill) > 0 { - var rumor dht_rumor - rumor, t.rumorMill = t.rumorMill[0], t.rumorMill[1:] - if rumor.target == rumor.info.getNodeID() { - // Note that the above is a pointer comparison, and target can be nil - // This is only for adding new nodes (learned from other lookups) - // It only makes sense to ping if the node isn't already in the table - if !t.shouldInsert(rumor.info) { - continue - } - } - t.ping(rumor.info, rumor.target) - break - } -} - -// Returns true if it would be worth pinging the specified node. -// This requires that the bucket doesn't already contain the node, and that either the bucket isn't full yet or the node is closer to us in keyspace than some other node in that bucket. -func (t *dht) shouldInsert(info *dhtInfo) bool { - bidx, isOK := t.getBucketIndex(info.getNodeID()) - if !isOK { - return false - } - b := t.getBucket(bidx) - if b.containsOther(info) { - return false - } - if len(b.other) < dht_bucket_size { - return true - } - for _, other := range b.other { - if dht_firstCloserThanThird(info.getNodeID(), &t.nodeID, other.getNodeID()) { - return true - } - } - return false -} - -// Returns true if the keyspace distance between the first and second node is smaller than the keyspace distance between the second and third node. -func dht_firstCloserThanThird(first *NodeID, - second *NodeID, - third *NodeID) bool { - for idx := 0; idx < NodeIDLen; idx++ { - f := first[idx] ^ second[idx] - t := third[idx] ^ second[idx] - if f == t { - continue - } - return f < t - } - return false -} - -// Resets the DHT in response to coord changes. -// This empties all buckets, resets the bootstrapping cycle to 0, and empties the rumor mill. -// It adds all old "other" node info to the rumor mill, so they'll be pinged quickly. -// If those nodes haven't also changed coords, then this is a relatively quick way to notify those nodes of our new coords and re-add them to our own DHT if they respond. -func (t *dht) reset() { - // This is mostly so bootstrapping will reset to resend coords into the network - t.offset = 0 - t.rumorMill = nil // reset mill - for _, b := range t.buckets_hidden { - b.peers = b.peers[:0] - for _, info := range b.other { - // Add other nodes to the rumor mill so they'll be pinged soon - // This will hopefully tell them our coords and re-learn theirs quickly if they haven't changed - t.addToMill(info, info.getNodeID()) - } - b.other = b.other[:0] + if successor != nil && + now.Sub(successor.recv) > 30*time.Second && + now.Sub(successor.send) > 6*time.Second { + t.ping(successor, nil) } } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index d2a8c43b..1027eabd 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -91,8 +91,7 @@ func (r *router) mainLoop() { case p := <-r.send: r.sendPacket(p) case info := <-r.core.dht.peers: - r.core.dht.insertIfNew(info, false) // Insert as a normal node - r.core.dht.insertIfNew(info, true) // Insert as a peer + r.core.dht.insert(info) case <-r.reset: r.core.sessions.resetInits() r.core.dht.reset() diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 1b72a63f..1be18bae 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -92,7 +92,7 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { if sinfo.visited[*info.getNodeID()] { continue } - if dht_firstCloserThanThird(info.getNodeID(), &res.Dest, from.getNodeID()) { + if dht_ordered(from.getNodeID(), info.getNodeID(), &res.Dest) { sinfo.toVisit = append(sinfo.toVisit, info) } } @@ -107,7 +107,7 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { } // Sort sort.SliceStable(sinfo.toVisit, func(i, j int) bool { - return dht_firstCloserThanThird(sinfo.toVisit[i].getNodeID(), &res.Dest, sinfo.toVisit[j].getNodeID()) + return dht_ordered(sinfo.toVisit[j].getNodeID(), sinfo.toVisit[i].getNodeID(), &res.Dest) }) // Truncate to some maximum size if len(sinfo.toVisit) > search_MAX_SEARCH_SIZE { @@ -126,10 +126,10 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { // Send to the next search target var next *dhtInfo next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] - var oldPings int - oldPings, next.pings = next.pings, 0 + //var oldPings int + //oldPings, next.pings = next.pings, 0 s.core.dht.ping(next, &sinfo.dest) - next.pings = oldPings // Don't evict a node for searching with it too much + //next.pings = oldPings // Don't evict a node for searching with it too much sinfo.visited[*next.getNodeID()] = true } } diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0bc27a12..662b4aea 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -358,7 +358,7 @@ func (ss *sessions) getSharedKey(myPriv *boxPrivKey, return skey } // First do some cleanup - const maxKeys = dht_bucket_number * dht_bucket_size + const maxKeys = 1024 for key := range ss.permShared { // Remove a random key until the store is small enough if len(ss.permShared) < maxKeys { From 1720dff4769dea7e8572b5cfe6bdc8182da02eb3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 15:21:40 -0500 Subject: [PATCH 004/145] add some debug output and get things to start working in the sim --- src/yggdrasil/debug.go | 12 ++++-------- src/yggdrasil/dht.go | 27 ++++++++++++++++++--------- src/yggdrasil/search.go | 2 +- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index f614fece..638bd8f4 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -229,14 +229,10 @@ func DEBUG_wire_encode_coords(coords []byte) []byte { // DHT, via core func (c *Core) DEBUG_getDHTSize() int { - total := 0 - /* FIXME - for bidx := 0; bidx < c.dht.nBuckets(); bidx++ { - b := c.dht.getBucket(bidx) - total += len(b.peers) - total += len(b.other) - } - */ + var total int + c.router.doAdmin(func() { + total = len(c.dht.table) + }) return total } diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 068a4f37..ea614b07 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "fmt" "sort" "time" ) @@ -65,11 +66,11 @@ func (t *dht) init(c *Core) { } func (t *dht) reset() { + t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) } func (t *dht) lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { - return nil var results []*dhtInfo var successor *dhtInfo sTarget := t.nodeID.next() @@ -96,6 +97,11 @@ func (t *dht) lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { // Insert into table, preserving the time we last sent a packet if the node was already in the table, otherwise setting that time to now func (t *dht) insert(info *dhtInfo) { + if *info.getNodeID() == t.nodeID { + // This shouldn't happen, but don't crash or add it in case it does + return + panic("FIXME") + } info.recv = time.Now() if oldInfo, isIn := t.table[*info.getNodeID()]; isIn { info.send = oldInfo.send @@ -139,14 +145,12 @@ func (t *dht) handleReq(req *dhtReq) { Infos: t.lookup(&req.Dest, false), } t.sendRes(&res, req) - // Also (possibly) add them to our DHT + // Also add them to our DHT info := dhtInfo{ key: req.Key, coords: req.Coords, } // For bootstrapping to work, we need to add these nodes to the table - // Using insertIfNew, they can lie about coords, but searches will route around them - // Using the mill would mean trying to block off the mill becomes an attack vector t.insert(&info) } @@ -169,7 +173,7 @@ func (t *dht) sendRes(res *dhtRes, req *dhtReq) { // Returns nodeID + 1 func (nodeID NodeID) next() NodeID { - for idx := len(nodeID); idx >= 0; idx-- { + for idx := len(nodeID) - 1; idx >= 0; idx-- { nodeID[idx] += 1 if nodeID[idx] != 0 { break @@ -180,7 +184,7 @@ func (nodeID NodeID) next() NodeID { // Returns nodeID - 1 func (nodeID NodeID) prev() NodeID { - for idx := len(nodeID); idx >= 0; idx-- { + for idx := len(nodeID) - 1; idx >= 0; idx-- { nodeID[idx] -= 1 if nodeID[idx] != 0xff { break @@ -222,13 +226,19 @@ func (t *dht) handleRes(res *dhtRes) { if *info.getNodeID() == t.nodeID { continue } // Skip self + if _, isIn := t.table[*info.getNodeID()]; isIn { + // TODO? don't skip if coords are different? + continue + } // Send a request to all better successors or predecessors // We could try sending to only the best, but then packet loss matters more if successor == nil || dht_ordered(&t.nodeID, info.getNodeID(), successor.getNodeID()) { - // ping + t.ping(info, &t.nodeID) + fmt.Println("pinging new successor", t.nodeID[:4], info.getNodeID()[:4], successor) } if predecessor == nil || dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { - // ping + t.ping(info, &t.nodeID) + fmt.Println("pinging new predecessor", t.nodeID[:4], info.getNodeID()[:4], predecessor) } } // TODO add everyting else to a rumor mill for later use? (when/how?) @@ -288,7 +298,6 @@ func (t *dht) doMaintenance() { } } if successor != nil && - now.Sub(successor.recv) > 30*time.Second && now.Sub(successor.send) > 6*time.Second { t.ping(successor, nil) } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 1be18bae..3f7ff388 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -89,7 +89,7 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { // Add responses to toVisit if closer to dest than the res node from := dhtInfo{key: res.Key, coords: res.Coords} for _, info := range res.Infos { - if sinfo.visited[*info.getNodeID()] { + if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { continue } if dht_ordered(from.getNodeID(), info.getNodeID(), &res.Dest) { From 02f0611ddeb6d92960a188d980ffbce756ea007f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 16:27:01 -0500 Subject: [PATCH 005/145] more debugging --- misc/sim/treesim.go | 4 ++-- src/yggdrasil/debug.go | 1 + src/yggdrasil/dht.go | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 793ef219..30ac2835 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -415,10 +415,10 @@ func main() { } fmt.Println("Test") Util_testAddrIDMask() - idxstore := makeStoreSquareGrid(4) + //idxstore := makeStoreSquareGrid(4) //idxstore := makeStoreStar(256) //idxstore := loadGraph("misc/sim/hype-2016-09-19.list") - //idxstore := loadGraph("misc/sim/fc00-2017-08-12.txt") + idxstore := loadGraph("misc/sim/fc00-2017-08-12.txt") //idxstore := loadGraph("skitter") kstore := getKeyedStore(idxstore) //* diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 638bd8f4..c71c2e5e 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -233,6 +233,7 @@ func (c *Core) DEBUG_getDHTSize() int { c.router.doAdmin(func() { total = len(c.dht.table) }) + fmt.Println("DEBUG_getDHTSize():", total) return total } diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index ea614b07..55121be1 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -66,6 +66,7 @@ func (t *dht) init(c *Core) { } func (t *dht) reset() { + fmt.Println("Resetting table:", t.nodeID) t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) } @@ -98,7 +99,7 @@ func (t *dht) lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { // Insert into table, preserving the time we last sent a packet if the node was already in the table, otherwise setting that time to now func (t *dht) insert(info *dhtInfo) { if *info.getNodeID() == t.nodeID { - // This shouldn't happen, but don't crash or add it in case it does + // This shouldn't happen, but don't add it in case it does return panic("FIXME") } @@ -152,6 +153,7 @@ func (t *dht) handleReq(req *dhtReq) { } // For bootstrapping to work, we need to add these nodes to the table t.insert(&info) + info.send = info.send.Add(-time.Minute) } // Sends a lookup response to the specified node. @@ -234,11 +236,19 @@ func (t *dht) handleRes(res *dhtRes) { // We could try sending to only the best, but then packet loss matters more if successor == nil || dht_ordered(&t.nodeID, info.getNodeID(), successor.getNodeID()) { t.ping(info, &t.nodeID) - fmt.Println("pinging new successor", t.nodeID[:4], info.getNodeID()[:4], successor) + if successor != nil { + fmt.Println("pinging better successor", t.nodeID[:4], info.getNodeID()[:4], successor.getNodeID()[:4], len(t.table)) + } else { + fmt.Println("pinging new successor", t.nodeID[:4], info.getNodeID()[:4], successor) + } } if predecessor == nil || dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { t.ping(info, &t.nodeID) - fmt.Println("pinging new predecessor", t.nodeID[:4], info.getNodeID()[:4], predecessor) + if predecessor != nil { + fmt.Println("pinging better predecessor", t.nodeID[:4], info.getNodeID()[:4], predecessor.getNodeID()[:4], len(t.table)) + } else { + fmt.Println("pinging new predecessor", t.nodeID[:4], info.getNodeID()[:4]) + } } } // TODO add everyting else to a rumor mill for later use? (when/how?) @@ -290,15 +300,30 @@ func (t *dht) doMaintenance() { // Ping successor, asking for their predecessor, and clean up old/expired info var successor *dhtInfo now := time.Now() + size := len(t.table) for infoID, info := range t.table { + /* + if now.Sub(info.recv) > time.Minute { + delete(t.table, infoID) + } else if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { + successor = info + } + */ + if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { + successor = info + } if now.Sub(info.recv) > time.Minute { delete(t.table, infoID) - } else if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { - successor = info } } if successor != nil && now.Sub(successor.send) > 6*time.Second { t.ping(successor, nil) } + if successor != nil && t.table[*successor.getNodeID()] == nil { + fmt.Println("DEBUG: successor timed out:", t.nodeID[:4], successor.getNodeID()[:4]) + } + if len(t.table) != size { + fmt.Println("DEBUG: timeouts:", t.nodeID[:4], size, len(t.table)) + } } From 5a85d3515d29febe5586c66cc58583d5f8a1b5a6 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 17:32:54 -0500 Subject: [PATCH 006/145] cleanup --- misc/sim/treesim.go | 4 ++-- src/yggdrasil/dht.go | 38 ++++---------------------------------- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 30ac2835..793ef219 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -415,10 +415,10 @@ func main() { } fmt.Println("Test") Util_testAddrIDMask() - //idxstore := makeStoreSquareGrid(4) + idxstore := makeStoreSquareGrid(4) //idxstore := makeStoreStar(256) //idxstore := loadGraph("misc/sim/hype-2016-09-19.list") - idxstore := loadGraph("misc/sim/fc00-2017-08-12.txt") + //idxstore := loadGraph("misc/sim/fc00-2017-08-12.txt") //idxstore := loadGraph("skitter") kstore := getKeyedStore(idxstore) //* diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 55121be1..5b43d066 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -1,7 +1,6 @@ package yggdrasil import ( - "fmt" "sort" "time" ) @@ -16,9 +15,6 @@ type dhtInfo struct { coords []byte send time.Time // When we last sent a message recv time.Time // When we last received a message - //pings int // Decide when to drop - //throttle time.Duration // Time to wait before pinging a node to bootstrap buckets, increases exponentially from 1 second to 1 minute - //bootstrapSend time.Time // The time checked/updated as part of throttle checks } // Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. @@ -54,7 +50,6 @@ type dht struct { table map[NodeID]*dhtInfo peers chan *dhtInfo // other goroutines put incoming dht updates here reqs map[boxPubKey]map[NodeID]time.Time - //rumorMill []dht_rumor } func (t *dht) init(c *Core) { @@ -66,7 +61,7 @@ func (t *dht) init(c *Core) { } func (t *dht) reset() { - fmt.Println("Resetting table:", t.nodeID) + //fmt.Println("Resetting table:", t.nodeID) t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) } @@ -99,7 +94,7 @@ func (t *dht) lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { // Insert into table, preserving the time we last sent a packet if the node was already in the table, otherwise setting that time to now func (t *dht) insert(info *dhtInfo) { if *info.getNodeID() == t.nodeID { - // This shouldn't happen, but don't add it in case it does + // This shouldn't happen, but don't add it if it does return panic("FIXME") } @@ -236,19 +231,9 @@ func (t *dht) handleRes(res *dhtRes) { // We could try sending to only the best, but then packet loss matters more if successor == nil || dht_ordered(&t.nodeID, info.getNodeID(), successor.getNodeID()) { t.ping(info, &t.nodeID) - if successor != nil { - fmt.Println("pinging better successor", t.nodeID[:4], info.getNodeID()[:4], successor.getNodeID()[:4], len(t.table)) - } else { - fmt.Println("pinging new successor", t.nodeID[:4], info.getNodeID()[:4], successor) - } } if predecessor == nil || dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { t.ping(info, &t.nodeID) - if predecessor != nil { - fmt.Println("pinging better predecessor", t.nodeID[:4], info.getNodeID()[:4], predecessor.getNodeID()[:4], len(t.table)) - } else { - fmt.Println("pinging new predecessor", t.nodeID[:4], info.getNodeID()[:4]) - } } } // TODO add everyting else to a rumor mill for later use? (when/how?) @@ -300,30 +285,15 @@ func (t *dht) doMaintenance() { // Ping successor, asking for their predecessor, and clean up old/expired info var successor *dhtInfo now := time.Now() - size := len(t.table) for infoID, info := range t.table { - /* - if now.Sub(info.recv) > time.Minute { - delete(t.table, infoID) - } else if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { - successor = info - } - */ - if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { - successor = info - } if now.Sub(info.recv) > time.Minute { delete(t.table, infoID) + } else if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { + successor = info } } if successor != nil && now.Sub(successor.send) > 6*time.Second { t.ping(successor, nil) } - if successor != nil && t.table[*successor.getNodeID()] == nil { - fmt.Println("DEBUG: successor timed out:", t.nodeID[:4], successor.getNodeID()[:4]) - } - if len(t.table) != size { - fmt.Println("DEBUG: timeouts:", t.nodeID[:4], size, len(t.table)) - } } From f3ec8c5b37d718b1dee249cfa8e528e256bac1e2 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 17:58:54 -0500 Subject: [PATCH 007/145] fix admin dht function, more cleanup, and slowly throttle back dht traffic when idle --- src/yggdrasil/admin.go | 38 +++++++++++++++++--------------------- src/yggdrasil/dht.go | 7 +++++++ src/yggdrasil/search.go | 3 --- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 2446be51..cfbef94c 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -555,27 +555,23 @@ func (a *admin) getData_getSwitchQueues() admin_nodeInfo { func (a *admin) getData_getDHT() []admin_nodeInfo { var infos []admin_nodeInfo getDHT := func() { - /* TODO fix this - now := time.Now() - for i := 0; i < a.core.dht.nBuckets(); i++ { - b := a.core.dht.getBucket(i) - getInfo := func(vs []*dhtInfo, isPeer bool) { - for _, v := range vs { - addr := *address_addrForNodeID(v.getNodeID()) - info := admin_nodeInfo{ - {"ip", net.IP(addr[:]).String()}, - {"coords", fmt.Sprint(v.coords)}, - {"bucket", i}, - {"peer_only", isPeer}, - {"last_seen", int(now.Sub(v.recv).Seconds())}, - } - infos = append(infos, info) - } - } - getInfo(b.other, false) - getInfo(b.peers, true) - } - */ + now := time.Now() + var dhtInfos []*dhtInfo + for _, v := range a.core.dht.table { + dhtInfos = append(dhtInfos, v) + } + sort.SliceStable(dhtInfos, func(i, j int) bool { + return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID()) + }) + for _, v := range dhtInfos { + addr := *address_addrForNodeID(v.getNodeID()) + info := admin_nodeInfo{ + {"ip", net.IP(addr[:]).String()}, + {"coords", fmt.Sprint(v.coords)}, + {"last_seen", int(now.Sub(v.recv).Seconds())}, + } + infos = append(infos, info) + } } a.core.router.doAdmin(getDHT) return infos diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 5b43d066..0516aa5b 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -15,6 +15,7 @@ type dhtInfo struct { coords []byte send time.Time // When we last sent a message recv time.Time // When we last received a message + throttle time.Duration } // Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. @@ -101,9 +102,14 @@ func (t *dht) insert(info *dhtInfo) { info.recv = time.Now() if oldInfo, isIn := t.table[*info.getNodeID()]; isIn { info.send = oldInfo.send + info.throttle = oldInfo.throttle } else { info.send = info.recv } + info.throttle += time.Second + if info.throttle > 30*time.Second { + info.throttle = 30 * time.Second + } t.table[*info.getNodeID()] = info } @@ -293,6 +299,7 @@ func (t *dht) doMaintenance() { } } if successor != nil && + now.Sub(successor.recv) > successor.throttle && now.Sub(successor.send) > 6*time.Second { t.ping(successor, nil) } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 3f7ff388..dd6b09cf 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -126,10 +126,7 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { // Send to the next search target var next *dhtInfo next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] - //var oldPings int - //oldPings, next.pings = next.pings, 0 s.core.dht.ping(next, &sinfo.dest) - //next.pings = oldPings // Don't evict a node for searching with it too much sinfo.visited[*next.getNodeID()] = true } } From 63d6ab425124537de9a671bc17b0323ac85c97c3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 18:12:34 -0500 Subject: [PATCH 008/145] more cleanup, comments, and dht reset() changes --- src/yggdrasil/dht.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 0516aa5b..6682eccf 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -53,20 +53,42 @@ type dht struct { reqs map[boxPubKey]map[NodeID]time.Time } +// Initializes the DHT func (t *dht) init(c *Core) { - // TODO t.core = c t.nodeID = *t.core.GetNodeID() t.peers = make(chan *dhtInfo, 1024) t.reset() } +// Resets the DHT in response to coord changes +// This empties all info from the DHT and drops outstanding requests +// It sends a ping to the old successor and predecessor, in case they're still around func (t *dht) reset() { - //fmt.Println("Resetting table:", t.nodeID) + var successor *dhtInfo + var predecessor *dhtInfo + for infoID, info := range t.table { + // Get current successor and predecessor + if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { + successor = info + } + if predecessor == nil || dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { + predecessor = info + } + } t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) + if successor != nil { + t.ping(successor, &t.nodeID) + } + if predecessor != nil { + t.ping(predecessor, &t.nodeID) + } } +// Does a DHT lookup and returns up to dht_lookup_size results +// If allowWorse = true, begins with best know predecessor for ID and works backwards, even if these nodes are worse predecessors than we are, to be used when intializing searches +// If allowWorse = false, begins with the best known successor for ID and works backwards (next is predecessor, etc, inclusive of the ID if it's a known node) func (t *dht) lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { var results []*dhtInfo var successor *dhtInfo From d851d9afe7d29d1bd13cfc23946f022f77c0a55a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 18:31:11 -0500 Subject: [PATCH 009/145] add max pings before timing out a successor --- src/yggdrasil/dht.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 6682eccf..4ac425c9 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -16,6 +16,7 @@ type dhtInfo struct { send time.Time // When we last sent a message recv time.Time // When we last received a message throttle time.Duration + pings int // Time out if at least 3 consecutive maintenance pings drop } // Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. @@ -314,7 +315,7 @@ func (t *dht) doMaintenance() { var successor *dhtInfo now := time.Now() for infoID, info := range t.table { - if now.Sub(info.recv) > time.Minute { + if now.Sub(info.recv) > time.Minute || info.pings > 3 { delete(t.table, infoID) } else if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { successor = info @@ -324,5 +325,6 @@ func (t *dht) doMaintenance() { now.Sub(successor.recv) > successor.throttle && now.Sub(successor.send) > 6*time.Second { t.ping(successor, nil) + successor.pings++ } } From 3dbffae99f7aee6231e4a0e7294c1afac10b9454 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 19:09:25 -0500 Subject: [PATCH 010/145] add search for successor, via parent, to the dht maintenance cycle --- src/yggdrasil/dht.go | 33 ++++++++++++++++++++++++++++++++- src/yggdrasil/switch.go | 2 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 4ac425c9..da3ed75a 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -52,6 +52,7 @@ type dht struct { table map[NodeID]*dhtInfo peers chan *dhtInfo // other goroutines put incoming dht updates here reqs map[boxPubKey]map[NodeID]time.Time + search time.Time } // Initializes the DHT @@ -79,6 +80,7 @@ func (t *dht) reset() { } t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) + t.search = time.Now().Add(-time.Minute) if successor != nil { t.ping(successor, &t.nodeID) } @@ -323,8 +325,37 @@ func (t *dht) doMaintenance() { } if successor != nil && now.Sub(successor.recv) > successor.throttle && - now.Sub(successor.send) > 6*time.Second { + now.Sub(successor.send) > 3*time.Second { t.ping(successor, nil) successor.pings++ + if now.Sub(t.search) > time.Minute { + // Start a search for our successor, beginning at this node's parent + // This should (hopefully) help bootstrap + t.core.switchTable.mutex.RLock() + parentPort := t.core.switchTable.parent + t.core.switchTable.mutex.RUnlock() + ports := t.core.peers.getPorts() + if parent, isIn := ports[parentPort]; isIn { + t.search = now + target := successor.getNodeID().prev() + sinfo, isIn := t.core.searches.searches[target] + if !isIn { + var mask NodeID + for idx := range mask { + mask[idx] = 0xff + } + sinfo = t.core.searches.newIterSearch(&target, &mask) + toVisit := sinfo.toVisit + parentNodeID := getNodeID(&parent.box) + for _, ninfo := range toVisit { + if *ninfo.getNodeID() == *parentNodeID { + toVisit = append(toVisit, ninfo) + } + } + sinfo.toVisit = toVisit + } + t.core.searches.continueSearch(sinfo) + } + } } } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 72f17ed2..e877880f 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -158,9 +158,9 @@ type switchTable struct { core *Core key sigPubKey // Our own key time time.Time // Time when locator.tstamp was last updated - parent switchPort // Port of whatever peer is our parent, or self if we're root drop map[sigPubKey]int64 // Tstamp associated with a dropped root mutex sync.RWMutex // Lock for reads/writes of switchData + parent switchPort // Port of whatever peer is our parent, or self if we're root data switchData // updater atomic.Value // *sync.Once table atomic.Value // lookupTable From 8825494d5964aa96336ab0b0fe68cbe4b1793009 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 20:11:32 -0500 Subject: [PATCH 011/145] remove maintenance searches and throttle logic, to focus on debugging in this simpler case first --- src/yggdrasil/dht.go | 49 ++------------------------------------------ 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index da3ed75a..17d0a235 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -13,10 +13,8 @@ type dhtInfo struct { nodeID_hidden *NodeID key boxPubKey coords []byte - send time.Time // When we last sent a message recv time.Time // When we last received a message - throttle time.Duration - pings int // Time out if at least 3 consecutive maintenance pings drop + pings int // Time out if at least 3 consecutive maintenance pings drop } // Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. @@ -125,16 +123,6 @@ func (t *dht) insert(info *dhtInfo) { panic("FIXME") } info.recv = time.Now() - if oldInfo, isIn := t.table[*info.getNodeID()]; isIn { - info.send = oldInfo.send - info.throttle = oldInfo.throttle - } else { - info.send = info.recv - } - info.throttle += time.Second - if info.throttle > 30*time.Second { - info.throttle = 30 * time.Second - } t.table[*info.getNodeID()] = info } @@ -179,7 +167,6 @@ func (t *dht) handleReq(req *dhtReq) { } // For bootstrapping to work, we need to add these nodes to the table t.insert(&info) - info.send = info.send.Add(-time.Minute) } // Sends a lookup response to the specified node. @@ -308,7 +295,6 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { Coords: coords, Dest: *target, } - info.send = time.Now() t.sendReq(&req, info) } @@ -323,39 +309,8 @@ func (t *dht) doMaintenance() { successor = info } } - if successor != nil && - now.Sub(successor.recv) > successor.throttle && - now.Sub(successor.send) > 3*time.Second { + if successor != nil { t.ping(successor, nil) successor.pings++ - if now.Sub(t.search) > time.Minute { - // Start a search for our successor, beginning at this node's parent - // This should (hopefully) help bootstrap - t.core.switchTable.mutex.RLock() - parentPort := t.core.switchTable.parent - t.core.switchTable.mutex.RUnlock() - ports := t.core.peers.getPorts() - if parent, isIn := ports[parentPort]; isIn { - t.search = now - target := successor.getNodeID().prev() - sinfo, isIn := t.core.searches.searches[target] - if !isIn { - var mask NodeID - for idx := range mask { - mask[idx] = 0xff - } - sinfo = t.core.searches.newIterSearch(&target, &mask) - toVisit := sinfo.toVisit - parentNodeID := getNodeID(&parent.box) - for _, ninfo := range toVisit { - if *ninfo.getNodeID() == *parentNodeID { - toVisit = append(toVisit, ninfo) - } - } - sinfo.toVisit = toVisit - } - t.core.searches.continueSearch(sinfo) - } - } } } From 95201669fe7475fa463ec0014eae1133edb1b258 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 20 Oct 2018 22:06:36 -0500 Subject: [PATCH 012/145] reintroduce (better) dht throttling --- src/yggdrasil/dht.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 17d0a235..b04d2585 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -15,6 +15,7 @@ type dhtInfo struct { coords []byte recv time.Time // When we last received a message pings int // Time out if at least 3 consecutive maintenance pings drop + throttle time.Duration } // Returns the *NodeID associated with dhtInfo.key, calculating it on the fly the first time or from a cache all subsequent times. @@ -123,6 +124,22 @@ func (t *dht) insert(info *dhtInfo) { panic("FIXME") } info.recv = time.Now() + if oldInfo, isIn := t.table[*info.getNodeID()]; isIn { + sameCoords := true + if len(info.coords) != len(oldInfo.coords) { + sameCoords = false + } else { + for idx := 0; idx < len(info.coords); idx++ { + if info.coords[idx] != oldInfo.coords[idx] { + sameCoords = false + break + } + } + } + if sameCoords { + info.throttle = oldInfo.throttle + } + } t.table[*info.getNodeID()] = info } @@ -309,8 +326,13 @@ func (t *dht) doMaintenance() { successor = info } } - if successor != nil { + if successor != nil && + now.Sub(successor.recv) > successor.throttle { t.ping(successor, nil) successor.pings++ + successor.throttle += time.Second + if successor.throttle > 30*time.Second { + successor.throttle = 30 * time.Second + } } } From 6c59ae862a8de95419961a35067385c891523e3d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Oct 2018 00:05:04 -0500 Subject: [PATCH 013/145] more debugging --- src/yggdrasil/dht.go | 68 ++++++++++++++++++++++++++++++++--------- src/yggdrasil/peer.go | 6 ++-- src/yggdrasil/router.go | 1 + 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index b04d2585..2a97ec03 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -1,6 +1,12 @@ package yggdrasil +// TODO signal to predecessor when we replace them? +// Sending a ping with an extra 0 at the end of our coords should be enough to reset our throttle in their table +// That should encorage them to ping us again sooner, and then we can reply with new info +// Maybe remember old predecessor and check this during maintenance? + import ( + "fmt" "sort" "time" ) @@ -146,22 +152,32 @@ func (t *dht) insert(info *dhtInfo) { // Return true if first/second/third are (partially) ordered correctly // FIXME? maybe total ordering makes more sense func dht_ordered(first, second, third *NodeID) bool { - var ordered bool - for idx := 0; idx < NodeIDLen; idx++ { - f, s, t := first[idx], second[idx], third[idx] - switch { - case f == s && s == t: - continue - case f <= s && s <= t: - ordered = true // nothing wrapped around 0 - case t <= f && f <= s: - ordered = true // 0 is between second and third - case s <= t && t <= f: - ordered = true // 0 is between first and second + lessOrEqual := func(first, second *NodeID) bool { + for idx := 0; idx < NodeIDLen; idx++ { + if first[idx] > second[idx] { + return false + } + if first[idx] < second[idx] { + return true + } } - break + return true } - return ordered + firstLessThanSecond := lessOrEqual(first, second) + secondLessThanThird := lessOrEqual(second, third) + thirdLessThanFirst := lessOrEqual(third, first) + switch { + case firstLessThanSecond && secondLessThanThird: + // Nothing wrapped around 0, the easy case + return true + case thirdLessThanFirst && firstLessThanSecond: + // Third wrapped around 0 + return true + case secondLessThanThird && thirdLessThanFirst: + // Second (and third) wrapped around 0 + return true + } + return false } // Reads a request, performs a lookup, and responds. @@ -254,6 +270,9 @@ func (t *dht) handleRes(res *dhtRes) { predecessor = info } } + if len(res.Infos) > dht_lookup_size { + res.Infos = res.Infos[:dht_lookup_size] + } for _, info := range res.Infos { if *info.getNodeID() == t.nodeID { continue @@ -331,8 +350,29 @@ func (t *dht) doMaintenance() { t.ping(successor, nil) successor.pings++ successor.throttle += time.Second + ///// + if now.Sub(t.search) > 30*time.Second { + t.search = now + target := successor.getNodeID().prev() + sinfo, isIn := t.core.searches.searches[target] + if !isIn { + var mask NodeID + for idx := range mask { + mask[idx] = 0xff + } + sinfo = t.core.searches.newIterSearch(&target, &mask) + } + t.core.searches.continueSearch(sinfo) + } + ///// + return + fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", successor.throttle, "nodeID:", successor.getNodeID()[:8], "coords:", successor.coords) + for infoID := range t.table { + fmt.Println("DEBUG other info:", infoID[:8], "ordered", dht_ordered(&t.nodeID, &infoID, successor.getNodeID()), "swapped:", dht_ordered(&t.nodeID, successor.getNodeID(), &infoID)) + } if successor.throttle > 30*time.Second { successor.throttle = 30 * time.Second } + fmt.Println("Table size:", len(t.table)) } } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index de463b43..f9511d5a 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -183,8 +183,10 @@ func (p *peer) linkLoop() { } p.sendSwitchMsg() case _ = <-tick.C: - if p.dinfo != nil { - p.core.dht.peers <- p.dinfo + pdinfo := p.dinfo // FIXME this is a bad workarond NPE on the next line + if pdinfo != nil { + dinfo := *pdinfo + p.core.dht.peers <- &dinfo } } } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 1027eabd..27aad8d7 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -92,6 +92,7 @@ func (r *router) mainLoop() { r.sendPacket(p) case info := <-r.core.dht.peers: r.core.dht.insert(info) + info.throttle = 0 case <-r.reset: r.core.sessions.resetInits() r.core.dht.reset() From b809adf9812c7df4526d79931b6577a7284319d6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Oct 2018 17:57:48 +0100 Subject: [PATCH 014/145] Add FriendlyName option, show friendly name and real endpoint in admin socket/yggdrasilctl --- src/yggdrasil/admin.go | 5 ++++ src/yggdrasil/config/config.go | 1 + src/yggdrasil/core.go | 49 +++++++++++++++++++++------------- src/yggdrasil/peer.go | 42 ++++++++++++++++------------- src/yggdrasil/router.go | 2 +- src/yggdrasil/tcp.go | 2 +- yggdrasilctl.go | 6 +++++ 7 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 9d3866f8..630db177 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -470,6 +470,7 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { {"ip", a.core.GetAddress().String()}, {"subnet", a.core.GetSubnet().String()}, {"coords", fmt.Sprint(coords)}, + {"friendly_name", a.core.friendlyName}, } return &self } @@ -492,6 +493,8 @@ func (a *admin) getData_getPeers() []admin_nodeInfo { {"uptime", int(time.Since(p.firstSeen).Seconds())}, {"bytes_sent", atomic.LoadUint64(&p.bytesSent)}, {"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)}, + {"endpoint", p.endpoint}, + {"friendly_name", p.friendlyName}, } peerInfos = append(peerInfos, info) } @@ -516,6 +519,8 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { {"port", elem.port}, {"bytes_sent", atomic.LoadUint64(&peer.bytesSent)}, {"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)}, + {"endpoint", peer.endpoint}, + {"friendly_name", peer.friendlyName}, } peerInfos = append(peerInfos, info) } diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index bcf4f322..530c18cb 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -2,6 +2,7 @@ package config // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { + FriendlyName string `comment:"Friendly name for this node. It is visible to direct peers."` 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."` Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 015147c4..8f61bf69 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -16,29 +16,31 @@ import ( // object for each Yggdrasil node you plan to run. type Core struct { // This is the main data structure that holds everything else for a node - boxPub boxPubKey - boxPriv boxPrivKey - sigPub sigPubKey - sigPriv sigPrivKey - switchTable switchTable - peers peers - sigs sigManager - sessions sessions - router router - dht dht - tun tunDevice - admin admin - searches searches - multicast multicast - tcp tcpInterface - log *log.Logger - ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this + boxPub boxPubKey + boxPriv boxPrivKey + sigPub sigPubKey + sigPriv sigPrivKey + friendlyName string + switchTable switchTable + peers peers + sigs sigManager + sessions sessions + router router + dht dht + tun tunDevice + admin admin + searches searches + multicast multicast + tcp tcpInterface + log *log.Logger + ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this } func (c *Core) init(bpub *boxPubKey, bpriv *boxPrivKey, spub *sigPubKey, - spriv *sigPrivKey) { + spriv *sigPrivKey, + friendlyname string) { // TODO separate init and start functions // Init sets up structs // Start launches goroutines that depend on structs being set up @@ -49,6 +51,7 @@ func (c *Core) init(bpub *boxPubKey, } c.boxPub, c.boxPriv = *bpub, *bpriv c.sigPub, c.sigPriv = *spub, *spriv + c.friendlyName = friendlyname c.admin.core = c c.sigs.init() c.searches.init(c) @@ -61,6 +64,14 @@ func (c *Core) init(bpub *boxPubKey, c.tun.init(c) } +// Gets the friendly name of this node, as specified in the NodeConfig. +func (c *Core) GetFriendlyName() string { + if c.friendlyName == "" { + return "(none)" + } + return c.friendlyName +} + // Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging // through the provided log.Logger. The started stack will include TCP and UDP // sockets, a multicast discovery socket, an admin socket, router, switch and @@ -94,7 +105,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { copy(sigPub[:], sigPubHex) copy(sigPriv[:], sigPrivHex) - c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) + c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, nc.FriendlyName) c.admin.init(c, nc.AdminListen) if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index de463b43..4f792376 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -79,30 +79,34 @@ 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 - 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 *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 + 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 *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. -func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey) *peer { +func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string, friendlyname string) *peer { now := time.Now() p := peer{box: *box, - sig: *sig, - shared: *getSharedKey(&ps.core.boxPriv, box), - linkShared: *linkShared, - firstSeen: now, - doSend: make(chan struct{}, 1), - core: ps.core} + sig: *sig, + shared: *getSharedKey(&ps.core.boxPriv, box), + linkShared: *linkShared, + endpoint: endpoint, + friendlyName: friendlyname, + firstSeen: now, + doSend: make(chan struct{}, 1), + core: ps.core} ps.mutex.Lock() defer ps.mutex.Unlock() oldPorts := ps.getPorts() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index d2a8c43b..dcc6a5c4 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -47,7 +47,7 @@ func (r *router) init(core *Core) { r.core = core r.addr = *address_addrForNodeID(&r.core.dht.nodeID) in := make(chan []byte, 32) // TODO something better than this... - p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}) + p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.GetFriendlyName()) p.out = func(packet []byte) { // This is to make very sure it never blocks select { diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 0bc5802b..58d9422e 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -287,7 +287,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { }() // Note that multiple connections to the same node are allowed // E.g. over different interfaces - p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link)) + p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String(), "(none)") p.linkOut = make(chan []byte, 1) in := func(bs []byte) { p.handlePacket(bs) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index d98386b7..2b5b79a4 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -155,6 +155,12 @@ func main() { minutes := uint(preformatted.(float64)/60) % 60 hours := uint(preformatted.(float64) / 60 / 60) formatted = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) + case "friendly_name": + if len(preformatted.(string)) > 32 { + formatted = fmt.Sprintf("%s...", preformatted.(string)[:32]) + } else { + formatted = preformatted.(string) + } default: formatted = fmt.Sprint(preformatted) } From efe6cec11a865da58f53e44cdfaea45a384f4862 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Oct 2018 12:28:21 -0500 Subject: [PATCH 015/145] more debugging, trying to understand bootstrap issues --- src/yggdrasil/dht.go | 103 +++++++++++++++++++++++++++++------------- src/yggdrasil/peer.go | 3 +- 2 files changed, 73 insertions(+), 33 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 2a97ec03..b5bf926e 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -57,7 +57,6 @@ type dht struct { table map[NodeID]*dhtInfo peers chan *dhtInfo // other goroutines put incoming dht updates here reqs map[boxPubKey]map[NodeID]time.Time - search time.Time } // Initializes the DHT @@ -85,12 +84,11 @@ func (t *dht) reset() { } t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) - t.search = time.Now().Add(-time.Minute) if successor != nil { - t.ping(successor, &t.nodeID) + t.ping(successor, nil) } if predecessor != nil { - t.ping(predecessor, &t.nodeID) + t.ping(predecessor, nil) } } @@ -199,7 +197,24 @@ func (t *dht) handleReq(req *dhtReq) { coords: req.Coords, } // For bootstrapping to work, we need to add these nodes to the table - t.insert(&info) + //t.insert(&info) + // FIXME? DEBUG testing what happens if we only add better predecessors/successors + var successor *dhtInfo + var predecessor *dhtInfo + for infoID, v := range t.table { + // Get current successor and predecessor + if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { + successor = v + } + if predecessor == nil || dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { + predecessor = v + } + } + if successor != nil && dht_ordered(&t.nodeID, info.getNodeID(), successor.getNodeID()) { + t.insert(&info) + } else if predecessor != nil && dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { + t.insert(&info) + } } // Sends a lookup response to the specified node. @@ -263,10 +278,10 @@ func (t *dht) handleRes(res *dhtRes) { var predecessor *dhtInfo for infoID, info := range t.table { // Get current successor and predecessor - if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { + if successor != nil && dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { successor = info } - if predecessor == nil || dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { + if predecessor != nil && dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { predecessor = info } } @@ -284,10 +299,9 @@ func (t *dht) handleRes(res *dhtRes) { // Send a request to all better successors or predecessors // We could try sending to only the best, but then packet loss matters more if successor == nil || dht_ordered(&t.nodeID, info.getNodeID(), successor.getNodeID()) { - t.ping(info, &t.nodeID) - } - if predecessor == nil || dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { - t.ping(info, &t.nodeID) + t.ping(info, nil) + } else if predecessor == nil || dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { + t.ping(info, nil) } } // TODO add everyting else to a rumor mill for later use? (when/how?) @@ -322,7 +336,7 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { func (t *dht) ping(info *dhtInfo, target *NodeID) { // Creates a req for the node at dhtInfo, asking them about the target (if one is given) or themself (if no target is given) if target == nil { - target = info.getNodeID() + target = &t.nodeID } loc := t.core.switchTable.getLocator() coords := loc.getCoords() @@ -337,42 +351,67 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { func (t *dht) doMaintenance() { // Ping successor, asking for their predecessor, and clean up old/expired info var successor *dhtInfo + var predecessor *dhtInfo + toPing := make(map[NodeID]*dhtInfo) now := time.Now() for infoID, info := range t.table { if now.Sub(info.recv) > time.Minute || info.pings > 3 { delete(t.table, infoID) } else if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { successor = info + } else if predecessor == nil || dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { + predecessor = info } } + ////////////////////////////////////////////////////////////////////////////// + t.core.switchTable.mutex.RLock() + parentPort := t.core.switchTable.parent + parentInfo := t.core.switchTable.data.peers[parentPort] + t.core.switchTable.mutex.RUnlock() + ports := t.core.peers.getPorts() + if parent, isIn := ports[parentPort]; isIn { + loc := parentInfo.locator.clone() + end := len(loc.coords) + if end > 0 { + end -= 1 + } + loc.coords = loc.coords[:end] + pinfo := dhtInfo{key: parent.box, coords: loc.getCoords()} + t.insert(&pinfo) + } + ////////////////////////////////////////////////////////////////////////////// + if successor != nil { + toPing[*successor.getNodeID()] = successor + } + if predecessor != nil { + toPing[*predecessor.getNodeID()] = predecessor + } + for _, info := range toPing { + if now.Sub(info.recv) > info.throttle { + t.ping(info, nil) + info.pings++ + info.throttle += time.Second + if info.throttle > 30*time.Second { + info.throttle = 30 * time.Second + } + //fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", info.throttle, "nodeID:", info.getNodeID()[:8], "coords:", info.coords) + } + } + return + ////////////////////////////////////////////////////////////////////////////// if successor != nil && now.Sub(successor.recv) > successor.throttle { t.ping(successor, nil) successor.pings++ successor.throttle += time.Second - ///// - if now.Sub(t.search) > 30*time.Second { - t.search = now - target := successor.getNodeID().prev() - sinfo, isIn := t.core.searches.searches[target] - if !isIn { - var mask NodeID - for idx := range mask { - mask[idx] = 0xff - } - sinfo = t.core.searches.newIterSearch(&target, &mask) - } - t.core.searches.continueSearch(sinfo) - } - ///// - return + //return fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", successor.throttle, "nodeID:", successor.getNodeID()[:8], "coords:", successor.coords) - for infoID := range t.table { - fmt.Println("DEBUG other info:", infoID[:8], "ordered", dht_ordered(&t.nodeID, &infoID, successor.getNodeID()), "swapped:", dht_ordered(&t.nodeID, successor.getNodeID(), &infoID)) - } + //for infoID := range t.table { + // fmt.Println("DEBUG other info:", infoID[:8], "ordered", dht_ordered(&t.nodeID, &infoID, successor.getNodeID()), "swapped:", dht_ordered(&t.nodeID, successor.getNodeID(), &infoID)) + //} if successor.throttle > 30*time.Second { successor.throttle = 30 * time.Second } - fmt.Println("Table size:", len(t.table)) + //fmt.Println("Table size:", len(t.table)) } } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index f9511d5a..eee40fd6 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -183,6 +183,7 @@ func (p *peer) linkLoop() { } p.sendSwitchMsg() case _ = <-tick.C: + break // FIXME disabled the below completely to test something pdinfo := p.dinfo // FIXME this is a bad workarond NPE on the next line if pdinfo != nil { dinfo := *pdinfo @@ -332,7 +333,7 @@ func (p *peer) handleSwitchMsg(packet []byte) { key: p.box, coords: loc.getCoords(), } - p.core.dht.peers <- &dinfo + //p.core.dht.peers <- &dinfo p.dinfo = &dinfo } From bcbd24120d806c0f004fd1d1e3b4ca80aaff2732 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Oct 2018 14:57:04 -0500 Subject: [PATCH 016/145] keep track of all keys we're supposed to care about in the dht, don't give special treatment to successors/predecessors --- src/yggdrasil/dht.go | 146 +++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 89 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index b5bf926e..c5102f04 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -52,11 +52,23 @@ type dhtRes struct { // The main DHT struct. type dht struct { - core *Core - nodeID NodeID - table map[NodeID]*dhtInfo - peers chan *dhtInfo // other goroutines put incoming dht updates here - reqs map[boxPubKey]map[NodeID]time.Time + core *Core + nodeID NodeID + table map[NodeID]*dhtInfo + peers chan *dhtInfo // other goroutines put incoming dht updates here + reqs map[boxPubKey]map[NodeID]time.Time + targets [NodeIDLen*8 + 1]NodeID +} + +func (nodeID NodeID) add(toAdd *NodeID) NodeID { + var accumulator uint16 + for idx := len(nodeID) - 1; idx >= 0; idx-- { + accumulator += uint16(nodeID[idx]) + accumulator += uint16(toAdd[idx]) + nodeID[idx] = byte(accumulator) + accumulator >>= 8 + } + return nodeID } // Initializes the DHT @@ -64,32 +76,27 @@ func (t *dht) init(c *Core) { t.core = c t.nodeID = *t.core.GetNodeID() t.peers = make(chan *dhtInfo, 1024) + getDist := func(bit int) *NodeID { + nBits := NodeIDLen * 8 + theByte := (nBits - bit) / 8 + theBitmask := uint8(0x80) >> uint8(nBits-bit) + var nid NodeID + //fmt.Println("DEBUG: bit", bit, "theByte", theByte) + nid[theByte] = theBitmask + return &nid + } + for idx := range t.targets { + t.targets[idx] = t.nodeID.add(getDist(idx + 1)) + } + t.targets[len(t.targets)-1] = t.nodeID // Last one wraps around to self t.reset() } // Resets the DHT in response to coord changes // This empties all info from the DHT and drops outstanding requests -// It sends a ping to the old successor and predecessor, in case they're still around func (t *dht) reset() { - var successor *dhtInfo - var predecessor *dhtInfo - for infoID, info := range t.table { - // Get current successor and predecessor - if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { - successor = info - } - if predecessor == nil || dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { - predecessor = info - } - } t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) - if successor != nil { - t.ping(successor, nil) - } - if predecessor != nil { - t.ping(predecessor, nil) - } } // Does a DHT lookup and returns up to dht_lookup_size results @@ -197,24 +204,7 @@ func (t *dht) handleReq(req *dhtReq) { coords: req.Coords, } // For bootstrapping to work, we need to add these nodes to the table - //t.insert(&info) - // FIXME? DEBUG testing what happens if we only add better predecessors/successors - var successor *dhtInfo - var predecessor *dhtInfo - for infoID, v := range t.table { - // Get current successor and predecessor - if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { - successor = v - } - if predecessor == nil || dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { - predecessor = v - } - } - if successor != nil && dht_ordered(&t.nodeID, info.getNodeID(), successor.getNodeID()) { - t.insert(&info) - } else if predecessor != nil && dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { - t.insert(&info) - } + t.insert(&info) } // Sends a lookup response to the specified node. @@ -274,17 +264,6 @@ func (t *dht) handleRes(res *dhtRes) { coords: res.Coords, } t.insert(&rinfo) // Or at the end, after checking successor/predecessor? - var successor *dhtInfo - var predecessor *dhtInfo - for infoID, info := range t.table { - // Get current successor and predecessor - if successor != nil && dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { - successor = info - } - if predecessor != nil && dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { - predecessor = info - } - } if len(res.Infos) > dht_lookup_size { res.Infos = res.Infos[:dht_lookup_size] } @@ -296,11 +275,7 @@ func (t *dht) handleRes(res *dhtRes) { // TODO? don't skip if coords are different? continue } - // Send a request to all better successors or predecessors - // We could try sending to only the best, but then packet loss matters more - if successor == nil || dht_ordered(&t.nodeID, info.getNodeID(), successor.getNodeID()) { - t.ping(info, nil) - } else if predecessor == nil || dht_ordered(predecessor.getNodeID(), info.getNodeID(), &t.nodeID) { + if t.isImportant(info.getNodeID()) { t.ping(info, nil) } } @@ -349,18 +324,13 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { } func (t *dht) doMaintenance() { - // Ping successor, asking for their predecessor, and clean up old/expired info - var successor *dhtInfo - var predecessor *dhtInfo toPing := make(map[NodeID]*dhtInfo) now := time.Now() for infoID, info := range t.table { if now.Sub(info.recv) > time.Minute || info.pings > 3 { delete(t.table, infoID) - } else if successor == nil || dht_ordered(&t.nodeID, &infoID, successor.getNodeID()) { - successor = info - } else if predecessor == nil || dht_ordered(predecessor.getNodeID(), &infoID, &t.nodeID) { - predecessor = info + } else if t.isImportant(info.getNodeID()) { + toPing[infoID] = info } } ////////////////////////////////////////////////////////////////////////////// @@ -380,38 +350,36 @@ func (t *dht) doMaintenance() { t.insert(&pinfo) } ////////////////////////////////////////////////////////////////////////////// - if successor != nil { - toPing[*successor.getNodeID()] = successor - } - if predecessor != nil { - toPing[*predecessor.getNodeID()] = predecessor - } for _, info := range toPing { if now.Sub(info.recv) > info.throttle { - t.ping(info, nil) + t.ping(info, info.getNodeID()) info.pings++ info.throttle += time.Second if info.throttle > 30*time.Second { info.throttle = 30 * time.Second } - //fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", info.throttle, "nodeID:", info.getNodeID()[:8], "coords:", info.coords) + //continue + fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", info.throttle, "nodeID:", info.getNodeID()[:8], "coords:", info.coords) } } - return - ////////////////////////////////////////////////////////////////////////////// - if successor != nil && - now.Sub(successor.recv) > successor.throttle { - t.ping(successor, nil) - successor.pings++ - successor.throttle += time.Second - //return - fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", successor.throttle, "nodeID:", successor.getNodeID()[:8], "coords:", successor.coords) - //for infoID := range t.table { - // fmt.Println("DEBUG other info:", infoID[:8], "ordered", dht_ordered(&t.nodeID, &infoID, successor.getNodeID()), "swapped:", dht_ordered(&t.nodeID, successor.getNodeID(), &infoID)) - //} - if successor.throttle > 30*time.Second { - successor.throttle = 30 * time.Second - } - //fmt.Println("Table size:", len(t.table)) - } +} + +func (t *dht) isImportant(nodeID *NodeID) bool { + // TODO persistently store stuff about best nodes, so we don't need to keep doing this + // Ideally switch to a better data structure... linked list? + for _, target := range t.targets { + // Get the best known node for this target + var best *dhtInfo + for _, info := range t.table { + if best == nil || dht_ordered(best.getNodeID(), info.getNodeID(), &target) { + best = info + } + } + if best != nil && dht_ordered(best.getNodeID(), nodeID, &target) { + // This is an equal or better finger table entry than what we currently have + return true + } + } + // We didn't find anything where this is better, so it must be worse + return false } From f0bd40ff6853943a66494e394979a5a924f0eba9 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Oct 2018 15:10:18 -0500 Subject: [PATCH 017/145] more testing --- src/yggdrasil/dht.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index c5102f04..c89663a4 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -102,12 +102,22 @@ func (t *dht) reset() { // Does a DHT lookup and returns up to dht_lookup_size results // If allowWorse = true, begins with best know predecessor for ID and works backwards, even if these nodes are worse predecessors than we are, to be used when intializing searches // If allowWorse = false, begins with the best known successor for ID and works backwards (next is predecessor, etc, inclusive of the ID if it's a known node) -func (t *dht) lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { +func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo { + var results []*dhtInfo + for infoID, info := range t.table { + if everything || t.isImportant(&infoID) { + results = append(results, info) + } + } + return results +} + +func (t *dht) old_lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { var results []*dhtInfo var successor *dhtInfo sTarget := t.nodeID.next() for infoID, info := range t.table { - if allowWorse || dht_ordered(&t.nodeID, &infoID, nodeID) { + if true || allowWorse || dht_ordered(&t.nodeID, &infoID, nodeID) { results = append(results, info) } else { if successor == nil || dht_ordered(&sTarget, &infoID, successor.getNodeID()) { @@ -122,7 +132,7 @@ func (t *dht) lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { results = append([]*dhtInfo{successor}, results...) } if len(results) > dht_lookup_size { - results = results[:dht_lookup_size] + //results = results[:dht_lookup_size] //FIXME debug } return results } @@ -265,7 +275,7 @@ func (t *dht) handleRes(res *dhtRes) { } t.insert(&rinfo) // Or at the end, after checking successor/predecessor? if len(res.Infos) > dht_lookup_size { - res.Infos = res.Infos[:dht_lookup_size] + //res.Infos = res.Infos[:dht_lookup_size] //FIXME debug } for _, info := range res.Infos { if *info.getNodeID() == t.nodeID { From aab0502a4ad69ebf23b99e7d360b9688bbc2d8ba Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 21 Oct 2018 23:20:14 +0100 Subject: [PATCH 018/145] Remove friendlyname traces, preserve endpoints --- src/yggdrasil/admin.go | 3 --- src/yggdrasil/config/config.go | 1 - src/yggdrasil/core.go | 49 +++++++++++++--------------------- src/yggdrasil/debug.go | 2 +- src/yggdrasil/peer.go | 17 ++++++------ src/yggdrasil/router.go | 2 +- src/yggdrasil/tcp.go | 2 +- yggdrasilctl.go | 6 ----- 8 files changed, 30 insertions(+), 52 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 630db177..98bb3e4b 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -470,7 +470,6 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { {"ip", a.core.GetAddress().String()}, {"subnet", a.core.GetSubnet().String()}, {"coords", fmt.Sprint(coords)}, - {"friendly_name", a.core.friendlyName}, } return &self } @@ -494,7 +493,6 @@ func (a *admin) getData_getPeers() []admin_nodeInfo { {"bytes_sent", atomic.LoadUint64(&p.bytesSent)}, {"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)}, {"endpoint", p.endpoint}, - {"friendly_name", p.friendlyName}, } peerInfos = append(peerInfos, info) } @@ -520,7 +518,6 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { {"bytes_sent", atomic.LoadUint64(&peer.bytesSent)}, {"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)}, {"endpoint", peer.endpoint}, - {"friendly_name", peer.friendlyName}, } peerInfos = append(peerInfos, info) } diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 530c18cb..bcf4f322 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -2,7 +2,6 @@ package config // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { - FriendlyName string `comment:"Friendly name for this node. It is visible to direct peers."` 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."` Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 8f61bf69..015147c4 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -16,31 +16,29 @@ import ( // object for each Yggdrasil node you plan to run. type Core struct { // This is the main data structure that holds everything else for a node - boxPub boxPubKey - boxPriv boxPrivKey - sigPub sigPubKey - sigPriv sigPrivKey - friendlyName string - switchTable switchTable - peers peers - sigs sigManager - sessions sessions - router router - dht dht - tun tunDevice - admin admin - searches searches - multicast multicast - tcp tcpInterface - log *log.Logger - ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this + boxPub boxPubKey + boxPriv boxPrivKey + sigPub sigPubKey + sigPriv sigPrivKey + switchTable switchTable + peers peers + sigs sigManager + sessions sessions + router router + dht dht + tun tunDevice + admin admin + searches searches + multicast multicast + tcp tcpInterface + log *log.Logger + ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this } func (c *Core) init(bpub *boxPubKey, bpriv *boxPrivKey, spub *sigPubKey, - spriv *sigPrivKey, - friendlyname string) { + spriv *sigPrivKey) { // TODO separate init and start functions // Init sets up structs // Start launches goroutines that depend on structs being set up @@ -51,7 +49,6 @@ func (c *Core) init(bpub *boxPubKey, } c.boxPub, c.boxPriv = *bpub, *bpriv c.sigPub, c.sigPriv = *spub, *spriv - c.friendlyName = friendlyname c.admin.core = c c.sigs.init() c.searches.init(c) @@ -64,14 +61,6 @@ func (c *Core) init(bpub *boxPubKey, c.tun.init(c) } -// Gets the friendly name of this node, as specified in the NodeConfig. -func (c *Core) GetFriendlyName() string { - if c.friendlyName == "" { - return "(none)" - } - return c.friendlyName -} - // Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging // through the provided log.Logger. The started stack will include TCP and UDP // sockets, a multicast discovery socket, an admin socket, router, switch and @@ -105,7 +94,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { copy(sigPub[:], sigPubHex) copy(sigPriv[:], sigPrivHex) - c.init(&boxPub, &boxPriv, &sigPub, &sigPriv, nc.FriendlyName) + c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) c.admin.init(c, nc.AdminListen) if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 892529b6..6e749792 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -84,7 +84,7 @@ func (c *Core) DEBUG_getPeers() *peers { func (ps *peers) DEBUG_newPeer(box boxPubKey, sig sigPubKey, link boxSharedKey) *peer { //in <-chan []byte, //out chan<- []byte) *peer { - return ps.newPeer(&box, &sig, &link) //, in, out) + return ps.newPeer(&box, &sig, &link, "(simulator)") //, in, out) } /* diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 4f792376..092a3c69 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -96,17 +96,16 @@ type peer struct { } // Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number. -func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string, friendlyname string) *peer { +func (ps *peers) newPeer(box *boxPubKey, sig *sigPubKey, linkShared *boxSharedKey, endpoint string) *peer { now := time.Now() p := peer{box: *box, - sig: *sig, - shared: *getSharedKey(&ps.core.boxPriv, box), - linkShared: *linkShared, - endpoint: endpoint, - friendlyName: friendlyname, - firstSeen: now, - doSend: make(chan struct{}, 1), - core: ps.core} + sig: *sig, + shared: *getSharedKey(&ps.core.boxPriv, box), + linkShared: *linkShared, + endpoint: endpoint, + firstSeen: now, + doSend: make(chan struct{}, 1), + core: ps.core} ps.mutex.Lock() defer ps.mutex.Unlock() oldPorts := ps.getPorts() diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index dcc6a5c4..86eb193c 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -47,7 +47,7 @@ func (r *router) init(core *Core) { r.core = core r.addr = *address_addrForNodeID(&r.core.dht.nodeID) in := make(chan []byte, 32) // TODO something better than this... - p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)", r.core.GetFriendlyName()) + p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)") p.out = func(packet []byte) { // This is to make very sure it never blocks select { diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 58d9422e..5ca66304 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -287,7 +287,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { }() // Note that multiple connections to the same node are allowed // E.g. over different interfaces - p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String(), "(none)") + p := iface.core.peers.newPeer(&info.box, &info.sig, getSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String()) p.linkOut = make(chan []byte, 1) in := func(bs []byte) { p.handlePacket(bs) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 2b5b79a4..d98386b7 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -155,12 +155,6 @@ func main() { minutes := uint(preformatted.(float64)/60) % 60 hours := uint(preformatted.(float64) / 60 / 60) formatted = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) - case "friendly_name": - if len(preformatted.(string)) > 32 { - formatted = fmt.Sprintf("%s...", preformatted.(string)[:32]) - } else { - formatted = preformatted.(string) - } default: formatted = fmt.Sprint(preformatted) } From 5e3959f1d0d26c0503208a6ac34fa34fc0875b5f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Oct 2018 17:40:43 -0500 Subject: [PATCH 019/145] yet more debugging --- misc/sim/treesim.go | 2 +- src/yggdrasil/dht.go | 130 +++++++++++++++++++--------------------- src/yggdrasil/peer.go | 2 +- src/yggdrasil/search.go | 11 +++- 4 files changed, 72 insertions(+), 73 deletions(-) diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 793ef219..fc5e8449 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -437,7 +437,7 @@ func main() { pingNodes(kstore) //pingBench(kstore) // Only after disabling debug output //stressTest(kstore) - //time.Sleep(120*time.Second) + time.Sleep(120 * time.Second) dumpDHTSize(kstore) // note that this uses racey functions to read things... if false { // This connects the sim to the local network diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index c89663a4..bbf08ca8 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -11,7 +11,7 @@ import ( "time" ) -const dht_lookup_size = 16 +const dht_lookup_size = 4 // dhtInfo represents everything we know about a node in the DHT. // This includes its key, a cache of it's NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance. @@ -52,23 +52,11 @@ type dhtRes struct { // The main DHT struct. type dht struct { - core *Core - nodeID NodeID - table map[NodeID]*dhtInfo - peers chan *dhtInfo // other goroutines put incoming dht updates here - reqs map[boxPubKey]map[NodeID]time.Time - targets [NodeIDLen*8 + 1]NodeID -} - -func (nodeID NodeID) add(toAdd *NodeID) NodeID { - var accumulator uint16 - for idx := len(nodeID) - 1; idx >= 0; idx-- { - accumulator += uint16(nodeID[idx]) - accumulator += uint16(toAdd[idx]) - nodeID[idx] = byte(accumulator) - accumulator >>= 8 - } - return nodeID + core *Core + nodeID NodeID + table map[NodeID]*dhtInfo + peers chan *dhtInfo // other goroutines put incoming dht updates here + reqs map[boxPubKey]map[NodeID]time.Time } // Initializes the DHT @@ -76,19 +64,6 @@ func (t *dht) init(c *Core) { t.core = c t.nodeID = *t.core.GetNodeID() t.peers = make(chan *dhtInfo, 1024) - getDist := func(bit int) *NodeID { - nBits := NodeIDLen * 8 - theByte := (nBits - bit) / 8 - theBitmask := uint8(0x80) >> uint8(nBits-bit) - var nid NodeID - //fmt.Println("DEBUG: bit", bit, "theByte", theByte) - nid[theByte] = theBitmask - return &nid - } - for idx := range t.targets { - t.targets[idx] = t.nodeID.add(getDist(idx + 1)) - } - t.targets[len(t.targets)-1] = t.nodeID // Last one wraps around to self t.reset() } @@ -103,11 +78,15 @@ func (t *dht) reset() { // If allowWorse = true, begins with best know predecessor for ID and works backwards, even if these nodes are worse predecessors than we are, to be used when intializing searches // If allowWorse = false, begins with the best known successor for ID and works backwards (next is predecessor, etc, inclusive of the ID if it's a known node) func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo { - var results []*dhtInfo - for infoID, info := range t.table { - if everything || t.isImportant(&infoID) { - results = append(results, info) - } + results := make([]*dhtInfo, 0, len(t.table)) + for _, info := range t.table { + results = append(results, info) + } + sort.SliceStable(results, func(i, j int) bool { + return dht_ordered(results[j].getNodeID(), results[i].getNodeID(), nodeID) + }) + if len(results) > dht_lookup_size { + //results = results[:dht_lookup_size] //FIXME debug } return results } @@ -277,6 +256,7 @@ func (t *dht) handleRes(res *dhtRes) { if len(res.Infos) > dht_lookup_size { //res.Infos = res.Infos[:dht_lookup_size] //FIXME debug } + imp := t.getImportant() for _, info := range res.Infos { if *info.getNodeID() == t.nodeID { continue @@ -285,7 +265,7 @@ func (t *dht) handleRes(res *dhtRes) { // TODO? don't skip if coords are different? continue } - if t.isImportant(info.getNodeID()) { + if t.isImportant(info, imp) { t.ping(info, nil) } } @@ -336,30 +316,18 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { func (t *dht) doMaintenance() { toPing := make(map[NodeID]*dhtInfo) now := time.Now() + imp := t.getImportant() + good := make(map[NodeID]*dhtInfo) + for _, info := range imp { + good[*info.getNodeID()] = info + } for infoID, info := range t.table { if now.Sub(info.recv) > time.Minute || info.pings > 3 { delete(t.table, infoID) - } else if t.isImportant(info.getNodeID()) { + } else if t.isImportant(info, imp) { toPing[infoID] = info } } - ////////////////////////////////////////////////////////////////////////////// - t.core.switchTable.mutex.RLock() - parentPort := t.core.switchTable.parent - parentInfo := t.core.switchTable.data.peers[parentPort] - t.core.switchTable.mutex.RUnlock() - ports := t.core.peers.getPorts() - if parent, isIn := ports[parentPort]; isIn { - loc := parentInfo.locator.clone() - end := len(loc.coords) - if end > 0 { - end -= 1 - } - loc.coords = loc.coords[:end] - pinfo := dhtInfo{key: parent.box, coords: loc.getCoords()} - t.insert(&pinfo) - } - ////////////////////////////////////////////////////////////////////////////// for _, info := range toPing { if now.Sub(info.recv) > info.throttle { t.ping(info, info.getNodeID()) @@ -368,28 +336,52 @@ func (t *dht) doMaintenance() { if info.throttle > 30*time.Second { info.throttle = 30 * time.Second } - //continue + continue fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", info.throttle, "nodeID:", info.getNodeID()[:8], "coords:", info.coords) } } } -func (t *dht) isImportant(nodeID *NodeID) bool { - // TODO persistently store stuff about best nodes, so we don't need to keep doing this - // Ideally switch to a better data structure... linked list? - for _, target := range t.targets { - // Get the best known node for this target - var best *dhtInfo - for _, info := range t.table { - if best == nil || dht_ordered(best.getNodeID(), info.getNodeID(), &target) { - best = info - } +func (t *dht) getImportant() []*dhtInfo { + // Get a list of all known nodes + infos := make([]*dhtInfo, 0, len(t.table)) + for _, info := range t.table { + infos = append(infos, info) + } + // Sort them by increasing order in distance along the ring + sort.SliceStable(infos, func(i, j int) bool { + // Sort in order of successors + return dht_ordered(&t.nodeID, infos[i].getNodeID(), infos[j].getNodeID()) + }) + // Keep the ones that are no further than the closest seen so far + minDist := ^uint64(0) + loc := t.core.switchTable.getLocator() + important := infos[:0] + for _, info := range infos { + dist := uint64(loc.dist(info.coords)) + if dist < minDist { + minDist = dist + important = append(important, info) } - if best != nil && dht_ordered(best.getNodeID(), nodeID, &target) { - // This is an equal or better finger table entry than what we currently have + } + return important +} + +func (t *dht) isImportant(ninfo *dhtInfo, important []*dhtInfo) bool { + // Check if ninfo is of equal or greater importance to what we already know + loc := t.core.switchTable.getLocator() + ndist := uint64(loc.dist(ninfo.coords)) + minDist := ^uint64(0) + for _, info := range important { + dist := uint64(loc.dist(info.coords)) + if dist < minDist { + minDist = dist + } + if dht_ordered(&t.nodeID, ninfo.getNodeID(), info.getNodeID()) && ndist <= minDist { + // This node is at least as close in both key space and tree space return true } } - // We didn't find anything where this is better, so it must be worse + // We didn't find any important node that ninfo is better than return false } diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index eee40fd6..05351e60 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -183,7 +183,7 @@ func (p *peer) linkLoop() { } p.sendSwitchMsg() case _ = <-tick.C: - break // FIXME disabled the below completely to test something + //break // FIXME disabled the below completely to test something pdinfo := p.dinfo // FIXME this is a bad workarond NPE on the next line if pdinfo != nil { dinfo := *pdinfo diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index dd6b09cf..d27b8c5a 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -12,6 +12,7 @@ package yggdrasil // A new search packet is sent periodically, once per second, in case a packet was dropped (this slowly causes the search to become parallel if the search doesn't timeout but also doesn't finish within 1 second for whatever reason) import ( + "fmt" "sort" "time" ) @@ -73,6 +74,9 @@ func (s *searches) handleDHTRes(res *dhtRes) { sinfo, isIn := s.searches[res.Dest] if !isIn || s.checkDHTRes(sinfo, res) { // Either we don't recognize this search, or we just finished it + if isIn { + fmt.Println("DEBUG: search finished, length:", len(sinfo.visited)) + } return } else { // Add to the search and continue @@ -92,7 +96,7 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { continue } - if dht_ordered(from.getNodeID(), info.getNodeID(), &res.Dest) { + if true || dht_ordered(from.getNodeID(), info.getNodeID(), &res.Dest) { sinfo.toVisit = append(sinfo.toVisit, info) } } @@ -107,11 +111,13 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { } // Sort sort.SliceStable(sinfo.toVisit, func(i, j int) bool { + // Should return true if i is closer to the destination than j + // FIXME for some reason it works better backwards, why?! return dht_ordered(sinfo.toVisit[j].getNodeID(), sinfo.toVisit[i].getNodeID(), &res.Dest) }) // Truncate to some maximum size if len(sinfo.toVisit) > search_MAX_SEARCH_SIZE { - sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] + //sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] } } @@ -121,6 +127,7 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup delete(s.searches, sinfo.dest) + fmt.Println("DEBUG: search abandoned, length:", len(sinfo.visited)) return } else { // Send to the next search target From 253861ebd39f309f19fe3e3bcd41f3b7120a8814 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 21 Oct 2018 18:15:04 -0500 Subject: [PATCH 020/145] reverse dht ownership order from predecessor to successor, this plays nicer with the default 0 bits in unknown node IDs --- src/yggdrasil/dht.go | 41 ++++++++--------------------------------- src/yggdrasil/search.go | 5 +++-- 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index bbf08ca8..398cdb74 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -11,7 +11,7 @@ import ( "time" ) -const dht_lookup_size = 4 +const dht_lookup_size = 16 // dhtInfo represents everything we know about a node in the DHT. // This includes its key, a cache of it's NodeID, coords, and timing/ping related info for deciding who/when to ping nodes for maintenance. @@ -75,43 +75,16 @@ func (t *dht) reset() { } // Does a DHT lookup and returns up to dht_lookup_size results -// If allowWorse = true, begins with best know predecessor for ID and works backwards, even if these nodes are worse predecessors than we are, to be used when intializing searches -// If allowWorse = false, begins with the best known successor for ID and works backwards (next is predecessor, etc, inclusive of the ID if it's a known node) func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo { results := make([]*dhtInfo, 0, len(t.table)) for _, info := range t.table { results = append(results, info) } sort.SliceStable(results, func(i, j int) bool { - return dht_ordered(results[j].getNodeID(), results[i].getNodeID(), nodeID) + return dht_ordered(nodeID, results[i].getNodeID(), results[j].getNodeID()) }) if len(results) > dht_lookup_size { - //results = results[:dht_lookup_size] //FIXME debug - } - return results -} - -func (t *dht) old_lookup(nodeID *NodeID, allowWorse bool) []*dhtInfo { - var results []*dhtInfo - var successor *dhtInfo - sTarget := t.nodeID.next() - for infoID, info := range t.table { - if true || allowWorse || dht_ordered(&t.nodeID, &infoID, nodeID) { - results = append(results, info) - } else { - if successor == nil || dht_ordered(&sTarget, &infoID, successor.getNodeID()) { - successor = info - } - } - } - sort.SliceStable(results, func(i, j int) bool { - return dht_ordered(results[j].getNodeID(), results[i].getNodeID(), nodeID) - }) - if successor != nil { - results = append([]*dhtInfo{successor}, results...) - } - if len(results) > dht_lookup_size { - //results = results[:dht_lookup_size] //FIXME debug + results = results[:dht_lookup_size] } return results } @@ -350,8 +323,9 @@ func (t *dht) getImportant() []*dhtInfo { } // Sort them by increasing order in distance along the ring sort.SliceStable(infos, func(i, j int) bool { - // Sort in order of successors - return dht_ordered(&t.nodeID, infos[i].getNodeID(), infos[j].getNodeID()) + // Sort in order of predecessors (!), reverse from chord normal, becuase it plays nicer with zero bits for unknown parts of target addresses + return dht_ordered(infos[j].getNodeID(), infos[i].getNodeID(), &t.nodeID) + //return dht_ordered(&t.nodeID, infos[i].getNodeID(), infos[j].getNodeID()) }) // Keep the ones that are no further than the closest seen so far minDist := ^uint64(0) @@ -377,7 +351,8 @@ func (t *dht) isImportant(ninfo *dhtInfo, important []*dhtInfo) bool { if dist < minDist { minDist = dist } - if dht_ordered(&t.nodeID, ninfo.getNodeID(), info.getNodeID()) && ndist <= minDist { + //if dht_ordered(&t.nodeID, ninfo.getNodeID(), info.getNodeID()) && ndist <= minDist { + if dht_ordered(info.getNodeID(), ninfo.getNodeID(), &t.nodeID) && ndist <= minDist { // This node is at least as close in both key space and tree space return true } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index d27b8c5a..b4bbb123 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -113,11 +113,12 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { sort.SliceStable(sinfo.toVisit, func(i, j int) bool { // Should return true if i is closer to the destination than j // FIXME for some reason it works better backwards, why?! - return dht_ordered(sinfo.toVisit[j].getNodeID(), sinfo.toVisit[i].getNodeID(), &res.Dest) + //return dht_ordered(sinfo.toVisit[j].getNodeID(), sinfo.toVisit[i].getNodeID(), &res.Dest) + return dht_ordered(&res.Dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) }) // Truncate to some maximum size if len(sinfo.toVisit) > search_MAX_SEARCH_SIZE { - //sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] + sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] } } From c0531627bc4d6e765bec15cc48e7f43179823d16 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 24 Oct 2018 22:03:27 -0500 Subject: [PATCH 021/145] fix some chord dht bootstrapping bugs, no known cases where it now fails --- misc/sim/treesim.go | 1 + src/yggdrasil/debug.go | 1 - src/yggdrasil/dht.go | 92 +++++++++++++++++++++++++++-------------- src/yggdrasil/search.go | 5 ++- 4 files changed, 65 insertions(+), 34 deletions(-) diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index fc5e8449..4aa463dc 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -300,6 +300,7 @@ func pingNodes(store map[[32]byte]*Node) { } case <-ch: sendTo(payload, destAddr) + //dumpDHTSize(store) // note that this uses racey functions to read things... } } ticker.Stop() diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index c71c2e5e..638bd8f4 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -233,7 +233,6 @@ func (c *Core) DEBUG_getDHTSize() int { c.router.doAdmin(func() { total = len(c.dht.table) }) - fmt.Println("DEBUG_getDHTSize():", total) return total } diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 398cdb74..3774b5c4 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -5,6 +5,16 @@ package yggdrasil // That should encorage them to ping us again sooner, and then we can reply with new info // Maybe remember old predecessor and check this during maintenance? +// TODO make sure that, if your peer is your successor or predecessor, you still bother to ping them and ask for better nodes +// Basically, don't automatically reset the dhtInfo.recv to time.Now() whenever updating them from the outside +// But *do* set it to something that won't instantly time them out or make them get pingspammed? +// Could set throttle to 0, but that's imperfect at best... pingspam + +// TODO? cache all nodes we ping (from e.g. searches), not just the important ones +// But only send maintenance pings to the important ones + +// TODO reoptimize search stuff (size, timeouts, etc) to play nicer with DHT churn + import ( "fmt" "sort" @@ -77,8 +87,12 @@ func (t *dht) reset() { // Does a DHT lookup and returns up to dht_lookup_size results func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo { results := make([]*dhtInfo, 0, len(t.table)) + //imp := t.getImportant() for _, info := range t.table { results = append(results, info) + //if t.isImportant(info, imp) { + // results = append(results, info) + //} } sort.SliceStable(results, func(i, j int) bool { return dht_ordered(nodeID, results[i].getNodeID(), results[j].getNodeID()) @@ -165,8 +179,10 @@ func (t *dht) handleReq(req *dhtReq) { key: req.Key, coords: req.Coords, } - // For bootstrapping to work, we need to add these nodes to the table - t.insert(&info) + imp := t.getImportant() + if t.isImportant(&info, imp) { + t.insert(&info) + } } // Sends a lookup response to the specified node. @@ -186,28 +202,6 @@ func (t *dht) sendRes(res *dhtRes, req *dhtReq) { t.core.router.out(packet) } -// Returns nodeID + 1 -func (nodeID NodeID) next() NodeID { - for idx := len(nodeID) - 1; idx >= 0; idx-- { - nodeID[idx] += 1 - if nodeID[idx] != 0 { - break - } - } - return nodeID -} - -// Returns nodeID - 1 -func (nodeID NodeID) prev() NodeID { - for idx := len(nodeID) - 1; idx >= 0; idx-- { - nodeID[idx] -= 1 - if nodeID[idx] != 0xff { - break - } - } - return nodeID -} - // Reads a lookup response, checks that we had sent a matching request, and processes the response info. // This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses func (t *dht) handleRes(res *dhtRes) { @@ -225,11 +219,14 @@ func (t *dht) handleRes(res *dhtRes) { key: res.Key, coords: res.Coords, } - t.insert(&rinfo) // Or at the end, after checking successor/predecessor? + imp := t.getImportant() + if t.isImportant(&rinfo, imp) { + t.insert(&rinfo) + } + //t.insert(&rinfo) // Or at the end, after checking successor/predecessor? if len(res.Infos) > dht_lookup_size { //res.Infos = res.Infos[:dht_lookup_size] //FIXME debug } - imp := t.getImportant() for _, info := range res.Infos { if *info.getNodeID() == t.nodeID { continue @@ -313,6 +310,14 @@ func (t *dht) doMaintenance() { fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", info.throttle, "nodeID:", info.getNodeID()[:8], "coords:", info.coords) } } + return // Skip printing debug info + var out []interface{} + out = append(out, "DEBUG important:") + out = append(out, t.nodeID[:8]) + for _, info := range imp { + out = append(out, info.getNodeID()[:8]) + } + fmt.Println(out...) } func (t *dht) getImportant() []*dhtInfo { @@ -323,9 +328,8 @@ func (t *dht) getImportant() []*dhtInfo { } // Sort them by increasing order in distance along the ring sort.SliceStable(infos, func(i, j int) bool { - // Sort in order of predecessors (!), reverse from chord normal, becuase it plays nicer with zero bits for unknown parts of target addresses + // Sort in order of predecessors (!), reverse from chord normal, because it plays nicer with zero bits for unknown parts of target addresses return dht_ordered(infos[j].getNodeID(), infos[i].getNodeID(), &t.nodeID) - //return dht_ordered(&t.nodeID, infos[i].getNodeID(), infos[j].getNodeID()) }) // Keep the ones that are no further than the closest seen so far minDist := ^uint64(0) @@ -338,6 +342,19 @@ func (t *dht) getImportant() []*dhtInfo { important = append(important, info) } } + var temp []*dhtInfo + minDist = ^uint64(0) + for idx := len(infos) - 1; idx >= 0; idx-- { + info := infos[idx] + dist := uint64(loc.dist(info.coords)) + if dist < minDist { + minDist = dist + temp = append(temp, info) + } + } + for idx := len(temp) - 1; idx >= 0; idx-- { + important = append(important, temp[idx]) + } return important } @@ -347,15 +364,28 @@ func (t *dht) isImportant(ninfo *dhtInfo, important []*dhtInfo) bool { ndist := uint64(loc.dist(ninfo.coords)) minDist := ^uint64(0) for _, info := range important { + if (*info.getNodeID() == *ninfo.getNodeID()) || + (ndist < minDist && dht_ordered(info.getNodeID(), ninfo.getNodeID(), &t.nodeID)) { + // Either the same node, or a better one + return true + } dist := uint64(loc.dist(info.coords)) if dist < minDist { minDist = dist } - //if dht_ordered(&t.nodeID, ninfo.getNodeID(), info.getNodeID()) && ndist <= minDist { - if dht_ordered(info.getNodeID(), ninfo.getNodeID(), &t.nodeID) && ndist <= minDist { - // This node is at least as close in both key space and tree space + } + minDist = ^uint64(0) + for idx := len(important) - 1; idx >= 0; idx-- { + info := important[idx] + if (*info.getNodeID() == *ninfo.getNodeID()) || + (ndist < minDist && dht_ordered(&t.nodeID, ninfo.getNodeID(), info.getNodeID())) { + // Either the same node, or a better one return true } + dist := uint64(loc.dist(info.coords)) + if dist < minDist { + minDist = dist + } } // We didn't find any important node that ninfo is better than return false diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index b4bbb123..9039444f 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -96,7 +96,8 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { continue } - if true || dht_ordered(from.getNodeID(), info.getNodeID(), &res.Dest) { + if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) { + // Response is closer to the destination sinfo.toVisit = append(sinfo.toVisit, info) } } @@ -118,7 +119,7 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { }) // Truncate to some maximum size if len(sinfo.toVisit) > search_MAX_SEARCH_SIZE { - sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] + sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] //FIXME debug } } From 671c7f2a479facd407d69f03b888ba55e83a007d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 28 Oct 2018 15:04:44 -0500 Subject: [PATCH 022/145] don't update recv time for known nodes that ping us or known peers --- src/yggdrasil/dht.go | 2 +- src/yggdrasil/router.go | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 3774b5c4..a94de5e8 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -180,7 +180,7 @@ func (t *dht) handleReq(req *dhtReq) { coords: req.Coords, } imp := t.getImportant() - if t.isImportant(&info, imp) { + if _, isIn := t.table[*info.getNodeID()]; !isIn || t.isImportant(&info, imp) { t.insert(&info) } } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 27aad8d7..cd01e740 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -91,8 +91,12 @@ func (r *router) mainLoop() { case p := <-r.send: r.sendPacket(p) case info := <-r.core.dht.peers: + now := time.Now() + oldInfo, isIn := r.core.dht.table[*info.getNodeID()] r.core.dht.insert(info) - info.throttle = 0 + if isIn && now.Sub(oldInfo.recv) < 45*time.Second { + info.recv = oldInfo.recv + } case <-r.reset: r.core.sessions.resetInits() r.core.dht.reset() From a008b42f99ad8b365841f305f60a3c749278e6cd Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 29 Oct 2018 22:24:18 -0500 Subject: [PATCH 023/145] cleanup and some bugfixes, cache important dht nodes until something gets added/removed --- src/yggdrasil/dht.go | 101 +++++++++++++++++----------------------- src/yggdrasil/search.go | 10 ++-- 2 files changed, 47 insertions(+), 64 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index a94de5e8..b8a303a8 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -16,7 +16,6 @@ package yggdrasil // TODO reoptimize search stuff (size, timeouts, etc) to play nicer with DHT churn import ( - "fmt" "sort" "time" ) @@ -64,9 +63,11 @@ type dhtRes struct { type dht struct { core *Core nodeID NodeID - table map[NodeID]*dhtInfo peers chan *dhtInfo // other goroutines put incoming dht updates here reqs map[boxPubKey]map[NodeID]time.Time + // These next two could be replaced by a single linked list or similar... + table map[NodeID]*dhtInfo + imp []*dhtInfo } // Initializes the DHT @@ -82,6 +83,7 @@ func (t *dht) init(c *Core) { func (t *dht) reset() { t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) + t.imp = nil } // Does a DHT lookup and returns up to dht_lookup_size results @@ -127,6 +129,7 @@ func (t *dht) insert(info *dhtInfo) { info.throttle = oldInfo.throttle } } + t.imp = nil // It needs to update to get a pointer to the new info t.table[*info.getNodeID()] = info } @@ -180,7 +183,7 @@ func (t *dht) handleReq(req *dhtReq) { coords: req.Coords, } imp := t.getImportant() - if _, isIn := t.table[*info.getNodeID()]; !isIn || t.isImportant(&info, imp) { + if _, isIn := t.table[*info.getNodeID()]; !isIn && t.isImportant(&info, imp) { t.insert(&info) } } @@ -223,10 +226,6 @@ func (t *dht) handleRes(res *dhtRes) { if t.isImportant(&rinfo, imp) { t.insert(&rinfo) } - //t.insert(&rinfo) // Or at the end, after checking successor/predecessor? - if len(res.Infos) > dht_lookup_size { - //res.Infos = res.Infos[:dht_lookup_size] //FIXME debug - } for _, info := range res.Infos { if *info.getNodeID() == t.nodeID { continue @@ -284,21 +283,14 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { } func (t *dht) doMaintenance() { - toPing := make(map[NodeID]*dhtInfo) now := time.Now() - imp := t.getImportant() - good := make(map[NodeID]*dhtInfo) - for _, info := range imp { - good[*info.getNodeID()] = info - } for infoID, info := range t.table { if now.Sub(info.recv) > time.Minute || info.pings > 3 { delete(t.table, infoID) - } else if t.isImportant(info, imp) { - toPing[infoID] = info + t.imp = nil } } - for _, info := range toPing { + for _, info := range t.getImportant() { if now.Sub(info.recv) > info.throttle { t.ping(info, info.getNodeID()) info.pings++ @@ -306,56 +298,49 @@ func (t *dht) doMaintenance() { if info.throttle > 30*time.Second { info.throttle = 30 * time.Second } - continue - fmt.Println("DEBUG self:", t.nodeID[:8], "throttle:", info.throttle, "nodeID:", info.getNodeID()[:8], "coords:", info.coords) } } - return // Skip printing debug info - var out []interface{} - out = append(out, "DEBUG important:") - out = append(out, t.nodeID[:8]) - for _, info := range imp { - out = append(out, info.getNodeID()[:8]) - } - fmt.Println(out...) } func (t *dht) getImportant() []*dhtInfo { - // Get a list of all known nodes - infos := make([]*dhtInfo, 0, len(t.table)) - for _, info := range t.table { - infos = append(infos, info) - } - // Sort them by increasing order in distance along the ring - sort.SliceStable(infos, func(i, j int) bool { - // Sort in order of predecessors (!), reverse from chord normal, because it plays nicer with zero bits for unknown parts of target addresses - return dht_ordered(infos[j].getNodeID(), infos[i].getNodeID(), &t.nodeID) - }) - // Keep the ones that are no further than the closest seen so far - minDist := ^uint64(0) - loc := t.core.switchTable.getLocator() - important := infos[:0] - for _, info := range infos { - dist := uint64(loc.dist(info.coords)) - if dist < minDist { - minDist = dist - important = append(important, info) + if t.imp == nil { + // Get a list of all known nodes + infos := make([]*dhtInfo, 0, len(t.table)) + for _, info := range t.table { + infos = append(infos, info) } - } - var temp []*dhtInfo - minDist = ^uint64(0) - for idx := len(infos) - 1; idx >= 0; idx-- { - info := infos[idx] - dist := uint64(loc.dist(info.coords)) - if dist < minDist { - minDist = dist - temp = append(temp, info) + // Sort them by increasing order in distance along the ring + sort.SliceStable(infos, func(i, j int) bool { + // Sort in order of predecessors (!), reverse from chord normal, because it plays nicer with zero bits for unknown parts of target addresses + return dht_ordered(infos[j].getNodeID(), infos[i].getNodeID(), &t.nodeID) + }) + // Keep the ones that are no further than the closest seen so far + minDist := ^uint64(0) + loc := t.core.switchTable.getLocator() + important := infos[:0] + for _, info := range infos { + dist := uint64(loc.dist(info.coords)) + if dist < minDist { + minDist = dist + important = append(important, info) + } } + var temp []*dhtInfo + minDist = ^uint64(0) + for idx := len(infos) - 1; idx >= 0; idx-- { + info := infos[idx] + dist := uint64(loc.dist(info.coords)) + if dist < minDist { + minDist = dist + temp = append(temp, info) + } + } + for idx := len(temp) - 1; idx >= 0; idx-- { + important = append(important, temp[idx]) + } + t.imp = important } - for idx := len(temp) - 1; idx >= 0; idx-- { - important = append(important, temp[idx]) - } - return important + return t.imp } func (t *dht) isImportant(ninfo *dhtInfo, important []*dhtInfo) bool { diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 9039444f..f22fbe2a 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -11,8 +11,10 @@ package yggdrasil // A new search packet is sent immediately after receiving a response // A new search packet is sent periodically, once per second, in case a packet was dropped (this slowly causes the search to become parallel if the search doesn't timeout but also doesn't finish within 1 second for whatever reason) +// TODO? +// Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there? + import ( - "fmt" "sort" "time" ) @@ -74,9 +76,6 @@ func (s *searches) handleDHTRes(res *dhtRes) { sinfo, isIn := s.searches[res.Dest] if !isIn || s.checkDHTRes(sinfo, res) { // Either we don't recognize this search, or we just finished it - if isIn { - fmt.Println("DEBUG: search finished, length:", len(sinfo.visited)) - } return } else { // Add to the search and continue @@ -92,6 +91,7 @@ func (s *searches) handleDHTRes(res *dhtRes) { func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { // Add responses to toVisit if closer to dest than the res node from := dhtInfo{key: res.Key, coords: res.Coords} + sinfo.visited[*from.getNodeID()] = true for _, info := range res.Infos { if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { continue @@ -129,14 +129,12 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup delete(s.searches, sinfo.dest) - fmt.Println("DEBUG: search abandoned, length:", len(sinfo.visited)) return } else { // Send to the next search target var next *dhtInfo next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] s.core.dht.ping(next, &sinfo.dest) - sinfo.visited[*next.getNodeID()] = true } } From 52206dc381b2724a594f2212c6811d30373a408f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 16:40:47 +0000 Subject: [PATCH 024/145] Add initial crypto-key routing handlers --- src/yggdrasil/ckr.go | 104 +++++++++++++++++++++++++++++++++ src/yggdrasil/config/config.go | 8 +++ src/yggdrasil/core.go | 8 +++ src/yggdrasil/router.go | 18 +++--- 4 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 src/yggdrasil/ckr.go diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go new file mode 100644 index 00000000..15f8828d --- /dev/null +++ b/src/yggdrasil/ckr.go @@ -0,0 +1,104 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "net" + "sort" +) + +// This module implements crypto-key routing, similar to Wireguard, where we +// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. + +type cryptokey struct { + core *Core + enabled bool + ipv4routes []cryptokey_route + ipv6routes []cryptokey_route +} + +type cryptokey_route struct { + subnet net.IPNet + destination []byte +} + +func (c *cryptokey) init(core *Core) { + c.core = core + c.ipv4routes = make([]cryptokey_route, 0) + c.ipv6routes = make([]cryptokey_route, 0) +} + +func (c *cryptokey) isEnabled() bool { + return c.enabled +} + +func (c *cryptokey) addRoute(cidr string, dest string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + prefixlen, prefixsize := ipnet.Mask.Size() + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + // IPv6 + for _, route := range c.ipv6routes { + // Do we already have a route for this subnet? + routeprefixlen, _ := route.subnet.Mask.Size() + if route.subnet.IP.Equal(ipnet.IP) && routeprefixlen == prefixlen { + return errors.New("IPv6 route already exists") + } + } + // Decode the public key + if boxPubKey, err := hex.DecodeString(dest); err != nil { + return err + } else { + // Add the new crypto-key route + c.ipv6routes = append(c.ipv6routes, cryptokey_route{ + subnet: *ipnet, + destination: boxPubKey, + }) + // Sort so most specific routes are first + sort.Slice(c.ipv6routes, func(i, j int) bool { + im, _ := c.ipv6routes[i].subnet.Mask.Size() + jm, _ := c.ipv6routes[j].subnet.Mask.Size() + return im > jm + }) + return nil + } + } else if prefixsize == net.IPv4len*8 { + // IPv4 + return errors.New("IPv4 not supported at this time") + } + return errors.New("Unspecified error") +} + +func (c *cryptokey) getPublicKeyForAddress(addr string) (boxPubKey, error) { + ipaddr := net.ParseIP(addr) + + if ipaddr.To4() == nil { + // IPv6 + for _, route := range c.ipv6routes { + if route.subnet.Contains(ipaddr) { + var box boxPubKey + copy(box[:boxPubKeyLen], route.destination) + return box, nil + } + } + } else { + // IPv4 + return boxPubKey{}, errors.New("IPv4 not supported at this time") + /* + for _, route := range c.ipv4routes { + if route.subnet.Contains(ipaddr) { + return route.destination, nil + } + } + */ + } + + return boxPubKey{}, errors.New("No route") +} diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index bcf4f322..6f1871fc 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -17,6 +17,7 @@ type NodeConfig struct { 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."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } @@ -26,6 +27,7 @@ type NetConfig struct { I2P I2PConfig `comment:"Experimental options for configuring peerings over I2P."` } +// SessionFirewall controls the session firewall configuration type SessionFirewall struct { Enable bool `comment:"Enable or disable the session firewall. If disabled, network traffic\nfrom any node will be allowed. If enabled, the below rules apply."` AllowFromDirect bool `comment:"Allow network traffic from directly connected peers."` @@ -34,3 +36,9 @@ type SessionFirewall struct { WhitelistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always accepted,\nregardless of AllowFromDirect or AllowFromRemote."` BlacklistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` } + +// TunnelRouting contains the crypto-key routing tables for tunneling +type TunnelRouting struct { + Enable bool `comment:"Enable or disable tunneling."` + IPv6Routes map[string]string `comment:"IPv6 subnets, mapped to the public keys to which they should be routed."` +} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 015147c4..f4fb3d78 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -121,6 +121,14 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } + if nc.TunnelRouting.Enable { + for ipv6, pubkey := range nc.TunnelRouting.IPv6Routes { + if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil { + panic(err) + } + } + } + if err := c.admin.start(); err != nil { c.log.Println("Failed to start admin socket") return err diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 86eb193c..e83bb114 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -32,14 +32,15 @@ import ( // The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { - core *Core - addr address - 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" - 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) - admin chan func() // pass a lambda for the admin socket to query stuff + core *Core + addr address + 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" + 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) + admin chan func() // pass a lambda for the admin socket to query stuff + cryptokey cryptokey } // Initializes the router struct, which includes setting up channels to/from the tun/tap. @@ -67,6 +68,7 @@ func (r *router) init(core *Core) { r.core.tun.send = send r.reset = make(chan struct{}, 1) r.admin = make(chan func()) + r.cryptokey.init(r.core) // go r.mainLoop() } From ec751e8cc71683537ea5790e73e00b2de237035c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 17:03:58 +0000 Subject: [PATCH 025/145] Don't allow Yggdrasil ranges as crypto-key routes --- src/yggdrasil/ckr.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 15f8828d..c4f6d697 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -3,6 +3,7 @@ package yggdrasil import ( "encoding/hex" "errors" + "fmt" "net" "sort" ) @@ -34,22 +35,28 @@ func (c *cryptokey) isEnabled() bool { func (c *cryptokey) addRoute(cidr string, dest string) error { // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) + ipaddr, ipnet, err := net.ParseCIDR(cidr) if err != nil { return err } // Get the prefix length and size - prefixlen, prefixsize := ipnet.Mask.Size() + _, prefixsize := ipnet.Mask.Size() // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { - // IPv6 + // Is the route an Yggdrasil destination? + var addr address + var snet subnet + copy(addr[:], ipaddr) + copy(snet[:], ipnet.IP) + if addr.isValid() || snet.isValid() { + return errors.New("Can't specify Yggdrasil destination as crypto-key route") + } + // Do we already have a route for this subnet? for _, route := range c.ipv6routes { - // Do we already have a route for this subnet? - routeprefixlen, _ := route.subnet.Mask.Size() - if route.subnet.IP.Equal(ipnet.IP) && routeprefixlen == prefixlen { - return errors.New("IPv6 route already exists") + if route.subnet.String() == ipnet.String() { + return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) } } // Decode the public key @@ -99,6 +106,5 @@ func (c *cryptokey) getPublicKeyForAddress(addr string) (boxPubKey, error) { } */ } - - return boxPubKey{}, errors.New("No route") + return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", addr)) } From 295e9c9a1020eafd6cad1320e0219bf0124762ce Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 17:31:10 +0000 Subject: [PATCH 026/145] Cache crypto-key routes (until routing table changes) --- src/yggdrasil/ckr.go | 51 ++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index c4f6d697..f24cd9d5 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -16,6 +16,8 @@ type cryptokey struct { enabled bool ipv4routes []cryptokey_route ipv6routes []cryptokey_route + ipv4cache map[address]cryptokey_route + ipv6cache map[address]cryptokey_route } type cryptokey_route struct { @@ -27,6 +29,8 @@ func (c *cryptokey) init(core *Core) { c.core = core c.ipv4routes = make([]cryptokey_route, 0) c.ipv6routes = make([]cryptokey_route, 0) + c.ipv4cache = make(map[address]cryptokey_route, 0) + c.ipv6cache = make(map[address]cryptokey_route, 0) } func (c *cryptokey) isEnabled() bool { @@ -68,12 +72,20 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { subnet: *ipnet, destination: boxPubKey, }) + // Sort so most specific routes are first sort.Slice(c.ipv6routes, func(i, j int) bool { im, _ := c.ipv6routes[i].subnet.Mask.Size() jm, _ := c.ipv6routes[j].subnet.Mask.Size() return im > jm }) + + // Clear the cache as this route might change future routing + // Setting an empty slice keeps the memory whereas nil invokes GC + for k := range c.ipv6cache { + delete(c.ipv6cache, k) + } + return nil } } else if prefixsize == net.IPv4len*8 { @@ -83,28 +95,39 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { return errors.New("Unspecified error") } -func (c *cryptokey) getPublicKeyForAddress(addr string) (boxPubKey, error) { - ipaddr := net.ParseIP(addr) +func (c *cryptokey) getPublicKeyForAddress(addr address) (boxPubKey, error) { + // Check if there's a cache entry for this addr + if route, ok := c.ipv6cache[addr]; ok { + var box boxPubKey + copy(box[:boxPubKeyLen], route.destination) + return box, nil + } - if ipaddr.To4() == nil { - // IPv6 + // No cache was found - start by converting the address into a net.IP + ip := make(net.IP, 16) + copy(ip[:16], addr[:]) + + // Check whether it's an IPv4 or an IPv6 address + if ip.To4() == nil { + // Check if we have a route. At this point c.ipv6routes should be + // pre-sorted so that the most specific routes are first for _, route := range c.ipv6routes { - if route.subnet.Contains(ipaddr) { + // Does this subnet match the given IP? + if route.subnet.Contains(ip) { + // Cache the entry for future packets to get a faster lookup + c.ipv6cache[addr] = route + + // Return the boxPubKey var box boxPubKey copy(box[:boxPubKeyLen], route.destination) return box, nil } } } else { - // IPv4 + // IPv4 isn't supported yet return boxPubKey{}, errors.New("IPv4 not supported at this time") - /* - for _, route := range c.ipv4routes { - if route.subnet.Contains(ipaddr) { - return route.destination, nil - } - } - */ } - return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", addr)) + + // No route was found if we got to this point + return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) } From 87b0f5fe24212678c500407a808db3c65b4deadf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 22:39:30 +0000 Subject: [PATCH 027/145] Use CKR in router when sending packets --- src/yggdrasil/router.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index e83bb114..0faf850b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -134,7 +134,16 @@ func (r *router) sendPacket(bs []byte) { var snet subnet copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { - return + if key, err := r.cryptokey.getPublicKeyForAddress(dest); err == nil { + addr := *address_addrForNodeID(getNodeID(&key)) + copy(dest[:], addr[:]) + copy(snet[:], addr[:]) + if !dest.isValid() && !snet.isValid() { + return + } + } else { + return + } } doSearch := func(packet []byte) { var nodeID, mask *NodeID From c7f2427de11abc986a68237f8725934fe8ac8bb6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 22:58:58 +0000 Subject: [PATCH 028/145] Check CKR routes when receiving packets in router --- src/yggdrasil/router.go | 13 ++++++++----- src/yggdrasil/session.go | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 0faf850b..e369d836 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -253,7 +253,7 @@ func (r *router) sendPacket(bs []byte) { // Called for incoming traffic by the session worker for that connection. // Checks that the IP address is correct (matches the session) and passes the packet to the tun/tap. -func (r *router) recvPacket(bs []byte, theirAddr *address, theirSubnet *subnet) { +func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { // Note: called directly by the session worker, not the router goroutine if len(bs) < 24 { util_putBytes(bs) @@ -264,11 +264,14 @@ func (r *router) recvPacket(bs []byte, theirAddr *address, theirSubnet *subnet) var snet subnet copy(snet[:], bs[8:]) switch { - case source.isValid() && source == *theirAddr: - case snet.isValid() && snet == *theirSubnet: + case source.isValid() && source == sinfo.theirAddr: + case snet.isValid() && snet == sinfo.theirSubnet: default: - util_putBytes(bs) - return + key, err := r.cryptokey.getPublicKeyForAddress(source) + if err != nil || key != sinfo.theirPermPub { + util_putBytes(bs) + return + } } //go func() { r.recv<-bs }() r.recv <- bs diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0bc27a12..0e587d52 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -589,5 +589,5 @@ func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) { sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) - sinfo.core.router.recvPacket(bs, &sinfo.theirAddr, &sinfo.theirSubnet) + sinfo.core.router.recvPacket(bs, sinfo) } From 7218b5a56c3ec735807b7dccd2ddfa766f6500f8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 23:12:26 +0000 Subject: [PATCH 029/145] Don't look up public keys for Yggdrasil native addresses --- src/yggdrasil/ckr.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index f24cd9d5..0e195fd3 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -96,6 +96,12 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { } func (c *cryptokey) getPublicKeyForAddress(addr address) (boxPubKey, error) { + // Check if the address is a valid Yggdrasil address - if so it + // is exempt from all CKR checking + if addr.isValid() { + return + } + // Check if there's a cache entry for this addr if route, ok := c.ipv6cache[addr]; ok { var box boxPubKey From cfdbc481a5502c9cbc5aa23b6a8e5c1b77f50dd2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 23:22:45 +0000 Subject: [PATCH 030/145] Modify source address check for CKR --- src/yggdrasil/ckr.go | 2 +- src/yggdrasil/router.go | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 0e195fd3..2b6c0d1f 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -99,7 +99,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address) (boxPubKey, error) { // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking if addr.isValid() { - return + return boxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") } // Check if there's a cache entry for this addr diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index e369d836..dcce7271 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -124,14 +124,11 @@ func (r *router) sendPacket(bs []byte) { } var sourceAddr address var sourceSubnet subnet + var dest address + var snet subnet copy(sourceAddr[:], bs[8:]) copy(sourceSubnet[:], bs[8:]) - if !sourceAddr.isValid() && !sourceSubnet.isValid() { - return - } - var dest address copy(dest[:], bs[24:]) - var snet subnet copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { if key, err := r.cryptokey.getPublicKeyForAddress(dest); err == nil { @@ -144,6 +141,10 @@ func (r *router) sendPacket(bs []byte) { } else { return } + } else { + if !sourceAddr.isValid() && !sourceSubnet.isValid() { + return + } } doSearch := func(packet []byte) { var nodeID, mask *NodeID From 8c2327a2bf15e6388b03957983023b704b4266a1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 23:59:41 +0000 Subject: [PATCH 031/145] Add source addresses option and more intelligent source checking --- src/yggdrasil/ckr.go | 62 ++++++++++++++++++++++++++++++---- src/yggdrasil/config/config.go | 5 +-- src/yggdrasil/router.go | 9 ++--- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 2b6c0d1f..96fde27e 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -12,12 +12,14 @@ import ( // allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. type cryptokey struct { - core *Core - enabled bool - ipv4routes []cryptokey_route - ipv6routes []cryptokey_route - ipv4cache map[address]cryptokey_route - ipv6cache map[address]cryptokey_route + core *Core + enabled bool + ipv4routes []cryptokey_route + ipv6routes []cryptokey_route + ipv4cache map[address]cryptokey_route + ipv6cache map[address]cryptokey_route + ipv4sources []net.IPNet + ipv6sources []net.IPNet } type cryptokey_route struct { @@ -31,12 +33,60 @@ func (c *cryptokey) init(core *Core) { c.ipv6routes = make([]cryptokey_route, 0) c.ipv4cache = make(map[address]cryptokey_route, 0) c.ipv6cache = make(map[address]cryptokey_route, 0) + c.ipv4sources = make([]net.IPNet, 0) + c.ipv6sources = make([]net.IPNet, 0) } func (c *cryptokey) isEnabled() bool { return c.enabled } +func (c *cryptokey) isValidSource(addr address) bool { + ip := net.IP(addr[:]) + + // Does this match our node's address? + if addr == c.core.router.addr { + return true + } + + // Does this match our node's subnet? + var subnet net.IPNet + copy(subnet.IP, c.core.router.subnet[:]) + copy(subnet.Mask, net.CIDRMask(64, 128)) + if subnet.Contains(ip) { + return true + } + + // Does it match a configured CKR source? + for _, subnet := range c.ipv6sources { + if subnet.Contains(ip) { + return true + } + } + + // Doesn't match any of the above + return false +} + +func (c *cryptokey) addSourceSubnet(cidr string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Check if we already have this CIDR + for _, subnet := range c.ipv6sources { + if subnet.String() == ipnet.String() { + return errors.New("Source subnet already configured") + } + } + + // Add the source subnet + c.ipv6sources = append(c.ipv6sources, *ipnet) + return nil +} + func (c *cryptokey) addRoute(cidr string, dest string) error { // Is the CIDR we've been given valid? ipaddr, ipnet, err := net.ParseCIDR(cidr) diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 6f1871fc..95947454 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -39,6 +39,7 @@ type SessionFirewall struct { // TunnelRouting contains the crypto-key routing tables for tunneling type TunnelRouting struct { - Enable bool `comment:"Enable or disable tunneling."` - IPv6Routes map[string]string `comment:"IPv6 subnets, mapped to the public keys to which they should be routed."` + Enable bool `comment:"Enable or disable tunneling."` + IPv6Routes map[string]string `comment:"IPv6 subnets, mapped to the public keys to which they should be routed."` + IPv6Sources []string `comment:"Allow source addresses in these subnets."` } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index dcce7271..e3634640 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -34,6 +34,7 @@ import ( type router struct { core *Core addr address + subnet subnet 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" recv chan<- []byte // place where the tun pulls received packets from @@ -47,6 +48,7 @@ type router struct { func (r *router) init(core *Core) { r.core = core r.addr = *address_addrForNodeID(&r.core.dht.nodeID) + r.subnet = *address_subnetForNodeID(&r.core.dht.nodeID) in := make(chan []byte, 32) // TODO something better than this... p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)") p.out = func(packet []byte) { @@ -128,6 +130,9 @@ func (r *router) sendPacket(bs []byte) { var snet subnet copy(sourceAddr[:], bs[8:]) copy(sourceSubnet[:], bs[8:]) + if !r.cryptokey.isValidSource(sourceAddr) { + return + } copy(dest[:], bs[24:]) copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { @@ -141,10 +146,6 @@ func (r *router) sendPacket(bs []byte) { } else { return } - } else { - if !sourceAddr.isValid() && !sourceSubnet.isValid() { - return - } } doSearch := func(packet []byte) { var nodeID, mask *NodeID From e3d4aed44adc1dfa1d089ae0d0f1b012b0c71692 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 00:05:01 +0000 Subject: [PATCH 032/145] Configure IPv6Sources --- src/yggdrasil/core.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index f4fb3d78..31142180 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -127,6 +127,11 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { panic(err) } } + for _, source := range nc.TunnelRouting.IPv6Sources { + if c.router.cryptokey.addSourceSubnet(source); err != nil { + panic(err) + } + } } if err := c.admin.start(); err != nil { From 19e6aaf9f54ddf00066e39c27ab56b0a89dd81f2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 00:06:37 +0000 Subject: [PATCH 033/145] Remove sourceSubnet from router --- src/yggdrasil/router.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index e3634640..09058e43 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -125,11 +125,9 @@ func (r *router) sendPacket(bs []byte) { panic("Tried to send a packet shorter than a header...") } var sourceAddr address - var sourceSubnet subnet var dest address var snet subnet copy(sourceAddr[:], bs[8:]) - copy(sourceSubnet[:], bs[8:]) if !r.cryptokey.isValidSource(sourceAddr) { return } From f0947223bb552eded37f7433eb9c54a30063e702 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 11:11:57 +0000 Subject: [PATCH 034/145] Only validate CKR routes if CKR enabled --- src/yggdrasil/ckr.go | 8 +++++--- src/yggdrasil/config/config.go | 6 +++--- src/yggdrasil/core.go | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 96fde27e..ffe9206c 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -58,9 +58,11 @@ func (c *cryptokey) isValidSource(addr address) bool { } // Does it match a configured CKR source? - for _, subnet := range c.ipv6sources { - if subnet.Contains(ip) { - return true + if c.isEnabled() { + for _, subnet := range c.ipv6sources { + if subnet.Contains(ip) { + return true + } } } diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 95947454..f6694662 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -39,7 +39,7 @@ type SessionFirewall struct { // TunnelRouting contains the crypto-key routing tables for tunneling type TunnelRouting struct { - Enable bool `comment:"Enable or disable tunneling."` - IPv6Routes map[string]string `comment:"IPv6 subnets, mapped to the public keys to which they should be routed."` - IPv6Sources []string `comment:"Allow source addresses in these subnets."` + Enable bool `comment:"Enable or disable tunneling."` + IPv6Destinations map[string]string `comment:"IPv6 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` + IPv6Sources []string `comment:"Optional IPv6 subnets which are allowed to be used as source addresses\nin addition to this node's Yggdrasil address/subnet."` } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 31142180..03e8a263 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -122,7 +122,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { } if nc.TunnelRouting.Enable { - for ipv6, pubkey := range nc.TunnelRouting.IPv6Routes { + for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations { if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil { panic(err) } From bc578f571c2f01d5b8943a0754dbf7ff91b57176 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 11:56:32 +0000 Subject: [PATCH 035/145] Some output at startup --- src/yggdrasil/ckr.go | 2 ++ src/yggdrasil/core.go | 1 + 2 files changed, 3 insertions(+) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index ffe9206c..c1558663 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -86,6 +86,7 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { // Add the source subnet c.ipv6sources = append(c.ipv6sources, *ipnet) + c.core.log.Println("Added CKR source subnet", cidr) return nil } @@ -138,6 +139,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { delete(c.ipv6cache, k) } + c.core.log.Println("Added CKR destination subnet", cidr) return nil } } else if prefixsize == net.IPv4len*8 { diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 03e8a263..10cf272d 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -122,6 +122,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { } if nc.TunnelRouting.Enable { + c.log.Println("Crypto-key routing enabled") for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations { if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil { panic(err) From bc62af7f7dbfcd66faa30a4e9031d936bdf7a0bc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 12:32:16 +0000 Subject: [PATCH 036/145] Enable CKR properly from config --- src/yggdrasil/ckr.go | 4 ++++ src/yggdrasil/core.go | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index c1558663..baa0058c 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -37,6 +37,10 @@ func (c *cryptokey) init(core *Core) { c.ipv6sources = make([]net.IPNet, 0) } +func (c *cryptokey) setEnabled(enabled bool) { + c.enabled = enabled +} + func (c *cryptokey) isEnabled() bool { return c.enabled } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 10cf272d..3a3531f6 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -121,7 +121,8 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } - if nc.TunnelRouting.Enable { + c.router.cryptokey.setEnabled(nc.TunnelRouting.Enable) + if c.router.cryptokey.isEnabled() { c.log.Println("Crypto-key routing enabled") for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations { if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil { From 2f75075da3812ed5273df34758535fcf8fc4355d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 14:28:57 +0000 Subject: [PATCH 037/145] Fix Yggdrasil subnet routing --- src/yggdrasil/ckr.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index baa0058c..ccedc649 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "bytes" "encoding/hex" "errors" "fmt" @@ -49,15 +50,12 @@ func (c *cryptokey) isValidSource(addr address) bool { ip := net.IP(addr[:]) // Does this match our node's address? - if addr == c.core.router.addr { + if bytes.Equal(addr[:16], c.core.router.addr[:16]) { return true } // Does this match our node's subnet? - var subnet net.IPNet - copy(subnet.IP, c.core.router.subnet[:]) - copy(subnet.Mask, net.CIDRMask(64, 128)) - if subnet.Contains(ip) { + if bytes.Equal(addr[:8], c.core.router.subnet[:8]) { return true } From cb7a5f17d9b5b5c47bb454b0474ef07ea98bdae1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 19:23:20 +0000 Subject: [PATCH 038/145] Check destination address upon receive in router --- src/yggdrasil/router.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 09058e43..8b5d17e4 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -259,6 +259,12 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { util_putBytes(bs) return } + var dest address + copy(dest[:], bs[24:]) + if !r.cryptokey.isValidSource(dest) { + util_putBytes(bs) + return + } var source address copy(source[:], bs[8:]) var snet subnet From 424faa1c516e7e487925a7cd096a75fe19099309 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 20:04:49 +0000 Subject: [PATCH 039/145] Support IPv4 in ckr.go --- src/yggdrasil/ckr.go | 164 ++++++++++++++++++++------------- src/yggdrasil/config/config.go | 2 + src/yggdrasil/core.go | 10 ++ src/yggdrasil/router.go | 4 +- 4 files changed, 113 insertions(+), 67 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index ccedc649..8036486b 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -79,15 +79,30 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { return err } + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingsources = &c.ipv6sources + } else if prefixsize == net.IPv4len*8 { + routingsources = &c.ipv4sources + } else { + return errors.New("Unexpected prefix size") + } + // Check if we already have this CIDR - for _, subnet := range c.ipv6sources { + for _, subnet := range *routingsources { if subnet.String() == ipnet.String() { return errors.New("Source subnet already configured") } } // Add the source subnet - c.ipv6sources = append(c.ipv6sources, *ipnet) + *routingsources = append(*routingsources, *ipnet) c.core.log.Println("Added CKR source subnet", cidr) return nil } @@ -102,92 +117,111 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // Get the prefix length and size _, prefixsize := ipnet.Mask.Size() + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address]cryptokey_route + // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { - // Is the route an Yggdrasil destination? - var addr address - var snet subnet - copy(addr[:], ipaddr) - copy(snet[:], ipnet.IP) - if addr.isValid() || snet.isValid() { - return errors.New("Can't specify Yggdrasil destination as crypto-key route") - } - // Do we already have a route for this subnet? - for _, route := range c.ipv6routes { - if route.subnet.String() == ipnet.String() { - return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) - } - } - // Decode the public key - if boxPubKey, err := hex.DecodeString(dest); err != nil { - return err - } else { - // Add the new crypto-key route - c.ipv6routes = append(c.ipv6routes, cryptokey_route{ - subnet: *ipnet, - destination: boxPubKey, - }) - - // Sort so most specific routes are first - sort.Slice(c.ipv6routes, func(i, j int) bool { - im, _ := c.ipv6routes[i].subnet.Mask.Size() - jm, _ := c.ipv6routes[j].subnet.Mask.Size() - return im > jm - }) - - // Clear the cache as this route might change future routing - // Setting an empty slice keeps the memory whereas nil invokes GC - for k := range c.ipv6cache { - delete(c.ipv6cache, k) - } - - c.core.log.Println("Added CKR destination subnet", cidr) - return nil - } + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache } else if prefixsize == net.IPv4len*8 { - // IPv4 - return errors.New("IPv4 not supported at this time") + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return errors.New("Unexpected prefix size") } + + // Is the route an Yggdrasil destination? + var addr address + var snet subnet + copy(addr[:], ipaddr) + copy(snet[:], ipnet.IP) + if addr.isValid() || snet.isValid() { + return errors.New("Can't specify Yggdrasil destination as crypto-key route") + } + // Do we already have a route for this subnet? + for _, route := range *routingtable { + if route.subnet.String() == ipnet.String() { + return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) + } + } + // Decode the public key + if boxPubKey, err := hex.DecodeString(dest); err != nil { + return err + } else { + // Add the new crypto-key route + *routingtable = append(*routingtable, cryptokey_route{ + subnet: *ipnet, + destination: boxPubKey, + }) + + // Sort so most specific routes are first + sort.Slice(*routingtable, func(i, j int) bool { + im, _ := (*routingtable)[i].subnet.Mask.Size() + jm, _ := (*routingtable)[j].subnet.Mask.Size() + return im > jm + }) + + // Clear the cache as this route might change future routing + // Setting an empty slice keeps the memory whereas nil invokes GC + for k := range *routingcache { + delete(*routingcache, k) + } + + c.core.log.Println("Added CKR destination subnet", cidr) + return nil + } + return errors.New("Unspecified error") } -func (c *cryptokey) getPublicKeyForAddress(addr address) (boxPubKey, error) { +func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey, error) { // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking if addr.isValid() { return boxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") } + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if addrlen == net.IPv4len { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return boxPubKey{}, errors.New("Unexpected prefix size") + } + // Check if there's a cache entry for this addr - if route, ok := c.ipv6cache[addr]; ok { + if route, ok := (*routingcache)[addr]; ok { var box boxPubKey copy(box[:boxPubKeyLen], route.destination) return box, nil } // No cache was found - start by converting the address into a net.IP - ip := make(net.IP, 16) - copy(ip[:16], addr[:]) + ip := make(net.IP, addrlen) + copy(ip[:addrlen], addr[:]) - // Check whether it's an IPv4 or an IPv6 address - if ip.To4() == nil { - // Check if we have a route. At this point c.ipv6routes should be - // pre-sorted so that the most specific routes are first - for _, route := range c.ipv6routes { - // Does this subnet match the given IP? - if route.subnet.Contains(ip) { - // Cache the entry for future packets to get a faster lookup - c.ipv6cache[addr] = route + // Check if we have a route. At this point c.ipv6routes should be + // pre-sorted so that the most specific routes are first + for _, route := range *routingtable { + // Does this subnet match the given IP? + if route.subnet.Contains(ip) { + // Cache the entry for future packets to get a faster lookup + (*routingcache)[addr] = route - // Return the boxPubKey - var box boxPubKey - copy(box[:boxPubKeyLen], route.destination) - return box, nil - } + // Return the boxPubKey + var box boxPubKey + copy(box[:boxPubKeyLen], route.destination) + return box, nil } - } else { - // IPv4 isn't supported yet - return boxPubKey{}, errors.New("IPv4 not supported at this time") } // No route was found if we got to this point diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index f6694662..38f93404 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -42,4 +42,6 @@ type TunnelRouting struct { Enable bool `comment:"Enable or disable tunneling."` IPv6Destinations map[string]string `comment:"IPv6 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` IPv6Sources []string `comment:"Optional IPv6 subnets which are allowed to be used as source addresses\nin addition to this node's Yggdrasil address/subnet."` + IPv4Destinations map[string]string `comment:"IPv4 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` + IPv4Sources []string `comment:"Optional IPv4 subnets which are allowed to be used as source addresses."` } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 3a3531f6..2f60a1ba 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -134,6 +134,16 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { panic(err) } } + for ipv4, pubkey := range nc.TunnelRouting.IPv4Destinations { + if err := c.router.cryptokey.addRoute(ipv4, pubkey); err != nil { + panic(err) + } + } + for _, source := range nc.TunnelRouting.IPv4Sources { + if c.router.cryptokey.addSourceSubnet(source); err != nil { + panic(err) + } + } } if err := c.admin.start(); err != nil { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 8b5d17e4..cd922a9c 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -134,7 +134,7 @@ func (r *router) sendPacket(bs []byte) { copy(dest[:], bs[24:]) copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { - if key, err := r.cryptokey.getPublicKeyForAddress(dest); err == nil { + if key, err := r.cryptokey.getPublicKeyForAddress(dest, 16); err == nil { addr := *address_addrForNodeID(getNodeID(&key)) copy(dest[:], addr[:]) copy(snet[:], addr[:]) @@ -273,7 +273,7 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { case source.isValid() && source == sinfo.theirAddr: case snet.isValid() && snet == sinfo.theirSubnet: default: - key, err := r.cryptokey.getPublicKeyForAddress(source) + key, err := r.cryptokey.getPublicKeyForAddress(source, 16) if err != nil || key != sinfo.theirPermPub { util_putBytes(bs) return From 024037541741696e22527f7c93cead4a94ea6e70 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 20:49:19 +0000 Subject: [PATCH 040/145] IPv4 CKR support in router --- src/yggdrasil/ckr.go | 34 +++++++++++++++++++--------- src/yggdrasil/router.go | 50 ++++++++++++++++++++++++++++++----------- src/yggdrasil/tun.go | 2 +- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 8036486b..f22840c3 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -46,22 +46,36 @@ func (c *cryptokey) isEnabled() bool { return c.enabled } -func (c *cryptokey) isValidSource(addr address) bool { - ip := net.IP(addr[:]) +func (c *cryptokey) isValidSource(addr address, addrlen int) bool { + ip := net.IP(addr[:addrlen]) - // Does this match our node's address? - if bytes.Equal(addr[:16], c.core.router.addr[:16]) { - return true - } + if addrlen == net.IPv6len { + // Does this match our node's address? + if bytes.Equal(addr[:16], c.core.router.addr[:16]) { + return true + } - // Does this match our node's subnet? - if bytes.Equal(addr[:8], c.core.router.subnet[:8]) { - return true + // Does this match our node's subnet? + if bytes.Equal(addr[:8], c.core.router.subnet[:8]) { + return true + } } // Does it match a configured CKR source? if c.isEnabled() { - for _, subnet := range c.ipv6sources { + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingsources = &c.ipv6sources + } else if addrlen == net.IPv4len { + routingsources = &c.ipv4sources + } else { + return false + } + + for _, subnet := range *routingsources { if subnet.Contains(ip) { return true } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index cd922a9c..0c633acc 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -127,14 +127,26 @@ func (r *router) sendPacket(bs []byte) { var sourceAddr address var dest address var snet subnet - copy(sourceAddr[:], bs[8:]) - if !r.cryptokey.isValidSource(sourceAddr) { + var addrlen int + if bs[0]&0xf0 == 0x60 { + // IPv6 address + addrlen = 16 + copy(sourceAddr[:addrlen], bs[8:]) + copy(dest[:addrlen], bs[24:]) + copy(snet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // IPv4 address + addrlen = 4 + copy(sourceAddr[:addrlen], bs[12:]) + copy(dest[:addrlen], bs[16:]) + } else { + return + } + if !r.cryptokey.isValidSource(sourceAddr, addrlen) { return } - copy(dest[:], bs[24:]) - copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { - if key, err := r.cryptokey.getPublicKeyForAddress(dest, 16); err == nil { + if key, err := r.cryptokey.getPublicKeyForAddress(dest, addrlen); err == nil { addr := *address_addrForNodeID(getNodeID(&key)) copy(dest[:], addr[:]) copy(snet[:], addr[:]) @@ -259,21 +271,33 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { util_putBytes(bs) return } + var sourceAddr address var dest address - copy(dest[:], bs[24:]) - if !r.cryptokey.isValidSource(dest) { + var snet subnet + var addrlen int + if bs[0]&0xf0 == 0x60 { + // IPv6 address + addrlen = 16 + copy(sourceAddr[:addrlen], bs[8:]) + copy(dest[:addrlen], bs[24:]) + copy(snet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // IPv4 address + addrlen = 4 + copy(sourceAddr[:addrlen], bs[12:]) + copy(dest[:addrlen], bs[16:]) + } else { + return + } + if !r.cryptokey.isValidSource(dest, addrlen) { util_putBytes(bs) return } - var source address - copy(source[:], bs[8:]) - var snet subnet - copy(snet[:], bs[8:]) switch { - case source.isValid() && source == sinfo.theirAddr: + case sourceAddr.isValid() && sourceAddr == sinfo.theirAddr: case snet.isValid() && snet == sinfo.theirSubnet: default: - key, err := r.cryptokey.getPublicKeyForAddress(source, 16) + key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen) if err != nil || key != sinfo.theirPermPub { util_putBytes(bs) return diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index cbbcdea7..ac703985 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -105,7 +105,7 @@ func (tun *tunDevice) read() error { n != 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o { // Either not an IPv6 packet or not the complete packet for some reason //panic("Should not happen in testing") - continue + //continue } if buf[o+6] == 58 { // Found an ICMPv6 packet From a3a53f92c3891d531df9b9cc2c0a05412950e744 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 22:35:28 +0000 Subject: [PATCH 041/145] Reinstate length/bounds check in tun.go --- src/yggdrasil/tun.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index ac703985..1987c2d2 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -101,11 +101,11 @@ func (tun *tunDevice) read() error { if tun.iface.IsTAP() { o = tun_ETHER_HEADER_LENGTH } - if buf[o]&0xf0 != 0x60 || - n != 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o { - // Either not an IPv6 packet or not the complete packet for some reason - //panic("Should not happen in testing") - //continue + switch { + case buf[o]&0xf0 == 0x60 && n == 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o: + case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o: + default: + continue } if buf[o+6] == 58 { // Found an ICMPv6 packet From 39dab53ac709eb294491fa65627b806d3d03525b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 22:57:53 +0000 Subject: [PATCH 042/145] Update comments in configuration and some godoc descriptions --- src/yggdrasil/ckr.go | 13 +++++++++++++ src/yggdrasil/config/config.go | 16 ++++++++-------- src/yggdrasil/router.go | 2 ++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index f22840c3..2a054711 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -28,6 +28,7 @@ type cryptokey_route struct { destination []byte } +// Initialise crypto-key routing. This must be done before any other CKR calls. func (c *cryptokey) init(core *Core) { c.core = core c.ipv4routes = make([]cryptokey_route, 0) @@ -38,14 +39,19 @@ func (c *cryptokey) init(core *Core) { c.ipv6sources = make([]net.IPNet, 0) } +// Enable or disable crypto-key routing. func (c *cryptokey) setEnabled(enabled bool) { c.enabled = enabled } +// Check if crypto-key routing is enabled. func (c *cryptokey) isEnabled() bool { return c.enabled } +// Check whether the given address (with the address length specified in bytes) +// matches either the current node's address, the node's routed subnet or the +// list of subnets specified in IPv4Sources/IPv6Sources. func (c *cryptokey) isValidSource(addr address, addrlen int) bool { ip := net.IP(addr[:addrlen]) @@ -86,6 +92,8 @@ func (c *cryptokey) isValidSource(addr address, addrlen int) bool { return false } +// Adds a source subnet, which allows traffic with these source addresses to +// be tunnelled using crypto-key routing. func (c *cryptokey) addSourceSubnet(cidr string) error { // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) @@ -121,6 +129,8 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { return nil } +// Adds a destination route for the given CIDR to be tunnelled to the node +// with the given BoxPubKey. func (c *cryptokey) addRoute(cidr string, dest string) error { // Is the CIDR we've been given valid? ipaddr, ipnet, err := net.ParseCIDR(cidr) @@ -190,6 +200,9 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { return errors.New("Unspecified error") } +// Looks up the most specific route for the given address (with the address +// length specified in bytes) from the crypto-key routing table. An error is +// returned if the address is not suitable or no route was found. func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey, error) { // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 38f93404..a14ece9d 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -4,8 +4,8 @@ package config 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."` - Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\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, i.e. { \"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."` + 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."` @@ -17,7 +17,7 @@ type NodeConfig struct { 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."` + 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."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } @@ -39,9 +39,9 @@ type SessionFirewall struct { // TunnelRouting contains the crypto-key routing tables for tunneling type TunnelRouting struct { - Enable bool `comment:"Enable or disable tunneling."` - IPv6Destinations map[string]string `comment:"IPv6 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` - IPv6Sources []string `comment:"Optional IPv6 subnets which are allowed to be used as source addresses\nin addition to this node's Yggdrasil address/subnet."` - IPv4Destinations map[string]string `comment:"IPv4 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` - IPv4Sources []string `comment:"Optional IPv4 subnets which are allowed to be used as source addresses."` + Enable bool `comment:"Enable or disable tunnel routing."` + IPv6Destinations map[string]string `comment:"IPv6 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"` + IPv6Sources []string `comment:"Optional IPv6 source subnets which are allowed to be tunnelled in\naddition to this node's Yggdrasil address/subnet. If not\nspecified, only traffic originating from this node's Yggdrasil\naddress or subnet will be tunnelled."` + IPv4Destinations map[string]string `comment:"IPv4 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"` + IPv4Sources []string `comment:"IPv4 source subnets which are allowed to be tunnelled. Unlike for\nIPv6, this option is required for bridging IPv4 traffic. Only\ntraffic with a source matching these subnets will be tunnelled."` } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 0c633acc..f57f80f0 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -140,6 +140,7 @@ func (r *router) sendPacket(bs []byte) { copy(sourceAddr[:addrlen], bs[12:]) copy(dest[:addrlen], bs[16:]) } else { + // Unknown address length return } if !r.cryptokey.isValidSource(sourceAddr, addrlen) { @@ -287,6 +288,7 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { copy(sourceAddr[:addrlen], bs[12:]) copy(dest[:addrlen], bs[16:]) } else { + // Unknown address length return } if !r.cryptokey.isValidSource(dest, addrlen) { From fbfae473d47ded8bbb4b16822e242a1711d51e19 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Nov 2018 10:04:31 +0000 Subject: [PATCH 043/145] Use full node ID for CKR routes instead of truncated node IDs from the address/subnet --- src/yggdrasil/router.go | 57 ++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index f57f80f0..a2f9cc83 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -125,20 +125,21 @@ func (r *router) sendPacket(bs []byte) { panic("Tried to send a packet shorter than a header...") } var sourceAddr address - var dest address - var snet subnet + var destAddr address + var destSnet subnet + var destNodeID *NodeID var addrlen int if bs[0]&0xf0 == 0x60 { // IPv6 address addrlen = 16 copy(sourceAddr[:addrlen], bs[8:]) - copy(dest[:addrlen], bs[24:]) - copy(snet[:addrlen/2], bs[24:]) + copy(destAddr[:addrlen], bs[24:]) + copy(destSnet[:addrlen/2], bs[24:]) } else if bs[0]&0xf0 == 0x40 { // IPv4 address addrlen = 4 copy(sourceAddr[:addrlen], bs[12:]) - copy(dest[:addrlen], bs[16:]) + copy(destAddr[:addrlen], bs[16:]) } else { // Unknown address length return @@ -146,12 +147,13 @@ func (r *router) sendPacket(bs []byte) { if !r.cryptokey.isValidSource(sourceAddr, addrlen) { return } - if !dest.isValid() && !snet.isValid() { - if key, err := r.cryptokey.getPublicKeyForAddress(dest, addrlen); err == nil { - addr := *address_addrForNodeID(getNodeID(&key)) - copy(dest[:], addr[:]) - copy(snet[:], addr[:]) - if !dest.isValid() && !snet.isValid() { + if !destAddr.isValid() && !destSnet.isValid() { + if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil { + destNodeID = getNodeID(&key) + addr := *address_addrForNodeID(destNodeID) + copy(destAddr[:], addr[:]) + copy(destSnet[:], addr[:]) + if !destAddr.isValid() && !destSnet.isValid() { return } } else { @@ -160,11 +162,26 @@ func (r *router) sendPacket(bs []byte) { } doSearch := func(packet []byte) { var nodeID, mask *NodeID - if dest.isValid() { - nodeID, mask = dest.getNodeIDandMask() - } - if snet.isValid() { - nodeID, mask = snet.getNodeIDandMask() + switch { + case destNodeID != nil: + // We already know the full node ID, probably because it's from a CKR + // route in which the public key is known ahead of time + nodeID = destNodeID + var m NodeID + for i := range m { + m[i] = 0xFF + } + mask = &m + case destAddr.isValid(): + // We don't know the full node ID - try and use the address to generate + // a truncated node ID + nodeID, mask = destAddr.getNodeIDandMask() + case destSnet.isValid(): + // We don't know the full node ID - try and use the subnet to generate + // a truncated node ID + nodeID, mask = destSnet.getNodeIDandMask() + default: + return } sinfo, isIn := r.core.searches.searches[*nodeID] if !isIn { @@ -177,11 +194,11 @@ func (r *router) sendPacket(bs []byte) { } var sinfo *sessionInfo var isIn bool - if dest.isValid() { - sinfo, isIn = r.core.sessions.getByTheirAddr(&dest) + if destAddr.isValid() { + sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr) } - if snet.isValid() { - sinfo, isIn = r.core.sessions.getByTheirSubnet(&snet) + if destSnet.isValid() { + sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet) } switch { case !isIn || !sinfo.init: From 9542bfa902c20f36fe0bc341c59f6cc4bd89381e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Nov 2018 10:16:46 +0000 Subject: [PATCH 044/145] Check the session perm pub key against the CKR key --- src/yggdrasil/router.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index a2f9cc83..72a8cfba 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -23,6 +23,7 @@ package yggdrasil // The router then runs some sanity checks before passing it to the tun import ( + "bytes" "time" "golang.org/x/net/icmp" @@ -127,6 +128,7 @@ func (r *router) sendPacket(bs []byte) { var sourceAddr address var destAddr address var destSnet subnet + var destPubKey *boxPubKey var destNodeID *NodeID var addrlen int if bs[0]&0xf0 == 0x60 { @@ -149,7 +151,8 @@ func (r *router) sendPacket(bs []byte) { } if !destAddr.isValid() && !destSnet.isValid() { if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil { - destNodeID = getNodeID(&key) + destPubKey = &key + destNodeID = getNodeID(destPubKey) addr := *address_addrForNodeID(destNodeID) copy(destAddr[:], addr[:]) copy(destSnet[:], addr[:]) @@ -227,6 +230,14 @@ func (r *router) sendPacket(bs []byte) { } fallthrough // Also send the packet default: + // If we know the public key ahead of time (i.e. a CKR route) then check + // if the session perm pub key matches before we send the packet to it + if destPubKey != nil { + if !bytes.Equal((*destPubKey)[:], sinfo.theirPermPub[:]) { + return + } + } + // 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 { @@ -277,6 +288,7 @@ func (r *router) sendPacket(bs []byte) { // Don't continue - drop the packet return } + sinfo.send <- bs } } From 685b565512381da980278a9514c8fff1a42d31b4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Nov 2018 10:29:08 +0000 Subject: [PATCH 045/145] Check IP header lengths correctly per protocol --- src/yggdrasil/router.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 72a8cfba..fdcbb97f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -122,9 +122,6 @@ func (r *router) mainLoop() { // If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly. // It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their tun/tap disabled. func (r *router) sendPacket(bs []byte) { - if len(bs) < 40 { - panic("Tried to send a packet shorter than a header...") - } var sourceAddr address var destAddr address var destSnet subnet @@ -132,12 +129,20 @@ func (r *router) sendPacket(bs []byte) { var destNodeID *NodeID var addrlen int if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized header + if len(bs) < 40 { + panic("Tried to send a packet shorter than an IPv6 header...") + } // IPv6 address addrlen = 16 copy(sourceAddr[:addrlen], bs[8:]) copy(destAddr[:addrlen], bs[24:]) copy(destSnet[:addrlen/2], bs[24:]) } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized header + if len(bs) < 20 { + panic("Tried to send a packet shorter than an IPv4 header...") + } // IPv4 address addrlen = 4 copy(sourceAddr[:addrlen], bs[12:]) @@ -147,12 +152,19 @@ func (r *router) sendPacket(bs []byte) { return } if !r.cryptokey.isValidSource(sourceAddr, addrlen) { + // The packet had a source address that doesn't belong to us or our + // configured crypto-key routing source subnets return } if !destAddr.isValid() && !destSnet.isValid() { + // The addresses didn't match valid Yggdrasil node addresses so let's see + // whether it matches a crypto-key routing range instead if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search destPubKey = &key destNodeID = getNodeID(destPubKey) + // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil + // address or subnet - this might be superfluous addr := *address_addrForNodeID(destNodeID) copy(destAddr[:], addr[:]) copy(destSnet[:], addr[:]) @@ -160,6 +172,7 @@ func (r *router) sendPacket(bs []byte) { return } } else { + // No public key was found in the CKR table so we've exhausted our options return } } @@ -320,10 +333,13 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { // Unknown address length return } + // Check that the packet is destined for either our Yggdrasil address or + // subnet, or that it matches one of the crypto-key routing source routes if !r.cryptokey.isValidSource(dest, addrlen) { util_putBytes(bs) return } + // See whether the packet they sent should have originated from this session switch { case sourceAddr.isValid() && sourceAddr == sinfo.theirAddr: case snet.isValid() && snet == sinfo.theirSubnet: From 15d5b3f82c21fa772c7fe7de44c8852eb685b7ad Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 9 Nov 2018 23:02:38 -0600 Subject: [PATCH 046/145] comments and minor cleanup --- src/yggdrasil/dht.go | 51 +++++++++++++++-------------------------- src/yggdrasil/search.go | 4 +--- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index b8a303a8..9891ec90 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -1,19 +1,9 @@ package yggdrasil -// TODO signal to predecessor when we replace them? -// Sending a ping with an extra 0 at the end of our coords should be enough to reset our throttle in their table -// That should encorage them to ping us again sooner, and then we can reply with new info -// Maybe remember old predecessor and check this during maintenance? - -// TODO make sure that, if your peer is your successor or predecessor, you still bother to ping them and ask for better nodes -// Basically, don't automatically reset the dhtInfo.recv to time.Now() whenever updating them from the outside -// But *do* set it to something that won't instantly time them out or make them get pingspammed? -// Could set throttle to 0, but that's imperfect at best... pingspam - -// TODO? cache all nodes we ping (from e.g. searches), not just the important ones -// But only send maintenance pings to the important ones - -// TODO reoptimize search stuff (size, timeouts, etc) to play nicer with DHT churn +// A chord-like Distributed Hash Table (DHT). +// Used to look up coords given a NodeID and bitmask (taken from an IPv6 address). +// Keeps track of immediate successor, predecessor, and all peers. +// Also keeps track of other nodes if they're closer in tree space than all other known nodes encountered when heading in either direction to that point, under the hypothesis that, for the kinds of networks we care about, this should probabilistically include the node needed to keep lookups to near O(logn) steps. import ( "sort" @@ -70,7 +60,7 @@ type dht struct { imp []*dhtInfo } -// Initializes the DHT +// Initializes the DHT. func (t *dht) init(c *Core) { t.core = c t.nodeID = *t.core.GetNodeID() @@ -78,23 +68,19 @@ func (t *dht) init(c *Core) { t.reset() } -// Resets the DHT in response to coord changes -// This empties all info from the DHT and drops outstanding requests +// Resets the DHT in response to coord changes. +// This empties all info from the DHT and drops outstanding requests. func (t *dht) reset() { t.reqs = make(map[boxPubKey]map[NodeID]time.Time) t.table = make(map[NodeID]*dhtInfo) t.imp = nil } -// Does a DHT lookup and returns up to dht_lookup_size results +// Does a DHT lookup and returns up to dht_lookup_size results. func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo { results := make([]*dhtInfo, 0, len(t.table)) - //imp := t.getImportant() for _, info := range t.table { results = append(results, info) - //if t.isImportant(info, imp) { - // results = append(results, info) - //} } sort.SliceStable(results, func(i, j int) bool { return dht_ordered(nodeID, results[i].getNodeID(), results[j].getNodeID()) @@ -105,7 +91,7 @@ func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo { return results } -// Insert into table, preserving the time we last sent a packet if the node was already in the table, otherwise setting that time to now +// Insert into table, preserving the time we last sent a packet if the node was already in the table, otherwise setting that time to now. func (t *dht) insert(info *dhtInfo) { if *info.getNodeID() == t.nodeID { // This shouldn't happen, but don't add it if it does @@ -133,8 +119,7 @@ func (t *dht) insert(info *dhtInfo) { t.table[*info.getNodeID()] = info } -// Return true if first/second/third are (partially) ordered correctly -// FIXME? maybe total ordering makes more sense +// Return true if first/second/third are (partially) ordered correctly. func dht_ordered(first, second, third *NodeID) bool { lessOrEqual := func(first, second *NodeID) bool { for idx := 0; idx < NodeIDLen; idx++ { @@ -182,8 +167,7 @@ func (t *dht) handleReq(req *dhtReq) { key: req.Key, coords: req.Coords, } - imp := t.getImportant() - if _, isIn := t.table[*info.getNodeID()]; !isIn && t.isImportant(&info, imp) { + if _, isIn := t.table[*info.getNodeID()]; !isIn && t.isImportant(&info) { t.insert(&info) } } @@ -222,8 +206,7 @@ func (t *dht) handleRes(res *dhtRes) { key: res.Key, coords: res.Coords, } - imp := t.getImportant() - if t.isImportant(&rinfo, imp) { + if t.isImportant(&rinfo) { t.insert(&rinfo) } for _, info := range res.Infos { @@ -234,11 +217,10 @@ func (t *dht) handleRes(res *dhtRes) { // TODO? don't skip if coords are different? continue } - if t.isImportant(info, imp) { + if t.isImportant(info) { t.ping(info, nil) } } - // TODO add everyting else to a rumor mill for later use? (when/how?) } // Sends a lookup request to the specified node. @@ -267,6 +249,7 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { reqsToDest[req.Dest] = time.Now() } +// Sends a lookup to this info, looking for the target. func (t *dht) ping(info *dhtInfo, target *NodeID) { // Creates a req for the node at dhtInfo, asking them about the target (if one is given) or themself (if no target is given) if target == nil { @@ -282,6 +265,7 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { t.sendReq(&req, info) } +// Periodic maintenance work to keep important DHT nodes alive. func (t *dht) doMaintenance() { now := time.Now() for infoID, info := range t.table { @@ -302,6 +286,7 @@ func (t *dht) doMaintenance() { } } +// Gets a list of important nodes, used by isImportant. func (t *dht) getImportant() []*dhtInfo { if t.imp == nil { // Get a list of all known nodes @@ -343,7 +328,9 @@ func (t *dht) getImportant() []*dhtInfo { return t.imp } -func (t *dht) isImportant(ninfo *dhtInfo, important []*dhtInfo) bool { +// Returns true if this is a node we need to keep track of for the DHT to work. +func (t *dht) isImportant(ninfo *dhtInfo) bool { + important := t.getImportant() // Check if ninfo is of equal or greater importance to what we already know loc := t.core.switchTable.getLocator() ndist := uint64(loc.dist(ninfo.coords)) diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index f22fbe2a..21694907 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -113,13 +113,11 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { // Sort sort.SliceStable(sinfo.toVisit, func(i, j int) bool { // Should return true if i is closer to the destination than j - // FIXME for some reason it works better backwards, why?! - //return dht_ordered(sinfo.toVisit[j].getNodeID(), sinfo.toVisit[i].getNodeID(), &res.Dest) return dht_ordered(&res.Dest, sinfo.toVisit[i].getNodeID(), sinfo.toVisit[j].getNodeID()) }) // Truncate to some maximum size if len(sinfo.toVisit) > search_MAX_SEARCH_SIZE { - sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] //FIXME debug + sinfo.toVisit = sinfo.toVisit[:search_MAX_SEARCH_SIZE] } } From d50e1bc80369265dda86ba7711d17a44d44a8e0e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 10 Nov 2018 15:46:10 +0000 Subject: [PATCH 047/145] More complete NDP implementation for TAP mode, which tracks individual MAC addresses for neighbors --- src/yggdrasil/icmpv6.go | 115 +++++++++++++++++++++++++++++----------- src/yggdrasil/tun.go | 46 ++++++++++++---- 2 files changed, 120 insertions(+), 41 deletions(-) diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go index 0491f880..e7cd5fbb 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/yggdrasil/icmpv6.go @@ -23,11 +23,10 @@ type macAddress [6]byte const len_ETHER = 14 type icmpv6 struct { - tun *tunDevice - peermac macAddress - peerlladdr net.IP - mylladdr net.IP - mymac macAddress + tun *tunDevice + mylladdr net.IP + mymac macAddress + peermacs map[address]macAddress } // Marshal returns the binary encoding of h. @@ -52,13 +51,16 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // addresses. func (i *icmpv6) init(t *tunDevice) { i.tun = t + i.peermacs = make(map[address]macAddress) // Our MAC address and link-local address - copy(i.mymac[:], []byte{ - 0x02, 0x00, 0x00, 0x00, 0x00, 0x02}) + i.mymac = macAddress{ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x02} i.mylladdr = net.IP{ 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE} + copy(i.mymac[1:], i.tun.core.boxPub[:]) + copy(i.mylladdr[9:], i.tun.core.boxPub[:]) } // Parses an incoming ICMPv6 packet. The packet provided may be either an @@ -73,7 +75,7 @@ func (i *icmpv6) parse_packet(datain []byte) { if i.tun.iface.IsTAP() { response, err = i.parse_packet_tap(datain) } else { - response, err = i.parse_packet_tun(datain) + response, err = i.parse_packet_tun(datain, nil) } if err != nil { @@ -89,16 +91,14 @@ func (i *icmpv6) parse_packet(datain []byte) { // A response buffer is also created for the response message, also complete // with ethernet headers. func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) { - // Store the peer MAC address - copy(i.peermac[:6], datain[6:12]) - // Ignore non-IPv6 frames if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) { return nil, nil } // Hand over to parse_packet_tun to interpret the IPv6 packet - ipv6packet, err := i.parse_packet_tun(datain[len_ETHER:]) + mac := datain[6:12] + ipv6packet, err := i.parse_packet_tun(datain[len_ETHER:], &mac) if err != nil { return nil, err } @@ -120,7 +120,7 @@ func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) { // sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the // ICMPv6 message match a known expected type. The relevant handler function // is then called and a response packet may be returned. -func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) { +func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error) { // Parse the IPv6 packet headers ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen]) if err != nil { @@ -137,9 +137,6 @@ func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) { return nil, err } - // Store the peer link local address, it will come in useful later - copy(i.peerlladdr[:], ipv6Header.Src[:]) - // Parse the ICMPv6 message contents icmpv6Header, err := icmp.ParseMessage(58, datain[ipv6.HeaderLen:]) if err != nil { @@ -149,24 +146,31 @@ func (i *icmpv6) parse_packet_tun(datain []byte) ([]byte, error) { // Check for a supported message type switch icmpv6Header.Type { case ipv6.ICMPTypeNeighborSolicitation: - { - response, err := i.handle_ndp(datain[ipv6.HeaderLen:]) - if err == nil { - // Create our ICMPv6 response - responsePacket, err := i.create_icmpv6_tun( - ipv6Header.Src, i.mylladdr, - ipv6.ICMPTypeNeighborAdvertisement, 0, - &icmp.DefaultMessageBody{Data: response}) - if err != nil { - return nil, err - } - - // Send it back - return responsePacket, nil - } else { + response, err := i.handle_ndp(datain[ipv6.HeaderLen:]) + if err == nil { + // Create our ICMPv6 response + responsePacket, err := i.create_icmpv6_tun( + ipv6Header.Src, i.mylladdr, + ipv6.ICMPTypeNeighborAdvertisement, 0, + &icmp.DefaultMessageBody{Data: response}) + if err != nil { return nil, err } + + // Send it back + return responsePacket, nil + } else { + return nil, err } + case ipv6.ICMPTypeNeighborAdvertisement: + if datamac != nil { + var addr address + var mac macAddress + copy(addr[:], ipv6Header.Src[:]) + copy(mac[:], (*datamac)[:]) + i.peermacs[addr] = mac + } + return nil, errors.New("No response needed") } return nil, errors.New("ICMPv6 type not matched") @@ -238,6 +242,55 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, return responsePacket, nil } +func (i *icmpv6) create_ndp_tap(in []byte) ([]byte, error) { + // Parse the IPv6 packet headers + ipv6Header, err := ipv6.ParseHeader(in[:ipv6.HeaderLen]) + if err != nil { + return nil, err + } + + // Check if the packet is IPv6 + if ipv6Header.Version != ipv6.Version { + return nil, err + } + + // Check if the packet is ICMPv6 + if ipv6Header.NextHeader != 58 { + return nil, err + } + + // Create the ND payload + var payload [28]byte + copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) + copy(payload[4:20], ipv6Header.Dst[:]) + copy(payload[20:22], []byte{0x01, 0x01}) + copy(payload[22:28], i.mymac[:6]) + + // Create the ICMPv6 solicited-node address + var dstaddr address + copy(dstaddr[:13], []byte{ + 0xFF, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xFF}) + copy(dstaddr[13:], ipv6Header.Dst[13:16]) + + // Create the multicast MAC + var dstmac macAddress + copy(dstmac[:2], []byte{0x33, 0x33}) + copy(dstmac[2:6], dstaddr[12:16]) + + // Create the ND request + requestPacket, err := i.create_icmpv6_tap( + dstmac, dstaddr[:], i.mylladdr, + ipv6.ICMPTypeNeighborSolicitation, 0, + &icmp.DefaultMessageBody{Data: payload[:]}) + if err != nil { + return nil, err + } + + return requestPacket, nil +} + // Generates a response to an NDP discovery packet. This is effectively called // when the host operating system generates an NDP request for any address in // the fd00::/8 range, so that the operating system knows to route that traffic diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 1987c2d2..429c9d7a 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -3,6 +3,7 @@ package yggdrasil // This manages the tun driver to send/recv packets to/from applications import ( + "errors" "yggdrasil/defaults" "github.com/songgao/packets/ethernet" @@ -61,17 +62,42 @@ func (tun *tunDevice) write() error { continue } if tun.iface.IsTAP() { - var frame ethernet.Frame - frame.Prepare( - tun.icmpv6.peermac[:6], // Destination MAC address - tun.icmpv6.mymac[:6], // Source MAC address - ethernet.NotTagged, // VLAN tagging - ethernet.IPv6, // Ethertype - len(data)) // Payload length - copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) - if _, err := tun.iface.Write(frame); err != nil { - panic(err) + var destAddr address + if data[0]&0xf0 == 0x60 { + if len(data) < 40 { + panic("Tried to send a packet shorter than an IPv6 header...") + } + copy(destAddr[:16], data[24:]) + } else if data[0]&0xf0 == 0x40 { + if len(data) < 20 { + panic("Tried to send a packet shorter than an IPv4 header...") + } + copy(destAddr[:4], data[16:]) + } else { + return errors.New("Invalid address family") } + if peermac, ok := tun.icmpv6.peermacs[destAddr]; ok { + var frame ethernet.Frame + frame.Prepare( + peermac[:6], // Destination MAC address + tun.icmpv6.mymac[:6], // Source MAC address + ethernet.NotTagged, // VLAN tagging + ethernet.IPv6, // Ethertype + len(data)) // Payload length + copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) + if _, err := tun.iface.Write(frame); err != nil { + panic(err) + } + } else { + request, err := tun.icmpv6.create_ndp_tap(data) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + } + } else { if _, err := tun.iface.Write(data); err != nil { panic(err) From adc32fe92f183b04b14eb74ba3bb501b62f2d3a2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 10 Nov 2018 17:32:03 +0000 Subject: [PATCH 048/145] Track further neighbor state, don't send more NDPs than needed --- src/yggdrasil/icmpv6.go | 43 ++++++++++++++++---------------- src/yggdrasil/tun.go | 55 +++++++++++++++++++++++++++++++++-------- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go index e7cd5fbb..c62475c5 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/yggdrasil/icmpv6.go @@ -13,6 +13,7 @@ import ( "encoding/binary" "errors" "net" + "time" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" @@ -26,7 +27,14 @@ type icmpv6 struct { tun *tunDevice mylladdr net.IP mymac macAddress - peermacs map[address]macAddress + peermacs map[address]neighbor +} + +type neighbor struct { + mac macAddress + learned bool + lastadvertisement time.Time + lastsolicitation time.Time } // Marshal returns the binary encoding of h. @@ -51,7 +59,7 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // addresses. func (i *icmpv6) init(t *tunDevice) { i.tun = t - i.peermacs = make(map[address]macAddress) + i.peermacs = make(map[address]neighbor) // Our MAC address and link-local address i.mymac = macAddress{ @@ -168,7 +176,11 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error var mac macAddress copy(addr[:], ipv6Header.Src[:]) copy(mac[:], (*datamac)[:]) - i.peermacs[addr] = mac + neighbor := i.peermacs[addr] + neighbor.mac = mac + neighbor.learned = true + neighbor.lastadvertisement = time.Now() + i.peermacs[addr] = neighbor } return nil, errors.New("No response needed") } @@ -242,27 +254,11 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, return responsePacket, nil } -func (i *icmpv6) create_ndp_tap(in []byte) ([]byte, error) { - // Parse the IPv6 packet headers - ipv6Header, err := ipv6.ParseHeader(in[:ipv6.HeaderLen]) - if err != nil { - return nil, err - } - - // Check if the packet is IPv6 - if ipv6Header.Version != ipv6.Version { - return nil, err - } - - // Check if the packet is ICMPv6 - if ipv6Header.NextHeader != 58 { - return nil, err - } - +func (i *icmpv6) create_ndp_tap(dst address) ([]byte, error) { // Create the ND payload var payload [28]byte copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) - copy(payload[4:20], ipv6Header.Dst[:]) + copy(payload[4:20], dst[:]) copy(payload[20:22], []byte{0x01, 0x01}) copy(payload[22:28], i.mymac[:6]) @@ -272,7 +268,7 @@ func (i *icmpv6) create_ndp_tap(in []byte) ([]byte, error) { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF}) - copy(dstaddr[13:], ipv6Header.Dst[13:16]) + copy(dstaddr[13:], dst[13:16]) // Create the multicast MAC var dstmac macAddress @@ -287,6 +283,9 @@ func (i *icmpv6) create_ndp_tap(in []byte) ([]byte, error) { if err != nil { return nil, err } + neighbor := i.peermacs[dstaddr] + neighbor.lastsolicitation = time.Now() + i.peermacs[dstaddr] = neighbor return requestPacket, nil } diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 429c9d7a..55ddbd74 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -4,6 +4,7 @@ package yggdrasil import ( "errors" + "time" "yggdrasil/defaults" "github.com/songgao/packets/ethernet" @@ -49,6 +50,21 @@ func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) } go func() { panic(tun.read()) }() go func() { panic(tun.write()) }() + go func() { + for { + if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok { + break + } + request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + time.Sleep(time.Second) + } + }() return nil } @@ -76,7 +92,35 @@ func (tun *tunDevice) write() error { } else { return errors.New("Invalid address family") } - if peermac, ok := tun.icmpv6.peermacs[destAddr]; ok { + sendndp := func(destAddr address) { + neigh, known := tun.icmpv6.peermacs[destAddr] + known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) + if !known { + request, err := tun.icmpv6.create_ndp_tap(destAddr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + tun.icmpv6.peermacs[destAddr] = neighbor{ + lastsolicitation: time.Now(), + } + } + } + var peermac macAddress + var peerknown bool + if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + } else if neighbor, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok && neighbor.learned { + peermac = neighbor.mac + peerknown = true + sendndp(destAddr) + } else { + sendndp(tun.core.router.addr) + } + if peerknown { var frame ethernet.Frame frame.Prepare( peermac[:6], // Destination MAC address @@ -88,16 +132,7 @@ func (tun *tunDevice) write() error { if _, err := tun.iface.Write(frame); err != nil { panic(err) } - } else { - request, err := tun.icmpv6.create_ndp_tap(data) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } } - } else { if _, err := tun.iface.Write(data); err != nil { panic(err) From 6fab0e9507d2a309b65cadb69e14d46366a23c2c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 10 Nov 2018 18:33:52 +0000 Subject: [PATCH 049/145] Fix CKR (IPv4/IPv6) in TAP mode so frames sent to node MAC, base MAC/LL from node IPv6 address --- src/yggdrasil/icmpv6.go | 4 ++-- src/yggdrasil/tun.go | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/icmpv6.go b/src/yggdrasil/icmpv6.go index c62475c5..957b192e 100644 --- a/src/yggdrasil/icmpv6.go +++ b/src/yggdrasil/icmpv6.go @@ -67,8 +67,8 @@ func (i *icmpv6) init(t *tunDevice) { i.mylladdr = net.IP{ 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE} - copy(i.mymac[1:], i.tun.core.boxPub[:]) - copy(i.mylladdr[9:], i.tun.core.boxPub[:]) + copy(i.mymac[:], i.tun.core.router.addr[:]) + copy(i.mylladdr[9:], i.tun.core.router.addr[1:]) } // Parses an incoming ICMPv6 packet. The packet provided may be either an diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 55ddbd74..447ba617 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -3,6 +3,7 @@ package yggdrasil // This manages the tun driver to send/recv packets to/from applications import ( + "bytes" "errors" "time" "yggdrasil/defaults" @@ -110,6 +111,13 @@ func (tun *tunDevice) write() error { } var peermac macAddress var peerknown bool + if data[0]&0xf0 == 0x40 { + destAddr = tun.core.router.addr + } else if data[0]&0xf0 == 0x60 { + if !bytes.Equal(tun.core.router.addr[:16], destAddr[:16]) && !bytes.Equal(tun.core.router.subnet[:8], destAddr[:8]) { + destAddr = tun.core.router.addr + } + } if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned { peermac = neighbor.mac peerknown = true @@ -121,12 +129,19 @@ func (tun *tunDevice) write() error { sendndp(tun.core.router.addr) } if peerknown { + var proto ethernet.Ethertype + switch { + case data[0]&0xf0 == 0x60: + proto = ethernet.IPv6 + case data[0]&0xf0 == 0x40: + proto = ethernet.IPv4 + } var frame ethernet.Frame frame.Prepare( peermac[:6], // Destination MAC address tun.icmpv6.mymac[:6], // Source MAC address ethernet.NotTagged, // VLAN tagging - ethernet.IPv6, // Ethertype + proto, // Ethertype len(data)) // Payload length copy(frame[tun_ETHER_HEADER_LENGTH:], data[:]) if _, err := tun.iface.Write(frame); err != nil { From 1b1b776097b1a888198844490a29c449f1c1910e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 10 Nov 2018 22:39:15 -0600 Subject: [PATCH 050/145] fix crash when starting in tun mode --- src/yggdrasil/tun.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 447ba617..4b3617c0 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -51,21 +51,23 @@ func (tun *tunDevice) start(ifname string, iftapmode bool, addr string, mtu int) } go func() { panic(tun.read()) }() go func() { panic(tun.write()) }() - go func() { - for { - if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok { - break + if iftapmode { + go func() { + for { + if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok { + break + } + request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr) + if err != nil { + panic(err) + } + if _, err := tun.iface.Write(request); err != nil { + panic(err) + } + time.Sleep(time.Second) } - request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } - time.Sleep(time.Second) - } - }() + }() + } return nil } From 8cf8b0ec411666e3fde9005086987d693abae270 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 11 Nov 2018 00:00:47 -0600 Subject: [PATCH 051/145] fix bug in recvPacket for packets coming from a subnet --- src/yggdrasil/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index fdcbb97f..7b3324c7 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -323,7 +323,7 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { addrlen = 16 copy(sourceAddr[:addrlen], bs[8:]) copy(dest[:addrlen], bs[24:]) - copy(snet[:addrlen/2], bs[24:]) + copy(snet[:addrlen/2], bs[8:]) } else if bs[0]&0xf0 == 0x40 { // IPv4 address addrlen = 4 From 289f1ce7c22fa317e5d06184e86fe903e039ca2a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 14 Nov 2018 21:58:48 -0600 Subject: [PATCH 052/145] set packet version in sim, so it plays nice with new parsing from the new ckr code --- misc/sim/treesim.go | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 4aa463dc..3391e1aa 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -267,6 +267,7 @@ func pingNodes(store map[[32]byte]*Node) { copy(packet[8:24], sourceAddr) copy(packet[24:40], destAddr) copy(packet[40:], bs) + packet[0] = 6 << 4 source.send <- packet } destCount := 0 From ef6cece7207d2921bb2576eb4f4c6cb31078641c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 16 Nov 2018 19:32:12 -0600 Subject: [PATCH 053/145] fix sim and tune dht to bootstrap a little faster --- misc/sim/treesim.go | 2 +- src/yggdrasil/dht.go | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 3391e1aa..3a0959e0 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -439,7 +439,7 @@ func main() { pingNodes(kstore) //pingBench(kstore) // Only after disabling debug output //stressTest(kstore) - time.Sleep(120 * time.Second) + //time.Sleep(120 * time.Second) dumpDHTSize(kstore) // note that this uses racey functions to read things... if false { // This connects the sim to the local network diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 9891ec90..220e291a 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -82,10 +82,16 @@ func (t *dht) lookup(nodeID *NodeID, everything bool) []*dhtInfo { for _, info := range t.table { results = append(results, info) } - sort.SliceStable(results, func(i, j int) bool { - return dht_ordered(nodeID, results[i].getNodeID(), results[j].getNodeID()) - }) if len(results) > dht_lookup_size { + // Drop the middle part, so we keep some nodes before and after. + // This should help to bootstrap / recover more quickly. + sort.SliceStable(results, func(i, j int) bool { + return dht_ordered(nodeID, results[i].getNodeID(), results[j].getNodeID()) + }) + newRes := make([]*dhtInfo, 0, len(results)) + newRes = append(newRes, results[len(results)-dht_lookup_size/2:]...) + newRes = append(newRes, results[:len(results)-dht_lookup_size/2]...) + results = newRes results = results[:dht_lookup_size] } return results @@ -168,7 +174,7 @@ func (t *dht) handleReq(req *dhtReq) { coords: req.Coords, } if _, isIn := t.table[*info.getNodeID()]; !isIn && t.isImportant(&info) { - t.insert(&info) + t.ping(&info, nil) } } @@ -276,7 +282,7 @@ func (t *dht) doMaintenance() { } for _, info := range t.getImportant() { if now.Sub(info.recv) > info.throttle { - t.ping(info, info.getNodeID()) + t.ping(info, nil) info.pings++ info.throttle += time.Second if info.throttle > 30*time.Second { From e9cff0506c875a04303a2b5064d30bfcc07a5f0d Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 19 Nov 2018 21:30:52 -0600 Subject: [PATCH 054/145] comment the switch a little better and limit how much uptime can affect which peer is used as a parent --- src/yggdrasil/switch.go | 47 ++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index e877880f..28f83122 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -21,6 +21,7 @@ import ( const switch_timeout = time.Minute const switch_updateInterval = switch_timeout / 2 const switch_throttle = switch_updateInterval / 2 +const switch_max_time = time.Hour // The switch locator represents the topology and network state dependent info about a node, minus the signatures that go with it. // Nodes will pick the best root they see, provided that the root continues to push out updates with new timestamps. @@ -390,39 +391,55 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort) { } return true }() + // Get the time we've known about the sender (or old parent's) current coords, up to a maximum of `switch_max_time`. sTime := now.Sub(sender.firstSeen) - pTime := oldParent.time.Sub(oldParent.firstSeen) + switch_timeout + if sTime > switch_max_time { + sTime = switch_max_time + } + pTime := now.Sub(oldParent.firstSeen) + if pTime > switch_max_time { + pTime = switch_max_time + } // Really want to compare sLen/sTime and pLen/pTime // Cross multiplied to avoid divide-by-zero - cost := len(sender.locator.coords) * int(pTime.Seconds()) - pCost := len(t.data.locator.coords) * int(sTime.Seconds()) + cost := float64(len(sender.locator.coords)) * pTime.Seconds() + pCost := float64(len(t.data.locator.coords)) * sTime.Seconds() dropTstamp, isIn := t.drop[sender.locator.root] - // Here be dragons switch { - case !noLoop: // do nothing - case isIn && dropTstamp >= sender.locator.tstamp: // do nothing + case !noLoop: + // This route loops, so we can't use the sender as our parent. + case isIn && dropTstamp >= sender.locator.tstamp: + // This is a known root with a timestamp older than a known timeout, so we can't trust it to be a new announcement. case firstIsBetter(&sender.locator.root, &t.data.locator.root): + // This is a better root than what we're currently using, so we should update. updateRoot = true - case t.data.locator.root != sender.locator.root: // do nothing - case t.data.locator.tstamp > sender.locator.tstamp: // do nothing + case t.data.locator.root != sender.locator.root: + // This is not the same root, and it's apparently not better (from the above), so we should ignore it. + case t.data.locator.tstamp > sender.locator.tstamp: + // This timetsamp is older than the most recently seen one from this root, so we should ignore it. case noParent: + // We currently have no working parent, and at this point in the switch statement, anything is better than nothing. updateRoot = true case cost < pCost: + // The sender has a better combination of path length and reliability than the current parent. updateRoot = true - case sender.port != t.parent: // do nothing + case sender.port != t.parent: + // Ignore further cases if the sender isn't our parent. case !equiv(&sender.locator, &t.data.locator): - // Special case - // If coords changed, then this may now be a worse parent than before - // Re-parent the node (de-parent and reprocess the message) - // Then reprocess *all* messages to look for a better parent - // This is so we don't keep using this node as our parent if there's something better + // Special case: + // If coords changed, then this may now be a worse parent than before. + // Re-parent the node (de-parent and reprocess the message). + // Then reprocess *all* messages to look for a better parent. + // This is so we don't keep using this node as our parent if there's something better. t.parent = 0 t.unlockedHandleMsg(msg, fromPort) for _, info := range t.data.peers { t.unlockedHandleMsg(&info.msg, info.port) } - case now.Sub(t.time) < switch_throttle: // do nothing + case now.Sub(t.time) < switch_throttle: + // We've already gotten an update from this root recently, so ignore this one to avoid flooding. case sender.locator.tstamp > t.data.locator.tstamp: + // The timestamp was updated, so we need to update locally and send to our peers. updateRoot = true } if updateRoot { From 5fa23b1e38f991eec4295bf6b845cedda0658947 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 20 Nov 2018 22:04:18 -0600 Subject: [PATCH 055/145] move router.recvPacket calls into the main router goroutine, to make the ckr checks threadsafe --- src/yggdrasil/router.go | 24 +++++++++++++++++------- src/yggdrasil/session.go | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 0723d735..41e2863f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -36,15 +36,22 @@ type router struct { core *Core addr address subnet subnet - 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" - 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) - admin chan func() // pass a lambda for the admin socket to query stuff + 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() + 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) + admin chan func() // pass a lambda for the admin socket to query stuff cryptokey cryptokey } +// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the tun. +type router_recvPacket struct { + bs []byte + sinfo *sessionInfo +} + // Initializes the router struct, which includes setting up channels to/from the tun/tap. func (r *router) init(core *Core) { r.core = core @@ -63,6 +70,7 @@ func (r *router) init(core *Core) { } r.in = in r.out = func(packet []byte) { p.handlePacket(packet) } // The caller is responsible for go-ing if it needs to not block + r.toRecv = make(chan router_recvPacket, 32) recv := make(chan []byte, 32) send := make(chan []byte, 32) r.recv = recv @@ -70,7 +78,7 @@ func (r *router) init(core *Core) { r.core.tun.recv = recv r.core.tun.send = send r.reset = make(chan struct{}, 1) - r.admin = make(chan func()) + r.admin = make(chan func(), 32) r.cryptokey.init(r.core) // go r.mainLoop() } @@ -91,6 +99,8 @@ func (r *router) mainLoop() { defer ticker.Stop() for { select { + case rp := <-r.toRecv: + r.recvPacket(rp.bs, rp.sinfo) case p := <-r.in: r.handleIn(p) case p := <-r.send: diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index a7b1d431..b0022d78 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -589,5 +589,5 @@ func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) { sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) - sinfo.core.router.recvPacket(bs, sinfo) + sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo} } From 59530274115212de0d45985a1f17f65e1c736bd8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 21 Nov 2018 00:10:20 -0600 Subject: [PATCH 056/145] switch from []byte to boxPubKey in ckr code, and start adding admin functions for existing code (yggdrasilctl.go still needs pretty printing support for the responses to the new get functions) --- src/yggdrasil/admin.go | 48 ++++++++++++++++++++++++++++++++++++++++++ src/yggdrasil/ckr.go | 16 ++++++-------- yggdrasilctl.go | 2 +- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 42ea8a51..f8749ce8 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -232,6 +232,54 @@ func (a *admin) init(c *Core, listenaddr string) { }, errors.New("Failed to remove allowed key") } }) + a.addHandler("addSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) + }) + if err == nil { + return admin_info{"added": []string{in["subnet"].(string)}}, nil + } else { + return admin_info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") + } + }) + a.addHandler("addRoute", []string{"subnet", "destPubKey"}, func(in admin_info) (admin_info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["destPubKey"].(string)) + }) + if err == nil { + return admin_info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, nil + } else { + return admin_info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, errors.New("Failed to add route") + } + }) + a.addHandler("getSourceSubnets", []string{}, func(in admin_info) (admin_info, error) { + var subnets []string + a.core.router.doAdmin(func() { + getSourceSubnets := func(snets []net.IPNet) { + for _, subnet := range snets { + subnets = append(subnets, subnet.String()) + } + } + getSourceSubnets(a.core.router.cryptokey.ipv4sources) + getSourceSubnets(a.core.router.cryptokey.ipv6sources) + }) + return admin_info{"source_subnets": subnets}, nil + }) + a.addHandler("getRoutes", []string{}, func(in admin_info) (admin_info, error) { + var routes []string + a.core.router.doAdmin(func() { + getRoutes := func(ckrs []cryptokey_route) { + for _, ckr := range ckrs { + routes = append(routes, fmt.Sprintf("%s via %s", ckr.subnet.String(), hex.EncodeToString(ckr.destination[:]))) + } + } + getRoutes(a.core.router.cryptokey.ipv4routes) + getRoutes(a.core.router.cryptokey.ipv6routes) + }) + return admin_info{"routes": routes}, nil + }) } // start runs the admin API socket to listen for / respond to admin API calls. diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 2a054711..f440b3d8 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -25,7 +25,7 @@ type cryptokey struct { type cryptokey_route struct { subnet net.IPNet - destination []byte + destination boxPubKey } // Initialise crypto-key routing. This must be done before any other CKR calls. @@ -171,13 +171,15 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { } } // Decode the public key - if boxPubKey, err := hex.DecodeString(dest); err != nil { + if bpk, err := hex.DecodeString(dest); err != nil && len(bpk) == boxPubKeyLen { return err } else { // Add the new crypto-key route + var key boxPubKey + copy(key[:], bpk) *routingtable = append(*routingtable, cryptokey_route{ subnet: *ipnet, - destination: boxPubKey, + destination: key, }) // Sort so most specific routes are first @@ -227,9 +229,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey // Check if there's a cache entry for this addr if route, ok := (*routingcache)[addr]; ok { - var box boxPubKey - copy(box[:boxPubKeyLen], route.destination) - return box, nil + return route.destination, nil } // No cache was found - start by converting the address into a net.IP @@ -245,9 +245,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey (*routingcache)[addr] = route // Return the boxPubKey - var box boxPubKey - copy(box[:boxPubKeyLen], route.destination) - return box, nil + return route.destination, nil } } diff --git a/yggdrasilctl.go b/yggdrasilctl.go index d98386b7..13a0c234 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -231,7 +231,7 @@ func main() { uint(k), uint(v), uint(queuesizepercent), uint(portqueuepackets[k])) } } - case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey": + case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute": if _, ok := res["added"]; ok { for _, v := range res["added"].([]interface{}) { fmt.Println("Added:", fmt.Sprint(v)) From 4870a2e149340853c6c2f88b52ed66400b6c09b4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 22 Nov 2018 21:30:56 -0600 Subject: [PATCH 057/145] removeSourceSubnet and removeRoute via the admin api --- src/yggdrasil/admin.go | 22 +++++++++++ src/yggdrasil/ckr.go | 88 +++++++++++++++++++++++++++++++++++++++++- yggdrasilctl.go | 2 +- 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index f8749ce8..de5ae878 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -280,6 +280,28 @@ func (a *admin) init(c *Core, listenaddr string) { }) return admin_info{"routes": routes}, nil }) + a.addHandler("removeSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) + }) + if err == nil { + return admin_info{"removed": []string{in["subnet"].(string)}}, nil + } else { + return admin_info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") + } + }) + a.addHandler("removeRoute", []string{"subnet", "destPubKey"}, func(in admin_info) (admin_info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["destPubKey"].(string)) + }) + if err == nil { + return admin_info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, nil + } else { + return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, errors.New("Failed to remove route") + } + }) } // start runs the admin API socket to listen for / respond to admin API calls. diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index f440b3d8..a2a29e9d 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -171,8 +171,10 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { } } // Decode the public key - if bpk, err := hex.DecodeString(dest); err != nil && len(bpk) == boxPubKeyLen { + if bpk, err := hex.DecodeString(dest); err != nil { return err + } else if len(bpk) != boxPubKeyLen { + return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) } else { // Add the new crypto-key route var key boxPubKey @@ -252,3 +254,87 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey // No route was found if we got to this point return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) } + +// Removes a source subnet, which allows traffic with these source addresses to +// be tunnelled using crypto-key routing. +func (c *cryptokey) removeSourceSubnet(cidr string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingsources = &c.ipv6sources + } else if prefixsize == net.IPv4len*8 { + routingsources = &c.ipv4sources + } else { + return errors.New("Unexpected prefix size") + } + + // Check if we already have this CIDR + for idx, subnet := range *routingsources { + if subnet.String() == ipnet.String() { + *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) + c.core.log.Println("Removed CKR source subnet", cidr) + return nil + } + } + return errors.New("Source subnet not found") +} + +// Removes a destination route for the given CIDR to be tunnelled to the node +// with the given BoxPubKey. +func (c *cryptokey) removeRoute(cidr string, dest string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if prefixsize == net.IPv4len*8 { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return errors.New("Unexpected prefix size") + } + + // Decode the public key + bpk, err := hex.DecodeString(dest) + if err != nil { + return err + } else if len(bpk) != boxPubKeyLen { + return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) + } + netStr := ipnet.String() + + for idx, route := range *routingtable { + if bytes.Equal(route.destination[:], bpk) && route.subnet.String() == netStr { + *routingtable = append((*routingtable)[:idx], (*routingtable)[idx+1:]...) + for k := range *routingcache { + delete(*routingcache, k) + } + c.core.log.Println("Removed CKR destination subnet %s via %s", cidr, dest) + return nil + } + } + return errors.New(fmt.Sprintf("Route does not exists for %s", cidr)) +} diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 13a0c234..6cc43e86 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -231,7 +231,7 @@ func main() { uint(k), uint(v), uint(queuesizepercent), uint(portqueuepackets[k])) } } - case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute": + case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute", "removesourcesubnet", "removeroute": if _, ok := res["added"]; ok { for _, v := range res["added"].([]interface{}) { fmt.Println("Added:", fmt.Sprint(v)) From 12cc7fc639cae30b52c7826b2603044cb69fbf1f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 22 Nov 2018 21:37:57 -0600 Subject: [PATCH 058/145] add yggdrasilctl support for getSourceSubnets and getRoutes --- yggdrasilctl.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 6cc43e86..a3e24096 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -274,6 +274,28 @@ func main() { fmt.Println("-", v) } } + case "getsourcesubnets": + if _, ok := res["source_subnets"]; !ok { + fmt.Println("No source subnets found") + } else if res["source_subnets"] == nil { + fmt.Println("No source subnets found") + } else { + fmt.Println("Source subnets:") + for _, v := range res["source_subnets"].([]interface{}) { + fmt.Println("-", v) + } + } + case "getroutes": + if _, ok := res["routes"]; !ok { + fmt.Println("No routes found") + } else if res["routes"] == nil { + fmt.Println("No routes found") + } else { + fmt.Println("Routes:") + for _, v := range res["routes"].([]interface{}) { + fmt.Println("-", v) + } + } default: if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil { fmt.Println(string(json)) From 6d0e40045ab8eb4c3c322a299073709d477c8d63 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Thu, 22 Nov 2018 21:41:16 -0600 Subject: [PATCH 059/145] cleanup/fixes from go vet --- src/yggdrasil/admin.go | 3 +-- src/yggdrasil/ckr.go | 4 +--- src/yggdrasil/dht.go | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index de5ae878..1e85907c 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -456,7 +456,6 @@ func (n *admin_nodeInfo) toString() string { out = append(out, fmt.Sprintf("%v: %v", p.key, p.val)) } return strings.Join(out, ", ") - return fmt.Sprint(*n) } // printInfos returns a newline separated list of strings from admin_nodeInfos, e.g. a printable string of info about all peers. @@ -597,7 +596,7 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { // getData_getSwitchQueues returns info from Core.switchTable for an queue data. func (a *admin) getData_getSwitchQueues() admin_nodeInfo { var peerInfos admin_nodeInfo - switchTable := a.core.switchTable + switchTable := &a.core.switchTable getSwitchQueues := func() { queues := make([]map[string]interface{}, 0) for k, v := range switchTable.queues.bufs { diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index a2a29e9d..b73946c4 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -200,8 +200,6 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { c.core.log.Println("Added CKR destination subnet", cidr) return nil } - - return errors.New("Unspecified error") } // Looks up the most specific route for the given address (with the address @@ -332,7 +330,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error { for k := range *routingcache { delete(*routingcache, k) } - c.core.log.Println("Removed CKR destination subnet %s via %s", cidr, dest) + c.core.log.Printf("Removed CKR destination subnet %s via %s\n", cidr, dest) return nil } } diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 220e291a..31acd8a2 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -102,7 +102,6 @@ func (t *dht) insert(info *dhtInfo) { if *info.getNodeID() == t.nodeID { // This shouldn't happen, but don't add it if it does return - panic("FIXME") } info.recv = time.Now() if oldInfo, isIn := t.table[*info.getNodeID()]; isIn { From 8d6beebac48674b4677a1acc6f389c0756d276a4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 24 Nov 2018 20:04:14 -0600 Subject: [PATCH 060/145] clean up old requests during dht maintenance --- src/yggdrasil/dht.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 31acd8a2..84d8e409 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -273,6 +273,19 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { // Periodic maintenance work to keep important DHT nodes alive. func (t *dht) doMaintenance() { now := time.Now() + for key, dests := range t.reqs { + for nodeID, start := range dests { + if now.Sub(start) > 6*time.Second { + if info, isIn := t.table[*getNodeID(&key)]; isIn { + info.pings++ + } + delete(dests, nodeID) + } + if len(dests) == 0 { + delete(t.reqs, key) + } + } + } for infoID, info := range t.table { if now.Sub(info.recv) > time.Minute || info.pings > 3 { delete(t.table, infoID) From 4e156bd4f72806142bc8712290099f715be8c8b3 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 12:25:38 -0600 Subject: [PATCH 061/145] better cleanup of maps --- misc/sim/run-sim | 2 +- misc/sim/treesim.go | 16 ++++------------ src/yggdrasil/dht.go | 12 ++++++++---- src/yggdrasil/peer.go | 1 - src/yggdrasil/session.go | 35 +++++++++++++++++++++++++++++++++++ src/yggdrasil/signature.go | 5 +++++ 6 files changed, 53 insertions(+), 18 deletions(-) diff --git a/misc/sim/run-sim b/misc/sim/run-sim index 985fe2f3..abe108cf 100755 --- a/misc/sim/run-sim +++ b/misc/sim/run-sim @@ -1,4 +1,4 @@ #!/bin/bash export GOPATH=$PWD go get -d yggdrasil -go run -tags debug misc/sim/treesim.go +go run -tags debug misc/sim/treesim.go "$@" diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 3a0959e0..8a4bb2a1 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -8,6 +8,7 @@ import "strconv" import "time" import "log" +import "runtime" import "runtime/pprof" import "flag" @@ -280,17 +281,7 @@ func pingNodes(store map[[32]byte]*Node) { } destAddr := dest.core.DEBUG_getAddr()[:] ticker := time.NewTicker(150 * time.Millisecond) - ch := make(chan bool, 1) - ch <- true - doTicker := func() { - for range ticker.C { - select { - case ch <- true: - default: - } - } - } - go doTicker() + sendTo(payload, destAddr) for loop := true; loop; { select { case packet := <-dest.recv: @@ -299,7 +290,7 @@ func pingNodes(store map[[32]byte]*Node) { loop = false } } - case <-ch: + case <-ticker.C: sendTo(payload, destAddr) //dumpDHTSize(store) // note that this uses racey functions to read things... } @@ -458,4 +449,5 @@ func main() { var block chan struct{} <-block } + runtime.GC() } diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 84d8e409..9c2afc2d 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -273,19 +273,23 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { // Periodic maintenance work to keep important DHT nodes alive. func (t *dht) doMaintenance() { now := time.Now() + newReqs := make(map[boxPubKey]map[NodeID]time.Time, len(t.reqs)) for key, dests := range t.reqs { + newDests := make(map[NodeID]time.Time, len(dests)) for nodeID, start := range dests { if now.Sub(start) > 6*time.Second { if info, isIn := t.table[*getNodeID(&key)]; isIn { info.pings++ } - delete(dests, nodeID) - } - if len(dests) == 0 { - delete(t.reqs, key) + continue } + newDests[nodeID] = start + } + if len(newDests) > 0 { + newReqs[key] = newDests } } + t.reqs = newReqs for infoID, info := range t.table { if now.Sub(info.recv) > time.Minute || info.pings > 3 { delete(t.table, infoID) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index cf827925..e4d09988 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -175,7 +175,6 @@ func (p *peer) doSendSwitchMsgs() { // This must be launched in a separate goroutine by whatever sets up the peer struct. // It handles link protocol traffic. func (p *peer) linkLoop() { - go p.doSendSwitchMsgs() tick := time.NewTicker(time.Second) defer tick.Stop() for { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index b0022d78..92ae262b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -311,6 +311,11 @@ func (ss *sessions) createSession(theirPermKey *boxPubKey) *sessionInfo { func (ss *sessions) cleanup() { // Time thresholds almost certainly could use some adjusting + for k := range ss.permShared { + // Delete a key, to make sure this eventually shrinks to 0 + delete(ss.permShared, k) + break + } if time.Since(ss.lastCleanup) < time.Minute { return } @@ -319,6 +324,36 @@ func (ss *sessions) cleanup() { s.close() } } + permShared := make(map[boxPubKey]*boxSharedKey, len(ss.permShared)) + for k, v := range ss.permShared { + permShared[k] = v + } + ss.permShared = permShared + sinfos := make(map[handle]*sessionInfo, len(ss.sinfos)) + for k, v := range ss.sinfos { + sinfos[k] = v + } + ss.sinfos = sinfos + byMySes := make(map[boxPubKey]*handle, len(ss.byMySes)) + for k, v := range ss.byMySes { + byMySes[k] = v + } + ss.byMySes = byMySes + byTheirPerm := make(map[boxPubKey]*handle, len(ss.byTheirPerm)) + for k, v := range ss.byTheirPerm { + byTheirPerm[k] = v + } + ss.byTheirPerm = byTheirPerm + addrToPerm := make(map[address]*boxPubKey, len(ss.addrToPerm)) + for k, v := range ss.addrToPerm { + addrToPerm[k] = v + } + ss.addrToPerm = addrToPerm + subnetToPerm := make(map[subnet]*boxPubKey, len(ss.subnetToPerm)) + for k, v := range ss.subnetToPerm { + subnetToPerm[k] = v + } + ss.subnetToPerm = subnetToPerm ss.lastCleanup = time.Now() } diff --git a/src/yggdrasil/signature.go b/src/yggdrasil/signature.go index 203c9adc..12a3d37b 100644 --- a/src/yggdrasil/signature.go +++ b/src/yggdrasil/signature.go @@ -86,5 +86,10 @@ func (m *sigManager) cleanup() { delete(m.checked, s) } } + newChecked := make(map[sigBytes]knownSig, len(m.checked)) + for s, k := range m.checked { + newChecked[s] = k + } + m.checked = newChecked m.lastCleaned = time.Now() } From 9046dbde4fe8b71df2dbb0b5a0091d59d29adb95 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 13:06:54 -0600 Subject: [PATCH 062/145] remove sigManager, it seems safer to just burn the CPU than to store a map of strings of potentially arbitrary length --- src/yggdrasil/core.go | 2 - src/yggdrasil/peer.go | 2 +- src/yggdrasil/router.go | 1 - src/yggdrasil/signature.go | 95 -------------------------------------- 4 files changed, 1 insertion(+), 99 deletions(-) delete mode 100644 src/yggdrasil/signature.go diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 2f60a1ba..9b9bcc15 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -22,7 +22,6 @@ type Core struct { sigPriv sigPrivKey switchTable switchTable peers peers - sigs sigManager sessions sessions router router dht dht @@ -50,7 +49,6 @@ func (c *Core) init(bpub *boxPubKey, 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) c.sessions.init(c) diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index e4d09988..67aa805a 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -316,7 +316,7 @@ func (p *peer) handleSwitchMsg(packet []byte) { sigMsg.Hops = msg.Hops[:idx] loc.coords = append(loc.coords, hop.Port) bs := getBytesForSig(&hop.Next, &sigMsg) - if !p.core.sigs.check(&prevKey, &hop.Sig, bs) { + if !verify(&prevKey, bs, &hop.Sig) { p.core.peers.removePeer(p.port) } prevKey = hop.Next diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 41e2863f..b4824767 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -121,7 +121,6 @@ func (r *router) mainLoop() { r.core.switchTable.doMaintenance() r.core.dht.doMaintenance() r.core.sessions.cleanup() - r.core.sigs.cleanup() util_getBytes() // To slowly drain things } case f := <-r.admin: diff --git a/src/yggdrasil/signature.go b/src/yggdrasil/signature.go deleted file mode 100644 index 12a3d37b..00000000 --- a/src/yggdrasil/signature.go +++ /dev/null @@ -1,95 +0,0 @@ -package yggdrasil - -// This is where we record which signatures we've previously checked -// It's so we can avoid needlessly checking them again - -import ( - "sync" - "time" -) - -// This keeps track of what signatures have already been checked. -// It's used to skip expensive crypto operations, given that many signatures are likely to be the same for the average node's peers. -type sigManager struct { - mutex sync.RWMutex - checked map[sigBytes]knownSig - lastCleaned time.Time -} - -// Represents a known signature. -// Includes the key, the signature bytes, the bytes that were signed, and the time it was last used. -type knownSig struct { - key sigPubKey - sig sigBytes - bs []byte - time time.Time -} - -// Initializes the signature manager. -func (m *sigManager) init() { - m.checked = make(map[sigBytes]knownSig) -} - -// Checks if a key and signature match the supplied bytes. -// If the same key/sig/bytes have been checked before, it returns true from the cached results. -// If not, it checks the key, updates it in the cache if successful, and returns the checked results. -func (m *sigManager) check(key *sigPubKey, sig *sigBytes, bs []byte) bool { - if m.isChecked(key, sig, bs) { - return true - } - verified := verify(key, bs, sig) - if verified { - m.putChecked(key, sig, bs) - } - return verified -} - -// Checks the cache to see if this key/sig/bytes combination has already been verified. -// Returns true if it finds a match. -func (m *sigManager) isChecked(key *sigPubKey, sig *sigBytes, bs []byte) bool { - m.mutex.RLock() - defer m.mutex.RUnlock() - k, isIn := m.checked[*sig] - if !isIn { - return false - } - if k.key != *key || k.sig != *sig || len(bs) != len(k.bs) { - return false - } - for idx := 0; idx < len(bs); idx++ { - if bs[idx] != k.bs[idx] { - return false - } - } - k.time = time.Now() - return true -} - -// Puts a new result into the cache. -// This result is then used by isChecked to skip the expensive crypto verification if it's needed again. -// This is useful because, for nodes with multiple peers, there is often a lot of overlap between the signatures provided by each peer. -func (m *sigManager) putChecked(key *sigPubKey, newsig *sigBytes, bs []byte) { - m.mutex.Lock() - defer m.mutex.Unlock() - k := knownSig{key: *key, sig: *newsig, bs: bs, time: time.Now()} - m.checked[*newsig] = k -} - -func (m *sigManager) cleanup() { - m.mutex.Lock() - defer m.mutex.Unlock() - if time.Since(m.lastCleaned) < time.Minute { - return - } - for s, k := range m.checked { - if time.Since(k.time) > time.Minute { - delete(m.checked, s) - } - } - newChecked := make(map[sigBytes]knownSig, len(m.checked)) - for s, k := range m.checked { - newChecked[s] = k - } - m.checked = newChecked - m.lastCleaned = time.Now() -} From e17efb6e915296d6f0789cd4bf59cf2ee89badbf Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 13:21:13 -0600 Subject: [PATCH 063/145] don't penalize dht timeouts a second time --- src/yggdrasil/dht.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 9c2afc2d..e7815b7a 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -278,9 +278,6 @@ func (t *dht) doMaintenance() { newDests := make(map[NodeID]time.Time, len(dests)) for nodeID, start := range dests { if now.Sub(start) > 6*time.Second { - if info, isIn := t.table[*getNodeID(&key)]; isIn { - info.pings++ - } continue } newDests[nodeID] = start From d520a8a1d509f0eab670a34b763392451ace1781 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 16:10:32 -0600 Subject: [PATCH 064/145] refactor dht code to call arbitrary callbacks instead of only searches.checkDHTRes, and add admin API fuction to dhtPing a node (with an optional target NodeID) --- src/yggdrasil/admin.go | 73 +++++++++++++++++++++++++++++++++++++++++ src/yggdrasil/dht.go | 68 +++++++++++++++++++++----------------- src/yggdrasil/search.go | 2 ++ 3 files changed, 112 insertions(+), 31 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 1e85907c..ca3baa27 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -302,6 +302,24 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, errors.New("Failed to remove route") } }) + a.addHandler("dhtPing", []string{"key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { + if in["target"] == nil { + in["target"] = "none" + } + result, err := a.admin_dhtPing(in["key"].(string), in["coords"].(string), in["target"].(string)) + if err == nil { + var infos []map[string]string + for _, dinfo := range result.Infos { + info := make(map[string]string) + info["key"] = hex.EncodeToString(dinfo.key[:]) + info["coords"] = fmt.Sprintf("%v", dinfo.coords) + infos = append(infos, info) + } + return admin_info{"nodes": infos}, nil + } else { + return admin_info{}, err + } + }) } // start runs the admin API socket to listen for / respond to admin API calls. @@ -536,6 +554,7 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { table := a.core.switchTable.table.Load().(lookupTable) coords := table.self.getCoords() self := admin_nodeInfo{ + {"key", hex.EncodeToString(a.core.boxPub[:])}, {"ip", a.core.GetAddress().String()}, {"subnet", a.core.GetSubnet().String()}, {"coords", fmt.Sprint(coords)}, @@ -702,6 +721,60 @@ func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) { return } +// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID. +func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) { + var key boxPubKey + if keyBytes, err := hex.DecodeString(keyString); err != nil { + return dhtRes{}, err + } else { + copy(key[:], keyBytes) + } + var coords []byte + for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") { + if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil { + return dhtRes{}, err + } else { + coords = append(coords, uint8(u64)) + } + } + resCh := make(chan *dhtRes) + info := dhtInfo{ + key: key, + coords: coords, + } + target := *info.getNodeID() + if targetString == "none" { + // Leave the default target in place + } else if targetBytes, err := hex.DecodeString(targetString); err != nil { + return dhtRes{}, err + } else if len(targetBytes) != len(target) { + return dhtRes{}, errors.New("Incorrect target NodeID length") + } else { + target = NodeID{} + copy(target[:], targetBytes) + } + rq := dhtReqKey{info.key, target} + sendPing := func() { + a.core.dht.addCallback(&rq, func(res *dhtRes) { + defer func() { recover() }() + select { + case resCh <- res: + default: + } + }) + a.core.dht.ping(&info, &target) + } + a.core.router.doAdmin(sendPing) + go func() { + time.Sleep(6 * time.Second) + close(resCh) + }() + for res := range resCh { + return *res, nil + } + return dhtRes{}, errors.New(fmt.Sprintf("DHT ping 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/dht.go b/src/yggdrasil/dht.go index e7815b7a..fd5ca585 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -49,12 +49,19 @@ type dhtRes struct { Infos []*dhtInfo // response } +// Parts of a DHT req usable as a key in a map. +type dhtReqKey struct { + key boxPubKey + dest NodeID +} + // The main DHT struct. type dht struct { - core *Core - nodeID NodeID - peers chan *dhtInfo // other goroutines put incoming dht updates here - reqs map[boxPubKey]map[NodeID]time.Time + core *Core + nodeID NodeID + peers chan *dhtInfo // other goroutines put incoming dht updates here + reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests + callbacks map[dhtReqKey][]func(*dhtRes) // Search and admin lookup callbacks // These next two could be replaced by a single linked list or similar... table map[NodeID]*dhtInfo imp []*dhtInfo @@ -65,13 +72,14 @@ func (t *dht) init(c *Core) { t.core = c t.nodeID = *t.core.GetNodeID() t.peers = make(chan *dhtInfo, 1024) + t.callbacks = make(map[dhtReqKey][]func(*dhtRes)) t.reset() } // Resets the DHT in response to coord changes. // This empties all info from the DHT and drops outstanding requests. func (t *dht) reset() { - t.reqs = make(map[boxPubKey]map[NodeID]time.Time) + t.reqs = make(map[dhtReqKey]time.Time) t.table = make(map[NodeID]*dhtInfo) t.imp = nil } @@ -194,19 +202,31 @@ func (t *dht) sendRes(res *dhtRes, req *dhtReq) { t.core.router.out(packet) } +// Adds a callback and removes it after some timeout. +func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) { + t.callbacks[*rq] = append(t.callbacks[*rq], callback) + go func() { + time.Sleep(6 * time.Second) + t.core.router.admin <- func() { + delete(t.callbacks, *rq) + } + }() +} + // Reads a lookup response, checks that we had sent a matching request, and processes the response info. // This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses func (t *dht) handleRes(res *dhtRes) { + rq := dhtReqKey{res.Key, res.Dest} + for _, callback := range t.callbacks[rq] { + callback(res) + } + delete(t.callbacks, rq) t.core.searches.handleDHTRes(res) - reqs, isIn := t.reqs[res.Key] + _, isIn := t.reqs[rq] if !isIn { return } - _, isIn = reqs[res.Dest] - if !isIn { - return - } - delete(reqs, res.Dest) + delete(t.reqs, rq) rinfo := dhtInfo{ key: res.Key, coords: res.Coords, @@ -243,15 +263,8 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { } packet := p.encode() t.core.router.out(packet) - reqsToDest, isIn := t.reqs[dest.key] - if !isIn { - t.reqs[dest.key] = make(map[NodeID]time.Time) - reqsToDest, isIn = t.reqs[dest.key] - if !isIn { - panic("This should never happen") - } - } - reqsToDest[req.Dest] = time.Now() + rq := dhtReqKey{req.Key, req.Dest} + t.reqs[rq] = time.Now() } // Sends a lookup to this info, looking for the target. @@ -273,17 +286,10 @@ func (t *dht) ping(info *dhtInfo, target *NodeID) { // Periodic maintenance work to keep important DHT nodes alive. func (t *dht) doMaintenance() { now := time.Now() - newReqs := make(map[boxPubKey]map[NodeID]time.Time, len(t.reqs)) - for key, dests := range t.reqs { - newDests := make(map[NodeID]time.Time, len(dests)) - for nodeID, start := range dests { - if now.Sub(start) > 6*time.Second { - continue - } - newDests[nodeID] = start - } - if len(newDests) > 0 { - newReqs[key] = newDests + newReqs := make(map[dhtReqKey]time.Time, len(t.reqs)) + for key, start := range t.reqs { + if now.Sub(start) < 6*time.Second { + newReqs[key] = start } } t.reqs = newReqs diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 21694907..be156dc6 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -132,6 +132,8 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { // Send to the next search target var next *dhtInfo next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] + rq := dhtReqKey{next.key, sinfo.dest} + s.core.dht.addCallback(&rq, s.handleDHTRes) s.core.dht.ping(next, &sinfo.dest) } } From 12e635f9464360187c8c6324717c3dbbbe9ced97 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 16:16:06 -0600 Subject: [PATCH 065/145] adjust dhtPing response so 'nodes' defaults to an empty list instead of null --- src/yggdrasil/admin.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index ca3baa27..fa8f8dd8 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -308,11 +308,12 @@ func (a *admin) init(c *Core, listenaddr string) { } result, err := a.admin_dhtPing(in["key"].(string), in["coords"].(string), in["target"].(string)) if err == nil { - var infos []map[string]string + infos := make([]map[string]string, 0, len(result.Infos)) for _, dinfo := range result.Infos { - info := make(map[string]string) - info["key"] = hex.EncodeToString(dinfo.key[:]) - info["coords"] = fmt.Sprintf("%v", dinfo.coords) + info := map[string]string{ + "key": hex.EncodeToString(dinfo.key[:]), + "coords": fmt.Sprintf("%v", dinfo.coords), + } infos = append(infos, info) } return admin_info{"nodes": infos}, nil From 9937a6102e91daa80e82e30820128287af92b72f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 16:29:47 -0600 Subject: [PATCH 066/145] add callbacks to maintenance map cleanup --- src/yggdrasil/dht.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index fd5ca585..e49e343a 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -293,6 +293,11 @@ func (t *dht) doMaintenance() { } } t.reqs = newReqs + newCallbacks := make(map[dhtReqKey][]func(*dhtRes), len(t.callbacks)) + for key, callback := range t.callbacks { + newCallbacks[key] = callback + } + t.callbacks = newCallbacks for infoID, info := range t.table { if now.Sub(info.recv) > time.Minute || info.pings > 3 { delete(t.table, infoID) From 7954fa3c33cbb7632a2bd24caf414221925440ef Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 17:08:45 -0600 Subject: [PATCH 067/145] store one callback instead of many, needed to prevent search failures if there are multiple outstanding packets --- src/yggdrasil/dht.go | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index e49e343a..6e08ad23 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -59,9 +59,9 @@ type dhtReqKey struct { type dht struct { core *Core nodeID NodeID - peers chan *dhtInfo // other goroutines put incoming dht updates here - reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests - callbacks map[dhtReqKey][]func(*dhtRes) // Search and admin lookup callbacks + peers chan *dhtInfo // other goroutines put incoming dht updates here + reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests + callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks // These next two could be replaced by a single linked list or similar... table map[NodeID]*dhtInfo imp []*dhtInfo @@ -72,7 +72,7 @@ func (t *dht) init(c *Core) { t.core = c t.nodeID = *t.core.GetNodeID() t.peers = make(chan *dhtInfo, 1024) - t.callbacks = make(map[dhtReqKey][]func(*dhtRes)) + t.callbacks = make(map[dhtReqKey]dht_callbackInfo) t.reset() } @@ -202,26 +202,25 @@ func (t *dht) sendRes(res *dhtRes, req *dhtReq) { t.core.router.out(packet) } +type dht_callbackInfo struct { + f func(*dhtRes) + time time.Time +} + // Adds a callback and removes it after some timeout. func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) { - t.callbacks[*rq] = append(t.callbacks[*rq], callback) - go func() { - time.Sleep(6 * time.Second) - t.core.router.admin <- func() { - delete(t.callbacks, *rq) - } - }() + info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)} + t.callbacks[*rq] = info } // Reads a lookup response, checks that we had sent a matching request, and processes the response info. // This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses func (t *dht) handleRes(res *dhtRes) { rq := dhtReqKey{res.Key, res.Dest} - for _, callback := range t.callbacks[rq] { - callback(res) + if callback, isIn := t.callbacks[rq]; isIn { + callback.f(res) + delete(t.callbacks, rq) } - delete(t.callbacks, rq) - t.core.searches.handleDHTRes(res) _, isIn := t.reqs[rq] if !isIn { return @@ -263,7 +262,7 @@ func (t *dht) sendReq(req *dhtReq, dest *dhtInfo) { } packet := p.encode() t.core.router.out(packet) - rq := dhtReqKey{req.Key, req.Dest} + rq := dhtReqKey{dest.key, req.Dest} t.reqs[rq] = time.Now() } @@ -293,9 +292,11 @@ func (t *dht) doMaintenance() { } } t.reqs = newReqs - newCallbacks := make(map[dhtReqKey][]func(*dhtRes), len(t.callbacks)) + newCallbacks := make(map[dhtReqKey]dht_callbackInfo, len(t.callbacks)) for key, callback := range t.callbacks { - newCallbacks[key] = callback + if now.Before(callback.time) { + newCallbacks[key] = callback + } } t.callbacks = newCallbacks for infoID, info := range t.table { From d253bb750c8435d48e064e547e71940e05a62982 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 17:50:56 -0600 Subject: [PATCH 068/145] yggdrasilctl support --- yggdrasilctl.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index a3e24096..a2b0731f 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -296,6 +296,17 @@ func main() { fmt.Println("-", v) } } + case "dhtping": + if _, ok := res["nodes"]; !ok { + fmt.Println("No nodes found") + } else if res["nodes"] == nil { + fmt.Println("No nodes found") + } else { + for _, v := range res["nodes"].([]interface{}) { + m := v.(map[string]interface{}) + fmt.Println("-", m["key"], m["coords"]) + } + } default: if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil { fmt.Println(string(json)) From a34ca40594f70ef16786ecdd3e562f061df7163f Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 17:59:36 -0600 Subject: [PATCH 069/145] use a buffered channel to avoid races, and run gofmt --- src/yggdrasil/admin.go | 2 +- yggdrasilctl.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index fa8f8dd8..00625e14 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -738,7 +738,7 @@ func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtR coords = append(coords, uint8(u64)) } } - resCh := make(chan *dhtRes) + resCh := make(chan *dhtRes, 1) info := dhtInfo{ key: key, coords: coords, diff --git a/yggdrasilctl.go b/yggdrasilctl.go index a2b0731f..4a76361f 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -303,7 +303,7 @@ func main() { fmt.Println("No nodes found") } else { for _, v := range res["nodes"].([]interface{}) { - m := v.(map[string]interface{}) + m := v.(map[string]interface{}) fmt.Println("-", m["key"], m["coords"]) } } From 0ec6207e054554e36f443c5b0633372d09cf32f0 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 18:25:31 -0600 Subject: [PATCH 070/145] better response format and yggdrasilctl printing --- src/yggdrasil/admin.go | 5 +++-- yggdrasilctl.go | 13 +------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 00625e14..2aee240b 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -308,13 +308,14 @@ func (a *admin) init(c *Core, listenaddr string) { } result, err := a.admin_dhtPing(in["key"].(string), in["coords"].(string), in["target"].(string)) if err == nil { - infos := make([]map[string]string, 0, len(result.Infos)) + infos := make(map[string]map[string]string, len(result.Infos)) for _, dinfo := range result.Infos { info := map[string]string{ "key": hex.EncodeToString(dinfo.key[:]), "coords": fmt.Sprintf("%v", dinfo.coords), } - infos = append(infos, info) + addr := net.IP(address_addrForNodeID(getNodeID(&dinfo.key))[:]).String() + infos[addr] = info } return admin_info{"nodes": infos}, nil } else { diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 4a76361f..b3b1cef0 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -107,7 +107,7 @@ func main() { switch strings.ToLower(req["request"].(string)) { case "dot": fmt.Println(res["dot"]) - case "help", "getpeers", "getswitchpeers", "getdht", "getsessions": + case "help", "getpeers", "getswitchpeers", "getdht", "getsessions", "dhtping": maxWidths := make(map[string]int) var keyOrder []string keysOrdered := false @@ -296,17 +296,6 @@ func main() { fmt.Println("-", v) } } - case "dhtping": - if _, ok := res["nodes"]; !ok { - fmt.Println("No nodes found") - } else if res["nodes"] == nil { - fmt.Println("No nodes found") - } else { - for _, v := range res["nodes"].([]interface{}) { - m := v.(map[string]interface{}) - fmt.Println("-", m["key"], m["coords"]) - } - } default: if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil { fmt.Println(string(json)) From d8d1e63c36735ac8eb097aa43e31c69c347beb12 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 25 Nov 2018 20:33:33 -0600 Subject: [PATCH 071/145] fix infinite loop from interaction between dht.isImportant and dht.insert --- src/yggdrasil/dht.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 6e08ad23..aa694f7a 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -361,6 +361,9 @@ func (t *dht) getImportant() []*dhtInfo { // Returns true if this is a node we need to keep track of for the DHT to work. func (t *dht) isImportant(ninfo *dhtInfo) bool { + if ninfo.key == t.core.boxPub { + return false + } important := t.getImportant() // Check if ninfo is of equal or greater importance to what we already know loc := t.core.switchTable.getLocator() From 5b10af7399d50b12385d8ee093dfe7cf919e85ad Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 26 Nov 2018 17:34:26 +0000 Subject: [PATCH 072/145] Rename key to box_pub_key in admin socket for consistency --- src/yggdrasil/admin.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 2aee240b..c5b138c5 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -202,32 +202,32 @@ func (a *admin) init(c *Core, listenaddr string) { a.addHandler("getAllowedEncryptionPublicKeys", []string{}, func(in admin_info) (admin_info, error) { return admin_info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil }) - a.addHandler("addAllowedEncryptionPublicKey", []string{"key"}, func(in admin_info) (admin_info, error) { - if a.addAllowedEncryptionPublicKey(in["key"].(string)) == nil { + a.addHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) { + if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { return admin_info{ "added": []string{ - in["key"].(string), + in["box_pub_key"].(string), }, }, nil } else { return admin_info{ "not_added": []string{ - in["key"].(string), + in["box_pub_key"].(string), }, }, errors.New("Failed to add allowed key") } }) - a.addHandler("removeAllowedEncryptionPublicKey", []string{"key"}, func(in admin_info) (admin_info, error) { - if a.removeAllowedEncryptionPublicKey(in["key"].(string)) == nil { + a.addHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) { + if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { return admin_info{ "removed": []string{ - in["key"].(string), + in["box_pub_key"].(string), }, }, nil } else { return admin_info{ "not_removed": []string{ - in["key"].(string), + in["box_pub_key"].(string), }, }, errors.New("Failed to remove allowed key") } @@ -302,17 +302,17 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, errors.New("Failed to remove route") } }) - a.addHandler("dhtPing", []string{"key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { + a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { if in["target"] == nil { in["target"] = "none" } - result, err := a.admin_dhtPing(in["key"].(string), in["coords"].(string), in["target"].(string)) + result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string)) if err == nil { infos := make(map[string]map[string]string, len(result.Infos)) for _, dinfo := range result.Infos { info := map[string]string{ - "key": hex.EncodeToString(dinfo.key[:]), - "coords": fmt.Sprintf("%v", dinfo.coords), + "box_pub_key": hex.EncodeToString(dinfo.key[:]), + "coords": fmt.Sprintf("%v", dinfo.coords), } addr := net.IP(address_addrForNodeID(getNodeID(&dinfo.key))[:]).String() infos[addr] = info @@ -556,7 +556,7 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { table := a.core.switchTable.table.Load().(lookupTable) coords := table.self.getCoords() self := admin_nodeInfo{ - {"key", hex.EncodeToString(a.core.boxPub[:])}, + {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])}, {"ip", a.core.GetAddress().String()}, {"subnet", a.core.GetSubnet().String()}, {"coords", fmt.Sprint(coords)}, From 8239989c36e3dbcf48abccabdd86b2e1fe15082c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 26 Nov 2018 17:38:02 +0000 Subject: [PATCH 073/145] Send box_pub_key with getSessions, getDHT, getSwitchPeers and getPeers --- src/yggdrasil/admin.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index c5b138c5..a91198ad 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -583,6 +583,7 @@ func (a *admin) getData_getPeers() []admin_nodeInfo { {"bytes_sent", atomic.LoadUint64(&p.bytesSent)}, {"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)}, {"endpoint", p.endpoint}, + {"box_pub_key", hex.EncodeToString(p.box[:])}, } peerInfos = append(peerInfos, info) } @@ -608,6 +609,7 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { {"bytes_sent", atomic.LoadUint64(&peer.bytesSent)}, {"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)}, {"endpoint", peer.endpoint}, + {"box_pub_key", hex.EncodeToString(peer.box[:])}, } peerInfos = append(peerInfos, info) } @@ -661,6 +663,7 @@ func (a *admin) getData_getDHT() []admin_nodeInfo { {"ip", net.IP(addr[:]).String()}, {"coords", fmt.Sprint(v.coords)}, {"last_seen", int(now.Sub(v.recv).Seconds())}, + {"box_pub_key", hex.EncodeToString(v.key[:])}, } infos = append(infos, info) } @@ -682,6 +685,7 @@ func (a *admin) getData_getSessions() []admin_nodeInfo { {"was_mtu_fixed", sinfo.wasMTUFixed}, {"bytes_sent", sinfo.bytesSent}, {"bytes_recvd", sinfo.bytesRecvd}, + {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])}, } infos = append(infos, info) } From a6be4bacbc7fce1dd9fad827dfddccc1fe4ea383 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 26 Nov 2018 17:50:31 +0000 Subject: [PATCH 074/145] Don't show box_pub_key in tables --- yggdrasilctl.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index b3b1cef0..75f78f1d 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -116,6 +116,9 @@ func main() { for slk, slv := range tlv.(map[string]interface{}) { if !keysOrdered { for k := range slv.(map[string]interface{}) { + if k == "box_pub_key" || k == "box_sig_key" { + continue + } keyOrder = append(keyOrder, fmt.Sprint(k)) } sort.Strings(keyOrder) @@ -182,6 +185,12 @@ func main() { if coords, ok := v.(map[string]interface{})["coords"].(string); ok { fmt.Println("Coords:", coords) } + if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok { + fmt.Println("Public encryption key:", boxPubKey) + } + if boxSigKey, ok := v.(map[string]interface{})["box_sig_key"].(string); ok { + fmt.Println("Public signing key:", boxPubKey) + } } case "getswitchqueues": maximumqueuesize := float64(4194304) From 315aadae067be7d68e07c2e15628313a20db3f7f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 26 Nov 2018 17:51:30 +0000 Subject: [PATCH 075/145] Rename help to list --- src/yggdrasil/admin.go | 4 ++-- yggdrasilctl.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index a91198ad..c253031a 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -52,12 +52,12 @@ func (a *admin) addHandler(name string, args []string, handler func(admin_info) func (a *admin) init(c *Core, listenaddr string) { a.core = c a.listenaddr = listenaddr - a.addHandler("help", nil, func(in admin_info) (admin_info, error) { + a.addHandler("list", nil, func(in admin_info) (admin_info, error) { handlers := make(map[string]interface{}) for _, handler := range a.handlers { handlers[handler.name] = admin_info{"fields": handler.args} } - return admin_info{"help": handlers}, nil + return admin_info{"list": handlers}, nil }) a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) { return admin_info{"dot": string(a.getResponse_dot())}, nil diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 75f78f1d..c40ea7fc 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -107,7 +107,7 @@ func main() { switch strings.ToLower(req["request"].(string)) { case "dot": fmt.Println(res["dot"]) - case "help", "getpeers", "getswitchpeers", "getdht", "getsessions", "dhtping": + case "list", "getpeers", "getswitchpeers", "getdht", "getsessions", "dhtping": maxWidths := make(map[string]int) var keyOrder []string keysOrdered := false From 498d664f51fdceaa9d43aa7028957c492bbf72e8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 26 Nov 2018 17:55:34 +0000 Subject: [PATCH 076/145] Add -v for verbose output from yggdrasilctl --- yggdrasilctl.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index c40ea7fc..3fa543b5 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -18,11 +18,12 @@ type admin_info map[string]interface{} func main() { server := flag.String("endpoint", defaults.GetDefaults().DefaultAdminListen, "Admin socket endpoint") injson := flag.Bool("json", false, "Output in JSON format") + verbose := flag.Bool("v", false, "Verbose output (includes public keys)") flag.Parse() args := flag.Args() if len(args) == 0 { - fmt.Println("usage:", os.Args[0], "[-endpoint=proto://server] [-json] command [key=value] [...]") + fmt.Println("usage:", os.Args[0], "[-endpoint=proto://server] [-verbose] [-json] command [key=value] [...]") fmt.Println("example:", os.Args[0], "getPeers") fmt.Println("example:", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") fmt.Println("example:", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") @@ -116,8 +117,10 @@ func main() { for slk, slv := range tlv.(map[string]interface{}) { if !keysOrdered { for k := range slv.(map[string]interface{}) { - if k == "box_pub_key" || k == "box_sig_key" { - continue + if !*verbose { + if k == "box_pub_key" || k == "box_sig_key" { + continue + } } keyOrder = append(keyOrder, fmt.Sprint(k)) } @@ -185,11 +188,13 @@ func main() { if coords, ok := v.(map[string]interface{})["coords"].(string); ok { fmt.Println("Coords:", coords) } - if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok { - fmt.Println("Public encryption key:", boxPubKey) - } - if boxSigKey, ok := v.(map[string]interface{})["box_sig_key"].(string); ok { - fmt.Println("Public signing key:", boxPubKey) + if *verbose { + if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok { + fmt.Println("Public encryption key:", boxPubKey) + } + if boxSigKey, ok := v.(map[string]interface{})["box_sig_key"].(string); ok { + fmt.Println("Public signing key:", boxSigKey) + } } } case "getswitchqueues": From 099fee9caefb6b7d5916d9919f9b843d55389be5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 26 Nov 2018 17:58:54 +0000 Subject: [PATCH 077/145] Rename destPubKey to box_pub_key in addRoute etc --- src/yggdrasil/admin.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index c253031a..c145a10b 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -243,15 +243,15 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") } }) - a.addHandler("addRoute", []string{"subnet", "destPubKey"}, func(in admin_info) (admin_info, error) { + a.addHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) { var err error a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["destPubKey"].(string)) + err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string)) }) if err == nil { - return admin_info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, nil + return admin_info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil } else { - return admin_info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, errors.New("Failed to add route") + return admin_info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") } }) a.addHandler("getSourceSubnets", []string{}, func(in admin_info) (admin_info, error) { @@ -291,15 +291,15 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") } }) - a.addHandler("removeRoute", []string{"subnet", "destPubKey"}, func(in admin_info) (admin_info, error) { + a.addHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) { var err error a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["destPubKey"].(string)) + err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)) }) if err == nil { - return admin_info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, nil + return admin_info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil } else { - return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, errors.New("Failed to remove route") + return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") } }) a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) { From 5912dcc72c114c13e6eaf5b885d1fb8b82f8b8f7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 26 Nov 2018 18:34:17 +0000 Subject: [PATCH 078/145] Fix typo --- yggdrasilctl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 3fa543b5..ac25efd5 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -23,7 +23,7 @@ func main() { args := flag.Args() if len(args) == 0 { - fmt.Println("usage:", os.Args[0], "[-endpoint=proto://server] [-verbose] [-json] command [key=value] [...]") + fmt.Println("usage:", os.Args[0], "[-endpoint=proto://server] [-v] [-json] command [key=value] [...]") fmt.Println("example:", os.Args[0], "getPeers") fmt.Println("example:", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") fmt.Println("example:", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") From b3e2b8e6a563a18e0317a2bbad06554b019d5f33 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 26 Nov 2018 19:15:27 -0600 Subject: [PATCH 079/145] Update admin.go Replace `nil` with `[]string{}` for `list`'s argument list. --- src/yggdrasil/admin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index c145a10b..86ca6572 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -52,7 +52,7 @@ func (a *admin) addHandler(name string, args []string, handler func(admin_info) func (a *admin) init(c *Core, listenaddr string) { a.core = c a.listenaddr = listenaddr - a.addHandler("list", nil, func(in admin_info) (admin_info, error) { + a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) { handlers := make(map[string]interface{}) for _, handler := range a.handlers { handlers[handler.name] = admin_info{"fields": handler.args} From 38093219fd10b0cfc17a129088b69e97cb904317 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 2 Dec 2018 14:46:58 -0600 Subject: [PATCH 080/145] dimensionless way to track how often nodes are faster than the current parent --- src/yggdrasil/switch.go | 87 +++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 28f83122..60ba02a8 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -21,7 +21,7 @@ import ( const switch_timeout = time.Minute const switch_updateInterval = switch_timeout / 2 const switch_throttle = switch_updateInterval / 2 -const switch_max_time = time.Hour +const switch_faster_threshold = 2880 // 1 update per 30 seconds for 24 hours // The switch locator represents the topology and network state dependent info about a node, minus the signatures that go with it. // Nodes will pick the best root they see, provided that the root continues to push out updates with new timestamps. @@ -119,13 +119,13 @@ func (x *switchLocator) isAncestorOf(y *switchLocator) bool { // Information about a peer, used by the switch to build the tree and eventually make routing decisions. type peerInfo struct { - key sigPubKey // ID of this peer - locator switchLocator // Should be able to respond with signatures upon request - degree uint64 // Self-reported degree - time time.Time // Time this node was last seen - firstSeen time.Time - port switchPort // Interface number of this peer - msg switchMsg // The wire switchMsg used + key sigPubKey // ID of this peer + locator switchLocator // Should be able to respond with signatures upon request + degree uint64 // Self-reported degree + time time.Time // Time this node was last seen + faster uint16 // Counter of how often a node is faster than the current parent, penalized extra if slower + port switchPort // Interface number of this peer + msg switchMsg // The wire switchMsg used } // This is just a uint64 with a named type for clarity reasons. @@ -252,7 +252,7 @@ func (t *switchTable) forgetPeer(port switchPort) { return } for _, info := range t.data.peers { - t.unlockedHandleMsg(&info.msg, info.port) + t.unlockedHandleMsg(&info.msg, info.port, true) } } @@ -326,7 +326,7 @@ func (t *switchTable) checkRoot(msg *switchMsg) bool { func (t *switchTable) handleMsg(msg *switchMsg, fromPort switchPort) { t.mutex.Lock() defer t.mutex.Unlock() - t.unlockedHandleMsg(msg, fromPort) + t.unlockedHandleMsg(msg, fromPort, false) } // This updates the switch with information about a peer. @@ -334,7 +334,8 @@ func (t *switchTable) handleMsg(msg *switchMsg, fromPort switchPort) { // That happens if this node is already our parent, or is advertising a better root, or is advertising a better path to the same root, etc... // There are a lot of very delicate order sensitive checks here, so its' best to just read the code if you need to understand what it's doing. // It's very important to not change the order of the statements in the case function unless you're absolutely sure that it's safe, including safe if used along side nodes that used the previous order. -func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort) { +// Set the third arg to true if you're reprocessing an old message, e.g. to find a new parent after one disconnects, to avoid updating some timing related things. +func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, reprocessing bool) { // TODO directly use a switchMsg instead of switchMessage + sigs now := time.Now() // Set up the sender peerInfo @@ -350,10 +351,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort) { } sender.msg = *msg oldSender, isIn := t.data.peers[fromPort] - if !isIn { - oldSender.firstSeen = now - } - sender.firstSeen = oldSender.firstSeen + sender.faster = oldSender.faster sender.port = fromPort sender.time = now // Decide what to do @@ -374,9 +372,36 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort) { doUpdate := false if !equiv(&sender.locator, &oldSender.locator) { doUpdate = true - sender.firstSeen = now } + // Check if faster than the current parent, and update sender.faster accordingly + switch { + case reprocessing: + // Don't change anything if we're just reprocessing old messages. + case !isIn: + // Not known, sender.faster == 0, but set it explicitly just to make that obvious to the reader. + sender.faster = 0 + case msg.Root != oldSender.locator.root: + // This is a new root. + // Honestly not sure if we should reset or do something else. For now, we'll just leave it alone. + case sender.port == t.parent: + // This is the current parent. If roots change, there's a good chance that they're still the best route to the root, so we probably don't want them to converge towards 0. + // If we leae them alone, then when a different node gets parented, this one will get penalized by a couple of points, so it hopefully shouldn't flap too hard to leave this alone for now. + case sender.locator.tstamp <= t.data.locator.tstamp: + // This timestamp came in slower than our parent's, so we should penalize them by more than we reward faster nodes. + if sender.faster > 1 { + sender.faster -= 2 + } else { + // If exactly 1, don't let it roll under + sender.faster = 0 + } + default: + // They sent us an update faster than our parent did, so reward them. + // FIXME make sure this can't ever roll over. It shouldn't be possible, we'd switch to them as a parent first, but still... + sender.faster++ + } + // Update sender t.data.peers[fromPort] = sender + // Decide if we should also update our root info to make the sender our parent updateRoot := false oldParent, isIn := t.data.peers[t.parent] noParent := !isIn @@ -391,20 +416,8 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort) { } return true }() - // Get the time we've known about the sender (or old parent's) current coords, up to a maximum of `switch_max_time`. - sTime := now.Sub(sender.firstSeen) - if sTime > switch_max_time { - sTime = switch_max_time - } - pTime := now.Sub(oldParent.firstSeen) - if pTime > switch_max_time { - pTime = switch_max_time - } - // Really want to compare sLen/sTime and pLen/pTime - // Cross multiplied to avoid divide-by-zero - cost := float64(len(sender.locator.coords)) * pTime.Seconds() - pCost := float64(len(t.data.locator.coords)) * sTime.Seconds() dropTstamp, isIn := t.drop[sender.locator.root] + // Decide if we need to update info about the root or change parents. switch { case !noLoop: // This route loops, so we can't use the sender as our parent. @@ -420,8 +433,16 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort) { case noParent: // We currently have no working parent, and at this point in the switch statement, anything is better than nothing. updateRoot = true - case cost < pCost: - // The sender has a better combination of path length and reliability than the current parent. + case sender.faster > switch_faster_threshold: + // The is reliably faster than the current parent. + updateRoot = true + case reprocessing && len(sender.locator.coords) < len(oldParent.locator.coords): + // We're reprocessing old messages to find a new parent. + // That means we're in the middle of a route flap. + // We don't know how often each node is faster than the others, only relative to the old parent. + // If any of them was faster than the old parent, then we'd probably already be using them. + // So the best we can really do is pick the shortest route and hope it's OK as a starting point. + // TODO: Find some way to reliably store relative order between all peers. Basically a pxp "faster" matrix, more likely a faster port->uint map per peer, but preferably not literally that, since it'd be tedious to manage and probably slows down updates. updateRoot = true case sender.port != t.parent: // Ignore further cases if the sender isn't our parent. @@ -432,9 +453,9 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort) { // Then reprocess *all* messages to look for a better parent. // This is so we don't keep using this node as our parent if there's something better. t.parent = 0 - t.unlockedHandleMsg(msg, fromPort) + t.unlockedHandleMsg(msg, fromPort, true) for _, info := range t.data.peers { - t.unlockedHandleMsg(&info.msg, info.port) + t.unlockedHandleMsg(&info.msg, info.port, true) } case now.Sub(t.time) < switch_throttle: // We've already gotten an update from this root recently, so ignore this one to avoid flooding. From dcfe55dae8682aeb59336c189a0d837dcc0ec67b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 2 Dec 2018 16:36:25 -0600 Subject: [PATCH 081/145] store 'faster' relationships between all pairs of peers, to make fallback easier when a parent goes offline --- src/yggdrasil/switch.go | 111 +++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 60ba02a8..9c1bfe90 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -18,10 +18,12 @@ import ( "time" ) -const switch_timeout = time.Minute -const switch_updateInterval = switch_timeout / 2 -const switch_throttle = switch_updateInterval / 2 -const switch_faster_threshold = 2880 // 1 update per 30 seconds for 24 hours +const ( + switch_timeout = time.Minute + switch_updateInterval = switch_timeout / 2 + switch_throttle = switch_updateInterval / 2 + switch_faster_threshold = 240 //Number of switch updates before switching to a faster parent +) // The switch locator represents the topology and network state dependent info about a node, minus the signatures that go with it. // Nodes will pick the best root they see, provided that the root continues to push out updates with new timestamps. @@ -119,13 +121,13 @@ func (x *switchLocator) isAncestorOf(y *switchLocator) bool { // Information about a peer, used by the switch to build the tree and eventually make routing decisions. type peerInfo struct { - key sigPubKey // ID of this peer - locator switchLocator // Should be able to respond with signatures upon request - degree uint64 // Self-reported degree - time time.Time // Time this node was last seen - faster uint16 // Counter of how often a node is faster than the current parent, penalized extra if slower - port switchPort // Interface number of this peer - msg switchMsg // The wire switchMsg used + key sigPubKey // ID of this peer + locator switchLocator // Should be able to respond with signatures upon request + degree uint64 // Self-reported degree + time time.Time // Time this node was last seen + faster map[switchPort]uint64 // Counter of how often a node is faster than the current parent, penalized extra if slower + port switchPort // Interface number of this peer + msg switchMsg // The wire switchMsg used } // This is just a uint64 with a named type for clarity reasons. @@ -350,8 +352,6 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep prevKey = hop.Next } sender.msg = *msg - oldSender, isIn := t.data.peers[fromPort] - sender.faster = oldSender.faster sender.port = fromPort sender.time = now // Decide what to do @@ -370,34 +370,39 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep return true } doUpdate := false + oldSender := t.data.peers[fromPort] if !equiv(&sender.locator, &oldSender.locator) { doUpdate = true } - // Check if faster than the current parent, and update sender.faster accordingly - switch { - case reprocessing: - // Don't change anything if we're just reprocessing old messages. - case !isIn: - // Not known, sender.faster == 0, but set it explicitly just to make that obvious to the reader. - sender.faster = 0 - case msg.Root != oldSender.locator.root: - // This is a new root. - // Honestly not sure if we should reset or do something else. For now, we'll just leave it alone. - case sender.port == t.parent: - // This is the current parent. If roots change, there's a good chance that they're still the best route to the root, so we probably don't want them to converge towards 0. - // If we leae them alone, then when a different node gets parented, this one will get penalized by a couple of points, so it hopefully shouldn't flap too hard to leave this alone for now. - case sender.locator.tstamp <= t.data.locator.tstamp: - // This timestamp came in slower than our parent's, so we should penalize them by more than we reward faster nodes. - if sender.faster > 1 { - sender.faster -= 2 - } else { - // If exactly 1, don't let it roll under - sender.faster = 0 + // Update the matrix of peer "faster" thresholds + if reprocessing { + sender.faster = oldSender.faster + } else { + sender.faster = make(map[switchPort]uint64, len(oldSender.faster)) + for port, peer := range t.data.peers { + if port == fromPort { + continue + } + switch { + case msg.Root != peer.locator.root: + // Different roots, blindly guess that the relationships will stay the same? + sender.faster[port] = oldSender.faster[peer.port] + case sender.locator.tstamp <= peer.locator.tstamp: + // Slower than this node, penalize (more than the reward amount) + if oldSender.faster[port] > 1 { + sender.faster[port] = oldSender.faster[peer.port] - 2 + } else { + sender.faster[port] = 0 + } + default: + // We were faster than this node, so increment, as long as we don't overflow because of it + if oldSender.faster[peer.port] < switch_faster_threshold { + sender.faster[port] = oldSender.faster[peer.port] + 1 + } else { + sender.faster[port] = switch_faster_threshold + } + } } - default: - // They sent us an update faster than our parent did, so reward them. - // FIXME make sure this can't ever roll over. It shouldn't be possible, we'd switch to them as a parent first, but still... - sender.faster++ } // Update sender t.data.peers[fromPort] = sender @@ -433,30 +438,30 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep case noParent: // We currently have no working parent, and at this point in the switch statement, anything is better than nothing. updateRoot = true - case sender.faster > switch_faster_threshold: + case sender.faster[t.parent] >= switch_faster_threshold: // The is reliably faster than the current parent. updateRoot = true - case reprocessing && len(sender.locator.coords) < len(oldParent.locator.coords): - // We're reprocessing old messages to find a new parent. - // That means we're in the middle of a route flap. - // We don't know how often each node is faster than the others, only relative to the old parent. - // If any of them was faster than the old parent, then we'd probably already be using them. - // So the best we can really do is pick the shortest route and hope it's OK as a starting point. - // TODO: Find some way to reliably store relative order between all peers. Basically a pxp "faster" matrix, more likely a faster port->uint map per peer, but preferably not literally that, since it'd be tedious to manage and probably slows down updates. + case reprocessing && sender.faster[t.parent] > oldParent.faster[sender.port]: + // The sender seems to be reliably faster than the current parent, so switch to them instead. updateRoot = true case sender.port != t.parent: // Ignore further cases if the sender isn't our parent. - case !equiv(&sender.locator, &t.data.locator): + case !reprocessing && !equiv(&sender.locator, &t.data.locator): // Special case: - // If coords changed, then this may now be a worse parent than before. - // Re-parent the node (de-parent and reprocess the message). - // Then reprocess *all* messages to look for a better parent. - // This is so we don't keep using this node as our parent if there's something better. + // If coords changed, then we need to penalize this node somehow, to prevent flapping. + // First, reset all faster-related info to 0. + // Then, de-parent the node and reprocess all messages to find a new parent. t.parent = 0 - t.unlockedHandleMsg(msg, fromPort, true) - for _, info := range t.data.peers { - t.unlockedHandleMsg(&info.msg, info.port, true) + sender.faster = nil + for _, peer := range t.data.peers { + if peer.port == sender.port { + continue + } + delete(peer.faster, sender.port) + t.unlockedHandleMsg(&peer.msg, peer.port, true) } + // Process the sender last, to avoid keeping them as a parent if at all possible. + t.unlockedHandleMsg(&sender.msg, sender.port, true) case now.Sub(t.time) < switch_throttle: // We've already gotten an update from this root recently, so ignore this one to avoid flooding. case sender.locator.tstamp > t.data.locator.tstamp: From 86da073226b27c976f99d22c5b32ba8ed673b9ef Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Dec 2018 22:49:27 +0000 Subject: [PATCH 082/145] Add SwitchOptions and MaxTotalQueueSize --- src/yggdrasil/admin.go | 2 +- src/yggdrasil/config/config.go | 6 +++++ src/yggdrasil/core.go | 4 ++++ src/yggdrasil/switch.go | 41 +++++++++++++++++----------------- yggdrasil.go | 1 + yggdrasilctl.go | 2 +- 6 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 86ca6572..7784ea46 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -638,7 +638,7 @@ func (a *admin) getData_getSwitchQueues() admin_nodeInfo { {"queues_size", switchTable.queues.size}, {"highest_queues_count", switchTable.queues.maxbufs}, {"highest_queues_size", switchTable.queues.maxsize}, - {"maximum_queues_size", switch_buffer_maxSize}, + {"maximum_queues_size", switchTable.queuetotalmaxsize}, } } a.core.switchTable.doAdmin(getSwitchQueues) diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index a14ece9d..855eb92f 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -18,6 +18,7 @@ type NodeConfig struct { 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."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } @@ -45,3 +46,8 @@ type TunnelRouting struct { IPv4Destinations map[string]string `comment:"IPv4 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"` IPv4Sources []string `comment:"IPv4 source subnets which are allowed to be tunnelled. Unlike for\nIPv6, this option is required for bridging IPv4 traffic. Only\ntraffic with a source matching these subnets will be tunnelled."` } + +// SwitchOptions contains tuning options for the switch +type SwitchOptions struct { + MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined."` +} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 9b9bcc15..67a50c9f 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -105,6 +105,10 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } + c.switchTable.doAdmin(func() { + c.switchTable.queuetotalmaxsize = nc.SwitchOptions.MaxTotalQueueSize + }) + c.sessions.setSessionFirewallState(nc.SessionFirewall.Enable) c.sessions.setSessionFirewallDefaults( nc.SessionFirewall.AllowFromDirect, diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 28f83122..5dede5be 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -156,19 +156,20 @@ type switchData struct { // All the information stored by the switch. type switchTable struct { - core *Core - key sigPubKey // Our own key - time time.Time // Time when locator.tstamp was last updated - drop map[sigPubKey]int64 // Tstamp associated with a dropped root - mutex sync.RWMutex // Lock for reads/writes of switchData - parent switchPort // Port of whatever peer is our parent, or self if we're root - data switchData // - updater atomic.Value // *sync.Once - table atomic.Value // lookupTable - packetIn chan []byte // Incoming packets for the worker to handle - idleIn chan switchPort // Incoming idle notifications from peer links - admin chan func() // Pass a lambda for the admin socket to query stuff - queues switch_buffers // Queues - not atomic so ONLY use through admin chan + core *Core + key sigPubKey // Our own key + time time.Time // Time when locator.tstamp was last updated + drop map[sigPubKey]int64 // Tstamp associated with a dropped root + mutex sync.RWMutex // Lock for reads/writes of switchData + parent switchPort // Port of whatever peer is our parent, or self if we're root + data switchData // + updater atomic.Value // *sync.Once + table atomic.Value // lookupTable + packetIn chan []byte // Incoming packets for the worker to handle + idleIn chan switchPort // Incoming idle notifications from peer links + admin chan func() // Pass a lambda for the admin socket to query stuff + queues switch_buffers // Queues - not atomic so ONLY use through admin chan + queuetotalmaxsize uint64 // Maximum combined size of queues } // Initializes the switchTable struct. @@ -620,8 +621,6 @@ type switch_packetInfo struct { time time.Time // Timestamp of when the packet arrived } -const switch_buffer_maxSize = 4 * 1048576 // Maximum 4 MB - // Used to keep track of buffered packets type switch_buffer struct { packets []switch_packetInfo // Currently buffered packets, which may be dropped if it grows too large @@ -629,10 +628,11 @@ type switch_buffer struct { } type switch_buffers struct { - bufs map[string]switch_buffer // Buffers indexed by StreamID - size uint64 // Total size of all buffers, in bytes - maxbufs int - maxsize uint64 + switchTable *switchTable + bufs map[string]switch_buffer // Buffers indexed by StreamID + size uint64 // Total size of all buffers, in bytes + maxbufs int + maxsize uint64 } func (b *switch_buffers) cleanup(t *switchTable) { @@ -649,7 +649,7 @@ func (b *switch_buffers) cleanup(t *switchTable) { } } - for b.size > switch_buffer_maxSize { + for b.size > b.switchTable.queuetotalmaxsize { // Drop a random queue target := rand.Uint64() % b.size var size uint64 // running total @@ -719,6 +719,7 @@ func (t *switchTable) handleIdle(port switchPort) bool { // The switch worker does routing lookups and sends packets to where they need to be func (t *switchTable) doWorker() { + t.queues.switchTable = t t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string) idle := make(map[switchPort]struct{}) // this is to deduplicate things for { diff --git a/yggdrasil.go b/yggdrasil.go index 447bb3ec..c3df8052 100644 --- a/yggdrasil.go +++ b/yggdrasil.go @@ -69,6 +69,7 @@ func generateConfig(isAutoconf bool) *nodeConfig { cfg.SessionFirewall.Enable = false cfg.SessionFirewall.AllowFromDirect = true cfg.SessionFirewall.AllowFromRemote = true + cfg.SwitchOptions.MaxTotalQueueSize = 4 * 1048576 return &cfg } diff --git a/yggdrasilctl.go b/yggdrasilctl.go index ac25efd5..79b5f86d 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -216,8 +216,8 @@ func main() { fmt.Printf("Highest queue size: %d bytes\n", uint(highestqueuesize)) } if m, ok := v["maximum_queues_size"].(float64); ok { - fmt.Printf("Maximum queue size: %d bytes\n", uint(maximumqueuesize)) maximumqueuesize = m + fmt.Printf("Maximum queue size: %d bytes\n", uint(maximumqueuesize)) } if queues, ok := v["queues"].([]interface{}); ok { if len(queues) != 0 { From 319457ae2747bf926577c242d4c5f338f25accd2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Dec 2018 23:03:10 +0000 Subject: [PATCH 083/145] Update comment for MaxTotalQueueSize --- src/yggdrasil/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 855eb92f..904907b2 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -49,5 +49,5 @@ type TunnelRouting struct { // SwitchOptions contains tuning options for the switch type SwitchOptions struct { - MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined."` + MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."` } From b5f4637b5cceac4630fda0ad4df53be2059c6253 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Dec 2018 23:20:11 +0000 Subject: [PATCH 084/145] Enforce min 4MB switch queue total size --- src/yggdrasil/admin.go | 2 +- src/yggdrasil/core.go | 8 +++++--- src/yggdrasil/switch.go | 8 ++++++-- yggdrasil.go | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 7784ea46..2b5bc645 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -638,7 +638,7 @@ func (a *admin) getData_getSwitchQueues() admin_nodeInfo { {"queues_size", switchTable.queues.size}, {"highest_queues_count", switchTable.queues.maxbufs}, {"highest_queues_size", switchTable.queues.maxsize}, - {"maximum_queues_size", switchTable.queuetotalmaxsize}, + {"maximum_queues_size", switchTable.queueTotalMaxSize}, } } a.core.switchTable.doAdmin(getSwitchQueues) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 67a50c9f..224945e0 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -105,9 +105,11 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } - c.switchTable.doAdmin(func() { - c.switchTable.queuetotalmaxsize = nc.SwitchOptions.MaxTotalQueueSize - }) + if nc.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { + c.switchTable.doAdmin(func() { + c.switchTable.queueTotalMaxSize = nc.SwitchOptions.MaxTotalQueueSize + }) + } c.sessions.setSessionFirewallState(nc.SessionFirewall.Enable) c.sessions.setSessionFirewallDefaults( diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 5dede5be..adb624a0 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -169,9 +169,12 @@ type switchTable struct { idleIn chan switchPort // Incoming idle notifications from peer links admin chan func() // Pass a lambda for the admin socket to query stuff queues switch_buffers // Queues - not atomic so ONLY use through admin chan - queuetotalmaxsize uint64 // Maximum combined size of queues + queueTotalMaxSize uint64 // Maximum combined size of queues } +// Minimum allowed total size of switch queues. +const SwitchQueueTotalMinSize = 4 * 1024 * 1024 + // Initializes the switchTable struct. func (t *switchTable) init(core *Core, key sigPubKey) { now := time.Now() @@ -186,6 +189,7 @@ func (t *switchTable) init(core *Core, key sigPubKey) { t.packetIn = make(chan []byte, 1024) t.idleIn = make(chan switchPort, 1024) t.admin = make(chan func()) + t.queueTotalMaxSize = SwitchQueueTotalMinSize } // Safely gets a copy of this node's locator. @@ -649,7 +653,7 @@ func (b *switch_buffers) cleanup(t *switchTable) { } } - for b.size > b.switchTable.queuetotalmaxsize { + for b.size > b.switchTable.queueTotalMaxSize { // Drop a random queue target := rand.Uint64() % b.size var size uint64 // running total diff --git a/yggdrasil.go b/yggdrasil.go index c3df8052..8536fa13 100644 --- a/yggdrasil.go +++ b/yggdrasil.go @@ -69,7 +69,7 @@ func generateConfig(isAutoconf bool) *nodeConfig { cfg.SessionFirewall.Enable = false cfg.SessionFirewall.AllowFromDirect = true cfg.SessionFirewall.AllowFromRemote = true - cfg.SwitchOptions.MaxTotalQueueSize = 4 * 1048576 + cfg.SwitchOptions.MaxTotalQueueSize = yggdrasil.SwitchQueueTotalMinSize return &cfg } From 5a89a869be1f94a16c5dd558d5150d1f8af1ff2d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Dec 2018 23:24:54 +0000 Subject: [PATCH 085/145] Set queueTotalMaxSize before switch worker starts --- src/yggdrasil/core.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 224945e0..706b8aa3 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -100,17 +100,15 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } + if nc.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { + c.switchTable.queueTotalMaxSize = nc.SwitchOptions.MaxTotalQueueSize + } + if err := c.switchTable.start(); err != nil { c.log.Println("Failed to start switch") return err } - if nc.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize { - c.switchTable.doAdmin(func() { - c.switchTable.queueTotalMaxSize = nc.SwitchOptions.MaxTotalQueueSize - }) - } - c.sessions.setSessionFirewallState(nc.SessionFirewall.Enable) c.sessions.setSessionFirewallDefaults( nc.SessionFirewall.AllowFromDirect, From ad30e3688177deec08e2e3cfb40f59670dee8cb8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Dec 2018 23:49:48 +0000 Subject: [PATCH 086/145] Add -json flag for -genconf and -normaliseconf --- yggdrasil.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/yggdrasil.go b/yggdrasil.go index 8536fa13..0a82437f 100644 --- a/yggdrasil.go +++ b/yggdrasil.go @@ -76,9 +76,15 @@ func generateConfig(isAutoconf bool) *nodeConfig { // Generates a new configuration and returns it in HJSON format. This is used // with -genconf. -func doGenconf() string { +func doGenconf(isjson bool) string { cfg := generateConfig(false) - bs, err := hjson.Marshal(cfg) + var bs []byte + var err error + if isjson { + bs, err = json.MarshalIndent(cfg, "", " ") + } else { + bs, err = hjson.Marshal(cfg) + } if err != nil { panic(err) } @@ -92,6 +98,7 @@ func main() { useconf := flag.Bool("useconf", false, "read config from stdin") useconffile := flag.String("useconffile", "", "read config from specified file path") normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") + confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") flag.Parse() @@ -186,7 +193,12 @@ func main() { // their configuration file with newly mapped names (like above) or to // convert from plain JSON to commented HJSON. if *normaliseconf { - bs, err := hjson.Marshal(cfg) + var bs []byte + if *confjson { + bs, err = json.MarshalIndent(cfg, "", " ") + } else { + bs, err = hjson.Marshal(cfg) + } if err != nil { panic(err) } @@ -195,7 +207,7 @@ func main() { } case *genconf: // Generate a new configuration and print it to stdout. - fmt.Println(doGenconf()) + fmt.Println(doGenconf(*confjson)) default: // No flags were provided, therefore print the list of flags to stdout. flag.PrintDefaults() From 150cf810ddfbe5925eb13e1a4d55f486340c5810 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 2 Dec 2018 23:52:57 +0000 Subject: [PATCH 087/145] Update comments for -useconf and -useconffile --- yggdrasil.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yggdrasil.go b/yggdrasil.go index 0a82437f..5244a8ec 100644 --- a/yggdrasil.go +++ b/yggdrasil.go @@ -95,8 +95,8 @@ func doGenconf(isjson bool) string { func main() { // Configure the command line parameters. genconf := flag.Bool("genconf", false, "print a new config to stdout") - useconf := flag.Bool("useconf", false, "read config from stdin") - useconffile := flag.String("useconffile", "", "read config from specified file path") + useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin") + useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path") normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") From 8b7b3452cf6d6e480d87896de5520986fde8d272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christer=20War=C3=A9n?= Date: Mon, 3 Dec 2018 05:57:00 +0200 Subject: [PATCH 088/145] Creating Dockerfile to /contrib/docker/ - Multiple architectures supported by using Golang's official Debian Stretch image. - Upgrading os to latest updates - Adding all files to image - Creating user for yggdrasil (kinda unused) - Building from source code --- contrib/docker/Dockerfile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 contrib/docker/Dockerfile diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile new file mode 100644 index 00000000..a5174eb4 --- /dev/null +++ b/contrib/docker/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:stretch +MAINTAINER Christer Waren/CWINFO "christer.waren@cwinfo.org" + +RUN apt-get update \ + && apt-get upgrade -y + +ADD . /src + +WORKDIR /src + +RUN adduser --system --home /etc/yggdrasil-network --uid 1000 yggdrasil-network \ + && rm -rf build_* && ./build \ + && cp yggdrasil /usr/bin \ + && cp contrib/docker/entrypoint.sh / + +VOLUME [ "/etc/yggdrasil-network" ] + +ENTRYPOINT [ "/entrypoint.sh" ] From 80b876d21d1b8077e1fce2b23fd1eed8d587e6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christer=20War=C3=A9n?= Date: Mon, 3 Dec 2018 05:58:24 +0200 Subject: [PATCH 089/145] Creating entrypoint.sh to /contrib/docker/ --- contrib/docker/entrypoint.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 contrib/docker/entrypoint.sh diff --git a/contrib/docker/entrypoint.sh b/contrib/docker/entrypoint.sh new file mode 100644 index 00000000..f1a9d3e5 --- /dev/null +++ b/contrib/docker/entrypoint.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +CONF_DIR="/etc/yggdrasil-network" + +if [ ! -f "$CONF_DIR/config.conf" ]; then + echo "generate $CONF_DIR/config.conf" + yggdrasil --genconf > "$CONF_DIR/config.conf" +fi + +yggdrasil --useconf < "$CONF_DIR/config.conf" +exit $? From 4fc0117e086750cf82ffb8c939e4e953b31e8daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christer=20War=C3=A9n?= Date: Mon, 3 Dec 2018 06:06:58 +0200 Subject: [PATCH 090/145] Creating Dockerfile to / Hint for support of docker, same in cjdns repository --- Dockerfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4cac86d9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +contrib/docker/Dockerfile From ecc0cd499202c9254dd92d3d39117ad556a9e6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christer=20War=C3=A9n?= Date: Mon, 3 Dec 2018 06:39:28 +0200 Subject: [PATCH 091/145] Update and rename LICENSE to LICENSE.md --- LICENSE => LICENSE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename LICENSE => LICENSE.md (99%) diff --git a/LICENSE b/LICENSE.md similarity index 99% rename from LICENSE rename to LICENSE.md index 53320c35..1182c200 100644 --- a/LICENSE +++ b/LICENSE.md @@ -17,11 +17,10 @@ statement from your version. This exception does not (and cannot) modify any license terms which apply to the Application, with which you must still comply. - GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. From 6170f7268f2c5a4c009144b6ad5d0fb84baad0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christer=20War=C3=A9n?= Date: Mon, 3 Dec 2018 06:46:05 +0200 Subject: [PATCH 092/145] Rename LICENSE.md to LICENSE --- LICENSE.md => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE.md => LICENSE (100%) diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE From a7f5c427d467f9f5db984a3eba6c1a0e7ccd26a7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 3 Dec 2018 17:44:26 +0000 Subject: [PATCH 093/145] Tag releases in master using CI (also checks for v0.x.0 instead of v0.x when deciding version numbers) --- .circleci/config.yml | 12 ++++++++++++ contrib/.DS_Store | Bin 0 -> 6148 bytes contrib/semver/version.sh | 11 +++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 contrib/.DS_Store diff --git a/.circleci/config.yml b/.circleci/config.yml index fa5ebcac..90bba23b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,6 +18,8 @@ jobs: mkdir /tmp/upload echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV echo 'export CIVERSION=$(sh contrib/semver/version.sh | cut -c 2-)' >> $BASH_ENV + git config --global user.email "$(git log --format='%ae' HEAD -1)"; + git config --global user.name "$(git log --format='%an' HEAD -1)"; - run: name: Install alien @@ -98,3 +100,13 @@ jobs: - store_artifacts: path: /tmp/upload destination: / + + - run: + name: Create tags (master branch only) + command: > + if [ "${CIRCLE_BRANCH}" == "master" ]; then + git tag -f -a $(sh contrib/semver/version.sh) -m "Created by CircleCI" && git push -f --tags; + else + echo "Only runs for master branch (this is ${CIRCLE_BRANCH})"; + fi; + when: on_success diff --git a/contrib/.DS_Store b/contrib/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..799616ae9f2aed17d20533c4052922a02fadaab9 GIT binary patch literal 6148 zcmeHKPfx-y6mJ2gV2m70)LSp!I50Kf#blXy@Mbcj2Q{!|C`)iNY)BL_`yBd>`~rR* z-)jpI(TfLT$a~A{@Ads@)8CeMWsGrW5Lk>=7-IqyF;{}-8^Jj0oaBrLk*hI$maxDd zCU<^sE}Iv_;EG-v>D2wuH`O59aLpSx( zLE?D*3+$bEY1cRQX&m>aw&$OPonfo8ei%oo7e<}FDh`7Vq+FhdQ4kNEcn}4N8pqWE zAxfgus%(u$)=sTnm6o+XuF6rPQLDd;Dx5!bYMy5Y@J&ip0yIR4T^$! nnZ{`e80sj7Ts(?5L6v}Cq5)_d%rt@rgnk4R4b%_=f6BlIbwN_$ literal 0 HcmV?d00001 diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index bd009a9a..215753bb 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -4,7 +4,7 @@ DEVELOPBRANCH="yggdrasil-network/develop" # Get the last tag -TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*" 2>/dev/null) +TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.0" 2>/dev/null) # Get last merge to master MERGE=$(git rev-list $TAG..master --grep "from $DEVELOPBRANCH" 2>/dev/null | head -n 1) @@ -31,8 +31,12 @@ MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) BRANCH=$(git rev-parse --abbrev-ref HEAD) # Output in the desired format -if [ $PATCH = 0 ]; then - printf 'v%d.%d' "$MAJOR" "$MINOR" +if [ $PATCH == 0 ]; then + if [ ! -z $FULL ]; then + printf 'v%d.%d.0' "$MAJOR" "$MINOR" + else + printf 'v%d.%d' "$MAJOR" "$MINOR" + fi else printf 'v%d.%d.%d' "$MAJOR" "$MINOR" "$PATCH" fi @@ -43,4 +47,3 @@ if [ $BRANCH != "master" ]; then printf -- "-%04d" "$BUILD" fi fi - From 8a04cbe3c8e9f9c05d35d728002addde5710c15d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 3 Dec 2018 17:49:03 +0000 Subject: [PATCH 094/145] Try to fix CircleCI shell error --- contrib/semver/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 215753bb..6eeffc5f 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -31,7 +31,7 @@ MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) BRANCH=$(git rev-parse --abbrev-ref HEAD) # Output in the desired format -if [ $PATCH == 0 ]; then +if [ $PATCH = 0 ]; then if [ ! -z $FULL ]; then printf 'v%d.%d.0' "$MAJOR" "$MINOR" else From 3d4b49b6933a539052dc72131ef497a8fe2d7d48 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 3 Dec 2018 19:21:23 -0600 Subject: [PATCH 095/145] reset the switch speed info for a peer whenever it changes coords, instead of only if they're a parent and change coords. Also, make sure packets in the sim preserve order when sending, to avoid races when testing --- src/yggdrasil/debug.go | 54 ++++++++++++++++++++++++++--------------- src/yggdrasil/switch.go | 28 ++++++++++----------- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 91f57708..7da84749 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -504,27 +504,43 @@ func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) { func DEBUG_simLinkPeers(p, q *peer) { // Sets q.out() to point to p and starts p.linkLoop() - p.linkOut, q.linkOut = make(chan []byte, 1), make(chan []byte, 1) - go func() { - for bs := range p.linkOut { - q.handlePacket(bs) + goWorkers := func(source, dest *peer) { + source.linkOut = make(chan []byte, 1) + send := make(chan []byte, 1) + source.out = func(bs []byte) { + send <- bs } - }() - go func() { - for bs := range q.linkOut { - p.handlePacket(bs) - } - }() - p.out = func(bs []byte) { - p.core.switchTable.idleIn <- p.port - go q.handlePacket(bs) + go source.linkLoop() + go func() { + var packets [][]byte + for { + select { + case packet := <-source.linkOut: + packets = append(packets, packet) + continue + case packet := <-send: + packets = append(packets, packet) + source.core.switchTable.idleIn <- source.port + continue + default: + } + if len(packets) > 0 { + dest.handlePacket(packets[0]) + packets = packets[1:] + continue + } + select { + case packet := <-source.linkOut: + packets = append(packets, packet) + case packet := <-send: + packets = append(packets, packet) + source.core.switchTable.idleIn <- source.port + } + } + }() } - q.out = func(bs []byte) { - q.core.switchTable.idleIn <- q.port - go p.handlePacket(bs) - } - go p.linkLoop() - go q.linkLoop() + goWorkers(p, q) + goWorkers(q, p) p.core.switchTable.idleIn <- p.port q.core.switchTable.idleIn <- q.port } diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index b04578ca..99661031 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -377,6 +377,11 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep doUpdate := false oldSender := t.data.peers[fromPort] if !equiv(&sender.locator, &oldSender.locator) { + // Reset faster info, we'll start refilling it right after this + sender.faster = nil + for _, peer := range t.data.peers { + delete(peer.faster, sender.port) + } doUpdate = true } // Update the matrix of peer "faster" thresholds @@ -387,25 +392,20 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep for port, peer := range t.data.peers { if port == fromPort { continue - } - switch { - case msg.Root != peer.locator.root: - // Different roots, blindly guess that the relationships will stay the same? - sender.faster[port] = oldSender.faster[peer.port] - case sender.locator.tstamp <= peer.locator.tstamp: - // Slower than this node, penalize (more than the reward amount) - if oldSender.faster[port] > 1 { - sender.faster[port] = oldSender.faster[peer.port] - 2 - } else { - sender.faster[port] = 0 - } - default: + } else if sender.locator.root != peer.locator.root || sender.locator.tstamp > peer.locator.tstamp { // We were faster than this node, so increment, as long as we don't overflow because of it if oldSender.faster[peer.port] < switch_faster_threshold { sender.faster[port] = oldSender.faster[peer.port] + 1 } else { sender.faster[port] = switch_faster_threshold } + } else { + // Slower than this node, penalize (more than the reward amount) + if oldSender.faster[port] > 1 { + sender.faster[port] = oldSender.faster[peer.port] - 2 + } else { + sender.faster[port] = 0 + } } } } @@ -457,12 +457,10 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep // First, reset all faster-related info to 0. // Then, de-parent the node and reprocess all messages to find a new parent. t.parent = 0 - sender.faster = nil for _, peer := range t.data.peers { if peer.port == sender.port { continue } - delete(peer.faster, sender.port) t.unlockedHandleMsg(&peer.msg, peer.port, true) } // Process the sender last, to avoid keeping them as a parent if at all possible. From 9f4fc3669ba56140f158f404247ea7e2721b30f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Tue, 4 Dec 2018 13:00:01 +0100 Subject: [PATCH 096/145] Reduce container image size --- contrib/docker/Dockerfile | 28 ++++++++++++++++------------ contrib/docker/entrypoint.sh | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) mode change 100644 => 100755 contrib/docker/entrypoint.sh diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index a5174eb4..6b4bfcb6 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,18 +1,22 @@ -FROM golang:stretch -MAINTAINER Christer Waren/CWINFO "christer.waren@cwinfo.org" - -RUN apt-get update \ - && apt-get upgrade -y - -ADD . /src +FROM docker.io/golang:alpine as builder +COPY . /src WORKDIR /src +RUN apk add git && ./build -RUN adduser --system --home /etc/yggdrasil-network --uid 1000 yggdrasil-network \ - && rm -rf build_* && ./build \ - && cp yggdrasil /usr/bin \ - && cp contrib/docker/entrypoint.sh / +FROM docker.io/alpine +LABEL maintainer="Christer Waren/CWINFO " + +COPY --from=builder /src/yggdrasil /usr/bin/yggdrasil +COPY --from=builder /src/yggdrasilctl /usr/bin/yggdrasilctl +COPY contrib/docker/entrypoint.sh /usr/bin/entrypoint.sh + +# RUN addgroup -g 1000 -S yggdrasil-network \ +# && adduser -u 1000 -S -g 1000 --home /etc/yggdrasil-network yggdrasil-network +# +# USER yggdrasil-network +# TODO: Make running unprivileged work VOLUME [ "/etc/yggdrasil-network" ] -ENTRYPOINT [ "/entrypoint.sh" ] +ENTRYPOINT [ "/usr/bin/entrypoint.sh" ] diff --git a/contrib/docker/entrypoint.sh b/contrib/docker/entrypoint.sh old mode 100644 new mode 100755 index f1a9d3e5..26c685a8 --- a/contrib/docker/entrypoint.sh +++ b/contrib/docker/entrypoint.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh set -e From eae8f9a666ce0b8e59deb1b5fa335e892d136df0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 5 Dec 2018 22:39:04 +0000 Subject: [PATCH 097/145] Try to SO_REUSEPORT on UNIX platforms --- src/yggdrasil/multicast.go | 6 +++++- src/yggdrasil/multicast_other.go | 9 +++++++++ src/yggdrasil/multicast_unix.go | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/yggdrasil/multicast_other.go create mode 100644 src/yggdrasil/multicast_unix.go diff --git a/src/yggdrasil/multicast.go b/src/yggdrasil/multicast.go index 697744cb..749dfcdb 100644 --- a/src/yggdrasil/multicast.go +++ b/src/yggdrasil/multicast.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "context" "fmt" "net" "time" @@ -35,7 +36,10 @@ func (m *multicast) start() error { return err } listenString := fmt.Sprintf("[::]:%v", addr.Port) - conn, err := net.ListenPacket("udp6", listenString) + lc := net.ListenConfig{ + Control: multicastReuse, + } + conn, err := lc.ListenPacket(context.Background(), "udp6", listenString) if err != nil { return err } diff --git a/src/yggdrasil/multicast_other.go b/src/yggdrasil/multicast_other.go new file mode 100644 index 00000000..98fe2df1 --- /dev/null +++ b/src/yggdrasil/multicast_other.go @@ -0,0 +1,9 @@ +// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd + +package yggdrasil + +import "syscall" + +func multicastReuse(network string, address string, c syscall.RawConn) error { + return nil +} diff --git a/src/yggdrasil/multicast_unix.go b/src/yggdrasil/multicast_unix.go new file mode 100644 index 00000000..9c6d1f11 --- /dev/null +++ b/src/yggdrasil/multicast_unix.go @@ -0,0 +1,22 @@ +// +build linux darwin netbsd freebsd openbsd dragonflybsd + +package yggdrasil + +import "syscall" +import "golang.org/x/sys/unix" + +func multicastReuse(network string, address string, c syscall.RawConn) error { + var control error + var reuseport error + + control = c.Control(func(fd uintptr) { + reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }) + + switch { + case reuseport != nil: + return reuseport + default: + return control + } +} From ae48a1721e665c7cc4213cca7d93748301bd702f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 5 Dec 2018 23:10:50 +0000 Subject: [PATCH 098/145] Try to SO_REUSEADDR on Windows --- src/yggdrasil/multicast_other.go | 2 +- src/yggdrasil/multicast_windows.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/yggdrasil/multicast_windows.go diff --git a/src/yggdrasil/multicast_other.go b/src/yggdrasil/multicast_other.go index 98fe2df1..8a4ce56c 100644 --- a/src/yggdrasil/multicast_other.go +++ b/src/yggdrasil/multicast_other.go @@ -1,4 +1,4 @@ -// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd +// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows package yggdrasil diff --git a/src/yggdrasil/multicast_windows.go b/src/yggdrasil/multicast_windows.go new file mode 100644 index 00000000..13f20315 --- /dev/null +++ b/src/yggdrasil/multicast_windows.go @@ -0,0 +1,22 @@ +// +build windows + +package yggdrasil + +import "syscall" +import "golang.org/x/sys/windows" + +func multicastReuse(network string, address string, c syscall.RawConn) error { + var control error + var reuseaddr error + + control = c.Control(func(fd uintptr) { + reuseaddr = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) + }) + + switch { + case reuseaddr != nil: + return reuseaddr + default: + return control + } +} From 2eedcce3fd37852b2b06562cf3a02b906d566453 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 5 Dec 2018 23:39:28 +0000 Subject: [PATCH 099/145] Update changelog --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c72c27a..c779c375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.x.x] - TBD +### Added +- Crypto-key routing support for both IPv4 and IPv6 +- Add `SwitchOptions` in configuration file for tuning the switch +- Add `dhtPing` to the admin socket to aid in crawling the network +- New macOS .pkgs built automatically by CircleCI +- Add Docker support +- Add `-json` command line flag for generating and normalising configuration in plain JSON + +### Changed +- Switched to Chord DHT (instead of Kademlia, although protocol-compatible) +- Admin socket clean-up (making some names consistent) +- Latency-based parent selection for the switch instead of uptime-based +- Real peering endpoints now shown in the admin socket `getPeers` call + +### Fixed +- Memory leaks in the DHT fixed +- Crash where ICMPv6 NDP goroutine would incorrectly start in TUN mode fixed + ## [0.2.7] - 2018-10-13 ### Added - Session firewall, which makes it possible to control who can open sessions with your node From fe772dd38e161609ee1d907ab0ef17c24880cd52 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 5 Dec 2018 18:22:39 -0600 Subject: [PATCH 100/145] switch bugfixes --- CHANGELOG.md | 1 + src/yggdrasil/switch.go | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c779c375..b91caca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - Memory leaks in the DHT fixed - Crash where ICMPv6 NDP goroutine would incorrectly start in TUN mode fixed +- Remove peers from the switch table of they stop sending switch messages but keep the TCP connection alive ## [0.2.7] - 2018-10-13 ### Added diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 99661031..920926b4 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -208,6 +208,7 @@ func (t *switchTable) doMaintenance() { defer t.mutex.Unlock() // Release lock when we're done t.cleanRoot() t.cleanDropped() + t.cleanPeers() } // Updates the root periodically if it is ourself, or promotes ourself to root if we're better than the current root or if the current root has timed out. @@ -258,11 +259,33 @@ func (t *switchTable) forgetPeer(port switchPort) { if port != t.parent { return } + t.parent = 0 for _, info := range t.data.peers { t.unlockedHandleMsg(&info.msg, info.port, true) } } +// Clean all unresponsive peers from the table, needed in case a peer stops updating. +// Needed in case a non-parent peer keeps the connection open but stops sending updates. +// Also reclaims space from deleted peers by copying the map. +func (t switchTable) cleanPeers() { + now := time.Now() + for port, peer := range t.data.peers { + if now.Sub(peer.time) > switch_timeout+switch_throttle { + // Longer than switch_timeout to make sure we don't remove a working peer because the root stopped responding. + delete(t.data.peers, port) + } + } + if _, isIn := t.data.peers[t.parent]; !isIn { + // The root timestamp would probably time out before this happens, but better safe than sorry. + // We removed the current parent, so find a new one. + t.parent = 0 + for _, peer := range t.data.peers { + t.unlockedHandleMsg(&peer.msg, peer.port, true) + } + } +} + // Dropped is a list of roots that are better than the current root, but stopped sending new timestamps. // If we switch to a new root, and that root is better than an old root that previously timed out, then we can clean up the old dropped root infos. // This function is called periodically to do that cleanup. @@ -379,9 +402,6 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep if !equiv(&sender.locator, &oldSender.locator) { // Reset faster info, we'll start refilling it right after this sender.faster = nil - for _, peer := range t.data.peers { - delete(peer.faster, sender.port) - } doUpdate = true } // Update the matrix of peer "faster" thresholds From d0c2ce90bbf194995f331003f10852a29872f49f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 7 Dec 2018 22:03:57 +0000 Subject: [PATCH 101/145] Fix semver when git history is not present --- contrib/semver/name.sh | 4 ++-- contrib/semver/version.sh | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/contrib/semver/name.sh b/contrib/semver/name.sh index d749d3ff..9dbdca33 100644 --- a/contrib/semver/name.sh +++ b/contrib/semver/name.sh @@ -1,10 +1,10 @@ #!/bin/sh # Get the branch name, removing any "/" characters from pull requests -BRANCH=$(git symbolic-ref --short HEAD | tr -d "/" 2>/dev/null) +BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null | tr -d "/") # Check if the branch name is not master -if [ "$BRANCH" = "master" ]; then +if [ "$BRANCH" = "master" ] || [ $? != 0 ]; then printf "yggdrasil" exit 0 fi diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 6eeffc5f..c23abf41 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -16,6 +16,11 @@ PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" if [ $? != 0 ]; then PATCH=$(git rev-list HEAD --count 2>/dev/null) + if [ $? != 0 ]; then + printf 'unknown' + exit -1 + fi + printf 'v0.0.%d' "$PATCH" exit -1 fi From 8bd566d4d8bb35ad47b414574d2315ed17ec6a3b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 7 Dec 2018 22:05:36 +0000 Subject: [PATCH 102/145] Remove VERSION --- VERSION | 1 - 1 file changed, 1 deletion(-) delete mode 100644 VERSION diff --git a/VERSION b/VERSION deleted file mode 100644 index 3b04cfb6..00000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.2 From 4bc009d8457f40dfaef4addc23bf9baabe1f360a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 7 Dec 2018 22:17:09 +0000 Subject: [PATCH 103/145] Update semver behaviour --- contrib/semver/name.sh | 15 ++++++++++++--- contrib/semver/version.sh | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/contrib/semver/name.sh b/contrib/semver/name.sh index 9dbdca33..9cab7e96 100644 --- a/contrib/semver/name.sh +++ b/contrib/semver/name.sh @@ -1,10 +1,19 @@ #!/bin/sh -# Get the branch name, removing any "/" characters from pull requests -BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null | tr -d "/") +# Get the current branch name +BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) + +# Complain if the git history is not available +if [ $? != 0 ]; then + printf "unknown" + exit -1 +fi + +# Remove "/" characters from the branch name if present +BRANCH=$(echo $BRANCH | tr -d "/") # Check if the branch name is not master -if [ "$BRANCH" = "master" ] || [ $? != 0 ]; then +if [ "$BRANCH" = "master" ]; then printf "yggdrasil" exit 0 fi diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index c23abf41..f7769a34 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -16,6 +16,7 @@ PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" if [ $? != 0 ]; then PATCH=$(git rev-list HEAD --count 2>/dev/null) + # Complain if the git history is not available if [ $? != 0 ]; then printf 'unknown' exit -1 From 8e784438c7ebb77eb903c964192012d8a0826d02 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 7 Dec 2018 22:20:11 +0000 Subject: [PATCH 104/145] Imprint build name and version number if available --- build | 5 +++-- src/yggdrasil/core.go | 21 +++++++++++++++++++++ yggdrasil.go | 5 +++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/build b/build index c07e5eac..2e7c20cc 100755 --- a/build +++ b/build @@ -14,10 +14,11 @@ go get -d -v yggdrasil for file in *.go ; do echo "Building: $file" #go build $@ $file + IMPRINT="-X yggdrasil.buildName=$(sh contrib/semver/name.sh) -X yggdrasil.buildVersion=$(sh contrib/semver/version.sh)" if [ $DEBUG ]; then - go build -tags debug -v $file + go build -ldflags="$IMPRINT" -tags debug -v $file else - go build -ldflags="-s -w" -v $file + go build -ldflags="$IMPRINT -s -w" -v $file fi if [ $UPX ]; then upx --brute ${file%.go} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 706b8aa3..54f1820c 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -12,6 +12,9 @@ import ( "yggdrasil/defaults" ) +var buildName string +var buildVersion string + // The Core object represents the Yggdrasil node. You should create a Core // object for each Yggdrasil node you plan to run. type Core struct { @@ -59,6 +62,24 @@ func (c *Core) init(bpub *boxPubKey, c.tun.init(c) } +// Get the current build name. This is usually injected if built from git, +// or returns "unknown" otherwise. +func GetBuildName() string { + if buildName == "" { + return "unknown" + } + return buildName +} + +// Get the current build version. This is usually injected if built from git, +// or returns "unknown" otherwise. +func GetBuildVersion() string { + if buildVersion == "" { + return "unknown" + } + return buildVersion +} + // Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging // through the provided log.Logger. The started stack will include TCP and UDP // sockets, a multicast discovery socket, an admin socket, router, switch and diff --git a/yggdrasil.go b/yggdrasil.go index 5244a8ec..d326d18e 100644 --- a/yggdrasil.go +++ b/yggdrasil.go @@ -100,10 +100,15 @@ func main() { normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") + version := flag.Bool("version", false, "prints the version of this build") flag.Parse() var cfg *nodeConfig switch { + case *version: + fmt.Println("Build name:", yggdrasil.GetBuildName()) + fmt.Println("Build version:", yggdrasil.GetBuildVersion()) + os.Exit(0) case *autoconf: // Use an autoconf-generated config, this will give us random keys and // port numbers, and will use an automatically selected TUN/TAP interface. From 3524c6eff617ede773ee83dce1d50a2601088846 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 7 Dec 2018 22:22:46 +0000 Subject: [PATCH 105/145] Add build name and version to getSelf call on admin socket --- src/yggdrasil/admin.go | 2 ++ yggdrasilctl.go | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 2b5bc645..af59734e 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -556,6 +556,8 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { table := a.core.switchTable.table.Load().(lookupTable) coords := table.self.getCoords() self := admin_nodeInfo{ + {"build_name", GetBuildName()}, + {"build_version", GetBuildVersion()}, {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])}, {"ip", a.core.GetAddress().String()}, {"subnet", a.core.GetSubnet().String()}, diff --git a/yggdrasilctl.go b/yggdrasilctl.go index 79b5f86d..6919ec33 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -181,6 +181,12 @@ func main() { } case "getself": for k, v := range res["self"].(map[string]interface{}) { + if buildname, ok := v.(map[string]interface{})["build_name"].(string); ok { + fmt.Println("Build name:", buildname) + } + if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok { + fmt.Println("Build version:", buildversion) + } fmt.Println("IPv6 address:", k) if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok { fmt.Println("IPv6 subnet:", subnet) From 5149c6c349d6540772799259c6a0e30a3aa66f33 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 7 Dec 2018 22:24:01 +0000 Subject: [PATCH 106/145] Show build name and version at startup if available --- src/yggdrasil/core.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 54f1820c..b57bd863 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -86,6 +86,14 @@ func GetBuildVersion() string { // DHT node. func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.log = log + + if buildName != "" { + c.log.Println("Build name:", buildName) + } + if buildVersion != "" { + c.log.Println("Build version:", buildVersion) + } + c.log.Println("Starting up...") var boxPub boxPubKey From f99c2241719e74b175abf5af74cdf938d78c034d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 7 Dec 2018 22:31:37 +0000 Subject: [PATCH 107/145] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b91caca4..fa5d08dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New macOS .pkgs built automatically by CircleCI - Add Docker support - Add `-json` command line flag for generating and normalising configuration in plain JSON +- Build nae and version numbers are now imprinted onto the build, accessible through `yggdrasil -version` and `yggdrasilctl getSelf` ### Changed - Switched to Chord DHT (instead of Kademlia, although protocol-compatible) From ffbdef292f3c2638263b6f5d4bfb3a46ac8fd92e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 7 Dec 2018 17:30:07 -0600 Subject: [PATCH 108/145] Fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5d08dc..1791bb74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New macOS .pkgs built automatically by CircleCI - Add Docker support - Add `-json` command line flag for generating and normalising configuration in plain JSON -- Build nae and version numbers are now imprinted onto the build, accessible through `yggdrasil -version` and `yggdrasilctl getSelf` +- Build name and version numbers are now imprinted onto the build, accessible through `yggdrasil -version` and `yggdrasilctl getSelf` ### Changed - Switched to Chord DHT (instead of Kademlia, although protocol-compatible) From 586781b49c18b7e9b4340f2b8f850c951bbbd696 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 7 Dec 2018 19:56:04 -0600 Subject: [PATCH 109/145] convert to go module --- build | 16 +++++-------- yggdrasil.go => cmd/yggdrasil/main.go | 6 ++--- yggdrasilctl.go => cmd/yggdrasilctl/main.go | 24 ++++++++++--------- contrib/config/yggdrasilconf.go | 2 +- go.mod | 14 +++++++++++ go.sum | 20 ++++++++++++++++ misc/genkeys.go | 2 +- misc/sim/run-sim | 2 -- misc/sim/treesim.go | 2 +- src/{yggdrasil => }/config/config.go | 0 src/{yggdrasil => }/config/i2p.go | 0 src/{yggdrasil => }/config/tor.go | 0 src/{yggdrasil => }/defaults/defaults.go | 0 .../defaults/defaults_darwin.go | 0 .../defaults/defaults_freebsd.go | 0 .../defaults/defaults_linux.go | 0 .../defaults/defaults_netbsd.go | 0 .../defaults/defaults_openbsd.go | 0 .../defaults/defaults_other.go | 0 .../defaults/defaults_windows.go | 0 src/yggdrasil/admin.go | 2 +- src/yggdrasil/core.go | 4 ++-- src/yggdrasil/debug.go | 2 +- src/yggdrasil/tun.go | 3 ++- 24 files changed, 65 insertions(+), 34 deletions(-) rename yggdrasil.go => cmd/yggdrasil/main.go (98%) rename yggdrasilctl.go => cmd/yggdrasilctl/main.go (98%) create mode 100644 go.mod create mode 100644 go.sum rename src/{yggdrasil => }/config/config.go (100%) rename src/{yggdrasil => }/config/i2p.go (100%) rename src/{yggdrasil => }/config/tor.go (100%) rename src/{yggdrasil => }/defaults/defaults.go (100%) rename src/{yggdrasil => }/defaults/defaults_darwin.go (100%) rename src/{yggdrasil => }/defaults/defaults_freebsd.go (100%) rename src/{yggdrasil => }/defaults/defaults_linux.go (100%) rename src/{yggdrasil => }/defaults/defaults_netbsd.go (100%) rename src/{yggdrasil => }/defaults/defaults_openbsd.go (100%) rename src/{yggdrasil => }/defaults/defaults_other.go (100%) rename src/{yggdrasil => }/defaults/defaults_windows.go (100%) diff --git a/build b/build index 2e7c20cc..a62c92d4 100755 --- a/build +++ b/build @@ -7,20 +7,16 @@ do d) DEBUG=true;; esac done -export GOPATH=$PWD echo "Downloading..." -go get -d -v -go get -d -v yggdrasil -for file in *.go ; do - echo "Building: $file" - #go build $@ $file - IMPRINT="-X yggdrasil.buildName=$(sh contrib/semver/name.sh) -X yggdrasil.buildVersion=$(sh contrib/semver/version.sh)" +for CMD in `ls cmd/` ; do + echo "Building: $CMD" + IMPRINT="-X github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil.buildName=$(sh contrib/semver/name.sh) -X github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil.buildVersion=$(sh contrib/semver/version.sh)" if [ $DEBUG ]; then - go build -ldflags="$IMPRINT" -tags debug -v $file + go build -ldflags="$IMPRINT" -tags debug -v ./cmd/$CMD else - go build -ldflags="$IMPRINT -s -w" -v $file + go build -ldflags="$IMPRINT -s -w" -v ./cmd/$CMD fi if [ $UPX ]; then - upx --brute ${file%.go} + upx --brute $CMD fi done diff --git a/yggdrasil.go b/cmd/yggdrasil/main.go similarity index 98% rename from yggdrasil.go rename to cmd/yggdrasil/main.go index d326d18e..5b756908 100644 --- a/yggdrasil.go +++ b/cmd/yggdrasil/main.go @@ -21,9 +21,9 @@ import ( "github.com/mitchellh/mapstructure" "github.com/neilalexander/hjson-go" - "yggdrasil" - "yggdrasil/config" - "yggdrasil/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) type nodeConfig = config.NodeConfig diff --git a/yggdrasilctl.go b/cmd/yggdrasilctl/main.go similarity index 98% rename from yggdrasilctl.go rename to cmd/yggdrasilctl/main.go index 6919ec33..ca3078b1 100644 --- a/yggdrasilctl.go +++ b/cmd/yggdrasilctl/main.go @@ -1,17 +1,19 @@ package main -import "errors" -import "flag" -import "fmt" -import "strings" -import "net" -import "net/url" -import "sort" -import "encoding/json" -import "strconv" -import "os" +import ( + "encoding/json" + "errors" + "flag" + "fmt" + "net" + "net/url" + "os" + "sort" + "strconv" + "strings" -import "yggdrasil/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" +) type admin_info map[string]interface{} diff --git a/contrib/config/yggdrasilconf.go b/contrib/config/yggdrasilconf.go index bc6e1326..78c0e6d7 100644 --- a/contrib/config/yggdrasilconf.go +++ b/contrib/config/yggdrasilconf.go @@ -17,7 +17,7 @@ import ( "github.com/neilalexander/hjson-go" "golang.org/x/text/encoding/unicode" - "yggdrasil/config" + "github.com/yggdrasil-network/yggdrasil-go/src/config" ) type nodeConfig = config.NodeConfig diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..1622ad40 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/yggdrasil-network/yggdrasil-go + +require ( + github.com/docker/libcontainer v2.2.1+incompatible + github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 + github.com/mitchellh/mapstructure v1.1.2 + github.com/neilalexander/hjson-go v3.0.0+incompatible + github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 + github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae + golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 + golang.org/x/net v0.0.0-20181207154023-610586996380 + golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e + golang.org/x/text v0.3.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..2c8b145d --- /dev/null +++ b/go.sum @@ -0,0 +1,20 @@ +github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0= +github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= +github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= +github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/neilalexander/hjson-go v3.0.0+incompatible h1:MRqki7QoLwAe9kD12DF4yy6r04KKxvjBcMGvVGNNQ8g= +github.com/neilalexander/hjson-go v3.0.0+incompatible/go.mod h1:l+Zao6IpQ+6d/y7LnYnOfbfOeU/9xRiTi4HLVpnkcTg= +github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= +github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= +github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc= +github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0= +golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo= +golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/misc/genkeys.go b/misc/genkeys.go index 6e2df3f9..e995805f 100644 --- a/misc/genkeys.go +++ b/misc/genkeys.go @@ -16,7 +16,7 @@ import "encoding/hex" import "flag" import "fmt" import "runtime" -import . "yggdrasil" +import . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" var doSig = flag.Bool("sig", false, "generate new signing keys instead") diff --git a/misc/sim/run-sim b/misc/sim/run-sim index abe108cf..14057e81 100755 --- a/misc/sim/run-sim +++ b/misc/sim/run-sim @@ -1,4 +1,2 @@ #!/bin/bash -export GOPATH=$PWD -go get -d yggdrasil go run -tags debug misc/sim/treesim.go "$@" diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 8a4bb2a1..f4cd75fa 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -12,7 +12,7 @@ import "runtime" import "runtime/pprof" import "flag" -import . "yggdrasil" +import . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" //////////////////////////////////////////////////////////////////////////////// diff --git a/src/yggdrasil/config/config.go b/src/config/config.go similarity index 100% rename from src/yggdrasil/config/config.go rename to src/config/config.go diff --git a/src/yggdrasil/config/i2p.go b/src/config/i2p.go similarity index 100% rename from src/yggdrasil/config/i2p.go rename to src/config/i2p.go diff --git a/src/yggdrasil/config/tor.go b/src/config/tor.go similarity index 100% rename from src/yggdrasil/config/tor.go rename to src/config/tor.go diff --git a/src/yggdrasil/defaults/defaults.go b/src/defaults/defaults.go similarity index 100% rename from src/yggdrasil/defaults/defaults.go rename to src/defaults/defaults.go diff --git a/src/yggdrasil/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go similarity index 100% rename from src/yggdrasil/defaults/defaults_darwin.go rename to src/defaults/defaults_darwin.go diff --git a/src/yggdrasil/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go similarity index 100% rename from src/yggdrasil/defaults/defaults_freebsd.go rename to src/defaults/defaults_freebsd.go diff --git a/src/yggdrasil/defaults/defaults_linux.go b/src/defaults/defaults_linux.go similarity index 100% rename from src/yggdrasil/defaults/defaults_linux.go rename to src/defaults/defaults_linux.go diff --git a/src/yggdrasil/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go similarity index 100% rename from src/yggdrasil/defaults/defaults_netbsd.go rename to src/defaults/defaults_netbsd.go diff --git a/src/yggdrasil/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go similarity index 100% rename from src/yggdrasil/defaults/defaults_openbsd.go rename to src/defaults/defaults_openbsd.go diff --git a/src/yggdrasil/defaults/defaults_other.go b/src/defaults/defaults_other.go similarity index 100% rename from src/yggdrasil/defaults/defaults_other.go rename to src/defaults/defaults_other.go diff --git a/src/yggdrasil/defaults/defaults_windows.go b/src/defaults/defaults_windows.go similarity index 100% rename from src/yggdrasil/defaults/defaults_windows.go rename to src/defaults/defaults_windows.go diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index af59734e..ebd11d9c 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -14,7 +14,7 @@ import ( "sync/atomic" "time" - "yggdrasil/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) // TODO: Add authentication diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index b57bd863..682d815e 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -8,8 +8,8 @@ import ( "net" "regexp" - "yggdrasil/config" - "yggdrasil/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) var buildName string diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 7da84749..e463518d 100644 --- a/src/yggdrasil/debug.go +++ b/src/yggdrasil/debug.go @@ -22,7 +22,7 @@ import "net/http" import "runtime" import "os" -import "yggdrasil/defaults" +import "github.com/yggdrasil-network/yggdrasil-go/src/defaults" // Start the profiler in debug builds, if the required environment variable is set. func init() { diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 4b3617c0..e4625020 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -6,10 +6,11 @@ import ( "bytes" "errors" "time" - "yggdrasil/defaults" "github.com/songgao/packets/ethernet" "github.com/yggdrasil-network/water" + + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) const tun_IPv6_HEADER_LENGTH = 40 From 2d5f99a00847cdca264dd0fbc6afa4cc14f73168 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 7 Dec 2018 20:19:19 -0600 Subject: [PATCH 110/145] remove working_directory from circleci config, let it use the default, as per their blog post on go modules in 1.11 --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 90bba23b..32a789c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,8 +7,6 @@ jobs: docker: - image: circleci/golang:1.11 - working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}} - steps: - checkout From bd2d70674569750f0719b1d6bbdcae964a384f89 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 7 Dec 2018 20:36:30 -0600 Subject: [PATCH 111/145] fix bug from go vet while I'm at it --- src/yggdrasil/switch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 920926b4..99ed25b4 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -268,7 +268,7 @@ func (t *switchTable) forgetPeer(port switchPort) { // Clean all unresponsive peers from the table, needed in case a peer stops updating. // Needed in case a non-parent peer keeps the connection open but stops sending updates. // Also reclaims space from deleted peers by copying the map. -func (t switchTable) cleanPeers() { +func (t *switchTable) cleanPeers() { now := time.Now() for port, peer := range t.data.peers { if now.Sub(peer.time) > switch_timeout+switch_throttle { From af478e0e4514474c64d7b7e4dfdf520e1a376fdc Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 8 Dec 2018 00:42:13 -0600 Subject: [PATCH 112/145] fix very special case bug when trying to dhtPing the root via the admin api --- src/yggdrasil/admin.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index ebd11d9c..7d82b135 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -739,6 +739,10 @@ func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtR } 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 dhtRes{}, err } else { From 2da3ef420c13a2da1e253be0bcafa4db2157d09b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 8 Dec 2018 10:30:43 +0000 Subject: [PATCH 113/145] Update documentation, remove stray .DS_Store file --- CHANGELOG.md | 1 + README.md | 2 +- contrib/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 contrib/.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 1791bb74..cc31b965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Admin socket clean-up (making some names consistent) - Latency-based parent selection for the switch instead of uptime-based - Real peering endpoints now shown in the admin socket `getPeers` call +- Reuse the multicast port on supported platforms so that multiple Yggdrasil processes can run ### Fixed - Memory leaks in the DHT fixed diff --git a/README.md b/README.md index 15731952..11a4cbe5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You're encouraged to play with it, but it is strongly advised not to use it for ## Building -1. Install Go (tested on 1.9+, [godeb](https://github.com/niemeyer/godeb) is recommended for debian-based linux distributions). +1. Install Go (requires 1.11 or later, [godeb](https://github.com/niemeyer/godeb) is recommended for Debian-based Linux distributions). 2. Clone this repository. 2. `./build` diff --git a/contrib/.DS_Store b/contrib/.DS_Store deleted file mode 100644 index 799616ae9f2aed17d20533c4052922a02fadaab9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKPfx-y6mJ2gV2m70)LSp!I50Kf#blXy@Mbcj2Q{!|C`)iNY)BL_`yBd>`~rR* z-)jpI(TfLT$a~A{@Ads@)8CeMWsGrW5Lk>=7-IqyF;{}-8^Jj0oaBrLk*hI$maxDd zCU<^sE}Iv_;EG-v>D2wuH`O59aLpSx( zLE?D*3+$bEY1cRQX&m>aw&$OPonfo8ei%oo7e<}FDh`7Vq+FhdQ4kNEcn}4N8pqWE zAxfgus%(u$)=sTnm6o+XuF6rPQLDd;Dx5!bYMy5Y@J&ip0yIR4T^$! nnZ{`e80sj7Ts(?5L6v}Cq5)_d%rt@rgnk4R4b%_=f6BlIbwN_$ From 5315bc25c5d676a5a0b25c68d669965e490bf1c4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 8 Dec 2018 10:33:33 +0000 Subject: [PATCH 114/145] Return 1 instead of -1 from semver/deb --- contrib/deb/generate.sh | 4 ++-- contrib/semver/name.sh | 2 +- contrib/semver/version.sh | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index 9d5064bd..f99d8348 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -7,7 +7,7 @@ if [ `pwd` != `git rev-parse --show-toplevel` ] then echo "You should run this script from the top-level directory of the git repo" - exit -1 + exit 1 fi PKGBRANCH=$(basename `git name-rev --name-only HEAD`) @@ -29,7 +29,7 @@ elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=7 ./build elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build else echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64" - exit -1 + exit 1 fi echo "Building $PKGFILE" diff --git a/contrib/semver/name.sh b/contrib/semver/name.sh index 9cab7e96..935cc750 100644 --- a/contrib/semver/name.sh +++ b/contrib/semver/name.sh @@ -6,7 +6,7 @@ BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) # Complain if the git history is not available if [ $? != 0 ]; then printf "unknown" - exit -1 + exit 1 fi # Remove "/" characters from the branch name if present diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index f7769a34..143bd906 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -19,11 +19,11 @@ if [ $? != 0 ]; then # Complain if the git history is not available if [ $? != 0 ]; then printf 'unknown' - exit -1 + exit 1 fi printf 'v0.0.%d' "$PATCH" - exit -1 + exit 1 fi # Get the number of merges on the current branch since the last tag From 9d0b8ac6f4350a3fe490fac55fb8badf1f73571d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 8 Dec 2018 10:51:31 +0000 Subject: [PATCH 115/145] Strip v from version during imprint --- build | 2 +- contrib/semver/version.sh | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/build b/build index a62c92d4..8dd23ef4 100755 --- a/build +++ b/build @@ -10,7 +10,7 @@ done echo "Downloading..." for CMD in `ls cmd/` ; do echo "Building: $CMD" - IMPRINT="-X github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil.buildName=$(sh contrib/semver/name.sh) -X github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil.buildVersion=$(sh contrib/semver/version.sh)" + IMPRINT="-X github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil.buildName=$(sh contrib/semver/name.sh) -X github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil.buildVersion=$(sh contrib/semver/version.sh --bare)" if [ $DEBUG ]; then go build -ldflags="$IMPRINT" -tags debug -v ./cmd/$CMD else diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 143bd906..480c4dda 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -12,6 +12,13 @@ MERGE=$(git rev-list $TAG..master --grep "from $DEVELOPBRANCH" 2>/dev/null | hea # Get the number of merges since the last merge to master PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" 2>/dev/null) +# Decide whether we should prepend the version with "v" - the default is that +# we do because we use it in git tags, but we might not always need it +PREPEND="v" +if [ "$1" == "--bare" ]; then + PREPEND="" +fi + # If it fails then there's no last tag - go from the first commit if [ $? != 0 ]; then PATCH=$(git rev-list HEAD --count 2>/dev/null) @@ -22,7 +29,7 @@ if [ $? != 0 ]; then exit 1 fi - printf 'v0.0.%d' "$PATCH" + printf '%s0.0.%d' "$PREPEND" "$PATCH" exit 1 fi @@ -39,12 +46,12 @@ BRANCH=$(git rev-parse --abbrev-ref HEAD) # Output in the desired format if [ $PATCH = 0 ]; then if [ ! -z $FULL ]; then - printf 'v%d.%d.0' "$MAJOR" "$MINOR" + printf '%s%d.%d.0' "$PREPEND" "$MAJOR" "$MINOR" else - printf 'v%d.%d' "$MAJOR" "$MINOR" + printf '%s%d.%d' "$PREPEND" "$MAJOR" "$MINOR" fi else - printf 'v%d.%d.%d' "$MAJOR" "$MINOR" "$PATCH" + printf '%s%d.%d.%d' "$PREPEND" "$MAJOR" "$MINOR" "$PATCH" fi # Add the build tag on non-master branches From f2d01aa54db864282a2813883937d77aaec0cd68 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 8 Dec 2018 10:54:47 +0000 Subject: [PATCH 116/145] Use bare version in deb/macos packages instead of cut --- contrib/deb/generate.sh | 2 +- contrib/macos/create-pkg.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index f99d8348..5af31d5c 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -12,7 +12,7 @@ fi PKGBRANCH=$(basename `git name-rev --name-only HEAD`) PKGNAME=$(sh contrib/semver/name.sh) -PKGVERSION=$(sh contrib/semver/version.sh | cut -c 2-) +PKGVERSION=$(sh contrib/semver/version.sh --bare) PKGARCH=${PKGARCH-amd64} PKGFILE=$PKGNAME-$PKGVERSION-$PKGARCH.deb PKGREPLACES=yggdrasil diff --git a/contrib/macos/create-pkg.sh b/contrib/macos/create-pkg.sh index 167184e3..cc9a74f7 100755 --- a/contrib/macos/create-pkg.sh +++ b/contrib/macos/create-pkg.sh @@ -72,7 +72,7 @@ chmod +x pkgbuild/root/usr/local/bin/yggdrasilctl # Work out metadata for the package info PKGNAME=$(sh contrib/semver/name.sh) -PKGVERSION=$(sh contrib/semver/version.sh | cut -c 2-) +PKGVERSION=$(sh contrib/semver/version.sh --bare) PKGARCH=${PKGARCH-amd64} PAYLOADSIZE=$(( $(wc -c pkgbuild/flat/base.pkg/Payload | awk '{ print $1 }') / 1024 )) From 02f98a2592290ad4eb17a7bee14cb77400316f3c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 8 Dec 2018 11:01:05 +0000 Subject: [PATCH 117/145] Only show build name and version if it is known --- cmd/yggdrasilctl/main.go | 4 ++-- src/yggdrasil/admin.go | 9 +++++++-- src/yggdrasil/core.go | 8 ++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index ca3078b1..dcbde87a 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -183,10 +183,10 @@ func main() { } case "getself": for k, v := range res["self"].(map[string]interface{}) { - if buildname, ok := v.(map[string]interface{})["build_name"].(string); ok { + if buildname, ok := v.(map[string]interface{})["build_name"].(string); ok && buildname != "unknown" { fmt.Println("Build name:", buildname) } - if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok { + if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok && buildversion != "unknown" { fmt.Println("Build version:", buildversion) } fmt.Println("IPv6 address:", k) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 7d82b135..2c65b6a8 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -556,13 +556,18 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { table := a.core.switchTable.table.Load().(lookupTable) coords := table.self.getCoords() self := admin_nodeInfo{ - {"build_name", GetBuildName()}, - {"build_version", GetBuildVersion()}, {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])}, {"ip", a.core.GetAddress().String()}, {"subnet", a.core.GetSubnet().String()}, {"coords", fmt.Sprint(coords)}, } + if name := GetBuildName(); name != "unknown" { + self = append(self, admin_pair{"build_name", name}) + } + if version := GetBuildVersion(); version != "unknown" { + self = append(self, admin_pair{"build_version", version}) + } + return &self } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 682d815e..5ab91a74 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -87,11 +87,11 @@ func GetBuildVersion() string { func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.log = log - if buildName != "" { - c.log.Println("Build name:", buildName) + if name := GetBuildName(); name != "unknown" { + c.log.Println("Build name:", name) } - if buildVersion != "" { - c.log.Println("Build version:", buildVersion) + if version := GetBuildVersion(); version != "unknown" { + c.log.Println("Build version:", version) } c.log.Println("Starting up...") From 22e650507960a5a062d6a4f85552241666b409d6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 8 Dec 2018 11:28:47 +0000 Subject: [PATCH 118/145] Fix bug from #228 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 32a789c2..f3092ba1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,7 @@ jobs: command: | mkdir /tmp/upload echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV - echo 'export CIVERSION=$(sh contrib/semver/version.sh | cut -c 2-)' >> $BASH_ENV + echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV git config --global user.email "$(git log --format='%ae' HEAD -1)"; git config --global user.name "$(git log --format='%an' HEAD -1)"; From 3b2044666d65594bd57651a6724ede973e774971 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 8 Dec 2018 11:31:20 +0000 Subject: [PATCH 119/145] Fix bug in semver version.sh --- contrib/semver/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index 480c4dda..331046f3 100644 --- a/contrib/semver/version.sh +++ b/contrib/semver/version.sh @@ -15,7 +15,7 @@ PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" # Decide whether we should prepend the version with "v" - the default is that # we do because we use it in git tags, but we might not always need it PREPEND="v" -if [ "$1" == "--bare" ]; then +if [ "$1" = "--bare" ]; then PREPEND="" fi From 34949f7c328eeaf7e62e7e14e07723d0e7570dfa Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 8 Dec 2018 13:21:58 +0000 Subject: [PATCH 120/145] Fix go.mod for neilalexander/hjson-go --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1622ad40..db6d359a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/docker/libcontainer v2.2.1+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 - github.com/neilalexander/hjson-go v3.0.0+incompatible + github.com/neilalexander/hjson-go v0.0.0-20180509131856-23267a251165 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 diff --git a/go.sum b/go.sum index 2c8b145d..7d064112 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/neilalexander/hjson-go v3.0.0+incompatible h1:MRqki7QoLwAe9kD12DF4yy6r04KKxvjBcMGvVGNNQ8g= -github.com/neilalexander/hjson-go v3.0.0+incompatible/go.mod h1:l+Zao6IpQ+6d/y7LnYnOfbfOeU/9xRiTi4HLVpnkcTg= +github.com/neilalexander/hjson-go v0.0.0-20180509131856-23267a251165 h1:Oo7Yfu5lEQLGvvh2p9Z8FRHJIsl7fdOCK9xXFNBkqmQ= +github.com/neilalexander/hjson-go v0.0.0-20180509131856-23267a251165/go.mod h1:l+Zao6IpQ+6d/y7LnYnOfbfOeU/9xRiTi4HLVpnkcTg= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc= From ba4507c02edf991b584535feaa828f3663e74bb6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 13:33:18 +0000 Subject: [PATCH 121/145] Update yggdrasilctl help (fixes #194) --- cmd/yggdrasilctl/main.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index dcbde87a..ab8ee595 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -18,18 +18,26 @@ import ( type admin_info map[string]interface{} func main() { + flag.Usage = func() { + fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n", os.Args[0]) + fmt.Println("Options:") + flag.PrintDefaults() + fmt.Println("Commands:\n - Use \"list\" for a list of available commands") + fmt.Println("Examples:") + fmt.Println(" - ", os.Args[0], "list") + fmt.Println(" - ", os.Args[0], "getPeers") + fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") + fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") + fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT") + } server := flag.String("endpoint", defaults.GetDefaults().DefaultAdminListen, "Admin socket endpoint") - injson := flag.Bool("json", false, "Output in JSON format") + injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)") verbose := flag.Bool("v", false, "Verbose output (includes public keys)") flag.Parse() args := flag.Args() if len(args) == 0 { - fmt.Println("usage:", os.Args[0], "[-endpoint=proto://server] [-v] [-json] command [key=value] [...]") - fmt.Println("example:", os.Args[0], "getPeers") - fmt.Println("example:", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") - fmt.Println("example:", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") - fmt.Println("example:", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT") + flag.Usage() return } From e759fb1c3855a5e9d319c6f1ebc491f0a1bfd06e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 13:37:19 +0000 Subject: [PATCH 122/145] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc31b965..a073532f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Latency-based parent selection for the switch instead of uptime-based - Real peering endpoints now shown in the admin socket `getPeers` call - Reuse the multicast port on supported platforms so that multiple Yggdrasil processes can run +- `yggdrasilctl` now has more useful help text (with `-help` or when no arguments passed) ### Fixed - Memory leaks in the DHT fixed From 0fdc814c4ac7bae91b7d1a8868ac41f281ce882b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 14:32:24 +0000 Subject: [PATCH 123/145] Allow specifying PKGSRC=, PKGVER= and PKGNAME= to build script --- build | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build b/build index 8dd23ef4..5f13f844 100755 --- a/build +++ b/build @@ -1,5 +1,8 @@ #!/bin/sh -while getopts ud option +PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil} +PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} +PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} +while getopts "ud" option do case "${option}" in @@ -10,7 +13,7 @@ done echo "Downloading..." for CMD in `ls cmd/` ; do echo "Building: $CMD" - IMPRINT="-X github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil.buildName=$(sh contrib/semver/name.sh) -X github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil.buildVersion=$(sh contrib/semver/version.sh --bare)" + IMPRINT="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" if [ $DEBUG ]; then go build -ldflags="$IMPRINT" -tags debug -v ./cmd/$CMD else From 80d087404f62dec363779160bbe54ba885370e2e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 17:46:48 +0000 Subject: [PATCH 124/145] Allow disabling admin socket with AdminListen="none" --- src/config/config.go | 2 +- src/yggdrasil/admin.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index 904907b2..9671eca3 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -3,7 +3,7 @@ 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."` + 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."` diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 2c65b6a8..19c8d0ad 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -326,7 +326,9 @@ func (a *admin) init(c *Core, listenaddr string) { // start runs the admin API socket to listen for / respond to admin API calls. func (a *admin) start() error { - go a.listen() + if a.listenaddr != "none" { + go a.listen() + } return nil } From 5ed197b3ca7992b7599c2d295a4d8077a955e82a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 17:48:35 +0000 Subject: [PATCH 125/145] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a073532f..d2c58a27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add Docker support - Add `-json` command line flag for generating and normalising configuration in plain JSON - Build name and version numbers are now imprinted onto the build, accessible through `yggdrasil -version` and `yggdrasilctl getSelf` +- Add ability to disable admin socket by setting `AdminListen` to `"none"` ### Changed - Switched to Chord DHT (instead of Kademlia, although protocol-compatible) From 6801d713a7e73f7ec661cf71137432a5d0d4402b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 17:53:31 +0000 Subject: [PATCH 126/145] Also don't start if AdminListen is empty --- src/yggdrasil/admin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 19c8d0ad..dea4577a 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -326,7 +326,7 @@ func (a *admin) init(c *Core, listenaddr string) { // start runs the admin API socket to listen for / respond to admin API calls. func (a *admin) start() error { - if a.listenaddr != "none" { + if a.listenaddr != "none" && a.listenaddr != "" { go a.listen() } return nil From bec044346e0c568c67b342e3864fc19b0db7af10 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 22:31:58 +0000 Subject: [PATCH 127/145] Add -t, -c and -l to build script for specifying DWARF tables, GCFLAGS and LDFLAGS --- build | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/build b/build index 5f13f844..e463c852 100755 --- a/build +++ b/build @@ -1,23 +1,34 @@ #!/bin/sh + PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} -while getopts "ud" option + +LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" + +while getopts "udtc:l:" option do case "${option}" in u) UPX=true;; d) DEBUG=true;; + t) TABLES=true;; + c) GCFLAGS="$GCFLAGS $OPTARG";; + l) LDFLAGS="$LDFLAGS $OPTARG";; esac done -echo "Downloading..." + +if [ -z $TABLES ]; then + STRIP="-s -w" +fi + for CMD in `ls cmd/` ; do echo "Building: $CMD" - IMPRINT="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" + if [ $DEBUG ]; then - go build -ldflags="$IMPRINT" -tags debug -v ./cmd/$CMD + go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD else - go build -ldflags="$IMPRINT -s -w" -v ./cmd/$CMD + go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD fi if [ $UPX ]; then upx --brute $CMD From 08ad163dfe79bee2b5f8e6880ec9b598adfdc4ee Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 23:10:12 +0000 Subject: [PATCH 128/145] Add starting point for an RPM spec file --- contrib/rpm/yggdrasil.spec | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 contrib/rpm/yggdrasil.spec diff --git a/contrib/rpm/yggdrasil.spec b/contrib/rpm/yggdrasil.spec new file mode 100644 index 00000000..bab50906 --- /dev/null +++ b/contrib/rpm/yggdrasil.spec @@ -0,0 +1,47 @@ +Name: yggdrasil +Version: 0.3.0 +Release: 1%{?dist} +Summary: End-to-end encrypted IPv6 networking + +License: GPLv3 +URL: https://yggdrasil-network.github.io +Source0: https://codeload.github.com/yggdrasil-network/yggdrasil-go/tar.gz/v0.3.0 + +%{?systemd_requires} +BuildRequires: systemd golang >= 1.11 + +%description +Yggdrasil is a proof-of-concept to explore a wholly different approach to +network routing. Whereas current computer networks depend heavily on very +centralised design and configuration, Yggdrasil breaks this mould by making +use of a global spanning tree to form a scalable IPv6 encrypted mesh network. + +%prep +%setup -qn yggdrasil-go-%{version} + +%build +./build -t -l "-linkmode=external" + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{_bindir} +mkdir -p %{buildroot}/%{_sysconfdir}/systemd/system +install -m 0755 yggdrasil %{buildroot}/%{_bindir}/yggdrasil +install -m 0755 yggdrasilctl %{buildroot}/%{_bindir}/yggdrasilctl +install -m 0755 contrib/systemd/yggdrasil.service %{buildroot}/%{_sysconfdir}/systemd/system/yggdrasil.service +install -m 0755 contrib/systemd/yggdrasil-resume.service %{buildroot}/%{_sysconfdir}/systemd/system/yggdrasil-resume.service + +%files +%{_bindir}/yggdrasil +%{_bindir}/yggdrasilctl +%{_sysconfdir}/systemd/system/yggdrasil.service +%{_sysconfdir}/systemd/system/yggdrasil-resume.service + +%post +%systemd_post yggdrasil.service + +%preun +%systemd_preun yggdrasil.service + +%postun +%systemd_postun_with_restart yggdrasil.service From f6cdb8e38e6e847d46b3f69682ab8ceb2913e0bd Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 9 Dec 2018 23:35:40 +0000 Subject: [PATCH 129/145] Update CHANGELOG.md --- CHANGELOG.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2c58a27..8501f3e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,29 +25,29 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> -## [0.x.x] - TBD +## [0.3.0] - 2018-12-12 ### Added -- Crypto-key routing support for both IPv4 and IPv6 -- Add `SwitchOptions` in configuration file for tuning the switch +- Crypto-key routing support for tunnelling both IPv4 and IPv6 over Yggdrasil +- Add advanced `SwitchOptions` in configuration file for tuning the switch - Add `dhtPing` to the admin socket to aid in crawling the network - New macOS .pkgs built automatically by CircleCI -- Add Docker support -- Add `-json` command line flag for generating and normalising configuration in plain JSON +- Add Dockerfile to repository for Docker support +- Add `-json` command line flag for generating and normalising configuration in plain JSON instead of HJSON - Build name and version numbers are now imprinted onto the build, accessible through `yggdrasil -version` and `yggdrasilctl getSelf` - Add ability to disable admin socket by setting `AdminListen` to `"none"` ### Changed -- Switched to Chord DHT (instead of Kademlia, although protocol-compatible) -- Admin socket clean-up (making some names consistent) -- Latency-based parent selection for the switch instead of uptime-based -- Real peering endpoints now shown in the admin socket `getPeers` call +- Switched to Chord DHT (instead of Kademlia, although still compatible at the protocol level) +- Cleaned up some of the parameter naming in the admin socket +- Latency-based parent selection for the switch instead of uptime-based (should help to avoid high latency links somewhat) +- Real peering endpoints now shown in the admin socket `getPeers` call to help identify peerings - Reuse the multicast port on supported platforms so that multiple Yggdrasil processes can run - `yggdrasilctl` now has more useful help text (with `-help` or when no arguments passed) ### Fixed - Memory leaks in the DHT fixed -- Crash where ICMPv6 NDP goroutine would incorrectly start in TUN mode fixed -- Remove peers from the switch table of they stop sending switch messages but keep the TCP connection alive +- Crash fixed where the ICMPv6 NDP goroutine would incorrectly start in TUN mode +- Removing peers from the switch table if they stop sending switch messages but keep the TCP connection alive ## [0.2.7] - 2018-10-13 ### Added From f791df4977fc3fb4ab4c884d187d1b4edd7ac744 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 00:00:23 +0000 Subject: [PATCH 130/145] Try to chmod 660 the admin socket if using AF_UNIX --- src/yggdrasil/admin.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index dea4577a..50fdcf90 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -344,6 +344,11 @@ func (a *admin) listen() { switch strings.ToLower(u.Scheme) { case "unix": a.listener, err = net.Listen("unix", a.listenaddr[7:]) + if err == nil { + if err := os.Chmod(a.listenaddr[7:], 0660); err != nil { + a.core.log.Printf("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") + } + } case "tcp": a.listener, err = net.Listen("tcp", u.Host) default: From 06c6dfc67f3c5aa90356e3ccf2ca326f7d8cfa62 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 00:19:21 +0000 Subject: [PATCH 131/145] Complain if socket file already exists --- src/yggdrasil/admin.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 50fdcf90..720c30f8 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -343,11 +343,14 @@ func (a *admin) listen() { if err == nil { switch strings.ToLower(u.Scheme) { case "unix": + if _, err := os.Stat(a.listenaddr[7:]); err == nil { + a.core.log.Println("WARNING:", a.listenaddr[7:], "already exists and may be in use by another process") + } a.listener, err = net.Listen("unix", a.listenaddr[7:]) if err == nil { if err := os.Chmod(a.listenaddr[7:], 0660); err != nil { - a.core.log.Printf("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") - } + a.core.log.Println("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") + } } case "tcp": a.listener, err = net.Listen("tcp", u.Host) From 74a904d04cca967c3a3b8b88f775fecdf07b6514 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 00:26:12 +0000 Subject: [PATCH 132/145] Don't os.Chmod if we suspect the socket belongs to an abstract namespace --- src/yggdrasil/admin.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 720c30f8..2c3dc345 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -348,8 +348,12 @@ func (a *admin) listen() { } a.listener, err = net.Listen("unix", a.listenaddr[7:]) if err == nil { - if err := os.Chmod(a.listenaddr[7:], 0660); err != nil { - a.core.log.Println("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") + switch a.listenaddr[7:8] { + case "@": // maybe abstract namespace + default: + if err := os.Chmod(a.listenaddr[7:], 0660); err != nil { + a.core.log.Println("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") + } } } case "tcp": From bbe2f56b74ba7632de990361640dc7b3751300dd Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 00:31:31 +0000 Subject: [PATCH 133/145] Default to /var/run/yggdrasil.sock for admin on darwin/macOS --- src/defaults/defaults_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 778162c0..68a13931 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -7,7 +7,7 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // TUN/TAP MaximumIfMTU: 65535, From 8aaaeb26eb5d80ef753a64e14108ed5cfedf5880 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 00:37:32 +0000 Subject: [PATCH 134/145] Default to /var/run/yggdrasil.sock for admin on Linux, BSDs --- src/defaults/defaults_freebsd.go | 2 +- src/defaults/defaults_linux.go | 2 +- src/defaults/defaults_netbsd.go | 2 +- src/defaults/defaults_openbsd.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index 7c5c7752..4ba7face 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -7,7 +7,7 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // TUN/TAP MaximumIfMTU: 32767, diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index 85287eeb..e3e23e20 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -7,7 +7,7 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // TUN/TAP MaximumIfMTU: 65535, diff --git a/src/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go index 8e8f7b5f..7879a415 100644 --- a/src/defaults/defaults_netbsd.go +++ b/src/defaults/defaults_netbsd.go @@ -7,7 +7,7 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // TUN/TAP MaximumIfMTU: 9000, diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index 8b3e2bbc..7bfd30e6 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -7,7 +7,7 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // TUN/TAP MaximumIfMTU: 16384, From 2928fcb046a2ab1e1c525e3aa491730a2523b138 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 00:42:56 +0000 Subject: [PATCH 135/145] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8501f3e9..da8c7022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Switched to Chord DHT (instead of Kademlia, although still compatible at the protocol level) +- The `AdminListen` option and `yggdrasilctl` now default to `unix:///var/run/yggdrasil.sock` on BSDs, macOS and Linux - Cleaned up some of the parameter naming in the admin socket - Latency-based parent selection for the switch instead of uptime-based (should help to avoid high latency links somewhat) - Real peering endpoints now shown in the admin socket `getPeers` call to help identify peerings From dff1dca19c8f713d0f2167ff0ed93cbea27fcae1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 10:20:59 +0000 Subject: [PATCH 136/145] Add DefaultConfigFile to defaults for yggdrasilctl --- src/defaults/defaults.go | 3 +++ src/defaults/defaults_darwin.go | 3 +++ src/defaults/defaults_freebsd.go | 3 +++ src/defaults/defaults_linux.go | 3 +++ src/defaults/defaults_netbsd.go | 3 +++ src/defaults/defaults_openbsd.go | 3 +++ src/defaults/defaults_other.go | 3 +++ src/defaults/defaults_windows.go | 3 +++ 8 files changed, 24 insertions(+) diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index 753efc53..3834990e 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -7,6 +7,9 @@ type platformDefaultParameters struct { // Admin socket DefaultAdminListen string + // Configuration (used for yggdrasilctl) + DefaultConfigFile string + // TUN/TAP MaximumIfMTU int DefaultIfMTU int diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 68a13931..9bac3aad 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -9,6 +9,9 @@ func GetDefaults() platformDefaultParameters { // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index 4ba7face..df1a3c65 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -9,6 +9,9 @@ func GetDefaults() platformDefaultParameters { // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", + // TUN/TAP MaximumIfMTU: 32767, DefaultIfMTU: 32767, diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index e3e23e20..2f3459ca 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -9,6 +9,9 @@ func GetDefaults() platformDefaultParameters { // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go index 7879a415..40476dcb 100644 --- a/src/defaults/defaults_netbsd.go +++ b/src/defaults/defaults_netbsd.go @@ -9,6 +9,9 @@ func GetDefaults() platformDefaultParameters { // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", + // TUN/TAP MaximumIfMTU: 9000, DefaultIfMTU: 9000, diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index 7bfd30e6..cd6d202a 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -9,6 +9,9 @@ func GetDefaults() platformDefaultParameters { // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", + // TUN/TAP MaximumIfMTU: 16384, DefaultIfMTU: 16384, diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index d780872b..a01ab7af 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -9,6 +9,9 @@ func GetDefaults() platformDefaultParameters { // Admin DefaultAdminListen: "tcp://localhost:9001", + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index 83877d62..3b04783a 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -9,6 +9,9 @@ func GetDefaults() platformDefaultParameters { // Admin DefaultAdminListen: "tcp://localhost:9001", + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf", + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, From cd11d2eccded97cd0454d2d2918a4725d1baae74 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 10:54:41 +0000 Subject: [PATCH 137/145] Produce more meaningful logging from yggdrasilctl when things go wrong --- cmd/yggdrasilctl/main.go | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index ab8ee595..5b6784c3 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -1,10 +1,12 @@ package main import ( + "bytes" "encoding/json" "errors" "flag" "fmt" + "log" "net" "net/url" "os" @@ -18,10 +20,15 @@ import ( type admin_info map[string]interface{} func main() { + logbuffer := &bytes.Buffer{} + logger := log.New(logbuffer, "", log.Flags()) + + defaultEndpoint := defaults.GetDefaults().DefaultAdminListen + flag.Usage = func() { - fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n", os.Args[0]) + fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n", os.Args[0]) fmt.Println("Options:") - flag.PrintDefaults() + flag.PrintDefaults() fmt.Println("Commands:\n - Use \"list\" for a list of available commands") fmt.Println("Examples:") fmt.Println(" - ", os.Args[0], "list") @@ -30,7 +37,7 @@ func main() { fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT") } - server := flag.String("endpoint", defaults.GetDefaults().DefaultAdminListen, "Admin socket endpoint") + server := flag.String("endpoint", defaultEndpoint, "Admin socket endpoint") injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)") verbose := flag.Bool("v", false, "Verbose output (includes public keys)") flag.Parse() @@ -46,18 +53,24 @@ func main() { if err == nil { switch strings.ToLower(u.Scheme) { case "unix": + logger.Println("Connecting to UNIX socket", (*server)[7:]) conn, err = net.Dial("unix", (*server)[7:]) case "tcp": + logger.Println("Connecting to TCP socket", u.Host) conn, err = net.Dial("tcp", u.Host) default: + logger.Println("Unknown protocol", u.Scheme, "- please check your endpoint") err = errors.New("protocol not supported") } } else { + logger.Println("Connecting to TCP socket", u.Host) conn, err = net.Dial("tcp", *server) } if err != nil { + fmt.Print(logbuffer) panic(err) } + logger.Println("Connected") defer conn.Close() decoder := json.NewDecoder(conn) @@ -67,11 +80,13 @@ func main() { for c, a := range args { if c == 0 { + logger.Printf("Sending request: %v\n", a) send["request"] = a continue } tokens := strings.Split(a, "=") if i, err := strconv.Atoi(tokens[1]); err == nil { + logger.Printf("Sending parameter %s: %d\n", tokens[0], i) send[tokens[0]] = i } else { switch strings.ToLower(tokens[1]) { @@ -82,28 +97,32 @@ func main() { default: send[tokens[0]] = tokens[1] } + logger.Printf("Sending parameter %s: %v\n", tokens[0], send[tokens[0]]) } } if err := encoder.Encode(&send); err != nil { + fmt.Print(logbuffer) panic(err) } + logger.Printf("Request sent") if err := decoder.Decode(&recv); err == nil { + logger.Printf("Response received") if recv["status"] == "error" { if err, ok := recv["error"]; ok { - fmt.Println("Error:", err) + fmt.Println("Admin socket returned an error:", err) } else { - fmt.Println("Unspecified error occured") + fmt.Println("Admin socket returned an error but didn't specify any error text") } os.Exit(1) } if _, ok := recv["request"]; !ok { fmt.Println("Missing request in response (malformed response?)") - return + os.Exit(1) } if _, ok := recv["response"]; !ok { fmt.Println("Missing response body (malformed response?)") - return + os.Exit(1) } req := recv["request"].(map[string]interface{}) res := recv["response"].(map[string]interface{}) @@ -243,7 +262,7 @@ func main() { queuesize := v.(map[string]interface{})["queue_size"].(float64) queuepackets := v.(map[string]interface{})["queue_packets"].(float64) queueid := v.(map[string]interface{})["queue_id"].(string) - portqueues[queueport] += 1 + portqueues[queueport]++ portqueuesize[queueport] += queuesize portqueuepackets[queueport] += queuepackets queuesizepercent := (100 / maximumqueuesize) * queuesize @@ -331,9 +350,11 @@ func main() { fmt.Println(string(json)) } } + } else { + logger.Println("Error receiving response:", err) } - if v, ok := recv["status"]; ok && v == "error" { + if v, ok := recv["status"]; ok && v != "success" { os.Exit(1) } os.Exit(0) From d29b5a074a7cba359e9478fba0d40490c457fa31 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 11:09:10 +0000 Subject: [PATCH 138/145] Try to use default config file location to find AdminListen when yggdrasilctl is not given an endpoint --- cmd/yggdrasilctl/main.go | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 5b6784c3..1fdf1bcd 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -6,6 +6,7 @@ import ( "errors" "flag" "fmt" + "io/ioutil" "log" "net" "net/url" @@ -14,6 +15,9 @@ import ( "strconv" "strings" + "golang.org/x/text/encoding/unicode" + + "github.com/neilalexander/hjson-go" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) @@ -48,6 +52,37 @@ func main() { return } + if *server == defaultEndpoint { + if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { + if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 || + bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 { + utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) + decoder := utf.NewDecoder() + config, err = decoder.Bytes(config) + if err != nil { + panic(err) + } + } + var dat map[string]interface{} + if err := hjson.Unmarshal(config, &dat); err != nil { + panic(err) + } + if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") { + defaultEndpoint = ep + logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile) + logger.Println("Using endpoint", defaultEndpoint, "from AdminListen") + } else { + logger.Println("Configuration file doesn't contain appropriate AdminListen option") + logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) + } + } else { + logger.Println("Can't open config file from default location", defaults.GetDefaults().DefaultConfigFile) + logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) + } + } else { + logger.Println("Using endpoint", *server, "from command line") + } + var conn net.Conn u, err := url.Parse(*server) if err == nil { @@ -59,7 +94,7 @@ func main() { logger.Println("Connecting to TCP socket", u.Host) conn, err = net.Dial("tcp", u.Host) default: - logger.Println("Unknown protocol", u.Scheme, "- please check your endpoint") + logger.Println("Unknown protocol or malformed address - check your endpoint") err = errors.New("protocol not supported") } } else { From b4b3609678942f0fac200595b66c440295a6f472 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 11:12:40 +0000 Subject: [PATCH 139/145] Really use the correct endpoint --- cmd/yggdrasilctl/main.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 1fdf1bcd..9646f6c1 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -27,7 +27,7 @@ func main() { logbuffer := &bytes.Buffer{} logger := log.New(logbuffer, "", log.Flags()) - defaultEndpoint := defaults.GetDefaults().DefaultAdminListen + endpoint := defaults.GetDefaults().DefaultAdminListen flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n", os.Args[0]) @@ -41,7 +41,7 @@ func main() { fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT") fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT") } - server := flag.String("endpoint", defaultEndpoint, "Admin socket endpoint") + server := flag.String("endpoint", endpoint, "Admin socket endpoint") injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)") verbose := flag.Bool("v", false, "Verbose output (includes public keys)") flag.Parse() @@ -52,7 +52,7 @@ func main() { return } - if *server == defaultEndpoint { + if *server == endpoint { if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 || bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 { @@ -68,9 +68,9 @@ func main() { panic(err) } if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") { - defaultEndpoint = ep + endpoint = ep logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile) - logger.Println("Using endpoint", defaultEndpoint, "from AdminListen") + logger.Println("Using endpoint", endpoint, "from AdminListen") } else { logger.Println("Configuration file doesn't contain appropriate AdminListen option") logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) @@ -80,16 +80,16 @@ func main() { logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) } } else { - logger.Println("Using endpoint", *server, "from command line") + logger.Println("Using endpoint", endpoint, "from command line") } var conn net.Conn - u, err := url.Parse(*server) + u, err := url.Parse(endpoint) if err == nil { switch strings.ToLower(u.Scheme) { case "unix": - logger.Println("Connecting to UNIX socket", (*server)[7:]) - conn, err = net.Dial("unix", (*server)[7:]) + logger.Println("Connecting to UNIX socket", endpoint[7:]) + conn, err = net.Dial("unix", endpoint[7:]) case "tcp": logger.Println("Connecting to TCP socket", u.Host) conn, err = net.Dial("tcp", u.Host) @@ -99,7 +99,7 @@ func main() { } } else { logger.Println("Connecting to TCP socket", u.Host) - conn, err = net.Dial("tcp", *server) + conn, err = net.Dial("tcp", endpoint) } if err != nil { fmt.Print(logbuffer) From c78e1b98cccdfe1e83dddabdc95b849d2ef7aac2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 11:29:42 +0000 Subject: [PATCH 140/145] Show yggdrasilctl log buffer on panic --- cmd/yggdrasilctl/main.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 9646f6c1..b37c2561 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -26,6 +26,13 @@ type admin_info map[string]interface{} func main() { logbuffer := &bytes.Buffer{} logger := log.New(logbuffer, "", log.Flags()) + defer func() { + if r := recover(); r != nil { + logger.Println("Fatal error:", r) + fmt.Print(logbuffer) + os.Exit(1) + } + }() endpoint := defaults.GetDefaults().DefaultAdminListen @@ -102,7 +109,6 @@ func main() { conn, err = net.Dial("tcp", endpoint) } if err != nil { - fmt.Print(logbuffer) panic(err) } logger.Println("Connected") @@ -137,7 +143,6 @@ func main() { } if err := encoder.Encode(&send); err != nil { - fmt.Print(logbuffer) panic(err) } logger.Printf("Request sent") From 3eb1a40c684b32f07ef485b44723323c5db6c73f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 11:34:37 +0000 Subject: [PATCH 141/145] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da8c7022..2db4dde1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add `-json` command line flag for generating and normalising configuration in plain JSON instead of HJSON - Build name and version numbers are now imprinted onto the build, accessible through `yggdrasil -version` and `yggdrasilctl getSelf` - Add ability to disable admin socket by setting `AdminListen` to `"none"` +- `yggdrasilctl` now tries to look for the default configuration file to find `AdminListen` if `-endpoint` is not specified +- `yggdrasilctl` now returns more useful logging in the event of a fatal error ### Changed - Switched to Chord DHT (instead of Kademlia, although still compatible at the protocol level) From f09adc21926cef760b98ac0ebbf6633b201aa038 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 22:04:37 +0000 Subject: [PATCH 142/145] Update getRoutes format --- src/yggdrasil/admin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index dea4577a..253a9bfb 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -268,11 +268,11 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{"source_subnets": subnets}, nil }) a.addHandler("getRoutes", []string{}, func(in admin_info) (admin_info, error) { - var routes []string + routes := make(admin_info) a.core.router.doAdmin(func() { getRoutes := func(ckrs []cryptokey_route) { for _, ckr := range ckrs { - routes = append(routes, fmt.Sprintf("%s via %s", ckr.subnet.String(), hex.EncodeToString(ckr.destination[:]))) + routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) } } getRoutes(a.core.router.cryptokey.ipv4routes) From 65e34bbbab68811b362e6176b79e2409afde2a4d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 22:19:08 +0000 Subject: [PATCH 143/145] Enforce maximum CKR routing cache size --- src/yggdrasil/ckr.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index b73946c4..2324f612 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -241,6 +241,16 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey for _, route := range *routingtable { // Does this subnet match the given IP? if route.subnet.Contains(ip) { + // Check if the routing cache is above a certain size, if it is evict + // a random entry so we can make room for this one. We take advantage + // of the fact that the iteration order is random here + if len(*routingcache) > 1024 { + for k := range *routingcache { + delete(*routingcache, k) + break + } + } + // Cache the entry for future packets to get a faster lookup (*routingcache)[addr] = route From 90ace46587f7f037df6f1698047886737cfd1e21 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 10 Dec 2018 22:30:31 +0000 Subject: [PATCH 144/145] Enforce CKR cache size more strongly --- src/yggdrasil/ckr.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 2324f612..d88b8d3c 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -244,11 +244,11 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey // Check if the routing cache is above a certain size, if it is evict // a random entry so we can make room for this one. We take advantage // of the fact that the iteration order is random here - if len(*routingcache) > 1024 { - for k := range *routingcache { - delete(*routingcache, k) + for k := range *routingcache { + if len(*routingcache) < 1024 { break } + delete(*routingcache, k) } // Cache the entry for future packets to get a faster lookup From 871d6119ec58de12c2b05ec8b71748fb5ff8dbca Mon Sep 17 00:00:00 2001 From: Arceliar Date: Wed, 12 Dec 2018 01:47:31 -0600 Subject: [PATCH 145/145] minor whitepaper updates for v0.3 --- doc/README.md | 188 ---------------------------------------------- doc/Whitepaper.md | 28 +++---- 2 files changed, 15 insertions(+), 201 deletions(-) delete mode 100644 doc/README.md diff --git a/doc/README.md b/doc/README.md deleted file mode 100644 index 4310c5be..00000000 --- a/doc/README.md +++ /dev/null @@ -1,188 +0,0 @@ -# Yggdrasil-go - -## What is it? - -This is a toy implementation of an encrypted IPv6 network. -A number of years ago, I started to spend some of my free time studying and routing schemes, and eventually decided that it made sense to come up with my own. -After much time spent reflecting on the problem, and a few failed starts, I eventually cobbled together one that seemed to have, more or less, the performance characteristics I was looking for. -I resolved to eventually write a proof-of-principle / test implementation, and I thought it would make sense to include many of the nice bells and whistles that I've grown accustomed to from using [cjdns](https://github.com/cjdelisle/cjdns), plus a few additional features that I wanted to test. -Fast forward through a couple years of procrastination, and I've finally started working on it in my limited spare time. -I've found that it's now marginally more interesting than embarrassing, so here it is. - -The routing scheme was designed for scalable name-independent routing on graphs with an internet-like topology. -By internet-like, I mean that the network has a densely connected core with many triangles, a diameter that increases slowly with network size, and where any sparse edges tend to be relatively tree-like, all of which appear to be common features of large graphs describing "organically" grown relationships. -By scalable name-independent routing, I mean: - -1. Scalable: resource consumption should grow slowly with the size of the network. -In particular, for internet-like networks, the goal is to use only a (poly)logarithmic amount of memory, use a logarithmic amount of bandwidth per one-hop neighbor for control traffic, and to maintain low average multiplicative path stretch (introducing overhead of perhaps a few percent) that does not become worse as the network grows. - -2. Name-independent: a node's identifier should be independent of network topology and state, such that a node may freely change their identifier in a static network, or keep it static under state changes in a dynamic network. -In particular, addresses are self-assigned and derived from a public key, which circumvents the use of a centralized addressing authority or public key infrastructure. - -Running this code will: - -1. Set up a `tun` device and assign it a Unique Local Address (ULA) in `fd00::/8`. -2. Connect to other nodes running the software. -3. Route traffic for and through other nodes. - -A device's ULA is actually from `fd00::/9`, and a matching `/64` prefix is available under `fd80::/9`. This allows the node to advertise a route on its LAN, as a workaround for unsupported devices. - -## Building - -1. Install Go (tested on 1.9, I use [godeb](https://github.com/niemeyer/godeb)). -2. Clone this repository. -2. `./build` - -It's written in Go because I felt like learning a new language, and Go seemed like an easy language to learn while still being a reasonable choice for language to prototype network code. -Note that the build script defines its own `$GOPATH`, so the build and its dependencies should be self contained. -It only works on Linux at this time, because a little code (related to the `tun` device) is platform dependent, and changing that hasn't been a high priority. - -## Running - -To run the program, you'll need permission to create a `tun` device and configure it using `ip`. -If you don't want to mess with capabilities for the `tun` device, then using `sudo` should work, with the usual security caveats about running a program as root. - -To run with default settings: - -1. `./yggdrasil --autoconf` - -That will generate a new set of keys (and an IP address) each time the program is run. -The program will bind to all addresses on a random port and listen for incoming connections. -It will send announcements over IPv6 link-local multicast, and attempt to start a connection if it hears an announcement from another device. - -In practice, you probably want to run this instead: - -1. `./yggdrasil --genconf > conf.json` -2. `./yggdrasil --useconf < conf.json` - -The first step generates a configuration file with a set of cryptographic keys and default settings. -The second step runs the program using the configuration provided in that file. -Because ULAs are derived from keys, using a fixed set of keys causes a node to keep the same address each time the program is run. - -If you want to use it as an overlay network on top of e.g. the internet, then you can do so by adding the address and port of the device you want to connect to (as a string, e.g. `"1.2.3.4:5678"`) to the list of `Peers` in the configuration file. -This should accept IPv4 and IPv6 addresses, and I think it should resolve host/domain names, but I haven't really tested that, so your mileage may vary. -You can also configure which address and/or port to listen on by editing the configuration file, in case you want to bind to a specific address or listen for incoming connections on a fixed port. - -Also note that the nodes is connected to the network through a `tun` device, so it follows point-to-point semantics. -This means it's limited to routing traffic with source and destination addresses in `fd00::/8`--you can't add a prefix to your routing table "via" an address in that range, as the router has no idea who you meant to send it to. -In particular, this means you can't set a working default route that *directly* uses the overlay network, but I've had success *indirectly* using it to connect to an off-the-shelf VPN that I can use as a default route for internet access. - -## Optional: advertise a prefix locally - -Suppose a node has been given the address: `fd00:1111:2222:3333:4444:5555:6666:7777` - -Then the node may also use addresses from the prefix: `fd80:1111:2222:3333::/64` (note the `fd00` -> `fd80`, a separate `/9` is used for prefixes). - -To advertise this prefix and a route to `fd00::/8`, the following seems to work for me: - -1. Enable IPv6 forwarding (e.g. `sysctl -w net.ipv6.conf.all.forwarding=1` or add it to sysctl.conf). - -2. `ip addr add fd80:1111:2222:3333::1/64 dev eth0` or similar, to assign an address for the router to use in that prefix, where the LAN is reachable through `eth0`. - -3. Install/run `radvd` with something like the following in `/etc/radvd.conf`: -``` -interface eth0 -{ - AdvSendAdvert on; - prefix fd80:1111:2222:3333::/64 { - AdvOnLink on; - AdvAutonomous on; - }; - route fd00::/8 {}; -}; -``` - -Now any IPv6-enabled device in the LAN can use stateless address auto-configuration to assign itself a working `fd00::/8` address from the `/64` prefix, and communicate with the wider network through the router, without requiring any special configuration for each device. -I've used this to e.g. get my phone on the network. -Note that there are a some differences when accessing the network this way: - -1. There are 64 fewer bits of address space available for self-certifying addresses. -This means that it is 64 bits easier to brute force a prefix collision than collision for a full node's IP address. As such, you may want to change addresses frequently, or else brute force an address with more security bits (see: `misc/genkeys.go`). - -2. The LAN depends on the router for cryptography. -So while traffic going through the WAN is encrypted, the LAN is still just a LAN. You may want to secure your network. - -3. Related to the above, the cryptography and I/O through the `tun` device both place additional load on the router, above what is normally present from forwarding packets between full nodes in the network, so the router may need more computing power to reach line rate. - -## How does it work? - -Consider the internet, which uses a network-of-networks model with address aggregation. -Addresses are allocated by a central authority, as blocks of contiguous addresses with a matching prefix. -Within a network, each node may represent one or more prefixes, with each prefix representing a network of one or more nodes. -On the largest scale, BGP is used to route traffic between networks (autonomous systems), and other protocols can be used to route within a network. -The effectiveness of such hierarchical addressing and routing strategies depend on network topology, with the internet's observed topology being the worst case of all known topologies from a scalability standpoint (see [arxiv:0708.2309](https://arxiv.org/abs/0708.2309) for a better explanation of the issue, but the problem is essentially that address aggregation is ineffective in a network with a large number of nodes and a small diameter). - -The routing scheme implemented by this code tries a different approach. -Instead of using assigned addresses and a routing table based on prefixes and address aggregation, routing and addressing are handled through a combination of: - -1. Self-assigned cryptographically generated addresses, to handle address allocation without a central authority. -2. A kademlia-like distributed hash table, to look up a node's (name-dependent) routing information from their (name-independent routing) IP address. -3. A name-dependent routing scheme based on greedy routing in a metric space, constructed from an arbitrarily rooted spanning tree, which gives a reasonable approximation of the true distance between nodes for certain network topologies (namely the scale-free topology that seems to emerge in many large graphs, including the internet). The spanning tree embedding takes stability into account when selecting which one-hop neighbor to use as a parent, and path selection uses (poorly) estimated available bandwidth as a criteria, subject to the constraint that metric space distances must decrease with each hop. Incidentally, the name `yggdrasil` was selected for this test code because that's obviously what you call an immense tree that connects worlds. - -The network then presents itself as having a single "flat" address with no aggregation. -Under the hood, it runs as an overlay on top of existing IP networks. -Link-local IPv6 multicast traffic is used to advertise on the underlying networks, which can as easily be a wired or wireless LAN, a direct (e.g. ethernet) connection between two devices, a wireless ad-hoc network, etc. -Additional connections can be added manually to peer over networks where link-local multicast is insufficient, which allows you to e.g. use the internet to bridge local networks. - -The name-dependent routing layer uses cryptographically signed (`Ed25519`) path-vector-like routing messages, similar to S-BGP, which should prevent route poisoning and related attacks. -For encryption, it uses the Go implementation of the `nacl/box` scheme, which is built from a Curve25519 key exchange with XSalsa20 as a stream cypher and Poly1305 for integrity and authentication. -Permanent keys are used for protocol traffic, including the ephemeral key exchange, and a hash of a node's permanent public key is used to construct a node's address. -Ephemeral keys are used for encapsulated IP(v6) traffic, which provides forward secrecy. -Go's `crypto/rand` library is used for nonce generation. -In short, I've tried to not make this a complete security disaster, but the code hasn't been independently audited and I'm nothing close to a security expert, so it should be considered a proof-of-principle rather than a safe implementation. -At a minimum, I know of no way to prevent gray hole attacks. - -I realize that this is a terribly short description of how it works, so I may elaborate further in another document if the need arises. -Otherwise, I guess you could try to read my terrible and poorly documented code if you want to know more. - -## Related work - -A lot of inspiration comes from [cjdns](https://github.com/cjdelisle/cjdns). -I'm a contributor to that project, and I wanted to test out some ideas that weren't convenient to prototype in the existing code base, which is why I wrote this toy. - -On the routing side, a lot of influence came from compact routing. -A number of compact routing schemes are evaluated in [arxiv:0708.2309](https://arxiv.org/abs/0708.2309) and may be used as a basis for comparison. -When tested in a simplified simulation environment on CAIDA's 9204-node "skitter" network graph used in that paper, I observed an average multiplicative stretch of about 1.08 with my routing scheme, as implemented here. -This can be lowered to less than 1.02 using a source-routed version of the algorithm and including node degree as an additional parameter of the embedding, which is of academic interest, but degree's unverifiability makes it impractical for this implementation. -In either case, this only requires 1 routing table entry per one-hop neighbor (this averages ~6 for in the skitter network graph), plus a logarithmic number of DHT entries (expected to be ~26, based on extrapolations from networks with a few hundred nodes--running the full implementation on the skitter graph is impractical on my machine). -I don't think stretch is really an appropriate metric, as it doesn't consider the difference to total network cost from a high-stretch short path vs a high-stretch long path. -In this scheme, and I believe in most compact routing schemes, longer paths tend to have lower multiplicative stretch, and shorter paths are more likely to have longer stretch. -I would argue that this is preferable to the alternative. - -While I use a slightly different approach, the idea to try a greedy routing scheme was inspired by the use of greedy routing on networks embedded in the hyperbolic plane (such as [Kleinberg's work](https://doi.org/10.1109%2FINFCOM.2007.221) and [Greedy Forwarding on the NDN Testbed](https://www.caida.org/research/routing/greedy_forwarding_ndn/)). -I use distance on a spanning tree as the metric, as seems to work well on the types of networks I'm concerned with, and it simplifies other aspects of the implementation. -The hyperbolic embedding algorithms I'm aware of, or specifically the distributed ones, operate by constructing a spanning tree of the network and then embedding the tree. -So I don't see much harm, at present, of skipping the hyperbolic plane and directly using the tree for the metric space. - -## Misc. notes - -This is a toy experiment / proof-of-concept. -It's only meant to test if / how well some ideas work. -I have no idea what I'm doing, so for all I know it's entirely possible that it could crash your computer, eat your homework, or set fire to your house. -Some parts are also written to be as bad as I could make them while still being technically correct, in an effort to make bugs obvious if they occur, which means that even when it does work it may be fragile and error prone. - -In particular, you should expect it to perform poorly under mobility events, and to converge slowly in dynamic networks. All else being equal, this implementation should tend to prefer long-lived links over short-lived ones when embedding, and (poorly estimated) high bandwidth links over low bandwidth ones when forwarding traffic. As such, in multi-homed or mobile scenarios, there may be some tendency for it to make decisions you disagree with. - -While stretch is low on internet-like graphs, the best upper bound I've established on the *additive* stretch of this scheme, after convergence, is the same as for tree routing: proportional to network diameter. For sparse graphs with a large diameter, the scheme may not find particularly efficient paths, even under ideal circumstances. I would argue that such networks tend not to grow large enough for scalability to be an issue, so another routing scheme is better suited to those networks. - -Regarding the announce-able prefix thing, what I wanted to do is use `fc00::/7`, where `fc00::/8` is for nodes and `fd00::/8` is for prefixes. -I would also possibly widen the prefixes to `/48`, to match [rfc4193](https://tools.ietf.org/html/rfc4193), and possibly provide an option to keep using a `/64` by splitting it into two `/9` blocks (where `/64` prefixes would continue to live in `fd80::/9`), or else convince myself that the security implications of another 16 bits don't matter (to avoid the complexity of splitting it into two `/9` ranges for prefixes). -Using `fc00::/8` this way would cause issues if trying to also run cjdns. -Since I like cjdns, and want the option of running it on the same nodes, I've decided not to do that. -If I ever give up on avoiding cjdns conflicts, then I may change the addressing scheme to match the above. - -Despite the tree being constructed from path-vector-like routing messages, there's no support for routing policy right now. -As a result, peer relationships are bimodal: either you're not connected to someone, or you're connected and you'll route traffic *to* and *through* them. -Nodes also accept all incoming connections, so if you want to limit who can connect then you'll need to provide some other kind of access controls. - -The current implementation does all of its setup when the program starts, and then nothing can be reconfigured without restarting the program. -At some point I may add a remote API, so a running node can be reconfigured (to e.g. add/remove peers) without restarting, or probe the internal state of the router to get useful debugging info. -So far, things seem to work the way I want/expect without much trouble, so I haven't felt the need to do this yet. - -Some parts of the implementation can take advantage of multiple cores, but other parts that could simply do not. -Some parts are fast, but other parts are slower than they have any right to be, e.g. I can't figure out why some syscalls are as expensive as they are, so the `tun` in particular tends to be a CPU bottleneck (multi-queue could help in some cases, but that just spreads the cost around, and it doesn't help with single streams of traffic). -The Go runtime's GC tends to have short pauses, but it does have pauses. -So even if the ideas that went into this routing scheme turn out to be useful, this implementation is likely to remain mediocre at best for the foreseeable future. -If the is thing works well and the protocol stabilizes, then it's worth considering re-implementation and/or a formal spec and RFC. -In such a case, it's entirely reasonable to change parts of the spec purely to make the efficient implementation easier (e.g. it makes sense to want zero-copy networking, but a couple parts of the current protocol might make that impractical). - diff --git a/doc/Whitepaper.md b/doc/Whitepaper.md index fca9ffc2..674f6dc0 100644 --- a/doc/Whitepaper.md +++ b/doc/Whitepaper.md @@ -87,14 +87,17 @@ These signatures prevent nodes from forging arbitrary routing advertisements. The first hop, from the root, also includes a sequence number, which must be updated periodically. A node will blacklist the current root (keeping a record of the last sequence number observed) if the root fails to update for longer than some timeout (currently hard coded at 1 minute). Normally, a root node will update their sequence number for frequently than this (once every 30 seconds). -Nodes are throttled to ignore updates with a new sequence number for some period after updating their most recently seen sequence number (currently this cooldown is 10 seconds). +Nodes are throttled to ignore updates with a new sequence number for some period after updating their most recently seen sequence number (currently this cooldown is 15 seconds). The implementation chooses to set the sequence number equal to the unix time on the root's clock, so that a new (higher) sequence number will be selected if the root is restarted and the clock is not set back. Other than the root node, every other node in the network must select one of its neighbors to use as their parent. -This selection is done by maximizing: ` / `. -Here, `uptime` is the time between when we first and last received a message from the node which advertised the node's current location in the tree (resetting to zero if the location changes), and timeout is the time we wait before dropping a root due to inactivity. -This essentially means the numerator is at least as long as the amount of time between when the neighbor was first seen at its present location, and when the advertisement from the neighbor becomes invalid due to root timeout. -Resetting the uptime with each coordinate change causes nodes to favor long-lived stable paths over short-lived unstable ones, for the purposes of tree construction (indirectly impacting route selection). +This selection is done by tracking when each neighbor first sends us a message with a new timestamp from the root, to determine the ordering of the latency of each path from the root, to each neighbor, and then to the node that's searching for a parent. +These relative latencies are tracked by, for each neighbor, keeping a score vs each other neighbor. +If a neighbor sends a message with an updated timestamp before another neighbor, then the faster neighbor's score is increased by 1. +If the neighbor sends a message slower, then the score is decreased by 2, to make sure that a node must be reliably faster (at least 2/3 of the time) to see a net score increase over time. +If a node begins to advertise new coordinates, then its score vs all other nodes is reset to 0. +A node switches to a new parent if a neighbor's score (vs the current parent) reaches some threshold, currently 240, which corresponds to about 2 hours of being a reliably faster path. +The intended outcome of this process is that stable connections from fixed infrastructure near the "core" of the network should (eventually) select parents that minimize latency from the root to themselves, while the more dynamic parts of the network, presumably more towards the edges, will try to favor reliability when selecting a parent. The distance metric between nodes is simply the distance between the nodes if they routed on the spanning tree. This is equal to the sum of the distance from each node to the last common ancestor of the two nodes being compared. @@ -103,15 +106,14 @@ In practice, only the coords are used for routing, while the root and timestamp, ## Name-independent routing -A [Kademlia](https://en.wikipedia.org/wiki/Kademlia)-like Distributed Hash Table (DHT) is used as a distributed database that maps NodeIDs onto coordinates in the spanning tree metric space. -The DHT is Kademlia-like in that it uses the `xor` metric and structures the hash table into k-buckets (with 2 nodes per bucket in the normal case, plus some additional slots for keyspace neighbors and one-hop neighbors at the router level). -It differs from kademlia in that there are no values in the key:value store -- it only stores information about DHT peers. +A [Chord](https://en.wikipedia.org/wiki/Chord_(peer-to-peer))-like Distributed Hash Table (DHT) is used as a distributed database that maps NodeIDs onto coordinates in the spanning tree metric space. +The DHT is Chord-like in that it uses a successor/predecessor structure to do lookups in `O(n)` time with `O(1)` entries, then augments this with some additional information, adding roughly `O(logn)` additional entries, to reduce the lookup time to something around `O(logn)`. +In the long term, the idea is to favor spending our bandwidth making sure the minimum `O(1)` part is right, to prioritize correctness, and then try to conserve bandwidth (and power) by being a bit lazy about checking the remaining `O(logn)` portion when it's not in use. -The main complication is that, when the DHT is bootstrapped off of a node's one-hop neighbors, with no special measures taken about which nodes are included in each bucket, then the network may diverge (settle into a stable bad state, where at least some lookups will always fail). -The current strategy is to place additional preferences on which nodes are kept in each bucket -- in particular, we try to keep the closest nodes in xor space in each bucket. -This seems to mitigate the issue in some quick tests, but it's a topic that could use additional study. - -Other than these differences, the DHT is more-or-less what you might expect from a kad implementation. +To be specific, the DHT stores the immediate successor of a node, plus the next node it manages to find which is strictly closer (by the tree hop-count metric) than all previous nodes. +The same process is repeated for predecessor nodes, and lookups walk the network in the predecessor direction, with each key being owned by its successor (to make sure defaulting to 0 for unknown bits of a `NodeID` doesn't cause us to overshoot the target during a lookup). +In addition, all of a node's one-hop neighbors are included in the DHT, since we get this information "for free", and we must include it in our DHT to ensure that the network doesn't diverge to a broken state (though I suspect that only adding parents or parent-child relationships may be sufficient -- worth trying to prove or disprove, if somebody's bored). +The DHT differs from Chord in that there are no values in the key:value store -- it only stores information about DHT peers -- and that it uses a [Kademlia](https://en.wikipedia.org/wiki/Kademlia)-inspired iterative-parallel lookup process. To summarize the entire routing procedure, when given only a node's IP address, the goal is to find a route to the destination. That happens through 3 steps: