pax_global_header00006660000000000000000000000064141777640210014523gustar00rootroot0000000000000052 comment=559e31c502674f372375960fa9fd2f9a75bd22e8 yggdrasil-go-0.4.3/000077500000000000000000000000001417776402100141175ustar00rootroot00000000000000yggdrasil-go-0.4.3/.circleci/000077500000000000000000000000001417776402100157525ustar00rootroot00000000000000yggdrasil-go-0.4.3/.circleci/config.yml000066400000000000000000000241771417776402100177550ustar00rootroot00000000000000# Golang CircleCI 2.0 configuration file # # Check https://circleci.com/docs/2.0/language-go/ for more details version: 2.1 jobs: lint: docker: - image: circleci/golang:1.17 steps: - checkout - run: name: Run golangci-lint command: | go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0 golangci-lint run - run: name: Run Go tests command: | go test ./... build-linux: docker: - image: circleci/golang:1.17 steps: - checkout - run: name: Create artifact upload directory and set variables command: | mkdir /tmp/upload echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV echo 'export CIVERSIONRPM=$(sh contrib/semver/version.sh --bare | tr "-" ".")' >> $BASH_ENV echo 'export CIBRANCH=$(echo $CIRCLE_BRANCH | tr -d "/")' >> $BASH_ENV case "$CINAME" in \ "yggdrasil") (echo 'export CICONFLICTS=yggdrasil-develop' >> $BASH_ENV) ;; \ "yggdrasil-develop") (echo 'export CICONFLICTS=yggdrasil' >> $BASH_ENV) ;; \ *) (echo 'export CICONFLICTS="yggdrasil yggdrasil-develop"' >> $BASH_ENV) ;; \ esac 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 RPM utilities command: | sudo apt-get update sudo apt-get install -y rpm file mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS - run: name: Test debug builds command: | ./build -d test -f yggdrasil && test -f yggdrasilctl - run: name: Build for Linux (including Debian packages) command: | rm -f {yggdrasil,yggdrasilctl} PKGARCH=amd64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-amd64; PKGARCH=i386 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-i386; PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel; PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips; PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf; PKGARCH=armel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armel; PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64; mv *.deb /tmp/upload/ - run: name: Build for Linux (RPM packages) command: | git clone https://github.com/yggdrasil-network/yggdrasil-package-rpm ~/rpmbuild/SPECS cd ../ && tar -czvf ~/rpmbuild/SOURCES/v$CIVERSIONRPM --transform "s/project/yggdrasil-go-$CIBRANCH-$CIVERSIONRPM/" project sed -i "s/yggdrasil-go/yggdrasil-go-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^PKGNAME=yggdrasil/PKGNAME=yggdrasil-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^Name\:.*/Name\: $CINAME/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^Version\:.*/Version\: $CIVERSIONRPM/" ~/rpmbuild/SPECS/yggdrasil.spec sed -i "s/^Conflicts\:.*/Conflicts\: $CICONFLICTS/" ~/rpmbuild/SPECS/yggdrasil.spec cat ~/rpmbuild/SPECS/yggdrasil.spec GOARCH=amd64 rpmbuild -v --nodeps --target=x86_64 -ba ~/rpmbuild/SPECS/yggdrasil.spec #GOARCH=386 rpmbuild -v --nodeps --target=i386 -bb ~/rpmbuild/SPECS/yggdrasil.spec find ~/rpmbuild/RPMS/ -name '*.rpm' -exec mv {} /tmp/upload \; find ~/rpmbuild/SRPMS/ -name '*.rpm' -exec mv {} /tmp/upload \; - run: name: Build for EdgeRouter and VyOS command: | rm -f {yggdrasil,yggdrasilctl} git clone https://github.com/neilalexander/vyatta-yggdrasil /tmp/vyatta-yggdrasil; cd /tmp/vyatta-yggdrasil; BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-x $CIRCLE_BRANCH; BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-lite $CIRCLE_BRANCH; BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-vyos-i386 $CIRCLE_BRANCH BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-vyos-amd64 $CIRCLE_BRANCH mv *.deb /tmp/upload; - persist_to_workspace: root: /tmp paths: - upload build-macos: macos: xcode: "13.0.0" working_directory: ~/go/src/github.com/yggdrasil-network/yggdrasil-go steps: - checkout - run: name: Create artifact upload directory and set variables command: | mkdir /tmp/upload echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV echo 'export PATH=$PATH:/usr/local/go/bin:~/go/bin' >> $BASH_ENV git config --global user.email "$(git log --format='%ae' HEAD -1)"; git config --global user.name "$(git log --format='%an' HEAD -1)"; echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - run: name: Install Go 1.17 command: | cd /tmp curl -LO https://dl.google.com/go/go1.17.darwin-amd64.pkg sudo installer -pkg /tmp/go1.17.darwin-amd64.pkg -target / #- run: # name: Install Gomobile # command: | # GO111MODULE=off go get golang.org/x/mobile/cmd/gomobile # gomobile init - run: name: Build for macOS command: | GO111MODULE=on GOOS=darwin GOARCH=amd64 ./build cp yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64 cp yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64; GO111MODULE=on GOOS=darwin GOARCH=arm64 ./build cp yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-arm64 cp yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-arm64; - run: name: Build for macOS (.pkg format) command: | PKGARCH=amd64 sh contrib/macos/create-pkg.sh PKGARCH=arm64 sh contrib/macos/create-pkg.sh mv *.pkg /tmp/upload/ #- run: # name: Build framework for iOS (.framework format) # command: | # sudo GO111MODULE=off go get -v github.com/yggdrasil-network/yggdrasil-go/cmd/... # sudo GO111MODULE=off go get -v github.com/yggdrasil-network/yggdrasil-go/src/... # GO111MODULE=off ./build -i # mv *.framework /tmp/upload - persist_to_workspace: root: /tmp paths: - upload build-other: docker: - image: circleci/golang:1.17 steps: - checkout - run: name: Create artifact upload directory and set variables command: | mkdir /tmp/upload echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $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: Build for OpenBSD command: | rm -f {yggdrasil,yggdrasilctl} GOOS=openbsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-openbsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-openbsd-amd64; GOOS=openbsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-openbsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-openbsd-i386; - run: name: Build for FreeBSD command: | rm -f {yggdrasil,yggdrasilctl} GOOS=freebsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-amd64; GOOS=freebsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-i386; - run: name: Build for Windows command: | rm -f {yggdrasil,yggdrasilctl} GOOS=windows GOARCH=amd64 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-amd64.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-amd64.exe; GOOS=windows GOARCH=386 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-i386.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-i386.exe; - persist_to_workspace: root: /tmp paths: - upload upload: machine: true steps: - attach_workspace: at: /tmp - store_artifacts: path: /tmp/upload destination: / workflows: version: 2.1 build: jobs: - lint - build-linux - build-macos - build-other - upload: requires: - build-linux - build-macos - build-other yggdrasil-go-0.4.3/.golangci.yml000066400000000000000000000002651417776402100165060ustar00rootroot00000000000000run: build-tags: - lint issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds skip-dirs: - contrib/ - misc/ linters: disable: - gocycloyggdrasil-go-0.4.3/CHANGELOG.md000066400000000000000000001061141417776402100157330ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [0.4.3] - 2022-02-06 ### Added - `bytes_sent`, `bytes_recvd` and `uptime` have been added to `getPeers` - Clearer logging when connections are rejected due to incompatible peer versions ### Fixed - Latency-based parent selection tiebreak is now reliable on platforms even with low timer resolution - Tree distance calculation offsets have been corrected ## [0.4.2] - 2021-11-03 ### Fixed - Reverted a dependency update which resulted in problems building with Go 1.16 and running on Windows ## [0.4.1] - 2021-11-03 ### Added - TLS peerings now support Server Name Indication (SNI) - The SNI is sent automatically if the peering URI contains a DNS name - A custom SNI can be specified by adding the `?sni=domain.com` parameter to the peering URI - A new `ipv6rwc` API package now implements the IPv6-specific logic separate from the `tun` package ### Fixed - A crash when calculating the partial public key for very high IPv6 addresses has been fixed - A crash due to a concurrent map write has been fixed - A crash due to missing TUN configuration has been fixed - A race condition in the keystore code has been fixed ## [0.4.0] - 2021-07-04 ### Added - New routing scheme, which is backwards incompatible with previous versions of Yggdrasil - The wire protocol version number, exchanged as part of the peer setup handshake, has been increased to 0.4 - Nodes running this new version **will not** be able to peer with earlier versions of Yggdrasil - Please note that **the network may be temporarily unstable** while infrastructure is being upgraded to the new release - TLS connections now use public key pinning - If no public key was already pinned, then the public key received as part of the TLS handshake is pinned to the connection - The public key received as part of the handshake is checked against the pinned keys, and if no match is found, the connection is rejected ### Changed - IP addresses are now derived from ed25519 public (signing) keys - Previously, addresses were derived from a hash of X25519 (Diffie-Hellman) keys - Importantly, this means that **all internal IPv6 addresses will change with this release** — this will affect anyone running public services or relying on Yggdrasil for remote access - It is now recommended to peer over TLS - Link-local peers from multicast peer discovery will now connect over TLS, with the key from the multicast beacon pinned to the connection - `socks://` peers now expect the destination endpoint to be a `tls://` listener, instead of a `tcp://` listener - Multicast peer discovery is now more configurable - There are separate configuration options to control if beacons are sent, what port to listen on for incoming connections (if sending beacons), and whether or not to listen for beacons from other nodes (and open connections when receiving a beacon) - Each configuration entry in the list specifies a regular expression to match against interface names - If an interface matches multiple regex in the list, it will use the settings for the first entry in the list that it matches with - The session and routing code has been entirely redesigned and rewritten - This is still an early work-in-progress, so the code hasn't been as well tested or optimized as the old code base — please bear with us for these next few releases as we work through any bugs or issues - Generally speaking, we expect to see reduced bandwidth use and improved reliability with the new design, especially in cases where nodes move around or change peerings frequently - Cryptographic sessions no longer use a single shared (ephemeral) secret for the entire life of the session. Keys are now rotated regularly for ongoing sessions (currently rotated at least once per round trip exchange of traffic, subject to change in future releases) - Source routing has been added. Under normal circumstances, this is what is used to forward session traffic (e.g. the user's IPv6 traffic) - DHT-based routing has been added. This is used when the sender does not know a source route to the destination. Forwarding through the DHT is less efficient, but the only information that it requires the sender to know is the destination node's (static) key. This is primarily used during the key exchange at session setup, or as a temporary fallback when a source route fails due to changes in the network - The new DHT design is no longer RPC-based, does not support crawling and does not inherently allow nodes to look up the owner of an arbitrary key. Responding to lookups is now implemented at the application level and a response is only sent if the destination key matches the node's `/128` IP or `/64` prefix - The greedy routing scheme, used to forward all traffic in previous releases, is now only used for protocol traffic (i.e. DHT setup and source route discovery) - The routing logic now lives in a [standalone library](https://github.com/Arceliar/ironwood). You are encouraged **not** to use it, as it's still considered pre-alpha, but it's available for those who want to experiment with the new routing algorithm in other contexts - Session MTUs may be slightly lower now, in order to accommodate large packet headers if required - Many of the admin functions available over `yggdrasilctl` have been changed or removed as part of rewrites to the code - Several remote `debug` functions have been added temporarily, to allow for crawling and census gathering during the transition to the new version, but we intend to remove this at some point in the (possibly distant) future - The list of available functions will likely be expanded in future releases - The configuration file format has been updated in response to the changed/removed features ### Removed - Tunnel routing (a.k.a. crypto-key routing or "CKR") has been removed - It was far too easy to accidentally break routing altogether by capturing the route to peers with the TUN adapter - We recommend tunnelling an existing standard over Yggdrasil instead (e.g. `ip6gre`, `ip6gretap` or other similar encapsulations, using Yggdrasil IPv6 addresses as the tunnel endpoints) - All `TunnelRouting` configuration options will no longer take effect - Session firewall has been removed - This was never a true firewall — it didn't behave like a stateful IP firewall, often allowed return traffic unexpectedly and was simply a way to prevent a node from being flooded with unwanted sessions, so the name could be misleading and usually lead to a false sense of security - Due to design changes, the new code needs to address the possible memory exhaustion attacks in other ways and a single configurable list no longer makes sense - Users who want a firewall or other packet filter mechansim should configure something supported by their OS instead (e.g. `ip6tables`) - All `SessionFirewall` configuration options will no longer take effect - `SIGHUP` handling to reload the configuration at runtime has been removed - It was not obvious which parts of the configuration could be reloaded at runtime, and which required the application to be killed and restarted to take effect - Reloading the config without restarting was also a delicate and bug-prone process, and was distracting from more important developments - `SIGHUP` will be handled normally (i.e. by exiting) - `cmd/yggrasilsim` has been removed, and is unlikely to return to this repository ## [0.3.16] - 2021-03-18 ### Added - New simulation code under `cmd/yggdrasilsim` (work-in-progress) ### Changed - Multi-threading in the switch - Swich lookups happen independently for each (incoming) peer connection, instead of being funneled to a single dedicated switch worker - Packets are queued for each (outgoing) peer connection, instead of being handled by a single dedicated switch worker - Queue logic rewritten - Heap structure per peer that traffic is routed to, with one FIFO queue per traffic flow - The total size of each heap is configured automatically (we basically queue packets until we think we're blocked on a socket write) - When adding to a full heap, the oldest packet from the largest queue is dropped - Packets are popped from the queue in FIFO order (oldest packet from among all queues in the heap) to prevent packet reordering at the session level - Removed global `sync.Pool` of `[]byte` - Local `sync.Pool`s are used in the hot loops, but not exported, to avoid memory corruption if libraries are reused by other projects - This may increase allocations (and slightly reduce speed in CPU-bound benchmarks) when interacting with the tun/tap device, but traffic forwarded at the switch layer should be unaffected - Upgrade dependencies - Upgrade build to Go 1.16 ### Fixed - Fixed a bug where the connection listener could exit prematurely due to resoruce exhaustion (if e.g. too many connections were opened) - Fixed DefaultIfName for OpenBSD (`/dev/tun0` -> `tun0`) - Fixed an issue where a peer could sometimes never be added to the switch - Fixed a goroutine leak that could occur if a peer with an open connection continued to spam additional connection attempts ## [0.3.15] - 2020-09-27 ### Added - Support for pinning remote public keys in peering strings has been added, e.g. - By signing public key: `tcp://host:port?ed25519=key` - By encryption public key: `tcp://host:port?curve25519=key` - By both: `tcp://host:port?ed25519=key&curve25519=key` - By multiple, in case of DNS round-robin or similar: `tcp://host:port?curve25519=key&curve25519=key&ed25519=key&ed25519=key` - Some checks to prevent Yggdrasil-over-Yggdrasil peerings have been added - Added support for SOCKS proxy authentication, e.g. `socks://user@password:host/...` ### Fixed - Some bugs in the multicast code that could cause unnecessary CPU usage have been fixed - A possible multicast deadlock on macOS when enumerating interfaces has been fixed - A deadlock in the connection code has been fixed - Updated HJSON dependency that caused some build problems ### Changed - `DisconnectPeer` and `RemovePeer` have been separated and implemented properly now - Less nodes are stored in the DHT now, reducing ambient network traffic and possible instability - Default config file for FreeBSD is now at `/usr/local/etc/yggdrasil.conf` instead of `/etc/yggdrasil.conf` ## [0.3.14] - 2020-03-28 ### Fixed - Fixes a memory leak that may occur if packets are incorrectly never removed from a switch queue ### Changed - Make DHT searches a bit more reliable by tracking the 16 most recently visited nodes ## [0.3.13] - 2020-02-21 ### Added - Support for the Wireguard TUN driver, which now replaces Water and provides far better support and performance on Windows - Windows `.msi` installer files are now supported (bundling the Wireguard TUN driver) - NodeInfo code is now actorised, should be more reliable - The DHT now tries to store the two closest nodes in either direction instead of one, such that if a node goes offline, the replacement is already known - The Yggdrasil API now supports dialing a remote node using the public key instead of the Node ID ### Changed - The `-loglevel` command line parameter is now cumulative and automatically includes all levels below the one specified - DHT search code has been significantly simplified and processes rumoured nodes in parallel, speeding up search time - DHT search results are now sorted - The systemd service now handles configuration generation in a different unit - The Yggdrasil API now returns public keys instead of node IDs when querying for local and remote addresses ### Fixed - The multicast code no longer panics when shutting down the node - A potential OOB error when calculating IPv4 flow labels (when tunnel routing is enabled) has been fixed - A bug resulting in incorrect idle notifications in the switch should now be fixed - MTUs are now using a common datatype throughout the codebase ### Removed - TAP mode has been removed entirely, since it is no longer supported with the Wireguard TUN package. Please note that if you are using TAP mode, you may need to revise your config! - NetBSD support has been removed until the Wireguard TUN package supports NetBSD ## [0.3.12] - 2019-11-24 ### Added - New API functions `SetMaximumSessionMTU` and `GetMaximumSessionMTU` - New command line parameters `-address` and `-subnet` for getting the address/subnet from the config file, for use with `-useconffile` or `-useconf` - A warning is now produced in the Yggdrasil output at startup when the MTU in the config is invalid or has been adjusted for some reason ### Changed - On Linux, outgoing `InterfacePeers` connections now use `SO_BINDTODEVICE` to prefer an outgoing interface - The `genkeys` utility is now in `cmd` rather than `misc` ### Fixed - A data race condition has been fixed when updating session coordinates - A crash when shutting down when no multicast interfaces are configured has been fixed - A deadlock when calling `AddPeer` multiple times has been fixed - A typo in the systemd unit file (for some Linux packages) has been fixed - The NodeInfo and admin socket now report `unknown` correctly when no build name/version is available in the environment at build time - The MTU calculation now correctly accounts for ethernet headers when running in TAP mode ## [0.3.11] - 2019-10-25 ### Added - Support for TLS listeners and peers has been added, allowing the use of `tls://host:port` in `Peers`, `InterfacePeers` and `Listen` configuration settings - this allows hiding Yggdrasil peerings inside regular TLS connections ### Changed - Go 1.13 or later is now required for building Yggdrasil - Some exported API functions have been updated to work with standard Go interfaces: - `net.Conn` instead of `yggdrasil.Conn` - `net.Dialer` (the interface it would satisfy if it wasn't a concrete type) instead of `yggdrasil.Dialer` - `net.Listener` instead of `yggdrasil.Listener` - Session metadata is now updated correctly when a search completes for a node to which we already have an open session - Multicast module reloading behaviour has been improved ### Fixed - An incorrectly held mutex in the crypto-key routing code has been fixed - Multicast module no longer opens a listener socket if no multicast interfaces are configured ## [0.3.10] - 2019-10-10 ### Added - The core library now includes several unit tests for peering and `yggdrasil.Conn` connections ### Changed - On recent Linux kernels, Yggdrasil will now set the `tcp_congestion_control` algorithm used for its own TCP sockets to [BBR](https://github.com/google/bbr), which reduces latency under load - The systemd service configuration in `contrib` (and, by extension, some of our packages) now attempts to load the `tun` module, in case TUN/TAP support is available but not loaded, and it restricts Yggdrasil to the `CAP_NET_ADMIN` capability for managing the TUN/TAP adapter, rather than letting it do whatever the (typically `root`) user can do ### Fixed - The `yggdrasil.Conn.RemoteAddr()` function no longer blocks, fixing a deadlock when CKR is used while under heavy load ## [0.3.9] - 2019-09-27 ### Added - Yggdrasil will now complain more verbosely when a peer URI is incorrectly formatted - Soft-shutdown methods have been added, allowing a node to shut down gracefully when terminated - New multicast interval logic which sends multicast beacons more often when Yggdrasil is first started to increase the chance of finding nearby nodes quickly after startup ### Changed - The switch now buffers packets more eagerly in an attempt to give the best link a chance to send, which appears to reduce packet reordering when crossing aggregate sets of peerings - Substantial amounts of the codebase have been refactored to use the actor model, which should substantially reduce the chance of deadlocks - Nonce tracking in sessions has been modified so that memory usage is reduced whilst still only allowing duplicate packets within a small window - Soft-reconfiguration support has been simplified using new actor functions - The garbage collector threshold has been adjusted for mobile builds - The maximum queue size is now managed exclusively by the switch rather than by the core ### Fixed - The broken `hjson-go` dependency which affected builds of the previous version has now been resolved in the module manifest - Some minor memory leaks in the switch have been fixed, which improves memory usage on mobile builds - A memory leak in the add-peer loop has been fixed - The admin socket now reports the correct URI strings for SOCKS peers in `getPeers` - A race condition when dialing a remote node by both the node address and routed prefix simultaneously has been fixed - A race condition between the router and the dial code resulting in a panic has been fixed - A panic which could occur when the TUN/TAP interface disappears (e.g. during soft-shutdown) has been fixed - A bug in the semantic versioning script which accompanies Yggdrasil for builds has been fixed - A panic which could occur when the TUN/TAP interface reads an undersized/corrupted packet has been fixed ### Removed - A number of legacy debug functions have now been removed and a number of exported API functions are now better documented ## [0.3.8] - 2019-08-21 ### Changed - Yggdrasil can now send multiple packets from the switch at once, which results in improved throughput with smaller packets or lower MTUs - Performance has been slightly improved by not allocating cancellations where not necessary - Crypto-key routing options have been renamed for clarity - `IPv4Sources` is now named `IPv4LocalSubnets` - `IPv6Sources` is now named `IPv6LocalSubnets` - `IPv4Destinations` is now named `IPv4RemoteSubnets` - `IPv6Destinations` is now named `IPv6RemoteSubnets` - The old option names will continue to be accepted by the configuration parser for now but may not be indefinitely - When presented with multiple paths between two nodes, the switch now prefers the most recently used port when possible instead of the least recently used, helping to reduce packet reordering - New nonce tracking should help to reduce the number of packets dropped as a result of multiple/aggregate paths or congestion control in the switch ### Fixed - A deadlock was fixed in the session code which could result in Yggdrasil failing to pass traffic after some time ### Security - Address verification was not strict enough, which could result in a malicious session sending traffic with unexpected or spoofed source or destination addresses which Yggdrasil could fail to reject - Versions `0.3.6` and `0.3.7` are vulnerable - users of these versions should upgrade as soon as possible - Versions `0.3.5` and earlier are not affected ## [0.3.7] - 2019-08-14 ### Changed - The switch should now forward packets along a single path more consistently in cases where congestion is low and multiple equal-length paths exist, which should improve stability and result in fewer out-of-order packets - Sessions should now be more tolerant of out-of-order packets, by replacing a bitmask with a variable sized heap+map structure to track recently received nonces, which should reduce the number of packets dropped due to reordering when multiple paths are used or multiple independent flows are transmitted through the same session - The admin socket can no longer return a dotfile representation of the known parts of the network, this could be rebuilt by clients using information from `getSwitchPeers`,`getDHT` and `getSessions` ### Fixed - A number of significant performance regressions introduced in version 0.3.6 have been fixed, resulting in better performance - Flow labels are now used to prioritise traffic flows again correctly - In low-traffic scenarios where there are multiple peerings between a pair of nodes, Yggdrasil now prefers the most active peering instead of the least active, helping to reduce packet reordering - The `Listen` statement, when configured as a string rather than an array, will now be parsed correctly - The admin socket now returns `coords` as a correct array of unsigned 64-bit integers, rather than the internal representation - The admin socket now returns `box_pub_key` in string format again - Sessions no longer leak/block when no listener (e.g. TUN/TAP) is configured - Incoming session connections no longer block when a session already exists, which results in less leaked goroutines - Flooded sessions will no longer block other sessions - Searches are now cleaned up properly and a couple of edge-cases with duplicate searches have been fixed - A number of minor allocation and pointer fixes ## [0.3.6] - 2019-08-03 ### Added - Yggdrasil now has a public API with interfaces such as `yggdrasil.ConnDialer`, `yggdrasil.ConnListener` and `yggdrasil.Conn` for using Yggdrasil as a transport directly within applications - Session gatekeeper functions, part of the API, which can be used to control whether to allow or reject incoming or outgoing sessions dynamically (compared to the previous fixed whitelist/blacklist approach) - Support for logging to files or syslog (where supported) - Platform defaults now include the ability to set sane defaults for multicast interfaces ### Changed - Following a massive refactoring exercise, Yggdrasil's codebase has now been broken out into modules - Core node functionality in the `yggdrasil` package with a public API - This allows Yggdrasil to be integrated directly into other applications and used as a transport - IP-specific code has now been moved out of the core `yggdrasil` package, making Yggdrasil effectively protocol-agnostic - Multicast peer discovery functionality is now in the `multicast` package - Admin socket functionality is now in the `admin` package and uses the Yggdrasil public API - TUN/TAP, ICMPv6 and all IP-specific functionality is now in the `tuntap` package - `PPROF` debug output is now sent to `stderr` instead of `stdout` - Node IPv6 addresses on macOS are now configured as `secured` - Upstream dependency references have been updated, which includes a number of fixes in the Water library ### Fixed - Multicast discovery is no longer disabled if the nominated interfaces aren't available on the system yet, e.g. during boot - Multicast interfaces are now re-evaluated more frequently so that Yggdrasil doesn't need to be restarted to use interfaces that have become available since startup - Admin socket error cases are now handled better - Various fixes in the TUN/TAP module, particularly surrounding Windows platform support - Invalid keys will now cause the node to fail to start, rather than starting but silently not working as before - Session MTUs are now always calculated correctly, in some cases they were incorrectly defaulting to 1280 before - Multiple searches now don't take place for a single connection - Concurrency bugs fixed - Fixed a number of bugs in the ICMPv6 neighbor solicitation in the TUN/TAP code - A case where peers weren't always added correctly if one or more peers were unreachable has been fixed - Searches which include the local node are now handled correctly - Lots of small bug tweaks and clean-ups throughout the codebase ## [0.3.5] - 2019-03-13 ### Fixed - The `AllowedEncryptionPublicKeys` option has now been fixed to handle incoming connections properly and no longer blocks outgoing connections (this was broken in v0.3.4) - Multicast TCP listeners will now be stopped correctly when the link-local address on the interface changes or disappears altogether ## [0.3.4] - 2019-03-12 ### Added - Support for multiple listeners (although currently only TCP listeners are supported) - New multicast behaviour where each multicast interface is given its own link-local listener and does not depend on the `Listen` configuration - Blocking detection in the switch to avoid parenting a blocked peer - Support for adding and removing listeners and multicast interfaces when reloading configuration during runtime - Yggdrasil will now attempt to clean up UNIX admin sockets on startup if left behind by a previous crash - Admin socket `getTunnelRouting` and `setTunnelRouting` calls for enabling and disabling crypto-key routing during runtime - On macOS, Yggdrasil will now try to wake up AWDL on start-up when `awdl0` is a configured multicast interface, to keep it awake after system sleep, and to stop waking it when no longer needed - Added `LinkLocalTCPPort` option for controlling the port number that link-local TCP listeners will listen on by default when setting up `MulticastInterfaces` (a node restart is currently required for changes to `LinkLocalTCPPort` to take effect - it cannot be updated by reloading config during runtime) ### Changed - The `Listen` configuration statement is now an array instead of a string - The `Listen` configuration statement should now conform to the same formatting as peers with the protocol prefix, e.g. `tcp://[::]:0` - Session workers are now non-blocking - Multicast interval is now fixed at every 15 seconds and network interfaces are reevaluated for eligibility on each interval (where before the interval depended upon the number of configured multicast interfaces and evaluation only took place at startup) - Dead connections are now closed in the link handler as opposed to the switch - Peer forwarding is now prioritised instead of randomised ### Fixed - Admin socket `getTunTap` call now returns properly instead of claiming no interface is enabled in all cases - Handling of `getRoutes` etc in `yggdrasilctl` is now working - Local interface names are no longer leaked in multicast packets - Link-local TCP connections, particularly those initiated because of multicast beacons, are now always correctly scoped for the target interface - Yggdrasil now correctly responds to multicast interfaces going up and down during runtime ## [0.3.3] - 2019-02-18 ### Added - Dynamic reconfiguration, which allows reloading the configuration file to make changes during runtime by sending a `SIGHUP` signal (note: this only works with `-useconffile` and not `-useconf` and currently reconfiguring TUN/TAP is not supported) - Support for building Yggdrasil as an iOS or Android framework if the appropriate tools (e.g. `gomobile`/`gobind` + SDKs) are available - Connection contexts used for TCP connections which allow more exotic socket options to be set, e.g. - Reusing the multicast socket to allow multiple running Yggdrasil instances without having to disable multicast - Allowing supported Macs to peer with other nearby Macs that aren't even on the same Wi-Fi network using AWDL - Flexible logging support, which allows for logging at different levels of verbosity ### Changed - Switch changes to improve parent selection - Node configuration is now stored centrally, rather than having fragments/copies distributed at startup time - Significant refactoring in various areas, including for link types (TCP, AWDL etc), generic streams and adapters - macOS builds through CircleCI are now 64-bit only ### Fixed - Simplified `systemd` service now in `contrib` ### Removed - `ReadTimeout` option is now deprecated ## [0.3.2] - 2018-12-26 ### Added - The admin socket is now multithreaded, greatly improving performance of the crawler and allowing concurrent lookups to take place - The ability to hide NodeInfo defaults through either setting the `NodeInfoPrivacy` option or through setting individual `NodeInfo` attributes to `null` ### Changed - The `armhf` build now targets ARMv6 instead of ARMv7, adding support for Raspberry Pi Zero and other older models, amongst others ### Fixed - DHT entries are now populated using a copy in memory to fix various potential DHT bugs - DHT traffic should now throttle back exponentially to reduce idle traffic - Adjust how nodes are inserted into the DHT which should help to reduce some incorrect DHT traffic - In TAP mode, the NDP target address is now correctly used when populating the peer MAC table. This fixes serious connectivity problems when in TAP mode, particularly on BSD - In TUN mode, ICMPv6 packets are now ignored whereas they were incorrectly processed before ## [0.3.1] - 2018-12-17 ### Added - Build name and version is now imprinted onto the binaries if available/specified during build - Ability to disable admin socket with `AdminListen: none` - `AF_UNIX` domain sockets for the admin socket - Cache size restriction for crypto-key routes - `NodeInfo` support for specifying node information, e.g. node name or contact, which can be used in network crawls or surveys - `getNodeInfo` request added to admin socket - Adds flags `-c`, `-l` and `-t` to `build` script for specifying `GCFLAGS`, `LDFLAGS` or whether to keep symbol/DWARF tables ### Changed - Default `AdminListen` in newly generated config is now `unix:///var/run/yggdrasil.sock` - Formatting of `getRoutes` in the admin socket has been improved - Debian package now adds `yggdrasil` group to assist with `AF_UNIX` admin socket permissions - Crypto, address and other utility code refactored into separate Go packages ### Fixed - Switch peer convergence is now much faster again (previously it was taking up to a minute once the peering was established) - `yggdrasilctl` is now less prone to crashing when parameters are specified incorrectly - Panic fixed when `Peers` or `InterfacePeers` was commented out ## [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 - Add `getSwitchQueues` to admin socket - Add `InterfacePeers` for configuring static peerings via specific network interfaces - More output shown in `getSwitchPeers` - FreeBSD service script in `contrib` ## Changed - CircleCI builds are now built with Go 1.11 instead of Go 1.9 ## Fixed - Race condition in the switch table, reported by trn - Debug builds are now tested by CircleCI as well as platform release builds - Port number fixed on admin graph from unknown nodes ## [0.2.6] - 2018-07-31 ### Added - Configurable TCP timeouts to assist in peering over Tor/I2P - Prefer IPv6 flow label when extending coordinates to sort backpressure queues - `arm64` builds through CircleCI ### Changed - Sort dot graph links by integer value ## [0.2.5] - 2018-07-19 ### Changed - Make `yggdrasilctl` less case sensitive - More verbose TCP disconnect messages ### Fixed - Fixed debug builds - Cap maximum MTU on Linux in TAP mode - Process successfully-read TCP traffic before checking for / handling errors (fixes EOF behavior) ## [0.2.4] - 2018-07-08 ### Added - Support for UNIX domain sockets for the admin socket using `unix:///path/to/file.sock` - Centralised platform-specific defaults ### Changed - Backpressure tuning, including reducing resource consumption ### Fixed - macOS local ping bug, which previously prevented you from pinging your own `utun` adapter's IPv6 address ## [0.2.3] - 2018-06-29 ### Added - Begin keeping changelog (incomplete and possibly inaccurate information before this point). - Build RPMs in CircleCI using alien. This provides package support for Fedora, Red Hat Enterprise Linux, CentOS and other RPM-based distributions. ### Changed - Local backpressure improvements. - Change `box_pub_key` to `key` in admin API for simplicity. - Session cleanup. ## [0.2.2] - 2018-06-21 ### Added - Add `yggdrasilconf` utility for testing with the `vyatta-yggdrasil` package. - Add a randomized retry delay after TCP disconnects, to prevent synchronization livelocks. ### Changed - Update build script to strip by default, which significantly reduces the size of the binary. - Add debug `-d` and UPX `-u` flags to the `build` script. - Start pprof in debug builds based on an environment variable (e.g. `PPROFLISTEN=localhost:6060`), instead of a flag. ### Fixed - Fix typo in big-endian BOM so that both little-endian and big-endian UTF-16 files are detected correctly. ## [0.2.1] - 2018-06-15 ### Changed - The address range was moved from `fd00::/8` to `200::/7`. This range was chosen as it is marked as deprecated. The change prevents overlap with other ULA privately assigned ranges. ### Fixed - UTF-16 detection conversion for configuration files, which can particularly be a problem on Windows 10 if a configuration file is generated from within PowerShell. - Fixes to the Debian package control file. - Fixes to the launchd service for macOS. - Fixes to the DHT and switch. ## [0.2.0] - 2018-06-13 ### Added - Exchange version information during connection setup, to prevent connections with incompatible versions. ### Changed - Wire format changes (backwards incompatible). - Less maintenance traffic per peer. - Exponential back-off for DHT maintenance traffic (less maintenance traffic for known good peers). - Iterative DHT (added sometime between v0.1.0 and here). - Use local queue sizes for a sort of local-only backpressure routing, instead of the removed bandwidth estimates, when deciding where to send a packet. ### Removed - UDP peering, this may be added again if/when a better implementation appears. - Per peer bandwidth estimation, as this has been replaced with an early local backpressure implementation. ## [0.1.0] - 2018-02-01 ### Added - Adopt semantic versioning. ### Changed - Wire format changes (backwards incompatible). - Many other undocumented changes leading up to this release and before the next one. ## [0.0.1] - 2017-12-28 ### Added - First commit. - Initial public release. yggdrasil-go-0.4.3/Dockerfile000066400000000000000000000000321417776402100161040ustar00rootroot00000000000000contrib/docker/Dockerfile yggdrasil-go-0.4.3/LICENSE000066400000000000000000000210531417776402100151250ustar00rootroot00000000000000This software is licensed under the LGPLv3, included below. As a special exception to the GNU Lesser General Public License version 3 ("LGPL3"), the copyright holders of this Library give you permission to convey to a third party a Combined Work that links statically or dynamically to this Library without providing any Minimal Corresponding Source or Minimal Application Code as set out in 4d or providing the installation information set out in section 4e, provided that you comply with the other provisions of LGPL3 and provided that you meet, for the Application the terms and conditions of the license(s) which apply to the Application. Except as stated in this special exception, the provisions of LGPL3 will continue to comply in full to this Library. If you modify this Library, you may apply this exception to your version of this Library, but you are not obliged to do so. If you do not wish to do so, delete this exception 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. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. yggdrasil-go-0.4.3/README.md000066400000000000000000000064641417776402100154100ustar00rootroot00000000000000# Yggdrasil [![CircleCI](https://circleci.com/gh/yggdrasil-network/yggdrasil-go.svg?style=shield&circle-token=:circle-token )](https://circleci.com/gh/yggdrasil-network/yggdrasil-go) ## Introduction Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6 network. It is lightweight, self-arranging, supported on multiple platforms and allows pretty much any IPv6-capable application to communicate securely with other Yggdrasil nodes. Yggdrasil does not require you to have IPv6 Internet connectivity - it also works over IPv4. ## Supported Platforms Yggdrasil works on a number of platforms, including Linux, macOS, Ubiquiti EdgeRouter, VyOS, Windows, FreeBSD, OpenBSD and OpenWrt. Please see our [Installation](https://yggdrasil-network.github.io/installation.html) page for more information. You may also find other platform-specific wrappers, scripts or tools in the `contrib` folder. ## Building If you want to build from source, as opposed to installing one of the pre-built packages: 1. Install [Go](https://golang.org) (requires Go 1.16 or later) 2. Clone this repository 2. Run `./build` Note that you can cross-compile for other platforms and architectures by specifying the `GOOS` and `GOARCH` environment variables, e.g. `GOOS=windows ./build` or `GOOS=linux GOARCH=mipsle ./build`. ## Running ### Generate configuration To generate static configuration, either generate a HJSON file (human-friendly, complete with comments): ``` ./yggdrasil -genconf > /path/to/yggdrasil.conf ``` ... or generate a plain JSON file (which is easy to manipulate programmatically): ``` ./yggdrasil -genconf -json > /path/to/yggdrasil.conf ``` You will need to edit the `yggdrasil.conf` file to add or remove peers, modify other configuration such as listen addresses or multicast addresses, etc. ### Run Yggdrasil To run with the generated static configuration: ``` ./yggdrasil -useconffile /path/to/yggdrasil.conf ``` To run in auto-configuration mode (which will use sane defaults and random keys at each startup, instead of using a static configuration file): ``` ./yggdrasil -autoconf ``` You will likely need to run Yggdrasil as a privileged user or under `sudo`, unless you have permission to create TUN/TAP adapters. On Linux this can be done by giving the Yggdrasil binary the `CAP_NET_ADMIN` capability. ## Documentation Documentation is available [on our website](https://yggdrasil-network.github.io). - [Installing Yggdrasil](https://yggdrasil-network.github.io/installation.html) - [Configuring Yggdrasil](https://yggdrasil-network.github.io/configuration.html) - [Frequently asked questions](https://yggdrasil-network.github.io/faq.html) - [Version changelog](CHANGELOG.md) ## Community Feel free to join us on our [Matrix channel](https://matrix.to/#/#yggdrasil:matrix.org) at `#yggdrasil:matrix.org` or in the `#yggdrasil` IRC channel on [libera.chat](https://libera.chat). ## License This code is released under the terms of the LGPLv3, but with an added exception that was shamelessly taken from [godeb](https://github.com/niemeyer/godeb). Under certain circumstances, this exception permits distribution of binaries that are (statically or dynamically) linked with this code, without requiring the distribution of Minimal Corresponding Source or Minimal Application Code. For more details, see: [LICENSE](LICENSE). yggdrasil-go-0.4.3/appveyor.yml000066400000000000000000000011471417776402100165120ustar00rootroot00000000000000version: '{build}' pull_requests: do_not_increment_build_number: true os: Visual Studio 2019 shallow_clone: false environment: MSYS2_PATH_TYPE: inherit CHERE_INVOKING: enabled_from_arguments build_script: - cmd: cd %APPVEYOR_BUILD_FOLDER% - curl -o C:\projects\golang.zip https://dl.google.com/go/go1.17.5.windows-amd64.zip - 7z x C:\projects\golang.zip -oC:\projects\ - cmd: set PATH=C:\projects\go\bin;%PATH% - cmd: set GOROOT=C:\projects\go - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x64" - c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x86" test: off artifacts: - path: '*.msi' yggdrasil-go-0.4.3/build000077500000000000000000000015201417776402100151420ustar00rootroot00000000000000#!/bin/sh set -ef PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" ARGS="-v" while getopts "utc:l:dro:p" option do case "$option" in u) UPX=true;; t) TABLES=true;; c) GCFLAGS="$GCFLAGS $OPTARG";; l) LDFLAGS="$LDFLAGS $OPTARG";; d) ARGS="$ARGS -tags debug" DEBUG=true;; r) ARGS="$ARGS -race";; o) ARGS="$ARGS -o $OPTARG";; p) ARGS="$ARGS -buildmode=pie";; esac done if [ -z $TABLES ] && [ -z $DEBUG ]; then LDFLAGS="$LDFLAGS -s -w" fi for CMD in yggdrasil yggdrasilctl ; do echo "Building: $CMD" go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD if [ $UPX ]; then upx --brute $CMD fi done yggdrasil-go-0.4.3/clean000077500000000000000000000000311417776402100151210ustar00rootroot00000000000000#!/bin/sh git clean -dxf yggdrasil-go-0.4.3/cmd/000077500000000000000000000000001417776402100146625ustar00rootroot00000000000000yggdrasil-go-0.4.3/cmd/genkeys/000077500000000000000000000000001417776402100163275ustar00rootroot00000000000000yggdrasil-go-0.4.3/cmd/genkeys/main.go000066400000000000000000000033601417776402100176040ustar00rootroot00000000000000/* This file generates crypto keys. It prints out a new set of keys each time if finds a "better" one. By default, "better" means a higher NodeID (-> higher IP address). This is because the IP address format can compress leading 1s in the address, to increase the number of ID bits in the address. If run with the "-sig" flag, it generates signing keys instead. A "better" signing key means one with a higher TreeID. This only matters if it's high enough to make you the root of the tree. */ package main import ( "crypto/ed25519" "encoding/hex" "fmt" "net" "runtime" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) type keySet struct { priv ed25519.PrivateKey pub ed25519.PublicKey } func main() { threads := runtime.GOMAXPROCS(0) var currentBest ed25519.PublicKey newKeys := make(chan keySet, threads) for i := 0; i < threads; i++ { go doKeys(newKeys) } for { newKey := <-newKeys if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 { currentBest = newKey.pub fmt.Println("-----") fmt.Println("Priv:", hex.EncodeToString(newKey.priv)) fmt.Println("Pub:", hex.EncodeToString(newKey.pub)) addr := address.AddrForKey(newKey.pub) fmt.Println("IP:", net.IP(addr[:]).String()) } } } func isBetter(oldPub, newPub ed25519.PublicKey) bool { for idx := range oldPub { if newPub[idx] < oldPub[idx] { return true } if newPub[idx] > oldPub[idx] { break } } return false } func doKeys(out chan<- keySet) { bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize) for idx := range bestKey { bestKey[idx] = 0xff } for { pub, priv, err := ed25519.GenerateKey(nil) if err != nil { panic(err) } if !isBetter(bestKey, pub) { continue } bestKey = pub out <- keySet{priv, pub} } } yggdrasil-go-0.4.3/cmd/yggdrasil/000077500000000000000000000000001417776402100166475ustar00rootroot00000000000000yggdrasil-go-0.4.3/cmd/yggdrasil/main.go000066400000000000000000000306031417776402100201240ustar00rootroot00000000000000package main import ( "bytes" "context" "crypto/ed25519" "encoding/hex" "encoding/json" "flag" "fmt" "io/ioutil" "net" "os" "os/signal" "strings" "syscall" "golang.org/x/text/encoding/unicode" "github.com/gologme/log" gsyslog "github.com/hashicorp/go-syslog" "github.com/hjson/hjson-go" "github.com/kardianos/minwinsvc" "github.com/mitchellh/mapstructure" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) type node struct { core core.Core config *config.NodeConfig tuntap *tuntap.TunAdapter multicast *multicast.Multicast admin *admin.AdminSocket } func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf bool) *config.NodeConfig { // Use a configuration file. If -useconf, the configuration will be read // from stdin. If -useconffile, the configuration will be read from the // filesystem. var conf []byte var err error if useconffile != "" { // Read the file from the filesystem conf, err = ioutil.ReadFile(useconffile) } else { // Read the file from stdin. conf, err = ioutil.ReadAll(os.Stdin) } if err != nil { panic(err) } // If there's a byte order mark - which Windows 10 is now incredibly fond of // throwing everywhere when it's converting things into UTF-16 for the hell // of it - remove it and decode back down into UTF-8. This is necessary // because hjson doesn't know what to do with UTF-16 and will panic if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) || bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) { utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) decoder := utf.NewDecoder() conf, err = decoder.Bytes(conf) if err != nil { panic(err) } } // Generate a new configuration - this gives us a set of sane defaults - // then parse the configuration we loaded above on top of it. The effect // of this is that any configuration item that is missing from the provided // configuration will use a sane default. cfg := defaults.GenerateConfig() var dat map[string]interface{} if err := hjson.Unmarshal(conf, &dat); err != nil { panic(err) } // Check if we have old field names if _, ok := dat["TunnelRouting"]; ok { log.Warnln("WARNING: Tunnel routing is no longer supported") } if old, ok := dat["SigningPrivateKey"]; ok { log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"") if _, ok := dat["PrivateKey"]; !ok { if privstr, err := hex.DecodeString(old.(string)); err == nil { priv := ed25519.PrivateKey(privstr) pub := priv.Public().(ed25519.PublicKey) dat["PrivateKey"] = hex.EncodeToString(priv[:]) dat["PublicKey"] = hex.EncodeToString(pub[:]) } else { log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option contains an invalid value and will be ignored") } } } if oldmc, ok := dat["MulticastInterfaces"]; ok { if oldmcvals, ok := oldmc.([]interface{}); ok { var newmc []config.MulticastInterfaceConfig for _, oldmcval := range oldmcvals { if str, ok := oldmcval.(string); ok { newmc = append(newmc, config.MulticastInterfaceConfig{ Regex: str, Beacon: true, Listen: true, }) } } if newmc != nil { if oldport, ok := dat["LinkLocalTCPPort"]; ok { // numbers parse to float64 by default if port, ok := oldport.(float64); ok { for idx := range newmc { newmc[idx].Port = uint16(port) } } } dat["MulticastInterfaces"] = newmc } } } // Sanitise the config confJson, err := json.Marshal(dat) if err != nil { panic(err) } if err := json.Unmarshal(confJson, &cfg); err != nil { panic(err) } // Overlay our newly mapped configuration onto the autoconf node config that // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { panic(err) } return cfg } // Generates a new configuration and returns it in HJSON format. This is used // with -genconf. func doGenconf(isjson bool) string { cfg := defaults.GenerateConfig() var bs []byte var err error if isjson { bs, err = json.MarshalIndent(cfg, "", " ") } else { bs, err = hjson.Marshal(cfg) } if err != nil { panic(err) } return string(bs) } func setLogLevel(loglevel string, logger *log.Logger) { levels := [...]string{"error", "warn", "info", "debug", "trace"} loglevel = strings.ToLower(loglevel) contains := func() bool { for _, l := range levels { if l == loglevel { return true } } return false } if !contains() { // set default log level logger.Infoln("Loglevel parse failed. Set default level(info)") loglevel = "info" } for _, l := range levels { logger.EnableLevel(l) if l == loglevel { break } } } type yggArgs struct { genconf bool useconf bool normaliseconf bool confjson bool autoconf bool ver bool getaddr bool getsnet bool useconffile string logto string loglevel string } func getArgs() yggArgs { genconf := flag.Bool("genconf", false, "print a new config to stdout") 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)") ver := flag.Bool("version", false, "prints the version of this build") logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration") getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration") loglevel := flag.String("loglevel", "info", "loglevel to enable") flag.Parse() return yggArgs{ genconf: *genconf, useconf: *useconf, useconffile: *useconffile, normaliseconf: *normaliseconf, confjson: *confjson, autoconf: *autoconf, ver: *ver, logto: *logto, getaddr: *getaddr, getsnet: *getsnet, loglevel: *loglevel, } } // The main function is responsible for configuring and starting Yggdrasil. func run(args yggArgs, ctx context.Context, done chan struct{}) { defer close(done) // Create a new logger that logs output to stdout. var logger *log.Logger switch args.logto { case "stdout": logger = log.New(os.Stdout, "", log.Flags()) case "syslog": if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil { logger = log.New(syslogger, "", log.Flags()) } default: if logfd, err := os.OpenFile(args.logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { logger = log.New(logfd, "", log.Flags()) } } if logger == nil { logger = log.New(os.Stdout, "", log.Flags()) logger.Warnln("Logging defaulting to stdout") } if args.normaliseconf { setLogLevel("error", logger) } else { setLogLevel(args.loglevel, logger) } var cfg *config.NodeConfig var err error switch { case args.ver: fmt.Println("Build name:", version.BuildName()) fmt.Println("Build version:", version.BuildVersion()) return case args.autoconf: // Use an autoconf-generated config, this will give us random keys and // port numbers, and will use an automatically selected TUN/TAP interface. cfg = defaults.GenerateConfig() case args.useconffile != "" || args.useconf: // Read the configuration from either stdin or from the filesystem cfg = readConfig(logger, args.useconf, args.useconffile, args.normaliseconf) // If the -normaliseconf option was specified then remarshal the above // configuration and print it back to stdout. This lets the user update // their configuration file with newly mapped names (like above) or to // convert from plain JSON to commented HJSON. if args.normaliseconf { var bs []byte if args.confjson { bs, err = json.MarshalIndent(cfg, "", " ") } else { bs, err = hjson.Marshal(cfg) } if err != nil { panic(err) } fmt.Println(string(bs)) return } case args.genconf: // Generate a new configuration and print it to stdout. fmt.Println(doGenconf(args.confjson)) return default: // No flags were provided, therefore print the list of flags to stdout. flag.PrintDefaults() } // Have we got a working configuration? If we don't then it probably means // that neither -autoconf, -useconf or -useconffile were set above. Stop // if we don't. if cfg == nil { return } // Have we been asked for the node address yet? If so, print it and then stop. getNodeKey := func() ed25519.PublicKey { if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil { return ed25519.PrivateKey(pubkey).Public().(ed25519.PublicKey) } return nil } switch { case args.getaddr: if key := getNodeKey(); key != nil { addr := address.AddrForKey(key) ip := net.IP(addr[:]) fmt.Println(ip.String()) } return case args.getsnet: if key := getNodeKey(); key != nil { snet := address.SubnetForKey(key) ipnet := net.IPNet{ IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0), Mask: net.CIDRMask(len(snet)*8, 128), } fmt.Println(ipnet.String()) } return default: } // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. n := node{config: cfg} // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate if err = n.core.Start(cfg, logger); err != nil { logger.Errorln("An error occurred during startup") panic(err) } // Register the session firewall gatekeeper function // Allocate our modules n.admin = &admin.AdminSocket{} n.multicast = &multicast.Multicast{} n.tuntap = &tuntap.TunAdapter{} // Start the admin socket if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising admin socket:", err) } else if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } n.admin.SetupAdminHandlers(n.admin) // Start the multicast interface if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) } else if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } n.multicast.SetupAdminHandlers(n.admin) // Start the TUN/TAP interface rwc := ipv6rwc.NewReadWriteCloser(&n.core) if err := n.tuntap.Init(rwc, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising TUN/TAP:", err) } else if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } n.tuntap.SetupAdminHandlers(n.admin) // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() subnet := n.core.Subnet() public := n.core.GetSelf().Key logger.Infof("Your public key is %s", hex.EncodeToString(public[:])) logger.Infof("Your IPv6 address is %s", address.String()) logger.Infof("Your IPv6 subnet is %s", subnet.String()) // Catch interrupts from the operating system to exit gracefully. <-ctx.Done() // Capture the service being stopped on Windows. minwinsvc.SetOnExit(n.shutdown) n.shutdown() } func (n *node) shutdown() { _ = n.admin.Stop() _ = n.multicast.Stop() _ = n.tuntap.Stop() n.core.Stop() } func main() { args := getArgs() hup := make(chan os.Signal, 1) //signal.Notify(hup, os.Interrupt, syscall.SIGHUP) term := make(chan os.Signal, 1) signal.Notify(term, os.Interrupt, syscall.SIGTERM) for { done := make(chan struct{}) ctx, cancel := context.WithCancel(context.Background()) go run(args, ctx, done) select { case <-hup: cancel() <-done case <-term: cancel() <-done return case <-done: return } } } yggdrasil-go-0.4.3/cmd/yggdrasilctl/000077500000000000000000000000001417776402100173525ustar00rootroot00000000000000yggdrasil-go-0.4.3/cmd/yggdrasilctl/cmd_line_env.go000066400000000000000000000063241417776402100223300ustar00rootroot00000000000000package main import ( "bytes" "flag" "fmt" "io/ioutil" "log" "os" "github.com/hjson/hjson-go" "golang.org/x/text/encoding/unicode" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) type CmdLineEnv struct { args []string endpoint, server string injson, verbose, ver bool } func newCmdLineEnv() CmdLineEnv { var cmdLineEnv CmdLineEnv cmdLineEnv.endpoint = defaults.GetDefaults().DefaultAdminListen return cmdLineEnv } func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() { flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0]) fmt.Println("Options:") flag.PrintDefaults() fmt.Println() fmt.Println("Please note that options must always specified BEFORE the command\non the command line or they will be ignored.") fmt.Println() fmt.Println("Commands:\n - Use \"list\" for a list of available commands") fmt.Println() fmt.Println("Examples:") fmt.Println(" - ", os.Args[0], "list") fmt.Println(" - ", os.Args[0], "getPeers") fmt.Println(" - ", os.Args[0], "-v getSelf") 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", cmdLineEnv.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)") ver := flag.Bool("version", false, "Prints the version of this build") flag.Parse() cmdLineEnv.args = flag.Args() cmdLineEnv.server = *server cmdLineEnv.injson = *injson cmdLineEnv.verbose = *verbose cmdLineEnv.ver = *ver } func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) { if cmdLineEnv.server == cmdLineEnv.endpoint { if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) || bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) { 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 != "") { cmdLineEnv.endpoint = ep logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile) logger.Println("Using endpoint", cmdLineEnv.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 { cmdLineEnv.endpoint = cmdLineEnv.server logger.Println("Using endpoint", cmdLineEnv.endpoint, "from command line") } } yggdrasil-go-0.4.3/cmd/yggdrasilctl/main.go000066400000000000000000000315221417776402100206300ustar00rootroot00000000000000package main import ( "bytes" "encoding/json" "errors" "flag" "fmt" "log" "net" "net/url" "os" "sort" "strconv" "strings" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) type admin_info map[string]interface{} func main() { // makes sure we can use defer and still return an error code to the OS os.Exit(run()) } func run() int { logbuffer := &bytes.Buffer{} logger := log.New(logbuffer, "", log.Flags()) defer func() int { if r := recover(); r != nil { logger.Println("Fatal error:", r) fmt.Print(logbuffer) return 1 } return 0 }() cmdLineEnv := newCmdLineEnv() cmdLineEnv.parseFlagsAndArgs() if cmdLineEnv.ver { fmt.Println("Build name:", version.BuildName()) fmt.Println("Build version:", version.BuildVersion()) fmt.Println("To get the version number of the running Yggdrasil node, run", os.Args[0], "getSelf") return 0 } if len(cmdLineEnv.args) == 0 { flag.Usage() return 0 } cmdLineEnv.setEndpoint(logger) conn := connect(cmdLineEnv.endpoint, logger) logger.Println("Connected") defer conn.Close() decoder := json.NewDecoder(conn) encoder := json.NewEncoder(conn) send := make(admin_info) recv := make(admin_info) for c, a := range cmdLineEnv.args { if c == 0 { if strings.HasPrefix(a, "-") { logger.Printf("Ignoring flag %s as it should be specified before other parameters\n", a) continue } logger.Printf("Sending request: %v\n", a) send["request"] = a continue } tokens := strings.Split(a, "=") if len(tokens) == 1 { send[tokens[0]] = true } else if len(tokens) > 2 { send[tokens[0]] = strings.Join(tokens[1:], "=") } else if len(tokens) == 2 { 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]) { case "true": send[tokens[0]] = true case "false": send[tokens[0]] = false 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("Admin socket returned an error:", err) } else { fmt.Println("Admin socket returned an error but didn't specify any error text") } return 1 } if _, ok := recv["request"]; !ok { fmt.Println("Missing request in response (malformed response?)") return 1 } if _, ok := recv["response"]; !ok { fmt.Println("Missing response body (malformed response?)") return 1 } res := recv["response"].(map[string]interface{}) if cmdLineEnv.injson { if json, err := json.MarshalIndent(res, "", " "); err == nil { fmt.Println(string(json)) } return 0 } handleAll(recv, cmdLineEnv.verbose) } else { logger.Println("Error receiving response:", err) } if v, ok := recv["status"]; ok && v != "success" { return 1 } return 0 } func connect(endpoint string, logger *log.Logger) net.Conn { var conn net.Conn u, err := url.Parse(endpoint) if err == nil { switch strings.ToLower(u.Scheme) { case "unix": 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 { logger.Println("Connecting to TCP socket", u.Host) conn, err = net.Dial("tcp", endpoint) } if err != nil { panic(err) } return conn } func handleAll(recv map[string]interface{}, verbose bool) { req := recv["request"].(map[string]interface{}) res := recv["response"].(map[string]interface{}) switch strings.ToLower(req["request"].(string)) { case "dot": handleDot(res) case "list", "getpeers", "getswitchpeers", "getdht", "getsessions", "dhtping": handleVariousInfo(res, verbose) case "gettuntap", "settuntap": handleGetAndSetTunTap(res) case "getself": handleGetSelf(res, verbose) case "getswitchqueues": handleGetSwitchQueues(res) case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute", "removesourcesubnet", "removeroute": handleAddsAndRemoves(res) case "getallowedencryptionpublickeys": handleGetAllowedEncryptionPublicKeys(res) case "getmulticastinterfaces": handleGetMulticastInterfaces(res) case "getsourcesubnets": handleGetSourceSubnets(res) case "getroutes": handleGetRoutes(res) case "settunnelrouting": fallthrough case "gettunnelrouting": handleGetTunnelRouting(res) default: if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil { fmt.Println(string(json)) } } } func handleDot(res map[string]interface{}) { fmt.Println(res["dot"]) } func handleVariousInfo(res map[string]interface{}, verbose bool) { maxWidths := make(map[string]int) var keyOrder []string keysOrdered := false for _, tlv := range res { for slk, slv := range tlv.(map[string]interface{}) { if !keysOrdered { for k := range slv.(map[string]interface{}) { if !verbose { if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" { continue } } keyOrder = append(keyOrder, fmt.Sprint(k)) } sort.Strings(keyOrder) keysOrdered = true } for k, v := range slv.(map[string]interface{}) { if len(fmt.Sprint(slk)) > maxWidths["key"] { maxWidths["key"] = len(fmt.Sprint(slk)) } if len(fmt.Sprint(v)) > maxWidths[k] { maxWidths[k] = len(fmt.Sprint(v)) if maxWidths[k] < len(k) { maxWidths[k] = len(k) } } } } if len(keyOrder) > 0 { fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", "") for _, v := range keyOrder { fmt.Printf("%-"+fmt.Sprint(maxWidths[v])+"s ", v) } fmt.Println() } for slk, slv := range tlv.(map[string]interface{}) { fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", slk) for _, k := range keyOrder { preformatted := slv.(map[string]interface{})[k] var formatted string switch k { case "bytes_sent", "bytes_recvd": formatted = fmt.Sprintf("%d", uint(preformatted.(float64))) case "uptime", "last_seen": seconds := uint(preformatted.(float64)) % 60 minutes := uint(preformatted.(float64)/60) % 60 hours := uint(preformatted.(float64) / 60 / 60) formatted = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) default: formatted = fmt.Sprint(preformatted) } fmt.Printf("%-"+fmt.Sprint(maxWidths[k])+"s ", formatted) } fmt.Println() } } } func handleGetAndSetTunTap(res map[string]interface{}) { for k, v := range res { fmt.Println("Interface name:", k) if mtu, ok := v.(map[string]interface{})["mtu"].(float64); ok { fmt.Println("Interface MTU:", mtu) } if tap_mode, ok := v.(map[string]interface{})["tap_mode"].(bool); ok { fmt.Println("TAP mode:", tap_mode) } } } func handleGetSelf(res map[string]interface{}, verbose bool) { 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) } if boxSigKey, ok := v.(map[string]interface{})["key"].(string); ok { fmt.Println("Public key:", boxSigKey) } if coords, ok := v.(map[string]interface{})["coords"].(string); ok { fmt.Println("Coords:", coords) } if verbose { if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok { fmt.Println("Node ID:", nodeID) } 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) } } } } func handleGetSwitchQueues(res map[string]interface{}) { maximumqueuesize := float64(4194304) portqueues := make(map[float64]float64) portqueuesize := make(map[float64]float64) portqueuepackets := make(map[float64]float64) v := res["switchqueues"].(map[string]interface{}) if queuecount, ok := v["queues_count"].(float64); ok { fmt.Printf("Active queue count: %d queues\n", uint(queuecount)) } if queuesize, ok := v["queues_size"].(float64); ok { fmt.Printf("Active queue size: %d bytes\n", uint(queuesize)) } if highestqueuecount, ok := v["highest_queues_count"].(float64); ok { fmt.Printf("Highest queue count: %d queues\n", uint(highestqueuecount)) } if highestqueuesize, ok := v["highest_queues_size"].(float64); ok { fmt.Printf("Highest queue size: %d bytes\n", uint(highestqueuesize)) } if m, ok := v["maximum_queues_size"].(float64); ok { maximumqueuesize = m fmt.Printf("Maximum queue size: %d bytes\n", uint(maximumqueuesize)) } if queues, ok := v["queues"].([]interface{}); ok { if len(queues) != 0 { fmt.Println("Active queues:") for _, v := range queues { queueport := v.(map[string]interface{})["queue_port"].(float64) 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]++ portqueuesize[queueport] += queuesize portqueuepackets[queueport] += queuepackets queuesizepercent := (100 / maximumqueuesize) * queuesize fmt.Printf("- Switch port %d, Stream ID: %v, size: %d bytes (%d%% full), %d packets\n", uint(queueport), []byte(queueid), uint(queuesize), uint(queuesizepercent), uint(queuepackets)) } } } if len(portqueuesize) > 0 && len(portqueuepackets) > 0 { fmt.Println("Aggregated statistics by switchport:") for k, v := range portqueuesize { queuesizepercent := (100 / (portqueues[k] * maximumqueuesize)) * v fmt.Printf("- Switch port %d, size: %d bytes (%d%% full), %d packets\n", uint(k), uint(v), uint(queuesizepercent), uint(portqueuepackets[k])) } } } func handleAddsAndRemoves(res map[string]interface{}) { if _, ok := res["added"]; ok { for _, v := range res["added"].([]interface{}) { fmt.Println("Added:", fmt.Sprint(v)) } } if _, ok := res["not_added"]; ok { for _, v := range res["not_added"].([]interface{}) { fmt.Println("Not added:", fmt.Sprint(v)) } } if _, ok := res["removed"]; ok { for _, v := range res["removed"].([]interface{}) { fmt.Println("Removed:", fmt.Sprint(v)) } } if _, ok := res["not_removed"]; ok { for _, v := range res["not_removed"].([]interface{}) { fmt.Println("Not removed:", fmt.Sprint(v)) } } } func handleGetAllowedEncryptionPublicKeys(res map[string]interface{}) { if _, ok := res["allowed_box_pubs"]; !ok { fmt.Println("All connections are allowed") } else if res["allowed_box_pubs"] == nil { fmt.Println("All connections are allowed") } else { fmt.Println("Connections are allowed only from the following public box keys:") for _, v := range res["allowed_box_pubs"].([]interface{}) { fmt.Println("-", v) } } } func handleGetMulticastInterfaces(res map[string]interface{}) { if _, ok := res["multicast_interfaces"]; !ok { fmt.Println("No multicast interfaces found") } else if res["multicast_interfaces"] == nil { fmt.Println("No multicast interfaces found") } else { fmt.Println("Multicast peer discovery is active on:") for _, v := range res["multicast_interfaces"].([]interface{}) { fmt.Println("-", v) } } } func handleGetSourceSubnets(res map[string]interface{}) { 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) } } } func handleGetRoutes(res map[string]interface{}) { if routes, ok := res["routes"].(map[string]interface{}); !ok { fmt.Println("No routes found") } else { if res["routes"] == nil || len(routes) == 0 { fmt.Println("No routes found") } else { fmt.Println("Routes:") for k, v := range routes { if pv, ok := v.(string); ok { fmt.Println("-", k, " via ", pv) } } } } } func handleGetTunnelRouting(res map[string]interface{}) { if enabled, ok := res["enabled"].(bool); !ok { fmt.Println("Tunnel routing is disabled") } else if !enabled { fmt.Println("Tunnel routing is disabled") } else { fmt.Println("Tunnel routing is enabled") } } yggdrasil-go-0.4.3/contrib/000077500000000000000000000000001417776402100155575ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/ansible/000077500000000000000000000000001417776402100171745ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/ansible/genkeys.go000066400000000000000000000045631417776402100212000ustar00rootroot00000000000000/* This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/) */ package main import ( "crypto/ed25519" "encoding/hex" "flag" "fmt" "net" "os" "github.com/cheggaaa/pb/v3" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) var numHosts = flag.Int("hosts", 1, "number of host vars to generate") var keyTries = flag.Int("tries", 1000, "number of tries before taking the best keys") type keySet struct { priv []byte pub []byte ip string } func main() { flag.Parse() bar := pb.StartNew(*keyTries*2 + *numHosts) if *numHosts > *keyTries { println("Can't generate less keys than hosts.") return } var keys []keySet for i := 0; i < *numHosts+1; i++ { keys = append(keys, newKey()) bar.Increment() } keys = sortKeySetArray(keys) for i := 0; i < *keyTries-*numHosts-1; i++ { keys[0] = newKey() keys = bubbleUpTo(keys, 0) bar.Increment() } os.MkdirAll("host_vars", 0755) for i := 1; i <= *numHosts; i++ { os.MkdirAll(fmt.Sprintf("host_vars/%x", i), 0755) file, err := os.Create(fmt.Sprintf("host_vars/%x/vars", i)) if err != nil { return } defer file.Close() file.WriteString(fmt.Sprintf("yggdrasil_public_key: %v\n", hex.EncodeToString(keys[i].pub))) file.WriteString("yggdrasil_private_key: \"{{ vault_yggdrasil_private_key }}\"\n") file.WriteString(fmt.Sprintf("ansible_host: %v\n", keys[i].ip)) file, err = os.Create(fmt.Sprintf("host_vars/%x/vault", i)) if err != nil { return } defer file.Close() file.WriteString(fmt.Sprintf("vault_yggdrasil_private_key: %v\n", hex.EncodeToString(keys[i].priv))) bar.Increment() } bar.Finish() } func newKey() keySet { pub, priv, err := ed25519.GenerateKey(nil) if err != nil { panic(err) } ip := net.IP(address.AddrForKey(pub)[:]).String() return keySet{priv[:], pub[:], ip} } func isBetter(oldID, newID []byte) bool { for idx := range oldID { if newID[idx] < oldID[idx] { return true } if newID[idx] > oldID[idx] { return false } } return false } func sortKeySetArray(sets []keySet) []keySet { for i := 0; i < len(sets); i++ { sets = bubbleUpTo(sets, i) } return sets } func bubbleUpTo(sets []keySet, num int) []keySet { for i := 0; i < len(sets)-num-1; i++ { if isBetter(sets[i+1].pub, sets[i].pub) { var tmp = sets[i] sets[i] = sets[i+1] sets[i+1] = tmp } else { break } } return sets } yggdrasil-go-0.4.3/contrib/apparmor/000077500000000000000000000000001417776402100174005ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/apparmor/usr.bin.yggdrasil000066400000000000000000000005641417776402100226740ustar00rootroot00000000000000# Last Modified: Fri Oct 30 11:33:31 2020 #include /usr/bin/yggdrasil { #include #include capability net_admin, capability net_raw, /dev/net/tun rw, /proc/sys/net/core/somaxconn r, /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r, /etc/yggdrasil.conf rw, /run/yggdrasil.sock rw, } yggdrasil-go-0.4.3/contrib/busybox-init/000077500000000000000000000000001417776402100202135ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/busybox-init/S42yggdrasil000077500000000000000000000020451417776402100224200ustar00rootroot00000000000000#!/bin/sh CONFFILE="/etc/yggdrasil.conf" genconf() { /usr/bin/yggdrasil -genconf > "$1" return $? } probetun() { modprobe tun return $? } start() { if [ ! -f "$CONFFILE" ]; then printf 'Generating configuration file: ' if genconf "$CONFFILE"; then echo "OK" else echo "FAIL" return 1 fi fi if [ ! -e /dev/net/tun ]; then printf 'Inserting TUN module: ' if probetun; then echo "OK" else echo "FAIL" return 1 fi fi printf 'Starting yggdrasil: ' if start-stop-daemon -S -q -b -x /usr/bin/yggdrasil \ -- -useconffile "$CONFFILE"; then echo "OK" else echo "FAIL" fi } stop() { printf "Stopping yggdrasil: " if start-stop-daemon -K -q -x /usr/bin/yggdrasil; then echo "OK" else echo "FAIL" fi } reload() { printf "Reloading yggdrasil: " if start-stop-daemon -K -q -s HUP -x /usr/bin/yggdrasil; then echo "OK" else echo "FAIL" start fi } restart() { stop start } case "$1" in start|stop|restart|reload) "$1";; *) echo "Usage: $0 {start|stop|restart|reload}" exit 1 esac exit 0 yggdrasil-go-0.4.3/contrib/deb/000077500000000000000000000000001417776402100163115ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/deb/generate.sh000066400000000000000000000103461417776402100204430ustar00rootroot00000000000000#!/bin/sh # This is a lazy script to create a .deb for Debian/Ubuntu. It installs # yggdrasil and enables it in systemd. You can give it the PKGARCH= argument # i.e. PKGARCH=i386 sh contrib/deb/generate.sh 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 fi PKGBRANCH=$(basename `git name-rev --name-only HEAD`) PKGNAME=$(sh contrib/semver/name.sh) PKGVERSION=$(sh contrib/semver/version.sh --bare) PKGARCH=${PKGARCH-amd64} PKGFILE=$PKGNAME-$PKGVERSION-$PKGARCH.deb PKGREPLACES=yggdrasil if [ $PKGBRANCH = "master" ]; then PKGREPLACES=yggdrasil-develop fi if [ $PKGARCH = "amd64" ]; then GOARCH=amd64 GOOS=linux ./build elif [ $PKGARCH = "i386" ]; then GOARCH=386 GOOS=linux ./build elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build elif [ $PKGARCH = "mips" ]; then GOARCH=mips64 GOOS=linux ./build elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=6 ./build elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build elif [ $PKGARCH = "armel" ]; then GOARCH=arm GOOS=linux GOARM=5 ./build else echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64,armel" exit 1 fi echo "Building $PKGFILE" mkdir -p /tmp/$PKGNAME/ mkdir -p /tmp/$PKGNAME/debian/ mkdir -p /tmp/$PKGNAME/usr/bin/ mkdir -p /tmp/$PKGNAME/etc/systemd/system/ cat > /tmp/$PKGNAME/debian/changelog << EOF Please see https://github.com/yggdrasil-network/yggdrasil-go/ EOF echo 9 > /tmp/$PKGNAME/debian/compat cat > /tmp/$PKGNAME/debian/control << EOF Package: $PKGNAME Version: $PKGVERSION Section: contrib/net Priority: extra Architecture: $PKGARCH Replaces: $PKGREPLACES Conflicts: $PKGREPLACES Maintainer: Neil Alexander Description: Yggdrasil Network Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6 network. It is lightweight, self-arranging, supported on multiple platforms and allows pretty much any IPv6-capable application to communicate securely with other Yggdrasil nodes. EOF cat > /tmp/$PKGNAME/debian/copyright << EOF Please see https://github.com/yggdrasil-network/yggdrasil-go/ EOF cat > /tmp/$PKGNAME/debian/docs << EOF Please see https://github.com/yggdrasil-network/yggdrasil-go/ EOF cat > /tmp/$PKGNAME/debian/install << EOF usr/bin/yggdrasil usr/bin usr/bin/yggdrasilctl usr/bin etc/systemd/system/*.service etc/systemd/system EOF cat > /tmp/$PKGNAME/debian/postinst << EOF #!/bin/sh if ! getent group yggdrasil 2>&1 > /dev/null; then groupadd --system --force yggdrasil || echo "Failed to create group 'yggdrasil' - please create it manually and reinstall" fi if [ -f /etc/yggdrasil.conf ]; then mkdir -p /var/backups echo "Backing up configuration file to /var/backups/yggdrasil.conf.`date +%Y%m%d`" cp /etc/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d` echo "Normalising and updating /etc/yggdrasil.conf" /usr/bin/yggdrasil -useconf -normaliseconf < /var/backups/yggdrasil.conf.`date +%Y%m%d` > /etc/yggdrasil.conf chgrp yggdrasil /etc/yggdrasil.conf if command -v systemctl >/dev/null; then systemctl daemon-reload >/dev/null || true systemctl enable yggdrasil || true systemctl start yggdrasil || true fi else echo "Generating initial configuration file /etc/yggdrasil.conf" echo "Please familiarise yourself with this file before starting Yggdrasil" sh -c 'umask 0027 && /usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf' chgrp yggdrasil /etc/yggdrasil.conf fi EOF cat > /tmp/$PKGNAME/debian/prerm << EOF #!/bin/sh if command -v systemctl >/dev/null; then if systemctl is-active --quiet yggdrasil; then systemctl stop yggdrasil || true fi systemctl disable yggdrasil || true fi EOF cp yggdrasil /tmp/$PKGNAME/usr/bin/ cp yggdrasilctl /tmp/$PKGNAME/usr/bin/ cp contrib/systemd/*.service /tmp/$PKGNAME/etc/systemd/system/ tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \ usr/bin/yggdrasil usr/bin/yggdrasilctl \ etc/systemd/system/yggdrasil.service \ etc/systemd/system/yggdrasil-default-config.service tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian . echo 2.0 > /tmp/$PKGNAME/debian-binary ar -r $PKGFILE \ /tmp/$PKGNAME/debian-binary \ /tmp/$PKGNAME/control.tar.gz \ /tmp/$PKGNAME/data.tar.gz rm -rf /tmp/$PKGNAME yggdrasil-go-0.4.3/contrib/docker/000077500000000000000000000000001417776402100170265ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/docker/Dockerfile000066400000000000000000000012461417776402100210230ustar00rootroot00000000000000FROM docker.io/golang:alpine as builder COPY . /src WORKDIR /src ENV CGO_ENABLED=0 RUN apk add git && ./build && go build -o /src/genkeys cmd/genkeys/main.go FROM docker.io/alpine COPY --from=builder /src/yggdrasil /usr/bin/yggdrasil COPY --from=builder /src/yggdrasilctl /usr/bin/yggdrasilctl COPY --from=builder /src/genkeys /usr/bin/genkeys 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 [ "/usr/bin/entrypoint.sh" ] yggdrasil-go-0.4.3/contrib/docker/entrypoint.sh000077500000000000000000000003721417776402100216020ustar00rootroot00000000000000#!/usr/bin/env sh 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 $? yggdrasil-go-0.4.3/contrib/freebsd/000077500000000000000000000000001417776402100171715ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/freebsd/yggdrasil000066400000000000000000000042751417776402100211110ustar00rootroot00000000000000#!/bin/sh # # Put the yggdrasil and yggdrasilctl binaries into /usr/local/bin # Then copy this script into /etc/rc.d/yggdrasil # Finally, run: # 1. chmod +x /etc/rc.d/yggdrasil /usr/local/bin/{yggdrasil,yggdrasilctl} # 2. echo "yggdrasil_enable=yes" >> /etc/rc.d # 3. service yggdrasil start # # PROVIDE: yggdrasil # REQUIRE: networking # KEYWORD: . /etc/rc.subr name="yggdrasil" rcvar="yggdrasil_enable" start_cmd="${name}_start" stop_cmd="${name}_stop" pidfile="/var/run/yggdrasil/${name}.pid" command="/usr/sbin/daemon" command_args="-P ${pidfile} -r -f ${yggdrasil_command}" yggdrasil_start() { test ! -x /usr/local/bin/yggdrasil && ( logger -s -t yggdrasil "Warning: /usr/local/bin/yggdrasil is missing or not executable" logger -s -t yggdrasil "Copy the yggdrasil binary into /usr/local/bin and then chmod +x /usr/local/bin/yggdrasil" return 1 ) test ! -f /etc/yggdrasil.conf && ( logger -s -t yggdrasil "Generating new configuration file into /etc/yggdrasil.conf" /usr/local/bin/yggdrasil -genconf > /etc/yggdrasil.conf ) tap_path="$(cat /etc/yggdrasil.conf | egrep -o '/dev/tap[0-9]{1,2}$')" tap_name="$(echo -n ${tap_path} | tr -d '/dev/')" /sbin/ifconfig ${tap_name} >/dev/null 2>&1 || ( logger -s -t yggdrasil "Creating ${tap_name} adapter" /sbin/ifconfig ${tap_name} create || logger -s -t yggdrasil "Failed to create ${tap_name} adapter" ) test ! -d /var/run/yggdrasil && mkdir -p /var/run/yggdrasil logger -s -t yggdrasil "Starting yggdrasil" ${command} ${command_args} /usr/local/bin/yggdrasil -useconffile /etc/yggdrasil.conf \ 1>/var/log/yggdrasil.stdout.log \ 2>/var/log/yggdrasil.stderr.log & } yggdrasil_stop() { logger -s -t yggdrasil "Stopping yggdrasil" test -f /var/run/yggdrasil/${name}.pid && kill -TERM $(cat /var/run/yggdrasil/${name}.pid) tap_path="$(cat /etc/yggdrasil.conf | grep /dev/tap | egrep -o '/dev/.*$')" tap_name="$(echo -n ${tap_path} | tr -d '/dev/')" /sbin/ifconfig ${tap_name} >/dev/null 2>&1 && ( logger -s -t yggdrasil "Destroying ${tap_name} adapter" /sbin/ifconfig ${tap_name} destroy || logger -s -t yggdrasil "Failed to destroy ${tap_name} adapter" ) } load_rc_config $name : ${yggdrasil_enable:=no} run_rc_command "$1" yggdrasil-go-0.4.3/contrib/logo/000077500000000000000000000000001417776402100165175ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/logo/ygg-neilalexander.svg000066400000000000000000000342151417776402100226440ustar00rootroot00000000000000 image/svg+xml yggdrasil-go-0.4.3/contrib/macos/000077500000000000000000000000001417776402100166615ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/macos/create-pkg.sh000077500000000000000000000120301417776402100212360ustar00rootroot00000000000000#!/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) ) # Build Yggdrasil echo "running GO111MODULE=on GOOS=darwin GOARCH=${PKGARCH-amd64} ./build" GO111MODULE=on GOOS=darwin GOARCH=${PKGARCH-amd64} ./build # 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 # 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 # 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 --bare) PKGARCH=${PKGARCH-amd64} PAYLOADSIZE=$(( $(wc -c pkgbuild/flat/base.pkg/Payload | awk '{ print $1 }') / 1024 )) [ "$PKGARCH" = "amd64" ] && PKGHOSTARCH="x86_64" || PKGHOSTARCH=${PKGARCH} # 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 (${PKGNAME}-${PKGVERSION}) #base.pkg EOF # Finally pack the .pkg ( cd pkgbuild/flat && xar --compression none -cf "../../${PKGNAME}-${PKGVERSION}-macos-${PKGARCH}.pkg" * ) yggdrasil-go-0.4.3/contrib/macos/yggdrasil.plist000066400000000000000000000013261417776402100217250ustar00rootroot00000000000000 Label yggdrasil ProgramArguments sh -c /usr/local/bin/yggdrasil -useconffile /etc/yggdrasil.conf KeepAlive RunAtLoad ProcessType Interactive StandardOutPath /tmp/yggdrasil.stdout.log StandardErrorPath /tmp/yggdrasil.stderr.log yggdrasil-go-0.4.3/contrib/mobile/000077500000000000000000000000001417776402100170265ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/mobile/build000077500000000000000000000024541417776402100200600ustar00rootroot00000000000000#!/bin/sh set -ef [ ! -d contrib/mobile ] && (echo "Must run ./contrib/mobile/build [-i] [-a] from the repository top level folder"; exit 1) PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" ARGS="-v" while getopts "aitc:l:d" option do case "$option" in i) IOS=true;; a) ANDROID=true;; t) TABLES=true;; c) GCFLAGS="$GCFLAGS $OPTARG";; l) LDFLAGS="$LDFLAGS $OPTARG";; d) ARGS="$ARGS -tags debug" DEBUG=true;; esac done if [ -z $TABLES ] && [ -z $DEBUG ]; then LDFLAGS="$LDFLAGS -s -w" fi if [ ! $IOS ] && [ ! $ANDROID ]; then echo "Must specify -a (Android), -i (iOS) or both" exit 1 fi if [ $IOS ]; then echo "Building framework for iOS" go get golang.org/x/mobile/bind gomobile bind \ -target ios -tags mobile -o Yggdrasil.xcframework \ -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ ./contrib/mobile ./src/config; fi if [ $ANDROID ]; then echo "Building aar for Android" go get golang.org/x/mobile/bind gomobile bind \ -target android -tags mobile -o yggdrasil.aar \ -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \ ./contrib/mobile ./src/config; fi yggdrasil-go-0.4.3/contrib/mobile/mobile.go000066400000000000000000000111351417776402100206250ustar00rootroot00000000000000package mobile import ( "encoding/hex" "encoding/json" "fmt" "net" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/version" _ "golang.org/x/mobile/bind" ) // Yggdrasil mobile package is meant to "plug the gap" for mobile support, as // Gomobile will not create headers for Swift/Obj-C etc if they have complex // (non-native) types. Therefore for iOS we will expose some nice simple // functions. Note that in the case of iOS we handle reading/writing to/from TUN // in Swift therefore we use the "dummy" TUN interface instead. type Yggdrasil struct { core core.Core iprwc *ipv6rwc.ReadWriteCloser config *config.NodeConfig multicast multicast.Multicast log MobileLogger } // StartAutoconfigure starts a node with a randomly generated config func (m *Yggdrasil) StartAutoconfigure() error { return m.StartJSON([]byte("{}")) } // StartJSON starts a node with the given JSON config. You can get JSON config // (rather than HJSON) by using the GenerateConfigJSON() function func (m *Yggdrasil) StartJSON(configjson []byte) error { logger := log.New(m.log, "", 0) logger.EnableLevel("error") logger.EnableLevel("warn") logger.EnableLevel("info") m.config = defaults.GenerateConfig() if err := json.Unmarshal(configjson, &m.config); err != nil { return err } m.config.IfName = "none" if err := m.core.Start(m.config, logger); err != nil { logger.Errorln("An error occured starting Yggdrasil:", err) return err } mtu := m.config.IfMTU m.iprwc = ipv6rwc.NewReadWriteCloser(&m.core) if m.iprwc.MaxMTU() < mtu { mtu = m.iprwc.MaxMTU() } m.iprwc.SetMTU(mtu) if len(m.config.MulticastInterfaces) > 0 { if err := m.multicast.Init(&m.core, m.config, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) return err } if err := m.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) return err } } return nil } // Send sends a packet to Yggdrasil. It should be a fully formed // IPv6 packet func (m *Yggdrasil) Send(p []byte) error { if m.iprwc == nil { return nil } _, _ = m.iprwc.Write(p) return nil } // Recv waits for and reads a packet coming from Yggdrasil. It // will be a fully formed IPv6 packet func (m *Yggdrasil) Recv() ([]byte, error) { if m.iprwc == nil { return nil, nil } var buf [65535]byte n, _ := m.iprwc.Read(buf[:]) return buf[:n], nil } // Stop the mobile Yggdrasil instance func (m *Yggdrasil) Stop() error { logger := log.New(m.log, "", 0) logger.EnableLevel("info") logger.Infof("Stop the mobile Yggdrasil instance %s", "") if err := m.multicast.Stop(); err != nil { return err } m.core.Stop() return nil } // GenerateConfigJSON generates mobile-friendly configuration in JSON format func GenerateConfigJSON() []byte { nc := defaults.GenerateConfig() nc.IfName = "none" if json, err := json.Marshal(nc); err == nil { return json } return nil } // GetAddressString gets the node's IPv6 address func (m *Yggdrasil) GetAddressString() string { ip := m.core.Address() return ip.String() } // GetSubnetString gets the node's IPv6 subnet in CIDR notation func (m *Yggdrasil) GetSubnetString() string { subnet := m.core.Subnet() return subnet.String() } // GetPublicKeyString gets the node's public key in hex form func (m *Yggdrasil) GetPublicKeyString() string { return hex.EncodeToString(m.core.GetSelf().Key) } // GetCoordsString gets the node's coordinates func (m *Yggdrasil) GetCoordsString() string { return fmt.Sprintf("%v", m.core.GetSelf().Coords) } func (m *Yggdrasil) GetPeersJSON() (result string) { peers := []struct { core.Peer IP string }{} for _, v := range m.core.GetPeers() { a := address.AddrForKey(v.Key) ip := net.IP(a[:]).String() peers = append(peers, struct { core.Peer IP string }{ Peer: v, IP: ip, }) } if res, err := json.Marshal(peers); err == nil { return string(res) } else { return "{}" } } func (m *Yggdrasil) GetDHTJSON() (result string) { if res, err := json.Marshal(m.core.GetDHT()); err == nil { return string(res) } else { return "{}" } } // GetMTU returns the configured node MTU. This must be called AFTER Start. func (m *Yggdrasil) GetMTU() int { return int(m.core.MTU()) } func GetVersion() string { return version.BuildVersion() } yggdrasil-go-0.4.3/contrib/mobile/mobile_android.go000066400000000000000000000002701417776402100223230ustar00rootroot00000000000000// +build android package mobile import "log" type MobileLogger struct{} func (nsl MobileLogger) Write(p []byte) (n int, err error) { log.Println(string(p)) return len(p), nil } yggdrasil-go-0.4.3/contrib/mobile/mobile_ios.go000066400000000000000000000007161417776402100215020ustar00rootroot00000000000000// +build ios package mobile /* #cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Foundation #import void Log(const char *text) { NSString *nss = [NSString stringWithUTF8String:text]; NSLog(@"%@", nss); } */ import "C" import ( "unsafe" ) type MobileLogger struct { } func (nsl MobileLogger) Write(p []byte) (n int, err error) { p = append(p, 0) cstr := (*C.char)(unsafe.Pointer(&p[0])) C.Log(cstr) return len(p), nil } yggdrasil-go-0.4.3/contrib/mobile/mobile_other.go000066400000000000000000000002761417776402100220320ustar00rootroot00000000000000// +build !android,!ios package mobile import "fmt" type MobileLogger struct { } func (nsl MobileLogger) Write(p []byte) (n int, err error) { fmt.Print(string(p)) return len(p), nil } yggdrasil-go-0.4.3/contrib/mobile/mobile_test.go000066400000000000000000000006331417776402100216650ustar00rootroot00000000000000package mobile import "testing" func TestStartYggdrasil(t *testing.T) { ygg := &Yggdrasil{} if err := ygg.StartAutoconfigure(); err != nil { t.Fatalf("Failed to start Yggdrasil: %s", err) } t.Log("Address:", ygg.GetAddressString()) t.Log("Subnet:", ygg.GetSubnetString()) t.Log("Coords:", ygg.GetCoordsString()) if err := ygg.Stop(); err != nil { t.Fatalf("Failed to stop Yggdrasil: %s", err) } } yggdrasil-go-0.4.3/contrib/msi/000077500000000000000000000000001417776402100163475ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/msi/build-msi.sh000066400000000000000000000154571417776402100206040ustar00rootroot00000000000000#!/bin/sh # This script generates an MSI file for Yggdrasil for a given architecture. It # needs to run on Windows within MSYS2 and Go 1.13 or later must be installed on # the system and within the PATH. This is ran currently by Appveyor (see # appveyor.yml in the repository root) for both x86 and x64. # # Author: Neil Alexander # Get arch from command line if given PKGARCH=$1 if [ "${PKGARCH}" == "" ]; then echo "tell me the architecture: x86, x64 or arm" exit 1 fi # Get the rest of the repository history. This is needed within Appveyor because # otherwise we don't get all of the branch histories and therefore the semver # scripts don't work properly. if [ "${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH}" != "" ]; then git fetch --all # git checkout ${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH} elif [ "${APPVEYOR_REPO_BRANCH}" != "" ]; then git fetch --all git checkout ${APPVEYOR_REPO_BRANCH} fi # Install prerequisites within MSYS2 pacman -S --needed --noconfirm unzip git curl # Download the wix tools! if [ ! -d wixbin ]; then curl -LO https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip if [ `md5sum wix311-binaries.zip | cut -f 1 -d " "` != "47a506f8ab6666ee3cc502fb07d0ee2a" ]; then echo "wix package didn't match expected checksum" exit 1 fi mkdir -p wixbin unzip -o wix311-binaries.zip -d wixbin || ( echo "failed to unzip WiX" exit 1 ) fi # Build Yggdrasil! [ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build [ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build [ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build #[ "${PKGARCH}" == "arm64" ] && GOOS=windows GOARCH=arm64 CGO_ENABLED=0 ./build # Create the postinstall script cat > updateconfig.bat << EOF if not exist %ALLUSERSPROFILE%\\Yggdrasil ( mkdir %ALLUSERSPROFILE%\\Yggdrasil ) if not exist %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf ( if exist yggdrasil.exe ( yggdrasil.exe -genconf > %ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf ) ) EOF # Work out metadata for the package info PKGNAME=$(sh contrib/semver/name.sh) PKGVERSION=$(sh contrib/msi/msversion.sh --bare) PKGVERSIONMS=$(echo $PKGVERSION | tr - .) [ "${PKGARCH}" == "x64" ] && \ PKGGUID="77757838-1a23-40a5-a720-c3b43e0260cc" PKGINSTFOLDER="ProgramFiles64Folder" || \ PKGGUID="54a3294e-a441-4322-aefb-3bb40dd022bb" PKGINSTFOLDER="ProgramFilesFolder" # Download the Wintun driver if [ ! -d wintun ]; then curl -o wintun.zip https://www.wintun.net/builds/wintun-0.14.1.zip unzip wintun.zip fi if [ $PKGARCH = "x64" ]; then PKGWINTUNDLL=wintun/bin/amd64/wintun.dll elif [ $PKGARCH = "x86" ]; then PKGWINTUNDLL=wintun/bin/x86/wintun.dll elif [ $PKGARCH = "arm" ]; then PKGWINTUNDLL=wintun/bin/arm/wintun.dll #elif [ $PKGARCH = "arm64" ]; then # PKGWINTUNDLL=wintun/bin/arm64/wintun.dll else echo "wasn't sure which architecture to get wintun for" exit 1 fi if [ $PKGNAME != "master" ]; then PKGDISPLAYNAME="Yggdrasil Network (${PKGNAME} branch)" else PKGDISPLAYNAME="Yggdrasil Network" fi # Generate the wix.xml file cat > wix.xml << EOF NOT Installed AND NOT REMOVE EOF # Generate the MSI CANDLEFLAGS="-nologo" LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61" wixbin/candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \ wixbin/light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj yggdrasil-go-0.4.3/contrib/msi/msversion.sh000066400000000000000000000022671417776402100207370ustar00rootroot00000000000000#!/bin/sh # Get the last tag TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null) # Did getting the tag succeed? if [ $? != 0 ] || [ -z "$TAG" ]; then printf -- "unknown" exit 0 fi # Get the current branch BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null) # Did getting the branch succeed? if [ $? != 0 ] || [ -z "$BRANCH" ]; then BRANCH="master" fi # Split out into major, minor and patch numbers MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1) MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2) PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3 | awk -F"rc" '{print $1}') # Output in the desired format if [ $((PATCH)) -eq 0 ]; then printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" else printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))" fi # Add the build tag on non-master branches if [ "$BRANCH" != "master" ]; then BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null) # Did getting the count of commits since the tag succeed? if [ $? != 0 ] || [ -z "$BUILD" ]; then printf -- "-unknown" exit 0 fi # Is the build greater than zero? if [ $((BUILD)) -gt 0 ]; then printf -- "-%04d" "$((BUILD))" fi fiyggdrasil-go-0.4.3/contrib/openrc/000077500000000000000000000000001417776402100170455ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/openrc/yggdrasil000077500000000000000000000022421417776402100207600ustar00rootroot00000000000000#!/sbin/openrc-run description="An experiment in scalable routing as an encrypted IPv6 overlay network." CONFFILE="/etc/yggdrasil.conf" pidfile="/run/${RC_SVCNAME}.pid" command="/usr/bin/yggdrasil" extra_started_commands="reload" depend() { use net dns logger } start_pre() { if [ ! -f "${CONFFILE}" ]; then ebegin "Generating new configuration file into ${CONFFILE}" if ! eval ${command} -genconf > ${CONFFILE}; then eerror "Failed to generate configuration file" exit 1 fi fi if [ ! -e /dev/net/tun ]; then ebegin "Inserting TUN module" if ! modprobe tun; then eerror "Failed to insert TUN kernel module" exit 1 fi fi } start() { ebegin "Starting ${RC_SVCNAME}" start-stop-daemon --start --quiet \ --pidfile "${pidfile}" \ --make-pidfile \ --background \ --stdout /var/log/yggdrasil.stdout.log \ --stderr /var/log/yggdrasil.stderr.log \ --exec "${command}" -- -useconffile "${CONFFILE}" eend $? } reload() { ebegin "Reloading ${RC_SVCNAME}" start-stop-daemon --signal HUP --pidfile "${pidfile}" eend $? } stop() { ebegin "Stopping ${RC_SVCNAME}" start-stop-daemon --stop --pidfile "${pidfile}" --exec "${command}" eend $? } yggdrasil-go-0.4.3/contrib/semver/000077500000000000000000000000001417776402100170605ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/semver/name.sh000066400000000000000000000011501417776402100203310ustar00rootroot00000000000000#!/bin/sh # Get the current branch name BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) if [ -n "$APPVEYOR_PULL_REQUEST_NUMBER" ]; then printf "yggdrasil-pr%s" "$APPVEYOR_PULL_REQUEST_NUMBER" exit 0 # Complain if the git history is not available elif [ $? != 0 ] || [ -z "$BRANCH" ]; then printf "yggdrasil" exit 0 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 printf "yggdrasil" exit 0 fi # If it is something other than master, append it printf "yggdrasil-%s" "$BRANCH" yggdrasil-go-0.4.3/contrib/semver/version.sh000066400000000000000000000003301417776402100210750ustar00rootroot00000000000000#!/bin/sh case "$*" in *--bare*) # Remove the "v" prefix git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" | cut -c 2- ;; *) git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" ;; esac yggdrasil-go-0.4.3/contrib/systemd/000077500000000000000000000000001417776402100172475ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/systemd/yggdrasil-default-config.service000066400000000000000000000005441417776402100255060ustar00rootroot00000000000000[Unit] Description=yggdrasil default config generator ConditionPathExists=|!/etc/yggdrasil.conf ConditionFileNotEmpty=|!/etc/yggdrasil.conf Wants=local-fs.target After=local-fs.target [Service] Type=oneshot Group=yggdrasil StandardOutput=file:/etc/yggdrasil.conf ExecStart=/usr/bin/yggdrasil -genconf ExecStartPost=/usr/bin/chmod 0640 /etc/yggdrasil.conf yggdrasil-go-0.4.3/contrib/systemd/yggdrasil.service000066400000000000000000000007771417776402100226310ustar00rootroot00000000000000[Unit] Description=yggdrasil Wants=network-online.target Wants=yggdrasil-default-config.service After=network-online.target After=yggdrasil-default-config.service [Service] Group=yggdrasil ProtectHome=true ProtectSystem=true SyslogIdentifier=yggdrasil CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE ExecStartPre=+-/sbin/modprobe tun ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf ExecReload=/bin/kill -HUP $MAINPID Restart=always TimeoutStopSec=5 [Install] WantedBy=multi-user.target yggdrasil-go-0.4.3/contrib/yggdrasil-brute-simple/000077500000000000000000000000001417776402100221525ustar00rootroot00000000000000yggdrasil-go-0.4.3/contrib/yggdrasil-brute-simple/LICENSE000066400000000000000000000173531417776402100231700ustar00rootroot00000000000000This software is released into the public domain. As such, it can be used under the Unlicense or CC0 public domain dedications. The Unlicense This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to CC0 1.0 Universal Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. For more information, please see yggdrasil-go-0.4.3/contrib/yggdrasil-brute-simple/Makefile000066400000000000000000000007741417776402100236220ustar00rootroot00000000000000.PHONY: all all: util yggdrasil-brute-multi-curve25519 yggdrasil-brute-multi-ed25519 util: util.c gcc -Wall -std=c89 -O3 -c -o util.o util.c yggdrasil-brute-multi-ed25519: yggdrasil-brute-multi-ed25519.c util.o gcc -Wall -std=c89 -O3 -o yggdrasil-brute-multi-ed25519 -lsodium yggdrasil-brute-multi-ed25519.c util.o yggdrasil-brute-multi-curve25519: yggdrasil-brute-multi-curve25519.c util.o gcc -Wall -std=c89 -O3 -o yggdrasil-brute-multi-curve25519 -lsodium yggdrasil-brute-multi-curve25519.c util.o yggdrasil-go-0.4.3/contrib/yggdrasil-brute-simple/README.md000066400000000000000000000006351417776402100234350ustar00rootroot00000000000000# yggdrasil-brute-simple Simple program for finding curve25519 and ed25519 public keys whose sha512 hash has many leading ones. Because ed25519 private keys consist of a seed that is hashed to find the secret part of the keypair, this program is near optimal for finding ed25519 keypairs. Curve25519 key generation, on the other hand, could be further optimized with elliptic curve magic. Depends on libsodium. yggdrasil-go-0.4.3/contrib/yggdrasil-brute-simple/util.c000066400000000000000000000024761417776402100233040ustar00rootroot00000000000000#include "yggdrasil-brute.h" int find_where(unsigned char hash[64], unsigned char besthashlist[NUMKEYS][64]) { /* Where to insert hash into sorted hashlist */ int j; int where = -1; for (j = 0; j < NUMKEYS; ++j) { if (memcmp(hash, besthashlist[j], 64) > 0) ++where; else break; } return where; } void insert_64(unsigned char itemlist[NUMKEYS][64], unsigned char item[64], int where) { int j; for (j = 0; j < where; ++j) { memcpy(itemlist[j], itemlist[j+1], 64); } memcpy(itemlist[where], item, 64); } void insert_32(unsigned char itemlist[NUMKEYS][32], unsigned char item[32], int where) { int j; for (j = 0; j < where; ++j) { memcpy(itemlist[j], itemlist[j+1], 32); } memcpy(itemlist[where], item, 32); } void make_addr(unsigned char addr[32], unsigned char hash[64]) { /* Public key hash to yggdrasil ipv6 address */ int i; int offset; unsigned char mask; unsigned char c; int ones = 0; unsigned char br = 0; /* false */ for (i = 0; i < 64 && !br; ++i) { mask = 128; c = hash[i]; while (mask) { if (c & mask) { ++ones; } else { br = 1; /* true */ break; } mask >>= 1; } } addr[0] = 2; addr[1] = ones; offset = ones + 1; for (i = 0; i < 14; ++i) { c = hash[offset/8] << (offset%8); c |= hash[offset/8 + 1] >> (8 - offset%8); addr[i + 2] = c; offset += 8; } } yggdrasil-go-0.4.3/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c000066400000000000000000000047411417776402100303100ustar00rootroot00000000000000/* sk: 32 random bytes sk[0] &= 248; sk[31] &= 127; sk[31] |= 64; increment sk pk = curve25519_scalarmult_base(mysecret) hash = sha512(pk) if besthash: bestsk = sk besthash = hash */ #include "yggdrasil-brute.h" void seed(unsigned char sk[32]) { randombytes_buf(sk, 32); sk[0] &= 248; sk[31] &= 127; sk[31] |= 64; } int main(int argc, char **argv) { int i; int j; unsigned char addr[16]; time_t starttime; time_t requestedtime; unsigned char bestsklist[NUMKEYS][32]; unsigned char bestpklist[NUMKEYS][32]; unsigned char besthashlist[NUMKEYS][64]; unsigned char sk[32]; unsigned char pk[32]; unsigned char hash[64]; unsigned int runs = 0; int where; if (argc != 2) { fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 \n"); return 1; } if (sodium_init() < 0) { /* panic! the library couldn't be initialized, it is not safe to use */ printf("sodium init failed!\n"); return 1; } starttime = time(NULL); requestedtime = atoi(argv[1]); if (requestedtime < 0) requestedtime = 0; fprintf(stderr, "Searching for yggdrasil curve25519 keys (this will take slightly longer than %ld seconds)\n", requestedtime); sodium_memzero(bestsklist, NUMKEYS * 32); sodium_memzero(bestpklist, NUMKEYS * 32); sodium_memzero(besthashlist, NUMKEYS * 64); seed(sk); do { /* generate pubkey, hash, compare, increment secret. * this loop should take 4 seconds on modern hardware */ for (i = 0; i < (1 << 16); ++i) { ++runs; if (crypto_scalarmult_curve25519_base(pk, sk) != 0) { printf("scalarmult to create pub failed!\n"); return 1; } crypto_hash_sha512(hash, pk, 32); where = find_where(hash, besthashlist); if (where >= 0) { insert_32(bestsklist, sk, where); insert_32(bestpklist, pk, where); insert_64(besthashlist, hash, where); seed(sk); } for (j = 1; j < 31; ++j) if (++sk[j]) break; } } while (time(NULL) - starttime < requestedtime || runs < NUMKEYS); fprintf(stderr, "--------------addr-------------- -----------------------------secret----------------------------- -----------------------------public-----------------------------\n"); for (i = 0; i < NUMKEYS; ++i) { make_addr(addr, besthashlist[i]); for (j = 0; j < 16; ++j) printf("%02x", addr[j]); printf(" "); for (j = 0; j < 32; ++j) printf("%02x", bestsklist[i][j]); printf(" "); for (j = 0; j < 32; ++j) printf("%02x", bestpklist[i][j]); printf("\n"); } sodium_memzero(bestsklist, NUMKEYS * 32); sodium_memzero(sk, 32); return 0; } yggdrasil-go-0.4.3/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c000066400000000000000000000050331417776402100275470ustar00rootroot00000000000000/* seed: 32 random bytes sk: sha512(seed) sk[0] &= 248 sk[31] &= 127 sk[31] |= 64 pk: scalarmult_ed25519_base(sk) increment seed generate sk generate pk hash = sha512(mypub) if besthash: bestseed = seed bestseckey = sk bestpubkey = pk besthash = hash */ #include "yggdrasil-brute.h" int main(int argc, char **argv) { int i; int j; time_t starttime; time_t requestedtime; unsigned char bestsklist[NUMKEYS][64]; /* sk contains pk */ unsigned char besthashlist[NUMKEYS][64]; unsigned char seed[32]; unsigned char sk[64]; unsigned char pk[32]; unsigned char hash[64]; unsigned int runs = 0; int where; if (argc != 2) { fprintf(stderr, "usage: ./yggdrasil-brute-multi-curve25519 \n"); return 1; } if (sodium_init() < 0) { /* panic! the library couldn't be initialized, it is not safe to use */ printf("sodium init failed!\n"); return 1; } starttime = time(NULL); requestedtime = atoi(argv[1]); if (requestedtime < 0) requestedtime = 0; fprintf(stderr, "Searching for yggdrasil ed25519 keys (this will take slightly longer than %ld seconds)\n", requestedtime); sodium_memzero(bestsklist, NUMKEYS * 64); sodium_memzero(besthashlist, NUMKEYS * 64); randombytes_buf(seed, 32); do { /* generate pubkey, hash, compare, increment secret. * this loop should take 4 seconds on modern hardware */ for (i = 0; i < (1 << 17); ++i) { ++runs; crypto_hash_sha512(sk, seed, 32); if (crypto_scalarmult_ed25519_base(pk, sk) != 0) { printf("scalarmult to create pub failed!\n"); return 1; } memcpy(sk + 32, pk, 32); crypto_hash_sha512(hash, pk, 32); /* insert into local list of good key */ where = find_where(hash, besthashlist); if (where >= 0) { insert_64(bestsklist, sk, where); insert_64(besthashlist, hash, where); randombytes_buf(seed, 32); } for (j = 1; j < 31; ++j) if (++seed[j]) break; } } while (time(NULL) - starttime < requestedtime || runs < NUMKEYS); fprintf(stderr, "!! Secret key is seed concatenated with public !!\n"); fprintf(stderr, "---hash--- ------------------------------seed------------------------------ -----------------------------public-----------------------------\n"); for (i = 0; i < NUMKEYS; ++i) { for (j = 0; j < 5; ++j) printf("%02x", besthashlist[i][j]); printf(" "); for (j = 0; j < 32; ++j) printf("%02x", bestsklist[i][j]); printf(" "); for (j = 32; j < 64; ++j) printf("%02x", bestsklist[i][j]); printf("\n"); } sodium_memzero(bestsklist, NUMKEYS * 64); sodium_memzero(sk, 64); sodium_memzero(seed, 32); return 0; } yggdrasil-go-0.4.3/contrib/yggdrasil-brute-simple/yggdrasil-brute.h000066400000000000000000000007501417776402100254310ustar00rootroot00000000000000#include #include /* printf */ #include /* memcpy */ #include /* atoi */ #include /* time */ #define NUMKEYS 10 void make_addr(unsigned char addr[32], unsigned char hash[64]); int find_where(unsigned char hash[64], unsigned char besthashlist[NUMKEYS][64]); void insert_64(unsigned char itemlist[NUMKEYS][64], unsigned char item[64], int where); void insert_32(unsigned char itemlist[NUMKEYS][32], unsigned char item[32], int where); yggdrasil-go-0.4.3/go.mod000066400000000000000000000020461417776402100152270ustar00rootroot00000000000000module github.com/yggdrasil-network/yggdrasil-go go 1.16 require ( github.com/Arceliar/ironwood v0.0.0-20211125050254-8951369625d0 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/VividCortex/ewma v1.2.0 // indirect github.com/cheggaaa/pb/v3 v3.0.8 github.com/fatih/color v1.12.0 // indirect github.com/gologme/log v1.2.0 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v3.1.0+incompatible github.com/kardianos/minwinsvc v1.0.0 github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a golang.zx2c4.com/wireguard/windows v0.4.12 ) yggdrasil-go-0.4.3/go.sum000066400000000000000000000245741417776402100152660ustar00rootroot00000000000000github.com/Arceliar/ironwood v0.0.0-20211125050254-8951369625d0 h1:QUqcb7BOcBU2p7Nax7pESOb8hrZYtI0Ts6j4v4mvcQo= github.com/Arceliar/ironwood v0.0.0-20211125050254-8951369625d0/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA= github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw= github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v1.0.0 h1:+JfAi8IBJna0jY2dJGZqi7o15z13JelFIklJCAENALA= github.com/kardianos/minwinsvc v1.0.0/go.mod h1:Bgd0oc+D0Qo3bBytmNtyRKVlp85dAloLKhfxanPFFRc= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 h1:jhDgkcu3yQ4tasBZ+1YwDmK7eFmuVf1w1k+NGGGxfmE= golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc= golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0= golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098 h1:YuekqPskqwCCPM79F1X5Dhv4ezTCj+Ki1oNwiafxkA0= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw= golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA= golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w= yggdrasil-go-0.4.3/misc/000077500000000000000000000000001417776402100150525ustar00rootroot00000000000000yggdrasil-go-0.4.3/misc/run-schannel-netns000077500000000000000000000050441417776402100205250ustar00rootroot00000000000000#!/bin/bash # Connects nodes in a network resembling an s-channel feynmann diagram. # 1 5 # \ / # 3--4 # / \ # 2 6 # Bandwidth constraints are applied to 4<->5 and 4<->6. # The idea is to make sure that bottlenecks on one link don't affect the other. ip netns add node1 ip netns add node2 ip netns add node3 ip netns add node4 ip netns add node5 ip netns add node6 ip link add veth13 type veth peer name veth31 ip link set veth13 netns node1 up ip link set veth31 netns node3 up ip link add veth23 type veth peer name veth32 ip link set veth23 netns node2 up ip link set veth32 netns node3 up ip link add veth34 type veth peer name veth43 ip link set veth34 netns node3 up ip link set veth43 netns node4 up ip link add veth45 type veth peer name veth54 ip link set veth45 netns node4 up ip link set veth54 netns node5 up ip link add veth46 type veth peer name veth64 ip link set veth46 netns node4 up ip link set veth64 netns node6 up ip netns exec node4 tc qdisc add dev veth45 root tbf rate 100mbit burst 8192 latency 1ms ip netns exec node5 tc qdisc add dev veth54 root tbf rate 100mbit burst 8192 latency 1ms ip netns exec node4 tc qdisc add dev veth46 root tbf rate 10mbit burst 8192 latency 1ms ip netns exec node6 tc qdisc add dev veth64 root tbf rate 10mbit burst 8192 latency 1ms ip netns exec node1 ip link set lo up ip netns exec node2 ip link set lo up ip netns exec node3 ip link set lo up ip netns exec node4 ip link set lo up ip netns exec node5 ip link set lo up ip netns exec node6 ip link set lo up echo '{AdminListen: "none"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node3 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node4 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node5 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null & echo '{AdminListen: "none"}' | ip netns exec node6 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null & echo "Started, to continue you should (possibly w/ sudo):" echo "kill" $(jobs -p) wait ip netns delete node1 ip netns delete node2 ip netns delete node3 ip netns delete node4 ip netns delete node5 ip netns delete node6 ip link delete veth13 ip link delete veth23 ip link delete veth34 ip link delete veth45 ip link delete veth46 yggdrasil-go-0.4.3/misc/run-twolink-test000077500000000000000000000023271417776402100202520ustar00rootroot00000000000000#!/bin/bash # Connects nodes in two namespaces by two links with different bandwidth (10mbit and 100mbit) ip netns add node1 ip netns add node2 ip link add veth11 type veth peer name veth21 ip link set veth11 netns node1 up ip link set veth21 netns node2 up ip link add veth12 type veth peer name veth22 ip link set veth12 netns node1 up ip link set veth22 netns node2 up ip netns exec node1 tc qdisc add dev veth11 root tbf rate 10mbit burst 8192 latency 1ms ip netns exec node2 tc qdisc add dev veth21 root tbf rate 10mbit burst 8192 latency 1ms ip netns exec node1 tc qdisc add dev veth12 root tbf rate 100mbit burst 8192 latency 1ms ip netns exec node2 tc qdisc add dev veth22 root tbf rate 100mbit burst 8192 latency 1ms echo '{AdminListen: "unix://node1.sock"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./yggdrasil -logging "info,warn,error,debug" -useconf &> node1.log & echo '{AdminListen: "unix://node2.sock"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./yggdrasil -logging "info,warn,error,debug" -useconf &> node2.log & echo "Started, to continue you should (possibly w/ sudo):" echo "kill" $(jobs -p) wait ip netns delete node1 ip netns delete node2 ip link delete veth11 ip link delete veth12 yggdrasil-go-0.4.3/src/000077500000000000000000000000001417776402100147065ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/address/000077500000000000000000000000001417776402100163335ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/address/address.go000066400000000000000000000121641417776402100203130ustar00rootroot00000000000000// Package address contains the types used by yggdrasil to represent IPv6 addresses or prefixes, as well as functions for working with these types. // Of particular importance are the functions used to derive addresses or subnets from a NodeID, or to get the NodeID and bitmask of the bits visible from an address, which is needed for DHT searches. package address import ( "crypto/ed25519" ) // Address represents an IPv6 address in the yggdrasil address range. type Address [16]byte // Subnet represents an IPv6 /64 subnet in the yggdrasil subnet range. type Subnet [8]byte // GetPrefix returns the address prefix used by yggdrasil. // The current implementation requires this to be a multiple of 8 bits + 7 bits. // The 8th bit of the last byte is used to signal nodes (0) or /64 prefixes (1). // Nodes that configure this differently will be unable to communicate with each other using IP packets, though routing and the DHT machinery *should* still work. func GetPrefix() [1]byte { return [...]byte{0x02} } // IsValid returns true if an address falls within the range used by nodes in the network. func (a *Address) IsValid() bool { prefix := GetPrefix() for idx := range prefix { if (*a)[idx] != prefix[idx] { return false } } return true } // IsValid returns true if a prefix falls within the range usable by the network. func (s *Subnet) IsValid() bool { prefix := GetPrefix() l := len(prefix) for idx := range prefix[:l-1] { if (*s)[idx] != prefix[idx] { return false } } return (*s)[l-1] == prefix[l-1]|0x01 } // AddrForKey takes an ed25519.PublicKey as an argument and returns an *Address. // This function returns nil if the key length is not ed25519.PublicKeySize. // This address begins with the contents of GetPrefix(), with the last bit set to 0 to indicate an address. // The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the public key. // The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address. func AddrForKey(publicKey ed25519.PublicKey) *Address { // 128 bit address // Begins with prefix // Next bit is a 0 // Next 7 bits, interpreted as a uint, are # of leading 1s in the NodeID // Leading 1s and first leading 0 of the NodeID are truncated off // The rest is appended to the IPv6 address (truncated to 128 bits total) if len(publicKey) != ed25519.PublicKeySize { return nil } var buf [ed25519.PublicKeySize]byte copy(buf[:], publicKey) for idx := range buf { buf[idx] = ^buf[idx] } var addr Address var temp = make([]byte, 0, 32) done := false ones := byte(0) bits := byte(0) nBits := 0 for idx := 0; idx < 8*len(buf); idx++ { bit := (buf[idx/8] & (0x80 >> byte(idx%8))) >> byte(7-(idx%8)) if !done && bit != 0 { ones++ continue } if !done && bit == 0 { done = true continue // FIXME? this assumes that ones <= 127, probably only worth changing by using a variable length uint64, but that would require changes to the addressing scheme, and I'm not sure ones > 127 is realistic } bits = (bits << 1) | bit nBits++ if nBits == 8 { nBits = 0 temp = append(temp, bits) } } prefix := GetPrefix() copy(addr[:], prefix[:]) addr[len(prefix)] = ones copy(addr[len(prefix)+1:], temp) return &addr } // SubnetForKey takes an ed25519.PublicKey as an argument and returns a *Subnet. // This function returns nil if the key length is not ed25519.PublicKeySize. // The subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix. // The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the key. // The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet. func SubnetForKey(publicKey ed25519.PublicKey) *Subnet { // Exactly as the address version, with two exceptions: // 1) The first bit after the fixed prefix is a 1 instead of a 0 // 2) It's truncated to a subnet prefix length instead of 128 bits addr := AddrForKey(publicKey) if addr == nil { return nil } var snet Subnet copy(snet[:], addr[:]) prefix := GetPrefix() snet[len(prefix)-1] |= 0x01 return &snet } // GetKet returns the partial ed25519.PublicKey for the Address. // This is used for key lookup. func (a *Address) GetKey() ed25519.PublicKey { var key [ed25519.PublicKeySize]byte prefix := GetPrefix() ones := int(a[len(prefix)]) for idx := 0; idx < ones; idx++ { key[idx/8] |= 0x80 >> byte(idx%8) } keyOffset := ones + 1 addrOffset := 8*len(prefix) + 8 for idx := addrOffset; idx < 8*len(a); idx++ { bits := a[idx/8] & (0x80 >> byte(idx%8)) bits <<= byte(idx % 8) keyIdx := keyOffset + (idx - addrOffset) bits >>= byte(keyIdx % 8) idx := keyIdx / 8 if idx >= len(key) { break } key[idx] |= bits } for idx := range key { key[idx] = ^key[idx] } return ed25519.PublicKey(key[:]) } // GetKet returns the partial ed25519.PublicKey for the Subnet. // This is used for key lookup. func (s *Subnet) GetKey() ed25519.PublicKey { var addr Address copy(addr[:], s[:]) return addr.GetKey() } yggdrasil-go-0.4.3/src/address/address_test.go000066400000000000000000000047021417776402100213510ustar00rootroot00000000000000package address import ( "bytes" "crypto/ed25519" "math/rand" "testing" ) func TestAddress_Address_IsValid(t *testing.T) { var address Address rand.Read(address[:]) address[0] = 0 if address.IsValid() { t.Fatal("invalid address marked as valid") } address[0] = 0x03 if address.IsValid() { t.Fatal("invalid address marked as valid") } address[0] = 0x02 if !address.IsValid() { t.Fatal("valid address marked as invalid") } } func TestAddress_Subnet_IsValid(t *testing.T) { var subnet Subnet rand.Read(subnet[:]) subnet[0] = 0 if subnet.IsValid() { t.Fatal("invalid subnet marked as valid") } subnet[0] = 0x02 if subnet.IsValid() { t.Fatal("invalid subnet marked as valid") } subnet[0] = 0x03 if !subnet.IsValid() { t.Fatal("valid subnet marked as invalid") } } func TestAddress_AddrForKey(t *testing.T) { publicKey := ed25519.PublicKey{ 189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 82, 86, 251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251, } expectedAddress := Address{ 2, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149, } if *AddrForKey(publicKey) != expectedAddress { t.Fatal("invalid address returned") } } func TestAddress_SubnetForKey(t *testing.T) { publicKey := ed25519.PublicKey{ 189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 82, 86, 251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251, } expectedSubnet := Subnet{3, 0, 132, 138, 96, 79, 187, 126} if *SubnetForKey(publicKey) != expectedSubnet { t.Fatal("invalid subnet returned") } } func TestAddress_Address_GetKey(t *testing.T) { address := Address{ 2, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149, } expectedPublicKey := ed25519.PublicKey{ 189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, } if !bytes.Equal(address.GetKey(), expectedPublicKey) { t.Fatal("invalid public key returned") } } func TestAddress_Subnet_GetKey(t *testing.T) { subnet := Subnet{3, 0, 132, 138, 96, 79, 187, 126} expectedPublicKey := ed25519.PublicKey{ 189, 186, 207, 216, 34, 64, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, } if !bytes.Equal(subnet.GetKey(), expectedPublicKey) { t.Fatal("invalid public key returned") } } yggdrasil-go-0.4.3/src/admin/000077500000000000000000000000001417776402100157765ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/admin/admin.go000066400000000000000000000177761417776402100174370ustar00rootroot00000000000000package admin import ( "encoding/json" "errors" "fmt" "net" "net/url" "os" "strings" "time" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/core" ) // TODO: Add authentication type AdminSocket struct { core *core.Core log *log.Logger listenaddr string listener net.Listener handlers map[string]handler done chan struct{} } type AdminSocketResponse struct { Status string `json:"status"` Request struct { Name string `json:"request"` KeepAlive bool `json:"keepalive"` } `json:"request"` Response interface{} `json:"response"` } type handler struct { args []string // List of human-readable argument names handler core.AddHandlerFunc // First is input map, second is output } type ListResponse struct { List map[string]ListEntry `json:"list"` } type ListEntry struct { Fields []string `json:"fields"` } // AddHandler is called for each admin function to add the handler and help documentation to the API. func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc core.AddHandlerFunc) error { if _, ok := a.handlers[strings.ToLower(name)]; ok { return errors.New("handler already exists") } a.handlers[strings.ToLower(name)] = handler{ args: args, handler: handlerfunc, } return nil } // Init runs the initial admin setup. func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error { a.core = c a.log = log a.handlers = make(map[string]handler) nc.RLock() a.listenaddr = nc.AdminListen nc.RUnlock() a.done = make(chan struct{}) close(a.done) // Start in a done / not-started state _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { res := &ListResponse{ List: map[string]ListEntry{}, } for name, handler := range a.handlers { res.List[name] = ListEntry{ Fields: handler.args, } } return res, nil }) a.core.SetAdmin(a) return nil } func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { _ = a.AddHandler("getSelf", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetSelfRequest{} res := &GetSelfResponse{} if err := json.Unmarshal(in, &req); err != nil { return nil, err } if err := a.getSelfHandler(req, res); err != nil { return nil, err } return res, nil }) _ = a.AddHandler("getPeers", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetPeersRequest{} res := &GetPeersResponse{} if err := json.Unmarshal(in, &req); err != nil { return nil, err } if err := a.getPeersHandler(req, res); err != nil { return nil, err } return res, nil }) _ = a.AddHandler("getDHT", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetDHTRequest{} res := &GetDHTResponse{} if err := json.Unmarshal(in, &req); err != nil { return nil, err } if err := a.getDHTHandler(req, res); err != nil { return nil, err } return res, nil }) _ = a.AddHandler("getPaths", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetPathsRequest{} res := &GetPathsResponse{} if err := json.Unmarshal(in, &req); err != nil { return nil, err } if err := a.getPathsHandler(req, res); err != nil { return nil, err } return res, nil }) _ = a.AddHandler("getSessions", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetSessionsRequest{} res := &GetSessionsResponse{} if err := json.Unmarshal(in, &req); err != nil { return nil, err } if err := a.getSessionsHandler(req, res); err != nil { return nil, err } return res, nil }) //_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler) //_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler) //_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler) //_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler) } // Start runs the admin API socket to listen for / respond to admin API calls. func (a *AdminSocket) Start() error { if a.listenaddr != "none" && a.listenaddr != "" { a.done = make(chan struct{}) go a.listen() } return nil } // IsStarted returns true if the module has been started. func (a *AdminSocket) IsStarted() bool { select { case <-a.done: // Not blocking, so we're not currently running return false default: // Blocked, so we must have started return true } } // Stop will stop the admin API and close the socket. func (a *AdminSocket) Stop() error { if a.listener != nil { select { case <-a.done: default: close(a.done) } return a.listener.Close() } return nil } // listen is run by start and manages API connections. func (a *AdminSocket) listen() { u, err := url.Parse(a.listenaddr) if err == nil { switch strings.ToLower(u.Scheme) { case "unix": if _, err := os.Stat(a.listenaddr[7:]); err == nil { a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up") if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() { a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process") os.Exit(1) } else { if err := os.Remove(a.listenaddr[7:]); err == nil { a.log.Debugln(a.listenaddr[7:], "was cleaned up") } else { a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err) os.Exit(1) } } } 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.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!") } } } case "tcp": a.listener, err = net.Listen("tcp", u.Host) default: // err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme)) a.listener, err = net.Listen("tcp", a.listenaddr) } } else { a.listener, err = net.Listen("tcp", a.listenaddr) } if err != nil { a.log.Errorf("Admin socket failed to listen: %v", err) os.Exit(1) } a.log.Infof("%s admin socket listening on %s", strings.ToUpper(a.listener.Addr().Network()), a.listener.Addr().String()) defer a.listener.Close() for { conn, err := a.listener.Accept() if err == nil { go a.handleRequest(conn) } else { select { case <-a.done: // Not blocked, so we havent started or already stopped return default: // Blocked, so we're supposed to keep running } } } } // handleRequest calls the request handler for each request sent to the admin API. func (a *AdminSocket) handleRequest(conn net.Conn) { decoder := json.NewDecoder(conn) decoder.DisallowUnknownFields() encoder := json.NewEncoder(conn) encoder.SetIndent("", " ") defer conn.Close() defer func() { r := recover() if r != nil { a.log.Debugln("Admin socket error:", r) if err := encoder.Encode(&ErrorResponse{ Error: "Check your syntax and input types", }); err != nil { a.log.Debugln("Admin socket JSON encode error:", err) } conn.Close() } }() for { var err error var buf json.RawMessage _ = decoder.Decode(&buf) var resp AdminSocketResponse resp.Status = "success" if err = json.Unmarshal(buf, &resp.Request); err == nil { if resp.Request.Name == "" { resp.Status = "error" resp.Response = &ErrorResponse{ Error: "No request specified", } } else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok { resp.Response, err = h.handler(buf) if err != nil { resp.Status = "error" resp.Response = &ErrorResponse{ Error: err.Error(), } } } else { resp.Status = "error" resp.Response = &ErrorResponse{ Error: fmt.Sprintf("Unknown action '%s', try 'list' for help", resp.Request.Name), } } } if err = encoder.Encode(resp); err != nil { a.log.Debugln("Encode error:", err) } if !resp.Request.KeepAlive { break } else { continue } } } yggdrasil-go-0.4.3/src/admin/error.go000066400000000000000000000001121417776402100174500ustar00rootroot00000000000000package admin type ErrorResponse struct { Error string `json:"error"` } yggdrasil-go-0.4.3/src/admin/getdht.go000066400000000000000000000012571417776402100176110ustar00rootroot00000000000000package admin import ( "encoding/hex" "net" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) type GetDHTRequest struct{} type GetDHTResponse struct { DHT map[string]DHTEntry `json:"dht"` } type DHTEntry struct { PublicKey string `json:"key"` Port uint64 `json:"port"` Rest uint64 `json:"rest"` } func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error { res.DHT = map[string]DHTEntry{} for _, d := range a.core.GetDHT() { addr := address.AddrForKey(d.Key) so := net.IP(addr[:]).String() res.DHT[so] = DHTEntry{ PublicKey: hex.EncodeToString(d.Key[:]), Port: d.Port, Rest: d.Rest, } } return nil } yggdrasil-go-0.4.3/src/admin/getpaths.go000066400000000000000000000012241417776402100201430ustar00rootroot00000000000000package admin import ( "encoding/hex" "net" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) type GetPathsRequest struct { } type GetPathsResponse struct { Paths map[string]PathEntry `json:"paths"` } type PathEntry struct { PublicKey string `json:"key"` Path []uint64 `json:"path"` } func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error { res.Paths = map[string]PathEntry{} for _, p := range a.core.GetPaths() { addr := address.AddrForKey(p.Key) so := net.IP(addr[:]).String() res.Paths[so] = PathEntry{ PublicKey: hex.EncodeToString(p.Key), Path: p.Path, } } return nil } yggdrasil-go-0.4.3/src/admin/getpeers.go000066400000000000000000000017251417776402100201500ustar00rootroot00000000000000package admin import ( "encoding/hex" "net" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) type GetPeersRequest struct { } type GetPeersResponse struct { Peers map[string]PeerEntry `json:"peers"` } type PeerEntry struct { PublicKey string `json:"key"` Port uint64 `json:"port"` Coords []uint64 `json:"coords"` Remote string `json:"remote"` RXBytes uint64 `json:"bytes_recvd"` TXBytes uint64 `json:"bytes_sent"` Uptime float64 `json:"uptime"` } func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error { res.Peers = map[string]PeerEntry{} for _, p := range a.core.GetPeers() { addr := address.AddrForKey(p.Key) so := net.IP(addr[:]).String() res.Peers[so] = PeerEntry{ PublicKey: hex.EncodeToString(p.Key), Port: p.Port, Coords: p.Coords, Remote: p.Remote, RXBytes: p.RXBytes, TXBytes: p.TXBytes, Uptime: p.Uptime.Seconds(), } } return nil } yggdrasil-go-0.4.3/src/admin/getself.go000066400000000000000000000015601417776402100177600ustar00rootroot00000000000000package admin import ( "encoding/hex" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) type GetSelfRequest struct{} type GetSelfResponse struct { Self map[string]SelfEntry `json:"self"` } type SelfEntry struct { BuildName string `json:"build_name"` BuildVersion string `json:"build_version"` PublicKey string `json:"key"` Coords []uint64 `json:"coords"` Subnet string `json:"subnet"` } func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error { res.Self = make(map[string]SelfEntry) self := a.core.GetSelf() addr := a.core.Address().String() snet := a.core.Subnet() res.Self[addr] = SelfEntry{ BuildName: version.BuildName(), BuildVersion: version.BuildVersion(), PublicKey: hex.EncodeToString(self.Key[:]), Subnet: snet.String(), Coords: self.Coords, } return nil } yggdrasil-go-0.4.3/src/admin/getsessions.go000066400000000000000000000012051417776402100206710ustar00rootroot00000000000000package admin import ( "encoding/hex" "net" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) type GetSessionsRequest struct{} type GetSessionsResponse struct { Sessions map[string]SessionEntry `json:"sessions"` } type SessionEntry struct { PublicKey string `json:"key"` } func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error { res.Sessions = map[string]SessionEntry{} for _, s := range a.core.GetSessions() { addr := address.AddrForKey(s.Key) so := net.IP(addr[:]).String() res.Sessions[so] = SessionEntry{ PublicKey: hex.EncodeToString(s.Key[:]), } } return nil } yggdrasil-go-0.4.3/src/config/000077500000000000000000000000001417776402100161535ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/config/config.go000066400000000000000000000120561417776402100177530ustar00rootroot00000000000000/* The config package contains structures related to the configuration of an Yggdrasil node. The configuration contains, amongst other things, encryption keys which are used to derive a node's identity, information about peerings and node information that is shared with the network. There are also some module-specific options related to TUN, multicast and the admin socket. In order for a node to maintain the same identity across restarts, you should persist the configuration onto the filesystem or into some configuration storage so that the encryption keys (and therefore the node ID) do not change. Note that Yggdrasil will automatically populate sane defaults for any configuration option that is not provided. */ package config import ( "crypto/ed25519" "encoding/hex" "sync" ) // NodeConfig is the main configuration structure, containing configuration // options that are necessary for an Yggdrasil node to run. You will need to // supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { sync.RWMutex `json:"-"` Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tls://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` 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."` MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."` AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."` PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` } type MulticastInterfaceConfig struct { Regex string Beacon bool Listen bool Port uint16 } // NewSigningKeys replaces the signing keypair in the NodeConfig with a new // signing keypair. The signing keys are used by the switch to derive the // structure of the spanning tree. func (cfg *NodeConfig) NewKeys() { spub, spriv, err := ed25519.GenerateKey(nil) if err != nil { panic(err) } cfg.PublicKey = hex.EncodeToString(spub[:]) cfg.PrivateKey = hex.EncodeToString(spriv[:]) } yggdrasil-go-0.4.3/src/config/config_test.go000066400000000000000000000017721417776402100210150ustar00rootroot00000000000000package config import ( "bytes" "encoding/hex" "testing" ) func TestConfig_Keys(t *testing.T) { var nodeConfig NodeConfig nodeConfig.NewKeys() publicKey1, err := hex.DecodeString(nodeConfig.PublicKey) if err != nil { t.Fatal("can not decode generated public key") } if len(publicKey1) == 0 { t.Fatal("empty public key generated") } privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey) if err != nil { t.Fatal("can not decode generated private key") } if len(privateKey1) == 0 { t.Fatal("empty private key generated") } nodeConfig.NewKeys() publicKey2, err := hex.DecodeString(nodeConfig.PublicKey) if err != nil { t.Fatal("can not decode generated public key") } if bytes.Equal(publicKey2, publicKey1) { t.Fatal("same public key generated") } privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey) if err != nil { t.Fatal("can not decode generated private key") } if bytes.Equal(privateKey2, privateKey1) { t.Fatal("same private key generated") } } yggdrasil-go-0.4.3/src/core/000077500000000000000000000000001417776402100156365ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/core/api.go000066400000000000000000000170031417776402100167370ustar00rootroot00000000000000package core import ( "crypto/ed25519" "sync/atomic" "time" //"encoding/hex" "encoding/json" //"errors" //"fmt" "net" "net/url" //"sort" //"time" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/address" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" //"github.com/Arceliar/phony" ) type Self struct { Key ed25519.PublicKey Root ed25519.PublicKey Coords []uint64 } type Peer struct { Key ed25519.PublicKey Root ed25519.PublicKey Coords []uint64 Port uint64 Remote string RXBytes uint64 TXBytes uint64 Uptime time.Duration } type DHTEntry struct { Key ed25519.PublicKey Port uint64 Rest uint64 } type PathEntry struct { Key ed25519.PublicKey Path []uint64 } type Session struct { Key ed25519.PublicKey } func (c *Core) GetSelf() Self { var self Self s := c.PacketConn.PacketConn.Debug.GetSelf() self.Key = s.Key self.Root = s.Root self.Coords = s.Coords return self } func (c *Core) GetPeers() []Peer { var peers []Peer names := make(map[net.Conn]string) c.links.mutex.Lock() for _, info := range c.links.links { names[info.conn] = info.lname } c.links.mutex.Unlock() ps := c.PacketConn.PacketConn.Debug.GetPeers() for _, p := range ps { var info Peer info.Key = p.Key info.Root = p.Root info.Coords = p.Coords info.Port = p.Port info.Remote = p.Conn.RemoteAddr().String() if name := names[p.Conn]; name != "" { info.Remote = name } if linkconn, ok := p.Conn.(*linkConn); ok { info.RXBytes = atomic.LoadUint64(&linkconn.rx) info.TXBytes = atomic.LoadUint64(&linkconn.tx) info.Uptime = time.Since(linkconn.up) } peers = append(peers, info) } return peers } func (c *Core) GetDHT() []DHTEntry { var dhts []DHTEntry ds := c.PacketConn.PacketConn.Debug.GetDHT() for _, d := range ds { var info DHTEntry info.Key = d.Key info.Port = d.Port info.Rest = d.Rest dhts = append(dhts, info) } return dhts } func (c *Core) GetPaths() []PathEntry { var paths []PathEntry ps := c.PacketConn.PacketConn.Debug.GetPaths() for _, p := range ps { var info PathEntry info.Key = p.Key info.Path = p.Path paths = append(paths, info) } return paths } func (c *Core) GetSessions() []Session { var sessions []Session ss := c.PacketConn.Debug.GetSessions() for _, s := range ss { var info Session info.Key = s.Key sessions = append(sessions, info) } return sessions } // Listen starts a new listener (either TCP or TLS). The input should be a url.URL // parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a // link-local address, the interface should be provided as the second argument. func (c *Core) Listen(u *url.URL, sintf string) (*TcpListener, error) { return c.links.tcp.listenURL(u, sintf) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // address. The IPv6 address is only relevant when the node is operating as an // IP router and often is meaningless when embedded into an application, unless // that application also implements either VPN functionality or deals with IP // packets specifically. func (c *Core) Address() net.IP { addr := net.IP(address.AddrForKey(c.public)[:]) return addr } // Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a // /64 subnet. The IPv6 subnet is only relevant when the node is operating as an // IP router and often is meaningless when embedded into an application, unless // that application also implements either VPN functionality or deals with IP // packets specifically. func (c *Core) Subnet() net.IPNet { subnet := address.SubnetForKey(c.public)[:] subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } // SetLogger sets the output logger of the Yggdrasil node after startup. This // may be useful if you want to redirect the output later. Note that this // expects a Logger from the github.com/gologme/log package and not from Go's // built-in log package. func (c *Core) SetLogger(log *log.Logger) { c.log = log } // AddPeer adds a peer. This should be specified in the peer URI format, e.g.: // tcp://a.b.c.d:e // socks://a.b.c.d:e/f.g.h.i:j // This adds the peer to the peer list, so that they will be called again if the // connection drops. /* func (c *Core) AddPeer(addr string, sintf string) error { if err := c.CallPeer(addr, sintf); err != nil { // TODO: We maybe want this to write the peer to the persistent // configuration even if a connection attempt fails, but first we'll need to // move the code to check the peer URI so that we don't deliberately save a // peer with a known bad URI. Loading peers from config should really do the // same thing too but I don't think that happens today return err } c.config.Mutex.Lock() defer c.config.Mutex.Unlock() if sintf == "" { for _, peer := range c.config.Current.Peers { if peer == addr { return errors.New("peer already added") } } c.config.Current.Peers = append(c.config.Current.Peers, addr) } else { if _, ok := c.config.Current.InterfacePeers[sintf]; ok { for _, peer := range c.config.Current.InterfacePeers[sintf] { if peer == addr { return errors.New("peer already added") } } } if _, ok := c.config.Current.InterfacePeers[sintf]; !ok { c.config.Current.InterfacePeers[sintf] = []string{addr} } else { c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) } } return nil } */ /* func (c *Core) RemovePeer(addr string, sintf string) error { if sintf == "" { for i, peer := range c.config.Current.Peers { if peer == addr { c.config.Current.Peers = append(c.config.Current.Peers[:i], c.config.Current.Peers[i+1:]...) break } } } else if _, ok := c.config.Current.InterfacePeers[sintf]; ok { for i, peer := range c.config.Current.InterfacePeers[sintf] { if peer == addr { c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf][:i], c.config.Current.InterfacePeers[sintf][i+1:]...) break } } } panic("TODO") // Get the net.Conn to this peer (if any) and close it c.peers.Act(nil, func() { ports := c.peers.ports for _, peer := range ports { if addr == peer.intf.name() { c.peers._removePeer(peer) } } }) return nil } */ // CallPeer calls a peer once. This should be specified in the peer URI format, // e.g.: // tcp://a.b.c.d:e // socks://a.b.c.d:e/f.g.h.i:j // This does not add the peer to the peer list, so if the connection drops, the // peer will not be called again automatically. func (c *Core) CallPeer(u *url.URL, sintf string) error { return c.links.call(u, sintf) } func (c *Core) PublicKey() ed25519.PublicKey { return c.public } // Hack to get the admin stuff working, TODO something cleaner type AddHandler interface { AddHandler(name string, args []string, handlerfunc AddHandlerFunc) error } type AddHandlerFunc func(json.RawMessage) (interface{}, error) // SetAdmin must be called after Init and before Start. // It sets the admin handler for NodeInfo and the Debug admin functions. func (c *Core) SetAdmin(a AddHandler) error { if err := a.AddHandler("getNodeInfo", []string{"key"}, c.proto.nodeinfo.nodeInfoAdminHandler); err != nil { return err } if err := a.AddHandler("debug_remoteGetSelf", []string{"key"}, c.proto.getSelfHandler); err != nil { return err } if err := a.AddHandler("debug_remoteGetPeers", []string{"key"}, c.proto.getPeersHandler); err != nil { return err } if err := a.AddHandler("debug_remoteGetDHT", []string{"key"}, c.proto.getDHTHandler); err != nil { return err } return nil } yggdrasil-go-0.4.3/src/core/core.go000066400000000000000000000137571417776402100171320ustar00rootroot00000000000000package core import ( "context" "crypto/ed25519" "encoding/hex" "errors" "fmt" "io/ioutil" "net" "net/url" "time" iwe "github.com/Arceliar/ironwood/encrypted" iwt "github.com/Arceliar/ironwood/types" "github.com/Arceliar/phony" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) // The Core object represents the Yggdrasil node. You should create a Core // 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 // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex phony.Inbox *iwe.PacketConn config *config.NodeConfig // Config secret ed25519.PrivateKey public ed25519.PublicKey links links proto protoHandler log *log.Logger addPeerTimer *time.Timer ctx context.Context ctxCancel context.CancelFunc } func (c *Core) _init() error { // TODO separate init and start functions // Init sets up structs // Start launches goroutines that depend on structs being set up // This is pretty much required to completely avoid race conditions c.config.RLock() defer c.config.RUnlock() if c.log == nil { c.log = log.New(ioutil.Discard, "", 0) } sigPriv, err := hex.DecodeString(c.config.PrivateKey) if err != nil { return err } if len(sigPriv) < ed25519.PrivateKeySize { return errors.New("PrivateKey is incorrect length") } c.secret = ed25519.PrivateKey(sigPriv) c.public = c.secret.Public().(ed25519.PublicKey) // TODO check public against current.PublicKey, error if they don't match c.PacketConn, err = iwe.NewPacketConn(c.secret) c.ctx, c.ctxCancel = context.WithCancel(context.Background()) c.proto.init(c) if err := c.proto.nodeinfo.setNodeInfo(c.config.NodeInfo, c.config.NodeInfoPrivacy); err != nil { return fmt.Errorf("setNodeInfo: %w", err) } return err } // If any static peers were provided in the configuration above then we should // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. func (c *Core) _addPeerLoop() { c.config.RLock() defer c.config.RUnlock() if c.addPeerTimer == nil { return } // Add peers from the Peers section for _, peer := range c.config.Peers { go func(peer string, intf string) { u, err := url.Parse(peer) if err != nil { c.log.Errorln("Failed to parse peer url:", peer, err) } if err := c.CallPeer(u, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } }(peer, "") // TODO: this should be acted and not in a goroutine? } // Add peers from the InterfacePeers section for intf, intfpeers := range c.config.InterfacePeers { for _, peer := range intfpeers { go func(peer string, intf string) { u, err := url.Parse(peer) if err != nil { c.log.Errorln("Failed to parse peer url:", peer, err) } if err := c.CallPeer(u, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } }(peer, intf) // TODO: this should be acted and not in a goroutine? } } c.addPeerTimer = time.AfterFunc(time.Minute, func() { c.Act(nil, c._addPeerLoop) }) } // Start starts up Yggdrasil using the provided config.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. A config.NodeState is returned which contains both the // current and previous configurations (from reconfigures). func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (err error) { phony.Block(c, func() { err = c._start(nc, log) }) return } // This function is unsafe and should only be ran by the core actor. func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) error { c.log = log c.config = nc if name := version.BuildName(); name != "unknown" { c.log.Infoln("Build name:", name) } if version := version.BuildVersion(); version != "unknown" { c.log.Infoln("Build version:", version) } c.log.Infoln("Starting up...") if err := c._init(); err != nil { c.log.Errorln("Failed to initialize core") return err } if err := c.links.init(c); err != nil { c.log.Errorln("Failed to start link interfaces") return err } c.addPeerTimer = time.AfterFunc(0, func() { c.Act(nil, c._addPeerLoop) }) c.log.Infoln("Startup complete") return nil } // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { phony.Block(c, func() { c.log.Infoln("Stopping...") c._close() c.log.Infoln("Stopped") }) } func (c *Core) Close() error { var err error phony.Block(c, func() { err = c._close() }) return err } // This function is unsafe and should only be ran by the core actor. func (c *Core) _close() error { c.ctxCancel() err := c.PacketConn.Close() if c.addPeerTimer != nil { c.addPeerTimer.Stop() c.addPeerTimer = nil } _ = c.links.stop() return err } func (c *Core) MTU() uint64 { const sessionTypeOverhead = 1 return c.PacketConn.MTU() - sessionTypeOverhead } func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) { buf := make([]byte, c.PacketConn.MTU(), 65535) for { bs := buf n, from, err = c.PacketConn.ReadFrom(bs) if err != nil { return 0, from, err } if n == 0 { continue } switch bs[0] { case typeSessionTraffic: // This is what we want to handle here case typeSessionProto: var key keyArray copy(key[:], from.(iwt.Addr)) data := append([]byte(nil), bs[1:n]...) c.proto.handleProto(nil, key, data) continue default: continue } bs = bs[1:n] copy(p, bs) if len(p) < len(bs) { n = len(p) } else { n = len(bs) } return } } func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) { buf := make([]byte, 0, 65535) buf = append(buf, typeSessionTraffic) buf = append(buf, p...) n, err = c.PacketConn.WriteTo(buf, addr) if n > 0 { n -= 1 } return } yggdrasil-go-0.4.3/src/core/core_test.go000066400000000000000000000107001417776402100201520ustar00rootroot00000000000000package core import ( "bytes" "math/rand" "net/url" "os" "testing" "time" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) // GenerateConfig produces default configuration with suitable modifications for tests. func GenerateConfig() *config.NodeConfig { cfg := defaults.GenerateConfig() cfg.AdminListen = "none" cfg.Listen = []string{"tcp://127.0.0.1:0"} cfg.IfName = "none" return cfg } // GetLoggerWithPrefix creates a new logger instance with prefix. // If verbose is set to true, three log levels are enabled: "info", "warn", "error". func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { l := log.New(os.Stderr, prefix, log.Flags()) if !verbose { return l } l.EnableLevel("info") l.EnableLevel("warn") l.EnableLevel("error") return l } // CreateAndConnectTwo creates two nodes. nodeB connects to nodeA. // Verbosity flag is passed to logger. func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) { nodeA = new(Core) if err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)); err != nil { t.Fatal(err) } nodeB = new(Core) if err := nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)); err != nil { t.Fatal(err) } u, err := url.Parse("tcp://" + nodeA.links.tcp.getAddr().String()) if err != nil { t.Fatal(err) } err = nodeB.CallPeer(u, "") if err != nil { t.Fatal(err) } time.Sleep(100 * time.Millisecond) if l := len(nodeA.GetPeers()); l != 1 { t.Fatal("unexpected number of peers", l) } if l := len(nodeB.GetPeers()); l != 1 { t.Fatal("unexpected number of peers", l) } return nodeA, nodeB } // WaitConnected blocks until either nodes negotiated DHT or 5 seconds passed. func WaitConnected(nodeA, nodeB *Core) bool { // It may take up to 3 seconds, but let's wait 5. for i := 0; i < 50; i++ { time.Sleep(100 * time.Millisecond) if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 { return true } } return false } // CreateEchoListener creates a routine listening on nodeA. It expects repeats messages of length bufLen. // It returns a channel used to synchronize the routine with caller. func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan struct{} { // Start routine done := make(chan struct{}) go func() { buf := make([]byte, bufLen) res := make([]byte, bufLen) for i := 0; i < repeats; i++ { n, from, err := nodeA.ReadFrom(buf) if err != nil { t.Error(err) return } if n != bufLen { t.Error("missing data") return } copy(res, buf) copy(res[8:24], buf[24:40]) copy(res[24:40], buf[8:24]) _, err = nodeA.WriteTo(res, from) if err != nil { t.Error(err) } } done <- struct{}{} }() return done } // TestCore_Start_Connect checks if two nodes can connect together. func TestCore_Start_Connect(t *testing.T) { CreateAndConnectTwo(t, true) } // TestCore_Start_Transfer checks that messages can be passed between nodes (in both directions). func TestCore_Start_Transfer(t *testing.T) { nodeA, nodeB := CreateAndConnectTwo(t, true) defer nodeA.Stop() defer nodeB.Stop() msgLen := 1500 done := CreateEchoListener(t, nodeA, msgLen, 1) if !WaitConnected(nodeA, nodeB) { t.Fatal("nodes did not connect") } // Send msg := make([]byte, msgLen) rand.Read(msg[40:]) msg[0] = 0x60 copy(msg[8:24], nodeB.Address()) copy(msg[24:40], nodeA.Address()) _, err := nodeB.WriteTo(msg, nodeA.LocalAddr()) if err != nil { t.Fatal(err) } buf := make([]byte, msgLen) _, _, err = nodeB.ReadFrom(buf) if err != nil { t.Fatal(err) } if !bytes.Equal(msg[40:], buf[40:]) { t.Fatal("expected echo") } <-done } // BenchmarkCore_Start_Transfer estimates the possible transfer between nodes (in MB/s). func BenchmarkCore_Start_Transfer(b *testing.B) { nodeA, nodeB := CreateAndConnectTwo(b, false) msgLen := 1500 // typical MTU done := CreateEchoListener(b, nodeA, msgLen, b.N) if !WaitConnected(nodeA, nodeB) { b.Fatal("nodes did not connect") } // Send msg := make([]byte, msgLen) rand.Read(msg[40:]) msg[0] = 0x60 copy(msg[8:24], nodeB.Address()) copy(msg[24:40], nodeA.Address()) buf := make([]byte, msgLen) b.SetBytes(int64(msgLen)) b.ResetTimer() addr := nodeA.LocalAddr() for i := 0; i < b.N; i++ { _, err := nodeB.WriteTo(msg, addr) if err != nil { b.Fatal(err) } _, _, err = nodeB.ReadFrom(buf) if err != nil { b.Fatal(err) } } <-done } yggdrasil-go-0.4.3/src/core/debug.go000066400000000000000000000015211417776402100172520ustar00rootroot00000000000000//go:build debug // +build debug package core import "fmt" import _ "net/http/pprof" import "net/http" import "runtime" import "os" import "github.com/gologme/log" // Start the profiler in debug builds, if the required environment variable is set. func init() { envVarName := "PPROFLISTEN" hostPort := os.Getenv(envVarName) switch { case hostPort == "": fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName) default: fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort) go func() { fmt.Println(http.ListenAndServe(hostPort, nil)) }() } } // Starts the function profiler. This is only supported when built with // '-tags build'. func StartProfiler(log *log.Logger) error { runtime.SetBlockProfileRate(1) go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() return nil } yggdrasil-go-0.4.3/src/core/link.go000066400000000000000000000213041417776402100171220ustar00rootroot00000000000000package core import ( "crypto/ed25519" "encoding/hex" "errors" "fmt" "io" "net" "net/url" "strings" "sync" //"sync/atomic" "time" "sync/atomic" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" "golang.org/x/net/proxy" //"github.com/Arceliar/phony" // TODO? use instead of mutexes ) type links struct { core *Core mutex sync.RWMutex // protects links below links map[linkInfo]*link tcp tcp // TCP interface support stopped chan struct{} // TODO timeout (to remove from switch), read from config.ReadTimeout } // linkInfo is used as a map key type linkInfo struct { key keyArray linkType string // Type of link, e.g. TCP, AWDL local string // Local name or address remote string // Remote name or address } type link struct { lname string links *links conn *linkConn options linkOptions info linkInfo incoming bool force bool closed chan struct{} } type linkOptions struct { pinnedEd25519Keys map[keyArray]struct{} } func (l *links) init(c *Core) error { l.core = c l.mutex.Lock() l.links = make(map[linkInfo]*link) l.mutex.Unlock() l.stopped = make(chan struct{}) if err := l.tcp.init(l); err != nil { c.log.Errorln("Failed to start TCP interface") return err } return nil } func (l *links) call(u *url.URL, sintf string) error { //u, err := url.Parse(uri) //if err != nil { // return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) //} tcpOpts := tcpOptions{} if pubkeys, ok := u.Query()["key"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{}) for _, pubkey := range pubkeys { if sigPub, err := hex.DecodeString(pubkey); err == nil { var sigPubKey keyArray copy(sigPubKey[:], sigPub) tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{} } } } switch u.Scheme { case "tcp": l.tcp.call(u.Host, tcpOpts, sintf) case "socks": tcpOpts.socksProxyAddr = u.Host if u.User != nil { tcpOpts.socksProxyAuth = &proxy.Auth{} tcpOpts.socksProxyAuth.User = u.User.Username() tcpOpts.socksProxyAuth.Password, _ = u.User.Password() } tcpOpts.upgrade = l.tcp.tls.forDialer // TODO make this configurable pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": tcpOpts.upgrade = l.tcp.tls.forDialer // SNI headers must contain hostnames and not IP addresses, so we must make sure // that we do not populate the SNI with an IP literal. We do this by splitting // the host-port combo from the query option and then seeing if it parses to an // IP address successfully or not. if sni := u.Query().Get("sni"); sni != "" { if net.ParseIP(sni) == nil { tcpOpts.tlsSNI = sni } } // If the SNI is not configured still because the above failed then we'll try // again but this time we'll use the host part of the peering URI instead. if tcpOpts.tlsSNI == "" { if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil { tcpOpts.tlsSNI = host } } l.tcp.call(u.Host, tcpOpts, sintf) default: return errors.New("unknown call scheme: " + u.Scheme) } return nil } func (l *links) create(conn net.Conn, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*link, error) { // Technically anything unique would work for names, but let's pick something human readable, just for debugging intf := link{ conn: &linkConn{ Conn: conn, up: time.Now(), }, lname: name, links: l, options: options, info: linkInfo{ linkType: linkType, local: local, remote: remote, }, incoming: incoming, force: force, } return &intf, nil } func (l *links) stop() error { close(l.stopped) if err := l.tcp.stop(); err != nil { return err } return nil } func (intf *link) handler() (chan struct{}, error) { // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later defer intf.conn.Close() meta := version_getBaseMetadata() meta.key = intf.links.core.public metaBytes := meta.encode() // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer) var err error if !util.FuncTimeout(30*time.Second, func() { var n int n, err = intf.conn.Write(metaBytes) if err == nil && n != len(metaBytes) { err = errors.New("incomplete metadata send") } }) { return nil, errors.New("timeout on metadata send") } if err != nil { return nil, err } if !util.FuncTimeout(30*time.Second, func() { var n int n, err = io.ReadFull(intf.conn, metaBytes) if err == nil && n != len(metaBytes) { err = errors.New("incomplete metadata recv") } }) { return nil, errors.New("timeout on metadata recv") } if err != nil { return nil, err } meta = version_metadata{} base := version_getBaseMetadata() if !meta.decode(metaBytes) { return nil, errors.New("failed to decode metadata") } if !meta.check() { var connectError string if intf.incoming { connectError = "Rejected incoming connection" } else { connectError = "Failed to connect" } intf.links.core.log.Debugf("%s: %s is incompatible version (local %s, remote %s)", connectError, intf.lname, fmt.Sprintf("%d.%d", base.ver, base.minorVer), fmt.Sprintf("%d.%d", meta.ver, meta.minorVer), ) return nil, errors.New("remote node is incompatible version") } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. if pinned := intf.options.pinnedEd25519Keys; pinned != nil { var key keyArray copy(key[:], meta.key) if _, allowed := pinned[key]; !allowed { intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name()) return nil, fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } } // Check if we're authorized to connect to this key / IP intf.links.core.config.RLock() allowed := intf.links.core.config.AllowedPublicKeys intf.links.core.config.RUnlock() isallowed := len(allowed) == 0 for _, k := range allowed { if k == hex.EncodeToString(meta.key) { // TODO: this is yuck isallowed = true break } } if intf.incoming && !intf.force && !isallowed { intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key)) intf.close() return nil, nil } // Check if we already have a link to this node copy(intf.info.key[:], meta.key) intf.links.mutex.Lock() if oldIntf, isIn := intf.links.links[intf.info]; isIn { intf.links.mutex.Unlock() // FIXME we should really return an error and let the caller block instead // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc. intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name()) return oldIntf.closed, nil } else { intf.closed = make(chan struct{}) intf.links.links[intf.info] = intf defer func() { intf.links.mutex.Lock() delete(intf.links.links, intf.info) intf.links.mutex.Unlock() close(intf.closed) }() intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name()) } intf.links.mutex.Unlock() themAddr := address.AddrForKey(ed25519.PublicKey(intf.info.key[:])) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote) intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) // Run the handler err = intf.links.core.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn) // TODO don't report an error if it's just a 'use of closed network connection' if err != nil { intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local, err) } else { intf.links.core.log.Infof("Disconnected %s: %s, source %s", strings.ToUpper(intf.info.linkType), themString, intf.info.local) } return nil, err } func (intf *link) close() { intf.conn.Close() } func (intf *link) name() string { return intf.lname } type linkConn struct { // tx and rx are at the beginning of the struct to ensure 64-bit alignment // on 32-bit platforms, see https://pkg.go.dev/sync/atomic#pkg-note-BUG rx uint64 tx uint64 up time.Time net.Conn } func (c *linkConn) Read(p []byte) (n int, err error) { n, err = c.Conn.Read(p) atomic.AddUint64(&c.rx, uint64(n)) return } func (c *linkConn) Write(p []byte) (n int, err error) { n, err = c.Conn.Write(p) atomic.AddUint64(&c.tx, uint64(n)) return } yggdrasil-go-0.4.3/src/core/nodeinfo.go000066400000000000000000000110361417776402100177670ustar00rootroot00000000000000package core import ( "encoding/hex" "encoding/json" "errors" "net" "runtime" "strings" "time" iwt "github.com/Arceliar/ironwood/types" "github.com/Arceliar/phony" //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) // NodeInfoPayload represents a RequestNodeInfo response, in bytes. type NodeInfoPayload []byte type nodeinfo struct { phony.Inbox proto *protoHandler myNodeInfo NodeInfoPayload callbacks map[keyArray]nodeinfoCallback } type nodeinfoCallback struct { call func(nodeinfo NodeInfoPayload) created time.Time } // Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep // the cache/callback maps clean of stale entries func (m *nodeinfo) init(proto *protoHandler) { m.Act(nil, func() { m._init(proto) }) } func (m *nodeinfo) _init(proto *protoHandler) { m.proto = proto m.callbacks = make(map[keyArray]nodeinfoCallback) m._cleanup() } func (m *nodeinfo) _cleanup() { for boxPubKey, callback := range m.callbacks { if time.Since(callback.created) > time.Minute { delete(m.callbacks, boxPubKey) } } time.AfterFunc(time.Second*30, func() { m.Act(nil, m._cleanup) }) } func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo NodeInfoPayload)) { m.callbacks[sender] = nodeinfoCallback{ created: time.Now(), call: call, } } // Handles the callback, if there is one func (m *nodeinfo) _callback(sender keyArray, nodeinfo NodeInfoPayload) { if callback, ok := m.callbacks[sender]; ok { callback.call(nodeinfo) delete(m.callbacks, sender) } } func (m *nodeinfo) _getNodeInfo() NodeInfoPayload { return m.myNodeInfo } // Set the current node's nodeinfo func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) (err error) { phony.Block(m, func() { err = m._setNodeInfo(given, privacy) }) return } func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error { defaults := map[string]interface{}{ "buildname": version.BuildName(), "buildversion": version.BuildVersion(), "buildplatform": runtime.GOOS, "buildarch": runtime.GOARCH, } newnodeinfo := make(map[string]interface{}) if !privacy { for k, v := range defaults { newnodeinfo[k] = v } } if nodeinfomap, ok := given.(map[string]interface{}); ok { for key, value := range nodeinfomap { if _, ok := defaults[key]; ok { if strvalue, strok := value.(string); strok && strings.EqualFold(strvalue, "null") || value == nil { delete(newnodeinfo, key) } continue } newnodeinfo[key] = value } } newjson, err := json.Marshal(newnodeinfo) if err == nil { if len(newjson) > 16384 { return errors.New("NodeInfo exceeds max length of 16384 bytes") } m.myNodeInfo = newjson return nil } return err } func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo NodeInfoPayload)) { m.Act(from, func() { m._sendReq(key, callback) }) } func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo NodeInfoPayload)) { if callback != nil { m._addCallback(key, callback) } _, _ = m.proto.core.PacketConn.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:])) } func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) { m.Act(from, func() { m._sendRes(key) }) } func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayload) { m.Act(from, func() { m._callback(key, info) }) } func (m *nodeinfo) _sendRes(key keyArray) { bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...) _, _ = m.proto.core.PacketConn.WriteTo(bs, iwt.Addr(key[:])) } // Admin socket stuff type GetNodeInfoRequest struct { Key string `json:"key"` } type GetNodeInfoResponse map[string]interface{} func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) { var req GetNodeInfoRequest if err := json.Unmarshal(in, &req); err != nil { return nil, err } var key keyArray var kbs []byte var err error if kbs, err = hex.DecodeString(req.Key); err != nil { return nil, err } copy(key[:], kbs) ch := make(chan []byte, 1) m.sendReq(nil, key, func(info NodeInfoPayload) { ch <- info }) timer := time.NewTimer(6 * time.Second) defer timer.Stop() select { case <-timer.C: return nil, errors.New("timeout") case info := <-ch: var msg json.RawMessage if err := msg.UnmarshalJSON(info); err != nil { return nil, err } ip := net.IP(address.AddrForKey(kbs)[:]) res := GetNodeInfoResponse{ip.String(): msg} return res, nil } } yggdrasil-go-0.4.3/src/core/proto.go000066400000000000000000000206011417776402100173270ustar00rootroot00000000000000package core import ( "crypto/ed25519" "encoding/hex" "encoding/json" "errors" "fmt" "net" "time" iwt "github.com/Arceliar/ironwood/types" "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" ) const ( typeDebugDummy = iota typeDebugGetSelfRequest typeDebugGetSelfResponse typeDebugGetPeersRequest typeDebugGetPeersResponse typeDebugGetDHTRequest typeDebugGetDHTResponse ) type reqInfo struct { callback func([]byte) timer *time.Timer // time.AfterFunc cleanup } type keyArray [ed25519.PublicKeySize]byte type protoHandler struct { phony.Inbox core *Core nodeinfo nodeinfo selfRequests map[keyArray]*reqInfo peersRequests map[keyArray]*reqInfo dhtRequests map[keyArray]*reqInfo } func (p *protoHandler) init(core *Core) { p.core = core p.nodeinfo.init(p) p.selfRequests = make(map[keyArray]*reqInfo) p.peersRequests = make(map[keyArray]*reqInfo) p.dhtRequests = make(map[keyArray]*reqInfo) } // Common functions func (p *protoHandler) handleProto(from phony.Actor, key keyArray, bs []byte) { if len(bs) == 0 { return } switch bs[0] { case typeProtoDummy: case typeProtoNodeInfoRequest: p.nodeinfo.handleReq(p, key) case typeProtoNodeInfoResponse: p.nodeinfo.handleRes(p, key, bs[1:]) case typeProtoDebug: p._handleDebug(key, bs[1:]) } } func (p *protoHandler) _handleDebug(key keyArray, bs []byte) { if len(bs) == 0 { return } switch bs[0] { case typeDebugDummy: case typeDebugGetSelfRequest: p._handleGetSelfRequest(key) case typeDebugGetSelfResponse: p._handleGetSelfResponse(key, bs[1:]) case typeDebugGetPeersRequest: p._handleGetPeersRequest(key) case typeDebugGetPeersResponse: p._handleGetPeersResponse(key, bs[1:]) case typeDebugGetDHTRequest: p._handleGetDHTRequest(key) case typeDebugGetDHTResponse: p._handleGetDHTResponse(key, bs[1:]) } } func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) { bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...) _, _ = p.core.PacketConn.WriteTo(bs, iwt.Addr(key[:])) } // Get self func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { if info := p.selfRequests[key]; info != nil { info.timer.Stop() delete(p.selfRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { if p.selfRequests[key] == info { delete(p.selfRequests, key) } }) }) p.selfRequests[key] = info p._sendDebug(key, typeDebugGetSelfRequest, nil) }) } func (p *protoHandler) _handleGetSelfRequest(key keyArray) { self := p.core.GetSelf() res := map[string]string{ "key": hex.EncodeToString(self.Key[:]), "coords": fmt.Sprintf("%v", self.Coords), } bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex if err != nil { return } p._sendDebug(key, typeDebugGetSelfResponse, bs) } func (p *protoHandler) _handleGetSelfResponse(key keyArray, bs []byte) { if info := p.selfRequests[key]; info != nil { info.timer.Stop() info.callback(bs) delete(p.selfRequests, key) } } // Get peers func (p *protoHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { if info := p.peersRequests[key]; info != nil { info.timer.Stop() delete(p.peersRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { if p.peersRequests[key] == info { delete(p.peersRequests, key) } }) }) p.peersRequests[key] = info p._sendDebug(key, typeDebugGetPeersRequest, nil) }) } func (p *protoHandler) _handleGetPeersRequest(key keyArray) { peers := p.core.GetPeers() var bs []byte for _, pinfo := range peers { tmp := append(bs, pinfo.Key[:]...) const responseOverhead = 2 // 1 debug type, 1 getpeers type if uint64(len(tmp))+responseOverhead > p.core.MTU() { break } bs = tmp } p._sendDebug(key, typeDebugGetPeersResponse, bs) } func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) { if info := p.peersRequests[key]; info != nil { info.timer.Stop() info.callback(bs) delete(p.peersRequests, key) } } // Get DHT func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) { p.Act(nil, func() { if info := p.dhtRequests[key]; info != nil { info.timer.Stop() delete(p.dhtRequests, key) } info := new(reqInfo) info.callback = callback info.timer = time.AfterFunc(time.Minute, func() { p.Act(nil, func() { if p.dhtRequests[key] == info { delete(p.dhtRequests, key) } }) }) p.dhtRequests[key] = info p._sendDebug(key, typeDebugGetDHTRequest, nil) }) } func (p *protoHandler) _handleGetDHTRequest(key keyArray) { dinfos := p.core.GetDHT() var bs []byte for _, dinfo := range dinfos { tmp := append(bs, dinfo.Key[:]...) const responseOverhead = 2 // 1 debug type, 1 getdht type if uint64(len(tmp))+responseOverhead > p.core.MTU() { break } bs = tmp } p._sendDebug(key, typeDebugGetDHTResponse, bs) } func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { if info := p.dhtRequests[key]; info != nil { info.timer.Stop() info.callback(bs) delete(p.dhtRequests, key) } } // Admin socket stuff for "Get self" type DebugGetSelfRequest struct { Key string `json:"key"` } type DebugGetSelfResponse map[string]interface{} func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) { var req DebugGetSelfRequest if err := json.Unmarshal(in, &req); err != nil { return nil, err } var key keyArray var kbs []byte var err error if kbs, err = hex.DecodeString(req.Key); err != nil { return nil, err } copy(key[:], kbs) ch := make(chan []byte, 1) p.sendGetSelfRequest(key, func(info []byte) { ch <- info }) timer := time.NewTimer(6 * time.Second) defer timer.Stop() select { case <-timer.C: return nil, errors.New("timeout") case info := <-ch: var msg json.RawMessage if err := msg.UnmarshalJSON(info); err != nil { return nil, err } ip := net.IP(address.AddrForKey(kbs)[:]) res := DebugGetSelfResponse{ip.String(): msg} return res, nil } } // Admin socket stuff for "Get peers" type DebugGetPeersRequest struct { Key string `json:"key"` } type DebugGetPeersResponse map[string]interface{} func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error) { var req DebugGetPeersRequest if err := json.Unmarshal(in, &req); err != nil { return nil, err } var key keyArray var kbs []byte var err error if kbs, err = hex.DecodeString(req.Key); err != nil { return nil, err } copy(key[:], kbs) ch := make(chan []byte, 1) p.sendGetPeersRequest(key, func(info []byte) { ch <- info }) timer := time.NewTimer(6 * time.Second) defer timer.Stop() select { case <-timer.C: return nil, errors.New("timeout") case info := <-ch: ks := make(map[string][]string) bs := info for len(bs) >= len(key) { ks["keys"] = append(ks["keys"], hex.EncodeToString(bs[:len(key)])) bs = bs[len(key):] } js, err := json.Marshal(ks) if err != nil { return nil, err } var msg json.RawMessage if err := msg.UnmarshalJSON(js); err != nil { return nil, err } ip := net.IP(address.AddrForKey(kbs)[:]) res := DebugGetPeersResponse{ip.String(): msg} return res, nil } } // Admin socket stuff for "Get DHT" type DebugGetDHTRequest struct { Key string `json:"key"` } type DebugGetDHTResponse map[string]interface{} func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) { var req DebugGetDHTRequest if err := json.Unmarshal(in, &req); err != nil { return nil, err } var key keyArray var kbs []byte var err error if kbs, err = hex.DecodeString(req.Key); err != nil { return nil, err } copy(key[:], kbs) ch := make(chan []byte, 1) p.sendGetDHTRequest(key, func(info []byte) { ch <- info }) timer := time.NewTimer(6 * time.Second) defer timer.Stop() select { case <-timer.C: return nil, errors.New("timeout") case info := <-ch: ks := make(map[string][]string) bs := info for len(bs) >= len(key) { ks["keys"] = append(ks["keys"], hex.EncodeToString(bs[:len(key)])) bs = bs[len(key):] } js, err := json.Marshal(ks) if err != nil { return nil, err } var msg json.RawMessage if err := msg.UnmarshalJSON(js); err != nil { return nil, err } ip := net.IP(address.AddrForKey(kbs)[:]) res := DebugGetDHTResponse{ip.String(): msg} return res, nil } } yggdrasil-go-0.4.3/src/core/tcp.go000066400000000000000000000262511417776402100167610ustar00rootroot00000000000000package core // This sends packets to peers using TCP as a transport // It's generally better tested than the UDP implementation // Using it regularly is insane, but I find TCP easier to test/debug with it // Updating and optimizing the UDP version is a higher priority // TODO: // Something needs to make sure we're getting *valid* packets // Could be used to DoS (connect, give someone else's keys, spew garbage) // I guess the "peer" part should watch for link packets, disconnect? // TCP connections start with a metadata exchange. // It involves exchanging version numbers and crypto keys // See version.go for version metadata format import ( "context" "fmt" "math/rand" "net" "net/url" "strings" "sync" "time" "golang.org/x/net/proxy" "github.com/yggdrasil-network/yggdrasil-go/src/address" //"github.com/yggdrasil-network/yggdrasil-go/src/util" ) const default_timeout = 6 * time.Second // The TCP listener and information about active TCP connections, to avoid duplication. type tcp struct { links *links waitgroup sync.WaitGroup mutex sync.Mutex // Protecting the below listeners map[string]*TcpListener calls map[string]struct{} conns map[linkInfo](chan struct{}) tls tcptls } // TcpListener is a stoppable TCP listener interface. These are typically // returned from calls to the ListenTCP() function and are also used internally // to represent listeners created by the "Listen" configuration option and for // multicast interfaces. type TcpListener struct { Listener net.Listener opts tcpOptions stop chan struct{} } type TcpUpgrade struct { upgrade func(c net.Conn, o *tcpOptions) (net.Conn, error) name string } type tcpOptions struct { linkOptions upgrade *TcpUpgrade socksProxyAddr string socksProxyAuth *proxy.Auth socksPeerAddr string tlsSNI string } func (l *TcpListener) Stop() { defer func() { _ = recover() }() close(l.stop) } // Wrapper function to set additional options for specific connection types. func (t *tcp) setExtraOptions(c net.Conn) { switch sock := c.(type) { case *net.TCPConn: _ = sock.SetNoDelay(true) // TODO something for socks5 default: } } // Returns the address of the listener. func (t *tcp) getAddr() *net.TCPAddr { // TODO: Fix this, because this will currently only give a single address // to multicast.go, which obviously is not great, but right now multicast.go // doesn't have the ability to send more than one address in a packet either t.mutex.Lock() defer t.mutex.Unlock() for _, l := range t.listeners { return l.Listener.Addr().(*net.TCPAddr) } return nil } // Initializes the struct. func (t *tcp) init(l *links) error { t.links = l t.tls.init(t) t.mutex.Lock() t.calls = make(map[string]struct{}) t.conns = make(map[linkInfo](chan struct{})) t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() t.links.core.config.RLock() defer t.links.core.config.RUnlock() for _, listenaddr := range t.links.core.config.Listen { u, err := url.Parse(listenaddr) if err != nil { t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") } if _, err := t.listenURL(u, ""); err != nil { return err } } return nil } func (t *tcp) stop() error { t.mutex.Lock() for _, listener := range t.listeners { listener.Stop() } t.mutex.Unlock() t.waitgroup.Wait() return nil } func (t *tcp) listenURL(u *url.URL, sintf string) (*TcpListener, error) { var listener *TcpListener var err error hostport := u.Host // Used for tcp and tls if len(sintf) != 0 { host, port, err := net.SplitHostPort(hostport) if err == nil { hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) } } switch u.Scheme { case "tcp": listener, err = t.listen(hostport, nil) case "tls": listener, err = t.listen(hostport, t.tls.forListener) default: t.links.core.log.Errorln("Failed to add listener: listener", u.String(), "is not correctly formatted, ignoring") } return listener, err } func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) { var err error ctx := t.links.core.ctx lc := net.ListenConfig{ Control: t.tcpContext, } listener, err := lc.Listen(ctx, "tcp", listenaddr) if err == nil { l := TcpListener{ Listener: listener, opts: tcpOptions{upgrade: upgrade}, stop: make(chan struct{}), } t.waitgroup.Add(1) go t.listener(&l, listenaddr) return &l, nil } return nil, err } // Runs the listener, which spawns off goroutines for incoming connections. func (t *tcp) listener(l *TcpListener, listenaddr string) { defer t.waitgroup.Done() if l == nil { return } // Track the listener so that we can find it again in future t.mutex.Lock() if _, isIn := t.listeners[listenaddr]; isIn { t.mutex.Unlock() l.Listener.Close() return } callproto := "TCP" if l.opts.upgrade != nil { callproto = strings.ToUpper(l.opts.upgrade.name) } t.listeners[listenaddr] = l t.mutex.Unlock() // And here we go! defer func() { t.links.core.log.Infoln("Stopping", callproto, "listener on:", l.Listener.Addr().String()) l.Listener.Close() t.mutex.Lock() delete(t.listeners, listenaddr) t.mutex.Unlock() }() t.links.core.log.Infoln("Listening for", callproto, "on:", l.Listener.Addr().String()) go func() { <-l.stop l.Listener.Close() }() defer l.Stop() for { sock, err := l.Listener.Accept() if err != nil { t.links.core.log.Errorln("Failed to accept connection:", err) select { case <-l.stop: return default: } time.Sleep(time.Second) // So we don't busy loop continue } t.waitgroup.Add(1) options := l.opts go t.handler(sock, true, options) } } // Checks if we already are calling this address func (t *tcp) startCalling(saddr string) bool { t.mutex.Lock() defer t.mutex.Unlock() _, isIn := t.calls[saddr] t.calls[saddr] = struct{}{} return !isIn } // Checks if a connection already exists. // If not, it adds it to the list of active outgoing calls (to block future attempts) and dials the address. // If the dial is successful, it launches the handler. // When finished, it removes the outgoing call, so reconnection attempts can be made later. // This all happens in a separate goroutine that it spawns. func (t *tcp) call(saddr string, options tcpOptions, sintf string) { go func() { callname := saddr callproto := "TCP" if options.upgrade != nil { callproto = strings.ToUpper(options.upgrade.name) } if sintf != "" { callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf) } if !t.startCalling(callname) { return } defer func() { // Block new calls for a little while, to mitigate livelock scenarios rand.Seed(time.Now().UnixNano()) delay := default_timeout + time.Duration(rand.Intn(10000))*time.Millisecond time.Sleep(delay) t.mutex.Lock() delete(t.calls, callname) t.mutex.Unlock() }() var conn net.Conn var err error if options.socksProxyAddr != "" { if sintf != "" { return } dialerdst, er := net.ResolveTCPAddr("tcp", options.socksProxyAddr) if er != nil { return } var dialer proxy.Dialer dialer, err = proxy.SOCKS5("tcp", dialerdst.String(), options.socksProxyAuth, proxy.Direct) if err != nil { return } ctx, done := context.WithTimeout(t.links.core.ctx, default_timeout) conn, err = dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", saddr) done() if err != nil { return } t.waitgroup.Add(1) options.socksPeerAddr = saddr if ch := t.handler(conn, false, options); ch != nil { <-ch } } else { dst, err := net.ResolveTCPAddr("tcp", saddr) if err != nil { return } if dst.IP.IsLinkLocalUnicast() { dst.Zone = sintf if dst.Zone == "" { return } } dialer := net.Dialer{ Control: t.tcpContext, } if sintf != "" { dialer.Control = t.getControl(sintf) ief, err := net.InterfaceByName(sintf) if err != nil { return } if ief.Flags&net.FlagUp == 0 { return } addrs, err := ief.Addrs() if err == nil { for addrindex, addr := range addrs { src, _, err := net.ParseCIDR(addr.String()) if err != nil { continue } if src.Equal(dst.IP) { continue } if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() { continue } bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast() bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast() if !bothglobal && !bothlinklocal { continue } if (src.To4() != nil) != (dst.IP.To4() != nil) { continue } if bothglobal || bothlinklocal || addrindex == len(addrs)-1 { dialer.LocalAddr = &net.TCPAddr{ IP: src, Port: 0, Zone: sintf, } break } } if dialer.LocalAddr == nil { return } } } ctx, done := context.WithTimeout(t.links.core.ctx, default_timeout) conn, err = dialer.DialContext(ctx, "tcp", dst.String()) done() if err != nil { t.links.core.log.Debugf("Failed to dial %s: %s", callproto, err) return } t.waitgroup.Add(1) if ch := t.handler(conn, false, options); ch != nil { <-ch } } }() } func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) chan struct{} { defer t.waitgroup.Done() // Happens after sock.close defer sock.Close() t.setExtraOptions(sock) var upgraded bool if options.upgrade != nil { var err error if sock, err = options.upgrade.upgrade(sock, &options); err != nil { t.links.core.log.Errorln("TCP handler upgrade failed:", err) return nil } upgraded = true } var name, proto, local, remote string if options.socksProxyAddr != "" { name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksPeerAddr proto = "socks" local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) remote, _, _ = net.SplitHostPort(options.socksPeerAddr) } else { if upgraded { proto = options.upgrade.name name = proto + "://" + sock.RemoteAddr().String() } else { proto = "tcp" name = proto + "://" + sock.RemoteAddr().String() } local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } localIP := net.ParseIP(local) if localIP = localIP.To16(); localIP != nil { var laddr address.Address var lsubnet address.Subnet copy(laddr[:], localIP) copy(lsubnet[:], localIP) if laddr.IsValid() || lsubnet.IsValid() { // The local address is with the network address/prefix range // This would route ygg over ygg, which we don't want // FIXME ideally this check should happen outside of the core library // Maybe dial/listen at the application level // Then pass a net.Conn to the core library (after these kinds of checks are done) t.links.core.log.Debugln("Dropping ygg-tunneled connection", local, remote) return nil } } force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() link, err := t.links.create(sock, name, proto, local, remote, incoming, force, options.linkOptions) if err != nil { t.links.core.log.Println(err) panic(err) } t.links.core.log.Debugln("DEBUG: starting handler for", name) ch, err := link.handler() t.links.core.log.Debugln("DEBUG: stopped handler for", name, err) return ch } yggdrasil-go-0.4.3/src/core/tcp_darwin.go000066400000000000000000000012171417776402100203200ustar00rootroot00000000000000//go:build darwin // +build darwin package core import ( "syscall" "golang.org/x/sys/unix" ) // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { var control error var recvanyif error control = c.Control(func(fd uintptr) { // sys/socket.h: #define SO_RECV_ANYIF 0x1104 recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) }) switch { case recvanyif != nil: return recvanyif default: return control } } func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { return t.tcpContext } yggdrasil-go-0.4.3/src/core/tcp_linux.go000066400000000000000000000023411417776402100201720ustar00rootroot00000000000000//go:build linux // +build linux package core import ( "syscall" "golang.org/x/sys/unix" ) // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { var control error var bbr error control = c.Control(func(fd uintptr) { bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr") }) // Log any errors if bbr != nil { t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr) } if control != nil { t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, Control error:", control) } // Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal return nil } func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { var err error btd := func(fd uintptr) { err = unix.BindToDevice(int(fd), sintf) } _ = c.Control(btd) if err != nil { t.links.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf) } return t.tcpContext(network, address, c) } } yggdrasil-go-0.4.3/src/core/tcp_other.go000066400000000000000000000005621417776402100201570ustar00rootroot00000000000000//go:build !darwin && !linux // +build !darwin,!linux package core import ( "syscall" ) // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error { return nil } func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error { return t.tcpContext } yggdrasil-go-0.4.3/src/core/tls.go000066400000000000000000000064751417776402100170030ustar00rootroot00000000000000package core import ( "bytes" "crypto/ed25519" "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/hex" "encoding/pem" "errors" "log" "math/big" "net" "time" ) type tcptls struct { tcp *tcp config *tls.Config forDialer *TcpUpgrade forListener *TcpUpgrade } func (t *tcptls) init(tcp *tcp) { t.tcp = tcp t.forDialer = &TcpUpgrade{ upgrade: t.upgradeDialer, name: "tls", } t.forListener = &TcpUpgrade{ upgrade: t.upgradeListener, name: "tls", } edpriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) copy(edpriv[:], tcp.links.core.secret[:]) certBuf := &bytes.Buffer{} // TODO: because NotAfter is finite, we should add some mechanism to regenerate the certificate and restart the listeners periodically for nodes with very high uptimes. Perhaps regenerate certs and restart listeners every few months or so. pubtemp := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: hex.EncodeToString(tcp.links.core.public[:]), }, NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour * 24 * 365), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } derbytes, err := x509.CreateCertificate(rand.Reader, &pubtemp, &pubtemp, edpriv.Public(), edpriv) if err != nil { log.Fatalf("Failed to create certificate: %s", err) } if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derbytes}); err != nil { panic("failed to encode certificate into PEM") } cpool := x509.NewCertPool() cpool.AppendCertsFromPEM(derbytes) t.config = &tls.Config{ RootCAs: cpool, Certificates: []tls.Certificate{ { Certificate: [][]byte{derbytes}, PrivateKey: edpriv, }, }, InsecureSkipVerify: true, MinVersion: tls.VersionTLS13, } } func (t *tcptls) configForOptions(options *tcpOptions) *tls.Config { config := t.config.Clone() config.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { if len(rawCerts) != 1 { return errors.New("tls not exactly 1 cert") } cert, err := x509.ParseCertificate(rawCerts[0]) if err != nil { return errors.New("tls failed to parse cert") } if cert.PublicKeyAlgorithm != x509.Ed25519 { return errors.New("tls wrong cert algorithm") } pk := cert.PublicKey.(ed25519.PublicKey) var key keyArray copy(key[:], pk) // If options does not have a pinned key, then pin one now if options.pinnedEd25519Keys == nil { options.pinnedEd25519Keys = make(map[keyArray]struct{}) options.pinnedEd25519Keys[key] = struct{}{} } if _, isIn := options.pinnedEd25519Keys[key]; !isIn { return errors.New("tls key does not match pinned key") } return nil } return config } func (t *tcptls) upgradeListener(c net.Conn, options *tcpOptions) (net.Conn, error) { config := t.configForOptions(options) conn := tls.Server(c, config) if err := conn.Handshake(); err != nil { return c, err } return conn, nil } func (t *tcptls) upgradeDialer(c net.Conn, options *tcpOptions) (net.Conn, error) { config := t.configForOptions(options) config.ServerName = options.tlsSNI conn := tls.Client(c, config) if err := conn.Handshake(); err != nil { return c, err } return conn, nil } yggdrasil-go-0.4.3/src/core/types.go000066400000000000000000000004211417776402100173260ustar00rootroot00000000000000package core // In-band packet types const ( typeSessionDummy = iota // nolint:deadcode,varcheck typeSessionTraffic typeSessionProto ) // Protocol packet types const ( typeProtoDummy = iota typeProtoNodeInfoRequest typeProtoNodeInfoResponse typeProtoDebug = 255 ) yggdrasil-go-0.4.3/src/core/version.go000066400000000000000000000045711417776402100176610ustar00rootroot00000000000000package core // This file contains the version metadata struct // Used in the initial connection setup and key exchange // Some of this could arguably go in wire.go instead import "crypto/ed25519" // This is the version-specific metadata exchanged at the start of a connection. // It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number. // The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection. type version_metadata struct { meta [4]byte ver uint8 // 1 byte in this version // Everything after this point potentially depends on the version number, and is subject to change in future versions minorVer uint8 // 1 byte in this version key ed25519.PublicKey } // Gets a base metadata with no keys set, but with the correct version numbers. func version_getBaseMetadata() version_metadata { return version_metadata{ meta: [4]byte{'m', 'e', 't', 'a'}, ver: 0, minorVer: 4, } } // Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection. func version_getMetaLength() (mlen int) { mlen += 4 // meta mlen++ // ver, as long as it's < 127, which it is in this version mlen++ // minorVer, as long as it's < 127, which it is in this version mlen += ed25519.PublicKeySize // key return } // Encodes version metadata into its wire format. func (m *version_metadata) encode() []byte { bs := make([]byte, 0, version_getMetaLength()) bs = append(bs, m.meta[:]...) bs = append(bs, m.ver) bs = append(bs, m.minorVer) bs = append(bs, m.key[:]...) if len(bs) != version_getMetaLength() { panic("Inconsistent metadata length") } return bs } // Decodes version metadata from its wire format into the struct. func (m *version_metadata) decode(bs []byte) bool { if len(bs) != version_getMetaLength() { return false } offset := 0 offset += copy(m.meta[:], bs[offset:]) m.ver, offset = bs[offset], offset+1 m.minorVer, offset = bs[offset], offset+1 m.key = append([]byte(nil), bs[offset:]...) return true } // Checks that the "meta" bytes and the version numbers are the expected values. func (m *version_metadata) check() bool { base := version_getBaseMetadata() return base.meta == m.meta && base.ver == m.ver && base.minorVer == m.minorVer } yggdrasil-go-0.4.3/src/defaults/000077500000000000000000000000001417776402100165155ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/defaults/defaults.go000066400000000000000000000025161417776402100206570ustar00rootroot00000000000000package defaults import "github.com/yggdrasil-network/yggdrasil-go/src/config" type MulticastInterfaceConfig = config.MulticastInterfaceConfig // Defines which parameters are expected by default for configuration on a // specific platform. These values are populated in the relevant defaults_*.go // for the platform being targeted. They must be set. type platformDefaultParameters struct { // Admin socket DefaultAdminListen string // Configuration (used for yggdrasilctl) DefaultConfigFile string // Multicast interfaces DefaultMulticastInterfaces []MulticastInterfaceConfig // TUN/TAP MaximumIfMTU uint64 DefaultIfMTU uint64 DefaultIfName string } // Generates default configuration and returns a pointer to the resulting // NodeConfig. This is used when outputting the -genconf parameter and also when // using -autoconf. func GenerateConfig() *config.NodeConfig { // Create a node configuration and populate it. cfg := new(config.NodeConfig) cfg.NewKeys() cfg.Listen = []string{} cfg.AdminListen = GetDefaults().DefaultAdminListen cfg.Peers = []string{} cfg.InterfacePeers = map[string][]string{} cfg.AllowedPublicKeys = []string{} cfg.MulticastInterfaces = GetDefaults().DefaultMulticastInterfaces cfg.IfName = GetDefaults().DefaultIfName cfg.IfMTU = GetDefaults().DefaultIfMTU cfg.NodeInfoPrivacy = false return cfg } yggdrasil-go-0.4.3/src/defaults/defaults_darwin.go000066400000000000000000000012731417776402100222220ustar00rootroot00000000000000//go:build darwin // +build darwin package defaults // Sane defaults for the macOS/Darwin platform. The "default" options may be // may be replaced by the running configuration. func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ {Regex: "en.*", Beacon: true, Listen: true}, {Regex: "bridge.*", Beacon: true, Listen: true}, }, // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, DefaultIfName: "auto", } } yggdrasil-go-0.4.3/src/defaults/defaults_freebsd.go000066400000000000000000000012161417776402100223450ustar00rootroot00000000000000//go:build freebsd // +build freebsd package defaults // Sane defaults for the BSD platforms. The "default" options may be // may be replaced by the running configuration. func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // Configuration (used for yggdrasilctl) DefaultConfigFile: "/usr/local/etc/yggdrasil.conf", // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP MaximumIfMTU: 32767, DefaultIfMTU: 32767, DefaultIfName: "/dev/tun0", } } yggdrasil-go-0.4.3/src/defaults/defaults_linux.go000066400000000000000000000011741417776402100220750ustar00rootroot00000000000000//go:build linux // +build linux package defaults // Sane defaults for the Linux platform. The "default" options may be // may be replaced by the running configuration. func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, DefaultIfName: "auto", } } yggdrasil-go-0.4.3/src/defaults/defaults_openbsd.go000066400000000000000000000011771417776402100223730ustar00rootroot00000000000000//go:build openbsd // +build openbsd package defaults // Sane defaults for the BSD platforms. The "default" options may be // may be replaced by the running configuration. func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP MaximumIfMTU: 16384, DefaultIfMTU: 16384, DefaultIfName: "tun0", } } yggdrasil-go-0.4.3/src/defaults/defaults_other.go000066400000000000000000000013071417776402100220550ustar00rootroot00000000000000//go:build !linux && !darwin && !windows && !openbsd && !freebsd // +build !linux,!darwin,!windows,!openbsd,!freebsd package defaults // Sane defaults for the other platforms. The "default" options may be // may be replaced by the running configuration. func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "tcp://localhost:9001", // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, DefaultIfName: "none", } } yggdrasil-go-0.4.3/src/defaults/defaults_windows.go000066400000000000000000000012261417776402100224260ustar00rootroot00000000000000//go:build windows // +build windows package defaults // Sane defaults for the Windows platform. The "default" options may be // may be replaced by the running configuration. func GetDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin DefaultAdminListen: "tcp://localhost:9001", // Configuration (used for yggdrasilctl) DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf", // Multicast interfaces DefaultMulticastInterfaces: []MulticastInterfaceConfig{ {Regex: ".*", Beacon: true, Listen: true}, }, // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, DefaultIfName: "Yggdrasil", } } yggdrasil-go-0.4.3/src/ipv6rwc/000077500000000000000000000000001417776402100163065ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/ipv6rwc/icmpv6.go000066400000000000000000000043501417776402100200430ustar00rootroot00000000000000package ipv6rwc // The ICMPv6 module implements functions to easily create ICMPv6 // packets. These functions, when mixed with the built-in Go IPv6 // and ICMP libraries, can be used to send control messages back // to the host. Examples include: // - NDP messages, when running in TAP mode // - Packet Too Big messages, when packets exceed the session MTU // - Destination Unreachable messages, when a session prohibits // incoming traffic import ( "encoding/binary" "net" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" ) type ICMPv6 struct{} // Marshal returns the binary encoding of h. func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { b := make([]byte, 40) b[0] |= byte(h.Version) << 4 b[0] |= byte(h.TrafficClass) >> 4 b[1] |= byte(h.TrafficClass) << 4 b[1] |= byte(h.FlowLabel >> 16) b[2] = byte(h.FlowLabel >> 8) b[3] = byte(h.FlowLabel) binary.BigEndian.PutUint16(b[4:6], uint16(h.PayloadLen)) b[6] = byte(h.NextHeader) b[7] = byte(h.HopLimit) copy(b[8:24], h.Src) copy(b[24:40], h.Dst) return b, nil } // Creates an ICMPv6 packet based on the given icmp.MessageBody and other // parameters, complete with IP headers only, which can be written directly to // a TUN adapter, or called directly by the CreateICMPv6L2 function when // generating a message for TAP adapters. func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { // Create the ICMPv6 message icmpMessage := icmp.Message{ Type: mtype, Code: mcode, Body: mbody, } // Convert the ICMPv6 message into []byte icmpMessageBuf, err := icmpMessage.Marshal(icmp.IPv6PseudoHeader(src, dst)) if err != nil { return nil, err } // Create the IPv6 header ipv6Header := ipv6.Header{ Version: ipv6.Version, NextHeader: 58, PayloadLen: len(icmpMessageBuf), HopLimit: 255, Src: src, Dst: dst, } // Convert the IPv6 header into []byte ipv6HeaderBuf, err := ipv6Header_Marshal(&ipv6Header) if err != nil { return nil, err } // Construct the packet responsePacket := make([]byte, ipv6.HeaderLen+ipv6Header.PayloadLen) copy(responsePacket[:ipv6.HeaderLen], ipv6HeaderBuf) copy(responsePacket[ipv6.HeaderLen:], icmpMessageBuf) // Send it back return responsePacket, nil } yggdrasil-go-0.4.3/src/ipv6rwc/ipv6rwc.go000066400000000000000000000207601417776402100202420ustar00rootroot00000000000000package ipv6rwc import ( "crypto/ed25519" "errors" "fmt" "net" "sync" "time" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" iwt "github.com/Arceliar/ironwood/types" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/core" ) const keyStoreTimeout = 2 * time.Minute // Out-of-band packet types const ( typeKeyDummy = iota // nolint:deadcode,varcheck typeKeyLookup typeKeyResponse ) type keyArray [ed25519.PublicKeySize]byte type keyStore struct { core *core.Core address address.Address subnet address.Subnet mutex sync.Mutex keyToInfo map[keyArray]*keyInfo addrToInfo map[address.Address]*keyInfo addrBuffer map[address.Address]*buffer subnetToInfo map[address.Subnet]*keyInfo subnetBuffer map[address.Subnet]*buffer mtu uint64 } type keyInfo struct { key keyArray address address.Address subnet address.Subnet timeout *time.Timer // From calling a time.AfterFunc to do cleanup } type buffer struct { packet []byte timeout *time.Timer } func (k *keyStore) init(c *core.Core) { k.core = c k.address = *address.AddrForKey(k.core.PublicKey()) k.subnet = *address.SubnetForKey(k.core.PublicKey()) if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil { err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) panic(err) } k.keyToInfo = make(map[keyArray]*keyInfo) k.addrToInfo = make(map[address.Address]*keyInfo) k.addrBuffer = make(map[address.Address]*buffer) k.subnetToInfo = make(map[address.Subnet]*keyInfo) k.subnetBuffer = make(map[address.Subnet]*buffer) k.mtu = 1280 // Default to something safe, expect user to set this } func (k *keyStore) sendToAddress(addr address.Address, bs []byte) { k.mutex.Lock() if info := k.addrToInfo[addr]; info != nil { k.resetTimeout(info) k.mutex.Unlock() _, _ = k.core.WriteTo(bs, iwt.Addr(info.key[:])) } else { var buf *buffer if buf = k.addrBuffer[addr]; buf == nil { buf = new(buffer) k.addrBuffer[addr] = buf } msg := append([]byte(nil), bs...) buf.packet = msg if buf.timeout != nil { buf.timeout.Stop() } buf.timeout = time.AfterFunc(keyStoreTimeout, func() { k.mutex.Lock() defer k.mutex.Unlock() if nbuf := k.addrBuffer[addr]; nbuf == buf { delete(k.addrBuffer, addr) } }) k.mutex.Unlock() k.sendKeyLookup(addr.GetKey()) } } func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) { k.mutex.Lock() if info := k.subnetToInfo[subnet]; info != nil { k.resetTimeout(info) k.mutex.Unlock() _, _ = k.core.WriteTo(bs, iwt.Addr(info.key[:])) } else { var buf *buffer if buf = k.subnetBuffer[subnet]; buf == nil { buf = new(buffer) k.subnetBuffer[subnet] = buf } msg := append([]byte(nil), bs...) buf.packet = msg if buf.timeout != nil { buf.timeout.Stop() } buf.timeout = time.AfterFunc(keyStoreTimeout, func() { k.mutex.Lock() defer k.mutex.Unlock() if nbuf := k.subnetBuffer[subnet]; nbuf == buf { delete(k.subnetBuffer, subnet) } }) k.mutex.Unlock() k.sendKeyLookup(subnet.GetKey()) } } func (k *keyStore) update(key ed25519.PublicKey) *keyInfo { k.mutex.Lock() var kArray keyArray copy(kArray[:], key) var info *keyInfo var packets [][]byte if info = k.keyToInfo[kArray]; info == nil { info = new(keyInfo) info.key = kArray info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:])) info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:])) k.keyToInfo[info.key] = info k.addrToInfo[info.address] = info k.subnetToInfo[info.subnet] = info if buf := k.addrBuffer[info.address]; buf != nil { packets = append(packets, buf.packet) delete(k.addrBuffer, info.address) } if buf := k.subnetBuffer[info.subnet]; buf != nil { packets = append(packets, buf.packet) delete(k.subnetBuffer, info.subnet) } } k.resetTimeout(info) k.mutex.Unlock() for _, packet := range packets { k.core.WriteTo(packet, iwt.Addr(info.key[:])) } return info } func (k *keyStore) resetTimeout(info *keyInfo) { if info.timeout != nil { info.timeout.Stop() } info.timeout = time.AfterFunc(keyStoreTimeout, func() { k.mutex.Lock() defer k.mutex.Unlock() if nfo := k.keyToInfo[info.key]; nfo == info { delete(k.keyToInfo, info.key) } if nfo := k.addrToInfo[info.address]; nfo == info { delete(k.addrToInfo, info.address) } if nfo := k.subnetToInfo[info.subnet]; nfo == info { delete(k.subnetToInfo, info.subnet) } }) } func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { if len(data) != 1+ed25519.SignatureSize { return } sig := data[1:] switch data[0] { case typeKeyLookup: snet := *address.SubnetForKey(toKey) if snet == k.subnet && ed25519.Verify(fromKey, toKey[:], sig) { // This is looking for at least our subnet (possibly our address) // Send a response k.sendKeyResponse(fromKey) } case typeKeyResponse: // TODO keep a list of something to match against... // Ignore the response if it doesn't match anything of interest... if ed25519.Verify(fromKey, toKey[:], sig) { k.update(fromKey) } } } func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) { sig := ed25519.Sign(k.core.PrivateKey(), partial[:]) bs := append([]byte{typeKeyLookup}, sig...) _ = k.core.SendOutOfBand(partial, bs) } func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { sig := ed25519.Sign(k.core.PrivateKey(), dest[:]) bs := append([]byte{typeKeyResponse}, sig...) _ = k.core.SendOutOfBand(dest, bs) } func (k *keyStore) readPC(p []byte) (int, error) { buf := make([]byte, k.core.MTU(), 65535) for { bs := buf n, from, err := k.core.ReadFrom(bs) if err != nil { return n, err } if n == 0 { continue } bs = bs[:n] if len(bs) == 0 { continue } if bs[0]&0xf0 != 0x60 { continue // not IPv6 } if len(bs) < 40 { continue } k.mutex.Lock() mtu := int(k.mtu) k.mutex.Unlock() if len(bs) > mtu { // Using bs would make it leak off the stack, so copy to buf buf := make([]byte, 40) copy(buf, bs) ptb := &icmp.PacketTooBig{ MTU: mtu, Data: buf[:40], } if packet, err := CreateICMPv6(buf[8:24], buf[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil { _, _ = k.writePC(packet) } continue } var srcAddr, dstAddr address.Address var srcSubnet, dstSubnet address.Subnet copy(srcAddr[:], bs[8:]) copy(dstAddr[:], bs[24:]) copy(srcSubnet[:], bs[8:]) copy(dstSubnet[:], bs[24:]) if dstAddr != k.address && dstSubnet != k.subnet { continue // bad local address/subnet } info := k.update(ed25519.PublicKey(from.(iwt.Addr))) if srcAddr != info.address && srcSubnet != info.subnet { continue // bad remote address/subnet } n = copy(p, bs) return n, nil } } func (k *keyStore) writePC(bs []byte) (int, error) { if bs[0]&0xf0 != 0x60 { return 0, errors.New("not an IPv6 packet") // not IPv6 } if len(bs) < 40 { strErr := fmt.Sprint("undersized IPv6 packet, length: ", len(bs)) return 0, errors.New(strErr) } var srcAddr, dstAddr address.Address var srcSubnet, dstSubnet address.Subnet copy(srcAddr[:], bs[8:]) copy(dstAddr[:], bs[24:]) copy(srcSubnet[:], bs[8:]) copy(dstSubnet[:], bs[24:]) if srcAddr != k.address && srcSubnet != k.subnet { // This happens all the time due to link-local traffic // Don't send back an error, just drop it strErr := fmt.Sprint("incorrect source address: ", net.IP(srcAddr[:]).String()) return 0, errors.New(strErr) } if dstAddr.IsValid() { k.sendToAddress(dstAddr, bs) } else if dstSubnet.IsValid() { k.sendToSubnet(dstSubnet, bs) } else { return 0, errors.New("invalid destination address") } return len(bs), nil } // Exported API func (k *keyStore) MaxMTU() uint64 { return k.core.MTU() } func (k *keyStore) SetMTU(mtu uint64) { if mtu > k.MaxMTU() { mtu = k.MaxMTU() } if mtu < 1280 { mtu = 1280 } k.mutex.Lock() k.mtu = mtu k.mutex.Unlock() } func (k *keyStore) MTU() uint64 { k.mutex.Lock() mtu := k.mtu k.mutex.Unlock() return mtu } type ReadWriteCloser struct { keyStore } func NewReadWriteCloser(c *core.Core) *ReadWriteCloser { rwc := new(ReadWriteCloser) rwc.init(c) return rwc } func (rwc *ReadWriteCloser) Address() address.Address { return rwc.address } func (rwc *ReadWriteCloser) Subnet() address.Subnet { return rwc.subnet } func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) { return rwc.readPC(p) } func (rwc *ReadWriteCloser) Write(p []byte) (n int, err error) { return rwc.writePC(p) } func (rwc *ReadWriteCloser) Close() error { err := rwc.core.Close() rwc.core.Stop() return err } yggdrasil-go-0.4.3/src/multicast/000077500000000000000000000000001417776402100167135ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/multicast/admin.go000066400000000000000000000016701417776402100203360ustar00rootroot00000000000000package multicast import ( "encoding/json" "github.com/yggdrasil-network/yggdrasil-go/src/admin" ) type GetMulticastInterfacesRequest struct{} type GetMulticastInterfacesResponse struct { Interfaces []string `json:"multicast_interfaces"` } func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error { res.Interfaces = []string{} for _, v := range m.Interfaces() { res.Interfaces = append(res.Interfaces, v.Name) } return nil } func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { _ = a.AddHandler("getMulticastInterfaces", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetMulticastInterfacesRequest{} res := &GetMulticastInterfacesResponse{} if err := json.Unmarshal(in, &req); err != nil { return nil, err } if err := m.getMulticastInterfacesHandler(req, res); err != nil { return nil, err } return res, nil }) } yggdrasil-go-0.4.3/src/multicast/multicast.go000066400000000000000000000253151417776402100212550ustar00rootroot00000000000000package multicast import ( "bytes" "context" "crypto/ed25519" "encoding/binary" "encoding/hex" "fmt" "net" "net/url" "regexp" "time" "github.com/Arceliar/phony" "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/core" "golang.org/x/net/ipv6" ) // Multicast represents the multicast advertisement and discovery mechanism used // by Yggdrasil to find peers on the same subnet. When a beacon is received on a // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { phony.Inbox core *core.Core config *config.NodeConfig log *log.Logger sock *ipv6.PacketConn groupAddr string listeners map[string]*listenerInfo isOpen bool _interfaces map[string]interfaceInfo } type interfaceInfo struct { iface net.Interface addrs []net.Addr beacon bool listen bool port uint16 } type listenerInfo struct { listener *core.TcpListener time time.Time interval time.Duration port uint16 } // Init prepares the multicast interface for use. func (m *Multicast) Init(core *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error { m.core = core m.config = nc m.log = log m.listeners = make(map[string]*listenerInfo) m._interfaces = make(map[string]interfaceInfo) m.groupAddr = "[ff02::114]:9001" return nil } // Start starts the multicast interface. This launches goroutines which will // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. func (m *Multicast) Start() error { var err error phony.Block(m, func() { err = m._start() }) m.log.Debugln("Started multicast module") return err } func (m *Multicast) _start() error { if m.isOpen { return fmt.Errorf("multicast module is already started") } m.config.RLock() defer m.config.RUnlock() if len(m.config.MulticastInterfaces) == 0 { return nil } m.log.Infoln("Starting multicast module") addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err } listenString := fmt.Sprintf("[::]:%v", addr.Port) lc := net.ListenConfig{ Control: m.multicastReuse, } conn, err := lc.ListenPacket(context.Background(), "udp6", listenString) if err != nil { return err } m.sock = ipv6.NewPacketConn(conn) if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil { // nolint:staticcheck // Windows can't set this flag, so we need to handle it in other ways } m.isOpen = true go m.listen() m.Act(nil, m._multicastStarted) m.Act(nil, m._announce) return nil } // IsStarted returns true if the module has been started. func (m *Multicast) IsStarted() bool { var isOpen bool phony.Block(m, func() { isOpen = m.isOpen }) return isOpen } // Stop stops the multicast module. func (m *Multicast) Stop() error { var err error phony.Block(m, func() { err = m._stop() }) m.log.Debugln("Stopped multicast module") return err } func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false if m.sock != nil { m.sock.Close() } return nil } func (m *Multicast) _updateInterfaces() { interfaces := m.getAllowedInterfaces() for name, info := range interfaces { addrs, err := info.iface.Addrs() if err != nil { m.log.Warnf("Failed up get addresses for interface %s: %s", name, err) delete(interfaces, name) continue } info.addrs = addrs interfaces[name] = info } m._interfaces = interfaces } func (m *Multicast) Interfaces() map[string]net.Interface { interfaces := make(map[string]net.Interface) phony.Block(m, func() { for _, info := range m._interfaces { interfaces[info.iface.Name] = info.iface } }) return interfaces } // getAllowedInterfaces returns the currently known/enabled multicast interfaces. func (m *Multicast) getAllowedInterfaces() map[string]interfaceInfo { interfaces := make(map[string]interfaceInfo) // Get interface expressions from config ifcfgs := m.config.MulticastInterfaces // Ask the system for network interfaces allifaces, err := net.Interfaces() if err != nil { // Don't panic, since this may be from e.g. too many open files (from too much connection spam) // TODO? log something return nil } // Work out which interfaces to announce on for _, iface := range allifaces { if iface.Flags&net.FlagUp == 0 { // Ignore interfaces that are down continue } if iface.Flags&net.FlagMulticast == 0 { // Ignore non-multicast interfaces continue } if iface.Flags&net.FlagPointToPoint != 0 { // Ignore point-to-point interfaces continue } for _, ifcfg := range ifcfgs { // Compile each regular expression e, err := regexp.Compile(ifcfg.Regex) if err != nil { panic(err) } // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { if ifcfg.Beacon || ifcfg.Listen { info := interfaceInfo{ iface: iface, beacon: ifcfg.Beacon, listen: ifcfg.Listen, port: ifcfg.Port, } interfaces[iface.Name] = info } break } } } return interfaces } func (m *Multicast) _announce() { if !m.isOpen { return } m._updateInterfaces() groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) } destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) } // There might be interfaces that we configured listeners for but are no // longer up - if that's the case then we should stop the listeners for name, info := range m.listeners { // Prepare our stop function! stop := func() { info.listener.Stop() delete(m.listeners, name) m.log.Debugln("No longer multicasting on", name) } // If the interface is no longer visible on the system then stop the // listener, as another one will be started further down if _, ok := m._interfaces[name]; !ok { stop() continue } // It's possible that the link-local listener address has changed so if // that is the case then we should clean up the interface listener found := false listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String()) if err != nil { stop() continue } // Find the interface that matches the listener if info, ok := m._interfaces[name]; ok { for _, addr := range info.addrs { if ip, _, err := net.ParseCIDR(addr.String()); err == nil { // Does the interface address match our listener address? if ip.Equal(listenaddr.IP) { found = true break } } } } // If the address has not been found on the adapter then we should stop // and clean up the TCP listener. A new one will be created below if a // suitable link-local address is found if !found { stop() } } // Now that we have a list of valid interfaces from the operating system, // we can start checking if we can send multicasts on them for _, info := range m._interfaces { iface := info.iface for _, addr := range info.addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) // Ignore IPv4 addresses if addrIP.To4() != nil { continue } // Ignore non-link-local addresses if !addrIP.IsLinkLocalUnicast() { continue } if info.listen { // Join the multicast group, so we can listen for beacons _ = m.sock.JoinGroup(&iface, groupAddr) } if !info.beacon { break // Don't send multicast beacons or accept incoming connections } // Try and see if we already have a TCP listener for this interface var linfo *listenerInfo if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { // No listener was found - let's create one urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, info.port) u, err := url.Parse(urlString) if err != nil { panic(err) } if li, err := m.core.Listen(u, iface.Name); err == nil { m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed linfo = &listenerInfo{listener: li, time: time.Now()} m.listeners[iface.Name] = linfo } else { m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) } } else { // An existing listener was found linfo = m.listeners[iface.Name] } // Make sure nothing above failed for some reason if linfo == nil { continue } if time.Since(linfo.time) < linfo.interval { continue } // Get the listener details and construct the multicast beacon lladdr := linfo.listener.Listener.Addr().String() if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { a.Zone = "" destAddr.Zone = iface.Name msg := append([]byte(nil), m.core.GetSelf().Key...) msg = append(msg, a.IP...) pbs := make([]byte, 2) binary.BigEndian.PutUint16(pbs, uint16(a.Port)) msg = append(msg, pbs...) _, _ = m.sock.WriteTo(msg, nil, destAddr) } if linfo.interval.Seconds() < 15 { linfo.interval += time.Second } break } } time.AfterFunc(time.Second, func() { m.Act(nil, m._announce) }) } func (m *Multicast) listen() { groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) } bs := make([]byte, 2048) for { nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs) if err != nil { if !m.IsStarted() { return } panic(err) } if rcm != nil { // Windows can't set the flag needed to return a non-nil value here // So only make these checks if we get something useful back // TODO? Skip them always, I'm not sure if they're really needed... if !rcm.Dst.IsLinkLocalMulticast() { continue } if !rcm.Dst.Equal(groupAddr.IP) { continue } } if nBytes < ed25519.PublicKeySize { continue } var key ed25519.PublicKey key = append(key, bs[:ed25519.PublicKeySize]...) if bytes.Equal(key, m.core.GetSelf().Key) { continue // don't bother trying to peer with self } begin := ed25519.PublicKeySize end := nBytes - 2 if end <= begin { continue // malformed address } ip := bs[begin:end] port := binary.BigEndian.Uint16(bs[end:nBytes]) anAddr := net.TCPAddr{IP: ip, Port: int(port)} addr, err := net.ResolveTCPAddr("tcp6", anAddr.String()) if err != nil { continue } from := fromAddr.(*net.UDPAddr) if !from.IP.Equal(addr.IP) { continue } var interfaces map[string]interfaceInfo phony.Block(m, func() { interfaces = m._interfaces }) if info, ok := interfaces[from.Zone]; ok && info.listen { addr.Zone = "" pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key)) u, err := url.Parse("tls://" + addr.String() + pin) if err != nil { m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err) } if err := m.core.CallPeer(u, from.Zone); err != nil { m.log.Debugln("Call from multicast failed:", err) } } } } yggdrasil-go-0.4.3/src/multicast/multicast_darwin.go000066400000000000000000000013161417776402100226140ustar00rootroot00000000000000//go:build !cgo && (darwin || ios) // +build !cgo // +build darwin ios package multicast import ( "syscall" "golang.org/x/sys/unix" ) func (m *Multicast) _multicastStarted() { } func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error var reuseport error var recvanyif error control = c.Control(func(fd uintptr) { reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) // sys/socket.h: #define SO_RECV_ANYIF 0x1104 recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) }) switch { case reuseport != nil: return reuseport case recvanyif != nil: return recvanyif default: return control } } yggdrasil-go-0.4.3/src/multicast/multicast_darwin_cgo.go000066400000000000000000000026311417776402100234450ustar00rootroot00000000000000//go:build (darwin && cgo) || (ios && cgo) // +build darwin,cgo ios,cgo package multicast /* #cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Foundation #import NSNetServiceBrowser *serviceBrowser; void StartAWDLBrowsing() { if (serviceBrowser == nil) { serviceBrowser = [[NSNetServiceBrowser alloc] init]; serviceBrowser.includesPeerToPeer = YES; } [serviceBrowser searchForServicesOfType:@"_yggdrasil._tcp" inDomain:@""]; } void StopAWDLBrowsing() { if (serviceBrowser == nil) { return; } [serviceBrowser stop]; } */ import "C" import ( "syscall" "time" "golang.org/x/sys/unix" ) func (m *Multicast) _multicastStarted() { if !m.isOpen { return } C.StopAWDLBrowsing() for intf := range m._interfaces { if intf == "awdl0" { C.StartAWDLBrowsing() break } } time.AfterFunc(time.Minute, func() { m.Act(nil, m._multicastStarted) }) } func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error var reuseport error var recvanyif error control = c.Control(func(fd uintptr) { reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) // sys/socket.h: #define SO_RECV_ANYIF 0x1104 recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1) }) switch { case reuseport != nil: return reuseport case recvanyif != nil: return recvanyif default: return control } } yggdrasil-go-0.4.3/src/multicast/multicast_other.go000066400000000000000000000005641417776402100224550ustar00rootroot00000000000000//go:build !linux && !darwin && !ios && !netbsd && !freebsd && !openbsd && !dragonflybsd && !windows // +build !linux,!darwin,!ios,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows package multicast import "syscall" func (m *Multicast) _multicastStarted() { } func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { return nil } yggdrasil-go-0.4.3/src/multicast/multicast_unix.go000066400000000000000000000011021417776402100223040ustar00rootroot00000000000000//go:build linux || netbsd || freebsd || openbsd || dragonflybsd // +build linux netbsd freebsd openbsd dragonflybsd package multicast import "syscall" import "golang.org/x/sys/unix" func (m *Multicast) _multicastStarted() { } func (m *Multicast) 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 } } yggdrasil-go-0.4.3/src/multicast/multicast_windows.go000066400000000000000000000010111417776402100230120ustar00rootroot00000000000000//go:build windows // +build windows package multicast import "syscall" import "golang.org/x/sys/windows" func (m *Multicast) _multicastStarted() { } func (m *Multicast) 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 } } yggdrasil-go-0.4.3/src/tuntap/000077500000000000000000000000001417776402100162215ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/tuntap/admin.go000066400000000000000000000014041417776402100176370ustar00rootroot00000000000000package tuntap import ( "encoding/json" "github.com/yggdrasil-network/yggdrasil-go/src/admin" ) type GetTUNRequest struct{} type GetTUNResponse map[string]TUNEntry type TUNEntry struct { MTU uint64 `json:"mtu"` } func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error { *res = GetTUNResponse{ t.Name(): TUNEntry{ MTU: t.MTU(), }, } return nil } func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { _ = a.AddHandler("getTunTap", []string{}, func(in json.RawMessage) (interface{}, error) { req := &GetTUNRequest{} res := &GetTUNResponse{} if err := json.Unmarshal(in, &req); err != nil { return nil, err } if err := t.getTUNHandler(req, res); err != nil { return nil, err } return res, nil }) } yggdrasil-go-0.4.3/src/tuntap/iface.go000066400000000000000000000021151417776402100176160ustar00rootroot00000000000000package tuntap const TUN_OFFSET_BYTES = 4 func (tun *TunAdapter) read() { var buf [TUN_OFFSET_BYTES + 65535]byte for { n, err := tun.iface.Read(buf[:], TUN_OFFSET_BYTES) if n <= TUN_OFFSET_BYTES || err != nil { tun.log.Errorln("Error reading TUN:", err) ferr := tun.iface.Flush() if ferr != nil { tun.log.Errorln("Unable to flush packets:", ferr) } return } begin := TUN_OFFSET_BYTES end := begin + n bs := buf[begin:end] if _, err := tun.rwc.Write(bs); err != nil { tun.log.Debugln("Unable to send packet:", err) } } } func (tun *TunAdapter) write() { var buf [TUN_OFFSET_BYTES + 65535]byte for { bs := buf[TUN_OFFSET_BYTES:] n, err := tun.rwc.Read(bs) if err != nil { tun.log.Errorln("Exiting tun writer due to core read error:", err) return } if !tun.isEnabled { continue // Nothing to do, the tun isn't enabled } bs = buf[:TUN_OFFSET_BYTES+n] if _, err = tun.iface.Write(bs, TUN_OFFSET_BYTES); err != nil { tun.Act(nil, func() { if !tun.isOpen { tun.log.Errorln("TUN iface write error:", err) } }) } } } yggdrasil-go-0.4.3/src/tuntap/tun.go000066400000000000000000000121531417776402100173600ustar00rootroot00000000000000package tuntap // This manages the tun driver to send/recv packets to/from applications // TODO: Crypto-key routing support // TODO: Set MTU of session properly // TODO: Reject packets that exceed session MTU with ICMPv6 for PMTU Discovery // TODO: Connection timeouts (call Conn.Close() when we want to time out) // TODO: Don't block in reader on writes that are pending searches import ( "errors" "fmt" "net" //"sync" "github.com/Arceliar/phony" "github.com/gologme/log" "golang.zx2c4.com/wireguard/tun" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" ) type MTU uint16 // TunAdapter represents a running TUN interface and extends the // yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you // should pass this object to the yggdrasil.SetRouterAdapter() function before // calling yggdrasil.Start(). type TunAdapter struct { rwc *ipv6rwc.ReadWriteCloser config *config.NodeConfig log *log.Logger addr address.Address subnet address.Subnet mtu uint64 iface tun.Device phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below //mutex sync.RWMutex // Protects the below isOpen bool isEnabled bool // Used by the writer to drop sessionTraffic if not enabled } // Gets the maximum supported MTU for the platform based on the defaults in // defaults.GetDefaults(). func getSupportedMTU(mtu uint64) uint64 { if mtu < 1280 { return 1280 } if mtu > MaximumMTU() { return MaximumMTU() } return mtu } // Name returns the name of the adapter, e.g. "tun0". On Windows, this may // return a canonical adapter name instead. func (tun *TunAdapter) Name() string { if name, err := tun.iface.Name(); err == nil { return name } return "" } // MTU gets the adapter's MTU. This can range between 1280 and 65535, although // the maximum value is determined by your platform. The returned value will // never exceed that of MaximumMTU(). func (tun *TunAdapter) MTU() uint64 { return getSupportedMTU(tun.mtu) } // DefaultName gets the default TUN interface name for your platform. func DefaultName() string { return defaults.GetDefaults().DefaultIfName } // DefaultMTU gets the default TUN interface MTU for your platform. This can // be as high as MaximumMTU(), depending on platform, but is never lower than 1280. func DefaultMTU() uint64 { return defaults.GetDefaults().DefaultIfMTU } // MaximumMTU returns the maximum supported TUN interface MTU for your // platform. This can be as high as 65535, depending on platform, but is never // lower than 1280. func MaximumMTU() uint64 { return defaults.GetDefaults().MaximumIfMTU } // Init initialises the TUN module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. func (tun *TunAdapter) Init(rwc *ipv6rwc.ReadWriteCloser, config *config.NodeConfig, log *log.Logger, options interface{}) error { tun.rwc = rwc tun.config = config tun.log = log return nil } // Start the setup process for the TUN adapter. If successful, starts the // reader actor to handle packets on that interface. func (tun *TunAdapter) Start() error { var err error phony.Block(tun, func() { err = tun._start() }) return err } func (tun *TunAdapter) _start() error { if tun.isOpen { return errors.New("TUN module is already started") } if tun.config == nil { return errors.New("no configuration available to TUN") } tun.config.RLock() defer tun.config.RUnlock() tun.addr = tun.rwc.Address() tun.subnet = tun.rwc.Subnet() addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if tun.config.IfName == "none" || tun.config.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") tun.isEnabled = false go tun.write() return nil } mtu := tun.config.IfMTU if tun.rwc.MaxMTU() < mtu { mtu = tun.rwc.MaxMTU() } if err := tun.setup(tun.config.IfName, addr, mtu); err != nil { return err } if tun.MTU() != mtu { tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", tun.config.IfMTU, tun.MTU(), MaximumMTU()) } tun.rwc.SetMTU(tun.MTU()) tun.isOpen = true tun.isEnabled = true go tun.read() go tun.write() return nil } // IsStarted returns true if the module has been started. func (tun *TunAdapter) IsStarted() bool { var isOpen bool phony.Block(tun, func() { isOpen = tun.isOpen }) return isOpen } // Start the setup process for the TUN adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Stop() error { var err error phony.Block(tun, func() { err = tun._stop() }) return err } func (tun *TunAdapter) _stop() error { tun.isOpen = false // by TUN, e.g. readers/writers, sessions if tun.iface != nil { // Just in case we failed to start up the iface for some reason, this can apparently happen on Windows tun.iface.Close() } return nil } yggdrasil-go-0.4.3/src/tuntap/tun_bsd.go000066400000000000000000000101241417776402100202040ustar00rootroot00000000000000//go:build openbsd || freebsd // +build openbsd freebsd package tuntap import ( "encoding/binary" "os/exec" "strconv" "strings" "syscall" "unsafe" "golang.org/x/sys/unix" wgtun "golang.zx2c4.com/wireguard/tun" ) const SIOCSIFADDR_IN6 = (0x80000000) | ((288 & 0x1fff) << 16) | uint32(byte('i'))<<8 | 12 type in6_addrlifetime struct { ia6t_expire float64 ia6t_preferred float64 ia6t_vltime uint32 ia6t_pltime uint32 } type sockaddr_in6 struct { sin6_len uint8 sin6_family uint8 sin6_port uint8 sin6_flowinfo uint32 sin6_addr [8]uint16 sin6_scope_id uint32 } /* from struct in6_ifreq { 277 char ifr_name[IFNAMSIZ]; 278 union { 279 struct sockaddr_in6 ifru_addr; 280 struct sockaddr_in6 ifru_dstaddr; 281 int ifru_flags; 282 int ifru_flags6; 283 int ifru_metric; 284 caddr_t ifru_data; 285 struct in6_addrlifetime ifru_lifetime; 286 struct in6_ifstat ifru_stat; 287 struct icmp6_ifstat ifru_icmp6stat; 288 u_int32_t ifru_scope_id[16]; 289 } ifr_ifru; 290 }; */ type in6_ifreq_mtu struct { ifr_name [syscall.IFNAMSIZ]byte ifru_mtu int } type in6_ifreq_addr struct { ifr_name [syscall.IFNAMSIZ]byte ifru_addr sockaddr_in6 } type in6_ifreq_flags struct { ifr_name [syscall.IFNAMSIZ]byte flags int } type in6_ifreq_lifetime struct { ifr_name [syscall.IFNAMSIZ]byte ifru_addrlifetime in6_addrlifetime } // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { iface, err := wgtun.CreateTUN(ifname, int(mtu)) if err != nil { panic(err) } tun.iface = iface if mtu, err := iface.MTU(); err == nil { tun.mtu = getSupportedMTU(uint64(mtu)) } else { tun.mtu = 0 } return tun.setupAddress(addr) } func (tun *TunAdapter) setupAddress(addr string) error { var sfd int var err error // Create system socket if sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0); err != nil { tun.log.Printf("Create AF_INET socket failed: %v.", err) return err } // Friendly output tun.log.Infof("Interface name: %s", tun.Name()) tun.log.Infof("Interface IPv6: %s", addr) tun.log.Infof("Interface MTU: %d", tun.mtu) // Create the MTU request var ir in6_ifreq_mtu copy(ir.ifr_name[:], tun.Name()) ir.ifru_mtu = int(tun.mtu) // Set the MTU if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) // Fall back to ifconfig to set the MTU cmd := exec.Command("ifconfig", tun.Name(), "mtu", string(tun.mtu)) tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err) tun.log.Traceln(string(output)) } } // Create the address request // FIXME: I don't work! var ar in6_ifreq_addr copy(ar.ifr_name[:], tun.Name()) ar.ifru_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifru_addr)) ar.ifru_addr.sin6_family = unix.AF_INET6 parts := strings.Split(strings.Split(addr, "/")[0], ":") for i := 0; i < 8; i++ { addr, _ := strconv.ParseUint(parts[i], 16, 16) b := make([]byte, 16) binary.LittleEndian.PutUint16(b, uint16(addr)) ar.ifru_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b)) } // Set the interface address if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(SIOCSIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno) // Fall back to ifconfig to set the address cmd := exec.Command("ifconfig", tun.Name(), "inet6", addr) tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err) tun.log.Traceln(string(output)) } } return nil } yggdrasil-go-0.4.3/src/tuntap/tun_darwin.go000066400000000000000000000067371417776402100207370ustar00rootroot00000000000000//go:build !mobile // +build !mobile package tuntap // The darwin platform specific tun parts import ( "encoding/binary" "strconv" "strings" "unsafe" "golang.org/x/sys/unix" wgtun "golang.zx2c4.com/wireguard/tun" ) // Configures the "utun" adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { if ifname == "auto" { ifname = "utun" } iface, err := wgtun.CreateTUN(ifname, int(mtu)) if err != nil { panic(err) } tun.iface = iface if m, err := iface.MTU(); err == nil { tun.mtu = getSupportedMTU(uint64(m)) } else { tun.mtu = 0 } return tun.setupAddress(addr) } const ( darwin_SIOCAIFADDR_IN6 = 2155899162 // netinet6/in6_var.h darwin_IN6_IFF_NODAD = 0x0020 // netinet6/in6_var.h darwin_IN6_IFF_SECURED = 0x0400 // netinet6/in6_var.h darwin_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h ) // nolint:structcheck type in6_addrlifetime struct { ia6t_expire float64 // nolint:unused ia6t_preferred float64 // nolint:unused ia6t_vltime uint32 ia6t_pltime uint32 } // nolint:structcheck type sockaddr_in6 struct { sin6_len uint8 sin6_family uint8 sin6_port uint8 // nolint:unused sin6_flowinfo uint32 // nolint:unused sin6_addr [8]uint16 sin6_scope_id uint32 // nolint:unused } // nolint:structcheck type in6_aliasreq struct { ifra_name [16]byte ifra_addr sockaddr_in6 ifra_dstaddr sockaddr_in6 // nolint:unused ifra_prefixmask sockaddr_in6 ifra_flags uint32 ifra_lifetime in6_addrlifetime } type ifreq struct { ifr_name [16]byte ifru_mtu uint32 } // Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using // a system socket and making direct syscalls to the kernel. func (tun *TunAdapter) setupAddress(addr string) error { var fd int var err error if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err) return err } var ar in6_aliasreq copy(ar.ifra_name[:], tun.Name()) ar.ifra_prefixmask.sin6_len = uint8(unsafe.Sizeof(ar.ifra_prefixmask)) b := make([]byte, 16) binary.LittleEndian.PutUint16(b, uint16(0xFE00)) ar.ifra_prefixmask.sin6_addr[0] = binary.BigEndian.Uint16(b) ar.ifra_addr.sin6_len = uint8(unsafe.Sizeof(ar.ifra_addr)) ar.ifra_addr.sin6_family = unix.AF_INET6 parts := strings.Split(strings.Split(addr, "/")[0], ":") for i := 0; i < 8; i++ { addr, _ := strconv.ParseUint(parts[i], 16, 16) b := make([]byte, 16) binary.LittleEndian.PutUint16(b, uint16(addr)) ar.ifra_addr.sin6_addr[i] = binary.BigEndian.Uint16(b) } ar.ifra_flags |= darwin_IN6_IFF_NODAD ar.ifra_flags |= darwin_IN6_IFF_SECURED ar.ifra_lifetime.ia6t_vltime = darwin_ND6_INFINITE_LIFETIME ar.ifra_lifetime.ia6t_pltime = darwin_ND6_INFINITE_LIFETIME var ir ifreq copy(ir.ifr_name[:], tun.Name()) ir.ifru_mtu = uint32(tun.mtu) tun.log.Infof("Interface name: %s", ar.ifra_name) tun.log.Infof("Interface IPv6: %s", addr) tun.log.Infof("Interface MTU: %d", ir.ifru_mtu) if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { err = errno tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno) return err } if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { err = errno tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) return err } return err } yggdrasil-go-0.4.3/src/tuntap/tun_linux.go000066400000000000000000000027211417776402100205770ustar00rootroot00000000000000//go:build !mobile // +build !mobile package tuntap // The linux platform specific tun parts import ( "github.com/vishvananda/netlink" wgtun "golang.zx2c4.com/wireguard/tun" ) // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { if ifname == "auto" { ifname = "\000" } iface, err := wgtun.CreateTUN(ifname, int(mtu)) if err != nil { panic(err) } tun.iface = iface if mtu, err := iface.MTU(); err == nil { tun.mtu = getSupportedMTU(uint64(mtu)) } else { tun.mtu = 0 } return tun.setupAddress(addr) } // Configures the TAP adapter with the correct IPv6 address and MTU. Netlink // is used to do this, so there is not a hard requirement on "ip" or "ifconfig" // to exist on the system, but this will fail if Netlink is not present in the // kernel (it nearly always is). func (tun *TunAdapter) setupAddress(addr string) error { nladdr, err := netlink.ParseAddr(addr) if err != nil { return err } nlintf, err := netlink.LinkByName(tun.Name()) if err != nil { return err } if err := netlink.AddrAdd(nlintf, nladdr); err != nil { return err } if err := netlink.LinkSetMTU(nlintf, int(tun.mtu)); err != nil { return err } if err := netlink.LinkSetUp(nlintf); err != nil { return err } // Friendly output tun.log.Infof("Interface name: %s", tun.Name()) tun.log.Infof("Interface IPv6: %s", addr) tun.log.Infof("Interface MTU: %d", tun.mtu) return nil } yggdrasil-go-0.4.3/src/tuntap/tun_other.go000066400000000000000000000020121417776402100205520ustar00rootroot00000000000000//go:build !linux && !darwin && !windows && !openbsd && !freebsd && !mobile // +build !linux,!darwin,!windows,!openbsd,!freebsd,!mobile package tuntap // This is to catch unsupported platforms // If your platform supports tun devices, you could try configuring it manually import ( wgtun "golang.zx2c4.com/wireguard/tun" ) // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { iface, err := wgtun.CreateTUN(ifname, mtu) if err != nil { panic(err) } tun.iface = iface if mtu, err := iface.MTU(); err == nil { tun.mtu = getSupportedMTU(uint64(mtu)) } else { tun.mtu = 0 } return tun.setupAddress(addr) } // We don't know how to set the IPv6 address on an unknown platform, therefore // write about it to stdout and don't try to do anything further. func (tun *TunAdapter) setupAddress(addr string) error { tun.log.Warnln("Warning: Platform not supported, you must set the address of", tun.Name(), "to", addr) return nil } yggdrasil-go-0.4.3/src/tuntap/tun_windows.go000066400000000000000000000100751417776402100211330ustar00rootroot00000000000000//go:build windows // +build windows package tuntap import ( "bytes" "errors" "log" "net" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "golang.org/x/sys/windows" wgtun "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/windows/elevate" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ) // This is to catch Windows platforms // Configures the TUN adapter with the correct IPv6 address and MTU. func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { if ifname == "auto" { ifname = defaults.GetDefaults().DefaultIfName } return elevate.DoAsSystem(func() error { var err error var iface wgtun.Device var guid windows.GUID if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil { return err } if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)); err != nil { return err } tun.iface = iface if err = tun.setupAddress(addr); err != nil { tun.log.Errorln("Failed to set up TUN address:", err) return err } if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil { tun.log.Errorln("Failed to set up TUN MTU:", err) return err } if mtu, err := iface.MTU(); err == nil { tun.mtu = uint64(mtu) } return nil }) } // Sets the MTU of the TAP adapter. func (tun *TunAdapter) setupMTU(mtu uint64) error { if tun.iface == nil || tun.Name() == "" { return errors.New("Can't configure MTU as TUN adapter is not present") } if intf, ok := tun.iface.(*wgtun.NativeTun); ok { luid := winipcfg.LUID(intf.LUID()) ipfamily, err := luid.IPInterface(windows.AF_INET6) if err != nil { return err } ipfamily.NLMTU = uint32(mtu) intf.ForceMTU(int(ipfamily.NLMTU)) ipfamily.UseAutomaticMetric = false ipfamily.Metric = 0 ipfamily.DadTransmits = 0 ipfamily.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled if err := ipfamily.Set(); err != nil { return err } } return nil } // Sets the IPv6 address of the TAP adapter. func (tun *TunAdapter) setupAddress(addr string) error { if tun.iface == nil || tun.Name() == "" { return errors.New("Can't configure IPv6 address as TUN adapter is not present") } if intf, ok := tun.iface.(*wgtun.NativeTun); ok { if ipaddr, ipnet, err := net.ParseCIDR(addr); err == nil { luid := winipcfg.LUID(intf.LUID()) addresses := append([]net.IPNet{}, net.IPNet{ IP: ipaddr, Mask: ipnet.Mask, }) err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses) if err == windows.ERROR_OBJECT_ALREADY_EXISTS { cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses) err = luid.SetIPAddressesForFamily(windows.AF_INET6, addresses) } if err != nil { return err } } else { return err } } else { return errors.New("unable to get NativeTUN") } return nil } /* * cleanupAddressesOnDisconnectedInterfaces * SPDX-License-Identifier: MIT * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. */ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) { if len(addresses) == 0 { return } includedInAddresses := func(a net.IPNet) bool { // TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer! for _, addr := range addresses { ip := addr.IP if ip4 := ip.To4(); ip4 != nil { ip = ip4 } mA, _ := addr.Mask.Size() mB, _ := a.Mask.Size() if bytes.Equal(ip, a.IP) && mA == mB { return true } } return false } interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) if err != nil { return } for _, iface := range interfaces { if iface.OperStatus == winipcfg.IfOperStatusUp { continue } for address := iface.FirstUnicastAddress; address != nil; address = address.Next { ip := address.Address.IP() ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))} if includedInAddresses(ipnet) { log.Printf("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName()) iface.LUID.DeleteIPAddress(ipnet) } } } } yggdrasil-go-0.4.3/src/util/000077500000000000000000000000001417776402100156635ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/util/util.go000066400000000000000000000021571417776402100171740ustar00rootroot00000000000000// Package util contains miscellaneous utilities used by yggdrasil. // In particular, this includes a crypto worker pool, Cancellation machinery, and a sync.Pool used to reuse []byte. package util // These are misc. utility functions that didn't really fit anywhere else import ( "time" ) // TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing. func TimerStop(t *time.Timer) bool { stopped := t.Stop() select { case <-t.C: default: } return stopped } // FuncTimeout runs the provided function in a separate goroutine, and returns true if the function finishes executing before the timeout passes, or false if the timeout passes. // It includes no mechanism to stop the function if the timeout fires, so the user is expected to do so on their own (such as with a Cancellation or a context). func FuncTimeout(timeout time.Duration, f func()) bool { success := make(chan struct{}) go func() { defer close(success) f() }() timer := time.NewTimer(timeout) defer TimerStop(timer) select { case <-success: return true case <-timer.C: return false } } yggdrasil-go-0.4.3/src/version/000077500000000000000000000000001417776402100163735ustar00rootroot00000000000000yggdrasil-go-0.4.3/src/version/version.go000066400000000000000000000007651417776402100204170ustar00rootroot00000000000000package version var buildName string var buildVersion string // BuildName gets the current build name. This is usually injected if built // from git, or returns "unknown" otherwise. func BuildName() string { if buildName == "" { return "unknown" } return buildName } // BuildVersion gets the current build version. This is usually injected if // built from git, or returns "unknown" otherwise. func BuildVersion() string { if buildVersion == "" { return "unknown" } return buildVersion }