diff --git a/.circleci/config.yml b/.circleci/config.yml index fa5ebcac..f3092ba1 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 @@ -17,7 +15,9 @@ 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)"; - run: name: Install alien @@ -98,3 +98,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/CHANGELOG.md b/CHANGELOG.md index 4c72c27a..2db4dde1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,33 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.0] - 2018-12-12 +### Added +- 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 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"` +- `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) +- 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 +- 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 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 - Session firewall, which makes it possible to control who can open sessions with your node 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/VERSION b/VERSION deleted file mode 100644 index 3b04cfb6..00000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.2 diff --git a/build b/build index c07e5eac..e463c852 100755 --- a/build +++ b/build @@ -1,25 +1,36 @@ #!/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)} + +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 -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 + +if [ -z $TABLES ]; then + STRIP="-s -w" +fi + +for CMD in `ls cmd/` ; do + echo "Building: $CMD" + if [ $DEBUG ]; then - go build -tags debug -v $file + go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD else - go build -ldflags="-s -w" -v $file + go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -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 96% rename from yggdrasil.go rename to cmd/yggdrasil/main.go index 5244a8ec..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 @@ -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. diff --git a/yggdrasilctl.go b/cmd/yggdrasilctl/main.go similarity index 70% rename from yggdrasilctl.go rename to cmd/yggdrasilctl/main.go index 79b5f86d..b37c2561 100644 --- a/yggdrasilctl.go +++ b/cmd/yggdrasilctl/main.go @@ -1,53 +1,117 @@ 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 ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "net" + "net/url" + "os" + "sort" + "strconv" + "strings" -import "yggdrasil/defaults" + "golang.org/x/text/encoding/unicode" + + "github.com/neilalexander/hjson-go" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" +) 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") + 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 + + 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", 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() 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 } + 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 { + 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 != "") { + endpoint = ep + logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile) + 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) + } + } 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", 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": - 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) default: + logger.Println("Unknown protocol or malformed address - check your endpoint") err = errors.New("protocol not supported") } } else { - conn, err = net.Dial("tcp", *server) + logger.Println("Connecting to TCP socket", u.Host) + conn, err = net.Dial("tcp", endpoint) } if err != nil { panic(err) } + logger.Println("Connected") defer conn.Close() decoder := json.NewDecoder(conn) @@ -57,11 +121,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]) { @@ -72,28 +138,31 @@ 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 { 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{}) @@ -181,6 +250,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 && buildname != "unknown" { + fmt.Println("Build name:", buildname) + } + if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok && buildversion != "unknown" { + 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) @@ -227,7 +302,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 @@ -315,9 +390,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) 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/contrib/deb/generate.sh b/contrib/deb/generate.sh index 9d5064bd..5af31d5c 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -7,12 +7,12 @@ 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`) 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 @@ -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/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 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 )) 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 diff --git a/contrib/semver/name.sh b/contrib/semver/name.sh index d749d3ff..935cc750 100644 --- a/contrib/semver/name.sh +++ b/contrib/semver/name.sh @@ -1,7 +1,16 @@ #!/bin/sh -# Get the branch name, removing any "/" characters from pull requests -BRANCH=$(git symbolic-ref --short HEAD | tr -d "/" 2>/dev/null) +# 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" ]; then diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh index bd009a9a..331046f3 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) @@ -12,12 +12,25 @@ 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) - printf 'v0.0.%d' "$PATCH" - exit -1 + # Complain if the git history is not available + if [ $? != 0 ]; then + printf 'unknown' + exit 1 + fi + + printf '%s0.0.%d' "$PREPEND" "$PATCH" + exit 1 fi # Get the number of merges on the current branch since the last tag @@ -32,9 +45,13 @@ BRANCH=$(git rev-parse --abbrev-ref HEAD) # Output in the desired format if [ $PATCH = 0 ]; then - printf 'v%d.%d' "$MAJOR" "$MINOR" + if [ ! -z $FULL ]; then + printf '%s%d.%d.0' "$PREPEND" "$MAJOR" "$MINOR" + else + 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 @@ -43,4 +60,3 @@ if [ $BRANCH != "master" ]; then printf -- "-%04d" "$BUILD" fi fi - diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..db6d359a --- /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 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 + 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..7d064112 --- /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 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= +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 96% rename from src/yggdrasil/config/config.go rename to src/config/config.go index 904907b2..9671eca3 100644 --- a/src/yggdrasil/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/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 85% rename from src/yggdrasil/defaults/defaults.go rename to src/defaults/defaults.go index 753efc53..3834990e 100644 --- a/src/yggdrasil/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/yggdrasil/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go similarity index 72% rename from src/yggdrasil/defaults/defaults_darwin.go rename to src/defaults/defaults_darwin.go index 778162c0..9bac3aad 100644 --- a/src/yggdrasil/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -7,7 +7,10 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", // TUN/TAP MaximumIfMTU: 65535, diff --git a/src/yggdrasil/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go similarity index 72% rename from src/yggdrasil/defaults/defaults_freebsd.go rename to src/defaults/defaults_freebsd.go index 7c5c7752..df1a3c65 100644 --- a/src/yggdrasil/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -7,7 +7,10 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", // TUN/TAP MaximumIfMTU: 32767, diff --git a/src/yggdrasil/defaults/defaults_linux.go b/src/defaults/defaults_linux.go similarity index 72% rename from src/yggdrasil/defaults/defaults_linux.go rename to src/defaults/defaults_linux.go index 85287eeb..2f3459ca 100644 --- a/src/yggdrasil/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -7,7 +7,10 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", // TUN/TAP MaximumIfMTU: 65535, diff --git a/src/yggdrasil/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go similarity index 72% rename from src/yggdrasil/defaults/defaults_netbsd.go rename to src/defaults/defaults_netbsd.go index 8e8f7b5f..40476dcb 100644 --- a/src/yggdrasil/defaults/defaults_netbsd.go +++ b/src/defaults/defaults_netbsd.go @@ -7,7 +7,10 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", // TUN/TAP MaximumIfMTU: 9000, diff --git a/src/yggdrasil/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go similarity index 72% rename from src/yggdrasil/defaults/defaults_openbsd.go rename to src/defaults/defaults_openbsd.go index 8b3e2bbc..cd6d202a 100644 --- a/src/yggdrasil/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -7,7 +7,10 @@ package defaults func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + + // Configuration (used for yggdrasilctl) + DefaultConfigFile: "/etc/yggdrasil.conf", // TUN/TAP MaximumIfMTU: 16384, diff --git a/src/yggdrasil/defaults/defaults_other.go b/src/defaults/defaults_other.go similarity index 84% rename from src/yggdrasil/defaults/defaults_other.go rename to src/defaults/defaults_other.go index d780872b..a01ab7af 100644 --- a/src/yggdrasil/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/yggdrasil/defaults/defaults_windows.go b/src/defaults/defaults_windows.go similarity index 78% rename from src/yggdrasil/defaults/defaults_windows.go rename to src/defaults/defaults_windows.go index 83877d62..3b04783a 100644 --- a/src/yggdrasil/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, diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 2b5bc645..266d5a89 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 @@ -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) @@ -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" && a.listenaddr != "" { + go a.listen() + } return nil } @@ -341,7 +343,19 @@ 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 { + 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": a.listener, err = net.Listen("tcp", u.Host) default: @@ -561,6 +575,13 @@ func (a *admin) getData_getSelf() *admin_nodeInfo { {"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 } @@ -737,6 +758,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 { diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index b73946c4..d88b8d3c 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 + for k := range *routingcache { + if len(*routingcache) < 1024 { + break + } + delete(*routingcache, k) + } + // Cache the entry for future packets to get a faster lookup (*routingcache)[addr] = route diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 706b8aa3..5ab91a74 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -8,10 +8,13 @@ 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 +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,12 +62,38 @@ 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 // DHT node. func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.log = log + + if name := GetBuildName(); name != "unknown" { + c.log.Println("Build name:", name) + } + if version := GetBuildVersion(); version != "unknown" { + c.log.Println("Build version:", version) + } + c.log.Println("Starting up...") var boxPub boxPubKey diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go index 91f57708..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() { @@ -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/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..8a4ce56c --- /dev/null +++ b/src/yggdrasil/multicast_other.go @@ -0,0 +1,9 @@ +// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows + +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 + } +} 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 + } +} diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index b04578ca..99ed25b4 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. @@ -377,6 +400,8 @@ 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 doUpdate = true } // Update the matrix of peer "faster" thresholds @@ -387,25 +412,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 +477,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. 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