diff --git a/.circleci/config.yml b/.circleci/config.yml
index 99088d1c..f9cd0720 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,7 +5,7 @@ version: 2.1
jobs:
build-linux:
docker:
- - image: circleci/golang:1.12
+ - image: circleci/golang:1.12.7
steps:
- checkout
@@ -16,13 +16,21 @@ jobs:
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 alien
+ name: Install RPM utilities
command: |
- sudo apt-get install -y alien
+ sudo apt-get install -y rpm file
+ mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS
- run:
name: Test debug builds
@@ -31,7 +39,7 @@ jobs:
test -f yggdrasil && test -f yggdrasilctl
- run:
- name: Build for Linux (including Debian packages and RPMs)
+ 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;
@@ -40,9 +48,24 @@ jobs:
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=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64;
- sudo alien --to-rpm yggdrasil*.deb --scripts --keep-version && mv *.rpm /tmp/upload/;
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
command: |
@@ -79,11 +102,11 @@ jobs:
echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- run:
- name: Install Go 1.12
+ name: Install Go 1.12.7
command: |
cd /tmp
- curl -LO https://dl.google.com/go/go1.12.darwin-amd64.pkg
- sudo installer -pkg /tmp/go1.12.darwin-amd64.pkg -target /
+ curl -LO https://dl.google.com/go/go1.12.7.darwin-amd64.pkg
+ sudo installer -pkg /tmp/go1.12.7.darwin-amd64.pkg -target /
#- run:
# name: Install Gomobile
@@ -119,7 +142,7 @@ jobs:
build-other:
docker:
- - image: circleci/golang:1.12
+ - image: circleci/golang:1.12.7
steps:
- checkout
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5894598c..ee50dcc4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities.
-->
+## [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)
diff --git a/README.md b/README.md
index e9e0c7d9..3f07a2b2 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ some of the below:
- FreeBSD
- OpenBSD
- NetBSD
+- OpenWrt
Please see our [Platforms](https://yggdrasil-network.github.io/) pages for more
specific information about each of our supported platforms, including
diff --git a/build b/build
index f6c72466..80a971f9 100755
--- a/build
+++ b/build
@@ -7,40 +7,45 @@ 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 "udaitc:l:" option
+while getopts "uaitc:l:dro:" option
do
- case "${option}"
+ case "$option"
in
u) UPX=true;;
- d) DEBUG=true;;
i) IOS=true;;
a) ANDROID=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";;
esac
done
-if [ -z $TABLES ]; then
- STRIP="-s -w"
+if [ -z $TABLES ] && [ -z $DEBUG ]; then
+ LDFLAGS="$LDFLAGS -s -w"
fi
if [ $IOS ]; then
echo "Building framework for iOS"
- gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil
+ gomobile bind -target ios -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
+ github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \
+ github.com/yggdrasil-network/yggdrasil-go/src/config \
+ github.com/yggdrasil-network/yggdrasil-extras/src/mobile
elif [ $ANDROID ]; then
echo "Building aar for Android"
- gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil
+ gomobile bind -target android -tags mobile -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
+ github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil \
+ github.com/yggdrasil-network/yggdrasil-go/src/config \
+ github.com/yggdrasil-network/yggdrasil-extras/src/mobile
else
for CMD in `ls cmd/` ; do
echo "Building: $CMD"
+ go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD
- if [ $DEBUG ]; then
- go build -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" -tags debug -v ./cmd/$CMD
- else
- go build -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" -v ./cmd/$CMD
- fi
if [ $UPX ]; then
upx --brute $CMD
fi
diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go
index 8c8340f0..129b01d5 100644
--- a/cmd/yggdrasil/main.go
+++ b/cmd/yggdrasil/main.go
@@ -2,6 +2,7 @@ package main
import (
"bytes"
+ "encoding/hex"
"encoding/json"
"flag"
"fmt"
@@ -14,22 +15,28 @@ import (
"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/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
+ "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
+ "github.com/yggdrasil-network/yggdrasil-go/src/multicast"
+ "github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
-type nodeConfig = config.NodeConfig
-type Core = yggdrasil.Core
-
type node struct {
- core Core
+ core yggdrasil.Core
+ state *config.NodeState
+ tuntap tuntap.TunAdapter
+ multicast multicast.Multicast
+ admin admin.AdminSocket
}
-func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig {
+func readConfig(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.
@@ -72,77 +79,6 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeCo
panic(err)
}
json.Unmarshal(confJson, &cfg)
- // For now we will do a little bit to help the user adjust their
- // configuration to match the new configuration format, as some of the key
- // names have changed recently.
- changes := map[string]string{
- "Multicast": "",
- "ReadTimeout": "",
- "LinkLocal": "MulticastInterfaces",
- "BoxPub": "EncryptionPublicKey",
- "BoxPriv": "EncryptionPrivateKey",
- "SigPub": "SigningPublicKey",
- "SigPriv": "SigningPrivateKey",
- "AllowedBoxPubs": "AllowedEncryptionPublicKeys",
- }
- // Loop over the mappings aove and see if we have anything to fix.
- for from, to := range changes {
- if _, ok := dat[from]; ok {
- if to == "" {
- if !*normaliseconf {
- log.Println("Warning: Config option", from, "is deprecated")
- }
- } else {
- if !*normaliseconf {
- log.Println("Warning: Config option", from, "has been renamed - please change to", to)
- }
- // If the configuration file doesn't already contain a line with the
- // new name then set it to the old value. This makes sure that we
- // don't overwrite something that was put there intentionally.
- if _, ok := dat[to]; !ok {
- dat[to] = dat[from]
- }
- }
- }
- }
- // Check to see if the peers are in a parsable format, if not then default
- // them to the TCP scheme
- if peers, ok := dat["Peers"].([]interface{}); ok {
- for index, peer := range peers {
- uri := peer.(string)
- if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
- continue
- }
- if strings.HasPrefix(uri, "tcp:") {
- uri = uri[4:]
- }
- (dat["Peers"].([]interface{}))[index] = "tcp://" + uri
- }
- }
- // Now do the same with the interface peers
- if interfacepeers, ok := dat["InterfacePeers"].(map[string]interface{}); ok {
- for intf, peers := range interfacepeers {
- for index, peer := range peers.([]interface{}) {
- uri := peer.(string)
- if strings.HasPrefix(uri, "tcp://") || strings.HasPrefix(uri, "socks://") {
- continue
- }
- if strings.HasPrefix(uri, "tcp:") {
- uri = uri[4:]
- }
- ((dat["InterfacePeers"].(map[string]interface{}))[intf]).([]interface{})[index] = "tcp://" + uri
- }
- }
- }
- // Do a quick check for old-format Listen statement so that mapstructure
- // doesn't fail and crash
- if listen, ok := dat["Listen"].(string); ok {
- if strings.HasPrefix(listen, "tcp://") {
- dat["Listen"] = []string{listen}
- } else {
- dat["Listen"] = []string{"tcp://" + listen}
- }
- }
// Overlay our newly mapped configuration onto the autoconf node config that
// we generated above.
if err = mapstructure.Decode(dat, &cfg); err != nil {
@@ -179,14 +115,15 @@ func main() {
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
version := flag.Bool("version", false, "prints the version of this build")
logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable")
+ logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
flag.Parse()
- var cfg *nodeConfig
+ var cfg *config.NodeConfig
var err error
switch {
case *version:
- fmt.Println("Build name:", yggdrasil.GetBuildName())
- fmt.Println("Build version:", yggdrasil.GetBuildVersion())
+ fmt.Println("Build name:", yggdrasil.BuildName())
+ fmt.Println("Build version:", yggdrasil.BuildVersion())
os.Exit(0)
case *autoconf:
// Use an autoconf-generated config, this will give us random keys and
@@ -226,7 +163,23 @@ func main() {
return
}
// Create a new logger that logs output to stdout.
- logger := log.New(os.Stdout, "", log.Flags())
+ var logger *log.Logger
+ switch *logto {
+ case "stdout":
+ logger = log.New(os.Stdout, "", log.Flags())
+ case "syslog":
+ if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", yggdrasil.BuildName()); err == nil {
+ logger = log.New(syslogger, "", log.Flags())
+ }
+ default:
+ if logfd, err := os.OpenFile(*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")
+ }
//logger.EnableLevel("error")
//logger.EnableLevel("warn")
//logger.EnableLevel("info")
@@ -244,22 +197,44 @@ func main() {
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
// don't need to create this manually.
n := node{}
- // Now that we have a working configuration, we can now actually start
- // Yggdrasil. This will start the router, switch, DHT node, TCP and UDP
- // sockets, TUN/TAP adapter and multicast discovery port.
- if err := n.core.Start(cfg, logger); err != nil {
+ // Now start Yggdrasil - this starts the DHT, router, switch and other core
+ // components needed for Yggdrasil to operate
+ n.state, err = n.core.Start(cfg, logger)
+ if err != nil {
logger.Errorln("An error occurred during startup")
panic(err)
}
- // The Stop function ensures that the TUN/TAP adapter is correctly shut down
- // before the program exits.
- defer func() {
- n.core.Stop()
- }()
+ // Register the session firewall gatekeeper function
+ n.core.SetSessionGatekeeper(n.sessionFirewall)
+ // Start the admin socket
+ n.admin.Init(&n.core, n.state, logger, nil)
+ if err := n.admin.Start(); err != nil {
+ logger.Errorln("An error occurred starting admin socket:", err)
+ }
+ // Start the multicast interface
+ n.multicast.Init(&n.core, n.state, logger, nil)
+ 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
+ if listener, err := n.core.ConnListen(); err == nil {
+ if dialer, err := n.core.ConnDialer(); err == nil {
+ n.tuntap.Init(n.state, logger, listener, dialer)
+ if err := n.tuntap.Start(); err != nil {
+ logger.Errorln("An error occurred starting TUN/TAP:", err)
+ }
+ n.tuntap.SetupAdminHandlers(&n.admin)
+ } else {
+ logger.Errorln("Unable to get Dialer:", err)
+ }
+ } else {
+ logger.Errorln("Unable to get Listener:", err)
+ }
// 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.GetAddress()
- subnet := n.core.GetSubnet()
+ address := n.core.Address()
+ subnet := n.core.Subnet()
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.
@@ -267,25 +242,96 @@ func main() {
r := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
signal.Notify(r, os.Interrupt, syscall.SIGHUP)
- // Create a function to capture the service being stopped on Windows.
- winTerminate := func() {
- c <- os.Interrupt
- }
- minwinsvc.SetOnExit(winTerminate)
+ // Capture the service being stopped on Windows.
+ minwinsvc.SetOnExit(n.shutdown)
+ defer n.shutdown()
// Wait for the terminate/interrupt signal. Once a signal is received, the
// deferred Stop function above will run which will shut down TUN/TAP.
for {
select {
+ case _ = <-c:
+ goto exit
case _ = <-r:
if *useconffile != "" {
cfg = readConfig(useconf, useconffile, normaliseconf)
n.core.UpdateConfig(cfg)
+ n.tuntap.UpdateConfig(cfg)
+ n.multicast.UpdateConfig(cfg)
} else {
logger.Errorln("Reloading config at runtime is only possible with -useconffile")
}
- case _ = <-c:
- goto exit
}
}
exit:
}
+
+func (n *node) shutdown() {
+ n.core.Stop()
+ n.admin.Stop()
+ n.multicast.Stop()
+ n.tuntap.Stop()
+ os.Exit(0)
+}
+
+func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool {
+ n.state.Mutex.RLock()
+ defer n.state.Mutex.RUnlock()
+
+ // Allow by default if the session firewall is disabled
+ if !n.state.Current.SessionFirewall.Enable {
+ return true
+ }
+
+ // Prepare for checking whitelist/blacklist
+ var box crypto.BoxPubKey
+ // Reject blacklisted nodes
+ for _, b := range n.state.Current.SessionFirewall.BlacklistEncryptionPublicKeys {
+ key, err := hex.DecodeString(b)
+ if err == nil {
+ copy(box[:crypto.BoxPubKeyLen], key)
+ if box == *pubkey {
+ return false
+ }
+ }
+ }
+
+ // Allow whitelisted nodes
+ for _, b := range n.state.Current.SessionFirewall.WhitelistEncryptionPublicKeys {
+ key, err := hex.DecodeString(b)
+ if err == nil {
+ copy(box[:crypto.BoxPubKeyLen], key)
+ if box == *pubkey {
+ return true
+ }
+ }
+ }
+
+ // Allow outbound sessions if appropriate
+ if n.state.Current.SessionFirewall.AlwaysAllowOutbound {
+ if initiator {
+ return true
+ }
+ }
+
+ // Look and see if the pubkey is that of a direct peer
+ var isDirectPeer bool
+ for _, peer := range n.core.GetPeers() {
+ if peer.PublicKey == *pubkey {
+ isDirectPeer = true
+ break
+ }
+ }
+
+ // Allow direct peers if appropriate
+ if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer {
+ return true
+ }
+
+ // Allow remote nodes if appropriate
+ if n.state.Current.SessionFirewall.AllowFromRemote && !isDirectPeer {
+ return true
+ }
+
+ // Finally, default-deny if not matching any of the above rules
+ return false
+}
diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go
index b8864dc3..51f4fa51 100644
--- a/cmd/yggdrasilctl/main.go
+++ b/cmd/yggdrasilctl/main.go
@@ -200,7 +200,7 @@ func main() {
if !keysOrdered {
for k := range slv.(map[string]interface{}) {
if !*verbose {
- if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" {
+ if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" {
continue
}
}
@@ -277,6 +277,9 @@ func main() {
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)
}
diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh
index 4433a9d9..90707a7c 100644
--- a/contrib/deb/generate.sh
+++ b/contrib/deb/generate.sh
@@ -27,8 +27,9 @@ 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"
+ echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64,armel"
exit 1
fi
diff --git a/contrib/yggdrasil-brute-simple/LICENSE b/contrib/yggdrasil-brute-simple/LICENSE
new file mode 100644
index 00000000..2d61b400
--- /dev/null
+++ b/contrib/yggdrasil-brute-simple/LICENSE
@@ -0,0 +1,150 @@
+This 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
+
diff --git a/contrib/yggdrasil-brute-simple/Makefile b/contrib/yggdrasil-brute-simple/Makefile
new file mode 100644
index 00000000..aa2adc86
--- /dev/null
+++ b/contrib/yggdrasil-brute-simple/Makefile
@@ -0,0 +1,12 @@
+.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
diff --git a/contrib/yggdrasil-brute-simple/README.md b/contrib/yggdrasil-brute-simple/README.md
new file mode 100644
index 00000000..f7b68765
--- /dev/null
+++ b/contrib/yggdrasil-brute-simple/README.md
@@ -0,0 +1,8 @@
+# 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.
diff --git a/contrib/yggdrasil-brute-simple/util.c b/contrib/yggdrasil-brute-simple/util.c
new file mode 100644
index 00000000..fd17e496
--- /dev/null
+++ b/contrib/yggdrasil-brute-simple/util.c
@@ -0,0 +1,62 @@
+#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;
+ }
+}
diff --git a/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c
new file mode 100644
index 00000000..a592f38b
--- /dev/null
+++ b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-curve25519.c
@@ -0,0 +1,105 @@
+/*
+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;
+}
diff --git a/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c
new file mode 100644
index 00000000..02218e50
--- /dev/null
+++ b/contrib/yggdrasil-brute-simple/yggdrasil-brute-multi-ed25519.c
@@ -0,0 +1,106 @@
+/*
+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;
+}
diff --git a/contrib/yggdrasil-brute-simple/yggdrasil-brute.h b/contrib/yggdrasil-brute-simple/yggdrasil-brute.h
new file mode 100644
index 00000000..8e39e0f3
--- /dev/null
+++ b/contrib/yggdrasil-brute-simple/yggdrasil-brute.h
@@ -0,0 +1,12 @@
+#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);
diff --git a/doc/Whitepaper.md b/doc/Whitepaper.md
index 26d49a53..d7f13279 100644
--- a/doc/Whitepaper.md
+++ b/doc/Whitepaper.md
@@ -1,4 +1,4 @@
-# Yggdasil
+# Yggdrasil
Note: This is a very rough early draft.
diff --git a/go.mod b/go.mod
index 995e54c2..84025df3 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,15 @@ module github.com/yggdrasil-network/yggdrasil-go
require (
github.com/docker/libcontainer v2.2.1+incompatible
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8
+ github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0
github.com/mitchellh/mapstructure v1.1.2
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
- github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae
- golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
- golang.org/x/net v0.0.0-20181207154023-610586996380
- golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e
- golang.org/x/text v0.3.0
+ github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34
+ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
+ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
+ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3
+ golang.org/x/text v0.3.2
+ golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060 // indirect
)
diff --git a/go.sum b/go.sum
index 92dfe88c..81d337e1 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY=
github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/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 v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU=
github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g=
@@ -12,11 +14,42 @@ github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8h
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY=
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae h1:MYCANF1kehCG6x6G+/9txLfq6n3lS5Vp0Mxn1hdiBAc=
github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
+github.com/yggdrasil-network/water v0.0.0-20190719211521-a76871ea954b/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
+github.com/yggdrasil-network/water v0.0.0-20190719213007-b160316e362e/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
+github.com/yggdrasil-network/water v0.0.0-20190720101301-5db94379a5eb/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
+github.com/yggdrasil-network/water v0.0.0-20190720145626-28ccb9101d55/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
+github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a h1:mQ0mPD+dyB/vaDPyVkCBiXUQu9Or7/cRSTjPlV8tXvw=
+github.com/yggdrasil-network/water v0.0.0-20190725073841-250edb919f8a/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
+github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34 h1:Qh5FE+Q5iGqpmR/FPMYHuoZLN921au/nxAlmKe+Hdbo=
+github.com/yggdrasil-network/water v0.0.0-20190725123504-a16161896c34/go.mod h1:R0SBCsugm+Sf1katgTb2t7GXMm+nRIv43tM4VDZbaOs=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0=
golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo=
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/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-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
+golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
+golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20190802003818-e9bb7d36c060/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
diff --git a/src/admin/admin.go b/src/admin/admin.go
new file mode 100644
index 00000000..2b73764c
--- /dev/null
+++ b/src/admin/admin.go
@@ -0,0 +1,604 @@
+package admin
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net"
+ "net/url"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "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/crypto"
+ "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
+)
+
+// TODO: Add authentication
+
+type AdminSocket struct {
+ core *yggdrasil.Core
+ log *log.Logger
+ reconfigure chan chan error
+ listenaddr string
+ listener net.Listener
+ handlers map[string]handler
+}
+
+// Info refers to information that is returned to the admin socket handler.
+type Info map[string]interface{}
+
+type handler struct {
+ args []string // List of human-readable argument names
+ handler func(Info) (Info, error) // First is input map, second is output
+}
+
+// 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 func(Info) (Info, error)) 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 *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) {
+ a.core = c
+ a.log = log
+ a.reconfigure = make(chan chan error, 1)
+ a.handlers = make(map[string]handler)
+ go func() {
+ for {
+ e := <-a.reconfigure
+ current, previous := state.GetCurrent(), state.GetPrevious()
+ if current.AdminListen != previous.AdminListen {
+ a.listenaddr = current.AdminListen
+ a.Stop()
+ a.Start()
+ }
+ e <- nil
+ }
+ }()
+ current := state.GetCurrent()
+ a.listenaddr = current.AdminListen
+ a.AddHandler("list", []string{}, func(in Info) (Info, error) {
+ handlers := make(map[string]interface{})
+ for handlername, handler := range a.handlers {
+ handlers[handlername] = Info{"fields": handler.args}
+ }
+ return Info{"list": handlers}, nil
+ })
+ /*
+ a.AddHandler("dot", []string{}, func(in Info) (Info, error) {
+ return Info{"dot": string(a.getResponse_dot())}, nil
+ })
+ */
+ a.AddHandler("getSelf", []string{}, func(in Info) (Info, error) {
+ ip := c.Address().String()
+ subnet := c.Subnet()
+ return Info{
+ "self": Info{
+ ip: Info{
+ "box_pub_key": c.EncryptionPublicKey(),
+ "build_name": yggdrasil.BuildName(),
+ "build_version": yggdrasil.BuildVersion(),
+ "coords": fmt.Sprintf("%v", c.Coords()),
+ "subnet": subnet.String(),
+ },
+ },
+ }, nil
+ })
+ a.AddHandler("getPeers", []string{}, func(in Info) (Info, error) {
+ peers := make(Info)
+ for _, p := range a.core.GetPeers() {
+ addr := *address.AddrForNodeID(crypto.GetNodeID(&p.PublicKey))
+ so := net.IP(addr[:]).String()
+ peers[so] = Info{
+ "port": p.Port,
+ "uptime": p.Uptime.Seconds(),
+ "bytes_sent": p.BytesSent,
+ "bytes_recvd": p.BytesRecvd,
+ "proto": p.Protocol,
+ "endpoint": p.Endpoint,
+ "box_pub_key": p.PublicKey,
+ }
+ }
+ return Info{"peers": peers}, nil
+ })
+ a.AddHandler("getSwitchPeers", []string{}, func(in Info) (Info, error) {
+ switchpeers := make(Info)
+ for _, s := range a.core.GetSwitchPeers() {
+ addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
+ so := fmt.Sprint(s.Port)
+ switchpeers[so] = Info{
+ "ip": net.IP(addr[:]).String(),
+ "coords": fmt.Sprintf("%v", s.Coords),
+ "port": s.Port,
+ "bytes_sent": s.BytesSent,
+ "bytes_recvd": s.BytesRecvd,
+ "proto": s.Protocol,
+ "endpoint": s.Endpoint,
+ "box_pub_key": s.PublicKey,
+ }
+ }
+ return Info{"switchpeers": switchpeers}, nil
+ })
+ /*
+ a.AddHandler("getSwitchQueues", []string{}, func(in Info) (Info, error) {
+ queues := a.core.GetSwitchQueues()
+ return Info{"switchqueues": queues.asMap()}, nil
+ })
+ */
+ a.AddHandler("getDHT", []string{}, func(in Info) (Info, error) {
+ dht := make(Info)
+ for _, d := range a.core.GetDHT() {
+ addr := *address.AddrForNodeID(crypto.GetNodeID(&d.PublicKey))
+ so := net.IP(addr[:]).String()
+ dht[so] = Info{
+ "coords": fmt.Sprintf("%v", d.Coords),
+ "last_seen": d.LastSeen.Seconds(),
+ "box_pub_key": d.PublicKey,
+ }
+ }
+ return Info{"dht": dht}, nil
+ })
+ a.AddHandler("getSessions", []string{}, func(in Info) (Info, error) {
+ sessions := make(Info)
+ for _, s := range a.core.GetSessions() {
+ addr := *address.AddrForNodeID(crypto.GetNodeID(&s.PublicKey))
+ so := net.IP(addr[:]).String()
+ sessions[so] = Info{
+ "coords": fmt.Sprintf("%v", s.Coords),
+ "bytes_sent": s.BytesSent,
+ "bytes_recvd": s.BytesRecvd,
+ "mtu": s.MTU,
+ "uptime": s.Uptime.Seconds(),
+ "was_mtu_fixed": s.WasMTUFixed,
+ "box_pub_key": s.PublicKey,
+ }
+ }
+ return Info{"sessions": sessions}, nil
+ })
+ a.AddHandler("addPeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) {
+ // Set sane defaults
+ intf := ""
+ // Has interface been specified?
+ if itf, ok := in["interface"]; ok {
+ intf = itf.(string)
+ }
+ if a.core.AddPeer(in["uri"].(string), intf) == nil {
+ return Info{
+ "added": []string{
+ in["uri"].(string),
+ },
+ }, nil
+ } else {
+ return Info{
+ "not_added": []string{
+ in["uri"].(string),
+ },
+ }, errors.New("Failed to add peer")
+ }
+ })
+ a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) {
+ port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64)
+ if err != nil {
+ return Info{}, err
+ }
+ if a.core.DisconnectPeer(uint64(port)) == nil {
+ return Info{
+ "removed": []string{
+ fmt.Sprint(port),
+ },
+ }, nil
+ } else {
+ return Info{
+ "not_removed": []string{
+ fmt.Sprint(port),
+ },
+ }, errors.New("Failed to remove peer")
+ }
+ })
+ a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) {
+ return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil
+ })
+ a.AddHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
+ if a.core.AddAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
+ return Info{
+ "added": []string{
+ in["box_pub_key"].(string),
+ },
+ }, nil
+ } else {
+ return Info{
+ "not_added": []string{
+ in["box_pub_key"].(string),
+ },
+ }, errors.New("Failed to add allowed key")
+ }
+ })
+ a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) {
+ if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
+ return Info{
+ "removed": []string{
+ in["box_pub_key"].(string),
+ },
+ }, nil
+ } else {
+ return Info{
+ "not_removed": []string{
+ in["box_pub_key"].(string),
+ },
+ }, errors.New("Failed to remove allowed key")
+ }
+ })
+ a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) {
+ if in["target"] == nil {
+ in["target"] = "none"
+ }
+ result, err := a.core.DHTPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string))
+ if err == nil {
+ infos := make(map[string]map[string]string, len(result.Infos))
+ for _, dinfo := range result.Infos {
+ info := map[string]string{
+ "box_pub_key": hex.EncodeToString(dinfo.PublicKey[:]),
+ "coords": fmt.Sprintf("%v", dinfo.Coords),
+ }
+ addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.PublicKey))[:]).String()
+ infos[addr] = info
+ }
+ return Info{"nodes": infos}, nil
+ } else {
+ return Info{}, err
+ }
+ })
+ a.AddHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in Info) (Info, error) {
+ var nocache bool
+ if in["nocache"] != nil {
+ nocache = in["nocache"].(string) == "true"
+ }
+ var box_pub_key, coords string
+ if in["box_pub_key"] == nil && in["coords"] == nil {
+ nodeinfo := a.core.MyNodeInfo()
+ var jsoninfo interface{}
+ if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil {
+ return Info{}, err
+ } else {
+ return Info{"nodeinfo": jsoninfo}, nil
+ }
+ } else if in["box_pub_key"] == nil || in["coords"] == nil {
+ return Info{}, errors.New("Expecting both box_pub_key and coords")
+ } else {
+ box_pub_key = in["box_pub_key"].(string)
+ coords = in["coords"].(string)
+ }
+ result, err := a.core.GetNodeInfo(box_pub_key, coords, nocache)
+ if err == nil {
+ var m map[string]interface{}
+ if err = json.Unmarshal(result, &m); err == nil {
+ return Info{"nodeinfo": m}, nil
+ } else {
+ return Info{}, err
+ }
+ } else {
+ return Info{}, err
+ }
+ })
+}
+
+// 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 != "" {
+ go a.listen()
+ }
+ return nil
+}
+
+// cleans up when stopping
+func (a *AdminSocket) Stop() error {
+ if a.listener != nil {
+ return a.listener.Close()
+ } else {
+ 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)
+ }
+ }
+}
+
+// handleRequest calls the request handler for each request sent to the admin API.
+func (a *AdminSocket) handleRequest(conn net.Conn) {
+ decoder := json.NewDecoder(conn)
+ encoder := json.NewEncoder(conn)
+ encoder.SetIndent("", " ")
+ recv := make(Info)
+ send := make(Info)
+
+ defer func() {
+ r := recover()
+ if r != nil {
+ send = Info{
+ "status": "error",
+ "error": "Check your syntax and input types",
+ }
+ a.log.Debugln("Admin socket error:", r)
+ if err := encoder.Encode(&send); err != nil {
+ a.log.Debugln("Admin socket JSON encode error:", err)
+ }
+ conn.Close()
+ }
+ }()
+
+ for {
+ // Start with a clean slate on each request
+ recv = Info{}
+ send = Info{}
+
+ // Decode the input
+ if err := decoder.Decode(&recv); err != nil {
+ a.log.Debugln("Admin socket JSON decode error:", err)
+ return
+ }
+
+ // Send the request back with the response, and default to "error"
+ // unless the status is changed below by one of the handlers
+ send["request"] = recv
+ send["status"] = "error"
+
+ n := strings.ToLower(recv["request"].(string))
+
+ if _, ok := recv["request"]; !ok {
+ send["error"] = "No request sent"
+ goto respond
+ }
+
+ if h, ok := a.handlers[n]; ok {
+ // Check that we have all the required arguments
+ for _, arg := range h.args {
+ // An argument in [square brackets] is optional and not required,
+ // so we can safely ignore those
+ if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
+ continue
+ }
+ // Check if the field is missing
+ if _, ok := recv[arg]; !ok {
+ send = Info{
+ "status": "error",
+ "error": "Expected field missing: " + arg,
+ "expecting": arg,
+ }
+ goto respond
+ }
+ }
+
+ // By this point we should have all the fields we need, so call
+ // the handler
+ response, err := h.handler(recv)
+ if err != nil {
+ send["error"] = err.Error()
+ if response != nil {
+ send["response"] = response
+ goto respond
+ }
+ } else {
+ send["status"] = "success"
+ if response != nil {
+ send["response"] = response
+ goto respond
+ }
+ }
+ } else {
+ // Start with a clean response on each request, which defaults to an error
+ // state. If a handler is found below then this will be overwritten
+ send = Info{
+ "request": recv,
+ "status": "error",
+ "error": fmt.Sprintf("Unknown action '%s', try 'list' for help", recv["request"].(string)),
+ }
+ goto respond
+ }
+
+ // Send the response back
+ respond:
+ if err := encoder.Encode(&send); err != nil {
+ return
+ }
+
+ // If "keepalive" isn't true then close the connection
+ if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) {
+ conn.Close()
+ }
+ }
+}
+
+// getResponse_dot returns a response for a graphviz dot formatted
+// representation of the known parts of the network. This is color-coded and
+// labeled, and includes the self node, switch peers, nodes known to the DHT,
+// and nodes with open sessions. The graph is structured as a tree with directed
+// links leading away from the root.
+/*
+func (a *AdminSocket) getResponse_dot() []byte {
+ //self := a.getData_getSelf()
+ peers := a.core.GetSwitchPeers()
+ dht := a.core.GetDHT()
+ sessions := a.core.GetSessions()
+ // Start building a tree from all known nodes
+ type nodeInfo struct {
+ name string
+ key string
+ parent string
+ port uint64
+ options string
+ }
+ infos := make(map[string]nodeInfo)
+ // Get coords as a slice of strings, FIXME? this looks very fragile
+ coordSlice := func(coords string) []string {
+ tmp := strings.Replace(coords, "[", "", -1)
+ tmp = strings.Replace(tmp, "]", "", -1)
+ return strings.Split(tmp, " ")
+ }
+ // First fill the tree with all known nodes, no parents
+ addInfo := func(nodes []admin_nodeInfo, options string, tag string) {
+ for _, node := range nodes {
+ n := node.asMap()
+ info := nodeInfo{
+ key: n["coords"].(string),
+ options: options,
+ }
+ if len(tag) > 0 {
+ info.name = fmt.Sprintf("%s\n%s", n["ip"].(string), tag)
+ } else {
+ info.name = n["ip"].(string)
+ }
+ coordsSplit := coordSlice(info.key)
+ if len(coordsSplit) != 0 {
+ portStr := coordsSplit[len(coordsSplit)-1]
+ portUint, err := strconv.ParseUint(portStr, 10, 64)
+ if err == nil {
+ info.port = portUint
+ }
+ }
+ infos[info.key] = info
+ }
+ }
+ addInfo(dht, "fillcolor=\"#ffffff\" style=filled fontname=\"sans serif\"", "Known in DHT") // white
+ addInfo(sessions, "fillcolor=\"#acf3fd\" style=filled fontname=\"sans serif\"", "Open session") // blue
+ addInfo(peers, "fillcolor=\"#ffffb5\" style=filled fontname=\"sans serif\"", "Connected peer") // yellow
+ addInfo(append([]admin_nodeInfo(nil), *self), "fillcolor=\"#a5ff8a\" style=filled fontname=\"sans serif\"", "This node") // green
+ // Now go through and create placeholders for any missing nodes
+ for _, info := range infos {
+ // This is ugly string manipulation
+ coordsSplit := coordSlice(info.key)
+ for idx := range coordsSplit {
+ key := fmt.Sprintf("[%v]", strings.Join(coordsSplit[:idx], " "))
+ newInfo, isIn := infos[key]
+ if isIn {
+ continue
+ }
+ newInfo.name = "?"
+ newInfo.key = key
+ newInfo.options = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
+
+ coordsSplit := coordSlice(newInfo.key)
+ if len(coordsSplit) != 0 {
+ portStr := coordsSplit[len(coordsSplit)-1]
+ portUint, err := strconv.ParseUint(portStr, 10, 64)
+ if err == nil {
+ newInfo.port = portUint
+ }
+ }
+
+ infos[key] = newInfo
+ }
+ }
+ // Now go through and attach parents
+ for _, info := range infos {
+ pSplit := coordSlice(info.key)
+ if len(pSplit) > 0 {
+ pSplit = pSplit[:len(pSplit)-1]
+ }
+ info.parent = fmt.Sprintf("[%v]", strings.Join(pSplit, " "))
+ infos[info.key] = info
+ }
+ // Finally, get a sorted list of keys, which we use to organize the output
+ var keys []string
+ for _, info := range infos {
+ keys = append(keys, info.key)
+ }
+ // sort
+ sort.SliceStable(keys, func(i, j int) bool {
+ return keys[i] < keys[j]
+ })
+ sort.SliceStable(keys, func(i, j int) bool {
+ return infos[keys[i]].port < infos[keys[j]].port
+ })
+ // Now print it all out
+ var out []byte
+ put := func(s string) {
+ out = append(out, []byte(s)...)
+ }
+ put("digraph {\n")
+ // First set the labels
+ for _, key := range keys {
+ info := infos[key]
+ put(fmt.Sprintf("\"%v\" [ label = \"%v\" %v ];\n", info.key, info.name, info.options))
+ }
+ // Then print the tree structure
+ for _, key := range keys {
+ info := infos[key]
+ if info.key == info.parent {
+ continue
+ } // happens for the root, skip it
+ port := fmt.Sprint(info.port)
+ style := "fontname=\"sans serif\""
+ if infos[info.parent].name == "?" || infos[info.key].name == "?" {
+ style = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
+ }
+ put(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" %s ];\n", info.parent, info.key, port, style))
+ }
+ put("}\n")
+ return out
+}
+*/
diff --git a/src/config/config.go b/src/config/config.go
index a9007585..a7bbbacf 100644
--- a/src/config/config.go
+++ b/src/config/config.go
@@ -2,11 +2,43 @@ package config
import (
"encoding/hex"
+ "sync"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
)
+// NodeState represents the active and previous configuration of the node and
+// protects it with a mutex
+type NodeState struct {
+ Current NodeConfig
+ Previous NodeConfig
+ Mutex sync.RWMutex
+}
+
+// Current returns the current node config
+func (s *NodeState) GetCurrent() NodeConfig {
+ s.Mutex.RLock()
+ defer s.Mutex.RUnlock()
+ return s.Current
+}
+
+// Previous returns the previous node config
+func (s *NodeState) GetPrevious() NodeConfig {
+ s.Mutex.RLock()
+ defer s.Mutex.RUnlock()
+ return s.Previous
+}
+
+// Replace the node configuration with new configuration. This method returns
+// both the new and the previous node configs
+func (s *NodeState) Replace(n NodeConfig) {
+ s.Mutex.Lock()
+ defer s.Mutex.Unlock()
+ s.Previous = s.Current
+ s.Current = n
+}
+
// NodeConfig defines all configuration values needed to run a signle yggdrasil node
type NodeConfig struct {
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://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."`
@@ -76,7 +108,7 @@ func GenerateConfig() *NodeConfig {
cfg.Peers = []string{}
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedEncryptionPublicKeys = []string{}
- cfg.MulticastInterfaces = []string{".*"}
+ cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces
cfg.IfName = defaults.GetDefaults().DefaultIfName
cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
@@ -89,3 +121,19 @@ func GenerateConfig() *NodeConfig {
return &cfg
}
+
+// NewEncryptionKeys generates a new encryption keypair. The encryption keys are
+// used to encrypt traffic and to derive the IPv6 address/subnet of the node.
+func (cfg *NodeConfig) NewEncryptionKeys() {
+ bpub, bpriv := crypto.NewBoxKeys()
+ cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
+ cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
+}
+
+// NewSigningKeys generates a new signing keypair. The signing keys are used to
+// derive the structure of the spanning tree.
+func (cfg *NodeConfig) NewSigningKeys() {
+ spub, spriv := crypto.NewSigKeys()
+ cfg.SigningPublicKey = hex.EncodeToString(spub[:])
+ cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
+}
diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go
index c974a3c0..75736ba7 100644
--- a/src/crypto/crypto.go
+++ b/src/crypto/crypto.go
@@ -13,7 +13,9 @@ It also defines NodeID and TreeID as hashes of keys, and wraps hash functions
import (
"crypto/rand"
"crypto/sha512"
+ "encoding/hex"
+ "golang.org/x/crypto/curve25519"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
@@ -32,6 +34,41 @@ type NodeID [NodeIDLen]byte
type TreeID [TreeIDLen]byte
type Handle [handleLen]byte
+func (n *NodeID) String() string {
+ return hex.EncodeToString(n[:])
+}
+
+// Network returns "nodeid" nearly always right now.
+func (n *NodeID) Network() string {
+ return "nodeid"
+}
+
+// PrefixLength returns the number of bits set in a masked NodeID.
+func (n *NodeID) PrefixLength() int {
+ var len int
+ for i, v := range *n {
+ _, _ = i, v
+ if v == 0xff {
+ len += 8
+ continue
+ }
+ for v&0x80 != 0 {
+ len++
+ v <<= 1
+ }
+ if v != 0 {
+ return -1
+ }
+ for i++; i < NodeIDLen; i++ {
+ if n[i] != 0 {
+ return -1
+ }
+ }
+ break
+ }
+ return len
+}
+
func GetNodeID(pub *BoxPubKey) *NodeID {
h := sha512.Sum512(pub[:])
return (*NodeID)(&h)
@@ -88,6 +125,15 @@ func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool {
return ed25519.Verify(pub[:], msg, sig[:])
}
+func (p SigPrivKey) Public() SigPubKey {
+ priv := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
+ copy(priv[:], p[:])
+ pub := priv.Public().(ed25519.PublicKey)
+ var sigPub SigPubKey
+ copy(sigPub[:], pub[:])
+ return sigPub
+}
+
////////////////////////////////////////////////////////////////////////////////
// NaCl-like crypto "box" (curve25519+xsalsa20+poly1305)
@@ -168,6 +214,14 @@ func (n *BoxNonce) Increment() {
}
}
+func (p BoxPrivKey) Public() BoxPubKey {
+ var boxPub [BoxPubKeyLen]byte
+ var boxPriv [BoxPrivKeyLen]byte
+ copy(boxPriv[:BoxPrivKeyLen], p[:BoxPrivKeyLen])
+ curve25519.ScalarBaseMult(&boxPub, &boxPriv)
+ return boxPub
+}
+
// Used to subtract one nonce from another, staying in the range +- 64.
// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask.
// It's basically part of the machinery that prevents replays and duplicate packets.
diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go
index 3834990e..a5784198 100644
--- a/src/defaults/defaults.go
+++ b/src/defaults/defaults.go
@@ -10,6 +10,9 @@ type platformDefaultParameters struct {
// Configuration (used for yggdrasilctl)
DefaultConfigFile string
+ // Multicast interfaces
+ DefaultMulticastInterfaces []string
+
// TUN/TAP
MaximumIfMTU int
DefaultIfMTU int
diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go
index 9bac3aad..47683bf9 100644
--- a/src/defaults/defaults_darwin.go
+++ b/src/defaults/defaults_darwin.go
@@ -12,6 +12,12 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
+ // Multicast interfaces
+ DefaultMulticastInterfaces: []string{
+ "en.*",
+ "bridge.*",
+ },
+
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go
index df1a3c65..0e523483 100644
--- a/src/defaults/defaults_freebsd.go
+++ b/src/defaults/defaults_freebsd.go
@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
+ // Multicast interfaces
+ DefaultMulticastInterfaces: []string{
+ ".*",
+ },
+
// TUN/TAP
MaximumIfMTU: 32767,
DefaultIfMTU: 32767,
diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go
index 2f3459ca..b0aaf855 100644
--- a/src/defaults/defaults_linux.go
+++ b/src/defaults/defaults_linux.go
@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
+ // Multicast interfaces
+ DefaultMulticastInterfaces: []string{
+ ".*",
+ },
+
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
diff --git a/src/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go
index 40476dcb..52a487b7 100644
--- a/src/defaults/defaults_netbsd.go
+++ b/src/defaults/defaults_netbsd.go
@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
+ // Multicast interfaces
+ DefaultMulticastInterfaces: []string{
+ ".*",
+ },
+
// TUN/TAP
MaximumIfMTU: 9000,
DefaultIfMTU: 9000,
diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go
index cd6d202a..d44e5714 100644
--- a/src/defaults/defaults_openbsd.go
+++ b/src/defaults/defaults_openbsd.go
@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
+ // Multicast interfaces
+ DefaultMulticastInterfaces: []string{
+ ".*",
+ },
+
// TUN/TAP
MaximumIfMTU: 16384,
DefaultIfMTU: 16384,
diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go
index a01ab7af..0ba825c5 100644
--- a/src/defaults/defaults_other.go
+++ b/src/defaults/defaults_other.go
@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
+ // Multicast interfaces
+ DefaultMulticastInterfaces: []string{
+ ".*",
+ },
+
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go
index 3b04783a..6d53225a 100644
--- a/src/defaults/defaults_windows.go
+++ b/src/defaults/defaults_windows.go
@@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters {
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf",
+ // Multicast interfaces
+ DefaultMulticastInterfaces: []string{
+ ".*",
+ },
+
// TUN/TAP
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
diff --git a/src/multicast/admin.go b/src/multicast/admin.go
new file mode 100644
index 00000000..cafee07f
--- /dev/null
+++ b/src/multicast/admin.go
@@ -0,0 +1,13 @@
+package multicast
+
+import "github.com/yggdrasil-network/yggdrasil-go/src/admin"
+
+func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) {
+ a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) {
+ var intfs []string
+ for _, v := range m.Interfaces() {
+ intfs = append(intfs, v.Name)
+ }
+ return admin.Info{"multicast_interfaces": intfs}, nil
+ })
+}
diff --git a/src/yggdrasil/multicast.go b/src/multicast/multicast.go
similarity index 59%
rename from src/yggdrasil/multicast.go
rename to src/multicast/multicast.go
index ca3a1f75..5ab9673e 100644
--- a/src/yggdrasil/multicast.go
+++ b/src/multicast/multicast.go
@@ -1,4 +1,4 @@
-package yggdrasil
+package multicast
import (
"context"
@@ -7,74 +7,94 @@ import (
"regexp"
"time"
+ "github.com/gologme/log"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/config"
+ "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
"golang.org/x/net/ipv6"
)
-type multicast struct {
- core *Core
- reconfigure chan chan error
- sock *ipv6.PacketConn
- groupAddr string
- listeners map[string]*tcpListener
- listenPort uint16
+// 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 {
+ core *yggdrasil.Core
+ config *config.NodeState
+ log *log.Logger
+ sock *ipv6.PacketConn
+ groupAddr string
+ listeners map[string]*yggdrasil.TcpListener
+ listenPort uint16
+ isOpen bool
}
-func (m *multicast) init(core *Core) {
+// Init prepares the multicast interface for use.
+func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error {
m.core = core
- m.reconfigure = make(chan chan error, 1)
- m.listeners = make(map[string]*tcpListener)
- m.core.configMutex.RLock()
- m.listenPort = m.core.config.LinkLocalTCPPort
- m.core.configMutex.RUnlock()
- go func() {
- for {
- e := <-m.reconfigure
- e <- nil
- }
- }()
+ m.config = state
+ m.log = log
+ m.listeners = make(map[string]*yggdrasil.TcpListener)
+ current := m.config.GetCurrent()
+ m.listenPort = current.LinkLocalTCPPort
m.groupAddr = "[ff02::114]:9001"
- // Check if we've been given any expressions
- if count := len(m.interfaces()); count != 0 {
- m.core.log.Infoln("Found", count, "multicast interface(s)")
- }
-}
-
-func (m *multicast) start() error {
- if len(m.interfaces()) == 0 {
- m.core.log.Infoln("Multicast discovery is disabled")
- } else {
- m.core.log.Infoln("Multicast discovery is enabled")
- 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 {
- // Windows can't set this flag, so we need to handle it in other ways
- }
-
- go m.multicastStarted()
- go m.listen()
- go m.announce()
- }
return nil
}
-func (m *multicast) interfaces() map[string]net.Interface {
- // Get interface expressions from config
- m.core.configMutex.RLock()
- exprs := m.core.config.MulticastInterfaces
- m.core.configMutex.RUnlock()
- // Ask the system for network interfaces
+// 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 {
+ 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 {
+ // Windows can't set this flag, so we need to handle it in other ways
+ }
+
+ m.isOpen = true
+ go m.multicastStarted()
+ go m.listen()
+ go m.announce()
+
+ return nil
+}
+
+// Stop is not implemented for multicast yet.
+func (m *Multicast) Stop() error {
+ m.isOpen = false
+ m.sock.Close()
+ return nil
+}
+
+// UpdateConfig updates the multicast module with the provided config.NodeConfig
+// and then signals the various module goroutines to reconfigure themselves if
+// needed.
+func (m *Multicast) UpdateConfig(config *config.NodeConfig) {
+ m.log.Debugln("Reloading multicast configuration...")
+ m.config.Replace(*config)
+ m.log.Infoln("Multicast configuration reloaded successfully")
+}
+
+// GetInterfaces returns the currently known/enabled multicast interfaces. It is
+// expected that UpdateInterfaces has been called at least once before calling
+// this method.
+func (m *Multicast) Interfaces() map[string]net.Interface {
interfaces := make(map[string]net.Interface)
+ // Get interface expressions from config
+ current := m.config.GetCurrent()
+ exprs := current.MulticastInterfaces
+ // Ask the system for network interfaces
allifaces, err := net.Interfaces()
if err != nil {
panic(err)
@@ -108,7 +128,7 @@ func (m *multicast) interfaces() map[string]net.Interface {
return interfaces
}
-func (m *multicast) announce() {
+func (m *Multicast) announce() {
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
@@ -118,15 +138,15 @@ func (m *multicast) announce() {
panic(err)
}
for {
- interfaces := m.interfaces()
+ interfaces := m.Interfaces()
// 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, listener := range m.listeners {
// Prepare our stop function!
stop := func() {
- listener.stop <- true
+ listener.Stop <- true
delete(m.listeners, name)
- m.core.log.Debugln("No longer multicasting on", 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
@@ -137,7 +157,7 @@ func (m *multicast) announce() {
// 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", listener.listener.Addr().String())
+ listenaddr, err := net.ResolveTCPAddr("tcp6", listener.Listener.Addr().String())
if err != nil {
stop()
continue
@@ -186,17 +206,17 @@ func (m *multicast) announce() {
// Join the multicast group
m.sock.JoinGroup(&iface, groupAddr)
// Try and see if we already have a TCP listener for this interface
- var listener *tcpListener
- if l, ok := m.listeners[iface.Name]; !ok || l.listener == nil {
+ var listener *yggdrasil.TcpListener
+ if l, ok := m.listeners[iface.Name]; !ok || l.Listener == nil {
// No listener was found - let's create one
listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort)
- if li, err := m.core.link.tcp.listen(listenaddr); err == nil {
- m.core.log.Debugln("Started multicasting on", iface.Name)
+ if li, err := m.core.ListenTCP(listenaddr); err == nil {
+ m.log.Debugln("Started multicasting on", iface.Name)
// Store the listener so that we can stop it later if needed
m.listeners[iface.Name] = li
listener = li
} else {
- m.core.log.Warnln("Not multicasting on", iface.Name, "due to error:", err)
+ m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err)
}
} else {
// An existing listener was found
@@ -207,7 +227,7 @@ func (m *multicast) announce() {
continue
}
// Get the listener details and construct the multicast beacon
- lladdr := listener.listener.Addr().String()
+ lladdr := listener.Listener.Addr().String()
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
a.Zone = ""
destAddr.Zone = iface.Name
@@ -221,7 +241,7 @@ func (m *multicast) announce() {
}
}
-func (m *multicast) listen() {
+func (m *Multicast) listen() {
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
@@ -230,6 +250,9 @@ func (m *multicast) listen() {
for {
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil {
+ if !m.isOpen {
+ return
+ }
panic(err)
}
if rcm != nil {
@@ -252,9 +275,11 @@ func (m *multicast) listen() {
if addr.IP.String() != from.IP.String() {
continue
}
- addr.Zone = ""
- if err := m.core.link.call("tcp://"+addr.String(), from.Zone); err != nil {
- m.core.log.Debugln("Call from multicast failed:", err)
+ if _, ok := m.Interfaces()[from.Zone]; ok {
+ addr.Zone = ""
+ if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil {
+ m.log.Debugln("Call from multicast failed:", err)
+ }
}
}
}
diff --git a/src/yggdrasil/multicast_darwin.go b/src/multicast/multicast_darwin.go
similarity index 82%
rename from src/yggdrasil/multicast_darwin.go
rename to src/multicast/multicast_darwin.go
index 53646113..c88b4a81 100644
--- a/src/yggdrasil/multicast_darwin.go
+++ b/src/multicast/multicast_darwin.go
@@ -1,6 +1,6 @@
// +build darwin
-package yggdrasil
+package multicast
/*
#cgo CFLAGS: -x objective-c
@@ -31,16 +31,16 @@ import (
var awdlGoroutineStarted bool
-func (m *multicast) multicastStarted() {
+func (m *Multicast) multicastStarted() {
if awdlGoroutineStarted {
return
}
- m.core.log.Infoln("Multicast discovery will wake up AWDL if required")
awdlGoroutineStarted = true
for {
C.StopAWDLBrowsing()
- for _, intf := range m.interfaces() {
- if intf.Name == "awdl0" {
+ for intf := range m.Interfaces() {
+ if intf == "awdl0" {
+ m.log.Infoln("Multicast discovery is using AWDL discovery")
C.StartAWDLBrowsing()
break
}
@@ -49,7 +49,7 @@ func (m *multicast) multicastStarted() {
}
}
-func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
+func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseport error
var recvanyif error
diff --git a/src/yggdrasil/multicast_other.go b/src/multicast/multicast_other.go
similarity index 53%
rename from src/yggdrasil/multicast_other.go
rename to src/multicast/multicast_other.go
index e20bbda3..16ea15d6 100644
--- a/src/yggdrasil/multicast_other.go
+++ b/src/multicast/multicast_other.go
@@ -1,13 +1,13 @@
// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows
-package yggdrasil
+package multicast
import "syscall"
-func (m *multicast) multicastStarted() {
+func (m *Multicast) multicastStarted() {
}
-func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
+func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
return nil
}
diff --git a/src/yggdrasil/multicast_unix.go b/src/multicast/multicast_unix.go
similarity index 75%
rename from src/yggdrasil/multicast_unix.go
rename to src/multicast/multicast_unix.go
index 3da1ab4e..9d6a01a8 100644
--- a/src/yggdrasil/multicast_unix.go
+++ b/src/multicast/multicast_unix.go
@@ -1,15 +1,15 @@
// +build linux netbsd freebsd openbsd dragonflybsd
-package yggdrasil
+package multicast
import "syscall"
import "golang.org/x/sys/unix"
-func (m *multicast) multicastStarted() {
+func (m *Multicast) multicastStarted() {
}
-func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
+func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseport error
diff --git a/src/yggdrasil/multicast_windows.go b/src/multicast/multicast_windows.go
similarity index 75%
rename from src/yggdrasil/multicast_windows.go
rename to src/multicast/multicast_windows.go
index 3e07f6cc..7a846b1e 100644
--- a/src/yggdrasil/multicast_windows.go
+++ b/src/multicast/multicast_windows.go
@@ -1,15 +1,15 @@
// +build windows
-package yggdrasil
+package multicast
import "syscall"
import "golang.org/x/sys/windows"
-func (m *multicast) multicastStarted() {
+func (m *Multicast) multicastStarted() {
}
-func (m *multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
+func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
var control error
var reuseaddr error
diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go
new file mode 100644
index 00000000..21c7048d
--- /dev/null
+++ b/src/tuntap/admin.go
@@ -0,0 +1,119 @@
+package tuntap
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "net"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/admin"
+)
+
+func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
+ a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) {
+ defer func() {
+ if err := recover(); err != nil {
+ r = admin.Info{"none": admin.Info{}}
+ e = nil
+ }
+ }()
+
+ return admin.Info{
+ t.iface.Name(): admin.Info{
+ "tap_mode": t.iface.IsTAP(),
+ "mtu": t.mtu,
+ },
+ }, nil
+ })
+ /*
+ // TODO: rewrite this as I'm fairly sure it doesn't work right on many
+ // platforms anyway, but it may require changes to Water
+ a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) {
+ // Set sane defaults
+ iftapmode := defaults.GetDefaults().DefaultIfTAPMode
+ ifmtu := defaults.GetDefaults().DefaultIfMTU
+ // Has TAP mode been specified?
+ if tap, ok := in["tap_mode"]; ok {
+ iftapmode = tap.(bool)
+ }
+ // Check we have enough params for MTU
+ if mtu, ok := in["mtu"]; ok {
+ if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU {
+ ifmtu = int(in["mtu"].(float64))
+ }
+ }
+ // Start the TUN adapter
+ if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil {
+ return Info{}, errors.New("Failed to configure adapter")
+ } else {
+ return Info{
+ a.core.router.tun.iface.Name(): Info{
+ "tap_mode": a.core.router.tun.iface.IsTAP(),
+ "mtu": ifmtu,
+ },
+ }, nil
+ }
+ })
+ */
+ a.AddHandler("getTunnelRouting", []string{}, func(in admin.Info) (admin.Info, error) {
+ return admin.Info{"enabled": t.ckr.isEnabled()}, nil
+ })
+ a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in admin.Info) (admin.Info, error) {
+ enabled := false
+ if e, ok := in["enabled"].(bool); ok {
+ enabled = e
+ }
+ t.ckr.setEnabled(enabled)
+ return admin.Info{"enabled": enabled}, nil
+ })
+ a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) {
+ if err := t.ckr.addSourceSubnet(in["subnet"].(string)); err == nil {
+ return admin.Info{"added": []string{in["subnet"].(string)}}, nil
+ } else {
+ return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet")
+ }
+ })
+ a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) {
+ if err := t.ckr.addRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil {
+ return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
+ } else {
+ return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route")
+ }
+ })
+ a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) {
+ var subnets []string
+ getSourceSubnets := func(snets []net.IPNet) {
+ for _, subnet := range snets {
+ subnets = append(subnets, subnet.String())
+ }
+ }
+ getSourceSubnets(t.ckr.ipv4sources)
+ getSourceSubnets(t.ckr.ipv6sources)
+ return admin.Info{"source_subnets": subnets}, nil
+ })
+ a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) {
+ routes := make(admin.Info)
+ getRoutes := func(ckrs []cryptokey_route) {
+ for _, ckr := range ckrs {
+ routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:])
+ }
+ }
+ getRoutes(t.ckr.ipv4routes)
+ getRoutes(t.ckr.ipv6routes)
+ return admin.Info{"routes": routes}, nil
+ })
+ a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) {
+ if err := t.ckr.removeSourceSubnet(in["subnet"].(string)); err == nil {
+ return admin.Info{"removed": []string{in["subnet"].(string)}}, nil
+ } else {
+ return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet")
+ }
+ })
+ a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) {
+ if err := t.ckr.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil {
+ return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
+ } else {
+ return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route")
+ }
+ })
+}
diff --git a/src/yggdrasil/ckr.go b/src/tuntap/ckr.go
similarity index 81%
rename from src/yggdrasil/ckr.go
rename to src/tuntap/ckr.go
index 03bc5718..80e3e42f 100644
--- a/src/yggdrasil/ckr.go
+++ b/src/tuntap/ckr.go
@@ -1,4 +1,4 @@
-package yggdrasil
+package tuntap
import (
"bytes"
@@ -7,6 +7,8 @@ import (
"fmt"
"net"
"sort"
+ "sync"
+ "sync/atomic"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
@@ -16,15 +18,18 @@ import (
// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil.
type cryptokey struct {
- core *Core
- enabled bool
- reconfigure chan chan error
- ipv4routes []cryptokey_route
- ipv6routes []cryptokey_route
- ipv4cache map[address.Address]cryptokey_route
- ipv6cache map[address.Address]cryptokey_route
- ipv4sources []net.IPNet
- ipv6sources []net.IPNet
+ tun *TunAdapter
+ enabled atomic.Value // bool
+ reconfigure chan chan error
+ ipv4routes []cryptokey_route
+ ipv6routes []cryptokey_route
+ ipv4cache map[address.Address]cryptokey_route
+ ipv6cache map[address.Address]cryptokey_route
+ ipv4sources []net.IPNet
+ ipv6sources []net.IPNet
+ mutexroutes sync.RWMutex
+ mutexcaches sync.RWMutex
+ mutexsources sync.RWMutex
}
type cryptokey_route struct {
@@ -33,59 +38,61 @@ type cryptokey_route struct {
}
// Initialise crypto-key routing. This must be done before any other CKR calls.
-func (c *cryptokey) init(core *Core) {
- c.core = core
+func (c *cryptokey) init(tun *TunAdapter) {
+ c.tun = tun
c.reconfigure = make(chan chan error, 1)
go func() {
for {
e := <-c.reconfigure
- var err error
- c.core.router.doAdmin(func() {
- err = c.core.router.cryptokey.configure()
- })
- e <- err
+ e <- nil
}
}()
+ c.tun.log.Debugln("Configuring CKR...")
if err := c.configure(); err != nil {
- c.core.log.Errorln("CKR configuration failed:", err)
+ c.tun.log.Errorln("CKR configuration failed:", err)
+ } else {
+ c.tun.log.Debugln("CKR configured")
}
}
// Configure the CKR routes - this must only ever be called from the router
// goroutine, e.g. through router.doAdmin
func (c *cryptokey) configure() error {
- c.core.configMutex.RLock()
- defer c.core.configMutex.RUnlock()
+ current := c.tun.config.GetCurrent()
// Set enabled/disabled state
- c.setEnabled(c.core.config.TunnelRouting.Enable)
+ c.setEnabled(current.TunnelRouting.Enable)
// Clear out existing routes
+ c.mutexroutes.Lock()
c.ipv6routes = make([]cryptokey_route, 0)
c.ipv4routes = make([]cryptokey_route, 0)
+ c.mutexroutes.Unlock()
// Add IPv6 routes
- for ipv6, pubkey := range c.core.config.TunnelRouting.IPv6Destinations {
+ for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations {
if err := c.addRoute(ipv6, pubkey); err != nil {
return err
}
}
// Add IPv4 routes
- for ipv4, pubkey := range c.core.config.TunnelRouting.IPv4Destinations {
+ for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations {
if err := c.addRoute(ipv4, pubkey); err != nil {
return err
}
}
// Clear out existing sources
+ c.mutexsources.Lock()
c.ipv6sources = make([]net.IPNet, 0)
c.ipv4sources = make([]net.IPNet, 0)
+ c.mutexsources.Unlock()
// Add IPv6 sources
c.ipv6sources = make([]net.IPNet, 0)
- for _, source := range c.core.config.TunnelRouting.IPv6Sources {
+ for _, source := range current.TunnelRouting.IPv6Sources {
if err := c.addSourceSubnet(source); err != nil {
return err
}
@@ -93,43 +100,49 @@ func (c *cryptokey) configure() error {
// Add IPv4 sources
c.ipv4sources = make([]net.IPNet, 0)
- for _, source := range c.core.config.TunnelRouting.IPv4Sources {
+ for _, source := range current.TunnelRouting.IPv4Sources {
if err := c.addSourceSubnet(source); err != nil {
return err
}
}
// Wipe the caches
+ c.mutexcaches.Lock()
c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
+ c.mutexcaches.Unlock()
return nil
}
// Enable or disable crypto-key routing.
func (c *cryptokey) setEnabled(enabled bool) {
- c.enabled = enabled
+ c.enabled.Store(enabled)
}
// Check if crypto-key routing is enabled.
func (c *cryptokey) isEnabled() bool {
- return c.enabled
+ enabled, ok := c.enabled.Load().(bool)
+ return ok && enabled
}
// Check whether the given address (with the address length specified in bytes)
// matches either the current node's address, the node's routed subnet or the
// list of subnets specified in IPv4Sources/IPv6Sources.
func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
+ c.mutexsources.RLock()
+ defer c.mutexsources.RUnlock()
+
ip := net.IP(addr[:addrlen])
if addrlen == net.IPv6len {
// Does this match our node's address?
- if bytes.Equal(addr[:16], c.core.router.addr[:16]) {
+ if bytes.Equal(addr[:16], c.tun.addr[:16]) {
return true
}
// Does this match our node's subnet?
- if bytes.Equal(addr[:8], c.core.router.subnet[:8]) {
+ if bytes.Equal(addr[:8], c.tun.subnet[:8]) {
return true
}
}
@@ -162,6 +175,9 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool {
// Adds a source subnet, which allows traffic with these source addresses to
// be tunnelled using crypto-key routing.
func (c *cryptokey) addSourceSubnet(cidr string) error {
+ c.mutexsources.Lock()
+ defer c.mutexsources.Unlock()
+
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@@ -192,13 +208,18 @@ func (c *cryptokey) addSourceSubnet(cidr string) error {
// Add the source subnet
*routingsources = append(*routingsources, *ipnet)
- c.core.log.Infoln("Added CKR source subnet", cidr)
+ c.tun.log.Infoln("Added CKR source subnet", cidr)
return nil
}
// Adds a destination route for the given CIDR to be tunnelled to the node
// with the given BoxPubKey.
func (c *cryptokey) addRoute(cidr string, dest string) error {
+ c.mutexroutes.Lock()
+ c.mutexcaches.Lock()
+ defer c.mutexroutes.Unlock()
+ defer c.mutexcaches.Unlock()
+
// Is the CIDR we've been given valid?
ipaddr, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@@ -264,7 +285,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
delete(*routingcache, k)
}
- c.core.log.Infoln("Added CKR destination subnet", cidr)
+ c.tun.log.Infoln("Added CKR destination subnet", cidr)
return nil
}
}
@@ -273,6 +294,8 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
// length specified in bytes) from the crypto-key routing table. An error is
// returned if the address is not suitable or no route was found.
func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) {
+ c.mutexcaches.RLock()
+
// Check if the address is a valid Yggdrasil address - if so it
// is exempt from all CKR checking
if addr.IsValid() {
@@ -285,10 +308,8 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Check if the prefix is IPv4 or IPv6
if addrlen == net.IPv6len {
- routingtable = &c.ipv6routes
routingcache = &c.ipv6cache
} else if addrlen == net.IPv4len {
- routingtable = &c.ipv4routes
routingcache = &c.ipv4cache
} else {
return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
@@ -296,9 +317,24 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Check if there's a cache entry for this addr
if route, ok := (*routingcache)[addr]; ok {
+ c.mutexcaches.RUnlock()
return route.destination, nil
}
+ c.mutexcaches.RUnlock()
+
+ c.mutexroutes.RLock()
+ defer c.mutexroutes.RUnlock()
+
+ // Check if the prefix is IPv4 or IPv6
+ if addrlen == net.IPv6len {
+ routingtable = &c.ipv6routes
+ } else if addrlen == net.IPv4len {
+ routingtable = &c.ipv4routes
+ } else {
+ return crypto.BoxPubKey{}, errors.New("Unexpected prefix size")
+ }
+
// No cache was found - start by converting the address into a net.IP
ip := make(net.IP, addrlen)
copy(ip[:addrlen], addr[:])
@@ -308,6 +344,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
for _, route := range *routingtable {
// Does this subnet match the given IP?
if route.subnet.Contains(ip) {
+ c.mutexcaches.Lock()
+ defer c.mutexcaches.Unlock()
+
// Check if the routing cache is above a certain size, if it is evict
// a random entry so we can make room for this one. We take advantage
// of the fact that the iteration order is random here
@@ -333,6 +372,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c
// Removes a source subnet, which allows traffic with these source addresses to
// be tunnelled using crypto-key routing.
func (c *cryptokey) removeSourceSubnet(cidr string) error {
+ c.mutexsources.Lock()
+ defer c.mutexsources.Unlock()
+
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@@ -358,7 +400,7 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
for idx, subnet := range *routingsources {
if subnet.String() == ipnet.String() {
*routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...)
- c.core.log.Infoln("Removed CKR source subnet", cidr)
+ c.tun.log.Infoln("Removed CKR source subnet", cidr)
return nil
}
}
@@ -368,6 +410,11 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error {
// Removes a destination route for the given CIDR to be tunnelled to the node
// with the given BoxPubKey.
func (c *cryptokey) removeRoute(cidr string, dest string) error {
+ c.mutexroutes.Lock()
+ c.mutexcaches.Lock()
+ defer c.mutexroutes.Unlock()
+ defer c.mutexcaches.Unlock()
+
// Is the CIDR we've been given valid?
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
@@ -407,7 +454,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
for k := range *routingcache {
delete(*routingcache, k)
}
- c.core.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest)
+ c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest)
return nil
}
}
diff --git a/src/tuntap/conn.go b/src/tuntap/conn.go
new file mode 100644
index 00000000..1d47b378
--- /dev/null
+++ b/src/tuntap/conn.go
@@ -0,0 +1,159 @@
+package tuntap
+
+import (
+ "errors"
+ "time"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/address"
+ "github.com/yggdrasil-network/yggdrasil-go/src/util"
+ "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
+ "golang.org/x/net/icmp"
+ "golang.org/x/net/ipv6"
+)
+
+const tunConnTimeout = 2 * time.Minute
+
+type tunConn struct {
+ tun *TunAdapter
+ conn *yggdrasil.Conn
+ addr address.Address
+ snet address.Subnet
+ send chan []byte
+ stop chan struct{}
+ alive chan struct{}
+}
+
+func (s *tunConn) close() {
+ s.tun.mutex.Lock()
+ defer s.tun.mutex.Unlock()
+ s._close_nomutex()
+}
+
+func (s *tunConn) _close_nomutex() {
+ s.conn.Close()
+ delete(s.tun.addrToConn, s.addr)
+ delete(s.tun.subnetToConn, s.snet)
+ func() {
+ defer func() { recover() }()
+ close(s.stop) // Closes reader/writer goroutines
+ }()
+ func() {
+ defer func() { recover() }()
+ close(s.alive) // Closes timeout goroutine
+ }()
+}
+
+func (s *tunConn) reader() (err error) {
+ select {
+ case _, ok := <-s.stop:
+ if !ok {
+ return errors.New("session was already closed")
+ }
+ default:
+ }
+ s.tun.log.Debugln("Starting conn reader for", s.conn.String())
+ defer s.tun.log.Debugln("Stopping conn reader for", s.conn.String())
+ var n int
+ b := make([]byte, 65535)
+ for {
+ select {
+ case <-s.stop:
+ return nil
+ default:
+ }
+ if n, err = s.conn.Read(b); err != nil {
+ if e, eok := err.(yggdrasil.ConnError); eok && !e.Temporary() {
+ if e.Closed() {
+ s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn read debug:", err)
+ } else {
+ s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn read error:", err)
+ }
+ return e
+ }
+ } else if n > 0 {
+ bs := append(util.GetBytes(), b[:n]...)
+ select {
+ case s.tun.send <- bs:
+ default:
+ util.PutBytes(bs)
+ }
+ s.stillAlive()
+ }
+ }
+}
+
+func (s *tunConn) writer() error {
+ select {
+ case _, ok := <-s.stop:
+ if !ok {
+ return errors.New("session was already closed")
+ }
+ default:
+ }
+ s.tun.log.Debugln("Starting conn writer for", s.conn.String())
+ defer s.tun.log.Debugln("Stopping conn writer for", s.conn.String())
+ for {
+ select {
+ case <-s.stop:
+ return nil
+ case b, ok := <-s.send:
+ if !ok {
+ return errors.New("send closed")
+ }
+ // TODO write timeout and close
+ if _, err := s.conn.Write(b); err != nil {
+ if e, eok := err.(yggdrasil.ConnError); !eok {
+ if e.Closed() {
+ s.tun.log.Debugln(s.conn.String(), "TUN/TAP generic write debug:", err)
+ } else {
+ s.tun.log.Errorln(s.conn.String(), "TUN/TAP generic write error:", err)
+ }
+ } else if e.PacketTooBig() {
+ // TODO: This currently isn't aware of IPv4 for CKR
+ ptb := &icmp.PacketTooBig{
+ MTU: int(e.PacketMaximumSize()),
+ Data: b[:900],
+ }
+ if packet, err := CreateICMPv6(b[8:24], b[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil {
+ s.tun.send <- packet
+ }
+ } else {
+ if e.Closed() {
+ s.tun.log.Debugln(s.conn.String(), "TUN/TAP conn write debug:", err)
+ } else {
+ s.tun.log.Errorln(s.conn.String(), "TUN/TAP conn write error:", err)
+ }
+ }
+ } else {
+ s.stillAlive()
+ }
+ util.PutBytes(b)
+ }
+ }
+}
+
+func (s *tunConn) stillAlive() {
+ defer func() { recover() }()
+ select {
+ case s.alive <- struct{}{}:
+ default:
+ }
+}
+
+func (s *tunConn) checkForTimeouts() error {
+ timer := time.NewTimer(tunConnTimeout)
+ defer util.TimerStop(timer)
+ defer s.close()
+ for {
+ select {
+ case _, ok := <-s.alive:
+ if !ok {
+ return errors.New("connection closed")
+ }
+ util.TimerStop(timer)
+ timer.Reset(tunConnTimeout)
+ case <-timer.C:
+ return errors.New("timed out")
+ }
+ }
+}
diff --git a/src/yggdrasil/icmpv6.go b/src/tuntap/icmpv6.go
similarity index 63%
rename from src/yggdrasil/icmpv6.go
rename to src/tuntap/icmpv6.go
index 52ca50c6..e601acb5 100644
--- a/src/yggdrasil/icmpv6.go
+++ b/src/tuntap/icmpv6.go
@@ -1,4 +1,4 @@
-package yggdrasil
+package tuntap
// The ICMPv6 module implements functions to easily create ICMPv6
// packets. These functions, when mixed with the built-in Go IPv6
@@ -13,6 +13,7 @@ import (
"encoding/binary"
"errors"
"net"
+ "sync"
"time"
"golang.org/x/net/icmp"
@@ -21,19 +22,18 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
-type macAddress [6]byte
-
const len_ETHER = 14
-type icmpv6 struct {
- tun *tunAdapter
- mylladdr net.IP
- mymac macAddress
- peermacs map[address.Address]neighbor
+type ICMPv6 struct {
+ tun *TunAdapter
+ mylladdr net.IP
+ mymac net.HardwareAddr
+ peermacs map[address.Address]neighbor
+ peermacsmutex sync.RWMutex
}
type neighbor struct {
- mac macAddress
+ mac net.HardwareAddr
learned bool
lastadvertisement time.Time
lastsolicitation time.Time
@@ -59,56 +59,60 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) {
// Initialises the ICMPv6 module by assigning our link-local IPv6 address and
// our MAC address. ICMPv6 messages will always appear to originate from these
// addresses.
-func (i *icmpv6) init(t *tunAdapter) {
+func (i *ICMPv6) Init(t *TunAdapter) {
i.tun = t
+ i.peermacsmutex.Lock()
i.peermacs = make(map[address.Address]neighbor)
+ i.peermacsmutex.Unlock()
// Our MAC address and link-local address
- i.mymac = macAddress{
+ i.mymac = net.HardwareAddr{
0x02, 0x00, 0x00, 0x00, 0x00, 0x02}
i.mylladdr = net.IP{
0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE}
- copy(i.mymac[:], i.tun.core.router.addr[:])
- copy(i.mylladdr[9:], i.tun.core.router.addr[1:])
+ copy(i.mymac[:], i.tun.addr[:])
+ copy(i.mylladdr[9:], i.tun.addr[1:])
}
// Parses an incoming ICMPv6 packet. The packet provided may be either an
// ethernet frame containing an IP packet, or the IP packet alone. This is
// determined by whether the TUN/TAP adapter is running in TUN (layer 3) or
-// TAP (layer 2) mode.
-func (i *icmpv6) parse_packet(datain []byte) {
+// TAP (layer 2) mode. Returns an error condition which is nil if the ICMPv6
+// module handled the packet or contains the error if not.
+func (i *ICMPv6) ParsePacket(datain []byte) error {
var response []byte
var err error
// Parse the frame/packet
- if i.tun.iface.IsTAP() {
- response, err = i.parse_packet_tap(datain)
+ if i.tun.IsTAP() {
+ response, err = i.UnmarshalPacketL2(datain)
} else {
- response, err = i.parse_packet_tun(datain, nil)
+ response, err = i.UnmarshalPacket(datain, nil)
}
if err != nil {
- return
+ return err
}
// Write the packet to TUN/TAP
i.tun.iface.Write(response)
+ return nil
}
// Unwraps the ethernet headers of an incoming ICMPv6 packet and hands off
-// the IP packet to the parse_packet_tun function for further processing.
+// the IP packet to the ParsePacket function for further processing.
// A response buffer is also created for the response message, also complete
// with ethernet headers.
-func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) {
+func (i *ICMPv6) UnmarshalPacketL2(datain []byte) ([]byte, error) {
// Ignore non-IPv6 frames
if binary.BigEndian.Uint16(datain[12:14]) != uint16(0x86DD) {
- return nil, nil
+ return nil, errors.New("Ignoring non-IPv6 frame")
}
- // Hand over to parse_packet_tun to interpret the IPv6 packet
+ // Hand over to ParsePacket to interpret the IPv6 packet
mac := datain[6:12]
- ipv6packet, err := i.parse_packet_tun(datain[len_ETHER:], &mac)
+ ipv6packet, err := i.UnmarshalPacket(datain[len_ETHER:], &mac)
if err != nil {
return nil, err
}
@@ -130,7 +134,7 @@ func (i *icmpv6) parse_packet_tap(datain []byte) ([]byte, error) {
// sanity checks on the packet - i.e. is the packet an ICMPv6 packet, does the
// ICMPv6 message match a known expected type. The relevant handler function
// is then called and a response packet may be returned.
-func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error) {
+func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) {
// Parse the IPv6 packet headers
ipv6Header, err := ipv6.ParseHeader(datain[:ipv6.HeaderLen])
if err != nil {
@@ -139,12 +143,12 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Check if the packet is IPv6
if ipv6Header.Version != ipv6.Version {
- return nil, err
+ return nil, errors.New("Ignoring non-IPv6 packet")
}
// Check if the packet is ICMPv6
if ipv6Header.NextHeader != 58 {
- return nil, err
+ return nil, errors.New("Ignoring non-ICMPv6 packet")
}
// Parse the ICMPv6 message contents
@@ -156,42 +160,55 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Check for a supported message type
switch icmpv6Header.Type {
case ipv6.ICMPTypeNeighborSolicitation:
- if !i.tun.iface.IsTAP() {
+ if !i.tun.IsTAP() {
return nil, errors.New("Ignoring Neighbor Solicitation in TUN mode")
}
- response, err := i.handle_ndp(datain[ipv6.HeaderLen:])
+ response, err := i.HandleNDP(datain[ipv6.HeaderLen:])
if err == nil {
// Create our ICMPv6 response
- responsePacket, err := i.create_icmpv6_tun(
+ responsePacket, err := CreateICMPv6(
ipv6Header.Src, i.mylladdr,
ipv6.ICMPTypeNeighborAdvertisement, 0,
&icmp.DefaultMessageBody{Data: response})
if err != nil {
return nil, err
}
-
// Send it back
return responsePacket, nil
} else {
return nil, err
}
case ipv6.ICMPTypeNeighborAdvertisement:
- if !i.tun.iface.IsTAP() {
+ if !i.tun.IsTAP() {
return nil, errors.New("Ignoring Neighbor Advertisement in TUN mode")
}
if datamac != nil {
var addr address.Address
var target address.Address
- var mac macAddress
+ mac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
copy(addr[:], ipv6Header.Src[:])
copy(target[:], datain[48:64])
copy(mac[:], (*datamac)[:])
- // i.tun.core.log.Printf("Learning peer MAC %x for %x\n", mac, target)
+ i.peermacsmutex.Lock()
neighbor := i.peermacs[target]
neighbor.mac = mac
neighbor.learned = true
neighbor.lastadvertisement = time.Now()
i.peermacs[target] = neighbor
+ i.peermacsmutex.Unlock()
+ i.tun.log.Debugln("Learned peer MAC", mac.String(), "for", net.IP(target[:]).String())
+ /*
+ i.tun.log.Debugln("Peer MAC table:")
+ i.peermacsmutex.RLock()
+ for t, n := range i.peermacs {
+ if n.learned {
+ i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "has MAC", n.mac.String())
+ } else {
+ i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "is not learned yet")
+ }
+ }
+ i.peermacsmutex.RUnlock()
+ */
}
return nil, errors.New("No response needed")
}
@@ -202,9 +219,9 @@ func (i *icmpv6) parse_packet_tun(datain []byte, datamac *[]byte) ([]byte, error
// Creates an ICMPv6 packet based on the given icmp.MessageBody and other
// parameters, complete with ethernet and IP headers, which can be written
// directly to a TAP adapter.
-func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
- // Pass through to create_icmpv6_tun
- ipv6packet, err := i.create_icmpv6_tun(dst, src, mtype, mcode, mbody)
+func (i *ICMPv6) CreateICMPv6L2(dstmac net.HardwareAddr, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
+ // Pass through to CreateICMPv6
+ ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody)
if err != nil {
return nil, err
}
@@ -224,9 +241,9 @@ func (i *icmpv6) create_icmpv6_tap(dstmac macAddress, dst net.IP, src net.IP, mt
// 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 create_icmpv6_tap function when
+// a TUN adapter, or called directly by the CreateICMPv6L2 function when
// generating a message for TAP adapters.
-func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) {
+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,
@@ -265,13 +282,54 @@ func (i *icmpv6) create_icmpv6_tun(dst net.IP, src net.IP, mtype ipv6.ICMPType,
return responsePacket, nil
}
-func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
+func (i *ICMPv6) Solicit(addr address.Address) {
+ retries := 5
+ for retries > 0 {
+ retries--
+ i.peermacsmutex.RLock()
+ if n, ok := i.peermacs[addr]; ok && n.learned {
+ i.tun.log.Debugln("MAC learned for", net.IP(addr[:]).String())
+ i.peermacsmutex.RUnlock()
+ return
+ }
+ i.peermacsmutex.RUnlock()
+ i.tun.log.Debugln("Sending neighbor solicitation for", net.IP(addr[:]).String())
+ i.peermacsmutex.Lock()
+ if n, ok := i.peermacs[addr]; !ok {
+ i.peermacs[addr] = neighbor{
+ lastsolicitation: time.Now(),
+ }
+ } else {
+ n.lastsolicitation = time.Now()
+ }
+ i.peermacsmutex.Unlock()
+ request, err := i.createNDPL2(addr)
+ if err != nil {
+ panic(err)
+ }
+ if _, err := i.tun.iface.Write(request); err != nil {
+ panic(err)
+ }
+ i.tun.log.Debugln("Sent neighbor solicitation for", net.IP(addr[:]).String())
+ time.Sleep(time.Second)
+ }
+}
+
+func (i *ICMPv6) getNeighbor(addr address.Address) (neighbor, bool) {
+ i.peermacsmutex.RLock()
+ defer i.peermacsmutex.RUnlock()
+
+ n, ok := i.peermacs[addr]
+ return n, ok
+}
+
+func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) {
// Create the ND payload
var payload [28]byte
- copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00})
- copy(payload[4:20], dst[:])
- copy(payload[20:22], []byte{0x01, 0x01})
- copy(payload[22:28], i.mymac[:6])
+ copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Flags
+ copy(payload[4:20], dst[:]) // Destination
+ copy(payload[20:22], []byte{0x01, 0x01}) // Type & length
+ copy(payload[22:28], i.mymac[:6]) // Link layer address
// Create the ICMPv6 solicited-node address
var dstaddr address.Address
@@ -282,21 +340,18 @@ func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
copy(dstaddr[13:], dst[13:16])
// Create the multicast MAC
- var dstmac macAddress
+ dstmac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
copy(dstmac[:2], []byte{0x33, 0x33})
copy(dstmac[2:6], dstaddr[12:16])
// Create the ND request
- requestPacket, err := i.create_icmpv6_tap(
+ requestPacket, err := i.CreateICMPv6L2(
dstmac, dstaddr[:], i.mylladdr,
ipv6.ICMPTypeNeighborSolicitation, 0,
&icmp.DefaultMessageBody{Data: payload[:]})
if err != nil {
return nil, err
}
- neighbor := i.peermacs[dstaddr]
- neighbor.lastsolicitation = time.Now()
- i.peermacs[dstaddr] = neighbor
return requestPacket, nil
}
@@ -305,7 +360,7 @@ func (i *icmpv6) create_ndp_tap(dst address.Address) ([]byte, error) {
// when the host operating system generates an NDP request for any address in
// the fd00::/8 range, so that the operating system knows to route that traffic
// to the Yggdrasil TAP adapter.
-func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) {
+func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) {
// Ignore NDP requests for anything outside of fd00::/8
var source address.Address
copy(source[:], in[8:])
@@ -320,10 +375,10 @@ func (i *icmpv6) handle_ndp(in []byte) ([]byte, error) {
// Create our NDP message body response
body := make([]byte, 28)
- binary.BigEndian.PutUint32(body[:4], uint32(0x20000000))
- copy(body[4:20], in[8:24]) // Target address
- body[20] = uint8(2)
- body[21] = uint8(1)
+ binary.BigEndian.PutUint32(body[:4], uint32(0x40000000)) // Flags
+ copy(body[4:20], in[8:24]) // Target address
+ body[20] = uint8(2) // Type: Target link-layer address
+ body[21] = uint8(1) // Length: 1x address (8 bytes)
copy(body[22:28], i.mymac[:6])
// Send it back
diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go
new file mode 100644
index 00000000..a95dfae4
--- /dev/null
+++ b/src/tuntap/iface.go
@@ -0,0 +1,306 @@
+package tuntap
+
+import (
+ "bytes"
+ "errors"
+ "net"
+ "time"
+
+ "github.com/songgao/packets/ethernet"
+ "github.com/yggdrasil-network/yggdrasil-go/src/address"
+ "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
+ "github.com/yggdrasil-network/yggdrasil-go/src/util"
+)
+
+func (tun *TunAdapter) writer() error {
+ var w int
+ var err error
+ for {
+ b := <-tun.send
+ n := len(b)
+ if n == 0 {
+ continue
+ }
+ if tun.iface.IsTAP() {
+ var dstAddr address.Address
+ if b[0]&0xf0 == 0x60 {
+ if len(b) < 40 {
+ //panic("Tried to send a packet shorter than an IPv6 header...")
+ util.PutBytes(b)
+ continue
+ }
+ copy(dstAddr[:16], b[24:])
+ } else if b[0]&0xf0 == 0x40 {
+ if len(b) < 20 {
+ //panic("Tried to send a packet shorter than an IPv4 header...")
+ util.PutBytes(b)
+ continue
+ }
+ copy(dstAddr[:4], b[16:])
+ } else {
+ return errors.New("Invalid address family")
+ }
+ sendndp := func(dstAddr address.Address) {
+ neigh, known := tun.icmpv6.getNeighbor(dstAddr)
+ known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
+ if !known {
+ tun.icmpv6.Solicit(dstAddr)
+ }
+ }
+ peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+ var peerknown bool
+ if b[0]&0xf0 == 0x40 {
+ dstAddr = tun.addr
+ } else if b[0]&0xf0 == 0x60 {
+ if !bytes.Equal(tun.addr[:16], dstAddr[:16]) && !bytes.Equal(tun.subnet[:8], dstAddr[:8]) {
+ dstAddr = tun.addr
+ }
+ }
+ if neighbor, ok := tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned {
+ // If we've learned the MAC of a 300::/7 address, for example, or a CKR
+ // address, use the MAC address of that
+ peermac = neighbor.mac
+ peerknown = true
+ } else if neighbor, ok := tun.icmpv6.getNeighbor(tun.addr); ok && neighbor.learned {
+ // Otherwise send directly to the MAC address of the host if that's
+ // known instead
+ peermac = neighbor.mac
+ peerknown = true
+ } else {
+ // Nothing has been discovered, try to discover the destination
+ sendndp(tun.addr)
+
+ }
+ if peerknown {
+ var proto ethernet.Ethertype
+ switch {
+ case b[0]&0xf0 == 0x60:
+ proto = ethernet.IPv6
+ case b[0]&0xf0 == 0x40:
+ proto = ethernet.IPv4
+ }
+ var frame ethernet.Frame
+ frame.Prepare(
+ peermac[:6], // Destination MAC address
+ tun.icmpv6.mymac[:6], // Source MAC address
+ ethernet.NotTagged, // VLAN tagging
+ proto, // Ethertype
+ len(b)) // Payload length
+ copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n])
+ n += tun_ETHER_HEADER_LENGTH
+ w, err = tun.iface.Write(frame[:n])
+ } else {
+ tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet")
+ }
+ } else {
+ w, err = tun.iface.Write(b[:n])
+ util.PutBytes(b)
+ }
+ if err != nil {
+ if !tun.isOpen {
+ return err
+ }
+ tun.log.Errorln("TUN/TAP iface write error:", err)
+ continue
+ }
+ if w != n {
+ tun.log.Errorln("TUN/TAP iface write mismatch:", w, "bytes written vs", n, "bytes given")
+ continue
+ }
+ }
+}
+
+// Run in a separate goroutine by the reader
+// Does all of the per-packet ICMP checks, passes packets to the right Conn worker
+func (tun *TunAdapter) readerPacketHandler(ch chan []byte) {
+ for recvd := range ch {
+ // If it's a TAP adapter, update the buffer slice so that we no longer
+ // include the ethernet headers
+ offset := 0
+ if tun.iface.IsTAP() {
+ // Set our offset to beyond the ethernet headers
+ offset = tun_ETHER_HEADER_LENGTH
+ // Check first of all that we can go beyond the ethernet headers
+ if len(recvd) <= offset {
+ continue
+ }
+ }
+ // Offset the buffer from now on so that we can ignore ethernet frames if
+ // they are present
+ bs := recvd[offset:]
+ // If we detect an ICMP packet then hand it to the ICMPv6 module
+ if bs[6] == 58 {
+ // Found an ICMPv6 packet - we need to make sure to give ICMPv6 the full
+ // Ethernet frame rather than just the IPv6 packet as this is needed for
+ // NDP to work correctly
+ if err := tun.icmpv6.ParsePacket(recvd); err == nil {
+ // We acted on the packet in the ICMPv6 module so don't forward or do
+ // anything else with it
+ continue
+ }
+ }
+ // Shift forward to avoid leaking bytes off the front of the slide when we eventually store it
+ bs = append(recvd[:0], bs...)
+ // From the IP header, work out what our source and destination addresses
+ // and node IDs are. We will need these in order to work out where to send
+ // the packet
+ var srcAddr address.Address
+ var dstAddr address.Address
+ var dstNodeID *crypto.NodeID
+ var dstNodeIDMask *crypto.NodeID
+ var dstSnet address.Subnet
+ var addrlen int
+ n := len(bs)
+ // Check the IP protocol - if it doesn't match then we drop the packet and
+ // do nothing with it
+ if bs[0]&0xf0 == 0x60 {
+ // Check if we have a fully-sized IPv6 header
+ if len(bs) < 40 {
+ continue
+ }
+ // Check the packet size
+ if n-tun_IPv6_HEADER_LENGTH != 256*int(bs[4])+int(bs[5]) {
+ continue
+ }
+ // IPv6 address
+ addrlen = 16
+ copy(srcAddr[:addrlen], bs[8:])
+ copy(dstAddr[:addrlen], bs[24:])
+ copy(dstSnet[:addrlen/2], bs[24:])
+ } else if bs[0]&0xf0 == 0x40 {
+ // Check if we have a fully-sized IPv4 header
+ if len(bs) < 20 {
+ continue
+ }
+ // Check the packet size
+ if n != 256*int(bs[2])+int(bs[3]) {
+ continue
+ }
+ // IPv4 address
+ addrlen = 4
+ copy(srcAddr[:addrlen], bs[12:])
+ copy(dstAddr[:addrlen], bs[16:])
+ } else {
+ // Unknown address length or protocol, so drop the packet and ignore it
+ tun.log.Traceln("Unknown packet type, dropping")
+ continue
+ }
+ if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) {
+ // The packet had a source address that doesn't belong to us or our
+ // configured crypto-key routing source subnets
+ continue
+ }
+ if !dstAddr.IsValid() && !dstSnet.IsValid() {
+ if key, err := tun.ckr.getPublicKeyForAddress(dstAddr, addrlen); err == nil {
+ // A public key was found, get the node ID for the search
+ dstNodeID = crypto.GetNodeID(&key)
+ // Do a quick check to ensure that the node ID refers to a vaild
+ // Yggdrasil address or subnet - this might be superfluous
+ addr := *address.AddrForNodeID(dstNodeID)
+ copy(dstAddr[:], addr[:])
+ copy(dstSnet[:], addr[:])
+ // Are we certain we looked up a valid node?
+ if !dstAddr.IsValid() && !dstSnet.IsValid() {
+ continue
+ }
+ } else {
+ // No public key was found in the CKR table so we've exhausted our options
+ continue
+ }
+ }
+ // Do we have an active connection for this node address?
+ tun.mutex.RLock()
+ session, isIn := tun.addrToConn[dstAddr]
+ if !isIn || session == nil {
+ session, isIn = tun.subnetToConn[dstSnet]
+ if !isIn || session == nil {
+ // Neither an address nor a subnet mapping matched, therefore populate
+ // the node ID and mask to commence a search
+ if dstAddr.IsValid() {
+ dstNodeID, dstNodeIDMask = dstAddr.GetNodeIDandMask()
+ } else {
+ dstNodeID, dstNodeIDMask = dstSnet.GetNodeIDandMask()
+ }
+ }
+ }
+ tun.mutex.RUnlock()
+ // If we don't have a connection then we should open one
+ if !isIn || session == nil {
+ // Check we haven't been given empty node ID, really this shouldn't ever
+ // happen but just to be sure...
+ if dstNodeID == nil || dstNodeIDMask == nil {
+ panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen")
+ }
+ // Dial to the remote node
+ go func() {
+ // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes
+ tun.mutex.Lock()
+ _, known := tun.dials[*dstNodeID]
+ tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], bs)
+ for len(tun.dials[*dstNodeID]) > 32 {
+ util.PutBytes(tun.dials[*dstNodeID][0])
+ tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:]
+ }
+ tun.mutex.Unlock()
+ if known {
+ return
+ }
+ var tc *tunConn
+ if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil {
+ // We've been given a connection so prepare the session wrapper
+ if tc, err = tun.wrap(conn); err != nil {
+ // Something went wrong when storing the connection, typically that
+ // something already exists for this address or subnet
+ tun.log.Debugln("TUN/TAP iface wrap:", err)
+ }
+ }
+ tun.mutex.Lock()
+ packets := tun.dials[*dstNodeID]
+ delete(tun.dials, *dstNodeID)
+ tun.mutex.Unlock()
+ if tc != nil {
+ for _, packet := range packets {
+ select {
+ case tc.send <- packet:
+ default:
+ util.PutBytes(packet)
+ }
+ }
+ }
+ }()
+ // While the dial is going on we can't do much else
+ // continuing this iteration - skip to the next one
+ continue
+ }
+ // If we have a connection now, try writing to it
+ if isIn && session != nil {
+ select {
+ case session.send <- bs:
+ default:
+ util.PutBytes(bs)
+ }
+ }
+ }
+}
+
+func (tun *TunAdapter) reader() error {
+ recvd := make([]byte, 65535+tun_ETHER_HEADER_LENGTH)
+ toWorker := make(chan []byte, 32)
+ defer close(toWorker)
+ go tun.readerPacketHandler(toWorker)
+ for {
+ // Wait for a packet to be delivered to us through the TUN/TAP adapter
+ n, err := tun.iface.Read(recvd)
+ if err != nil {
+ if !tun.isOpen {
+ return err
+ }
+ panic(err)
+ }
+ if n == 0 {
+ continue
+ }
+ bs := append(util.GetBytes(), recvd[:n]...)
+ toWorker <- bs
+ }
+}
diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go
new file mode 100644
index 00000000..eef05b87
--- /dev/null
+++ b/src/tuntap/tun.go
@@ -0,0 +1,265 @@
+package 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 (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "net"
+ "sync"
+
+ "github.com/gologme/log"
+ "github.com/yggdrasil-network/water"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/address"
+ "github.com/yggdrasil-network/yggdrasil-go/src/config"
+ "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
+ "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
+ "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
+)
+
+const tun_IPv6_HEADER_LENGTH = 40
+const tun_ETHER_HEADER_LENGTH = 14
+
+// TunAdapter represents a running TUN/TAP interface and extends the
+// yggdrasil.Adapter type. In order to use the TUN/TAP adapter with Yggdrasil,
+// you should pass this object to the yggdrasil.SetRouterAdapter() function
+// before calling yggdrasil.Start().
+type TunAdapter struct {
+ config *config.NodeState
+ log *log.Logger
+ reconfigure chan chan error
+ listener *yggdrasil.Listener
+ dialer *yggdrasil.Dialer
+ addr address.Address
+ subnet address.Subnet
+ ckr cryptokey
+ icmpv6 ICMPv6
+ mtu int
+ iface *water.Interface
+ send chan []byte
+ mutex sync.RWMutex // Protects the below
+ addrToConn map[address.Address]*tunConn
+ subnetToConn map[address.Subnet]*tunConn
+ dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes
+ isOpen bool
+}
+
+// Gets the maximum supported MTU for the platform based on the defaults in
+// defaults.GetDefaults().
+func getSupportedMTU(mtu int) int {
+ if mtu > defaults.GetDefaults().MaximumIfMTU {
+ return defaults.GetDefaults().MaximumIfMTU
+ }
+ 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 {
+ return tun.iface.Name()
+}
+
+// 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() int {
+ return getSupportedMTU(tun.mtu)
+}
+
+// IsTAP returns true if the adapter is a TAP adapter (Layer 2) or false if it
+// is a TUN adapter (Layer 3).
+func (tun *TunAdapter) IsTAP() bool {
+ return tun.iface.IsTAP()
+}
+
+// DefaultName gets the default TUN/TAP interface name for your platform.
+func DefaultName() string {
+ return defaults.GetDefaults().DefaultIfName
+}
+
+// DefaultMTU gets the default TUN/TAP interface MTU for your platform. This can
+// be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
+func DefaultMTU() int {
+ return defaults.GetDefaults().DefaultIfMTU
+}
+
+// DefaultIsTAP returns true if the default adapter mode for the current
+// platform is TAP (Layer 2) and returns false for TUN (Layer 3).
+func DefaultIsTAP() bool {
+ return defaults.GetDefaults().DefaultIfTAPMode
+}
+
+// MaximumMTU returns the maximum supported TUN/TAP interface MTU for your
+// platform. This can be as high as 65535, depending on platform, but is never
+// lower than 1280.
+func MaximumMTU() int {
+ return defaults.GetDefaults().MaximumIfMTU
+}
+
+// Init initialises the TUN/TAP 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(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) {
+ tun.config = config
+ tun.log = log
+ tun.listener = listener
+ tun.dialer = dialer
+ tun.addrToConn = make(map[address.Address]*tunConn)
+ tun.subnetToConn = make(map[address.Subnet]*tunConn)
+ tun.dials = make(map[crypto.NodeID][][]byte)
+}
+
+// Start the setup process for the TUN/TAP adapter. If successful, starts the
+// read/write goroutines to handle packets on that interface.
+func (tun *TunAdapter) Start() error {
+ current := tun.config.GetCurrent()
+ if tun.config == nil || tun.listener == nil || tun.dialer == nil {
+ return errors.New("No configuration available to TUN/TAP")
+ }
+ var boxPub crypto.BoxPubKey
+ boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey)
+ if err != nil {
+ return err
+ }
+ copy(boxPub[:], boxPubHex)
+ nodeID := crypto.GetNodeID(&boxPub)
+ tun.addr = *address.AddrForNodeID(nodeID)
+ tun.subnet = *address.SubnetForNodeID(nodeID)
+ tun.mtu = current.IfMTU
+ ifname := current.IfName
+ iftapmode := current.IfTAPMode
+ addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1)
+ if ifname != "none" {
+ if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil {
+ return err
+ }
+ }
+ if ifname == "none" || ifname == "dummy" {
+ tun.log.Debugln("Not starting TUN/TAP as ifname is none or dummy")
+ return nil
+ }
+ tun.mutex.Lock()
+ tun.isOpen = true
+ tun.send = make(chan []byte, 32) // TODO: is this a sensible value?
+ tun.reconfigure = make(chan chan error)
+ tun.mutex.Unlock()
+ go func() {
+ for {
+ e := <-tun.reconfigure
+ e <- nil
+ }
+ }()
+ go tun.handler()
+ go tun.reader()
+ go tun.writer()
+ tun.icmpv6.Init(tun)
+ if iftapmode {
+ go tun.icmpv6.Solicit(tun.addr)
+ }
+ tun.ckr.init(tun)
+ return nil
+}
+
+// Start the setup process for the TUN/TAP adapter. If successful, starts the
+// read/write goroutines to handle packets on that interface.
+func (tun *TunAdapter) Stop() error {
+ tun.isOpen = false
+ // TODO: we have nothing that cleanly stops all the various goroutines opened
+ // by TUN/TAP, e.g. readers/writers, sessions
+ tun.iface.Close()
+ return nil
+}
+
+// UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig
+// and then signals the various module goroutines to reconfigure themselves if
+// needed.
+func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) {
+ tun.log.Debugln("Reloading TUN/TAP configuration...")
+
+ tun.config.Replace(*config)
+
+ errors := 0
+
+ components := []chan chan error{
+ tun.reconfigure,
+ tun.ckr.reconfigure,
+ }
+
+ for _, component := range components {
+ response := make(chan error)
+ component <- response
+ if err := <-response; err != nil {
+ tun.log.Errorln(err)
+ errors++
+ }
+ }
+
+ if errors > 0 {
+ tun.log.Warnln(errors, "TUN/TAP module(s) reported errors during configuration reload")
+ } else {
+ tun.log.Infoln("TUN/TAP configuration reloaded successfully")
+ }
+}
+
+func (tun *TunAdapter) handler() error {
+ for {
+ // Accept the incoming connection
+ conn, err := tun.listener.Accept()
+ if err != nil {
+ tun.log.Errorln("TUN/TAP connection accept error:", err)
+ return err
+ }
+ if _, err := tun.wrap(conn); err != nil {
+ // Something went wrong when storing the connection, typically that
+ // something already exists for this address or subnet
+ tun.log.Debugln("TUN/TAP handler wrap:", err)
+ }
+ }
+}
+
+func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) {
+ // Prepare a session wrapper for the given connection
+ s := tunConn{
+ tun: tun,
+ conn: conn,
+ send: make(chan []byte, 32), // TODO: is this a sensible value?
+ stop: make(chan struct{}),
+ alive: make(chan struct{}, 1),
+ }
+ c = &s
+ // Get the remote address and subnet of the other side
+ remoteNodeID := conn.RemoteAddr()
+ s.addr = *address.AddrForNodeID(&remoteNodeID)
+ s.snet = *address.SubnetForNodeID(&remoteNodeID)
+ // Work out if this is already a destination we already know about
+ tun.mutex.Lock()
+ defer tun.mutex.Unlock()
+ atc, aok := tun.addrToConn[s.addr]
+ stc, sok := tun.subnetToConn[s.snet]
+ // If we know about a connection for this destination already then assume it
+ // is no longer valid and close it
+ if aok {
+ atc._close_nomutex()
+ err = errors.New("replaced connection for address")
+ } else if sok {
+ stc._close_nomutex()
+ err = errors.New("replaced connection for subnet")
+ }
+ // Save the session wrapper so that we can look it up quickly next time
+ // we receive a packet through the interface for this address
+ tun.addrToConn[s.addr] = &s
+ tun.subnetToConn[s.snet] = &s
+ // Start the connection goroutines
+ go s.reader()
+ go s.writer()
+ go s.checkForTimeouts()
+ // Return
+ return c, err
+}
diff --git a/src/yggdrasil/tun_bsd.go b/src/tuntap/tun_bsd.go
similarity index 82%
rename from src/yggdrasil/tun_bsd.go
rename to src/tuntap/tun_bsd.go
index 81e2c46c..996f3140 100644
--- a/src/yggdrasil/tun_bsd.go
+++ b/src/tuntap/tun_bsd.go
@@ -1,6 +1,6 @@
// +build openbsd freebsd netbsd
-package yggdrasil
+package tuntap
import (
"encoding/binary"
@@ -77,7 +77,7 @@ type in6_ifreq_lifetime struct {
// a system socket and making syscalls to the kernel. This is not refined though
// and often doesn't work (if at all), therefore if a call fails, it resorts
// to calling "ifconfig" instead.
-func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
+func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if ifname[:4] == "auto" {
ifname = "/dev/tap0"
@@ -103,20 +103,20 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
return tun.setupAddress(addr)
}
-func (tun *tunAdapter) setupAddress(addr string) error {
+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.core.log.Printf("Create AF_INET socket failed: %v.", err)
+ tun.log.Printf("Create AF_INET socket failed: %v.", err)
return err
}
// Friendly output
- tun.core.log.Infof("Interface name: %s", tun.iface.Name())
- tun.core.log.Infof("Interface IPv6: %s", addr)
- tun.core.log.Infof("Interface MTU: %d", tun.mtu)
+ tun.log.Infof("Interface name: %s", tun.iface.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
@@ -126,15 +126,15 @@ func (tun *tunAdapter) setupAddress(addr string) error {
// Set the MTU
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno
- tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno)
+ tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
// Fall back to ifconfig to set the MTU
cmd := exec.Command("ifconfig", tun.iface.Name(), "mtu", string(tun.mtu))
- tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
+ tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
- tun.core.log.Traceln(string(output))
+ tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
+ tun.log.Traceln(string(output))
}
}
@@ -155,15 +155,15 @@ func (tun *tunAdapter) setupAddress(addr string) error {
// 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.core.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
+ tun.log.Errorf("Error in SIOCSIFADDR_IN6: %v", errno)
// Fall back to ifconfig to set the address
cmd := exec.Command("ifconfig", tun.iface.Name(), "inet6", addr)
- tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
+ tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
- tun.core.log.Traceln(string(output))
+ tun.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
+ tun.log.Traceln(string(output))
}
}
diff --git a/src/yggdrasil/tun_darwin.go b/src/tuntap/tun_darwin.go
similarity index 70%
rename from src/yggdrasil/tun_darwin.go
rename to src/tuntap/tun_darwin.go
index 7ec1b8b9..d7b46537 100644
--- a/src/yggdrasil/tun_darwin.go
+++ b/src/tuntap/tun_darwin.go
@@ -1,6 +1,6 @@
// +build !mobile
-package yggdrasil
+package tuntap
// The darwin platform specific tun parts
@@ -16,9 +16,9 @@ import (
)
// Configures the "utun" adapter with the correct IPv6 address and MTU.
-func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
+func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if iftapmode {
- tun.core.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
+ tun.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
}
config := water.Config{DeviceType: water.TUN}
iface, err := water.New(config)
@@ -30,7 +30,12 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
return tun.setupAddress(addr)
}
-const darwin_SIOCAIFADDR_IN6 = 2155899162
+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
+)
type in6_addrlifetime struct {
ia6t_expire float64
@@ -64,12 +69,12 @@ type ifreq struct {
// Sets the IPv6 address of the utun adapter. On Darwin/macOS this is done using
// a system socket and making direct syscalls to the kernel.
-func (tun *tunAdapter) setupAddress(addr string) error {
+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.core.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
+ tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
return err
}
@@ -91,26 +96,29 @@ func (tun *tunAdapter) setupAddress(addr string) error {
ar.ifra_addr.sin6_addr[i] = uint16(binary.BigEndian.Uint16(b))
}
- ar.ifra_lifetime.ia6t_vltime = 0xFFFFFFFF
- ar.ifra_lifetime.ia6t_pltime = 0xFFFFFFFF
+ 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.iface.Name())
ir.ifru_mtu = uint32(tun.mtu)
- tun.core.log.Infof("Interface name: %s", ar.ifra_name)
- tun.core.log.Infof("Interface IPv6: %s", addr)
- tun.core.log.Infof("Interface MTU: %d", ir.ifru_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.core.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", 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.core.log.Errorf("Error in SIOCSIFMTU: %v", errno)
+ tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
return err
}
diff --git a/src/yggdrasil/tun_linux.go b/src/tuntap/tun_linux.go
similarity index 86%
rename from src/yggdrasil/tun_linux.go
rename to src/tuntap/tun_linux.go
index 30ada235..c9c03c09 100644
--- a/src/yggdrasil/tun_linux.go
+++ b/src/tuntap/tun_linux.go
@@ -1,6 +1,6 @@
// +build !mobile
-package yggdrasil
+package tuntap
// The linux platform specific tun parts
@@ -15,7 +15,7 @@ import (
)
// Configures the TAP adapter with the correct IPv6 address and MTU.
-func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
+func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if iftapmode {
config = water.Config{DeviceType: water.TAP}
@@ -40,9 +40,9 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
}
}
// Friendly output
- tun.core.log.Infof("Interface name: %s", tun.iface.Name())
- tun.core.log.Infof("Interface IPv6: %s", addr)
- tun.core.log.Infof("Interface MTU: %d", tun.mtu)
+ tun.log.Infof("Interface name: %s", tun.iface.Name())
+ tun.log.Infof("Interface IPv6: %s", addr)
+ tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
}
@@ -50,7 +50,7 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
// is used to do this, so there is not a hard requirement on "ip" or "ifconfig"
// to exist on the system, but this will fail if Netlink is not present in the
// kernel (it nearly always is).
-func (tun *tunAdapter) setupAddress(addr string) error {
+func (tun *TunAdapter) setupAddress(addr string) error {
// Set address
var netIF *net.Interface
ifces, err := net.Interfaces()
diff --git a/src/yggdrasil/tun_other.go b/src/tuntap/tun_other.go
similarity index 78%
rename from src/yggdrasil/tun_other.go
rename to src/tuntap/tun_other.go
index 07ec25fd..48276b49 100644
--- a/src/yggdrasil/tun_other.go
+++ b/src/tuntap/tun_other.go
@@ -1,6 +1,6 @@
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile
-package yggdrasil
+package tuntap
import water "github.com/yggdrasil-network/water"
@@ -9,7 +9,7 @@ import water "github.com/yggdrasil-network/water"
// Creates the TUN/TAP adapter, if supported by the Water library. Note that
// no guarantees are made at this point on an unsupported platform.
-func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
+func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
var config water.Config
if iftapmode {
config = water.Config{DeviceType: water.TAP}
@@ -27,7 +27,7 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
// We don't know how to set the IPv6 address on an unknown platform, therefore
// write about it to stdout and don't try to do anything further.
-func (tun *tunAdapter) setupAddress(addr string) error {
- tun.core.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
+func (tun *TunAdapter) setupAddress(addr string) error {
+ tun.log.Warnln("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
return nil
}
diff --git a/src/yggdrasil/tun_windows.go b/src/tuntap/tun_windows.go
similarity index 55%
rename from src/yggdrasil/tun_windows.go
rename to src/tuntap/tun_windows.go
index 1c89a437..a826c7ad 100644
--- a/src/yggdrasil/tun_windows.go
+++ b/src/tuntap/tun_windows.go
@@ -1,6 +1,7 @@
-package yggdrasil
+package tuntap
import (
+ "errors"
"fmt"
"os/exec"
"strings"
@@ -13,9 +14,9 @@ import (
// Configures the TAP adapter with the correct IPv6 address and MTU. On Windows
// we don't make use of a direct operating system API to do this - we instead
// delegate the hard work to "netsh".
-func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
+func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if !iftapmode {
- tun.core.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
+ tun.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
}
config := water.Config{DeviceType: water.TAP}
config.PlatformSpecificParams.ComponentID = "tap0901"
@@ -27,23 +28,27 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
}
iface, err := water.New(config)
if err != nil {
- panic(err)
- }
- // Disable/enable the interface to resets its configuration (invalidating iface)
- cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
- tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
- output, err := cmd.CombinedOutput()
- if err != nil {
- tun.core.log.Errorf("Windows netsh failed: %v.", err)
- tun.core.log.Traceln(string(output))
return err
}
+ if iface.Name() == "" {
+ return errors.New("unable to find TAP adapter with component ID " + config.PlatformSpecificParams.ComponentID)
+ }
+ // Reset the adapter - this invalidates iface so we'll need to get a new one
+ cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED")
+ tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ tun.log.Errorln("Windows netsh failed:", err)
+ tun.log.Traceln(string(output))
+ return err
+ }
+ // Bring the interface back up
cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED")
- tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
+ tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err = cmd.CombinedOutput()
if err != nil {
- tun.core.log.Errorf("Windows netsh failed: %v.", err)
- tun.core.log.Traceln(string(output))
+ tun.log.Errorln("Windows netsh failed:", err)
+ tun.log.Traceln(string(output))
return err
}
// Get a new iface
@@ -58,41 +63,47 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
panic(err)
}
// Friendly output
- tun.core.log.Infof("Interface name: %s", tun.iface.Name())
- tun.core.log.Infof("Interface IPv6: %s", addr)
- tun.core.log.Infof("Interface MTU: %d", tun.mtu)
+ tun.log.Infof("Interface name: %s", tun.iface.Name())
+ tun.log.Infof("Interface IPv6: %s", addr)
+ tun.log.Infof("Interface MTU: %d", tun.mtu)
return tun.setupAddress(addr)
}
// Sets the MTU of the TAP adapter.
-func (tun *tunAdapter) setupMTU(mtu int) error {
+func (tun *TunAdapter) setupMTU(mtu int) error {
+ if tun.iface == nil || tun.iface.Name() == "" {
+ return errors.New("Can't configure MTU as TAP adapter is not present")
+ }
// Set MTU
cmd := exec.Command("netsh", "interface", "ipv6", "set", "subinterface",
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("mtu=%d", mtu),
"store=active")
- tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
+ tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Errorf("Windows netsh failed: %v.", err)
- tun.core.log.Traceln(string(output))
+ tun.log.Errorln("Windows netsh failed:", err)
+ tun.log.Traceln(string(output))
return err
}
return nil
}
// Sets the IPv6 address of the TAP adapter.
-func (tun *tunAdapter) setupAddress(addr string) error {
+func (tun *TunAdapter) setupAddress(addr string) error {
+ if tun.iface == nil || tun.iface.Name() == "" {
+ return errors.New("Can't configure IPv6 address as TAP adapter is not present")
+ }
// Set address
cmd := exec.Command("netsh", "interface", "ipv6", "add", "address",
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("addr=%s", addr),
"store=active")
- tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
+ tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Errorf("Windows netsh failed: %v.", err)
- tun.core.log.Traceln(string(output))
+ tun.log.Errorln("Windows netsh failed:", err)
+ tun.log.Traceln(string(output))
return err
}
return nil
diff --git a/src/util/cancellation.go b/src/util/cancellation.go
new file mode 100644
index 00000000..af4721bb
--- /dev/null
+++ b/src/util/cancellation.go
@@ -0,0 +1,90 @@
+package util
+
+import (
+ "errors"
+ "runtime"
+ "sync"
+ "time"
+)
+
+type Cancellation interface {
+ Finished() <-chan struct{}
+ Cancel(error) error
+ Error() error
+}
+
+var CancellationFinalized = errors.New("finalizer called")
+var CancellationTimeoutError = errors.New("timeout")
+
+func CancellationFinalizer(c Cancellation) {
+ c.Cancel(CancellationFinalized)
+}
+
+type cancellation struct {
+ cancel chan struct{}
+ mutex sync.RWMutex
+ err error
+ done bool
+}
+
+func NewCancellation() Cancellation {
+ c := cancellation{
+ cancel: make(chan struct{}),
+ }
+ runtime.SetFinalizer(&c, CancellationFinalizer)
+ return &c
+}
+
+func (c *cancellation) Finished() <-chan struct{} {
+ return c.cancel
+}
+
+func (c *cancellation) Cancel(err error) error {
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+ if c.done {
+ return c.err
+ } else {
+ c.err = err
+ c.done = true
+ close(c.cancel)
+ return nil
+ }
+}
+
+func (c *cancellation) Error() error {
+ c.mutex.RLock()
+ err := c.err
+ c.mutex.RUnlock()
+ return err
+}
+
+func CancellationChild(parent Cancellation) Cancellation {
+ child := NewCancellation()
+ go func() {
+ select {
+ case <-child.Finished():
+ case <-parent.Finished():
+ child.Cancel(parent.Error())
+ }
+ }()
+ return child
+}
+
+func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation {
+ child := CancellationChild(parent)
+ go func() {
+ timer := time.NewTimer(timeout)
+ defer TimerStop(timer)
+ select {
+ case <-child.Finished():
+ case <-timer.C:
+ child.Cancel(CancellationTimeoutError)
+ }
+ }()
+ return child
+}
+
+func CancellationWithDeadline(parent Cancellation, deadline time.Time) Cancellation {
+ return CancellationWithTimeout(parent, deadline.Sub(time.Now()))
+}
diff --git a/src/util/util.go b/src/util/util.go
index 49e0207a..4596474e 100644
--- a/src/util/util.go
+++ b/src/util/util.go
@@ -3,6 +3,7 @@ package util
// These are misc. utility functions that didn't really fit anywhere else
import "runtime"
+import "sync"
import "time"
// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere.
@@ -21,40 +22,37 @@ func UnlockThread() {
}
// This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops.
-// It's used like a sync.Pool, but with a fixed size and typechecked without type casts to/from interface{} (which were making the profiles look ugly).
-var byteStore chan []byte
+var byteStoreMutex sync.Mutex
+var byteStore [][]byte
-func init() {
- byteStore = make(chan []byte, 32)
-}
-
-// Gets an empty slice from the byte store, if one is available, or else returns a new nil slice.
+// Gets an empty slice from the byte store.
func GetBytes() []byte {
- select {
- case bs := <-byteStore:
- return bs[:0]
- default:
+ byteStoreMutex.Lock()
+ defer byteStoreMutex.Unlock()
+ if len(byteStore) > 0 {
+ var bs []byte
+ bs, byteStore = byteStore[len(byteStore)-1][:0], byteStore[:len(byteStore)-1]
+ return bs
+ } else {
return nil
}
}
-// Puts a slice in the store, if there's room, or else returns and lets the slice get collected.
+// Puts a slice in the store.
func PutBytes(bs []byte) {
- select {
- case byteStore <- bs:
- default:
- }
+ byteStoreMutex.Lock()
+ defer byteStoreMutex.Unlock()
+ byteStore = append(byteStore, bs)
}
// This is a workaround to go's broken timer implementation
func TimerStop(t *time.Timer) bool {
- if !t.Stop() {
- select {
- case <-t.C:
- default:
- }
+ stopped := t.Stop()
+ select {
+ case <-t.C:
+ default:
}
- return true
+ return stopped
}
// Run a blocking function with a timeout.
diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go
deleted file mode 100644
index 3ce80d2b..00000000
--- a/src/yggdrasil/adapter.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package yggdrasil
-
-// Defines the minimum required struct members for an adapter type (this is
-// now the base type for tunAdapter in tun.go)
-type Adapter struct {
- core *Core
- send chan<- []byte
- recv <-chan []byte
- reconfigure chan chan error
-}
-
-// Initialises the adapter.
-func (adapter *Adapter) init(core *Core, send chan<- []byte, recv <-chan []byte) {
- adapter.core = core
- adapter.send = send
- adapter.recv = recv
- adapter.reconfigure = make(chan chan error, 1)
-}
diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go
deleted file mode 100644
index a0854f22..00000000
--- a/src/yggdrasil/admin.go
+++ /dev/null
@@ -1,1038 +0,0 @@
-package yggdrasil
-
-import (
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
- "net"
- "net/url"
- "os"
- "sort"
- "strconv"
- "strings"
- "sync/atomic"
- "time"
-
- "github.com/yggdrasil-network/yggdrasil-go/src/address"
- "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
- "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
-)
-
-// TODO: Add authentication
-
-type admin struct {
- core *Core
- reconfigure chan chan error
- listenaddr string
- listener net.Listener
- handlers []admin_handlerInfo
-}
-
-type admin_info map[string]interface{}
-
-type admin_handlerInfo struct {
- name string // Checked against the first word of the api call
- args []string // List of human-readable argument names
- handler func(admin_info) (admin_info, error) // First is input map, second is output
-}
-
-// admin_pair maps things like "IP", "port", "bucket", or "coords" onto values.
-type admin_pair struct {
- key string
- val interface{}
-}
-
-// admin_nodeInfo represents the information we know about a node for an admin response.
-type admin_nodeInfo []admin_pair
-
-// addHandler is called for each admin function to add the handler and help documentation to the API.
-func (a *admin) addHandler(name string, args []string, handler func(admin_info) (admin_info, error)) {
- a.handlers = append(a.handlers, admin_handlerInfo{name, args, handler})
-}
-
-// init runs the initial admin setup.
-func (a *admin) init(c *Core) {
- a.core = c
- a.reconfigure = make(chan chan error, 1)
- go func() {
- for {
- e := <-a.reconfigure
- a.core.configMutex.RLock()
- if a.core.config.AdminListen != a.core.configOld.AdminListen {
- a.listenaddr = a.core.config.AdminListen
- a.close()
- a.start()
- }
- a.core.configMutex.RUnlock()
- e <- nil
- }
- }()
- a.core.configMutex.RLock()
- a.listenaddr = a.core.config.AdminListen
- a.core.configMutex.RUnlock()
- a.addHandler("list", []string{}, func(in admin_info) (admin_info, error) {
- handlers := make(map[string]interface{})
- for _, handler := range a.handlers {
- handlers[handler.name] = admin_info{"fields": handler.args}
- }
- return admin_info{"list": handlers}, nil
- })
- a.addHandler("dot", []string{}, func(in admin_info) (admin_info, error) {
- return admin_info{"dot": string(a.getResponse_dot())}, nil
- })
- a.addHandler("getSelf", []string{}, func(in admin_info) (admin_info, error) {
- self := a.getData_getSelf().asMap()
- ip := fmt.Sprint(self["ip"])
- delete(self, "ip")
- return admin_info{"self": admin_info{ip: self}}, nil
- })
- a.addHandler("getPeers", []string{}, func(in admin_info) (admin_info, error) {
- sort := "ip"
- peers := make(admin_info)
- for _, peerdata := range a.getData_getPeers() {
- p := peerdata.asMap()
- so := fmt.Sprint(p[sort])
- peers[so] = p
- delete(peers[so].(map[string]interface{}), sort)
- }
- return admin_info{"peers": peers}, nil
- })
- a.addHandler("getSwitchPeers", []string{}, func(in admin_info) (admin_info, error) {
- sort := "port"
- switchpeers := make(admin_info)
- for _, s := range a.getData_getSwitchPeers() {
- p := s.asMap()
- so := fmt.Sprint(p[sort])
- switchpeers[so] = p
- delete(switchpeers[so].(map[string]interface{}), sort)
- }
- return admin_info{"switchpeers": switchpeers}, nil
- })
- a.addHandler("getSwitchQueues", []string{}, func(in admin_info) (admin_info, error) {
- queues := a.getData_getSwitchQueues()
- return admin_info{"switchqueues": queues.asMap()}, nil
- })
- a.addHandler("getDHT", []string{}, func(in admin_info) (admin_info, error) {
- sort := "ip"
- dht := make(admin_info)
- for _, d := range a.getData_getDHT() {
- p := d.asMap()
- so := fmt.Sprint(p[sort])
- dht[so] = p
- delete(dht[so].(map[string]interface{}), sort)
- }
- return admin_info{"dht": dht}, nil
- })
- a.addHandler("getSessions", []string{}, func(in admin_info) (admin_info, error) {
- sort := "ip"
- sessions := make(admin_info)
- for _, s := range a.getData_getSessions() {
- p := s.asMap()
- so := fmt.Sprint(p[sort])
- sessions[so] = p
- delete(sessions[so].(map[string]interface{}), sort)
- }
- return admin_info{"sessions": sessions}, nil
- })
- a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) {
- // Set sane defaults
- intf := ""
- // Has interface been specified?
- if itf, ok := in["interface"]; ok {
- intf = itf.(string)
- }
- if a.addPeer(in["uri"].(string), intf) == nil {
- return admin_info{
- "added": []string{
- in["uri"].(string),
- },
- }, nil
- } else {
- return admin_info{
- "not_added": []string{
- in["uri"].(string),
- },
- }, errors.New("Failed to add peer")
- }
- })
- a.addHandler("removePeer", []string{"port"}, func(in admin_info) (admin_info, error) {
- if a.removePeer(fmt.Sprint(in["port"])) == nil {
- return admin_info{
- "removed": []string{
- fmt.Sprint(in["port"]),
- },
- }, nil
- } else {
- return admin_info{
- "not_removed": []string{
- fmt.Sprint(in["port"]),
- },
- }, errors.New("Failed to remove peer")
- }
- })
- a.addHandler("getTunTap", []string{}, func(in admin_info) (r admin_info, e error) {
- defer func() {
- if err := recover(); err != nil {
- r = admin_info{"none": admin_info{}}
- e = nil
- }
- }()
-
- return admin_info{
- a.core.router.tun.iface.Name(): admin_info{
- "tap_mode": a.core.router.tun.iface.IsTAP(),
- "mtu": a.core.router.tun.mtu,
- },
- }, nil
- })
- a.addHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in admin_info) (admin_info, error) {
- // Set sane defaults
- iftapmode := defaults.GetDefaults().DefaultIfTAPMode
- ifmtu := defaults.GetDefaults().DefaultIfMTU
- // Has TAP mode been specified?
- if tap, ok := in["tap_mode"]; ok {
- iftapmode = tap.(bool)
- }
- // Check we have enough params for MTU
- if mtu, ok := in["mtu"]; ok {
- if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU {
- ifmtu = int(in["mtu"].(float64))
- }
- }
- // Start the TUN adapter
- if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil {
- return admin_info{}, errors.New("Failed to configure adapter")
- } else {
- return admin_info{
- a.core.router.tun.iface.Name(): admin_info{
- "tap_mode": a.core.router.tun.iface.IsTAP(),
- "mtu": ifmtu,
- },
- }, nil
- }
- })
- a.addHandler("getMulticastInterfaces", []string{}, func(in admin_info) (admin_info, error) {
- var intfs []string
- for _, v := range a.core.multicast.interfaces() {
- intfs = append(intfs, v.Name)
- }
- return admin_info{"multicast_interfaces": intfs}, nil
- })
- a.addHandler("getAllowedEncryptionPublicKeys", []string{}, func(in admin_info) (admin_info, error) {
- return admin_info{"allowed_box_pubs": a.getAllowedEncryptionPublicKeys()}, nil
- })
- a.addHandler("addAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) {
- if a.addAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
- return admin_info{
- "added": []string{
- in["box_pub_key"].(string),
- },
- }, nil
- } else {
- return admin_info{
- "not_added": []string{
- in["box_pub_key"].(string),
- },
- }, errors.New("Failed to add allowed key")
- }
- })
- a.addHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in admin_info) (admin_info, error) {
- if a.removeAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil {
- return admin_info{
- "removed": []string{
- in["box_pub_key"].(string),
- },
- }, nil
- } else {
- return admin_info{
- "not_removed": []string{
- in["box_pub_key"].(string),
- },
- }, errors.New("Failed to remove allowed key")
- }
- })
- a.addHandler("getTunnelRouting", []string{}, func(in admin_info) (admin_info, error) {
- enabled := false
- a.core.router.doAdmin(func() {
- enabled = a.core.router.cryptokey.isEnabled()
- })
- return admin_info{"enabled": enabled}, nil
- })
- a.addHandler("setTunnelRouting", []string{"enabled"}, func(in admin_info) (admin_info, error) {
- enabled := false
- if e, ok := in["enabled"].(bool); ok {
- enabled = e
- }
- a.core.router.doAdmin(func() {
- a.core.router.cryptokey.setEnabled(enabled)
- })
- return admin_info{"enabled": enabled}, nil
- })
- a.addHandler("addSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) {
- var err error
- a.core.router.doAdmin(func() {
- err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string))
- })
- if err == nil {
- return admin_info{"added": []string{in["subnet"].(string)}}, nil
- } else {
- return admin_info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet")
- }
- })
- a.addHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) {
- var err error
- a.core.router.doAdmin(func() {
- err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string))
- })
- if err == nil {
- return admin_info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
- } else {
- return admin_info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route")
- }
- })
- a.addHandler("getSourceSubnets", []string{}, func(in admin_info) (admin_info, error) {
- var subnets []string
- a.core.router.doAdmin(func() {
- getSourceSubnets := func(snets []net.IPNet) {
- for _, subnet := range snets {
- subnets = append(subnets, subnet.String())
- }
- }
- getSourceSubnets(a.core.router.cryptokey.ipv4sources)
- getSourceSubnets(a.core.router.cryptokey.ipv6sources)
- })
- return admin_info{"source_subnets": subnets}, nil
- })
- a.addHandler("getRoutes", []string{}, func(in admin_info) (admin_info, error) {
- routes := make(admin_info)
- a.core.router.doAdmin(func() {
- getRoutes := func(ckrs []cryptokey_route) {
- for _, ckr := range ckrs {
- routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:])
- }
- }
- getRoutes(a.core.router.cryptokey.ipv4routes)
- getRoutes(a.core.router.cryptokey.ipv6routes)
- })
- return admin_info{"routes": routes}, nil
- })
- a.addHandler("removeSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) {
- var err error
- a.core.router.doAdmin(func() {
- err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string))
- })
- if err == nil {
- return admin_info{"removed": []string{in["subnet"].(string)}}, nil
- } else {
- return admin_info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet")
- }
- })
- a.addHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin_info) (admin_info, error) {
- var err error
- a.core.router.doAdmin(func() {
- err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string))
- })
- if err == nil {
- return admin_info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil
- } else {
- return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route")
- }
- })
- a.addHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in admin_info) (admin_info, error) {
- if in["target"] == nil {
- in["target"] = "none"
- }
- result, err := a.admin_dhtPing(in["box_pub_key"].(string), in["coords"].(string), in["target"].(string))
- if err == nil {
- infos := make(map[string]map[string]string, len(result.Infos))
- for _, dinfo := range result.Infos {
- info := map[string]string{
- "box_pub_key": hex.EncodeToString(dinfo.key[:]),
- "coords": fmt.Sprintf("%v", dinfo.coords),
- }
- addr := net.IP(address.AddrForNodeID(crypto.GetNodeID(&dinfo.key))[:]).String()
- infos[addr] = info
- }
- return admin_info{"nodes": infos}, nil
- } else {
- return admin_info{}, err
- }
- })
- a.addHandler("getNodeInfo", []string{"[box_pub_key]", "[coords]", "[nocache]"}, func(in admin_info) (admin_info, error) {
- var nocache bool
- if in["nocache"] != nil {
- nocache = in["nocache"].(string) == "true"
- }
- var box_pub_key, coords string
- if in["box_pub_key"] == nil && in["coords"] == nil {
- var nodeinfo []byte
- a.core.router.doAdmin(func() {
- nodeinfo = []byte(a.core.router.nodeinfo.getNodeInfo())
- })
- var jsoninfo interface{}
- if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil {
- return admin_info{}, err
- } else {
- return admin_info{"nodeinfo": jsoninfo}, nil
- }
- } else if in["box_pub_key"] == nil || in["coords"] == nil {
- return admin_info{}, errors.New("Expecting both box_pub_key and coords")
- } else {
- box_pub_key = in["box_pub_key"].(string)
- coords = in["coords"].(string)
- }
- result, err := a.admin_getNodeInfo(box_pub_key, coords, nocache)
- if err == nil {
- var m map[string]interface{}
- if err = json.Unmarshal(result, &m); err == nil {
- return admin_info{"nodeinfo": m}, nil
- } else {
- return admin_info{}, err
- }
- } else {
- return admin_info{}, err
- }
- })
-}
-
-// start runs the admin API socket to listen for / respond to admin API calls.
-func (a *admin) start() error {
- if a.listenaddr != "none" && a.listenaddr != "" {
- go a.listen()
- }
- return nil
-}
-
-// cleans up when stopping
-func (a *admin) close() error {
- if a.listener != nil {
- return a.listener.Close()
- } else {
- return nil
- }
-}
-
-// listen is run by start and manages API connections.
-func (a *admin) 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.core.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.core.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.core.log.Debugln(a.listenaddr[7:], "was cleaned up")
- } else {
- a.core.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.core.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.core.log.Errorf("Admin socket failed to listen: %v", err)
- os.Exit(1)
- }
- a.core.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)
- }
- }
-}
-
-// handleRequest calls the request handler for each request sent to the admin API.
-func (a *admin) handleRequest(conn net.Conn) {
- decoder := json.NewDecoder(conn)
- encoder := json.NewEncoder(conn)
- encoder.SetIndent("", " ")
- recv := make(admin_info)
- send := make(admin_info)
-
- defer func() {
- r := recover()
- if r != nil {
- send = admin_info{
- "status": "error",
- "error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax",
- }
- a.core.log.Errorln("Admin socket error:", r)
- if err := encoder.Encode(&send); err != nil {
- a.core.log.Errorln("Admin socket JSON encode error:", err)
- }
- conn.Close()
- }
- }()
-
- for {
- // Start with a clean slate on each request
- recv = admin_info{}
- send = admin_info{}
-
- // Decode the input
- if err := decoder.Decode(&recv); err != nil {
- // fmt.Println("Admin socket JSON decode error:", err)
- return
- }
-
- // Send the request back with the response, and default to "error"
- // unless the status is changed below by one of the handlers
- send["request"] = recv
- send["status"] = "error"
-
- handlers:
- for _, handler := range a.handlers {
- // We've found the handler that matches the request
- if strings.ToLower(recv["request"].(string)) == strings.ToLower(handler.name) {
- // Check that we have all the required arguments
- for _, arg := range handler.args {
- // An argument in [square brackets] is optional and not required,
- // so we can safely ignore those
- if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
- continue
- }
- // Check if the field is missing
- if _, ok := recv[arg]; !ok {
- send = admin_info{
- "status": "error",
- "error": "Expected field missing: " + arg,
- "expecting": arg,
- }
- break handlers
- }
- }
-
- // By this point we should have all the fields we need, so call
- // the handler
- response, err := handler.handler(recv)
- if err != nil {
- send["error"] = err.Error()
- if response != nil {
- send["response"] = response
- }
- } else {
- send["status"] = "success"
- if response != nil {
- send["response"] = response
- }
- }
-
- break
- }
- }
-
- // Send the response back
- if err := encoder.Encode(&send); err != nil {
- return
- }
-
- // If "keepalive" isn't true then close the connection
- if keepalive, ok := recv["keepalive"]; !ok || !keepalive.(bool) {
- conn.Close()
- }
- }
-}
-
-// asMap converts an admin_nodeInfo into a map of key/value pairs.
-func (n *admin_nodeInfo) asMap() map[string]interface{} {
- m := make(map[string]interface{}, len(*n))
- for _, p := range *n {
- m[p.key] = p.val
- }
- return m
-}
-
-// toString creates a printable string representation of an admin_nodeInfo.
-func (n *admin_nodeInfo) toString() string {
- // TODO return something nicer looking than this
- var out []string
- for _, p := range *n {
- out = append(out, fmt.Sprintf("%v: %v", p.key, p.val))
- }
- return strings.Join(out, ", ")
-}
-
-// printInfos returns a newline separated list of strings from admin_nodeInfos, e.g. a printable string of info about all peers.
-func (a *admin) printInfos(infos []admin_nodeInfo) string {
- var out []string
- for _, info := range infos {
- out = append(out, info.toString())
- }
- out = append(out, "") // To add a trailing "\n" in the join
- return strings.Join(out, "\n")
-}
-
-// addPeer triggers a connection attempt to a node.
-func (a *admin) addPeer(addr string, sintf string) error {
- err := a.core.link.call(addr, sintf)
- if err != nil {
- return err
- }
- return nil
-}
-
-// removePeer disconnects an existing node (given by the node's port number).
-func (a *admin) removePeer(p string) error {
- iport, err := strconv.Atoi(p)
- if err != nil {
- return err
- }
- a.core.peers.removePeer(switchPort(iport))
- return nil
-}
-
-// startTunWithMTU creates the tun/tap device, sets its address, and sets the MTU to the provided value.
-func (a *admin) startTunWithMTU(ifname string, iftapmode bool, ifmtu int) error {
- // Close the TUN first if open
- _ = a.core.router.tun.close()
- // Then reconfigure and start it
- addr := a.core.router.addr
- straddr := fmt.Sprintf("%s/%v", net.IP(addr[:]).String(), 8*len(address.GetPrefix())-1)
- if ifname != "none" {
- err := a.core.router.tun.setup(ifname, iftapmode, straddr, ifmtu)
- if err != nil {
- return err
- }
- // If we have open sessions then we need to notify them
- // that our MTU has now changed
- for _, sinfo := range a.core.sessions.sinfos {
- if ifname == "none" {
- sinfo.myMTU = 0
- } else {
- sinfo.myMTU = uint16(ifmtu)
- }
- a.core.sessions.sendPingPong(sinfo, false)
- }
- // Aaaaand... go!
- go a.core.router.tun.read()
- }
- go a.core.router.tun.write()
- return nil
-}
-
-// getData_getSelf returns the self node's info for admin responses.
-func (a *admin) getData_getSelf() *admin_nodeInfo {
- table := a.core.switchTable.table.Load().(lookupTable)
- coords := table.self.getCoords()
- self := admin_nodeInfo{
- {"box_pub_key", hex.EncodeToString(a.core.boxPub[:])},
- {"ip", a.core.GetAddress().String()},
- {"subnet", a.core.GetSubnet().String()},
- {"coords", fmt.Sprint(coords)},
- }
- if name := GetBuildName(); name != "unknown" {
- self = append(self, admin_pair{"build_name", name})
- }
- if version := GetBuildVersion(); version != "unknown" {
- self = append(self, admin_pair{"build_version", version})
- }
-
- return &self
-}
-
-// getData_getPeers returns info from Core.peers for an admin response.
-func (a *admin) getData_getPeers() []admin_nodeInfo {
- ports := a.core.peers.ports.Load().(map[switchPort]*peer)
- var peerInfos []admin_nodeInfo
- var ps []switchPort
- for port := range ports {
- ps = append(ps, port)
- }
- sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] })
- for _, port := range ps {
- p := ports[port]
- addr := *address.AddrForNodeID(crypto.GetNodeID(&p.box))
- info := admin_nodeInfo{
- {"ip", net.IP(addr[:]).String()},
- {"port", port},
- {"uptime", int(time.Since(p.firstSeen).Seconds())},
- {"bytes_sent", atomic.LoadUint64(&p.bytesSent)},
- {"bytes_recvd", atomic.LoadUint64(&p.bytesRecvd)},
- {"proto", p.intf.info.linkType},
- {"endpoint", p.intf.name},
- {"box_pub_key", hex.EncodeToString(p.box[:])},
- }
- peerInfos = append(peerInfos, info)
- }
- return peerInfos
-}
-
-// getData_getSwitchPeers returns info from Core.switchTable for an admin response.
-func (a *admin) getData_getSwitchPeers() []admin_nodeInfo {
- var peerInfos []admin_nodeInfo
- table := a.core.switchTable.table.Load().(lookupTable)
- peers := a.core.peers.ports.Load().(map[switchPort]*peer)
- for _, elem := range table.elems {
- peer, isIn := peers[elem.port]
- if !isIn {
- continue
- }
- addr := *address.AddrForNodeID(crypto.GetNodeID(&peer.box))
- coords := elem.locator.getCoords()
- info := admin_nodeInfo{
- {"ip", net.IP(addr[:]).String()},
- {"coords", fmt.Sprint(coords)},
- {"port", elem.port},
- {"bytes_sent", atomic.LoadUint64(&peer.bytesSent)},
- {"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)},
- {"proto", peer.intf.info.linkType},
- {"endpoint", peer.intf.info.remote},
- {"box_pub_key", hex.EncodeToString(peer.box[:])},
- }
- peerInfos = append(peerInfos, info)
- }
- return peerInfos
-}
-
-// getData_getSwitchQueues returns info from Core.switchTable for an queue data.
-func (a *admin) getData_getSwitchQueues() admin_nodeInfo {
- var peerInfos admin_nodeInfo
- switchTable := &a.core.switchTable
- getSwitchQueues := func() {
- queues := make([]map[string]interface{}, 0)
- for k, v := range switchTable.queues.bufs {
- nexthop := switchTable.bestPortForCoords([]byte(k))
- queue := map[string]interface{}{
- "queue_id": k,
- "queue_size": v.size,
- "queue_packets": len(v.packets),
- "queue_port": nexthop,
- }
- queues = append(queues, queue)
- }
- peerInfos = admin_nodeInfo{
- {"queues", queues},
- {"queues_count", len(switchTable.queues.bufs)},
- {"queues_size", switchTable.queues.size},
- {"highest_queues_count", switchTable.queues.maxbufs},
- {"highest_queues_size", switchTable.queues.maxsize},
- {"maximum_queues_size", switchTable.queueTotalMaxSize},
- }
- }
- a.core.switchTable.doAdmin(getSwitchQueues)
- return peerInfos
-}
-
-// getData_getDHT returns info from Core.dht for an admin response.
-func (a *admin) getData_getDHT() []admin_nodeInfo {
- var infos []admin_nodeInfo
- getDHT := func() {
- now := time.Now()
- var dhtInfos []*dhtInfo
- for _, v := range a.core.dht.table {
- dhtInfos = append(dhtInfos, v)
- }
- sort.SliceStable(dhtInfos, func(i, j int) bool {
- return dht_ordered(&a.core.dht.nodeID, dhtInfos[i].getNodeID(), dhtInfos[j].getNodeID())
- })
- for _, v := range dhtInfos {
- addr := *address.AddrForNodeID(v.getNodeID())
- info := admin_nodeInfo{
- {"ip", net.IP(addr[:]).String()},
- {"coords", fmt.Sprint(v.coords)},
- {"last_seen", int(now.Sub(v.recv).Seconds())},
- {"box_pub_key", hex.EncodeToString(v.key[:])},
- }
- infos = append(infos, info)
- }
- }
- a.core.router.doAdmin(getDHT)
- return infos
-}
-
-// getData_getSessions returns info from Core.sessions for an admin response.
-func (a *admin) getData_getSessions() []admin_nodeInfo {
- var infos []admin_nodeInfo
- getSessions := func() {
- for _, sinfo := range a.core.sessions.sinfos {
- // TODO? skipped known but timed out sessions?
- info := admin_nodeInfo{
- {"ip", net.IP(sinfo.theirAddr[:]).String()},
- {"coords", fmt.Sprint(sinfo.coords)},
- {"mtu", sinfo.getMTU()},
- {"was_mtu_fixed", sinfo.wasMTUFixed},
- {"bytes_sent", sinfo.bytesSent},
- {"bytes_recvd", sinfo.bytesRecvd},
- {"box_pub_key", hex.EncodeToString(sinfo.theirPermPub[:])},
- }
- infos = append(infos, info)
- }
- }
- a.core.router.doAdmin(getSessions)
- return infos
-}
-
-// getAllowedEncryptionPublicKeys returns the public keys permitted for incoming peer connections.
-func (a *admin) getAllowedEncryptionPublicKeys() []string {
- return a.core.peers.getAllowedEncryptionPublicKeys()
-}
-
-// addAllowedEncryptionPublicKey whitelists a key for incoming peer connections.
-func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) {
- a.core.peers.addAllowedEncryptionPublicKey(bstr)
- return nil
-}
-
-// removeAllowedEncryptionPublicKey removes a key from the whitelist for incoming peer connections.
-// If none are set, an empty list permits all incoming connections.
-func (a *admin) removeAllowedEncryptionPublicKey(bstr string) (err error) {
- a.core.peers.removeAllowedEncryptionPublicKey(bstr)
- return nil
-}
-
-// Send a DHT ping to the node with the provided key and coords, optionally looking up the specified target NodeID.
-func (a *admin) admin_dhtPing(keyString, coordString, targetString string) (dhtRes, error) {
- var key crypto.BoxPubKey
- if keyBytes, err := hex.DecodeString(keyString); err != nil {
- return dhtRes{}, err
- } else {
- copy(key[:], keyBytes)
- }
- var coords []byte
- for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
- if cstr == "" {
- // Special case, happens if trimmed is the empty string, e.g. this is the root
- continue
- }
- if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
- return dhtRes{}, err
- } else {
- coords = append(coords, uint8(u64))
- }
- }
- resCh := make(chan *dhtRes, 1)
- info := dhtInfo{
- key: key,
- coords: coords,
- }
- target := *info.getNodeID()
- if targetString == "none" {
- // Leave the default target in place
- } else if targetBytes, err := hex.DecodeString(targetString); err != nil {
- return dhtRes{}, err
- } else if len(targetBytes) != len(target) {
- return dhtRes{}, errors.New("Incorrect target NodeID length")
- } else {
- var target crypto.NodeID
- copy(target[:], targetBytes)
- }
- rq := dhtReqKey{info.key, target}
- sendPing := func() {
- a.core.dht.addCallback(&rq, func(res *dhtRes) {
- defer func() { recover() }()
- select {
- case resCh <- res:
- default:
- }
- })
- a.core.dht.ping(&info, &target)
- }
- a.core.router.doAdmin(sendPing)
- go func() {
- time.Sleep(6 * time.Second)
- close(resCh)
- }()
- for res := range resCh {
- return *res, nil
- }
- return dhtRes{}, errors.New(fmt.Sprintf("DHT ping timeout: %s", keyString))
-}
-
-func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (nodeinfoPayload, error) {
- var key crypto.BoxPubKey
- if keyBytes, err := hex.DecodeString(keyString); err != nil {
- return nodeinfoPayload{}, err
- } else {
- copy(key[:], keyBytes)
- }
- if !nocache {
- if response, err := a.core.router.nodeinfo.getCachedNodeInfo(key); err == nil {
- return response, nil
- }
- }
- var coords []byte
- for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
- if cstr == "" {
- // Special case, happens if trimmed is the empty string, e.g. this is the root
- continue
- }
- if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
- return nodeinfoPayload{}, err
- } else {
- coords = append(coords, uint8(u64))
- }
- }
- response := make(chan *nodeinfoPayload, 1)
- sendNodeInfoRequest := func() {
- a.core.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) {
- defer func() { recover() }()
- select {
- case response <- nodeinfo:
- default:
- }
- })
- a.core.router.nodeinfo.sendNodeInfo(key, coords, false)
- }
- a.core.router.doAdmin(sendNodeInfoRequest)
- go func() {
- time.Sleep(6 * time.Second)
- close(response)
- }()
- for res := range response {
- return *res, nil
- }
- return nodeinfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString))
-}
-
-// getResponse_dot returns a response for a graphviz dot formatted representation of the known parts of the network.
-// This is color-coded and labeled, and includes the self node, switch peers, nodes known to the DHT, and nodes with open sessions.
-// The graph is structured as a tree with directed links leading away from the root.
-func (a *admin) getResponse_dot() []byte {
- self := a.getData_getSelf()
- peers := a.getData_getSwitchPeers()
- dht := a.getData_getDHT()
- sessions := a.getData_getSessions()
- // Start building a tree from all known nodes
- type nodeInfo struct {
- name string
- key string
- parent string
- port switchPort
- options string
- }
- infos := make(map[string]nodeInfo)
- // Get coords as a slice of strings, FIXME? this looks very fragile
- coordSlice := func(coords string) []string {
- tmp := strings.Replace(coords, "[", "", -1)
- tmp = strings.Replace(tmp, "]", "", -1)
- return strings.Split(tmp, " ")
- }
- // First fill the tree with all known nodes, no parents
- addInfo := func(nodes []admin_nodeInfo, options string, tag string) {
- for _, node := range nodes {
- n := node.asMap()
- info := nodeInfo{
- key: n["coords"].(string),
- options: options,
- }
- if len(tag) > 0 {
- info.name = fmt.Sprintf("%s\n%s", n["ip"].(string), tag)
- } else {
- info.name = n["ip"].(string)
- }
- coordsSplit := coordSlice(info.key)
- if len(coordsSplit) != 0 {
- portStr := coordsSplit[len(coordsSplit)-1]
- portUint, err := strconv.ParseUint(portStr, 10, 64)
- if err == nil {
- info.port = switchPort(portUint)
- }
- }
- infos[info.key] = info
- }
- }
- addInfo(dht, "fillcolor=\"#ffffff\" style=filled fontname=\"sans serif\"", "Known in DHT") // white
- addInfo(sessions, "fillcolor=\"#acf3fd\" style=filled fontname=\"sans serif\"", "Open session") // blue
- addInfo(peers, "fillcolor=\"#ffffb5\" style=filled fontname=\"sans serif\"", "Connected peer") // yellow
- addInfo(append([]admin_nodeInfo(nil), *self), "fillcolor=\"#a5ff8a\" style=filled fontname=\"sans serif\"", "This node") // green
- // Now go through and create placeholders for any missing nodes
- for _, info := range infos {
- // This is ugly string manipulation
- coordsSplit := coordSlice(info.key)
- for idx := range coordsSplit {
- key := fmt.Sprintf("[%v]", strings.Join(coordsSplit[:idx], " "))
- newInfo, isIn := infos[key]
- if isIn {
- continue
- }
- newInfo.name = "?"
- newInfo.key = key
- newInfo.options = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
-
- coordsSplit := coordSlice(newInfo.key)
- if len(coordsSplit) != 0 {
- portStr := coordsSplit[len(coordsSplit)-1]
- portUint, err := strconv.ParseUint(portStr, 10, 64)
- if err == nil {
- newInfo.port = switchPort(portUint)
- }
- }
-
- infos[key] = newInfo
- }
- }
- // Now go through and attach parents
- for _, info := range infos {
- pSplit := coordSlice(info.key)
- if len(pSplit) > 0 {
- pSplit = pSplit[:len(pSplit)-1]
- }
- info.parent = fmt.Sprintf("[%v]", strings.Join(pSplit, " "))
- infos[info.key] = info
- }
- // Finally, get a sorted list of keys, which we use to organize the output
- var keys []string
- for _, info := range infos {
- keys = append(keys, info.key)
- }
- // sort
- sort.SliceStable(keys, func(i, j int) bool {
- return keys[i] < keys[j]
- })
- sort.SliceStable(keys, func(i, j int) bool {
- return infos[keys[i]].port < infos[keys[j]].port
- })
- // Now print it all out
- var out []byte
- put := func(s string) {
- out = append(out, []byte(s)...)
- }
- put("digraph {\n")
- // First set the labels
- for _, key := range keys {
- info := infos[key]
- put(fmt.Sprintf("\"%v\" [ label = \"%v\" %v ];\n", info.key, info.name, info.options))
- }
- // Then print the tree structure
- for _, key := range keys {
- info := infos[key]
- if info.key == info.parent {
- continue
- } // happens for the root, skip it
- port := fmt.Sprint(info.port)
- style := "fontname=\"sans serif\""
- if infos[info.parent].name == "?" || infos[info.key].name == "?" {
- style = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\""
- }
- put(fmt.Sprintf(" \"%+v\" -> \"%+v\" [ label = \"%v\" %s ];\n", info.parent, info.key, port, style))
- }
- put("}\n")
- return out
-}
diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go
new file mode 100644
index 00000000..014c370f
--- /dev/null
+++ b/src/yggdrasil/api.go
@@ -0,0 +1,540 @@
+package yggdrasil
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "net"
+ "sort"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/gologme/log"
+ "github.com/yggdrasil-network/yggdrasil-go/src/address"
+ "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
+)
+
+// Peer represents a single peer object. This contains information from the
+// preferred switch port for this peer, although there may be more than one in
+// reality.
+type Peer struct {
+ PublicKey crypto.BoxPubKey
+ Endpoint string
+ BytesSent uint64
+ BytesRecvd uint64
+ Protocol string
+ Port uint64
+ Uptime time.Duration
+}
+
+// SwitchPeer represents a switch connection to a peer. Note that there may be
+// multiple switch peers per actual peer, e.g. if there are multiple connections
+// to a given node.
+type SwitchPeer struct {
+ PublicKey crypto.BoxPubKey
+ Coords []byte
+ BytesSent uint64
+ BytesRecvd uint64
+ Port uint64
+ Protocol string
+ Endpoint string
+}
+
+// DHTEntry represents a single DHT entry that has been learned or cached from
+// DHT searches.
+type DHTEntry struct {
+ PublicKey crypto.BoxPubKey
+ Coords []byte
+ LastSeen time.Duration
+}
+
+// DHTRes represents a DHT response, as returned by DHTPing.
+type DHTRes struct {
+ PublicKey crypto.BoxPubKey // key of the sender
+ Coords []byte // coords of the sender
+ Dest crypto.NodeID // the destination node ID
+ Infos []DHTEntry // response
+}
+
+// NodeInfoPayload represents a RequestNodeInfo response, in bytes.
+type NodeInfoPayload []byte
+
+// SwitchQueues represents information from the switch related to link
+// congestion and a list of switch queues created in response to congestion on a
+// given link.
+type SwitchQueues struct {
+ Queues []SwitchQueue
+ Count uint64
+ Size uint64
+ HighestCount uint64
+ HighestSize uint64
+ MaximumSize uint64
+}
+
+// SwitchQueue represents a single switch queue, which is created in response
+// to congestion on a given link.
+type SwitchQueue struct {
+ ID string
+ Size uint64
+ Packets uint64
+ Port uint64
+}
+
+// Session represents an open session with another node.
+type Session struct {
+ PublicKey crypto.BoxPubKey
+ Coords []byte
+ BytesSent uint64
+ BytesRecvd uint64
+ MTU uint16
+ Uptime time.Duration
+ WasMTUFixed bool
+}
+
+// GetPeers returns one or more Peer objects containing information about active
+// peerings with other Yggdrasil nodes, where one of the responses always
+// includes information about the current node (with a port number of 0). If
+// there is exactly one entry then this node is not connected to any other nodes
+// and is therefore isolated.
+func (c *Core) GetPeers() []Peer {
+ ports := c.peers.ports.Load().(map[switchPort]*peer)
+ var peers []Peer
+ var ps []switchPort
+ for port := range ports {
+ ps = append(ps, port)
+ }
+ sort.Slice(ps, func(i, j int) bool { return ps[i] < ps[j] })
+ for _, port := range ps {
+ p := ports[port]
+ info := Peer{
+ Endpoint: p.intf.name,
+ BytesSent: atomic.LoadUint64(&p.bytesSent),
+ BytesRecvd: atomic.LoadUint64(&p.bytesRecvd),
+ Protocol: p.intf.info.linkType,
+ Port: uint64(port),
+ Uptime: time.Since(p.firstSeen),
+ }
+ copy(info.PublicKey[:], p.box[:])
+ peers = append(peers, info)
+ }
+ return peers
+}
+
+// GetSwitchPeers returns zero or more SwitchPeer objects containing information
+// about switch port connections with other Yggdrasil nodes. Note that, unlike
+// GetPeers, GetSwitchPeers does not include information about the current node,
+// therefore it is possible for this to return zero elements if the node is
+// isolated or not connected to any peers.
+func (c *Core) GetSwitchPeers() []SwitchPeer {
+ var switchpeers []SwitchPeer
+ table := c.switchTable.table.Load().(lookupTable)
+ peers := c.peers.ports.Load().(map[switchPort]*peer)
+ for _, elem := range table.elems {
+ peer, isIn := peers[elem.port]
+ if !isIn {
+ continue
+ }
+ coords := elem.locator.getCoords()
+ info := SwitchPeer{
+ Coords: append([]byte{}, coords...),
+ BytesSent: atomic.LoadUint64(&peer.bytesSent),
+ BytesRecvd: atomic.LoadUint64(&peer.bytesRecvd),
+ Port: uint64(elem.port),
+ Protocol: peer.intf.info.linkType,
+ Endpoint: peer.intf.info.remote,
+ }
+ copy(info.PublicKey[:], peer.box[:])
+ switchpeers = append(switchpeers, info)
+ }
+ return switchpeers
+}
+
+// GetDHT returns zero or more entries as stored in the DHT, cached primarily
+// from searches that have already taken place.
+func (c *Core) GetDHT() []DHTEntry {
+ var dhtentries []DHTEntry
+ getDHT := func() {
+ now := time.Now()
+ var dhtentry []*dhtInfo
+ for _, v := range c.dht.table {
+ dhtentry = append(dhtentry, v)
+ }
+ sort.SliceStable(dhtentry, func(i, j int) bool {
+ return dht_ordered(&c.dht.nodeID, dhtentry[i].getNodeID(), dhtentry[j].getNodeID())
+ })
+ for _, v := range dhtentry {
+ info := DHTEntry{
+ Coords: append([]byte{}, v.coords...),
+ LastSeen: now.Sub(v.recv),
+ }
+ copy(info.PublicKey[:], v.key[:])
+ dhtentries = append(dhtentries, info)
+ }
+ }
+ c.router.doAdmin(getDHT)
+ return dhtentries
+}
+
+// GetSwitchQueues returns information about the switch queues that are
+// currently in effect. These values can change within an instant.
+func (c *Core) GetSwitchQueues() SwitchQueues {
+ var switchqueues SwitchQueues
+ switchTable := &c.switchTable
+ getSwitchQueues := func() {
+ switchqueues = SwitchQueues{
+ Count: uint64(len(switchTable.queues.bufs)),
+ Size: switchTable.queues.size,
+ HighestCount: uint64(switchTable.queues.maxbufs),
+ HighestSize: switchTable.queues.maxsize,
+ MaximumSize: switchTable.queueTotalMaxSize,
+ }
+ for k, v := range switchTable.queues.bufs {
+ nexthop := switchTable.bestPortForCoords([]byte(k))
+ queue := SwitchQueue{
+ ID: k,
+ Size: v.size,
+ Packets: uint64(len(v.packets)),
+ Port: uint64(nexthop),
+ }
+ switchqueues.Queues = append(switchqueues.Queues, queue)
+ }
+
+ }
+ c.switchTable.doAdmin(getSwitchQueues)
+ return switchqueues
+}
+
+// GetSessions returns a list of open sessions from this node to other nodes.
+func (c *Core) GetSessions() []Session {
+ var sessions []Session
+ getSessions := func() {
+ for _, sinfo := range c.sessions.sinfos {
+ var session Session
+ workerFunc := func() {
+ session = Session{
+ Coords: append([]byte{}, sinfo.coords...),
+ MTU: sinfo.getMTU(),
+ BytesSent: sinfo.bytesSent,
+ BytesRecvd: sinfo.bytesRecvd,
+ Uptime: time.Now().Sub(sinfo.timeOpened),
+ WasMTUFixed: sinfo.wasMTUFixed,
+ }
+ copy(session.PublicKey[:], sinfo.theirPermPub[:])
+ }
+ var skip bool
+ func() {
+ defer func() {
+ if recover() != nil {
+ skip = true
+ }
+ }()
+ sinfo.doFunc(workerFunc)
+ }()
+ if skip {
+ continue
+ }
+ // TODO? skipped known but timed out sessions?
+ sessions = append(sessions, session)
+ }
+ }
+ c.router.doAdmin(getSessions)
+ return sessions
+}
+
+// BuildName gets the current build name. This is usually injected if built
+// from git, or returns "unknown" otherwise.
+func BuildName() string {
+ if buildName == "" {
+ return "yggdrasil"
+ }
+ 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
+}
+
+// ConnListen returns a listener for Yggdrasil session connections.
+func (c *Core) ConnListen() (*Listener, error) {
+ c.sessions.listenerMutex.Lock()
+ defer c.sessions.listenerMutex.Unlock()
+ if c.sessions.listener != nil {
+ return nil, errors.New("a listener already exists")
+ }
+ c.sessions.listener = &Listener{
+ core: c,
+ conn: make(chan *Conn),
+ close: make(chan interface{}),
+ }
+ return c.sessions.listener, nil
+}
+
+// ConnDialer returns a dialer for Yggdrasil session connections.
+func (c *Core) ConnDialer() (*Dialer, error) {
+ return &Dialer{
+ core: c,
+ }, nil
+}
+
+// ListenTCP starts a new TCP listener. The input URI should match that of the
+// "Listen" configuration item, e.g.
+// tcp://a.b.c.d:e
+func (c *Core) ListenTCP(uri string) (*TcpListener, error) {
+ return c.link.tcp.listen(uri)
+}
+
+// NodeID gets the node ID.
+func (c *Core) NodeID() *crypto.NodeID {
+ return crypto.GetNodeID(&c.boxPub)
+}
+
+// TreeID gets the tree ID.
+func (c *Core) TreeID() *crypto.TreeID {
+ return crypto.GetTreeID(&c.sigPub)
+}
+
+// SigningPublicKey gets the node's signing public key.
+func (c *Core) SigningPublicKey() string {
+ return hex.EncodeToString(c.sigPub[:])
+}
+
+// EncryptionPublicKey gets the node's encryption public key.
+func (c *Core) EncryptionPublicKey() string {
+ return hex.EncodeToString(c.boxPub[:])
+}
+
+// Coords returns the current coordinates of the node.
+func (c *Core) Coords() []byte {
+ table := c.switchTable.table.Load().(lookupTable)
+ return table.self.getCoords()
+}
+
+// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
+// address.
+func (c *Core) Address() net.IP {
+ address := net.IP(address.AddrForNodeID(c.NodeID())[:])
+ return address
+}
+
+// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a
+// /64 subnet.
+func (c *Core) Subnet() net.IPNet {
+ subnet := address.SubnetForNodeID(c.NodeID())[:]
+ subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
+ return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
+}
+
+// MyNodeInfo gets the currently configured nodeinfo.
+func (c *Core) MyNodeInfo() NodeInfoPayload {
+ return c.router.nodeinfo.getNodeInfo()
+}
+
+// SetNodeInfo the lcal nodeinfo. Note that nodeinfo can be any value or struct,
+// it will be serialised into JSON automatically.
+func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
+ c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
+}
+
+// GetNodeInfo requests nodeinfo from a remote node, as specified by the public
+// key and coordinates specified. The third parameter specifies whether a cached
+// result is acceptable - this results in less traffic being generated than is
+// necessary when, e.g. crawling the network.
+func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInfoPayload, error) {
+ var key crypto.BoxPubKey
+ if keyBytes, err := hex.DecodeString(keyString); err != nil {
+ return NodeInfoPayload{}, err
+ } else {
+ copy(key[:], keyBytes)
+ }
+ if !nocache {
+ if response, err := c.router.nodeinfo.getCachedNodeInfo(key); err == nil {
+ return response, nil
+ }
+ }
+ var coords []byte
+ for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
+ if cstr == "" {
+ // Special case, happens if trimmed is the empty string, e.g. this is the root
+ continue
+ }
+ if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
+ return NodeInfoPayload{}, err
+ } else {
+ coords = append(coords, uint8(u64))
+ }
+ }
+ response := make(chan *NodeInfoPayload, 1)
+ sendNodeInfoRequest := func() {
+ c.router.nodeinfo.addCallback(key, func(nodeinfo *NodeInfoPayload) {
+ defer func() { recover() }()
+ select {
+ case response <- nodeinfo:
+ default:
+ }
+ })
+ c.router.nodeinfo.sendNodeInfo(key, coords, false)
+ }
+ c.router.doAdmin(sendNodeInfoRequest)
+ go func() {
+ time.Sleep(6 * time.Second)
+ close(response)
+ }()
+ for res := range response {
+ return *res, nil
+ }
+ return NodeInfoPayload{}, fmt.Errorf("getNodeInfo timeout: %s", keyString)
+}
+
+// SetSessionGatekeeper allows you to configure a handler function for deciding
+// whether a session should be allowed or not. The default session firewall is
+// implemented in this way. The function receives the public key of the remote
+// side and a boolean which is true if we initiated the session or false if we
+// received an incoming session request. The function should return true to
+// allow the session or false to reject it.
+func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) {
+ c.sessions.isAllowedMutex.Lock()
+ defer c.sessions.isAllowedMutex.Unlock()
+
+ c.sessions.isAllowedHandler = f
+}
+
+// SetLogger sets the output logger of the Yggdrasil node after startup. This
+// may be useful if you want to redirect the output later.
+func (c *Core) SetLogger(log *log.Logger) {
+ 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 {
+ return err
+ }
+ c.config.Mutex.Lock()
+ if sintf == "" {
+ c.config.Current.Peers = append(c.config.Current.Peers, addr)
+ } else {
+ c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr)
+ }
+ c.config.Mutex.Unlock()
+ return nil
+}
+
+// RemovePeer is not implemented yet.
+func (c *Core) RemovePeer(addr string, sintf string) error {
+ // TODO: Implement a reverse of AddPeer, where we look up the port number
+ // based on the addr and sintf, disconnect it and then remove it from the
+ // peers list so we don't reconnect to it later
+ return errors.New("not implemented")
+}
+
+// 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(addr string, sintf string) error {
+ return c.link.call(addr, sintf)
+}
+
+// DisconnectPeer disconnects a peer once. This should be specified as a port
+// number.
+func (c *Core) DisconnectPeer(port uint64) error {
+ c.peers.removePeer(switchPort(port))
+ return nil
+}
+
+// GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming
+// peer connections.
+func (c *Core) GetAllowedEncryptionPublicKeys() []string {
+ return c.peers.getAllowedEncryptionPublicKeys()
+}
+
+// AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections.
+func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) {
+ c.peers.addAllowedEncryptionPublicKey(bstr)
+ return nil
+}
+
+// RemoveAllowedEncryptionPublicKey removes a key from the whitelist for
+// incoming peer connections. If none are set, an empty list permits all
+// incoming connections.
+func (c *Core) RemoveAllowedEncryptionPublicKey(bstr string) (err error) {
+ c.peers.removeAllowedEncryptionPublicKey(bstr)
+ return nil
+}
+
+// DHTPing sends a DHT ping to the node with the provided key and coords,
+// optionally looking up the specified target NodeID.
+func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, error) {
+ var key crypto.BoxPubKey
+ if keyBytes, err := hex.DecodeString(keyString); err != nil {
+ return DHTRes{}, err
+ } else {
+ copy(key[:], keyBytes)
+ }
+ var coords []byte
+ for _, cstr := range strings.Split(strings.Trim(coordString, "[]"), " ") {
+ if cstr == "" {
+ // Special case, happens if trimmed is the empty string, e.g. this is the root
+ continue
+ }
+ if u64, err := strconv.ParseUint(cstr, 10, 8); err != nil {
+ return DHTRes{}, err
+ } else {
+ coords = append(coords, uint8(u64))
+ }
+ }
+ resCh := make(chan *dhtRes, 1)
+ info := dhtInfo{
+ key: key,
+ coords: coords,
+ }
+ target := *info.getNodeID()
+ if targetString == "none" {
+ // Leave the default target in place
+ } else if targetBytes, err := hex.DecodeString(targetString); err != nil {
+ return DHTRes{}, err
+ } else if len(targetBytes) != len(target) {
+ return DHTRes{}, errors.New("Incorrect target NodeID length")
+ } else {
+ var target crypto.NodeID
+ copy(target[:], targetBytes)
+ }
+ rq := dhtReqKey{info.key, target}
+ sendPing := func() {
+ c.dht.addCallback(&rq, func(res *dhtRes) {
+ resCh <- res
+ })
+ c.dht.ping(&info, &target)
+ }
+ c.router.doAdmin(sendPing)
+ // TODO: do something better than the below...
+ res := <-resCh
+ if res != nil {
+ r := DHTRes{
+ Coords: append([]byte{}, res.Coords...),
+ }
+ copy(r.PublicKey[:], res.Key[:])
+ for _, i := range res.Infos {
+ e := DHTEntry{
+ Coords: append([]byte{}, i.coords...),
+ }
+ copy(e.PublicKey[:], i.key[:])
+ r.Infos = append(r.Infos, e)
+ }
+ return r, nil
+ }
+ return DHTRes{}, fmt.Errorf("DHT ping timeout: %s", keyString)
+}
diff --git a/src/yggdrasil/awdl.go b/src/yggdrasil/awdl.go
deleted file mode 100644
index 5e8cce16..00000000
--- a/src/yggdrasil/awdl.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package yggdrasil
-
-import (
- "errors"
- "io"
- "sync"
-)
-
-type awdl struct {
- link *link
- reconfigure chan chan error
- mutex sync.RWMutex // protects interfaces below
- interfaces map[string]*awdlInterface
-}
-
-type awdlInterface struct {
- linkif *linkInterface
- rwc awdlReadWriteCloser
- peer *peer
- stream stream
-}
-
-type awdlReadWriteCloser struct {
- fromAWDL chan []byte
- toAWDL chan []byte
-}
-
-func (c awdlReadWriteCloser) Read(p []byte) (n int, err error) {
- if packet, ok := <-c.fromAWDL; ok {
- n = copy(p, packet)
- return n, nil
- }
- return 0, io.EOF
-}
-
-func (c awdlReadWriteCloser) Write(p []byte) (n int, err error) {
- var pc []byte
- pc = append(pc, p...)
- c.toAWDL <- pc
- return len(pc), nil
-}
-
-func (c awdlReadWriteCloser) Close() error {
- close(c.fromAWDL)
- close(c.toAWDL)
- return nil
-}
-
-func (a *awdl) init(l *link) error {
- a.link = l
- a.mutex.Lock()
- a.interfaces = make(map[string]*awdlInterface)
- a.reconfigure = make(chan chan error, 1)
- a.mutex.Unlock()
-
- go func() {
- for e := range a.reconfigure {
- e <- nil
- }
- }()
-
- return nil
-}
-
-func (a *awdl) create(name, local, remote string, incoming bool) (*awdlInterface, error) {
- rwc := awdlReadWriteCloser{
- fromAWDL: make(chan []byte, 1),
- toAWDL: make(chan []byte, 1),
- }
- s := stream{}
- s.init(rwc)
- linkif, err := a.link.create(&s, name, "awdl", local, remote, incoming, true)
- if err != nil {
- return nil, err
- }
- intf := awdlInterface{
- linkif: linkif,
- rwc: rwc,
- }
- a.mutex.Lock()
- a.interfaces[name] = &intf
- a.mutex.Unlock()
- go intf.linkif.handler()
- return &intf, nil
-}
-
-func (a *awdl) getInterface(identity string) *awdlInterface {
- a.mutex.RLock()
- defer a.mutex.RUnlock()
- if intf, ok := a.interfaces[identity]; ok {
- return intf
- }
- return nil
-}
-
-func (a *awdl) shutdown(identity string) error {
- if intf, ok := a.interfaces[identity]; ok {
- close(intf.linkif.closed)
- intf.rwc.Close()
- a.mutex.Lock()
- delete(a.interfaces, identity)
- a.mutex.Unlock()
- return nil
- }
- return errors.New("Interface not found or already closed")
-}
diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go
new file mode 100644
index 00000000..d1cb7609
--- /dev/null
+++ b/src/yggdrasil/conn.go
@@ -0,0 +1,292 @@
+package yggdrasil
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
+ "github.com/yggdrasil-network/yggdrasil-go/src/util"
+)
+
+// ConnError implements the net.Error interface
+type ConnError struct {
+ error
+ timeout bool
+ temporary bool
+ closed bool
+ maxsize int
+}
+
+// Timeout returns true if the error relates to a timeout condition on the
+// connection.
+func (e *ConnError) Timeout() bool {
+ return e.timeout
+}
+
+// Temporary return true if the error is temporary or false if it is a permanent
+// error condition.
+func (e *ConnError) Temporary() bool {
+ return e.temporary
+}
+
+// PacketTooBig returns in response to sending a packet that is too large, and
+// if so, the maximum supported packet size that should be used for the
+// connection.
+func (e *ConnError) PacketTooBig() bool {
+ return e.maxsize > 0
+}
+
+// PacketMaximumSize returns the maximum supported packet size. This will only
+// return a non-zero value if ConnError.PacketTooBig() returns true.
+func (e *ConnError) PacketMaximumSize() int {
+ if !e.PacketTooBig() {
+ return 0
+ }
+ return e.maxsize
+}
+
+// Closed returns if the session is already closed and is now unusable.
+func (e *ConnError) Closed() bool {
+ return e.closed
+}
+
+type Conn struct {
+ core *Core
+ readDeadline atomic.Value // time.Time // TODO timer
+ writeDeadline atomic.Value // time.Time // TODO timer
+ cancel util.Cancellation
+ mutex sync.RWMutex // protects the below
+ nodeID *crypto.NodeID
+ nodeMask *crypto.NodeID
+ session *sessionInfo
+}
+
+// TODO func NewConn() that initializes additional fields as needed
+func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn {
+ conn := Conn{
+ core: core,
+ nodeID: nodeID,
+ nodeMask: nodeMask,
+ session: session,
+ cancel: util.NewCancellation(),
+ }
+ return &conn
+}
+
+func (c *Conn) String() string {
+ c.mutex.RLock()
+ defer c.mutex.RUnlock()
+ return fmt.Sprintf("conn=%p", c)
+}
+
+// This should never be called from the router goroutine
+func (c *Conn) search() error {
+ var sinfo *searchInfo
+ var isIn bool
+ c.core.router.doAdmin(func() { sinfo, isIn = c.core.searches.searches[*c.nodeID] })
+ if !isIn {
+ done := make(chan struct{}, 1)
+ var sess *sessionInfo
+ var err error
+ searchCompleted := func(sinfo *sessionInfo, e error) {
+ sess = sinfo
+ err = e
+ // FIXME close can be called multiple times, do a non-blocking send instead
+ select {
+ case done <- struct{}{}:
+ default:
+ }
+ }
+ c.core.router.doAdmin(func() {
+ sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
+ sinfo.continueSearch()
+ })
+ <-done
+ c.session = sess
+ if c.session == nil && err == nil {
+ panic("search failed but returned no error")
+ }
+ if c.session != nil {
+ c.nodeID = crypto.GetNodeID(&c.session.theirPermPub)
+ for i := range c.nodeMask {
+ c.nodeMask[i] = 0xFF
+ }
+ }
+ return err
+ } else {
+ return errors.New("search already exists")
+ }
+ return nil
+}
+
+func (c *Conn) getDeadlineCancellation(value *atomic.Value) util.Cancellation {
+ if deadline, ok := value.Load().(time.Time); ok {
+ // A deadline is set, so return a Cancellation that uses it
+ return util.CancellationWithDeadline(c.cancel, deadline)
+ } else {
+ // No cancellation was set, so return a child cancellation with no timeout
+ return util.CancellationChild(c.cancel)
+ }
+}
+
+func (c *Conn) Read(b []byte) (int, error) {
+ // Take a copy of the session object
+ sinfo := c.session
+ cancel := c.getDeadlineCancellation(&c.readDeadline)
+ defer cancel.Cancel(nil)
+ var bs []byte
+ for {
+ // Wait for some traffic to come through from the session
+ select {
+ case <-cancel.Finished():
+ if cancel.Error() == util.CancellationTimeoutError {
+ return 0, ConnError{errors.New("read timeout"), true, false, false, 0}
+ } else {
+ return 0, ConnError{errors.New("session closed"), false, false, true, 0}
+ }
+ case p, ok := <-sinfo.recv:
+ // If the session is closed then do nothing
+ if !ok {
+ return 0, ConnError{errors.New("session closed"), false, false, true, 0}
+ }
+ var err error
+ sessionFunc := func() {
+ defer util.PutBytes(p.Payload)
+ // If the nonce is bad then drop the packet and return an error
+ if !sinfo.nonceIsOK(&p.Nonce) {
+ err = ConnError{errors.New("packet dropped due to invalid nonce"), false, true, false, 0}
+ return
+ }
+ // Decrypt the packet
+ var isOK bool
+ bs, isOK = crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
+ // Check if we were unable to decrypt the packet for some reason and
+ // return an error if we couldn't
+ if !isOK {
+ err = ConnError{errors.New("packet dropped due to decryption failure"), false, true, false, 0}
+ return
+ }
+ // Update the session
+ sinfo.updateNonce(&p.Nonce)
+ sinfo.time = time.Now()
+ sinfo.bytesRecvd += uint64(len(bs))
+ }
+ sinfo.doFunc(sessionFunc)
+ // Something went wrong in the session worker so abort
+ if err != nil {
+ if ce, ok := err.(*ConnError); ok && ce.Temporary() {
+ continue
+ }
+ return 0, err
+ }
+ // Copy results to the output slice and clean up
+ copy(b, bs)
+ util.PutBytes(bs)
+ // If we've reached this point then everything went to plan, return the
+ // number of bytes we populated back into the given slice
+ return len(bs), nil
+ }
+ }
+}
+
+func (c *Conn) Write(b []byte) (bytesWritten int, err error) {
+ sinfo := c.session
+ var packet []byte
+ written := len(b)
+ sessionFunc := func() {
+ // Does the packet exceed the permitted size for the session?
+ if uint16(len(b)) > sinfo.getMTU() {
+ written, err = 0, ConnError{errors.New("packet too big"), true, false, false, int(sinfo.getMTU())}
+ return
+ }
+ // Encrypt the packet
+ payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, b, &sinfo.myNonce)
+ defer util.PutBytes(payload)
+ // Construct the wire packet to send to the router
+ p := wire_trafficPacket{
+ Coords: sinfo.coords,
+ Handle: sinfo.theirHandle,
+ Nonce: *nonce,
+ Payload: payload,
+ }
+ packet = p.encode()
+ sinfo.bytesSent += uint64(len(b))
+ // The rest of this work is session keep-alive traffic
+ doSearch := func() {
+ routerWork := func() {
+ // Check to see if there is a search already matching the destination
+ sinfo, isIn := c.core.searches.searches[*c.nodeID]
+ if !isIn {
+ // Nothing was found, so create a new search
+ searchCompleted := func(sinfo *sessionInfo, e error) {}
+ sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted)
+ c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo)
+ }
+ // Continue the search
+ sinfo.continueSearch()
+ }
+ go func() { c.core.router.admin <- routerWork }()
+ }
+ switch {
+ case time.Since(sinfo.time) > 6*time.Second:
+ if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second {
+ // TODO double check that the above condition is correct
+ doSearch()
+ } else {
+ sinfo.core.sessions.ping(sinfo)
+ }
+ case sinfo.reset && sinfo.pingTime.Before(sinfo.time):
+ sinfo.core.sessions.ping(sinfo)
+ default: // Don't do anything, to keep traffic throttled
+ }
+ }
+ sinfo.doFunc(sessionFunc)
+ // Give the packet to the router
+ if written > 0 {
+ sinfo.core.router.out(packet)
+ }
+ // Finally return the number of bytes we wrote
+ return written, err
+}
+
+func (c *Conn) Close() (err error) {
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+ if c.session != nil {
+ // Close the session, if it hasn't been closed already
+ c.core.router.doAdmin(c.session.close)
+ }
+ if e := c.cancel.Cancel(errors.New("connection closed")); e != nil {
+ err = ConnError{errors.New("close failed, session already closed"), false, false, true, 0}
+ }
+ return
+}
+
+func (c *Conn) LocalAddr() crypto.NodeID {
+ return *crypto.GetNodeID(&c.session.core.boxPub)
+}
+
+func (c *Conn) RemoteAddr() crypto.NodeID {
+ c.mutex.RLock()
+ defer c.mutex.RUnlock()
+ return *c.nodeID
+}
+
+func (c *Conn) SetDeadline(t time.Time) error {
+ c.SetReadDeadline(t)
+ c.SetWriteDeadline(t)
+ return nil
+}
+
+func (c *Conn) SetReadDeadline(t time.Time) error {
+ c.readDeadline.Store(t)
+ return nil
+}
+
+func (c *Conn) SetWriteDeadline(t time.Time) error {
+ c.writeDeadline.Store(t)
+ return nil
+}
diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go
index 12ff14fc..2aa78347 100644
--- a/src/yggdrasil/core.go
+++ b/src/yggdrasil/core.go
@@ -2,36 +2,26 @@ package yggdrasil
import (
"encoding/hex"
+ "errors"
"io/ioutil"
- "net"
- "sync"
"time"
"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/crypto"
- "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
)
var buildName string
var buildVersion string
-type module interface {
- init(*Core, *config.NodeConfig) error
- start() error
-}
-
// 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
- config config.NodeConfig // Active config
- configOld config.NodeConfig // Previous config
- configMutex sync.RWMutex // Protects both config and configOld
+ config config.NodeState // Config
boxPub crypto.BoxPubKey
boxPriv crypto.BoxPrivKey
sigPub crypto.SigPubKey
@@ -41,9 +31,7 @@ type Core struct {
sessions sessions
router router
dht dht
- admin admin
searches searches
- multicast multicast
link link
log *log.Logger
}
@@ -57,33 +45,42 @@ func (c *Core) init() error {
c.log = log.New(ioutil.Discard, "", 0)
}
- boxPubHex, err := hex.DecodeString(c.config.EncryptionPublicKey)
+ current := c.config.GetCurrent()
+
+ boxPrivHex, err := hex.DecodeString(current.EncryptionPrivateKey)
if err != nil {
return err
}
- boxPrivHex, err := hex.DecodeString(c.config.EncryptionPrivateKey)
- if err != nil {
- return err
- }
- sigPubHex, err := hex.DecodeString(c.config.SigningPublicKey)
- if err != nil {
- return err
- }
- sigPrivHex, err := hex.DecodeString(c.config.SigningPrivateKey)
- if err != nil {
- return err
+ if len(boxPrivHex) < crypto.BoxPrivKeyLen {
+ return errors.New("EncryptionPrivateKey is incorrect length")
+ }
+
+ sigPrivHex, err := hex.DecodeString(current.SigningPrivateKey)
+ if err != nil {
+ return err
+ }
+ if len(sigPrivHex) < crypto.SigPrivKeyLen {
+ return errors.New("SigningPrivateKey is incorrect length")
}
- copy(c.boxPub[:], boxPubHex)
copy(c.boxPriv[:], boxPrivHex)
- copy(c.sigPub[:], sigPubHex)
copy(c.sigPriv[:], sigPrivHex)
- c.admin.init(c)
+ boxPub, sigPub := c.boxPriv.Public(), c.sigPriv.Public()
+
+ copy(c.boxPub[:], boxPub[:])
+ copy(c.sigPub[:], sigPub[:])
+
+ if bp := hex.EncodeToString(c.boxPub[:]); current.EncryptionPublicKey != bp {
+ c.log.Warnln("EncryptionPublicKey in config is incorrect, should be", bp)
+ }
+ if sp := hex.EncodeToString(c.sigPub[:]); current.SigningPublicKey != sp {
+ c.log.Warnln("SigningPublicKey in config is incorrect, should be", sp)
+ }
+
c.searches.init(c)
c.dht.init(c)
c.sessions.init(c)
- c.multicast.init(c)
c.peers.init(c)
c.router.init(c)
c.switchTable.init(c) // TODO move before peers? before router?
@@ -96,22 +93,19 @@ func (c *Core) init() error {
// be reconnected with.
func (c *Core) addPeerLoop() {
for {
- // Get the peers from the config - these could change!
- c.configMutex.RLock()
- peers := c.config.Peers
- interfacepeers := c.config.InterfacePeers
- c.configMutex.RUnlock()
+ // the peers from the config - these could change!
+ current := c.config.GetCurrent()
// Add peers from the Peers section
- for _, peer := range peers {
- c.AddPeer(peer, "")
+ for _, peer := range current.Peers {
+ go c.AddPeer(peer, "")
time.Sleep(time.Second)
}
// Add peers from the InterfacePeers section
- for intf, intfpeers := range interfacepeers {
+ for intf, intfpeers := range current.InterfacePeers {
for _, peer := range intfpeers {
- c.AddPeer(peer, intf)
+ go c.AddPeer(peer, intf)
time.Sleep(time.Second)
}
}
@@ -121,30 +115,24 @@ func (c *Core) addPeerLoop() {
}
}
-// UpdateConfig updates the configuration in Core and then signals the
-// various module goroutines to reconfigure themselves if needed
+// UpdateConfig updates the configuration in Core with the provided
+// config.NodeConfig and then signals the various module goroutines to
+// reconfigure themselves if needed.
func (c *Core) UpdateConfig(config *config.NodeConfig) {
- c.log.Infoln("Reloading configuration...")
+ c.log.Debugln("Reloading node configuration...")
- c.configMutex.Lock()
- c.configOld = c.config
- c.config = *config
- c.configMutex.Unlock()
+ c.config.Replace(*config)
errors := 0
components := []chan chan error{
- c.admin.reconfigure,
c.searches.reconfigure,
c.dht.reconfigure,
c.sessions.reconfigure,
c.peers.reconfigure,
c.router.reconfigure,
- c.router.tun.reconfigure,
- c.router.cryptokey.reconfigure,
c.switchTable.reconfigure,
c.link.reconfigure,
- c.multicast.reconfigure,
}
for _, component := range components {
@@ -157,196 +145,64 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) {
}
if errors > 0 {
- c.log.Warnln(errors, "modules reported errors during configuration reload")
+ c.log.Warnln(errors, "node module(s) reported errors during configuration reload")
} else {
- c.log.Infoln("Configuration reloaded successfully")
+ c.log.Infoln("Node configuration reloaded successfully")
}
}
-// GetBuildName gets the current build name. This is usually injected if built
-// from git, or returns "unknown" otherwise.
-func GetBuildName() string {
- if buildName == "" {
- return "unknown"
- }
- return buildName
-}
-
-// Get the current build version. This is usually injected if built from git,
-// or returns "unknown" otherwise.
-func GetBuildVersion() string {
- if buildVersion == "" {
- return "unknown"
- }
- return buildVersion
-}
-
-// Starts up Yggdrasil using the provided NodeConfig, and outputs debug logging
-// through the provided log.Logger. The started stack will include TCP and UDP
-// sockets, a multicast discovery socket, an admin socket, router, switch and
-// DHT node.
-func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
+// 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) (*config.NodeState, error) {
c.log = log
- if name := GetBuildName(); name != "unknown" {
+ c.config = config.NodeState{
+ Current: *nc,
+ Previous: *nc,
+ }
+
+ if name := BuildName(); name != "unknown" {
c.log.Infoln("Build name:", name)
}
- if version := GetBuildVersion(); version != "unknown" {
+ if version := BuildVersion(); version != "unknown" {
c.log.Infoln("Build version:", version)
}
c.log.Infoln("Starting up...")
- c.configMutex.Lock()
- c.config = *nc
- c.configOld = c.config
- c.configMutex.Unlock()
-
c.init()
if err := c.link.init(c); err != nil {
c.log.Errorln("Failed to start link interfaces")
- return err
+ return nil, err
}
- if nc.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize {
- c.switchTable.queueTotalMaxSize = nc.SwitchOptions.MaxTotalQueueSize
+ c.config.Mutex.RLock()
+ if c.config.Current.SwitchOptions.MaxTotalQueueSize >= SwitchQueueTotalMinSize {
+ c.switchTable.queueTotalMaxSize = c.config.Current.SwitchOptions.MaxTotalQueueSize
}
+ c.config.Mutex.RUnlock()
if err := c.switchTable.start(); err != nil {
c.log.Errorln("Failed to start switch")
- return err
+ return nil, err
}
if err := c.router.start(); err != nil {
c.log.Errorln("Failed to start router")
- return err
- }
-
- if err := c.admin.start(); err != nil {
- c.log.Errorln("Failed to start admin socket")
- return err
- }
-
- if err := c.multicast.start(); err != nil {
- c.log.Errorln("Failed to start multicast interface")
- return err
- }
-
- if err := c.router.tun.start(); err != nil {
- c.log.Errorln("Failed to start TUN/TAP")
- return err
+ return nil, err
}
go c.addPeerLoop()
c.log.Infoln("Startup complete")
- return nil
+ return &c.config, nil
}
-// Stops the Yggdrasil node.
+// Stop shuts down the Yggdrasil node.
func (c *Core) Stop() {
c.log.Infoln("Stopping...")
- c.router.tun.close()
- c.admin.close()
-}
-
-// Generates a new encryption keypair. The encryption keys are used to
-// encrypt traffic and to derive the IPv6 address/subnet of the node.
-func (c *Core) NewEncryptionKeys() (*crypto.BoxPubKey, *crypto.BoxPrivKey) {
- return crypto.NewBoxKeys()
-}
-
-// Generates a new signing keypair. The signing keys are used to derive the
-// structure of the spanning tree.
-func (c *Core) NewSigningKeys() (*crypto.SigPubKey, *crypto.SigPrivKey) {
- return crypto.NewSigKeys()
-}
-
-// Gets the node ID.
-func (c *Core) GetNodeID() *crypto.NodeID {
- return crypto.GetNodeID(&c.boxPub)
-}
-
-// Gets the tree ID.
-func (c *Core) GetTreeID() *crypto.TreeID {
- return crypto.GetTreeID(&c.sigPub)
-}
-
-// Gets the IPv6 address of the Yggdrasil node. This is always a /128.
-func (c *Core) GetAddress() *net.IP {
- address := net.IP(address.AddrForNodeID(c.GetNodeID())[:])
- return &address
-}
-
-// Gets the routed IPv6 subnet of the Yggdrasil node. This is always a /64.
-func (c *Core) GetSubnet() *net.IPNet {
- subnet := address.SubnetForNodeID(c.GetNodeID())[:]
- subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
- return &net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
-}
-
-// Gets the nodeinfo.
-func (c *Core) GetNodeInfo() nodeinfoPayload {
- return c.router.nodeinfo.getNodeInfo()
-}
-
-// Sets the nodeinfo.
-func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
- c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
-}
-
-// Sets the output logger of the Yggdrasil node after startup. This may be
-// useful if you want to redirect the output later.
-func (c *Core) SetLogger(log *log.Logger) {
- c.log = log
-}
-
-// Adds a peer. This should be specified in the peer URI format, i.e.
-// tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j
-func (c *Core) AddPeer(addr string, sintf string) error {
- return c.admin.addPeer(addr, sintf)
-}
-
-// Adds an allowed public key. This allow peerings to be restricted only to
-// keys that you have selected.
-func (c *Core) AddAllowedEncryptionPublicKey(boxStr string) error {
- return c.admin.addAllowedEncryptionPublicKey(boxStr)
-}
-
-// Gets the default admin listen address for your platform.
-func (c *Core) GetAdminDefaultListen() string {
- return defaults.GetDefaults().DefaultAdminListen
-}
-
-// Gets the default TUN/TAP interface name for your platform.
-func (c *Core) GetTUNDefaultIfName() string {
- return defaults.GetDefaults().DefaultIfName
-}
-
-// Gets the default TUN/TAP interface MTU for your platform. This can be as high
-// as 65535, depending on platform, but is never lower than 1280.
-func (c *Core) GetTUNDefaultIfMTU() int {
- return defaults.GetDefaults().DefaultIfMTU
-}
-
-// Gets the maximum supported TUN/TAP interface MTU for your platform. This
-// can be as high as 65535, depending on platform, but is never lower than 1280.
-func (c *Core) GetTUNMaximumIfMTU() int {
- return defaults.GetDefaults().MaximumIfMTU
-}
-
-// Gets the default TUN/TAP interface mode for your platform.
-func (c *Core) GetTUNDefaultIfTAPMode() bool {
- return defaults.GetDefaults().DefaultIfTAPMode
-}
-
-// Gets the current TUN/TAP interface name.
-func (c *Core) GetTUNIfName() string {
- return c.router.tun.iface.Name()
-}
-
-// Gets the current TUN/TAP interface MTU.
-func (c *Core) GetTUNIfMTU() int {
- return c.router.tun.mtu
}
diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go
index e575b72d..ff3bbe70 100644
--- a/src/yggdrasil/debug.go
+++ b/src/yggdrasil/debug.go
@@ -35,9 +35,9 @@ func init() {
hostPort := os.Getenv(envVarName)
switch {
case hostPort == "":
- fmt.Printf("DEBUG: %s not set, profiler not started.\n", envVarName)
+ fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName)
default:
- fmt.Printf("DEBUG: Starting pprof on %s\n", hostPort)
+ fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort)
go func() { fmt.Println(http.ListenAndServe(hostPort, nil)) }()
}
}
@@ -59,13 +59,17 @@ func (c *Core) Init() {
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
- c.config = config.NodeConfig{
+ cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
- c.init( /*bpub, bpriv, spub, spriv*/ )
+ c.config = config.NodeState{
+ Current: cfg,
+ Previous: cfg,
+ }
+ c.init()
c.switchTable.start()
c.router.start()
}
@@ -82,6 +86,7 @@ func (c *Core) DEBUG_getEncryptionPublicKey() crypto.BoxPubKey {
return (crypto.BoxPubKey)(c.boxPub)
}
+/*
func (c *Core) DEBUG_getSend() chan<- []byte {
return c.router.tun.send
}
@@ -89,6 +94,7 @@ func (c *Core) DEBUG_getSend() chan<- []byte {
func (c *Core) DEBUG_getRecv() <-chan []byte {
return c.router.tun.recv
}
+*/
// Peer
@@ -317,6 +323,7 @@ func (c *Core) DEBUG_getAddr() *address.Address {
return address.AddrForNodeID(&c.dht.nodeID)
}
+/*
func (c *Core) DEBUG_startTun(ifname string, iftapmode bool) {
c.DEBUG_startTunWithMTU(ifname, iftapmode, 1280)
}
@@ -338,6 +345,7 @@ func (c *Core) DEBUG_startTunWithMTU(ifname string, iftapmode bool, mtu int) {
func (c *Core) DEBUG_stopTun() {
c.router.tun.close()
}
+*/
////////////////////////////////////////////////////////////////////////////////
@@ -382,13 +390,17 @@ func (c *Core) DEBUG_init(bpub []byte,
hbpriv := hex.EncodeToString(bpriv[:])
hspub := hex.EncodeToString(spub[:])
hspriv := hex.EncodeToString(spriv[:])
- c.config = config.NodeConfig{
+ cfg := config.NodeConfig{
EncryptionPublicKey: hbpub,
EncryptionPrivateKey: hbpriv,
SigningPublicKey: hspub,
SigningPrivateKey: hspriv,
}
- c.init( /*bpub, bpriv, spub, spriv*/ )
+ c.config = config.NodeState{
+ Current: cfg,
+ Previous: cfg,
+ }
+ c.init()
if err := c.router.start(); err != nil {
panic(err)
@@ -427,14 +439,14 @@ func (c *Core) DEBUG_maybeSendUDPKeys(saddr string) {
*/
////////////////////////////////////////////////////////////////////////////////
-
+/*
func (c *Core) DEBUG_addPeer(addr string) {
err := c.admin.addPeer(addr, "")
if err != nil {
panic(err)
}
}
-
+*/
/*
func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
go func() {
@@ -455,7 +467,7 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
}
*/
-//*
+/*
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
c.config.Listen = []string{addrport}
if err := c.link.init(c); err != nil {
@@ -503,10 +515,11 @@ func (c *Core) DEBUG_addKCPConn(saddr string) {
////////////////////////////////////////////////////////////////////////////////
+/*
func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) {
a := admin{}
c.config.AdminListen = addrport
- a.init(c /*, addrport*/)
+ a.init()
c.admin = a
}
@@ -516,6 +529,7 @@ func (c *Core) DEBUG_setupAndStartMulticastInterface() {
c.multicast = m
m.start()
}
+*/
////////////////////////////////////////////////////////////////////////////////
@@ -527,13 +541,14 @@ func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) {
c.log.Println("DEBUG_setIfceExpr no longer implemented")
}
+/*
func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) {
err := c.admin.addAllowedEncryptionPublicKey(boxStr)
if err != nil {
panic(err)
}
}
-
+*/
////////////////////////////////////////////////////////////////////////////////
func DEBUG_simLinkPeers(p, q *peer) {
@@ -579,9 +594,11 @@ func DEBUG_simLinkPeers(p, q *peer) {
q.core.switchTable.idleIn <- q.port
}
+/*
func (c *Core) DEBUG_simFixMTU() {
c.router.tun.mtu = 65535
}
+*/
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go
index 5427aca9..b53e29c9 100644
--- a/src/yggdrasil/dht.go
+++ b/src/yggdrasil/dht.go
@@ -68,9 +68,9 @@ type dht struct {
core *Core
reconfigure chan chan error
nodeID crypto.NodeID
- peers chan *dhtInfo // other goroutines put incoming dht updates here
- reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
- callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks
+ peers chan *dhtInfo // other goroutines put incoming dht updates here
+ reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests
+ callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks
// These next two could be replaced by a single linked list or similar...
table map[crypto.NodeID]*dhtInfo
imp []*dhtInfo
@@ -86,9 +86,9 @@ func (t *dht) init(c *Core) {
e <- nil
}
}()
- t.nodeID = *t.core.GetNodeID()
+ t.nodeID = *t.core.NodeID()
t.peers = make(chan *dhtInfo, 1024)
- t.callbacks = make(map[dhtReqKey]dht_callbackInfo)
+ t.callbacks = make(map[dhtReqKey][]dht_callbackInfo)
t.reset()
}
@@ -244,15 +244,17 @@ type dht_callbackInfo struct {
// Adds a callback and removes it after some timeout.
func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) {
info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)}
- t.callbacks[*rq] = info
+ t.callbacks[*rq] = append(t.callbacks[*rq], info)
}
// Reads a lookup response, checks that we had sent a matching request, and processes the response info.
// This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses
func (t *dht) handleRes(res *dhtRes) {
rq := dhtReqKey{res.Key, res.Dest}
- if callback, isIn := t.callbacks[rq]; isIn {
- callback.f(res)
+ if callbacks, isIn := t.callbacks[rq]; isIn {
+ for _, callback := range callbacks {
+ callback.f(res)
+ }
delete(t.callbacks, rq)
}
_, isIn := t.reqs[rq]
@@ -326,10 +328,15 @@ func (t *dht) doMaintenance() {
}
}
t.reqs = newReqs
- newCallbacks := make(map[dhtReqKey]dht_callbackInfo, len(t.callbacks))
- for key, callback := range t.callbacks {
- if now.Before(callback.time) {
- newCallbacks[key] = callback
+ newCallbacks := make(map[dhtReqKey][]dht_callbackInfo, len(t.callbacks))
+ for key, cs := range t.callbacks {
+ for _, c := range cs {
+ if now.Before(c.time) {
+ newCallbacks[key] = append(newCallbacks[key], c)
+ } else {
+ // Signal failure
+ c.f(nil)
+ }
}
}
t.callbacks = newCallbacks
diff --git a/src/yggdrasil/dial.go b/src/yggdrasil/dial.go
deleted file mode 100644
index 7aec4192..00000000
--- a/src/yggdrasil/dial.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package yggdrasil
-
-import (
- "net"
- "time"
-)
-
-// wrappedConn implements net.Conn
-type wrappedConn struct {
- c net.Conn
- raddr net.Addr
-}
-
-// wrappedAddr implements net.Addr
-type wrappedAddr struct {
- network string
- addr string
-}
-
-func (a *wrappedAddr) Network() string {
- return a.network
-}
-
-func (a *wrappedAddr) String() string {
- return a.addr
-}
-
-func (c *wrappedConn) Write(data []byte) (int, error) {
- return c.c.Write(data)
-}
-
-func (c *wrappedConn) Read(data []byte) (int, error) {
- return c.c.Read(data)
-}
-
-func (c *wrappedConn) SetDeadline(t time.Time) error {
- return c.c.SetDeadline(t)
-}
-
-func (c *wrappedConn) SetReadDeadline(t time.Time) error {
- return c.c.SetReadDeadline(t)
-}
-
-func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
- return c.c.SetWriteDeadline(t)
-}
-
-func (c *wrappedConn) Close() error {
- return c.c.Close()
-}
-
-func (c *wrappedConn) LocalAddr() net.Addr {
- return c.c.LocalAddr()
-}
-
-func (c *wrappedConn) RemoteAddr() net.Addr {
- return c.raddr
-}
diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go
new file mode 100644
index 00000000..6b24cfb4
--- /dev/null
+++ b/src/yggdrasil/dialer.go
@@ -0,0 +1,77 @@
+package yggdrasil
+
+import (
+ "encoding/hex"
+ "errors"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
+)
+
+// Dialer represents an Yggdrasil connection dialer.
+type Dialer struct {
+ core *Core
+}
+
+// TODO DialContext that allows timeouts/cancellation, Dial should just call this with no timeout set in the context
+
+// Dial opens a session to the given node. The first paramter should be "nodeid"
+// and the second parameter should contain a hexadecimal representation of the
+// target node ID.
+func (d *Dialer) Dial(network, address string) (*Conn, error) {
+ var nodeID crypto.NodeID
+ var nodeMask crypto.NodeID
+ // Process
+ switch network {
+ case "nodeid":
+ // A node ID was provided - we don't need to do anything special with it
+ if tokens := strings.Split(address, "/"); len(tokens) == 2 {
+ len, err := strconv.Atoi(tokens[1])
+ if err != nil {
+ return nil, err
+ }
+ dest, err := hex.DecodeString(tokens[0])
+ if err != nil {
+ return nil, err
+ }
+ copy(nodeID[:], dest)
+ for idx := 0; idx < len; idx++ {
+ nodeMask[idx/8] |= 0x80 >> byte(idx%8)
+ }
+ } else {
+ dest, err := hex.DecodeString(tokens[0])
+ if err != nil {
+ return nil, err
+ }
+ copy(nodeID[:], dest)
+ for i := range nodeMask {
+ nodeMask[i] = 0xFF
+ }
+ }
+ return d.DialByNodeIDandMask(&nodeID, &nodeMask)
+ default:
+ // An unexpected address type was given, so give up
+ return nil, errors.New("unexpected address type")
+ }
+}
+
+// DialByNodeIDandMask opens a session to the given node based on raw
+// NodeID parameters.
+func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) {
+ conn := newConn(d.core, nodeID, nodeMask, nil)
+ if err := conn.search(); err != nil {
+ conn.Close()
+ return nil, err
+ }
+ t := time.NewTimer(6 * time.Second) // TODO use a context instead
+ defer t.Stop()
+ select {
+ case <-conn.session.init:
+ return conn, nil
+ case <-t.C:
+ conn.Close()
+ return nil, errors.New("session handshake timeout")
+ }
+}
diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go
index bfec714b..de90fd94 100644
--- a/src/yggdrasil/link.go
+++ b/src/yggdrasil/link.go
@@ -23,8 +23,7 @@ type link struct {
reconfigure chan chan error
mutex sync.RWMutex // protects interfaces below
interfaces map[linkInfo]*linkInterface
- awdl awdl // AWDL interface support
- tcp tcp // TCP interface support
+ tcp tcp // TCP interface support
// TODO timeout (to remove from switch), read from config.ReadTimeout
}
@@ -68,26 +67,15 @@ func (l *link) init(c *Core) error {
return err
}
- if err := l.awdl.init(l); err != nil {
- c.log.Errorln("Failed to start AWDL interface")
- return err
- }
-
go func() {
for {
e := <-l.reconfigure
tcpresponse := make(chan error)
- awdlresponse := make(chan error)
l.tcp.reconfigure <- tcpresponse
if err := <-tcpresponse; err != nil {
e <- err
continue
}
- l.awdl.reconfigure <- awdlresponse
- if err := <-awdlresponse; err != nil {
- e <- err
- continue
- }
e <- nil
}
}()
diff --git a/src/yggdrasil/listener.go b/src/yggdrasil/listener.go
new file mode 100644
index 00000000..62225412
--- /dev/null
+++ b/src/yggdrasil/listener.go
@@ -0,0 +1,45 @@
+package yggdrasil
+
+import (
+ "errors"
+ "net"
+)
+
+// Listener waits for incoming sessions
+type Listener struct {
+ core *Core
+ conn chan *Conn
+ close chan interface{}
+}
+
+// Accept blocks until a new incoming session is received
+func (l *Listener) Accept() (*Conn, error) {
+ select {
+ case c, ok := <-l.conn:
+ if !ok {
+ return nil, errors.New("listener closed")
+ }
+ return c, nil
+ case <-l.close:
+ return nil, errors.New("listener closed")
+ }
+}
+
+// Close will stop the listener
+func (l *Listener) Close() (err error) {
+ defer func() {
+ recover()
+ err = errors.New("already closed")
+ }()
+ if l.core.sessions.listener == l {
+ l.core.sessions.listener = nil
+ }
+ close(l.close)
+ close(l.conn)
+ return nil
+}
+
+// Addr is not implemented for this type yet
+func (l *Listener) Addr() net.Addr {
+ return nil
+}
diff --git a/src/yggdrasil/mobile.go b/src/yggdrasil/mobile.go
deleted file mode 100644
index 81aa47f1..00000000
--- a/src/yggdrasil/mobile.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// +build mobile
-
-package yggdrasil
-
-import (
- "encoding/hex"
- "encoding/json"
- "os"
- "time"
-
- "github.com/gologme/log"
-
- hjson "github.com/hjson/hjson-go"
- "github.com/mitchellh/mapstructure"
- "github.com/yggdrasil-network/yggdrasil-go/src/config"
- "github.com/yggdrasil-network/yggdrasil-go/src/util"
-)
-
-// This file 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.
-
-func (c *Core) addStaticPeers(cfg *config.NodeConfig) {
- if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 {
- return
- }
- for {
- for _, peer := range cfg.Peers {
- c.AddPeer(peer, "")
- time.Sleep(time.Second)
- }
- for intf, intfpeers := range cfg.InterfacePeers {
- for _, peer := range intfpeers {
- c.AddPeer(peer, intf)
- time.Sleep(time.Second)
- }
- }
- time.Sleep(time.Minute)
- }
-}
-
-// Starts a node with a randomly generated config.
-func (c *Core) StartAutoconfigure() error {
- mobilelog := MobileLogger{}
- logger := log.New(mobilelog, "", 0)
- nc := config.GenerateConfig()
- nc.IfName = "dummy"
- nc.AdminListen = "tcp://localhost:9001"
- nc.Peers = []string{}
- if hostname, err := os.Hostname(); err == nil {
- nc.NodeInfo = map[string]interface{}{"name": hostname}
- }
- if err := c.Start(nc, logger); err != nil {
- return err
- }
- go c.addStaticPeers(nc)
- return nil
-}
-
-// Starts a node with the given JSON config. You can get JSON config (rather
-// than HJSON) by using the GenerateConfigJSON() function.
-func (c *Core) StartJSON(configjson []byte) error {
- mobilelog := MobileLogger{}
- logger := log.New(mobilelog, "", 0)
- nc := config.GenerateConfig()
- var dat map[string]interface{}
- if err := hjson.Unmarshal(configjson, &dat); err != nil {
- return err
- }
- if err := mapstructure.Decode(dat, &nc); err != nil {
- return err
- }
- nc.IfName = "dummy"
- if err := c.Start(nc, logger); err != nil {
- return err
- }
- go c.addStaticPeers(nc)
- return nil
-}
-
-// Generates mobile-friendly configuration in JSON format.
-func GenerateConfigJSON() []byte {
- nc := config.GenerateConfig()
- nc.IfName = "dummy"
- if json, err := json.Marshal(nc); err == nil {
- return json
- } else {
- return nil
- }
-}
-
-// Gets the node's IPv6 address.
-func (c *Core) GetAddressString() string {
- return c.GetAddress().String()
-}
-
-// Gets the node's IPv6 subnet in CIDR notation.
-func (c *Core) GetSubnetString() string {
- return c.GetSubnet().String()
-}
-
-// Gets the node's public encryption key.
-func (c *Core) GetBoxPubKeyString() string {
- return hex.EncodeToString(c.boxPub[:])
-}
-
-// Gets the node's public signing key.
-func (c *Core) GetSigPubKeyString() string {
- return hex.EncodeToString(c.sigPub[:])
-}
-
-// Wait for a packet from the router. You will use this when implementing a
-// dummy adapter in place of real TUN - when this call returns a packet, you
-// will probably want to give it to the OS to write to TUN.
-func (c *Core) RouterRecvPacket() ([]byte, error) {
- packet := <-c.router.tun.recv
- return packet, nil
-}
-
-// Send a packet to the router. You will use this when implementing a
-// dummy adapter in place of real TUN - when the operating system tells you
-// that a new packet is available from TUN, call this function to give it to
-// Yggdrasil.
-func (c *Core) RouterSendPacket(buf []byte) error {
- packet := append(util.GetBytes(), buf[:]...)
- c.router.tun.send <- packet
- return nil
-}
diff --git a/src/yggdrasil/mobile_android.go b/src/yggdrasil/mobile_android.go
deleted file mode 100644
index 24764840..00000000
--- a/src/yggdrasil/mobile_android.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// +build android
-
-package yggdrasil
-
-import "log"
-
-type MobileLogger struct{}
-
-func (nsl MobileLogger) Write(p []byte) (n int, err error) {
- log.Println(string(p))
- return len(p), nil
-}
diff --git a/src/yggdrasil/mobile_ios.go b/src/yggdrasil/mobile_ios.go
deleted file mode 100644
index 7b089992..00000000
--- a/src/yggdrasil/mobile_ios.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// +build mobile,darwin
-
-package yggdrasil
-
-/*
-#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 (
- "errors"
- "unsafe"
-
- "github.com/yggdrasil-network/yggdrasil-go/src/util"
-)
-
-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
-}
-
-func (c *Core) AWDLCreateInterface(name, local, remote string, incoming bool) error {
- if intf, err := c.link.awdl.create(name, local, remote, incoming); err != nil || intf == nil {
- c.log.Println("c.link.awdl.create:", err)
- return err
- }
- return nil
-}
-
-func (c *Core) AWDLShutdownInterface(name string) error {
- return c.link.awdl.shutdown(name)
-}
-
-func (c *Core) AWDLRecvPacket(identity string) ([]byte, error) {
- if intf := c.link.awdl.getInterface(identity); intf != nil {
- read, ok := <-intf.rwc.toAWDL
- if !ok {
- return nil, errors.New("AWDLRecvPacket: channel closed")
- }
- return read, nil
- }
- return nil, errors.New("AWDLRecvPacket identity not known: " + identity)
-}
-
-func (c *Core) AWDLSendPacket(identity string, buf []byte) error {
- packet := append(util.GetBytes(), buf[:]...)
- if intf := c.link.awdl.getInterface(identity); intf != nil {
- intf.rwc.fromAWDL <- packet
- return nil
- }
- return errors.New("AWDLSendPacket identity not known: " + identity)
-}
diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go
index 963a2fc2..f1c7ed07 100644
--- a/src/yggdrasil/nodeinfo.go
+++ b/src/yggdrasil/nodeinfo.go
@@ -13,7 +13,7 @@ import (
type nodeinfo struct {
core *Core
- myNodeInfo nodeinfoPayload
+ myNodeInfo NodeInfoPayload
myNodeInfoMutex sync.RWMutex
callbacks map[crypto.BoxPubKey]nodeinfoCallback
callbacksMutex sync.Mutex
@@ -21,15 +21,13 @@ type nodeinfo struct {
cacheMutex sync.RWMutex
}
-type nodeinfoPayload []byte
-
type nodeinfoCached struct {
- payload nodeinfoPayload
+ payload NodeInfoPayload
created time.Time
}
type nodeinfoCallback struct {
- call func(nodeinfo *nodeinfoPayload)
+ call func(nodeinfo *NodeInfoPayload)
created time.Time
}
@@ -38,7 +36,7 @@ type nodeinfoReqRes struct {
SendPermPub crypto.BoxPubKey // Sender's permanent key
SendCoords []byte // Sender's coords
IsResponse bool
- NodeInfo nodeinfoPayload
+ NodeInfo NodeInfoPayload
}
// Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep
@@ -70,7 +68,7 @@ func (m *nodeinfo) init(core *Core) {
}
// Add a callback for a nodeinfo lookup
-func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *nodeinfoPayload)) {
+func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *NodeInfoPayload)) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
m.callbacks[sender] = nodeinfoCallback{
@@ -80,7 +78,7 @@ func (m *nodeinfo) addCallback(sender crypto.BoxPubKey, call func(nodeinfo *node
}
// Handles the callback, if there is one
-func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) {
+func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo NodeInfoPayload) {
m.callbacksMutex.Lock()
defer m.callbacksMutex.Unlock()
if callback, ok := m.callbacks[sender]; ok {
@@ -90,7 +88,7 @@ func (m *nodeinfo) callback(sender crypto.BoxPubKey, nodeinfo nodeinfoPayload) {
}
// Get the current node's nodeinfo
-func (m *nodeinfo) getNodeInfo() nodeinfoPayload {
+func (m *nodeinfo) getNodeInfo() NodeInfoPayload {
m.myNodeInfoMutex.RLock()
defer m.myNodeInfoMutex.RUnlock()
return m.myNodeInfo
@@ -101,8 +99,8 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error {
m.myNodeInfoMutex.Lock()
defer m.myNodeInfoMutex.Unlock()
defaults := map[string]interface{}{
- "buildname": GetBuildName(),
- "buildversion": GetBuildVersion(),
+ "buildname": BuildName(),
+ "buildversion": BuildVersion(),
"buildplatform": runtime.GOOS,
"buildarch": runtime.GOARCH,
}
@@ -135,7 +133,7 @@ func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) error {
}
// Add nodeinfo into the cache for a node
-func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPayload) {
+func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload NodeInfoPayload) {
m.cacheMutex.Lock()
defer m.cacheMutex.Unlock()
m.cache[key] = nodeinfoCached{
@@ -145,13 +143,13 @@ func (m *nodeinfo) addCachedNodeInfo(key crypto.BoxPubKey, payload nodeinfoPaylo
}
// Get a nodeinfo entry from the cache
-func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (nodeinfoPayload, error) {
+func (m *nodeinfo) getCachedNodeInfo(key crypto.BoxPubKey) (NodeInfoPayload, error) {
m.cacheMutex.RLock()
defer m.cacheMutex.RUnlock()
if nodeinfo, ok := m.cache[key]; ok {
return nodeinfo.payload, nil
}
- return nodeinfoPayload{}, errors.New("No cache entry found")
+ return NodeInfoPayload{}, errors.New("No cache entry found")
}
// Handles a nodeinfo request/response - called from the router
diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go
index ce359368..06201f9b 100644
--- a/src/yggdrasil/peer.go
+++ b/src/yggdrasil/peer.go
@@ -44,42 +44,42 @@ func (ps *peers) init(c *Core) {
// because the key is in the whitelist or because the whitelist is empty.
func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool {
boxstr := hex.EncodeToString(box[:])
- ps.core.configMutex.RLock()
- defer ps.core.configMutex.RUnlock()
- for _, v := range ps.core.config.AllowedEncryptionPublicKeys {
+ ps.core.config.Mutex.RLock()
+ defer ps.core.config.Mutex.RUnlock()
+ for _, v := range ps.core.config.Current.AllowedEncryptionPublicKeys {
if v == boxstr {
return true
}
}
- return len(ps.core.config.AllowedEncryptionPublicKeys) == 0
+ return len(ps.core.config.Current.AllowedEncryptionPublicKeys) == 0
}
// Adds a key to the whitelist.
func (ps *peers) addAllowedEncryptionPublicKey(box string) {
- ps.core.configMutex.RLock()
- defer ps.core.configMutex.RUnlock()
- ps.core.config.AllowedEncryptionPublicKeys =
- append(ps.core.config.AllowedEncryptionPublicKeys, box)
+ ps.core.config.Mutex.RLock()
+ defer ps.core.config.Mutex.RUnlock()
+ ps.core.config.Current.AllowedEncryptionPublicKeys =
+ append(ps.core.config.Current.AllowedEncryptionPublicKeys, box)
}
// Removes a key from the whitelist.
func (ps *peers) removeAllowedEncryptionPublicKey(box string) {
- ps.core.configMutex.RLock()
- defer ps.core.configMutex.RUnlock()
- for k, v := range ps.core.config.AllowedEncryptionPublicKeys {
+ ps.core.config.Mutex.RLock()
+ defer ps.core.config.Mutex.RUnlock()
+ for k, v := range ps.core.config.Current.AllowedEncryptionPublicKeys {
if v == box {
- ps.core.config.AllowedEncryptionPublicKeys =
- append(ps.core.config.AllowedEncryptionPublicKeys[:k],
- ps.core.config.AllowedEncryptionPublicKeys[k+1:]...)
+ ps.core.config.Current.AllowedEncryptionPublicKeys =
+ append(ps.core.config.Current.AllowedEncryptionPublicKeys[:k],
+ ps.core.config.Current.AllowedEncryptionPublicKeys[k+1:]...)
}
}
}
// Gets the whitelist of allowed keys for incoming connections.
func (ps *peers) getAllowedEncryptionPublicKeys() []string {
- ps.core.configMutex.RLock()
- defer ps.core.configMutex.RUnlock()
- return ps.core.config.AllowedEncryptionPublicKeys
+ ps.core.config.Mutex.RLock()
+ defer ps.core.config.Mutex.RUnlock()
+ return ps.core.config.Current.AllowedEncryptionPublicKeys
}
// Atomically gets a map[switchPort]*peer of known peers.
diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go
index 1d4c0771..c5e1dde0 100644
--- a/src/yggdrasil/router.go
+++ b/src/yggdrasil/router.go
@@ -5,7 +5,7 @@ package yggdrasil
// TODO clean up old/unused code, maybe improve comments on whatever is left
// Send:
-// Receive a packet from the tun
+// Receive a packet from the adapter
// Look up session (if none exists, trigger a search)
// Hand off to session (which encrypts, etc)
// Session will pass it back to router.out, which hands it off to the self peer
@@ -20,47 +20,33 @@ package yggdrasil
// If it's dht/seach/etc. traffic, the router passes it to that part
// If it's an encapsulated IPv6 packet, the router looks up the session for it
// The packet is passed to the session, which decrypts it, router.recvPacket
-// The router then runs some sanity checks before passing it to the tun
+// The router then runs some sanity checks before passing it to the adapter
import (
- "bytes"
- "time"
+ //"bytes"
- "golang.org/x/net/icmp"
- "golang.org/x/net/ipv6"
+ "time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
"github.com/yggdrasil-network/yggdrasil-go/src/util"
)
-// The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
+// The router struct has channels to/from the adapter device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions.
type router struct {
core *Core
reconfigure chan chan error
addr address.Address
subnet address.Subnet
- in <-chan []byte // packets we received from the network, link to peer's "out"
- out func([]byte) // packets we're sending to the network, link to peer's "in"
- toRecv chan router_recvPacket // packets to handle via recvPacket()
- tun tunAdapter // TUN/TAP adapter
- adapters []Adapter // Other adapters
- recv chan<- []byte // place where the tun pulls received packets from
- send <-chan []byte // place where the tun puts outgoing packets
- reset chan struct{} // signal that coords changed (re-init sessions/dht)
- admin chan func() // pass a lambda for the admin socket to query stuff
- cryptokey cryptokey
+ in <-chan []byte // packets we received from the network, link to peer's "out"
+ out func([]byte) // packets we're sending to the network, link to peer's "in"
+ reset chan struct{} // signal that coords changed (re-init sessions/dht)
+ admin chan func() // pass a lambda for the admin socket to query stuff
nodeinfo nodeinfo
}
-// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the tun.
-type router_recvPacket struct {
- bs []byte
- sinfo *sessionInfo
-}
-
-// Initializes the router struct, which includes setting up channels to/from the tun/tap.
+// Initializes the router struct, which includes setting up channels to/from the adapter.
func (r *router) init(core *Core) {
r.core = core
r.reconfigure = make(chan chan error, 1)
@@ -104,19 +90,12 @@ func (r *router) init(core *Core) {
}
}()
r.out = func(packet []byte) { out2 <- packet }
- r.toRecv = make(chan router_recvPacket, 32)
- recv := make(chan []byte, 32)
- send := make(chan []byte, 32)
- r.recv = recv
- r.send = send
r.reset = make(chan struct{}, 1)
r.admin = make(chan func(), 32)
r.nodeinfo.init(r.core)
- r.core.configMutex.RLock()
- r.nodeinfo.setNodeInfo(r.core.config.NodeInfo, r.core.config.NodeInfoPrivacy)
- r.core.configMutex.RUnlock()
- r.cryptokey.init(r.core)
- r.tun.init(r.core, send, recv)
+ r.core.config.Mutex.RLock()
+ r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy)
+ r.core.config.Mutex.RUnlock()
}
// Starts the mainLoop goroutine.
@@ -126,7 +105,7 @@ func (r *router) start() error {
return nil
}
-// Takes traffic from the tun/tap and passes it to router.send, or from r.in and handles incoming traffic.
+// Takes traffic from the adapter and passes it to router.send, or from r.in and handles incoming traffic.
// Also adds new peer info to the DHT.
// Also resets the DHT and sesssions in the event of a coord change.
// Also does periodic maintenance stuff.
@@ -135,16 +114,12 @@ func (r *router) mainLoop() {
defer ticker.Stop()
for {
select {
- case rp := <-r.toRecv:
- r.recvPacket(rp.bs, rp.sinfo)
case p := <-r.in:
r.handleIn(p)
- case p := <-r.send:
- r.sendPacket(p)
case info := <-r.core.dht.peers:
r.core.dht.insertPeer(info)
case <-r.reset:
- r.core.sessions.resetInits()
+ r.core.sessions.reset()
r.core.dht.reset()
case <-ticker.C:
{
@@ -157,231 +132,12 @@ func (r *router) mainLoop() {
case f := <-r.admin:
f()
case e := <-r.reconfigure:
- r.core.configMutex.RLock()
- e <- r.nodeinfo.setNodeInfo(r.core.config.NodeInfo, r.core.config.NodeInfoPrivacy)
- r.core.configMutex.RUnlock()
+ current := r.core.config.GetCurrent()
+ e <- r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy)
}
}
}
-// Checks a packet's to/from address to make sure it's in the allowed range.
-// If a session to the destination exists, gets the session and passes the packet to it.
-// If no session exists, it triggers (or continues) a search.
-// If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly.
-// It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their tun/tap disabled.
-func (r *router) sendPacket(bs []byte) {
- var sourceAddr address.Address
- var destAddr address.Address
- var destSnet address.Subnet
- var destPubKey *crypto.BoxPubKey
- var destNodeID *crypto.NodeID
- var addrlen int
- if bs[0]&0xf0 == 0x60 {
- // Check if we have a fully-sized header
- if len(bs) < 40 {
- panic("Tried to send a packet shorter than an IPv6 header...")
- }
- // IPv6 address
- addrlen = 16
- copy(sourceAddr[:addrlen], bs[8:])
- copy(destAddr[:addrlen], bs[24:])
- copy(destSnet[:addrlen/2], bs[24:])
- } else if bs[0]&0xf0 == 0x40 {
- // Check if we have a fully-sized header
- if len(bs) < 20 {
- panic("Tried to send a packet shorter than an IPv4 header...")
- }
- // IPv4 address
- addrlen = 4
- copy(sourceAddr[:addrlen], bs[12:])
- copy(destAddr[:addrlen], bs[16:])
- } else {
- // Unknown address length
- return
- }
- if !r.cryptokey.isValidSource(sourceAddr, addrlen) {
- // The packet had a source address that doesn't belong to us or our
- // configured crypto-key routing source subnets
- return
- }
- if !destAddr.IsValid() && !destSnet.IsValid() {
- // The addresses didn't match valid Yggdrasil node addresses so let's see
- // whether it matches a crypto-key routing range instead
- if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil {
- // A public key was found, get the node ID for the search
- destPubKey = &key
- destNodeID = crypto.GetNodeID(destPubKey)
- // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil
- // address or subnet - this might be superfluous
- addr := *address.AddrForNodeID(destNodeID)
- copy(destAddr[:], addr[:])
- copy(destSnet[:], addr[:])
- if !destAddr.IsValid() && !destSnet.IsValid() {
- return
- }
- } else {
- // No public key was found in the CKR table so we've exhausted our options
- return
- }
- }
- doSearch := func(packet []byte) {
- var nodeID, mask *crypto.NodeID
- switch {
- case destNodeID != nil:
- // We already know the full node ID, probably because it's from a CKR
- // route in which the public key is known ahead of time
- nodeID = destNodeID
- var m crypto.NodeID
- for i := range m {
- m[i] = 0xFF
- }
- mask = &m
- case destAddr.IsValid():
- // We don't know the full node ID - try and use the address to generate
- // a truncated node ID
- nodeID, mask = destAddr.GetNodeIDandMask()
- case destSnet.IsValid():
- // We don't know the full node ID - try and use the subnet to generate
- // a truncated node ID
- nodeID, mask = destSnet.GetNodeIDandMask()
- default:
- return
- }
- sinfo, isIn := r.core.searches.searches[*nodeID]
- if !isIn {
- sinfo = r.core.searches.newIterSearch(nodeID, mask)
- }
- if packet != nil {
- sinfo.packet = packet
- }
- r.core.searches.continueSearch(sinfo)
- }
- var sinfo *sessionInfo
- var isIn bool
- if destAddr.IsValid() {
- sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr)
- }
- if destSnet.IsValid() {
- sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet)
- }
- switch {
- case !isIn || !sinfo.init:
- // No or unintiialized session, so we need to search first
- doSearch(bs)
- case time.Since(sinfo.time) > 6*time.Second:
- if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second {
- // We haven't heard from the dest in a while
- // We tried pinging but didn't get a response
- // They may have changed coords
- // Try searching to discover new coords
- // Note that search spam is throttled internally
- doSearch(nil)
- } else {
- // We haven't heard about the dest in a while
- now := time.Now()
- if !sinfo.time.Before(sinfo.pingTime) {
- // Update pingTime to start the clock for searches (above)
- sinfo.pingTime = now
- }
- if time.Since(sinfo.pingSend) > time.Second {
- // Send at most 1 ping per second
- sinfo.pingSend = now
- r.core.sessions.sendPingPong(sinfo, false)
- }
- }
- fallthrough // Also send the packet
- default:
- // If we know the public key ahead of time (i.e. a CKR route) then check
- // if the session perm pub key matches before we send the packet to it
- if destPubKey != nil {
- if !bytes.Equal((*destPubKey)[:], sinfo.theirPermPub[:]) {
- return
- }
- }
-
- // Drop packets if the session MTU is 0 - this means that one or other
- // side probably has their TUN adapter disabled
- if sinfo.getMTU() == 0 {
- // Don't continue - drop the packet
- return
- }
- // Generate an ICMPv6 Packet Too Big for packets larger than session MTU
- if len(bs) > int(sinfo.getMTU()) {
- // Get the size of the oversized payload, up to a max of 900 bytes
- window := 900
- if int(sinfo.getMTU()) < window {
- window = int(sinfo.getMTU())
- }
-
- // Create the Packet Too Big response
- ptb := &icmp.PacketTooBig{
- MTU: int(sinfo.getMTU()),
- Data: bs[:window],
- }
-
- // Create the ICMPv6 response from it
- icmpv6Buf, err := r.tun.icmpv6.create_icmpv6_tun(
- bs[8:24], bs[24:40],
- ipv6.ICMPTypePacketTooBig, 0, ptb)
- if err == nil {
- r.recv <- icmpv6Buf
- }
-
- // Don't continue - drop the packet
- return
- }
- sinfo.send <- bs
- }
-}
-
-// Called for incoming traffic by the session worker for that connection.
-// Checks that the IP address is correct (matches the session) and passes the packet to the tun/tap.
-func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) {
- // Note: called directly by the session worker, not the router goroutine
- if len(bs) < 24 {
- util.PutBytes(bs)
- return
- }
- var sourceAddr address.Address
- var dest address.Address
- var snet address.Subnet
- var addrlen int
- if bs[0]&0xf0 == 0x60 {
- // IPv6 address
- addrlen = 16
- copy(sourceAddr[:addrlen], bs[8:])
- copy(dest[:addrlen], bs[24:])
- copy(snet[:addrlen/2], bs[8:])
- } else if bs[0]&0xf0 == 0x40 {
- // IPv4 address
- addrlen = 4
- copy(sourceAddr[:addrlen], bs[12:])
- copy(dest[:addrlen], bs[16:])
- } else {
- // Unknown address length
- return
- }
- // Check that the packet is destined for either our Yggdrasil address or
- // subnet, or that it matches one of the crypto-key routing source routes
- if !r.cryptokey.isValidSource(dest, addrlen) {
- util.PutBytes(bs)
- return
- }
- // See whether the packet they sent should have originated from this session
- switch {
- case sourceAddr.IsValid() && sourceAddr == sinfo.theirAddr:
- case snet.IsValid() && snet == sinfo.theirSubnet:
- default:
- key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen)
- if err != nil || key != sinfo.theirPermPub {
- util.PutBytes(bs)
- return
- }
- }
- //go func() { r.recv<-bs }()
- r.recv <- bs
-}
-
// Checks incoming traffic type and passes it to the appropriate handler.
func (r *router) handleIn(packet []byte) {
pType, pTypeLen := wire_decode_uint64(packet)
@@ -398,7 +154,7 @@ func (r *router) handleIn(packet []byte) {
}
// Handles incoming traffic, i.e. encapuslated ordinary IPv6 packets.
-// Passes them to the crypto session worker to be decrypted and sent to the tun/tap.
+// Passes them to the crypto session worker to be decrypted and sent to the adapter.
func (r *router) handleTraffic(packet []byte) {
defer util.PutBytes(packet)
p := wire_trafficPacket{}
@@ -409,7 +165,11 @@ func (r *router) handleTraffic(packet []byte) {
if !isIn {
return
}
- sinfo.recv <- &p
+ select {
+ case sinfo.recv <- &p: // FIXME ideally this should be front drop
+ default:
+ util.PutBytes(p.Payload)
+ }
}
// Handles protocol traffic by decrypting it, checking its type, and passing it to the appropriate handler for that traffic type.
@@ -432,7 +192,7 @@ func (r *router) handleProto(packet []byte) {
return
}
// Now do something with the bytes in bs...
- // send dht messages to dht, sessionRefresh to sessions, data to tun...
+ // send dht messages to dht, sessionRefresh to sessions, data to adapter...
// For data, should check that key and IP match...
bsType, bsTypeLen := wire_decode_uint64(bs)
if bsTypeLen == 0 {
diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go
index c391dda2..4c31fd6b 100644
--- a/src/yggdrasil/search.go
+++ b/src/yggdrasil/search.go
@@ -15,6 +15,7 @@ package yggdrasil
// Some kind of max search steps, in case the node is offline, so we don't crawl through too much of the network looking for a destination that isn't there?
import (
+ "errors"
"sort"
"time"
@@ -32,12 +33,14 @@ const search_RETRY_TIME = time.Second
// Information about an ongoing search.
// Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
type searchInfo struct {
- dest crypto.NodeID
- mask crypto.NodeID
- time time.Time
- packet []byte
- toVisit []*dhtInfo
- visited map[crypto.NodeID]bool
+ core *Core
+ dest crypto.NodeID
+ mask crypto.NodeID
+ time time.Time
+ toVisit []*dhtInfo
+ visited map[crypto.NodeID]bool
+ callback func(*sessionInfo, error)
+ // TODO context.Context for timeout and cancellation
}
// This stores a map of active searches.
@@ -47,7 +50,7 @@ type searches struct {
searches map[crypto.NodeID]*searchInfo
}
-// Intializes the searches struct.
+// Initializes the searches struct.
func (s *searches) init(core *Core) {
s.core = core
s.reconfigure = make(chan chan error, 1)
@@ -61,17 +64,19 @@ func (s *searches) init(core *Core) {
}
// Creates a new search info, adds it to the searches struct, and returns a pointer to the info.
-func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
+func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
now := time.Now()
- for dest, sinfo := range s.searches {
- if now.Sub(sinfo.time) > time.Minute {
- delete(s.searches, dest)
- }
- }
+ //for dest, sinfo := range s.searches {
+ // if now.Sub(sinfo.time) > time.Minute {
+ // delete(s.searches, dest)
+ // }
+ //}
info := searchInfo{
- dest: *dest,
- mask: *mask,
- time: now.Add(-time.Second),
+ core: s.core,
+ dest: *dest,
+ mask: *mask,
+ time: now.Add(-time.Second),
+ callback: callback,
}
s.searches[*dest] = &info
return &info
@@ -79,31 +84,29 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searc
////////////////////////////////////////////////////////////////////////////////
-// Checks if there's an ongoing search relaed to a dhtRes.
+// Checks if there's an ongoing search related to a dhtRes.
// If there is, it adds the response info to the search and triggers a new search step.
// If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more.
-func (s *searches) handleDHTRes(res *dhtRes) {
- sinfo, isIn := s.searches[res.Dest]
- if !isIn || s.checkDHTRes(sinfo, res) {
+func (sinfo *searchInfo) handleDHTRes(res *dhtRes) {
+ if res == nil || sinfo.checkDHTRes(res) {
// Either we don't recognize this search, or we just finished it
return
- } else {
- // Add to the search and continue
- s.addToSearch(sinfo, res)
- s.doSearchStep(sinfo)
}
+ // Add to the search and continue
+ sinfo.addToSearch(res)
+ sinfo.doSearchStep()
}
// Adds the information from a dhtRes to an ongoing search.
// Info about a node that has already been visited is not re-added to the search.
// Duplicate information about nodes toVisit is deduplicated (the newest information is kept).
// The toVisit list is sorted in ascending order of keyspace distance from the destination.
-func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) {
+func (sinfo *searchInfo) addToSearch(res *dhtRes) {
// Add responses to toVisit if closer to dest than the res node
from := dhtInfo{key: res.Key, coords: res.Coords}
sinfo.visited[*from.getNodeID()] = true
for _, info := range res.Infos {
- if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] {
+ if *info.getNodeID() == sinfo.core.dht.nodeID || sinfo.visited[*info.getNodeID()] {
continue
}
if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) {
@@ -133,85 +136,91 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) {
// If there are no nodes left toVisit, then this cleans up the search.
// Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping.
-func (s *searches) doSearchStep(sinfo *searchInfo) {
+func (sinfo *searchInfo) doSearchStep() {
if len(sinfo.toVisit) == 0 {
// Dead end, do cleanup
- delete(s.searches, sinfo.dest)
+ delete(sinfo.core.searches.searches, sinfo.dest)
+ sinfo.callback(nil, errors.New("search reached dead end"))
return
- } else {
- // Send to the next search target
- var next *dhtInfo
- next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
- rq := dhtReqKey{next.key, sinfo.dest}
- s.core.dht.addCallback(&rq, s.handleDHTRes)
- s.core.dht.ping(next, &sinfo.dest)
}
+ // Send to the next search target
+ var next *dhtInfo
+ next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:]
+ rq := dhtReqKey{next.key, sinfo.dest}
+ sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes)
+ sinfo.core.dht.ping(next, &sinfo.dest)
}
// If we've recenty sent a ping for this search, do nothing.
// Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME.
-func (s *searches) continueSearch(sinfo *searchInfo) {
+func (sinfo *searchInfo) continueSearch() {
if time.Since(sinfo.time) < search_RETRY_TIME {
return
}
sinfo.time = time.Now()
- s.doSearchStep(sinfo)
+ sinfo.doSearchStep()
// In case the search dies, try to spawn another thread later
// Note that this will spawn multiple parallel searches as time passes
// Any that die aren't restarted, but a new one will start later
retryLater := func() {
- newSearchInfo := s.searches[sinfo.dest]
+ // FIXME this keeps the search alive forever if not for the searches map, fix that
+ newSearchInfo := sinfo.core.searches.searches[sinfo.dest]
if newSearchInfo != sinfo {
return
}
- s.continueSearch(sinfo)
+ sinfo.continueSearch()
}
go func() {
time.Sleep(search_RETRY_TIME)
- s.core.router.admin <- retryLater
+ sinfo.core.router.admin <- retryLater
}()
}
// Calls create search, and initializes the iterative search parts of the struct before returning it.
-func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID) *searchInfo {
- sinfo := s.createSearch(dest, mask)
- sinfo.toVisit = s.core.dht.lookup(dest, true)
+func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo {
+ sinfo := s.createSearch(dest, mask, callback)
sinfo.visited = make(map[crypto.NodeID]bool)
+ loc := s.core.switchTable.getLocator()
+ sinfo.toVisit = append(sinfo.toVisit, &dhtInfo{
+ key: s.core.boxPub,
+ coords: loc.getCoords(),
+ }) // Start the search by asking ourself, useful if we're the destination
return sinfo
}
// Checks if a dhtRes is good (called by handleDHTRes).
// If the response is from the target, get/create a session, trigger a session ping, and return true.
// Otherwise return false.
-func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool {
+func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool {
them := crypto.GetNodeID(&res.Key)
var destMasked crypto.NodeID
var themMasked crypto.NodeID
for idx := 0; idx < crypto.NodeIDLen; idx++ {
- destMasked[idx] = info.dest[idx] & info.mask[idx]
- themMasked[idx] = them[idx] & info.mask[idx]
+ destMasked[idx] = sinfo.dest[idx] & sinfo.mask[idx]
+ themMasked[idx] = them[idx] & sinfo.mask[idx]
}
if themMasked != destMasked {
return false
}
// They match, so create a session and send a sessionRequest
- sinfo, isIn := s.core.sessions.getByTheirPerm(&res.Key)
+ sess, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key)
if !isIn {
- sinfo = s.core.sessions.createSession(&res.Key)
- if sinfo == nil {
+ sess = sinfo.core.sessions.createSession(&res.Key)
+ if sess == nil {
// nil if the DHT search finished but the session wasn't allowed
+ sinfo.callback(nil, errors.New("session not allowed"))
return true
}
- _, isIn := s.core.sessions.getByTheirPerm(&res.Key)
+ _, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key)
if !isIn {
panic("This should never happen")
}
}
// FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)?
- sinfo.coords = res.Coords
- sinfo.packet = info.packet
- s.core.sessions.ping(sinfo)
+ sess.coords = res.Coords
+ sinfo.core.sessions.ping(sess)
+ sinfo.callback(sess, nil)
// Cleanup
- delete(s.searches, res.Dest)
+ delete(sinfo.core.searches.searches, res.Dest)
return true
}
diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go
index 012af579..eca3bb00 100644
--- a/src/yggdrasil/session.go
+++ b/src/yggdrasil/session.go
@@ -6,46 +6,52 @@ package yggdrasil
import (
"bytes"
- "encoding/hex"
+ "sync"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
- "github.com/yggdrasil-network/yggdrasil-go/src/util"
)
// All the information we know about an active session.
// This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API.
type sessionInfo struct {
- core *Core
- reconfigure chan chan error
- theirAddr address.Address
- theirSubnet address.Subnet
- theirPermPub crypto.BoxPubKey
- theirSesPub crypto.BoxPubKey
- mySesPub crypto.BoxPubKey
- mySesPriv crypto.BoxPrivKey
- sharedSesKey crypto.BoxSharedKey // derived from session keys
- theirHandle crypto.Handle
- myHandle crypto.Handle
- theirNonce crypto.BoxNonce
- myNonce crypto.BoxNonce
- theirMTU uint16
- myMTU uint16
- wasMTUFixed bool // Was the MTU fixed by a receive error?
- time time.Time // Time we last received a packet
- coords []byte // coords of destination
- packet []byte // a buffered packet, sent immediately on ping/pong
- init bool // Reset if coords change
- send chan []byte
- recv chan *wire_trafficPacket
- nonceMask uint64
- tstamp int64 // tstamp from their last session ping, replay attack mitigation
- mtuTime time.Time // time myMTU was last changed
- pingTime time.Time // time the first ping was sent since the last received packet
- pingSend time.Time // time the last ping was sent
- bytesSent uint64 // Bytes of real traffic sent in this session
- bytesRecvd uint64 // Bytes of real traffic received in this session
+ mutex sync.Mutex // Protects all of the below, use it any time you read/chance the contents of a session
+ core *Core //
+ reconfigure chan chan error //
+ theirAddr address.Address //
+ theirSubnet address.Subnet //
+ theirPermPub crypto.BoxPubKey //
+ theirSesPub crypto.BoxPubKey //
+ mySesPub crypto.BoxPubKey //
+ mySesPriv crypto.BoxPrivKey //
+ sharedSesKey crypto.BoxSharedKey // derived from session keys
+ theirHandle crypto.Handle //
+ myHandle crypto.Handle //
+ theirNonce crypto.BoxNonce //
+ theirNonceMask uint64 //
+ myNonce crypto.BoxNonce //
+ theirMTU uint16 //
+ myMTU uint16 //
+ wasMTUFixed bool // Was the MTU fixed by a receive error?
+ timeOpened time.Time // Time the sessino was opened
+ time time.Time // Time we last received a packet
+ mtuTime time.Time // time myMTU was last changed
+ pingTime time.Time // time the first ping was sent since the last received packet
+ pingSend time.Time // time the last ping was sent
+ coords []byte // coords of destination
+ reset bool // reset if coords change
+ tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation
+ bytesSent uint64 // Bytes of real traffic sent in this session
+ bytesRecvd uint64 // Bytes of real traffic received in this session
+ recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn
+ init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use
+}
+
+func (sinfo *sessionInfo) doFunc(f func()) {
+ sinfo.mutex.Lock()
+ defer sinfo.mutex.Unlock()
+ f()
}
// Represents a session ping/pong packet, andincludes information like public keys, a session handle, coords, a timestamp to prevent replays, and the tun/tap MTU.
@@ -53,10 +59,10 @@ type sessionPing struct {
SendPermPub crypto.BoxPubKey // Sender's permanent key
Handle crypto.Handle // Random number to ID session
SendSesPub crypto.BoxPubKey // Session key to use
- Coords []byte
- Tstamp int64 // unix time, but the only real requirement is that it increases
- IsPong bool
- MTU uint16
+ Coords []byte //
+ Tstamp int64 // unix time, but the only real requirement is that it increases
+ IsPong bool //
+ MTU uint16 //
}
// Updates session info in response to a ping, after checking that the ping is OK.
@@ -76,7 +82,7 @@ func (s *sessionInfo) update(p *sessionPing) bool {
s.theirHandle = p.Handle
s.sharedSesKey = *crypto.GetSharedKey(&s.mySesPriv, &s.theirSesPub)
s.theirNonce = crypto.BoxNonce{}
- s.nonceMask = 0
+ s.theirNonceMask = 0
}
if p.MTU >= 1280 || p.MTU == 0 {
s.theirMTU = p.MTU
@@ -85,35 +91,33 @@ func (s *sessionInfo) update(p *sessionPing) bool {
// allocate enough space for additional coords
s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...)
}
- now := time.Now()
- s.time = now
+ s.time = time.Now()
s.tstamp = p.Tstamp
- s.init = true
+ s.reset = false
+ defer func() { recover() }() // Recover if the below panics
+ select {
+ case <-s.init:
+ default:
+ // Unblock anything waiting for the session to initialize
+ close(s.init)
+ }
return true
}
-// Returns true if the session has been idle for longer than the allowed timeout.
-func (s *sessionInfo) timedout() bool {
- return time.Since(s.time) > time.Minute
-}
-
// Struct of all active sessions.
// Sessions are indexed by handle.
// Additionally, stores maps of address/subnet onto keys, and keys onto handles.
type sessions struct {
- core *Core
- reconfigure chan chan error
- lastCleanup time.Time
- // Maps known permanent keys to their shared key, used by DHT a lot
- permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey
- // Maps (secret) handle onto session info
- sinfos map[crypto.Handle]*sessionInfo
- // Maps mySesPub onto handle
- byMySes map[crypto.BoxPubKey]*crypto.Handle
- // Maps theirPermPub onto handle
- byTheirPerm map[crypto.BoxPubKey]*crypto.Handle
- addrToPerm map[address.Address]*crypto.BoxPubKey
- subnetToPerm map[address.Subnet]*crypto.BoxPubKey
+ core *Core
+ listener *Listener
+ listenerMutex sync.Mutex
+ reconfigure chan chan error
+ lastCleanup time.Time
+ isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed
+ isAllowedMutex sync.RWMutex // Protects the above
+ permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot
+ sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info
+ byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle
}
// Initializes the session struct.
@@ -139,96 +143,26 @@ func (ss *sessions) init(core *Core) {
}()
ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey)
ss.sinfos = make(map[crypto.Handle]*sessionInfo)
- ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle)
ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle)
- ss.addrToPerm = make(map[address.Address]*crypto.BoxPubKey)
- ss.subnetToPerm = make(map[address.Subnet]*crypto.BoxPubKey)
ss.lastCleanup = time.Now()
}
-// Determines whether the session firewall is enabled.
-func (ss *sessions) isSessionFirewallEnabled() bool {
- ss.core.configMutex.RLock()
- defer ss.core.configMutex.RUnlock()
-
- return ss.core.config.SessionFirewall.Enable
-}
-
// Determines whether the session with a given publickey is allowed based on
// session firewall rules.
func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool {
- ss.core.configMutex.RLock()
- defer ss.core.configMutex.RUnlock()
+ ss.isAllowedMutex.RLock()
+ defer ss.isAllowedMutex.RUnlock()
- // Allow by default if the session firewall is disabled
- if !ss.isSessionFirewallEnabled() {
+ if ss.isAllowedHandler == nil {
return true
}
- // Prepare for checking whitelist/blacklist
- var box crypto.BoxPubKey
- // Reject blacklisted nodes
- for _, b := range ss.core.config.SessionFirewall.BlacklistEncryptionPublicKeys {
- key, err := hex.DecodeString(b)
- if err == nil {
- copy(box[:crypto.BoxPubKeyLen], key)
- if box == *pubkey {
- return false
- }
- }
- }
- // Allow whitelisted nodes
- for _, b := range ss.core.config.SessionFirewall.WhitelistEncryptionPublicKeys {
- key, err := hex.DecodeString(b)
- if err == nil {
- copy(box[:crypto.BoxPubKeyLen], key)
- if box == *pubkey {
- return true
- }
- }
- }
- // Allow outbound sessions if appropriate
- if ss.core.config.SessionFirewall.AlwaysAllowOutbound {
- if initiator {
- return true
- }
- }
- // Look and see if the pubkey is that of a direct peer
- var isDirectPeer bool
- for _, peer := range ss.core.peers.ports.Load().(map[switchPort]*peer) {
- if peer.box == *pubkey {
- isDirectPeer = true
- break
- }
- }
- // Allow direct peers if appropriate
- if ss.core.config.SessionFirewall.AllowFromDirect && isDirectPeer {
- return true
- }
- // Allow remote nodes if appropriate
- if ss.core.config.SessionFirewall.AllowFromRemote && !isDirectPeer {
- return true
- }
- // Finally, default-deny if not matching any of the above rules
- return false
+
+ return ss.isAllowedHandler(pubkey, initiator)
}
// Gets the session corresponding to a given handle.
func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bool) {
sinfo, isIn := ss.sinfos[*handle]
- if isIn && sinfo.timedout() {
- // We have a session, but it has timed out
- return nil, false
- }
- return sinfo, isIn
-}
-
-// Gets a session corresponding to an ephemeral session key used by this node.
-func (ss *sessions) getByMySes(key *crypto.BoxPubKey) (*sessionInfo, bool) {
- h, isIn := ss.byMySes[*key]
- if !isIn {
- return nil, false
- }
- sinfo, isIn := ss.getSessionForHandle(h)
return sinfo, isIn
}
@@ -242,29 +176,11 @@ func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) {
return sinfo, isIn
}
-// Gets a session corresponding to an IPv6 address used by the remote node.
-func (ss *sessions) getByTheirAddr(addr *address.Address) (*sessionInfo, bool) {
- p, isIn := ss.addrToPerm[*addr]
- if !isIn {
- return nil, false
- }
- sinfo, isIn := ss.getByTheirPerm(p)
- return sinfo, isIn
-}
-
-// Gets a session corresponding to an IPv6 /64 subnet used by the remote node/network.
-func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) {
- p, isIn := ss.subnetToPerm[*snet]
- if !isIn {
- return nil, false
- }
- sinfo, isIn := ss.getByTheirPerm(p)
- return sinfo, isIn
-}
-
-// Creates a new session and lazily cleans up old/timedout existing sessions.
-// This includse initializing session info to sane defaults (e.g. lowest supported MTU).
+// Creates a new session and lazily cleans up old existing sessions. This
+// includse initializing session info to sane defaults (e.g. lowest supported
+// MTU).
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
+ // TODO: this check definitely needs to be moved
if !ss.isSessionAllowed(theirPermKey, true) {
return nil
}
@@ -277,12 +193,16 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.mySesPriv = *priv
sinfo.myNonce = *crypto.NewBoxNonce()
sinfo.theirMTU = 1280
- sinfo.myMTU = uint16(ss.core.router.tun.mtu)
+ ss.core.config.Mutex.RLock()
+ sinfo.myMTU = uint16(ss.core.config.Current.IfMTU)
+ ss.core.config.Mutex.RUnlock()
now := time.Now()
+ sinfo.timeOpened = now
sinfo.time = now
sinfo.mtuTime = now
sinfo.pingTime = now
sinfo.pingSend = now
+ sinfo.init = make(chan struct{})
higher := false
for idx := range ss.core.boxPub {
if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] {
@@ -302,14 +222,9 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
sinfo.myHandle = *crypto.NewHandle()
sinfo.theirAddr = *address.AddrForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
sinfo.theirSubnet = *address.SubnetForNodeID(crypto.GetNodeID(&sinfo.theirPermPub))
- sinfo.send = make(chan []byte, 32)
sinfo.recv = make(chan *wire_trafficPacket, 32)
- go sinfo.doWorker()
ss.sinfos[sinfo.myHandle] = &sinfo
- ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle
ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle
- ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub
- ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub
return &sinfo
}
@@ -323,11 +238,6 @@ func (ss *sessions) cleanup() {
if time.Since(ss.lastCleanup) < time.Minute {
return
}
- for _, s := range ss.sinfos {
- if s.timedout() {
- s.close()
- }
- }
permShared := make(map[crypto.BoxPubKey]*crypto.BoxSharedKey, len(ss.permShared))
for k, v := range ss.permShared {
permShared[k] = v
@@ -338,38 +248,20 @@ func (ss *sessions) cleanup() {
sinfos[k] = v
}
ss.sinfos = sinfos
- byMySes := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byMySes))
- for k, v := range ss.byMySes {
- byMySes[k] = v
- }
- ss.byMySes = byMySes
byTheirPerm := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byTheirPerm))
for k, v := range ss.byTheirPerm {
byTheirPerm[k] = v
}
ss.byTheirPerm = byTheirPerm
- addrToPerm := make(map[address.Address]*crypto.BoxPubKey, len(ss.addrToPerm))
- for k, v := range ss.addrToPerm {
- addrToPerm[k] = v
- }
- ss.addrToPerm = addrToPerm
- subnetToPerm := make(map[address.Subnet]*crypto.BoxPubKey, len(ss.subnetToPerm))
- for k, v := range ss.subnetToPerm {
- subnetToPerm[k] = v
- }
- ss.subnetToPerm = subnetToPerm
ss.lastCleanup = time.Now()
}
-// Closes a session, removing it from sessions maps and killing the worker goroutine.
+// Closes a session, removing it from sessions maps.
func (sinfo *sessionInfo) close() {
- delete(sinfo.core.sessions.sinfos, sinfo.myHandle)
- delete(sinfo.core.sessions.byMySes, sinfo.mySesPub)
- delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
- delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr)
- delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet)
- close(sinfo.send)
- close(sinfo.recv)
+ if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo {
+ delete(sinfo.core.sessions.sinfos, sinfo.myHandle)
+ delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub)
+ }
}
// Returns a session ping appropriate for the given session info.
@@ -393,6 +285,8 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing {
// This comes up with dht req/res and session ping/pong traffic.
func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey,
theirPub *crypto.BoxPubKey) *crypto.BoxSharedKey {
+ return crypto.GetSharedKey(myPriv, theirPub)
+ // FIXME concurrency issues with the below, so for now we just burn the CPU every time
if skey, isIn := ss.permShared[*theirPub]; isIn {
return skey
}
@@ -431,8 +325,8 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) {
}
packet := p.encode()
ss.core.router.out(packet)
- if !isPong {
- sinfo.pingSend = time.Now()
+ if sinfo.pingTime.Before(sinfo.time) {
+ sinfo.pingTime = time.Now()
}
}
@@ -441,35 +335,39 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) {
func (ss *sessions) handlePing(ping *sessionPing) {
// Get the corresponding session (or create a new session)
sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub)
- // Check the session firewall
- if !isIn && ss.isSessionFirewallEnabled() {
- if !ss.isSessionAllowed(&ping.SendPermPub, false) {
- return
- }
+ // Check if the session is allowed
+ // TODO: this check may need to be moved
+ if !isIn && !ss.isSessionAllowed(&ping.SendPermPub, false) {
+ return
}
- if !isIn || sinfo.timedout() {
- if isIn {
- sinfo.close()
- }
+ // Create the session if it doesn't already exist
+ if !isIn {
ss.createSession(&ping.SendPermPub)
sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub)
if !isIn {
panic("This should not happen")
}
+ ss.listenerMutex.Lock()
+ // Check and see if there's a Listener waiting to accept connections
+ // TODO: this should not block if nothing is accepting
+ if !ping.IsPong && ss.listener != nil {
+ conn := newConn(ss.core, crypto.GetNodeID(&sinfo.theirPermPub), &crypto.NodeID{}, sinfo)
+ for i := range conn.nodeMask {
+ conn.nodeMask[i] = 0xFF
+ }
+ ss.listener.conn <- conn
+ }
+ ss.listenerMutex.Unlock()
}
- // Update the session
- if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
- return
- }
- if !ping.IsPong {
- ss.sendPingPong(sinfo, true)
- }
- if sinfo.packet != nil {
- // send
- var bs []byte
- bs, sinfo.packet = sinfo.packet, nil
- ss.core.router.sendPacket(bs)
- }
+ sinfo.doFunc(func() {
+ // Update the session
+ if !sinfo.update(ping) { /*panic("Should not happen in testing")*/
+ return
+ }
+ if !ping.IsPong {
+ ss.sendPingPong(sinfo, true)
+ }
+ })
}
// Get the MTU of the session.
@@ -492,7 +390,7 @@ func (sinfo *sessionInfo) nonceIsOK(theirNonce *crypto.BoxNonce) bool {
if diff > 0 {
return true
}
- return ^sinfo.nonceMask&(0x01< 0 {
// This nonce is newer, so shift the window before setting the bit, and update theirNonce in the session info.
- sinfo.nonceMask <<= uint64(diff)
- sinfo.nonceMask &= 0x01
+ sinfo.theirNonceMask <<= uint64(diff)
+ sinfo.theirNonceMask &= 0x01
sinfo.theirNonce = *theirNonce
} else {
// This nonce is older, so set the bit but do not shift the window.
- sinfo.nonceMask &= 0x01 << uint64(-diff)
+ sinfo.theirNonceMask &= 0x01 << uint64(-diff)
}
}
// Resets all sessions to an uninitialized state.
// Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change.
-func (ss *sessions) resetInits() {
+func (ss *sessions) reset() {
for _, sinfo := range ss.sinfos {
- sinfo.init = false
+ sinfo.doFunc(func() {
+ sinfo.reset = true
+ })
}
}
-
-////////////////////////////////////////////////////////////////////////////////
-
-// This is for a per-session worker.
-// It handles calling the relatively expensive crypto operations.
-// It's also responsible for checking nonces and dropping out-of-date/duplicate packets, or else calling the function to update nonces if the packet is OK.
-func (sinfo *sessionInfo) doWorker() {
- send := make(chan []byte, 32)
- defer close(send)
- go func() {
- for bs := range send {
- sinfo.doSend(bs)
- }
- }()
- recv := make(chan *wire_trafficPacket, 32)
- defer close(recv)
- go func() {
- for p := range recv {
- sinfo.doRecv(p)
- }
- }()
- for {
- select {
- case p, ok := <-sinfo.recv:
- if ok {
- select {
- case recv <- p:
- default:
- // We need something to not block, and it's best to drop it before we decrypt
- util.PutBytes(p.Payload)
- }
- } else {
- return
- }
- case bs, ok := <-sinfo.send:
- if ok {
- send <- bs
- } else {
- return
- }
- case e := <-sinfo.reconfigure:
- e <- nil
- }
- }
-}
-
-// This encrypts a packet, creates a trafficPacket struct, encodes it, and sends it to router.out to pass it to the switch layer.
-func (sinfo *sessionInfo) doSend(bs []byte) {
- defer util.PutBytes(bs)
- if !sinfo.init {
- // To prevent using empty session keys
- return
- }
- // code isn't multithreaded so appending to this is safe
- coords := sinfo.coords
- // Work out the flowkey - this is used to determine which switch queue
- // traffic will be pushed to in the event of congestion
- var flowkey uint64
- // Get the IP protocol version from the packet
- switch bs[0] & 0xf0 {
- case 0x40: // IPv4 packet
- // Check the packet meets minimum UDP packet length
- if len(bs) >= 24 {
- // Is the protocol TCP, UDP or SCTP?
- if bs[9] == 0x06 || bs[9] == 0x11 || bs[9] == 0x84 {
- ihl := bs[0] & 0x0f * 4 // Header length
- flowkey = uint64(bs[9])<<32 /* proto */ |
- uint64(bs[ihl+0])<<24 | uint64(bs[ihl+1])<<16 /* sport */ |
- uint64(bs[ihl+2])<<8 | uint64(bs[ihl+3]) /* dport */
- }
- }
- case 0x60: // IPv6 packet
- // Check if the flowlabel was specified in the packet header
- flowkey = uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3])
- // If the flowlabel isn't present, make protokey from proto | sport | dport
- // if the packet meets minimum UDP packet length
- if flowkey == 0 && len(bs) >= 48 {
- // Is the protocol TCP, UDP or SCTP?
- if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 {
- flowkey = uint64(bs[6])<<32 /* proto */ |
- uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
- uint64(bs[42])<<8 | uint64(bs[43]) /* dport */
- }
- }
- }
- // If we have a flowkey, either through the IPv6 flowlabel field or through
- // known TCP/UDP/SCTP proto-sport-dport triplet, then append it to the coords.
- // Appending extra coords after a 0 ensures that we still target the local router
- // but lets us send extra data (which is otherwise ignored) to help separate
- // traffic streams into independent queues
- if flowkey != 0 {
- coords = append(coords, 0) // First target the local switchport
- coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey
- }
- // Prepare the payload
- payload, nonce := crypto.BoxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce)
- defer util.PutBytes(payload)
- p := wire_trafficPacket{
- Coords: coords,
- Handle: sinfo.theirHandle,
- Nonce: *nonce,
- Payload: payload,
- }
- packet := p.encode()
- sinfo.bytesSent += uint64(len(bs))
- sinfo.core.router.out(packet)
-}
-
-// This takes a trafficPacket and checks the nonce.
-// If the nonce is OK, it decrypts the packet.
-// If the decrypted packet is OK, it calls router.recvPacket to pass the packet to the tun/tap.
-// If a packet does not decrypt successfully, it assumes the packet was truncated, and updates the MTU accordingly.
-// TODO? remove the MTU updating part? That should never happen with TCP peers, and the old UDP code that caused it was removed (and if replaced, should be replaced with something that can reliably send messages with an arbitrary size).
-func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
- defer util.PutBytes(p.Payload)
- if !sinfo.nonceIsOK(&p.Nonce) {
- return
- }
- bs, isOK := crypto.BoxOpen(&sinfo.sharedSesKey, p.Payload, &p.Nonce)
- if !isOK {
- util.PutBytes(bs)
- return
- }
- sinfo.updateNonce(&p.Nonce)
- sinfo.time = time.Now()
- sinfo.bytesRecvd += uint64(len(bs))
- sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo}
-}
diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go
index 490b15f3..1bc40501 100644
--- a/src/yggdrasil/switch.go
+++ b/src/yggdrasil/switch.go
@@ -188,9 +188,7 @@ func (t *switchTable) init(core *Core) {
now := time.Now()
t.core = core
t.reconfigure = make(chan chan error, 1)
- t.core.configMutex.RLock()
t.key = t.core.sigPub
- t.core.configMutex.RUnlock()
locator := switchLocator{root: t.key, tstamp: now.Unix()}
peers := make(map[switchPort]peerInfo)
t.data = switchData{locator: locator, peers: peers}
@@ -579,23 +577,28 @@ func (t *switchTable) start() error {
return nil
}
+type closerInfo struct {
+ port switchPort
+ dist int
+}
+
// Return a map of ports onto distance, keeping only ports closer to the destination than this node
// If the map is empty (or nil), then no peer is closer
-func (t *switchTable) getCloser(dest []byte) map[switchPort]int {
+func (t *switchTable) getCloser(dest []byte) []closerInfo {
table := t.getTable()
myDist := table.self.dist(dest)
if myDist == 0 {
// Skip the iteration step if it's impossible to be closer
return nil
}
- closer := make(map[switchPort]int, len(table.elems))
+ t.queues.closer = t.queues.closer[:0]
for _, info := range table.elems {
dist := info.locator.dist(dest)
if dist < myDist {
- closer[info.port] = dist
+ t.queues.closer = append(t.queues.closer, closerInfo{info.port, dist})
}
}
- return closer
+ return t.queues.closer
}
// Returns true if the peer is closer to the destination than ourself
@@ -658,9 +661,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
var bestDist int
var bestTime time.Time
ports := t.core.peers.getPorts()
- for port, dist := range closer {
- to := ports[port]
- thisTime, isIdle := idle[port]
+ for _, cinfo := range closer {
+ to := ports[cinfo.port]
+ thisTime, isIdle := idle[cinfo.port]
var update bool
switch {
case to == nil:
@@ -669,9 +672,9 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
//nothing
case best == nil:
update = true
- case dist < bestDist:
+ case cinfo.dist < bestDist:
update = true
- case dist > bestDist:
+ case cinfo.dist > bestDist:
//nothing
case thisTime.Before(bestTime):
update = true
@@ -680,7 +683,7 @@ func (t *switchTable) handleIn(packet []byte, idle map[switchPort]time.Time) boo
}
if update {
best = to
- bestDist = dist
+ bestDist = cinfo.dist
bestTime = thisTime
}
}
@@ -713,6 +716,7 @@ type switch_buffers struct {
size uint64 // Total size of all buffers, in bytes
maxbufs int
maxsize uint64
+ closer []closerInfo // Scratch space
}
func (b *switch_buffers) cleanup(t *switchTable) {
diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go
index 8acf9c17..dfb41510 100644
--- a/src/yggdrasil/tcp.go
+++ b/src/yggdrasil/tcp.go
@@ -36,14 +36,18 @@ type tcp struct {
link *link
reconfigure chan chan error
mutex sync.Mutex // Protecting the below
- listeners map[string]*tcpListener
+ listeners map[string]*TcpListener
calls map[string]struct{}
conns map[linkInfo](chan struct{})
}
-type tcpListener struct {
- listener net.Listener
- stop chan bool
+// 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
+ Stop chan bool
}
// Wrapper function to set additional options for specific connection types.
@@ -64,7 +68,7 @@ func (t *tcp) getAddr() *net.TCPAddr {
t.mutex.Lock()
defer t.mutex.Unlock()
for _, l := range t.listeners {
- return l.listener.Addr().(*net.TCPAddr)
+ return l.Listener.Addr().(*net.TCPAddr)
}
return nil
}
@@ -76,16 +80,16 @@ func (t *tcp) init(l *link) error {
t.mutex.Lock()
t.calls = make(map[string]struct{})
t.conns = make(map[linkInfo](chan struct{}))
- t.listeners = make(map[string]*tcpListener)
+ t.listeners = make(map[string]*TcpListener)
t.mutex.Unlock()
go func() {
for {
e := <-t.reconfigure
- t.link.core.configMutex.RLock()
- added := util.Difference(t.link.core.config.Listen, t.link.core.configOld.Listen)
- deleted := util.Difference(t.link.core.configOld.Listen, t.link.core.config.Listen)
- t.link.core.configMutex.RUnlock()
+ t.link.core.config.Mutex.RLock()
+ added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen)
+ deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen)
+ t.link.core.config.Mutex.RUnlock()
if len(added) > 0 || len(deleted) > 0 {
for _, a := range added {
if a[:6] != "tcp://" {
@@ -103,7 +107,7 @@ func (t *tcp) init(l *link) error {
t.mutex.Lock()
if listener, ok := t.listeners[d[6:]]; ok {
t.mutex.Unlock()
- listener.stop <- true
+ listener.Stop <- true
} else {
t.mutex.Unlock()
}
@@ -115,9 +119,9 @@ func (t *tcp) init(l *link) error {
}
}()
- t.link.core.configMutex.RLock()
- defer t.link.core.configMutex.RUnlock()
- for _, listenaddr := range t.link.core.config.Listen {
+ t.link.core.config.Mutex.RLock()
+ defer t.link.core.config.Mutex.RUnlock()
+ for _, listenaddr := range t.link.core.config.Current.Listen {
if listenaddr[:6] != "tcp://" {
continue
}
@@ -129,7 +133,7 @@ func (t *tcp) init(l *link) error {
return nil
}
-func (t *tcp) listen(listenaddr string) (*tcpListener, error) {
+func (t *tcp) listen(listenaddr string) (*TcpListener, error) {
var err error
ctx := context.Background()
@@ -138,9 +142,9 @@ func (t *tcp) listen(listenaddr string) (*tcpListener, error) {
}
listener, err := lc.Listen(ctx, "tcp", listenaddr)
if err == nil {
- l := tcpListener{
- listener: listener,
- stop: make(chan bool),
+ l := TcpListener{
+ Listener: listener,
+ Stop: make(chan bool),
}
go t.listener(&l, listenaddr)
return &l, nil
@@ -150,7 +154,7 @@ func (t *tcp) listen(listenaddr string) (*tcpListener, error) {
}
// Runs the listener, which spawns off goroutines for incoming connections.
-func (t *tcp) listener(l *tcpListener, listenaddr string) {
+func (t *tcp) listener(l *TcpListener, listenaddr string) {
if l == nil {
return
}
@@ -158,7 +162,7 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) {
t.mutex.Lock()
if _, isIn := t.listeners[listenaddr]; isIn {
t.mutex.Unlock()
- l.listener.Close()
+ l.Listener.Close()
return
} else {
t.listeners[listenaddr] = l
@@ -167,20 +171,20 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) {
// And here we go!
accepted := make(chan bool)
defer func() {
- t.link.core.log.Infoln("Stopping TCP listener on:", l.listener.Addr().String())
- l.listener.Close()
+ t.link.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String())
+ l.Listener.Close()
t.mutex.Lock()
delete(t.listeners, listenaddr)
t.mutex.Unlock()
}()
- t.link.core.log.Infoln("Listening for TCP on:", l.listener.Addr().String())
+ t.link.core.log.Infoln("Listening for TCP on:", l.Listener.Addr().String())
for {
var sock net.Conn
var err error
// Listen in a separate goroutine, as that way it does not block us from
// receiving "stop" events
go func() {
- sock, err = l.listener.Accept()
+ sock, err = l.Listener.Accept()
accepted <- true
}()
// Wait for either an accepted connection, or a message telling us to stop
@@ -192,7 +196,7 @@ func (t *tcp) listener(l *tcpListener, listenaddr string) {
return
}
go t.handler(sock, true, nil)
- case <-l.stop:
+ case <-l.Stop:
return
}
}
@@ -251,13 +255,6 @@ func (t *tcp) call(saddr string, options interface{}, sintf string) {
if err != nil {
return
}
- conn = &wrappedConn{
- c: conn,
- raddr: &wrappedAddr{
- network: "tcp",
- addr: saddr,
- },
- }
t.handler(conn, false, dialerdst.String())
} else {
dst, err := net.ResolveTCPAddr("tcp", saddr)
diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go
deleted file mode 100644
index 465cbb1c..00000000
--- a/src/yggdrasil/tun.go
+++ /dev/null
@@ -1,266 +0,0 @@
-package yggdrasil
-
-// This manages the tun driver to send/recv packets to/from applications
-
-import (
- "bytes"
- "errors"
- "fmt"
- "net"
- "sync"
- "time"
-
- "github.com/songgao/packets/ethernet"
- "github.com/yggdrasil-network/water"
-
- "github.com/yggdrasil-network/yggdrasil-go/src/address"
- "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
- "github.com/yggdrasil-network/yggdrasil-go/src/util"
-)
-
-const tun_IPv6_HEADER_LENGTH = 40
-const tun_ETHER_HEADER_LENGTH = 14
-
-// Represents a running TUN/TAP interface.
-type tunAdapter struct {
- Adapter
- icmpv6 icmpv6
- mtu int
- iface *water.Interface
- mutex sync.RWMutex // Protects the below
- isOpen bool
-}
-
-// Gets the maximum supported MTU for the platform based on the defaults in
-// defaults.GetDefaults().
-func getSupportedMTU(mtu int) int {
- if mtu > defaults.GetDefaults().MaximumIfMTU {
- return defaults.GetDefaults().MaximumIfMTU
- }
- return mtu
-}
-
-// Initialises the TUN/TAP adapter.
-func (tun *tunAdapter) init(core *Core, send chan<- []byte, recv <-chan []byte) {
- tun.Adapter.init(core, send, recv)
- tun.icmpv6.init(tun)
- go func() {
- for {
- e := <-tun.reconfigure
- tun.core.configMutex.RLock()
- updated := tun.core.config.IfName != tun.core.configOld.IfName ||
- tun.core.config.IfTAPMode != tun.core.configOld.IfTAPMode ||
- tun.core.config.IfMTU != tun.core.configOld.IfMTU
- tun.core.configMutex.RUnlock()
- if updated {
- tun.core.log.Warnln("Reconfiguring TUN/TAP is not supported yet")
- e <- nil
- } else {
- e <- nil
- }
- }
- }()
-}
-
-// Starts the setup process for the TUN/TAP adapter, and if successful, starts
-// the read/write goroutines to handle packets on that interface.
-func (tun *tunAdapter) start() error {
- tun.core.configMutex.RLock()
- ifname := tun.core.config.IfName
- iftapmode := tun.core.config.IfTAPMode
- addr := fmt.Sprintf("%s/%d", net.IP(tun.core.router.addr[:]).String(), 8*len(address.GetPrefix())-1)
- mtu := tun.core.config.IfMTU
- tun.core.configMutex.RUnlock()
- if ifname != "none" {
- if err := tun.setup(ifname, iftapmode, addr, mtu); err != nil {
- return err
- }
- }
- if ifname == "none" || ifname == "dummy" {
- return nil
- }
- tun.mutex.Lock()
- tun.isOpen = true
- tun.mutex.Unlock()
- go func() { tun.core.log.Errorln("WARNING: tun.read() exited with error:", tun.read()) }()
- go func() { tun.core.log.Errorln("WARNING: tun.write() exited with error:", tun.write()) }()
- if iftapmode {
- go func() {
- for {
- if _, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok {
- break
- }
- request, err := tun.icmpv6.create_ndp_tap(tun.core.router.addr)
- if err != nil {
- panic(err)
- }
- if _, err := tun.iface.Write(request); err != nil {
- panic(err)
- }
- time.Sleep(time.Second)
- }
- }()
- }
- return nil
-}
-
-// Writes a packet to the TUN/TAP adapter. If the adapter is running in TAP
-// mode then additional ethernet encapsulation is added for the benefit of the
-// host operating system.
-func (tun *tunAdapter) write() error {
- for {
- data := <-tun.recv
- if tun.iface == nil {
- continue
- }
- if tun.iface.IsTAP() {
- var destAddr address.Address
- if data[0]&0xf0 == 0x60 {
- if len(data) < 40 {
- panic("Tried to send a packet shorter than an IPv6 header...")
- }
- copy(destAddr[:16], data[24:])
- } else if data[0]&0xf0 == 0x40 {
- if len(data) < 20 {
- panic("Tried to send a packet shorter than an IPv4 header...")
- }
- copy(destAddr[:4], data[16:])
- } else {
- return errors.New("Invalid address family")
- }
- sendndp := func(destAddr address.Address) {
- neigh, known := tun.icmpv6.peermacs[destAddr]
- known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30)
- if !known {
- request, err := tun.icmpv6.create_ndp_tap(destAddr)
- if err != nil {
- panic(err)
- }
- if _, err := tun.iface.Write(request); err != nil {
- panic(err)
- }
- tun.icmpv6.peermacs[destAddr] = neighbor{
- lastsolicitation: time.Now(),
- }
- }
- }
- var peermac macAddress
- var peerknown bool
- if data[0]&0xf0 == 0x40 {
- destAddr = tun.core.router.addr
- } else if data[0]&0xf0 == 0x60 {
- if !bytes.Equal(tun.core.router.addr[:16], destAddr[:16]) && !bytes.Equal(tun.core.router.subnet[:8], destAddr[:8]) {
- destAddr = tun.core.router.addr
- }
- }
- if neighbor, ok := tun.icmpv6.peermacs[destAddr]; ok && neighbor.learned {
- peermac = neighbor.mac
- peerknown = true
- } else if neighbor, ok := tun.icmpv6.peermacs[tun.core.router.addr]; ok && neighbor.learned {
- peermac = neighbor.mac
- peerknown = true
- sendndp(destAddr)
- } else {
- sendndp(tun.core.router.addr)
- }
- if peerknown {
- var proto ethernet.Ethertype
- switch {
- case data[0]&0xf0 == 0x60:
- proto = ethernet.IPv6
- case data[0]&0xf0 == 0x40:
- proto = ethernet.IPv4
- }
- var frame ethernet.Frame
- frame.Prepare(
- peermac[:6], // Destination MAC address
- tun.icmpv6.mymac[:6], // Source MAC address
- ethernet.NotTagged, // VLAN tagging
- proto, // Ethertype
- len(data)) // Payload length
- copy(frame[tun_ETHER_HEADER_LENGTH:], data[:])
- if _, err := tun.iface.Write(frame); err != nil {
- tun.mutex.RLock()
- open := tun.isOpen
- tun.mutex.RUnlock()
- if !open {
- return nil
- } else {
- panic(err)
- }
- }
- }
- } else {
- if _, err := tun.iface.Write(data); err != nil {
- tun.mutex.RLock()
- open := tun.isOpen
- tun.mutex.RUnlock()
- if !open {
- return nil
- } else {
- panic(err)
- }
- }
- }
- util.PutBytes(data)
- }
-}
-
-// Reads any packets that are waiting on the TUN/TAP adapter. If the adapter
-// is running in TAP mode then the ethernet headers will automatically be
-// processed and stripped if necessary. If an ICMPv6 packet is found, then
-// the relevant helper functions in icmpv6.go are called.
-func (tun *tunAdapter) read() error {
- mtu := tun.mtu
- if tun.iface.IsTAP() {
- mtu += tun_ETHER_HEADER_LENGTH
- }
- buf := make([]byte, mtu)
- for {
- n, err := tun.iface.Read(buf)
- if err != nil {
- tun.mutex.RLock()
- open := tun.isOpen
- tun.mutex.RUnlock()
- if !open {
- return nil
- } else {
- // panic(err)
- return err
- }
- }
- o := 0
- if tun.iface.IsTAP() {
- o = tun_ETHER_HEADER_LENGTH
- }
- switch {
- case buf[o]&0xf0 == 0x60 && n == 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o:
- case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o:
- default:
- continue
- }
- if buf[o+6] == 58 {
- if tun.iface.IsTAP() {
- // Found an ICMPv6 packet
- b := make([]byte, n)
- copy(b, buf)
- go tun.icmpv6.parse_packet(b)
- }
- }
- packet := append(util.GetBytes(), buf[o:n]...)
- tun.send <- packet
- }
-}
-
-// Closes the TUN/TAP adapter. This is only usually called when the Yggdrasil
-// process stops. Typically this operation will happen quickly, but on macOS
-// it can block until a read operation is completed.
-func (tun *tunAdapter) close() error {
- tun.mutex.Lock()
- tun.isOpen = false
- tun.mutex.Unlock()
- if tun.iface == nil {
- return nil
- }
- return tun.iface.Close()
-}
diff --git a/src/yggdrasil/tun_dummy.go b/src/yggdrasil/tun_dummy.go
deleted file mode 100644
index 234ab1de..00000000
--- a/src/yggdrasil/tun_dummy.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// +build mobile
-
-package yggdrasil
-
-// This is to catch unsupported platforms
-// If your platform supports tun devices, you could try configuring it manually
-
-// Creates the TUN/TAP adapter, if supported by the Water library. Note that
-// no guarantees are made at this point on an unsupported platform.
-func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
- tun.mtu = getSupportedMTU(mtu)
- 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 {
- return nil
-}
diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go
index 8891f1ab..5aa354dc 100644
--- a/src/yggdrasil/wire.go
+++ b/src/yggdrasil/wire.go
@@ -72,13 +72,16 @@ func wire_decode_uint64(bs []byte) (uint64, int) {
// Non-negative integers are mapped to even integers: 0 -> 0, 1 -> 2, etc.
// Negative integers are mapped to odd integers: -1 -> 1, -2 -> 3, etc.
// This means the least significant bit is a sign bit.
+// This is known as zigzag encoding.
func wire_intToUint(i int64) uint64 {
- return ((uint64(-(i+1))<<1)|0x01)*(uint64(i)>>63) + (uint64(i)<<1)*(^uint64(i)>>63)
+ // signed arithmetic shift
+ return uint64((i >> 63) ^ (i << 1))
}
// Converts uint64 back to int64, genreally when being read from the wire.
func wire_intFromUint(u uint64) int64 {
- return int64(u&0x01)*(-int64(u>>1)-1) + int64(^u&0x01)*int64(u>>1)
+ // non-arithmetic shift
+ return int64((u >> 1) ^ -(u & 1))
}
////////////////////////////////////////////////////////////////////////////////
@@ -391,7 +394,7 @@ func (p *nodeinfoReqRes) decode(bs []byte) bool {
if len(bs) == 0 {
return false
}
- p.NodeInfo = make(nodeinfoPayload, len(bs))
+ p.NodeInfo = make(NodeInfoPayload, len(bs))
if !wire_chop_slice(p.NodeInfo[:], &bs) {
return false
}