diff --git a/.circleci/config.yml b/.circleci/config.yml
index 8725e44..82a68aa 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -48,14 +48,12 @@ jobs:
command: |
rm -f {yggdrasil,yggdrasilctl}
GOOS=darwin GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64;
- GOOS=darwin GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-i386;
- run:
name: Build for macOS (.pkg format)
command: |
rm -rf {yggdrasil,yggdrasilctl}
GOOS=darwin GOARCH=amd64 ./build && PKGARCH=amd64 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/
- GOOS=darwin GOARCH=386 ./build && PKGARCH=i386 sh contrib/macos/create-pkg.sh && mv *.pkg /tmp/upload/
- run:
name: Build for OpenBSD
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84b5c38..6c290c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities.
-->
+## [0.3.3] - 2018-02-18
+### Added
+- Dynamic reconfiguration, which allows reloading the configuration file to make changes during runtime by sending a `SIGHUP` signal (note: this only works with `-useconffile` and not `-useconf` and currently reconfiguring TUN/TAP is not supported)
+- Support for building Yggdrasil as an iOS or Android framework if the appropriate tools (e.g. `gomobile`/`gobind` + SDKs) are available
+- Connection contexts used for TCP connections which allow more exotic socket options to be set, e.g.
+ - Reusing the multicast socket to allow multiple running Yggdrasil instances without having to disable multicast
+ - Allowing supported Macs to peer with other nearby Macs that aren't even on the same Wi-Fi network using AWDL
+- Flexible logging support, which allows for logging at different levels of verbosity
+
+### Changed
+- Switch changes to improve parent selection
+- Node configuration is now stored centrally, rather than having fragments/copies distributed at startup time
+- Significant refactoring in various areas, including for link types (TCP, AWDL etc), generic streams and adapters
+- macOS builds through CircleCI are now 64-bit only
+
+### Fixed
+- Simplified `systemd` service now in `contrib`
+
+### Removed
+- `ReadTimeout` option is now deprecated
+
## [0.3.2] - 2018-12-26
### Added
- The admin socket is now multithreaded, greatly improving performance of the crawler and allowing concurrent lookups to take place
diff --git a/build b/build
index e463c85..f6c7246 100755
--- a/build
+++ b/build
@@ -1,17 +1,21 @@
#!/bin/sh
+set -ef
+
PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil}
PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)}
PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
-while getopts "udtc:l:" option
+while getopts "udaitc:l:" option
do
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";;
@@ -22,15 +26,23 @@ if [ -z $TABLES ]; then
STRIP="-s -w"
fi
-for CMD in `ls cmd/` ; do
- echo "Building: $CMD"
+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
+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
+else
+ for CMD in `ls cmd/` ; do
+ echo "Building: $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
-done
+ 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
+ done
+fi
diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go
index 2b6d2f0..aa5a749 100644
--- a/cmd/yggdrasil/main.go
+++ b/cmd/yggdrasil/main.go
@@ -2,28 +2,23 @@ package main
import (
"bytes"
- "encoding/hex"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
- "log"
- "math/rand"
"os"
"os/signal"
- "regexp"
"strings"
"syscall"
- "time"
"golang.org/x/text/encoding/unicode"
+ "github.com/gologme/log"
"github.com/hjson/hjson-go"
"github.com/kardianos/minwinsvc"
"github.com/mitchellh/mapstructure"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
- "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil"
)
@@ -34,52 +29,124 @@ type node struct {
core Core
}
-// Generates default configuration. This is used when outputting the -genconf
-// parameter and also when using -autoconf. The isAutoconf flag is used to
-// determine whether the operating system should select a free port by itself
-// (which guarantees that there will not be a conflict with any other services)
-// or whether to generate a random port number. The only side effect of setting
-// isAutoconf is that the TCP and UDP ports will likely end up with different
-// port numbers.
-func generateConfig(isAutoconf bool) *nodeConfig {
- // Create a new core.
- core := Core{}
- // Generate encryption keys.
- bpub, bpriv := core.NewEncryptionKeys()
- spub, spriv := core.NewSigningKeys()
- // Create a node configuration and populate it.
- cfg := nodeConfig{}
- if isAutoconf {
- cfg.Listen = "[::]:0"
+func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig {
+ // Use a configuration file. If -useconf, the configuration will be read
+ // from stdin. If -useconffile, the configuration will be read from the
+ // filesystem.
+ var conf []byte
+ var err error
+ if *useconffile != "" {
+ // Read the file from the filesystem
+ conf, err = ioutil.ReadFile(*useconffile)
} else {
- r1 := rand.New(rand.NewSource(time.Now().UnixNano()))
- cfg.Listen = fmt.Sprintf("[::]:%d", r1.Intn(65534-32768)+32768)
+ // Read the file from stdin.
+ conf, err = ioutil.ReadAll(os.Stdin)
+ }
+ if err != nil {
+ panic(err)
+ }
+ // If there's a byte order mark - which Windows 10 is now incredibly fond of
+ // throwing everywhere when it's converting things into UTF-16 for the hell
+ // of it - remove it and decode back down into UTF-8. This is necessary
+ // because hjson doesn't know what to do with UTF-16 and will panic
+ if bytes.Compare(conf[0:2], []byte{0xFF, 0xFE}) == 0 ||
+ bytes.Compare(conf[0:2], []byte{0xFE, 0xFF}) == 0 {
+ utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
+ decoder := utf.NewDecoder()
+ conf, err = decoder.Bytes(conf)
+ if err != nil {
+ panic(err)
+ }
+ }
+ // Generate a new configuration - this gives us a set of sane defaults -
+ // then parse the configuration we loaded above on top of it. The effect
+ // of this is that any configuration item that is missing from the provided
+ // configuration will use a sane default.
+ cfg := config.GenerateConfig(false)
+ var dat map[string]interface{}
+ if err := hjson.Unmarshal(conf, &dat); err != nil {
+ panic(err)
+ }
+ confJson, err := json.Marshal(dat)
+ if err != nil {
+ 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
+ }
+ }
+ }
+ // Overlay our newly mapped configuration onto the autoconf node config that
+ // we generated above.
+ if err = mapstructure.Decode(dat, &cfg); err != nil {
+ panic(err)
}
- cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen
- cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
- cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
- cfg.SigningPublicKey = hex.EncodeToString(spub[:])
- cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
- cfg.Peers = []string{}
- cfg.InterfacePeers = map[string][]string{}
- cfg.AllowedEncryptionPublicKeys = []string{}
- cfg.MulticastInterfaces = []string{".*"}
- cfg.IfName = defaults.GetDefaults().DefaultIfName
- cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
- cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
- cfg.SessionFirewall.Enable = false
- cfg.SessionFirewall.AllowFromDirect = true
- cfg.SessionFirewall.AllowFromRemote = true
- cfg.SwitchOptions.MaxTotalQueueSize = yggdrasil.SwitchQueueTotalMinSize
- cfg.NodeInfoPrivacy = false
- return &cfg
+ return cfg
}
// Generates a new configuration and returns it in HJSON format. This is used
// with -genconf.
func doGenconf(isjson bool) string {
- cfg := generateConfig(false)
+ cfg := config.GenerateConfig(false)
var bs []byte
var err error
if isjson {
@@ -103,9 +170,11 @@ func main() {
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
version := flag.Bool("version", false, "prints the version of this build")
+ logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable")
flag.Parse()
var cfg *nodeConfig
+ var err error
switch {
case *version:
fmt.Println("Build name:", yggdrasil.GetBuildName())
@@ -114,116 +183,10 @@ func main() {
case *autoconf:
// Use an autoconf-generated config, this will give us random keys and
// port numbers, and will use an automatically selected TUN/TAP interface.
- cfg = generateConfig(true)
+ cfg = config.GenerateConfig(true)
case *useconffile != "" || *useconf:
- // Use a configuration file. If -useconf, the configuration will be read
- // from stdin. If -useconffile, the configuration will be read from the
- // filesystem.
- var config []byte
- var err error
- if *useconffile != "" {
- // Read the file from the filesystem
- config, err = ioutil.ReadFile(*useconffile)
- } else {
- // Read the file from stdin.
- config, err = ioutil.ReadAll(os.Stdin)
- }
- if err != nil {
- panic(err)
- }
- // If there's a byte order mark - which Windows 10 is now incredibly fond of
- // throwing everywhere when it's converting things into UTF-16 for the hell
- // of it - remove it and decode back down into UTF-8. This is necessary
- // because hjson doesn't know what to do with UTF-16 and will panic
- if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 ||
- bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 {
- utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
- decoder := utf.NewDecoder()
- config, err = decoder.Bytes(config)
- if err != nil {
- panic(err)
- }
- }
- // Generate a new configuration - this gives us a set of sane defaults -
- // then parse the configuration we loaded above on top of it. The effect
- // of this is that any configuration item that is missing from the provided
- // configuration will use a sane default.
- cfg = generateConfig(false)
- var dat map[string]interface{}
- if err := hjson.Unmarshal(config, &dat); err != nil {
- panic(err)
- }
- confJson, err := json.Marshal(dat)
- if err != nil {
- 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": "",
- "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: Deprecated config option", from, "- please remove")
- }
- } else {
- if !*normaliseconf {
- log.Println("Warning: Deprecated config option", from, "- please rename 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
- }
- }
- }
- // Overlay our newly mapped configuration onto the autoconf node config that
- // we generated above.
- if err = mapstructure.Decode(dat, &cfg); err != nil {
- panic(err)
- }
+ // Read the configuration from either stdin or from the filesystem
+ cfg = readConfig(useconf, useconffile, normaliseconf)
// If the -normaliseconf option was specified then remarshal the above
// configuration and print it back to stdout. This lets the user update
// their configuration file with newly mapped names (like above) or to
@@ -256,51 +219,30 @@ func main() {
}
// Create a new logger that logs output to stdout.
logger := log.New(os.Stdout, "", log.Flags())
+ //logger.EnableLevel("error")
+ //logger.EnableLevel("warn")
+ //logger.EnableLevel("info")
+ if levels := strings.Split(*logging, ","); len(levels) > 0 {
+ for _, level := range levels {
+ l := strings.TrimSpace(level)
+ switch l {
+ case "error", "warn", "info", "trace", "debug":
+ logger.EnableLevel(l)
+ default:
+ continue
+ }
+ }
+ }
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
// don't need to create this manually.
n := node{}
- // Check to see if any multicast interface expressions were provided in the
- // config. If they were then set them now.
- for _, ll := range cfg.MulticastInterfaces {
- ifceExpr, err := regexp.Compile(ll)
- if err != nil {
- panic(err)
- }
- n.core.AddMulticastInterfaceExpr(ifceExpr)
- }
// 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 {
- logger.Println("An error occurred during startup")
+ logger.Errorln("An error occurred during startup")
panic(err)
}
- // Check to see if any allowed encryption keys were provided in the config.
- // If they were then set them now.
- for _, pBoxStr := range cfg.AllowedEncryptionPublicKeys {
- n.core.AddAllowedEncryptionPublicKey(pBoxStr)
- }
- // If any static peers were provided in the configuration above then we should
- // configure them. The loop ensures that disconnected peers will eventually
- // be reconnected with.
- go func() {
- if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 {
- return
- }
- for {
- for _, peer := range cfg.Peers {
- n.core.AddPeer(peer, "")
- time.Sleep(time.Second)
- }
- for intf, intfpeers := range cfg.InterfacePeers {
- for _, peer := range intfpeers {
- n.core.AddPeer(peer, intf)
- time.Sleep(time.Second)
- }
- }
- time.Sleep(time.Minute)
- }
- }()
// The Stop function ensures that the TUN/TAP adapter is correctly shut down
// before the program exits.
defer func() {
@@ -310,11 +252,13 @@ func main() {
// This is just logged to stdout for the user.
address := n.core.GetAddress()
subnet := n.core.GetSubnet()
- logger.Printf("Your IPv6 address is %s", address.String())
- logger.Printf("Your IPv6 subnet is %s", subnet.String())
+ 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.
c := make(chan os.Signal, 1)
+ 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
@@ -322,5 +266,18 @@ func main() {
minwinsvc.SetOnExit(winTerminate)
// Wait for the terminate/interrupt signal. Once a signal is received, the
// deferred Stop function above will run which will shut down TUN/TAP.
- <-c
+ for {
+ select {
+ case _ = <-r:
+ if *useconffile != "" {
+ cfg = readConfig(useconf, useconffile, normaliseconf)
+ n.core.UpdateConfig(cfg)
+ } else {
+ logger.Errorln("Reloading config at runtime is only possible with -useconffile")
+ }
+ case _ = <-c:
+ goto exit
+ }
+ }
+exit:
}
diff --git a/contrib/ansible/genkeys.go b/contrib/ansible/genkeys.go
new file mode 100644
index 0000000..5213e8b
--- /dev/null
+++ b/contrib/ansible/genkeys.go
@@ -0,0 +1,125 @@
+/*
+
+This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/)
+
+*/
+package main
+
+import (
+ "encoding/hex"
+ "flag"
+ "fmt"
+ "net"
+ "os"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/address"
+ "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
+)
+
+var numHosts = flag.Int("hosts", 1, "number of host vars to generate")
+var keyTries = flag.Int("tries", 1000, "number of tries before taking the best keys")
+
+type keySet struct {
+ priv []byte
+ pub []byte
+ id []byte
+ ip string
+}
+
+func main() {
+ flag.Parse()
+
+ if *numHosts > *keyTries {
+ println("Can't generate less keys than hosts.")
+ return
+ }
+
+ var encryptionKeys []keySet
+ for i := 0; i < *numHosts+1; i++ {
+ encryptionKeys = append(encryptionKeys, newBoxKey())
+ }
+ encryptionKeys = sortKeySetArray(encryptionKeys)
+ for i := 0; i < *keyTries-*numHosts-1; i++ {
+ encryptionKeys[0] = newBoxKey()
+ encryptionKeys = bubbleUpTo(encryptionKeys, 0)
+ }
+
+ var signatureKeys []keySet
+ for i := 0; i < *numHosts+1; i++ {
+ signatureKeys = append(signatureKeys, newSigKey())
+ }
+ signatureKeys = sortKeySetArray(signatureKeys)
+ for i := 0; i < *keyTries-*numHosts-1; i++ {
+ signatureKeys[0] = newSigKey()
+ signatureKeys = bubbleUpTo(signatureKeys, 0)
+ }
+
+ os.MkdirAll("host_vars", 0755)
+
+ for i := 1; i <= *numHosts; i++ {
+ os.MkdirAll(fmt.Sprintf("host_vars/%x", i), 0755)
+ file, err := os.Create(fmt.Sprintf("host_vars/%x/vars", i))
+ if err != nil {
+ return
+ }
+ defer file.Close()
+ file.WriteString(fmt.Sprintf("yggdrasil_encryption_public_key: %v\n", hex.EncodeToString(encryptionKeys[i].pub)))
+ file.WriteString("yggdrasil_encryption_private_key: \"{{ vault_yggdrasil_encryption_private_key }}\"\n")
+ file.WriteString(fmt.Sprintf("yggdrasil_signing_public_key: %v\n", hex.EncodeToString(signatureKeys[i].pub)))
+ file.WriteString("yggdrasil_signing_private_key: \"{{ vault_yggdrasil_signing_private_key }}\"\n")
+ file.WriteString(fmt.Sprintf("ansible_host: %v\n", encryptionKeys[i].ip))
+
+ file, err = os.Create(fmt.Sprintf("host_vars/%x/vault", i))
+ if err != nil {
+ return
+ }
+ defer file.Close()
+ file.WriteString(fmt.Sprintf("vault_yggdrasil_encryption_private_key: %v\n", hex.EncodeToString(encryptionKeys[i].priv)))
+ file.WriteString(fmt.Sprintf("vault_yggdrasil_signing_private_key: %v\n", hex.EncodeToString(signatureKeys[i].priv)))
+ }
+}
+
+func newBoxKey() keySet {
+ pub, priv := crypto.NewBoxKeys()
+ id := crypto.GetNodeID(pub)
+ ip := net.IP(address.AddrForNodeID(id)[:]).String()
+ return keySet{priv[:], pub[:], id[:], ip}
+}
+
+func newSigKey() keySet {
+ pub, priv := crypto.NewSigKeys()
+ id := crypto.GetTreeID(pub)
+ return keySet{priv[:], pub[:], id[:], ""}
+}
+
+func isBetter(oldID, newID []byte) bool {
+ for idx := range oldID {
+ if newID[idx] > oldID[idx] {
+ return true
+ }
+ if newID[idx] < oldID[idx] {
+ return false
+ }
+ }
+ return false
+}
+
+func sortKeySetArray(sets []keySet) []keySet {
+ for i := 0; i < len(sets); i++ {
+ sets = bubbleUpTo(sets, i)
+ }
+ return sets
+}
+
+func bubbleUpTo(sets []keySet, num int) []keySet {
+ for i := 0; i < len(sets)-num-1; i++ {
+ if isBetter(sets[i+1].id, sets[i].id) {
+ var tmp = sets[i]
+ sets[i] = sets[i+1]
+ sets[i+1] = tmp
+ } else {
+ break
+ }
+ }
+ return sets
+}
diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh
index 6c8f955..4433a9d 100644
--- a/contrib/deb/generate.sh
+++ b/contrib/deb/generate.sh
@@ -110,12 +110,10 @@ EOF
cp yggdrasil /tmp/$PKGNAME/usr/bin/
cp yggdrasilctl /tmp/$PKGNAME/usr/bin/
cp contrib/systemd/yggdrasil.service /tmp/$PKGNAME/etc/systemd/system/
-cp contrib/systemd/yggdrasil-resume.service /tmp/$PKGNAME/etc/systemd/system/
tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \
usr/bin/yggdrasil usr/bin/yggdrasilctl \
- etc/systemd/system/yggdrasil.service \
- etc/systemd/system/yggdrasil-resume.service
+ etc/systemd/system/yggdrasil.service
tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
echo 2.0 > /tmp/$PKGNAME/debian-binary
diff --git a/contrib/logo/ygg-neilalexander.svg b/contrib/logo/ygg-neilalexander.svg
new file mode 100644
index 0000000..d222200
--- /dev/null
+++ b/contrib/logo/ygg-neilalexander.svg
@@ -0,0 +1,157 @@
+
+
+
+
diff --git a/contrib/semver/name.sh b/contrib/semver/name.sh
index 935cc75..1fa2ce0 100644
--- a/contrib/semver/name.sh
+++ b/contrib/semver/name.sh
@@ -4,8 +4,8 @@
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
# Complain if the git history is not available
-if [ $? != 0 ]; then
- printf "unknown"
+if [ $? != 0 ] || [ -z "$BRANCH" ]; then
+ printf "yggdrasil"
exit 1
fi
diff --git a/contrib/semver/version.sh b/contrib/semver/version.sh
index 964a320..3052094 100644
--- a/contrib/semver/version.sh
+++ b/contrib/semver/version.sh
@@ -1,63 +1,46 @@
#!/bin/sh
-# Merge commits from this branch are counted
-DEVELOPBRANCH="yggdrasil-network/develop"
-
# Get the last tag
-TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.0" 2>/dev/null)
+TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" 2>/dev/null)
-# Get last merge to master
-MERGE=$(git rev-list $TAG..master --grep "from $DEVELOPBRANCH" 2>/dev/null | head -n 1)
-
-# Get the number of merges since the last merge to master
-PATCH=$(git rev-list $TAG..master --count --merges --grep="from $DEVELOPBRANCH" --first-parent master 2>/dev/null)
-
-# Decide whether we should prepend the version with "v" - the default is that
-# we do because we use it in git tags, but we might not always need it
-PREPEND="v"
-if [ "$1" = "--bare" ]; then
- PREPEND=""
+# Did getting the tag succeed?
+if [ $? != 0 ] || [ -z "$TAG" ]; then
+ printf -- "unknown"
+ exit 1
fi
-# If it fails then there's no last tag - go from the first commit
-if [ $? != 0 ]; then
- PATCH=$(git rev-list HEAD --count 2>/dev/null)
+# Get the current branch
+BRANCH=$(git symbolic-ref -q HEAD --short 2>/dev/null)
- # Complain if the git history is not available
- if [ $? != 0 ]; then
- printf 'unknown'
- exit 1
- fi
-
- printf '%s0.0.%d' "$PREPEND" "$PATCH"
- exit 1
+# Did getting the branch succeed?
+if [ $? != 0 ] || [ -z "$BRANCH" ]; then
+ BRANCH="master"
fi
# Split out into major, minor and patch numbers
MAJOR=$(echo $TAG | cut -c 2- | cut -d "." -f 1)
MINOR=$(echo $TAG | cut -c 2- | cut -d "." -f 2)
-
-# Get the current checked out branch
-BRANCH=$(git rev-parse --abbrev-ref HEAD)
+PATCH=$(echo $TAG | cut -c 2- | cut -d "." -f 3)
# Output in the desired format
-if [ $PATCH = 0 ]; then
- if [ ! -z $FULL ]; then
- printf '%s%d.%d.0' "$PREPEND" "$MAJOR" "$MINOR"
- else
- printf '%s%d.%d' "$PREPEND" "$MAJOR" "$MINOR"
- fi
+if [ $((PATCH)) -eq 0 ]; then
+ printf '%s%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))"
else
- printf '%s%d.%d.%d' "$PREPEND" "$MAJOR" "$MINOR" "$PATCH"
+ printf '%s%d.%d.%d' "$PREPEND" "$((MAJOR))" "$((MINOR))" "$((PATCH))"
fi
-# Get the number of merges on the current branch since the last tag
-TAG=$(git describe --abbrev=0 --tags --match="v[0-9]*\.[0-9]*\.[0-9]*" --first-parent master 2>/dev/null)
-BUILD=$(git rev-list $TAG.. --count)
-
# Add the build tag on non-master branches
-if [ $BRANCH != "master" ]; then
- if [ $BUILD != 0 ]; then
- printf -- "-%04d" "$BUILD"
+if [ "$BRANCH" != "master" ]; then
+ BUILD=$(git rev-list --count $TAG..HEAD 2>/dev/null)
+
+ # Did getting the count of commits since the tag succeed?
+ if [ $? != 0 ] || [ -z "$BUILD" ]; then
+ printf -- "-unknown"
+ exit 1
+ fi
+
+ # Is the build greater than zero?
+ if [ $((BUILD)) -gt 0 ]; then
+ printf -- "-%04d" "$((BUILD))"
fi
fi
diff --git a/contrib/systemd/yggdrasil-resume.service b/contrib/systemd/yggdrasil-resume.service
deleted file mode 100644
index c725127..0000000
--- a/contrib/systemd/yggdrasil-resume.service
+++ /dev/null
@@ -1,10 +0,0 @@
-[Unit]
-Description=Restart yggdrasil on resume from sleep
-After=sleep.target
-
-[Service]
-Type=oneshot
-ExecStart=/bin/systemctl restart yggdrasil
-
-[Install]
-WantedBy=sleep.target
diff --git a/contrib/systemd/yggdrasil.service b/contrib/systemd/yggdrasil.service
index 1b6f1f0..bd52ec9 100644
--- a/contrib/systemd/yggdrasil.service
+++ b/contrib/systemd/yggdrasil.service
@@ -14,8 +14,8 @@ ExecStartPre=/bin/sh -ec "if ! test -s /etc/yggdrasil.conf; \
echo 'WARNING: A new /etc/yggdrasil.conf file has been generated.'; \
fi"
ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf
+ExecReload=/bin/kill -HUP $MAINPID
Restart=always
[Install]
WantedBy=multi-user.target
-Also=yggdrasil-resume.service
diff --git a/go.mod b/go.mod
index 53a5a2b..995e54c 100644
--- a/go.mod
+++ b/go.mod
@@ -2,6 +2,7 @@ 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/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222
github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0
github.com/mitchellh/mapstructure v1.1.2
diff --git a/go.sum b/go.sum
index 1695daf..92dfe88 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0=
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
+github.com/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/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=
diff --git a/misc/run-schannel-netns b/misc/run-schannel-netns
index 9723e73..74a0294 100755
--- a/misc/run-schannel-netns
+++ b/misc/run-schannel-netns
@@ -51,12 +51,12 @@ ip netns exec node4 ip link set lo up
ip netns exec node5 ip link set lo up
ip netns exec node6 ip link set lo up
-ip netns exec node1 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
-ip netns exec node2 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
-ip netns exec node3 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
-ip netns exec node4 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
-ip netns exec node5 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
-ip netns exec node6 env PPROFLISTEN=localhost:6060 ./run --autoconf &> /dev/null &
+echo '{AdminListen: "none"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
+echo '{AdminListen: "none"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
+echo '{AdminListen: "none"}' | ip netns exec node3 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
+echo '{AdminListen: "none"}' | ip netns exec node4 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
+echo '{AdminListen: "none"}' | ip netns exec node5 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
+echo '{AdminListen: "none"}' | ip netns exec node6 env PPROFLISTEN=localhost:6060 ./yggdrasil --useconf &> /dev/null &
echo "Started, to continue you should (possibly w/ sudo):"
echo "kill" $(jobs -p)
diff --git a/misc/run-twolink-test b/misc/run-twolink-test
new file mode 100755
index 0000000..987b6de
--- /dev/null
+++ b/misc/run-twolink-test
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Connects nodes in two namespaces by two links with different bandwidth (10mbit and 100mbit)
+
+ip netns add node1
+ip netns add node2
+
+ip link add veth11 type veth peer name veth21
+ip link set veth11 netns node1 up
+ip link set veth21 netns node2 up
+
+ip link add veth12 type veth peer name veth22
+ip link set veth12 netns node1 up
+ip link set veth22 netns node2 up
+
+ip netns exec node1 tc qdisc add dev veth11 root tbf rate 10mbit burst 8192 latency 1ms
+ip netns exec node2 tc qdisc add dev veth21 root tbf rate 10mbit burst 8192 latency 1ms
+
+ip netns exec node1 tc qdisc add dev veth12 root tbf rate 100mbit burst 8192 latency 1ms
+ip netns exec node2 tc qdisc add dev veth22 root tbf rate 100mbit burst 8192 latency 1ms
+
+echo '{AdminListen: "unix://node1.sock"}' | ip netns exec node1 env PPROFLISTEN=localhost:6060 ./yggdrasil -logging "info,warn,error,debug" -useconf &> node1.log &
+echo '{AdminListen: "unix://node2.sock"}' | ip netns exec node2 env PPROFLISTEN=localhost:6060 ./yggdrasil -logging "info,warn,error,debug" -useconf &> node2.log &
+
+echo "Started, to continue you should (possibly w/ sudo):"
+echo "kill" $(jobs -p)
+wait
+
+ip netns delete node1
+ip netns delete node2
+
+ip link delete veth11
+ip link delete veth12
diff --git a/src/config/config.go b/src/config/config.go
index 192f435..14b1649 100644
--- a/src/config/config.go
+++ b/src/config/config.go
@@ -1,12 +1,21 @@
package config
+import (
+ "encoding/hex"
+ "fmt"
+ "math/rand"
+ "time"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
+ "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
+)
+
// NodeConfig defines all configuration values needed to run a signle yggdrasil node
type NodeConfig struct {
Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."`
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."`
InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."`
- ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."`
AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."`
EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."`
EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"`
@@ -53,3 +62,45 @@ type TunnelRouting struct {
type SwitchOptions struct {
MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."`
}
+
+// Generates default configuration. This is used when outputting the -genconf
+// parameter and also when using -autoconf. The isAutoconf flag is used to
+// determine whether the operating system should select a free port by itself
+// (which guarantees that there will not be a conflict with any other services)
+// or whether to generate a random port number. The only side effect of setting
+// isAutoconf is that the TCP and UDP ports will likely end up with different
+// port numbers.
+func GenerateConfig(isAutoconf bool) *NodeConfig {
+ // Create a new core.
+ //core := Core{}
+ // Generate encryption keys.
+ bpub, bpriv := crypto.NewBoxKeys()
+ spub, spriv := crypto.NewSigKeys()
+ // Create a node configuration and populate it.
+ cfg := NodeConfig{}
+ if isAutoconf {
+ cfg.Listen = "[::]:0"
+ } else {
+ r1 := rand.New(rand.NewSource(time.Now().UnixNano()))
+ cfg.Listen = fmt.Sprintf("[::]:%d", r1.Intn(65534-32768)+32768)
+ }
+ cfg.AdminListen = defaults.GetDefaults().DefaultAdminListen
+ cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:])
+ cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:])
+ cfg.SigningPublicKey = hex.EncodeToString(spub[:])
+ cfg.SigningPrivateKey = hex.EncodeToString(spriv[:])
+ cfg.Peers = []string{}
+ cfg.InterfacePeers = map[string][]string{}
+ cfg.AllowedEncryptionPublicKeys = []string{}
+ cfg.MulticastInterfaces = []string{".*"}
+ cfg.IfName = defaults.GetDefaults().DefaultIfName
+ cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU
+ cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode
+ cfg.SessionFirewall.Enable = false
+ cfg.SessionFirewall.AllowFromDirect = true
+ cfg.SessionFirewall.AllowFromRemote = true
+ cfg.SwitchOptions.MaxTotalQueueSize = 4 * 1024 * 1024
+ cfg.NodeInfoPrivacy = false
+
+ return &cfg
+}
diff --git a/src/util/util.go b/src/util/util.go
index 65e6d46..45be3b1 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 "time"
// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere.
func Yield() {
@@ -44,3 +45,14 @@ func PutBytes(bs []byte) {
default:
}
}
+
+// This is a workaround to go's broken timer implementation
+func TimerStop(t *time.Timer) bool {
+ if !t.Stop() {
+ select {
+ case <-t.C:
+ default:
+ }
+ }
+ return true
+}
diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go
index 4a43209..3ce80d2 100644
--- a/src/yggdrasil/adapter.go
+++ b/src/yggdrasil/adapter.go
@@ -1,20 +1,12 @@
package yggdrasil
-// Defines the minimum required functions for an adapter type.
-type AdapterInterface interface {
- init(core *Core, send chan<- []byte, recv <-chan []byte)
- read() error
- write() error
- close() error
-}
-
// Defines the minimum required struct members for an adapter type (this is
// now the base type for tunAdapter in tun.go)
type Adapter struct {
- AdapterInterface
- core *Core
- send chan<- []byte
- recv <-chan []byte
+ core *Core
+ send chan<- []byte
+ recv <-chan []byte
+ reconfigure chan chan error
}
// Initialises the adapter.
@@ -22,4 +14,5 @@ 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
index bd3c905..f8347f0 100644
--- a/src/yggdrasil/admin.go
+++ b/src/yggdrasil/admin.go
@@ -22,10 +22,11 @@ import (
// TODO: Add authentication
type admin struct {
- core *Core
- listenaddr string
- listener net.Listener
- handlers []admin_handlerInfo
+ core *Core
+ reconfigure chan chan error
+ listenaddr string
+ listener net.Listener
+ handlers []admin_handlerInfo
}
type admin_info map[string]interface{}
@@ -51,9 +52,25 @@ func (a *admin) addHandler(name string, args []string, handler func(admin_info)
}
// init runs the initial admin setup.
-func (a *admin) init(c *Core, listenaddr string) {
+func (a *admin) init(c *Core) {
a.core = c
- a.listenaddr = listenaddr
+ 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 {
@@ -324,12 +341,30 @@ func (a *admin) init(c *Core, listenaddr string) {
return admin_info{}, err
}
})
- a.addHandler("getNodeInfo", []string{"box_pub_key", "coords", "[nocache]"}, func(in admin_info) (admin_info, error) {
+ 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"
}
- result, err := a.admin_getNodeInfo(in["box_pub_key"].(string), in["coords"].(string), nocache)
+ 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 {
@@ -353,7 +388,11 @@ func (a *admin) start() error {
// cleans up when stopping
func (a *admin) close() error {
- return a.listener.Close()
+ if a.listener != nil {
+ return a.listener.Close()
+ } else {
+ return nil
+ }
}
// listen is run by start and manages API connections.
@@ -363,7 +402,7 @@ func (a *admin) listen() {
switch strings.ToLower(u.Scheme) {
case "unix":
if _, err := os.Stat(a.listenaddr[7:]); err == nil {
- a.core.log.Println("WARNING:", a.listenaddr[7:], "already exists and may be in use by another process")
+ a.core.log.Warnln("WARNING:", a.listenaddr[7:], "already exists and may be in use by another process")
}
a.listener, err = net.Listen("unix", a.listenaddr[7:])
if err == nil {
@@ -371,7 +410,7 @@ func (a *admin) listen() {
case "@": // maybe abstract namespace
default:
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
- a.core.log.Println("WARNING:", a.listenaddr[:7], "may have unsafe permissions!")
+ a.core.log.Warnln("WARNING:", a.listenaddr[:7], "may have unsafe permissions!")
}
}
}
@@ -385,10 +424,10 @@ func (a *admin) listen() {
a.listener, err = net.Listen("tcp", a.listenaddr)
}
if err != nil {
- a.core.log.Printf("Admin socket failed to listen: %v", err)
+ a.core.log.Errorf("Admin socket failed to listen: %v", err)
os.Exit(1)
}
- a.core.log.Printf("%s admin socket listening on %s",
+ a.core.log.Infof("%s admin socket listening on %s",
strings.ToUpper(a.listener.Addr().Network()),
a.listener.Addr().String())
defer a.listener.Close()
@@ -415,9 +454,9 @@ func (a *admin) handleRequest(conn net.Conn) {
"status": "error",
"error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax",
}
- fmt.Println("Admin socket error:", r)
+ a.core.log.Errorln("Admin socket error:", r)
if err := encoder.Encode(&send); err != nil {
- fmt.Println("Admin socket JSON encode error:", err)
+ a.core.log.Errorln("Admin socket JSON encode error:", err)
}
conn.Close()
}
@@ -730,35 +769,20 @@ func (a *admin) getData_getSessions() []admin_nodeInfo {
// getAllowedEncryptionPublicKeys returns the public keys permitted for incoming peer connections.
func (a *admin) getAllowedEncryptionPublicKeys() []string {
- pubs := a.core.peers.getAllowedEncryptionPublicKeys()
- var out []string
- for _, pub := range pubs {
- out = append(out, hex.EncodeToString(pub[:]))
- }
- return out
+ return a.core.peers.getAllowedEncryptionPublicKeys()
}
// addAllowedEncryptionPublicKey whitelists a key for incoming peer connections.
func (a *admin) addAllowedEncryptionPublicKey(bstr string) (err error) {
- boxBytes, err := hex.DecodeString(bstr)
- if err == nil {
- var box crypto.BoxPubKey
- copy(box[:], boxBytes)
- a.core.peers.addAllowedEncryptionPublicKey(&box)
- }
- return
+ 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) {
- boxBytes, err := hex.DecodeString(bstr)
- if err == nil {
- var box crypto.BoxPubKey
- copy(box[:], boxBytes)
- a.core.peers.removeAllowedEncryptionPublicKey(&box)
- }
- return
+ 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.
@@ -827,7 +851,7 @@ func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (
copy(key[:], keyBytes)
}
if !nocache {
- if response, err := a.core.nodeinfo.getCachedNodeInfo(key); err == nil {
+ if response, err := a.core.router.nodeinfo.getCachedNodeInfo(key); err == nil {
return response, nil
}
}
@@ -845,14 +869,14 @@ func (a *admin) admin_getNodeInfo(keyString, coordString string, nocache bool) (
}
response := make(chan *nodeinfoPayload, 1)
sendNodeInfoRequest := func() {
- a.core.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) {
+ a.core.router.nodeinfo.addCallback(key, func(nodeinfo *nodeinfoPayload) {
defer func() { recover() }()
select {
case response <- nodeinfo:
default:
}
})
- a.core.nodeinfo.sendNodeInfo(key, coords, false)
+ a.core.router.nodeinfo.sendNodeInfo(key, coords, false)
}
a.core.router.doAdmin(sendNodeInfoRequest)
go func() {
diff --git a/src/yggdrasil/awdl.go b/src/yggdrasil/awdl.go
new file mode 100644
index 0000000..4236688
--- /dev/null
+++ b/src/yggdrasil/awdl.go
@@ -0,0 +1,98 @@
+package yggdrasil
+
+import (
+ "errors"
+ "io"
+ "sync"
+)
+
+type awdl struct {
+ link *link
+ 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.mutex.Unlock()
+
+ 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/ckr.go b/src/yggdrasil/ckr.go
index a3df891..03bc571 100644
--- a/src/yggdrasil/ckr.go
+++ b/src/yggdrasil/ckr.go
@@ -18,6 +18,7 @@ import (
type cryptokey struct {
core *Core
enabled bool
+ reconfigure chan chan error
ipv4routes []cryptokey_route
ipv6routes []cryptokey_route
ipv4cache map[address.Address]cryptokey_route
@@ -34,12 +35,75 @@ 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
- c.ipv4routes = make([]cryptokey_route, 0)
+ 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
+ }
+ }()
+
+ if err := c.configure(); err != nil {
+ c.core.log.Errorln("CKR configuration failed:", err)
+ }
+}
+
+// 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()
+
+ // Set enabled/disabled state
+ c.setEnabled(c.core.config.TunnelRouting.Enable)
+
+ // Clear out existing routes
c.ipv6routes = make([]cryptokey_route, 0)
+ c.ipv4routes = make([]cryptokey_route, 0)
+
+ // Add IPv6 routes
+ for ipv6, pubkey := range c.core.config.TunnelRouting.IPv6Destinations {
+ if err := c.addRoute(ipv6, pubkey); err != nil {
+ return err
+ }
+ }
+
+ // Add IPv4 routes
+ for ipv4, pubkey := range c.core.config.TunnelRouting.IPv4Destinations {
+ if err := c.addRoute(ipv4, pubkey); err != nil {
+ return err
+ }
+ }
+
+ // Clear out existing sources
+ c.ipv6sources = make([]net.IPNet, 0)
+ c.ipv4sources = make([]net.IPNet, 0)
+
+ // Add IPv6 sources
+ c.ipv6sources = make([]net.IPNet, 0)
+ for _, source := range c.core.config.TunnelRouting.IPv6Sources {
+ if err := c.addSourceSubnet(source); err != nil {
+ return err
+ }
+ }
+
+ // Add IPv4 sources
+ c.ipv4sources = make([]net.IPNet, 0)
+ for _, source := range c.core.config.TunnelRouting.IPv4Sources {
+ if err := c.addSourceSubnet(source); err != nil {
+ return err
+ }
+ }
+
+ // Wipe the caches
c.ipv4cache = make(map[address.Address]cryptokey_route, 0)
c.ipv6cache = make(map[address.Address]cryptokey_route, 0)
- c.ipv4sources = make([]net.IPNet, 0)
- c.ipv6sources = make([]net.IPNet, 0)
+
+ return nil
}
// Enable or disable crypto-key routing.
@@ -128,7 +192,7 @@ func (c *cryptokey) addSourceSubnet(cidr string) error {
// Add the source subnet
*routingsources = append(*routingsources, *ipnet)
- c.core.log.Println("Added CKR source subnet", cidr)
+ c.core.log.Infoln("Added CKR source subnet", cidr)
return nil
}
@@ -200,7 +264,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error {
delete(*routingcache, k)
}
- c.core.log.Println("Added CKR destination subnet", cidr)
+ c.core.log.Infoln("Added CKR destination subnet", cidr)
return nil
}
}
@@ -294,7 +358,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.Println("Removed CKR source subnet", cidr)
+ c.core.log.Infoln("Removed CKR source subnet", cidr)
return nil
}
}
@@ -343,7 +407,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error {
for k := range *routingcache {
delete(*routingcache, k)
}
- c.core.log.Printf("Removed CKR destination subnet %s via %s\n", cidr, dest)
+ c.core.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest)
return nil
}
}
diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go
index e38274f..2e23dd1 100644
--- a/src/yggdrasil/core.go
+++ b/src/yggdrasil/core.go
@@ -2,11 +2,12 @@ package yggdrasil
import (
"encoding/hex"
- "fmt"
"io/ioutil"
- "log"
"net"
- "regexp"
+ "sync"
+ "time"
+
+ "github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
@@ -17,10 +18,20 @@ import (
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
boxPub crypto.BoxPubKey
boxPriv crypto.BoxPrivKey
sigPub crypto.SigPubKey
@@ -33,16 +44,12 @@ type Core struct {
admin admin
searches searches
multicast multicast
- nodeinfo nodeinfo
tcp tcpInterface
+ link link
log *log.Logger
- ifceExpr []*regexp.Regexp // the zone of link-local IPv6 peers must match this
}
-func (c *Core) init(bpub *crypto.BoxPubKey,
- bpriv *crypto.BoxPrivKey,
- spub *crypto.SigPubKey,
- spriv *crypto.SigPrivKey) {
+func (c *Core) init() error {
// TODO separate init and start functions
// Init sets up structs
// Start launches goroutines that depend on structs being set up
@@ -50,20 +57,104 @@ func (c *Core) init(bpub *crypto.BoxPubKey,
if c.log == nil {
c.log = log.New(ioutil.Discard, "", 0)
}
- c.boxPub, c.boxPriv = *bpub, *bpriv
- c.sigPub, c.sigPriv = *spub, *spriv
- c.admin.core = c
+
+ boxPubHex, err := hex.DecodeString(c.config.EncryptionPublicKey)
+ 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
+ }
+
+ copy(c.boxPub[:], boxPubHex)
+ copy(c.boxPriv[:], boxPrivHex)
+ copy(c.sigPub[:], sigPubHex)
+ copy(c.sigPriv[:], sigPrivHex)
+
+ c.admin.init(c)
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, c.sigPub) // TODO move before peers? before router?
+ c.switchTable.init(c) // TODO move before peers? before router?
+
+ return nil
}
-// Get the current build name. This is usually injected if built from git,
-// or returns "unknown" otherwise.
+// If any static peers were provided in the configuration above then we should
+// configure them. The loop ensures that disconnected peers will eventually
+// be reconnected with.
+func (c *Core) addPeerLoop() {
+ for {
+ // Get the peers from the config - these could change!
+ c.configMutex.RLock()
+ peers := c.config.Peers
+ interfacepeers := c.config.InterfacePeers
+ c.configMutex.RUnlock()
+
+ // Add peers from the Peers section
+ for _, peer := range peers {
+ c.AddPeer(peer, "")
+ time.Sleep(time.Second)
+ }
+
+ // Add peers from the InterfacePeers section
+ for intf, intfpeers := range interfacepeers {
+ for _, peer := range intfpeers {
+ c.AddPeer(peer, intf)
+ time.Sleep(time.Second)
+ }
+ }
+
+ // Sit for a while
+ time.Sleep(time.Minute)
+ }
+}
+
+// UpdateConfig updates the configuration in Core and then signals the
+// various module goroutines to reconfigure themselves if needed
+func (c *Core) UpdateConfig(config *config.NodeConfig) {
+ c.configMutex.Lock()
+ c.configOld = c.config
+ c.config = *config
+ c.configMutex.Unlock()
+
+ 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.tcp.reconfigure,
+ c.multicast.reconfigure,
+ }
+
+ for _, component := range components {
+ response := make(chan error)
+ component <- response
+ if err := <-response; err != nil {
+ c.log.Println(err)
+ }
+ }
+}
+
+// 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"
@@ -88,47 +179,28 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
c.log = log
if name := GetBuildName(); name != "unknown" {
- c.log.Println("Build name:", name)
+ c.log.Infoln("Build name:", name)
}
if version := GetBuildVersion(); version != "unknown" {
- c.log.Println("Build version:", version)
+ c.log.Infoln("Build version:", version)
}
- c.log.Println("Starting up...")
+ c.log.Infoln("Starting up...")
- var boxPub crypto.BoxPubKey
- var boxPriv crypto.BoxPrivKey
- var sigPub crypto.SigPubKey
- var sigPriv crypto.SigPrivKey
- boxPubHex, err := hex.DecodeString(nc.EncryptionPublicKey)
- if err != nil {
+ c.configMutex.Lock()
+ c.config = *nc
+ c.configOld = c.config
+ c.configMutex.Unlock()
+
+ c.init()
+
+ if err := c.tcp.init(c); err != nil {
+ c.log.Errorln("Failed to start TCP interface")
return err
}
- boxPrivHex, err := hex.DecodeString(nc.EncryptionPrivateKey)
- if err != nil {
- return err
- }
- sigPubHex, err := hex.DecodeString(nc.SigningPublicKey)
- if err != nil {
- return err
- }
- sigPrivHex, err := hex.DecodeString(nc.SigningPrivateKey)
- if err != nil {
- return err
- }
- copy(boxPub[:], boxPubHex)
- copy(boxPriv[:], boxPrivHex)
- copy(sigPub[:], sigPubHex)
- copy(sigPriv[:], sigPrivHex)
- c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)
- c.admin.init(c, nc.AdminListen)
-
- c.nodeinfo.init(c)
- c.nodeinfo.setNodeInfo(nc.NodeInfo, nc.NodeInfoPrivacy)
-
- if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil {
- c.log.Println("Failed to start TCP interface")
+ if err := c.link.init(c); err != nil {
+ c.log.Errorln("Failed to start link interfaces")
return err
}
@@ -137,72 +209,39 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
}
if err := c.switchTable.start(); err != nil {
- c.log.Println("Failed to start switch")
+ c.log.Errorln("Failed to start switch")
return err
}
- c.sessions.setSessionFirewallState(nc.SessionFirewall.Enable)
- c.sessions.setSessionFirewallDefaults(
- nc.SessionFirewall.AllowFromDirect,
- nc.SessionFirewall.AllowFromRemote,
- nc.SessionFirewall.AlwaysAllowOutbound,
- )
- c.sessions.setSessionFirewallWhitelist(nc.SessionFirewall.WhitelistEncryptionPublicKeys)
- c.sessions.setSessionFirewallBlacklist(nc.SessionFirewall.BlacklistEncryptionPublicKeys)
-
if err := c.router.start(); err != nil {
- c.log.Println("Failed to start router")
+ c.log.Errorln("Failed to start router")
return err
}
- c.router.cryptokey.setEnabled(nc.TunnelRouting.Enable)
- if c.router.cryptokey.isEnabled() {
- c.log.Println("Crypto-key routing enabled")
- for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations {
- if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil {
- panic(err)
- }
- }
- for _, source := range nc.TunnelRouting.IPv6Sources {
- if c.router.cryptokey.addSourceSubnet(source); err != nil {
- panic(err)
- }
- }
- for ipv4, pubkey := range nc.TunnelRouting.IPv4Destinations {
- if err := c.router.cryptokey.addRoute(ipv4, pubkey); err != nil {
- panic(err)
- }
- }
- for _, source := range nc.TunnelRouting.IPv4Sources {
- if c.router.cryptokey.addSourceSubnet(source); err != nil {
- panic(err)
- }
- }
- }
-
if err := c.admin.start(); err != nil {
- c.log.Println("Failed to start admin socket")
+ c.log.Errorln("Failed to start admin socket")
return err
}
if err := c.multicast.start(); err != nil {
- c.log.Println("Failed to start multicast interface")
+ c.log.Errorln("Failed to start multicast interface")
return err
}
- ip := net.IP(c.router.addr[:]).String()
- if err := c.router.tun.start(nc.IfName, nc.IfTAPMode, fmt.Sprintf("%s/%d", ip, 8*len(address.GetPrefix())-1), nc.IfMTU); err != nil {
- c.log.Println("Failed to start TUN/TAP")
+ if err := c.router.tun.start(); err != nil {
+ c.log.Errorln("Failed to start TUN/TAP")
return err
}
- c.log.Println("Startup complete")
+ go c.addPeerLoop()
+
+ c.log.Infoln("Startup complete")
return nil
}
// Stops the Yggdrasil node.
func (c *Core) Stop() {
- c.log.Println("Stopping...")
+ c.log.Infoln("Stopping...")
c.router.tun.close()
c.admin.close()
}
@@ -244,12 +283,12 @@ func (c *Core) GetSubnet() *net.IPNet {
// Gets the nodeinfo.
func (c *Core) GetNodeInfo() nodeinfoPayload {
- return c.nodeinfo.getNodeInfo()
+ return c.router.nodeinfo.getNodeInfo()
}
// Sets the nodeinfo.
func (c *Core) SetNodeInfo(nodeinfo interface{}, nodeinfoprivacy bool) {
- c.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
+ c.router.nodeinfo.setNodeInfo(nodeinfo, nodeinfoprivacy)
}
// Sets the output logger of the Yggdrasil node after startup. This may be
@@ -264,13 +303,6 @@ func (c *Core) AddPeer(addr string, sintf string) error {
return c.admin.addPeer(addr, sintf)
}
-// Adds an expression to select multicast interfaces for peer discovery. This
-// should be done before calling Start. This function can be called multiple
-// times to add multiple search expressions.
-func (c *Core) AddMulticastInterfaceExpr(expr *regexp.Regexp) {
- c.ifceExpr = append(c.ifceExpr, expr)
-}
-
// 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 {
diff --git a/src/yggdrasil/debug.go b/src/yggdrasil/debug.go
index 4a32eb6..94faba4 100644
--- a/src/yggdrasil/debug.go
+++ b/src/yggdrasil/debug.go
@@ -14,15 +14,18 @@ import _ "golang.org/x/net/ipv6" // TODO put this somewhere better
import "fmt"
import "net"
-import "log"
import "regexp"
+import "encoding/hex"
import _ "net/http/pprof"
import "net/http"
import "runtime"
import "os"
+import "github.com/gologme/log"
+
import "github.com/yggdrasil-network/yggdrasil-go/src/address"
+import "github.com/yggdrasil-network/yggdrasil-go/src/config"
import "github.com/yggdrasil-network/yggdrasil-go/src/crypto"
import "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
@@ -52,7 +55,17 @@ func StartProfiler(log *log.Logger) error {
func (c *Core) Init() {
bpub, bpriv := crypto.NewBoxKeys()
spub, spriv := crypto.NewSigKeys()
- c.init(bpub, bpriv, spub, spriv)
+ hbpub := hex.EncodeToString(bpub[:])
+ hbpriv := hex.EncodeToString(bpriv[:])
+ hspub := hex.EncodeToString(spub[:])
+ hspriv := hex.EncodeToString(spriv[:])
+ c.config = config.NodeConfig{
+ EncryptionPublicKey: hbpub,
+ EncryptionPrivateKey: hbpriv,
+ SigningPublicKey: hspub,
+ SigningPrivateKey: hspriv,
+ }
+ c.init( /*bpub, bpriv, spub, spriv*/ )
c.switchTable.start()
c.router.start()
}
@@ -84,9 +97,7 @@ func (c *Core) DEBUG_getPeers() *peers {
}
func (ps *peers) DEBUG_newPeer(box crypto.BoxPubKey, sig crypto.SigPubKey, link crypto.BoxSharedKey) *peer {
- //in <-chan []byte,
- //out chan<- []byte) *peer {
- return ps.newPeer(&box, &sig, &link, "(simulator)") //, in, out)
+ return ps.newPeer(&box, &sig, &link, "(simulator)", nil)
}
/*
@@ -350,7 +361,7 @@ func (c *Core) DEBUG_init(bpub []byte,
bpriv []byte,
spub []byte,
spriv []byte) {
- var boxPub crypto.BoxPubKey
+ /*var boxPub crypto.BoxPubKey
var boxPriv crypto.BoxPrivKey
var sigPub crypto.SigPubKey
var sigPriv crypto.SigPrivKey
@@ -358,7 +369,18 @@ func (c *Core) DEBUG_init(bpub []byte,
copy(boxPriv[:], bpriv)
copy(sigPub[:], spub)
copy(sigPriv[:], spriv)
- c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)
+ c.init(&boxPub, &boxPriv, &sigPub, &sigPriv)*/
+ hbpub := hex.EncodeToString(bpub[:])
+ hbpriv := hex.EncodeToString(bpriv[:])
+ hspub := hex.EncodeToString(spub[:])
+ hspriv := hex.EncodeToString(spriv[:])
+ c.config = config.NodeConfig{
+ EncryptionPublicKey: hbpub,
+ EncryptionPrivateKey: hbpriv,
+ SigningPublicKey: hspub,
+ SigningPrivateKey: hspriv,
+ }
+ c.init( /*bpub, bpriv, spub, spriv*/ )
if err := c.router.start(); err != nil {
panic(err)
@@ -427,7 +449,8 @@ func (c *Core) DEBUG_addSOCKSConn(socksaddr, peeraddr string) {
//*
func (c *Core) DEBUG_setupAndStartGlobalTCPInterface(addrport string) {
- if err := c.tcp.init(c, addrport, 0); err != nil {
+ c.config.Listen = addrport
+ if err := c.tcp.init(c /*, addrport, 0*/); err != nil {
c.log.Println("Failed to start TCP interface:", err)
panic(err)
}
@@ -474,7 +497,8 @@ func (c *Core) DEBUG_addKCPConn(saddr string) {
func (c *Core) DEBUG_setupAndStartAdminInterface(addrport string) {
a := admin{}
- a.init(c, addrport)
+ c.config.AdminListen = addrport
+ a.init(c /*, addrport*/)
c.admin = a
}
@@ -492,7 +516,7 @@ func (c *Core) DEBUG_setLogger(log *log.Logger) {
}
func (c *Core) DEBUG_setIfceExpr(expr *regexp.Regexp) {
- c.ifceExpr = append(c.ifceExpr, expr)
+ c.log.Println("DEBUG_setIfceExpr no longer implemented")
}
func (c *Core) DEBUG_addAllowedEncryptionPublicKey(boxStr string) {
diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go
index b52a820..5427aca 100644
--- a/src/yggdrasil/dht.go
+++ b/src/yggdrasil/dht.go
@@ -65,11 +65,12 @@ type dhtReqKey struct {
// The main DHT struct.
type dht struct {
- core *Core
- 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
+ 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
// These next two could be replaced by a single linked list or similar...
table map[crypto.NodeID]*dhtInfo
imp []*dhtInfo
@@ -78,6 +79,13 @@ type dht struct {
// Initializes the DHT.
func (t *dht) init(c *Core) {
t.core = c
+ t.reconfigure = make(chan chan error, 1)
+ go func() {
+ for {
+ e := <-t.reconfigure
+ e <- nil
+ }
+ }()
t.nodeID = *t.core.GetNodeID()
t.peers = make(chan *dhtInfo, 1024)
t.callbacks = make(map[dhtReqKey]dht_callbackInfo)
diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go
new file mode 100644
index 0000000..ad4b1fa
--- /dev/null
+++ b/src/yggdrasil/link.go
@@ -0,0 +1,321 @@
+package yggdrasil
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "net"
+ "strings"
+ "sync"
+ //"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/util"
+)
+
+type link struct {
+ core *Core
+ mutex sync.RWMutex // protects interfaces below
+ interfaces map[linkInfo]*linkInterface
+ awdl awdl // AWDL interface support
+ // TODO timeout (to remove from switch), read from config.ReadTimeout
+}
+
+type linkInfo struct {
+ box crypto.BoxPubKey // Their encryption key
+ sig crypto.SigPubKey // Their signing key
+ linkType string // Type of link, e.g. TCP, AWDL
+ local string // Local name or address
+ remote string // Remote name or address
+}
+
+type linkInterfaceMsgIO interface {
+ readMsg() ([]byte, error)
+ writeMsg([]byte) (int, error)
+ close() error
+ // These are temporary workarounds to stream semantics
+ _sendMetaBytes([]byte) error
+ _recvMetaBytes() ([]byte, error)
+}
+
+type linkInterface struct {
+ name string
+ link *link
+ peer *peer
+ msgIO linkInterfaceMsgIO
+ info linkInfo
+ incoming bool
+ force bool
+ closed chan struct{}
+}
+
+func (l *link) init(c *Core) error {
+ l.core = c
+ l.mutex.Lock()
+ l.interfaces = make(map[linkInfo]*linkInterface)
+ l.mutex.Unlock()
+
+ if err := l.awdl.init(l); err != nil {
+ l.core.log.Errorln("Failed to start AWDL interface")
+ return err
+ }
+
+ return nil
+}
+
+func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) {
+ // Technically anything unique would work for names, but lets pick something human readable, just for debugging
+ intf := linkInterface{
+ name: name,
+ link: l,
+ msgIO: msgIO,
+ info: linkInfo{
+ linkType: linkType,
+ local: local,
+ remote: remote,
+ },
+ incoming: incoming,
+ force: force,
+ }
+ return &intf, nil
+}
+
+func (intf *linkInterface) handler() error {
+ // TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later
+ myLinkPub, myLinkPriv := crypto.NewBoxKeys()
+ meta := version_getBaseMetadata()
+ meta.box = intf.link.core.boxPub
+ meta.sig = intf.link.core.sigPub
+ meta.link = *myLinkPub
+ metaBytes := meta.encode()
+ // TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer)
+ err := intf.msgIO._sendMetaBytes(metaBytes)
+ if err != nil {
+ return err
+ }
+ metaBytes, err = intf.msgIO._recvMetaBytes()
+ if err != nil {
+ return err
+ }
+ meta = version_metadata{}
+ if !meta.decode(metaBytes) || !meta.check() {
+ return errors.New("failed to decode metadata")
+ }
+ base := version_getBaseMetadata()
+ if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer {
+ intf.link.core.log.Errorln("Failed to connect to node: " + intf.name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
+ return errors.New("failed to connect: wrong version")
+ }
+ // Check if we're authorized to connect to this key / IP
+ if !intf.force && !intf.link.core.peers.isAllowedEncryptionPublicKey(&meta.box) {
+ intf.link.core.log.Debugf("%s connection to %s forbidden: AllowedEncryptionPublicKeys does not contain key %s",
+ strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.box[:]))
+ intf.msgIO.close()
+ return nil
+ }
+ // Check if we already have a link to this node
+ intf.info.box = meta.box
+ intf.info.sig = meta.sig
+ intf.link.mutex.Lock()
+ if oldIntf, isIn := intf.link.interfaces[intf.info]; isIn {
+ intf.link.mutex.Unlock()
+ // FIXME we should really return an error and let the caller block instead
+ // That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc.
+ intf.link.core.log.Debugln("DEBUG: found existing interface for", intf.name)
+ intf.msgIO.close()
+ <-oldIntf.closed
+ return nil
+ } else {
+ intf.closed = make(chan struct{})
+ intf.link.interfaces[intf.info] = intf
+ defer func() {
+ intf.link.mutex.Lock()
+ delete(intf.link.interfaces, intf.info)
+ intf.link.mutex.Unlock()
+ close(intf.closed)
+ }()
+ intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name)
+ }
+ intf.link.mutex.Unlock()
+ // Create peer
+ shared := crypto.GetSharedKey(myLinkPriv, &meta.link)
+ intf.peer = intf.link.core.peers.newPeer(&meta.box, &meta.sig, shared, intf.name, func() { intf.msgIO.close() })
+ if intf.peer == nil {
+ return errors.New("failed to create peer")
+ }
+ defer func() {
+ // More cleanup can go here
+ intf.link.core.peers.removePeer(intf.peer.port)
+ }()
+ // Finish setting up the peer struct
+ out := make(chan []byte, 1)
+ defer close(out)
+ intf.peer.out = func(msg []byte) {
+ defer func() { recover() }()
+ out <- msg
+ }
+ intf.peer.linkOut = make(chan []byte, 1)
+ themAddr := address.AddrForNodeID(crypto.GetNodeID(&intf.info.box))
+ themAddrString := net.IP(themAddr[:]).String()
+ themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote)
+ intf.link.core.log.Infof("Connected %s: %s, source %s",
+ strings.ToUpper(intf.info.linkType), themString, intf.info.local)
+ defer intf.link.core.log.Infof("Disconnected %s: %s, source %s",
+ strings.ToUpper(intf.info.linkType), themString, intf.info.local)
+ // Start the link loop
+ go intf.peer.linkLoop()
+ // Start the writer
+ signalReady := make(chan struct{}, 1)
+ signalSent := make(chan bool, 1)
+ sendAck := make(chan struct{}, 1)
+ go func() {
+ defer close(signalReady)
+ defer close(signalSent)
+ interval := 4 * time.Second
+ tcpTimer := time.NewTimer(interval) // used for backwards compat with old tcp
+ defer util.TimerStop(tcpTimer)
+ send := func(bs []byte) {
+ intf.msgIO.writeMsg(bs)
+ select {
+ case signalSent <- len(bs) > 0:
+ default:
+ }
+ }
+ for {
+ // First try to send any link protocol traffic
+ select {
+ case msg := <-intf.peer.linkOut:
+ send(msg)
+ continue
+ default:
+ }
+ // No protocol traffic to send, so reset the timer
+ util.TimerStop(tcpTimer)
+ tcpTimer.Reset(interval)
+ // Now block until something is ready or the timer triggers keepalive traffic
+ select {
+ case <-tcpTimer.C:
+ intf.link.core.log.Debugf("Sending (legacy) keep-alive to %s: %s, source %s",
+ strings.ToUpper(intf.info.linkType), themString, intf.info.local)
+ send(nil)
+ case <-sendAck:
+ intf.link.core.log.Debugf("Sending ack to %s: %s, source %s",
+ strings.ToUpper(intf.info.linkType), themString, intf.info.local)
+ send(nil)
+ case msg := <-intf.peer.linkOut:
+ intf.msgIO.writeMsg(msg)
+ case msg, ok := <-out:
+ if !ok {
+ return
+ }
+ send(msg)
+ util.PutBytes(msg)
+ select {
+ case signalReady <- struct{}{}:
+ default:
+ }
+ intf.link.core.log.Debugf("Sending packet to %s: %s, source %s",
+ strings.ToUpper(intf.info.linkType), themString, intf.info.local)
+ }
+ }
+ }()
+ //intf.link.core.switchTable.idleIn <- intf.peer.port // notify switch that we're idle
+ // Used to enable/disable activity in the switch
+ signalAlive := make(chan bool, 1) // True = real packet, false = keep-alive
+ defer close(signalAlive)
+ go func() {
+ var isAlive bool
+ var isReady bool
+ var sendTimerRunning bool
+ var recvTimerRunning bool
+ recvTime := 6 * time.Second // TODO set to ReadTimeout from the config, reset if it gets changed
+ sendTime := time.Second
+ sendTimer := time.NewTimer(sendTime)
+ defer util.TimerStop(sendTimer)
+ recvTimer := time.NewTimer(recvTime)
+ defer util.TimerStop(recvTimer)
+ for {
+ intf.link.core.log.Debugf("State of %s: %s, source %s :: isAlive %t isReady %t sendTimerRunning %t recvTimerRunning %t",
+ strings.ToUpper(intf.info.linkType), themString, intf.info.local,
+ isAlive, isReady, sendTimerRunning, recvTimerRunning)
+ select {
+ case gotMsg, ok := <-signalAlive:
+ if !ok {
+ return
+ }
+ util.TimerStop(recvTimer)
+ recvTimerRunning = false
+ isAlive = true
+ if !isReady {
+ // (Re-)enable in the switch
+ intf.link.core.switchTable.idleIn <- intf.peer.port
+ isReady = true
+ }
+ if gotMsg && !sendTimerRunning {
+ // We got a message
+ // Start a timer, if it expires then send a 0-sized ack to let them know we're alive
+ util.TimerStop(sendTimer)
+ sendTimer.Reset(sendTime)
+ sendTimerRunning = true
+ }
+ if !gotMsg {
+ intf.link.core.log.Debugf("Received ack from %s: %s, source %s",
+ strings.ToUpper(intf.info.linkType), themString, intf.info.local)
+ }
+ case sentMsg, ok := <-signalSent:
+ // Stop any running ack timer
+ if !ok {
+ return
+ }
+ util.TimerStop(sendTimer)
+ sendTimerRunning = false
+ if sentMsg && !recvTimerRunning {
+ // We sent a message
+ // Start a timer, if it expires and we haven't gotten any return traffic (including a 0-sized ack), then assume there's a problem
+ util.TimerStop(recvTimer)
+ recvTimer.Reset(recvTime)
+ recvTimerRunning = true
+ }
+ case _, ok := <-signalReady:
+ if !ok {
+ return
+ }
+ if !isAlive {
+ // Disable in the switch
+ isReady = false
+ } else {
+ // Keep enabled in the switch
+ intf.link.core.switchTable.idleIn <- intf.peer.port
+ isReady = true
+ }
+ case <-sendTimer.C:
+ // We haven't sent anything, so signal a send of a 0 packet to let them know we're alive
+ select {
+ case sendAck <- struct{}{}:
+ default:
+ }
+ case <-recvTimer.C:
+ // We haven't received anything, so assume there's a problem and don't return this node to the switch until they start responding
+ isAlive = false
+ }
+ }
+ }()
+ // Run reader loop
+ for {
+ msg, err := intf.msgIO.readMsg()
+ if len(msg) > 0 {
+ intf.peer.handlePacket(msg)
+ }
+ if err != nil {
+ return err
+ }
+ select {
+ case signalAlive <- len(msg) > 0:
+ default:
+ }
+ }
+ ////////////////////////////////////////////////////////////////////////////////
+ return nil
+}
diff --git a/src/yggdrasil/mobile.go b/src/yggdrasil/mobile.go
new file mode 100644
index 0000000..76fbe54
--- /dev/null
+++ b/src/yggdrasil/mobile.go
@@ -0,0 +1,130 @@
+// +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(true)
+ 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(false)
+ 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(false)
+ 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
new file mode 100644
index 0000000..2476484
--- /dev/null
+++ b/src/yggdrasil/mobile_android.go
@@ -0,0 +1,12 @@
+// +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
new file mode 100644
index 0000000..7b08999
--- /dev/null
+++ b/src/yggdrasil/mobile_ios.go
@@ -0,0 +1,62 @@
+// +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/multicast.go b/src/yggdrasil/multicast.go
index 749dfcd..42651de 100644
--- a/src/yggdrasil/multicast.go
+++ b/src/yggdrasil/multicast.go
@@ -4,33 +4,46 @@ import (
"context"
"fmt"
"net"
+ "regexp"
+ "sync"
"time"
"golang.org/x/net/ipv6"
)
type multicast struct {
- core *Core
- sock *ipv6.PacketConn
- groupAddr string
+ core *Core
+ reconfigure chan chan error
+ sock *ipv6.PacketConn
+ groupAddr string
+ myAddr *net.TCPAddr
+ myAddrMutex sync.RWMutex
}
func (m *multicast) init(core *Core) {
m.core = core
+ m.reconfigure = make(chan chan error, 1)
+ go func() {
+ for {
+ e := <-m.reconfigure
+ m.myAddrMutex.Lock()
+ m.myAddr = m.core.tcp.getAddr()
+ m.myAddrMutex.Unlock()
+ e <- nil
+ }
+ }()
m.groupAddr = "[ff02::114]:9001"
// Check if we've been given any expressions
- if len(m.core.ifceExpr) == 0 {
- return
+ if count := len(m.interfaces()); count != 0 {
+ m.core.log.Infoln("Found", count, "multicast interface(s)")
}
- // Ask the system for network interfaces
- m.core.log.Println("Found", len(m.interfaces()), "multicast interface(s)")
}
func (m *multicast) start() error {
- if len(m.core.ifceExpr) == 0 {
- m.core.log.Println("Multicast discovery is disabled")
+ if len(m.interfaces()) == 0 {
+ m.core.log.Infoln("Multicast discovery is disabled")
} else {
- m.core.log.Println("Multicast discovery is enabled")
+ m.core.log.Infoln("Multicast discovery is enabled")
addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
if err != nil {
return err
@@ -55,6 +68,10 @@ func (m *multicast) start() error {
}
func (m *multicast) interfaces() []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
var interfaces []net.Interface
allifaces, err := net.Interfaces()
@@ -75,8 +92,12 @@ func (m *multicast) interfaces() []net.Interface {
// Ignore point-to-point interfaces
continue
}
- for _, expr := range m.core.ifceExpr {
- if expr.MatchString(iface.Name) {
+ for _, expr := range exprs {
+ e, err := regexp.Compile(expr)
+ if err != nil {
+ panic(err)
+ }
+ if e.MatchString(iface.Name) {
interfaces = append(interfaces, iface)
}
}
@@ -85,13 +106,14 @@ func (m *multicast) interfaces() []net.Interface {
}
func (m *multicast) announce() {
+ var anAddr net.TCPAddr
+ m.myAddrMutex.Lock()
+ m.myAddr = m.core.tcp.getAddr()
+ m.myAddrMutex.Unlock()
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
}
- var anAddr net.TCPAddr
- myAddr := m.core.tcp.getAddr()
- anAddr.Port = myAddr.Port
destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
if err != nil {
panic(err)
@@ -103,6 +125,9 @@ func (m *multicast) announce() {
if err != nil {
panic(err)
}
+ m.myAddrMutex.RLock()
+ anAddr.Port = m.myAddr.Port
+ m.myAddrMutex.RUnlock()
for _, addr := range addrs {
addrIP, _, _ := net.ParseCIDR(addr.String())
if addrIP.To4() != nil {
@@ -157,6 +182,6 @@ func (m *multicast) listen() {
}
addr.Zone = from.Zone
saddr := addr.String()
- m.core.tcp.connect(saddr, "")
+ m.core.tcp.connect(saddr, addr.Zone)
}
}
diff --git a/src/yggdrasil/multicast_darwin.go b/src/yggdrasil/multicast_darwin.go
new file mode 100644
index 0000000..71eecce
--- /dev/null
+++ b/src/yggdrasil/multicast_darwin.go
@@ -0,0 +1,28 @@
+// +build darwin
+
+package yggdrasil
+
+import "syscall"
+import "golang.org/x/sys/unix"
+
+func multicastReuse(network string, address string, c syscall.RawConn) error {
+ var control error
+ var reuseport error
+ var recvanyif error
+
+ control = c.Control(func(fd uintptr) {
+ reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
+
+ // sys/socket.h: #define SO_RECV_ANYIF 0x1104
+ recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1)
+ })
+
+ switch {
+ case reuseport != nil:
+ return reuseport
+ case recvanyif != nil:
+ return recvanyif
+ default:
+ return control
+ }
+}
diff --git a/src/yggdrasil/multicast_unix.go b/src/yggdrasil/multicast_unix.go
index 9c6d1f1..54bbc64 100644
--- a/src/yggdrasil/multicast_unix.go
+++ b/src/yggdrasil/multicast_unix.go
@@ -1,4 +1,4 @@
-// +build linux darwin netbsd freebsd openbsd dragonflybsd
+// +build linux netbsd freebsd openbsd dragonflybsd
package yggdrasil
diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go
index b907632..963a2fc 100644
--- a/src/yggdrasil/nodeinfo.go
+++ b/src/yggdrasil/nodeinfo.go
@@ -170,7 +170,7 @@ func (m *nodeinfo) sendNodeInfo(key crypto.BoxPubKey, coords []byte, isResponse
nodeinfo := nodeinfoReqRes{
SendCoords: table.self.getCoords(),
IsResponse: isResponse,
- NodeInfo: m.core.nodeinfo.getNodeInfo(),
+ NodeInfo: m.getNodeInfo(),
}
bs := nodeinfo.encode()
shared := m.core.sessions.getSharedKey(&m.core.boxPriv, &key)
diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go
index a2b94b6..237d6f6 100644
--- a/src/yggdrasil/peer.go
+++ b/src/yggdrasil/peer.go
@@ -5,6 +5,7 @@ package yggdrasil
// Live code should be better commented
import (
+ "encoding/hex"
"sync"
"sync/atomic"
"time"
@@ -14,15 +15,14 @@ import (
)
// The peers struct represents peers with an active connection.
-// Incomping packets are passed to the corresponding peer, which handles them somehow.
+// Incoming packets are passed to the corresponding peer, which handles them somehow.
// In most cases, this involves passing the packet to the handler for outgoing traffic to another peer.
// In other cases, it's link protocol traffic used to build the spanning tree, in which case this checks signatures and passes the message along to the switch.
type peers struct {
- core *Core
- mutex sync.Mutex // Synchronize writes to atomic
- ports atomic.Value //map[switchPort]*peer, use CoW semantics
- authMutex sync.RWMutex
- allowedEncryptionPublicKeys map[crypto.BoxPubKey]struct{}
+ core *Core
+ reconfigure chan chan error
+ mutex sync.Mutex // Synchronize writes to atomic
+ ports atomic.Value //map[switchPort]*peer, use CoW semantics
}
// Initializes the peers struct.
@@ -31,40 +31,55 @@ func (ps *peers) init(c *Core) {
defer ps.mutex.Unlock()
ps.putPorts(make(map[switchPort]*peer))
ps.core = c
- ps.allowedEncryptionPublicKeys = make(map[crypto.BoxPubKey]struct{})
+ ps.reconfigure = make(chan chan error, 1)
+ go func() {
+ for {
+ e := <-ps.reconfigure
+ e <- nil
+ }
+ }()
}
-// Returns true if an incoming peer connection to a key is allowed, either because the key is in the whitelist or because the whitelist is empty.
+// Returns true if an incoming peer connection to a key is allowed, either
+// because the key is in the whitelist or because the whitelist is empty.
func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool {
- ps.authMutex.RLock()
- defer ps.authMutex.RUnlock()
- _, isIn := ps.allowedEncryptionPublicKeys[*box]
- return isIn || len(ps.allowedEncryptionPublicKeys) == 0
+ boxstr := hex.EncodeToString(box[:])
+ ps.core.configMutex.RLock()
+ defer ps.core.configMutex.RUnlock()
+ for _, v := range ps.core.config.AllowedEncryptionPublicKeys {
+ if v == boxstr {
+ return true
+ }
+ }
+ return len(ps.core.config.AllowedEncryptionPublicKeys) == 0
}
// Adds a key to the whitelist.
-func (ps *peers) addAllowedEncryptionPublicKey(box *crypto.BoxPubKey) {
- ps.authMutex.Lock()
- defer ps.authMutex.Unlock()
- ps.allowedEncryptionPublicKeys[*box] = struct{}{}
+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)
}
// Removes a key from the whitelist.
-func (ps *peers) removeAllowedEncryptionPublicKey(box *crypto.BoxPubKey) {
- ps.authMutex.Lock()
- defer ps.authMutex.Unlock()
- delete(ps.allowedEncryptionPublicKeys, *box)
+func (ps *peers) removeAllowedEncryptionPublicKey(box string) {
+ ps.core.configMutex.RLock()
+ defer ps.core.configMutex.RUnlock()
+ for k, v := range ps.core.config.AllowedEncryptionPublicKeys {
+ if v == box {
+ ps.core.config.AllowedEncryptionPublicKeys =
+ append(ps.core.config.AllowedEncryptionPublicKeys[:k],
+ ps.core.config.AllowedEncryptionPublicKeys[k+1:]...)
+ }
+ }
}
// Gets the whitelist of allowed keys for incoming connections.
-func (ps *peers) getAllowedEncryptionPublicKeys() []crypto.BoxPubKey {
- ps.authMutex.RLock()
- defer ps.authMutex.RUnlock()
- keys := make([]crypto.BoxPubKey, 0, len(ps.allowedEncryptionPublicKeys))
- for key := range ps.allowedEncryptionPublicKeys {
- keys = append(keys, key)
- }
- return keys
+func (ps *peers) getAllowedEncryptionPublicKeys() []string {
+ ps.core.configMutex.RLock()
+ defer ps.core.configMutex.RUnlock()
+ return ps.core.config.AllowedEncryptionPublicKeys
}
// Atomically gets a map[switchPort]*peer of known peers.
@@ -97,8 +112,8 @@ type peer struct {
close func() // Called when a peer is removed, to close the underlying connection, or via admin api
}
-// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unocupied port number.
-func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string) *peer {
+// Creates a new peer with the specified box, sig, and linkShared keys, using the lowest unoccupied port number.
+func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShared *crypto.BoxSharedKey, endpoint string, closer func()) *peer {
now := time.Now()
p := peer{box: *box,
sig: *sig,
@@ -108,6 +123,7 @@ func (ps *peers) newPeer(box *crypto.BoxPubKey, sig *crypto.SigPubKey, linkShare
firstSeen: now,
doSend: make(chan struct{}, 1),
dinfo: make(chan *dhtInfo, 1),
+ close: closer,
core: ps.core}
ps.mutex.Lock()
defer ps.mutex.Unlock()
@@ -217,6 +233,7 @@ func (p *peer) handlePacket(packet []byte) {
default:
util.PutBytes(packet)
}
+ return
}
// Called to handle traffic or protocolTraffic packets.
@@ -234,6 +251,7 @@ func (p *peer) handleTraffic(packet []byte, pTypeLen int) {
func (p *peer) sendPacket(packet []byte) {
// Is there ever a case where something more complicated is needed?
// What if p.out blocks?
+ atomic.AddUint64(&p.bytesSent, uint64(len(packet)))
p.out(packet)
}
@@ -341,7 +359,7 @@ func (p *peer) handleSwitchMsg(packet []byte) {
}
// This generates the bytes that we sign or check the signature of for a switchMsg.
-// It begins with the next node's key, followed by the root and the timetsamp, followed by coords being advertised to the next node.
+// It begins with the next node's key, followed by the root and the timestamp, followed by coords being advertised to the next node.
func getBytesForSig(next *crypto.SigPubKey, msg *switchMsg) []byte {
var loc switchLocator
for _, hop := range msg.Hops {
diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go
index 87da882..99e6982 100644
--- a/src/yggdrasil/router.go
+++ b/src/yggdrasil/router.go
@@ -37,19 +37,21 @@ import (
// The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer.
// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions.
type router struct {
- core *Core
- addr address.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
+ 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
+ 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.
@@ -61,10 +63,11 @@ type router_recvPacket struct {
// Initializes the router struct, which includes setting up channels to/from the tun/tap.
func (r *router) init(core *Core) {
r.core = core
+ r.reconfigure = make(chan chan error, 1)
r.addr = *address.AddrForNodeID(&r.core.dht.nodeID)
r.subnet = *address.SubnetForNodeID(&r.core.dht.nodeID)
in := make(chan []byte, 32) // TODO something better than this...
- p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)")
+ p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &crypto.BoxSharedKey{}, "(self)", nil)
p.out = func(packet []byte) {
// This is to make very sure it never blocks
select {
@@ -83,13 +86,17 @@ func (r *router) init(core *Core) {
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)
}
// Starts the mainLoop goroutine.
func (r *router) start() error {
- r.core.log.Println("Starting router")
+ r.core.log.Infoln("Starting router")
go r.mainLoop()
return nil
}
@@ -124,6 +131,10 @@ 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()
}
}
}
@@ -463,7 +474,7 @@ func (r *router) handleNodeInfo(bs []byte, fromKey *crypto.BoxPubKey) {
return
}
req.SendPermPub = *fromKey
- r.core.nodeinfo.handleNodeInfo(&req)
+ r.nodeinfo.handleNodeInfo(&req)
}
// Passed a function to call.
diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go
index c85b719..c391dda 100644
--- a/src/yggdrasil/search.go
+++ b/src/yggdrasil/search.go
@@ -30,7 +30,7 @@ const search_MAX_SEARCH_SIZE = 16
const search_RETRY_TIME = time.Second
// Information about an ongoing search.
-// Includes the targed NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited.
+// 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
@@ -42,13 +42,21 @@ type searchInfo struct {
// This stores a map of active searches.
type searches struct {
- core *Core
- searches map[crypto.NodeID]*searchInfo
+ core *Core
+ reconfigure chan chan error
+ searches map[crypto.NodeID]*searchInfo
}
// Intializes the searches struct.
func (s *searches) init(core *Core) {
s.core = core
+ s.reconfigure = make(chan chan error, 1)
+ go func() {
+ for {
+ e := <-s.reconfigure
+ e <- nil
+ }
+ }()
s.searches = make(map[crypto.NodeID]*searchInfo)
}
diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go
index 4f395b0..cdabaf2 100644
--- a/src/yggdrasil/session.go
+++ b/src/yggdrasil/session.go
@@ -18,6 +18,7 @@ import (
// 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
@@ -101,6 +102,7 @@ func (s *sessionInfo) timedout() bool {
// 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
@@ -112,18 +114,29 @@ type sessions struct {
byTheirPerm map[crypto.BoxPubKey]*crypto.Handle
addrToPerm map[address.Address]*crypto.BoxPubKey
subnetToPerm map[address.Subnet]*crypto.BoxPubKey
- // Options from the session firewall
- sessionFirewallEnabled bool
- sessionFirewallAllowsDirect bool
- sessionFirewallAllowsRemote bool
- sessionFirewallAlwaysAllowsOutbound bool
- sessionFirewallWhitelist []string
- sessionFirewallBlacklist []string
}
// Initializes the session struct.
func (ss *sessions) init(core *Core) {
ss.core = core
+ ss.reconfigure = make(chan chan error, 1)
+ go func() {
+ for {
+ e := <-ss.reconfigure
+ responses := make(map[crypto.Handle]chan error)
+ for index, session := range ss.sinfos {
+ responses[index] = make(chan error)
+ session.reconfigure <- responses[index]
+ }
+ for _, response := range responses {
+ if err := <-response; err != nil {
+ e <- err
+ continue
+ }
+ }
+ e <- nil
+ }
+ }()
ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey)
ss.sinfos = make(map[crypto.Handle]*sessionInfo)
ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle)
@@ -133,40 +146,28 @@ func (ss *sessions) init(core *Core) {
ss.lastCleanup = time.Now()
}
-// Enable or disable the session firewall
-func (ss *sessions) setSessionFirewallState(enabled bool) {
- ss.sessionFirewallEnabled = enabled
-}
+// Determines whether the session firewall is enabled.
+func (ss *sessions) isSessionFirewallEnabled() bool {
+ ss.core.configMutex.RLock()
+ defer ss.core.configMutex.RUnlock()
-// Set the session firewall defaults (first parameter is whether to allow
-// sessions from direct peers, second is whether to allow from remote nodes).
-func (ss *sessions) setSessionFirewallDefaults(allowsDirect bool, allowsRemote bool, alwaysAllowsOutbound bool) {
- ss.sessionFirewallAllowsDirect = allowsDirect
- ss.sessionFirewallAllowsRemote = allowsRemote
- ss.sessionFirewallAlwaysAllowsOutbound = alwaysAllowsOutbound
-}
-
-// Set the session firewall whitelist - nodes always allowed to open sessions.
-func (ss *sessions) setSessionFirewallWhitelist(whitelist []string) {
- ss.sessionFirewallWhitelist = whitelist
-}
-
-// Set the session firewall blacklist - nodes never allowed to open sessions.
-func (ss *sessions) setSessionFirewallBlacklist(blacklist []string) {
- ss.sessionFirewallBlacklist = blacklist
+ 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()
+
// Allow by default if the session firewall is disabled
- if !ss.sessionFirewallEnabled {
+ if !ss.isSessionFirewallEnabled() {
return true
}
// Prepare for checking whitelist/blacklist
var box crypto.BoxPubKey
// Reject blacklisted nodes
- for _, b := range ss.sessionFirewallBlacklist {
+ for _, b := range ss.core.config.SessionFirewall.BlacklistEncryptionPublicKeys {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
@@ -176,7 +177,7 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b
}
}
// Allow whitelisted nodes
- for _, b := range ss.sessionFirewallWhitelist {
+ for _, b := range ss.core.config.SessionFirewall.WhitelistEncryptionPublicKeys {
key, err := hex.DecodeString(b)
if err == nil {
copy(box[:crypto.BoxPubKeyLen], key)
@@ -186,7 +187,7 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b
}
}
// Allow outbound sessions if appropriate
- if ss.sessionFirewallAlwaysAllowsOutbound {
+ if ss.core.config.SessionFirewall.AlwaysAllowOutbound {
if initiator {
return true
}
@@ -200,11 +201,11 @@ func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) b
}
}
// Allow direct peers if appropriate
- if ss.sessionFirewallAllowsDirect && isDirectPeer {
+ if ss.core.config.SessionFirewall.AllowFromDirect && isDirectPeer {
return true
}
// Allow remote nodes if appropriate
- if ss.sessionFirewallAllowsRemote && !isDirectPeer {
+ if ss.core.config.SessionFirewall.AllowFromRemote && !isDirectPeer {
return true
}
// Finally, default-deny if not matching any of the above rules
@@ -264,13 +265,12 @@ func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool)
// 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).
func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo {
- if ss.sessionFirewallEnabled {
- if !ss.isSessionAllowed(theirPermKey, true) {
- return nil
- }
+ if !ss.isSessionAllowed(theirPermKey, true) {
+ return nil
}
sinfo := sessionInfo{}
sinfo.core = ss.core
+ sinfo.reconfigure = make(chan chan error, 1)
sinfo.theirPermPub = *theirPermKey
pub, priv := crypto.NewBoxKeys()
sinfo.mySesPub = *pub
@@ -442,7 +442,7 @@ 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.sessionFirewallEnabled {
+ if !isIn && ss.isSessionFirewallEnabled() {
if !ss.isSessionAllowed(&ping.SendPermPub, false) {
return
}
@@ -539,6 +539,8 @@ func (sinfo *sessionInfo) doWorker() {
} else {
return
}
+ case e := <-sinfo.reconfigure:
+ e <- nil
}
}
}
@@ -552,17 +554,30 @@ func (sinfo *sessionInfo) doSend(bs []byte) {
}
// code isn't multithreaded so appending to this is safe
coords := sinfo.coords
- // Read IPv6 flowlabel field (20 bits).
- // Assumes packet at least contains IPv6 header.
- flowkey := uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3])
- // Check if the flowlabel was specified
- if flowkey == 0 {
- // Does the packet meet the minimum UDP packet size? (others are bigger)
- if len(bs) >= 48 {
- // Is the protocol TCP, UDP, SCTP?
+ // 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 {
- // if flowlabel was unspecified (0), try to use known protocols' ports
- // protokey: proto | sport | dport
flowkey = uint64(bs[6])<<32 /* proto */ |
uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ |
uint64(bs[42])<<8 | uint64(bs[43]) /* dport */
@@ -610,5 +625,8 @@ func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
sinfo.updateNonce(&p.Nonce)
sinfo.time = time.Now()
sinfo.bytesRecvd += uint64(len(bs))
- sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo}
+ select {
+ case sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo}:
+ default: // avoid deadlocks, maybe do this somewhere else?...
+ }
}
diff --git a/src/yggdrasil/stream.go b/src/yggdrasil/stream.go
new file mode 100644
index 0000000..4d73844
--- /dev/null
+++ b/src/yggdrasil/stream.go
@@ -0,0 +1,141 @@
+package yggdrasil
+
+import (
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/yggdrasil-network/yggdrasil-go/src/util"
+)
+
+// Test that this matches the interface we expect
+var _ = linkInterfaceMsgIO(&stream{})
+
+type stream struct {
+ rwc io.ReadWriteCloser
+ inputBuffer []byte // Incoming packet stream
+ frag [2 * streamMsgSize]byte // Temporary data read off the underlying rwc, on its way to the inputBuffer
+ outputBuffer [2 * streamMsgSize]byte // Temporary data about to be written to the rwc
+}
+
+func (s *stream) close() error {
+ return s.rwc.Close()
+}
+
+const streamMsgSize = 2048 + 65535
+
+var streamMsg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits"
+
+func (s *stream) init(rwc io.ReadWriteCloser) {
+ // TODO have this also do the metadata handshake and create the peer struct
+ s.rwc = rwc
+ // TODO call something to do the metadata exchange
+}
+
+// writeMsg writes a message with stream padding, and is *not* thread safe.
+func (s *stream) writeMsg(bs []byte) (int, error) {
+ buf := s.outputBuffer[:0]
+ buf = append(buf, streamMsg[:]...)
+ buf = wire_put_uint64(uint64(len(bs)), buf)
+ padLen := len(buf)
+ buf = append(buf, bs...)
+ var bn int
+ for bn < len(buf) {
+ n, err := s.rwc.Write(buf[bn:])
+ bn += n
+ if err != nil {
+ l := bn - padLen
+ if l < 0 {
+ l = 0
+ }
+ return l, err
+ }
+ }
+ return len(bs), nil
+}
+
+// readMsg reads a message from the stream, accounting for stream padding, and is *not* thread safe.
+func (s *stream) readMsg() ([]byte, error) {
+ for {
+ buf := s.inputBuffer
+ msg, ok, err := stream_chopMsg(&buf)
+ switch {
+ case err != nil:
+ // Something in the stream format is corrupt
+ return nil, fmt.Errorf("message error: %v", err)
+ case ok:
+ // Copy the packet into bs, shift the buffer, and return
+ msg = append(util.GetBytes(), msg...)
+ s.inputBuffer = append(s.inputBuffer[:0], buf...)
+ return msg, nil
+ default:
+ // Wait for the underlying reader to return enough info for us to proceed
+ n, err := s.rwc.Read(s.frag[:])
+ if n > 0 {
+ s.inputBuffer = append(s.inputBuffer, s.frag[:n]...)
+ } else if err != nil {
+ return nil, err
+ }
+ }
+ }
+}
+
+// Writes metadata bytes without stream padding, meant to be temporary
+func (s *stream) _sendMetaBytes(metaBytes []byte) error {
+ var written int
+ for written < len(metaBytes) {
+ n, err := s.rwc.Write(metaBytes)
+ written += n
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Reads metadata bytes without stream padding, meant to be temporary
+func (s *stream) _recvMetaBytes() ([]byte, error) {
+ var meta version_metadata
+ frag := meta.encode()
+ metaBytes := make([]byte, 0, len(frag))
+ for len(metaBytes) < len(frag) {
+ n, err := s.rwc.Read(frag)
+ if err != nil {
+ return nil, err
+ }
+ metaBytes = append(metaBytes, frag[:n]...)
+ }
+ return metaBytes, nil
+}
+
+// This takes a pointer to a slice as an argument. It checks if there's a
+// complete message and, if so, slices out those parts and returns the message,
+// true, and nil. If there's no error, but also no complete message, it returns
+// nil, false, and nil. If there's an error, it returns nil, false, and the
+// error, which the reader then handles (currently, by returning from the
+// reader, which causes the connection to close).
+func stream_chopMsg(bs *[]byte) ([]byte, bool, error) {
+ // Returns msg, ok, err
+ if len(*bs) < len(streamMsg) {
+ return nil, false, nil
+ }
+ for idx := range streamMsg {
+ if (*bs)[idx] != streamMsg[idx] {
+ return nil, false, errors.New("bad message")
+ }
+ }
+ msgLen, msgLenLen := wire_decode_uint64((*bs)[len(streamMsg):])
+ if msgLen > streamMsgSize {
+ return nil, false, errors.New("oversized message")
+ }
+ msgBegin := len(streamMsg) + msgLenLen
+ msgEnd := msgBegin + int(msgLen)
+ if msgLenLen == 0 || len(*bs) < msgEnd {
+ // We don't have the full message
+ // Need to buffer this and wait for the rest to come in
+ return nil, false, nil
+ }
+ msg := (*bs)[msgBegin:msgEnd]
+ (*bs) = (*bs)[msgEnd:]
+ return msg, true, nil
+}
diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go
index 3c1dae6..a2877eb 100644
--- a/src/yggdrasil/switch.go
+++ b/src/yggdrasil/switch.go
@@ -4,7 +4,7 @@ package yggdrasil
// It routes packets based on distance on the spanning tree
// In general, this is *not* equivalent to routing on the tree
// It falls back to the tree in the worst case, but it can take shortcuts too
-// This is the part that makse routing reasonably efficient on scale-free graphs
+// This is the part that makes routing reasonably efficient on scale-free graphs
// TODO document/comment everything in a lot more detail
@@ -162,6 +162,7 @@ type switchData struct {
// All the information stored by the switch.
type switchTable struct {
core *Core
+ reconfigure chan chan error
key crypto.SigPubKey // Our own key
time time.Time // Time when locator.tstamp was last updated
drop map[crypto.SigPubKey]int64 // Tstamp associated with a dropped root
@@ -181,11 +182,14 @@ type switchTable struct {
const SwitchQueueTotalMinSize = 4 * 1024 * 1024
// Initializes the switchTable struct.
-func (t *switchTable) init(core *Core, key crypto.SigPubKey) {
+func (t *switchTable) init(core *Core) {
now := time.Now()
t.core = core
- t.key = key
- locator := switchLocator{root: key, tstamp: now.Unix()}
+ 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}
t.updater.Store(&sync.Once{})
@@ -277,6 +281,7 @@ func (t *switchTable) cleanPeers() {
if now.Sub(peer.time) > switch_timeout+switch_throttle {
// Longer than switch_timeout to make sure we don't remove a working peer because the root stopped responding.
delete(t.data.peers, port)
+ go t.core.peers.removePeer(port) // TODO figure out if it's safe to do this without a goroutine, or make it safe
}
}
if _, isIn := t.data.peers[t.parent]; !isIn {
@@ -410,6 +415,7 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep
// Update the matrix of peer "faster" thresholds
if reprocessing {
sender.faster = oldSender.faster
+ sender.time = oldSender.time
} else {
sender.faster = make(map[switchPort]uint64, len(oldSender.faster))
for port, peer := range t.data.peers {
@@ -559,7 +565,7 @@ func (t *switchTable) getTable() lookupTable {
// Starts the switch worker
func (t *switchTable) start() error {
- t.core.log.Println("Starting switch")
+ t.core.log.Infoln("Starting switch")
go t.doWorker()
return nil
}
@@ -774,6 +780,7 @@ func (t *switchTable) doWorker() {
t.queues.bufs = make(map[string]switch_buffer) // Packets per PacketStreamID (string)
idle := make(map[switchPort]struct{}) // this is to deduplicate things
for {
+ t.core.log.Debugf("Switch state: idle = %d, buffers = %d", len(idle), len(t.queues.bufs))
select {
case bytes := <-t.packetIn:
// Try to send it somewhere (or drop it if it's corrupt or at a dead end)
@@ -808,6 +815,8 @@ func (t *switchTable) doWorker() {
}
case f := <-t.admin:
f()
+ case e := <-t.reconfigure:
+ e <- nil
}
}
}
diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go
index 6d92344..c65e2e6 100644
--- a/src/yggdrasil/tcp.go
+++ b/src/yggdrasil/tcp.go
@@ -15,31 +15,28 @@ package yggdrasil
// See version.go for version metadata format
import (
- "errors"
+ "context"
"fmt"
- "io"
"math/rand"
"net"
"sync"
- "sync/atomic"
"time"
"golang.org/x/net/proxy"
- "github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
- "github.com/yggdrasil-network/yggdrasil-go/src/util"
)
-const tcp_msgSize = 2048 + 65535 // TODO figure out what makes sense
-const default_tcp_timeout = 6 * time.Second
-const tcp_ping_interval = (default_tcp_timeout * 2 / 3)
+const default_timeout = 6 * time.Second
+const tcp_ping_interval = (default_timeout * 2 / 3)
// The TCP listener and information about active TCP connections, to avoid duplication.
type tcpInterface struct {
core *Core
+ reconfigure chan chan error
serv net.Listener
- tcp_timeout time.Duration
+ stop chan bool
+ addr string
mutex sync.Mutex // Protecting the below
calls map[string]struct{}
conns map[tcpInfo](chan struct{})
@@ -80,19 +77,48 @@ func (iface *tcpInterface) connectSOCKS(socksaddr, peeraddr string) {
}
// Initializes the struct.
-func (iface *tcpInterface) init(core *Core, addr string, readTimeout int32) (err error) {
+func (iface *tcpInterface) init(core *Core) (err error) {
iface.core = core
+ iface.stop = make(chan bool, 1)
+ iface.reconfigure = make(chan chan error, 1)
+ go func() {
+ for {
+ e := <-iface.reconfigure
+ iface.core.configMutex.RLock()
+ updated := iface.core.config.Listen != iface.core.configOld.Listen
+ iface.core.configMutex.RUnlock()
+ if updated {
+ iface.stop <- true
+ iface.serv.Close()
+ e <- iface.listen()
+ } else {
+ e <- nil
+ }
+ }
+ }()
- iface.tcp_timeout = time.Duration(readTimeout) * time.Millisecond
- if iface.tcp_timeout >= 0 && iface.tcp_timeout < default_tcp_timeout {
- iface.tcp_timeout = default_tcp_timeout
+ return iface.listen()
+}
+
+func (iface *tcpInterface) listen() error {
+ var err error
+
+ iface.core.configMutex.RLock()
+ iface.addr = iface.core.config.Listen
+ iface.core.configMutex.RUnlock()
+
+ ctx := context.Background()
+ lc := net.ListenConfig{
+ Control: iface.tcpContext,
}
-
- iface.serv, err = net.Listen("tcp", addr)
+ iface.serv, err = lc.Listen(ctx, "tcp", iface.addr)
if err == nil {
+ iface.mutex.Lock()
iface.calls = make(map[string]struct{})
iface.conns = make(map[tcpInfo](chan struct{}))
+ iface.mutex.Unlock()
go iface.listener()
+ return nil
}
return err
@@ -101,16 +127,42 @@ func (iface *tcpInterface) init(core *Core, addr string, readTimeout int32) (err
// Runs the listener, which spawns off goroutines for incoming connections.
func (iface *tcpInterface) listener() {
defer iface.serv.Close()
- iface.core.log.Println("Listening for TCP on:", iface.serv.Addr().String())
+ iface.core.log.Infoln("Listening for TCP on:", iface.serv.Addr().String())
for {
sock, err := iface.serv.Accept()
if err != nil {
- panic(err)
+ iface.core.log.Errorln("Failed to accept connection:", err)
+ return
+ }
+ select {
+ case <-iface.stop:
+ iface.core.log.Errorln("Stopping listener")
+ return
+ default:
+ if err != nil {
+ panic(err)
+ }
+ go iface.handler(sock, true)
}
- go iface.handler(sock, true)
}
}
+// Checks if we already have a connection to this node
+func (iface *tcpInterface) isAlreadyConnected(info tcpInfo) bool {
+ iface.mutex.Lock()
+ defer iface.mutex.Unlock()
+ _, isIn := iface.conns[info]
+ return isIn
+}
+
+// Checks if we already are calling this address
+func (iface *tcpInterface) isAlreadyCalling(saddr string) bool {
+ iface.mutex.Lock()
+ defer iface.mutex.Unlock()
+ _, isIn := iface.calls[saddr]
+ return isIn
+}
+
// Checks if a connection already exists.
// If not, it adds it to the list of active outgoing calls (to block future attempts) and dials the address.
// If the dial is successful, it launches the handler.
@@ -122,25 +174,20 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
if sintf != "" {
callname = fmt.Sprintf("%s/%s", saddr, sintf)
}
- quit := false
- iface.mutex.Lock()
- if _, isIn := iface.calls[callname]; isIn {
- quit = true
- } else {
- iface.calls[callname] = struct{}{}
- defer func() {
- // Block new calls for a little while, to mitigate livelock scenarios
- time.Sleep(default_tcp_timeout)
- time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
- iface.mutex.Lock()
- delete(iface.calls, callname)
- iface.mutex.Unlock()
- }()
- }
- iface.mutex.Unlock()
- if quit {
+ if iface.isAlreadyCalling(callname) {
return
}
+ iface.mutex.Lock()
+ iface.calls[callname] = struct{}{}
+ iface.mutex.Unlock()
+ defer func() {
+ // Block new calls for a little while, to mitigate livelock scenarios
+ time.Sleep(default_timeout)
+ time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
+ iface.mutex.Lock()
+ delete(iface.calls, callname)
+ iface.mutex.Unlock()
+ }()
var conn net.Conn
var err error
if socksaddr != nil {
@@ -164,40 +211,57 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
},
}
} else {
- dialer := net.Dialer{}
+ dialer := net.Dialer{
+ Control: iface.tcpContext,
+ }
if sintf != "" {
ief, err := net.InterfaceByName(sintf)
if err != nil {
return
- } else {
- if ief.Flags&net.FlagUp == 0 {
+ }
+ if ief.Flags&net.FlagUp == 0 {
+ return
+ }
+ addrs, err := ief.Addrs()
+ if err == nil {
+ dst, err := net.ResolveTCPAddr("tcp", saddr)
+ if err != nil {
return
}
- addrs, err := ief.Addrs()
- if err == nil {
- dst, err := net.ResolveTCPAddr("tcp", saddr)
+ for addrindex, addr := range addrs {
+ src, _, err := net.ParseCIDR(addr.String())
if err != nil {
- return
+ continue
}
- for _, addr := range addrs {
- src, _, err := net.ParseCIDR(addr.String())
- if err != nil {
- continue
- }
- if (src.To4() != nil) == (dst.IP.To4() != nil) && src.IsGlobalUnicast() {
- dialer.LocalAddr = &net.TCPAddr{
- IP: src,
- Port: 0,
- }
- break
+ if src.Equal(dst.IP) {
+ continue
+ }
+ if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() {
+ continue
+ }
+ bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast()
+ bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast()
+ if !bothglobal && !bothlinklocal {
+ continue
+ }
+ if (src.To4() != nil) != (dst.IP.To4() != nil) {
+ continue
+ }
+ if bothglobal || bothlinklocal || addrindex == len(addrs)-1 {
+ dialer.LocalAddr = &net.TCPAddr{
+ IP: src,
+ Port: 0,
+ Zone: sintf,
}
+ break
}
- if dialer.LocalAddr == nil {
- return
- }
+ }
+ if dialer.LocalAddr == nil {
+ return
}
}
}
+
conn, err = dialer.Dial("tcp", saddr)
if err != nil {
return
@@ -207,237 +271,21 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) {
}()
}
-// This exchanges/checks connection metadata, sets up the peer struct, sets up the writer goroutine, and then runs the reader within the current goroutine.
-// It defers a bunch of cleanup stuff to tear down all of these things when the reader exists (e.g. due to a closed connection or a timeout).
func (iface *tcpInterface) handler(sock net.Conn, incoming bool) {
defer sock.Close()
iface.setExtraOptions(sock)
- // Get our keys
- myLinkPub, myLinkPriv := crypto.NewBoxKeys() // ephemeral link keys
- meta := version_getBaseMetadata()
- meta.box = iface.core.boxPub
- meta.sig = iface.core.sigPub
- meta.link = *myLinkPub
- metaBytes := meta.encode()
- _, err := sock.Write(metaBytes)
+ stream := stream{}
+ stream.init(sock)
+ local, _, _ := net.SplitHostPort(sock.LocalAddr().String())
+ remote, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
+ remotelinklocal := net.ParseIP(remote).IsLinkLocalUnicast()
+ name := "tcp://" + sock.RemoteAddr().String()
+ link, err := iface.core.link.create(&stream, name, "tcp", local, remote, incoming, remotelinklocal)
if err != nil {
- return
+ iface.core.log.Println(err)
+ panic(err)
}
- if iface.tcp_timeout > 0 {
- sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout))
- }
- _, err = sock.Read(metaBytes)
- if err != nil {
- return
- }
- meta = version_metadata{} // Reset to zero value
- if !meta.decode(metaBytes) || !meta.check() {
- // Failed to decode and check the metadata
- // If it's a version mismatch issue, then print an error message
- base := version_getBaseMetadata()
- if meta.meta == base.meta {
- if meta.ver > base.ver {
- iface.core.log.Println("Failed to connect to node:", sock.RemoteAddr().String(), "version:", meta.ver)
- } else if meta.ver == base.ver && meta.minorVer > base.minorVer {
- iface.core.log.Println("Failed to connect to node:", sock.RemoteAddr().String(), "version:", fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
- }
- }
- // TODO? Block forever to prevent future connection attempts? suppress future messages about the same node?
- return
- }
- info := tcpInfo{ // used as a map key, so don't include ephemeral link key
- box: meta.box,
- sig: meta.sig,
- }
- // Quit the parent call if this is a connection to ourself
- equiv := func(k1, k2 []byte) bool {
- for idx := range k1 {
- if k1[idx] != k2[idx] {
- return false
- }
- }
- return true
- }
- if equiv(info.box[:], iface.core.boxPub[:]) {
- return
- }
- if equiv(info.sig[:], iface.core.sigPub[:]) {
- return
- }
- // Check if we're authorized to connect to this key / IP
- if incoming && !iface.core.peers.isAllowedEncryptionPublicKey(&info.box) {
- // Allow unauthorized peers if they're link-local
- raddrStr, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
- raddr := net.ParseIP(raddrStr)
- if !raddr.IsLinkLocalUnicast() {
- return
- }
- }
- // Check if we already have a connection to this node, close and block if yes
- info.localAddr, _, _ = net.SplitHostPort(sock.LocalAddr().String())
- info.remoteAddr, _, _ = net.SplitHostPort(sock.RemoteAddr().String())
- iface.mutex.Lock()
- if blockChan, isIn := iface.conns[info]; isIn {
- iface.mutex.Unlock()
- sock.Close()
- <-blockChan
- return
- }
- blockChan := make(chan struct{})
- iface.conns[info] = blockChan
- iface.mutex.Unlock()
- defer func() {
- iface.mutex.Lock()
- delete(iface.conns, info)
- iface.mutex.Unlock()
- close(blockChan)
- }()
- // Note that multiple connections to the same node are allowed
- // E.g. over different interfaces
- p := iface.core.peers.newPeer(&info.box, &info.sig, crypto.GetSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String())
- p.linkOut = make(chan []byte, 1)
- in := func(bs []byte) {
- p.handlePacket(bs)
- }
- out := make(chan []byte, 1)
- defer close(out)
- go func() {
- // This goroutine waits for outgoing packets, link protocol traffic, or sends idle keep-alive traffic
- send := func(msg []byte) {
- msgLen := wire_encode_uint64(uint64(len(msg)))
- buf := net.Buffers{tcp_msg[:], msgLen, msg}
- buf.WriteTo(sock)
- atomic.AddUint64(&p.bytesSent, uint64(len(tcp_msg)+len(msgLen)+len(msg)))
- util.PutBytes(msg)
- }
- timerInterval := tcp_ping_interval
- timer := time.NewTimer(timerInterval)
- defer timer.Stop()
- for {
- select {
- case msg := <-p.linkOut:
- // Always send outgoing link traffic first, if needed
- send(msg)
- continue
- default:
- }
- // Otherwise wait reset the timer and wait for something to do
- timer.Stop()
- select {
- case <-timer.C:
- default:
- }
- timer.Reset(timerInterval)
- select {
- case _ = <-timer.C:
- send(nil) // TCP keep-alive traffic
- case msg := <-p.linkOut:
- send(msg)
- case msg, ok := <-out:
- if !ok {
- return
- }
- send(msg) // Block until the socket write has finished
- // Now inform the switch that we're ready for more traffic
- p.core.switchTable.idleIn <- p.port
- }
- }
- }()
- p.core.switchTable.idleIn <- p.port // Start in the idle state
- p.out = func(msg []byte) {
- defer func() { recover() }()
- out <- msg
- }
- p.close = func() { sock.Close() }
- go p.linkLoop()
- defer func() {
- // Put all of our cleanup here...
- p.core.peers.removePeer(p.port)
- }()
- us, _, _ := net.SplitHostPort(sock.LocalAddr().String())
- them, _, _ := net.SplitHostPort(sock.RemoteAddr().String())
- themNodeID := crypto.GetNodeID(&info.box)
- themAddr := address.AddrForNodeID(themNodeID)
- themAddrString := net.IP(themAddr[:]).String()
- themString := fmt.Sprintf("%s@%s", themAddrString, them)
- iface.core.log.Println("Connected:", themString, "source", us)
- err = iface.reader(sock, in) // In this goroutine, because of defers
- if err == nil {
- iface.core.log.Println("Disconnected:", themString, "source", us)
- } else {
- iface.core.log.Println("Disconnected:", themString, "source", us, "with error:", err)
- }
- return
-}
-
-// This reads from the socket into a []byte buffer for incomping messages.
-// It copies completed messages out of the cache into a new slice, and passes them to the peer struct via the provided `in func([]byte)` argument.
-// Then it shifts the incomplete fragments of data forward so future reads won't overwrite it.
-func (iface *tcpInterface) reader(sock net.Conn, in func([]byte)) error {
- bs := make([]byte, 2*tcp_msgSize)
- frag := bs[:0]
- for {
- if iface.tcp_timeout > 0 {
- sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout))
- }
- n, err := sock.Read(bs[len(frag):])
- if n > 0 {
- frag = bs[:len(frag)+n]
- for {
- msg, ok, err2 := tcp_chop_msg(&frag)
- if err2 != nil {
- return fmt.Errorf("Message error: %v", err2)
- }
- if !ok {
- // We didn't get the whole message yet
- break
- }
- newMsg := append(util.GetBytes(), msg...)
- in(newMsg)
- util.Yield()
- }
- frag = append(bs[:0], frag...)
- }
- if err != nil || n == 0 {
- if err != io.EOF {
- return err
- }
- return nil
- }
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-// These are 4 bytes of padding used to catch if something went horribly wrong with the tcp connection.
-var tcp_msg = [...]byte{0xde, 0xad, 0xb1, 0x75} // "dead bits"
-
-// This takes a pointer to a slice as an argument.
-// It checks if there's a complete message and, if so, slices out those parts and returns the message, true, and nil.
-// If there's no error, but also no complete message, it returns nil, false, and nil.
-// If there's an error, it returns nil, false, and the error, which the reader then handles (currently, by returning from the reader, which causes the connection to close).
-func tcp_chop_msg(bs *[]byte) ([]byte, bool, error) {
- // Returns msg, ok, err
- if len(*bs) < len(tcp_msg) {
- return nil, false, nil
- }
- for idx := range tcp_msg {
- if (*bs)[idx] != tcp_msg[idx] {
- return nil, false, errors.New("Bad message!")
- }
- }
- msgLen, msgLenLen := wire_decode_uint64((*bs)[len(tcp_msg):])
- if msgLen > tcp_msgSize {
- return nil, false, errors.New("Oversized message!")
- }
- msgBegin := len(tcp_msg) + msgLenLen
- msgEnd := msgBegin + int(msgLen)
- if msgLenLen == 0 || len(*bs) < msgEnd {
- // We don't have the full message
- // Need to buffer this and wait for the rest to come in
- return nil, false, nil
- }
- msg := (*bs)[msgBegin:msgEnd]
- (*bs) = (*bs)[msgEnd:]
- return msg, true, nil
+ iface.core.log.Debugln("DEBUG: starting handler for", name)
+ err = link.handler()
+ iface.core.log.Debugln("DEBUG: stopped handler for", name, err)
}
diff --git a/src/yggdrasil/tcp_darwin.go b/src/yggdrasil/tcp_darwin.go
new file mode 100644
index 0000000..6483ef8
--- /dev/null
+++ b/src/yggdrasil/tcp_darwin.go
@@ -0,0 +1,28 @@
+// +build darwin
+
+package yggdrasil
+
+import (
+ "syscall"
+
+ "golang.org/x/sys/unix"
+)
+
+// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
+
+func (iface *tcpInterface) tcpContext(network, address string, c syscall.RawConn) error {
+ var control error
+ var recvanyif error
+
+ control = c.Control(func(fd uintptr) {
+ // sys/socket.h: #define SO_RECV_ANYIF 0x1104
+ recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1)
+ })
+
+ switch {
+ case recvanyif != nil:
+ return recvanyif
+ default:
+ return control
+ }
+}
diff --git a/src/yggdrasil/tcp_other.go b/src/yggdrasil/tcp_other.go
new file mode 100644
index 0000000..5d62b53
--- /dev/null
+++ b/src/yggdrasil/tcp_other.go
@@ -0,0 +1,13 @@
+// +build !darwin
+
+package yggdrasil
+
+import (
+ "syscall"
+)
+
+// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
+
+func (iface *tcpInterface) tcpContext(network, address string, c syscall.RawConn) error {
+ return nil
+}
diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go
index 8ed5333..465cbb1 100644
--- a/src/yggdrasil/tun.go
+++ b/src/yggdrasil/tun.go
@@ -5,6 +5,8 @@ package yggdrasil
import (
"bytes"
"errors"
+ "fmt"
+ "net"
"sync"
"time"
@@ -42,22 +44,46 @@ func getSupportedMTU(mtu int) int {
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(ifname string, iftapmode bool, addr string, mtu int) error {
- if ifname == "none" {
- return nil
+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 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.Println("WARNING: tun.read() exited with error:", tun.read()) }()
- go func() { tun.core.log.Println("WARNING: tun.write() exited with error:", tun.write()) }()
+ 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 {
diff --git a/src/yggdrasil/tun_bsd.go b/src/yggdrasil/tun_bsd.go
index 620c79d..81e2c46 100644
--- a/src/yggdrasil/tun_bsd.go
+++ b/src/yggdrasil/tun_bsd.go
@@ -114,9 +114,9 @@ func (tun *tunAdapter) setupAddress(addr string) error {
}
// Friendly output
- tun.core.log.Printf("Interface name: %s", tun.iface.Name())
- tun.core.log.Printf("Interface IPv6: %s", addr)
- tun.core.log.Printf("Interface MTU: %d", tun.mtu)
+ 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)
// 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.Printf("Error in SIOCSIFMTU: %v", errno)
+ tun.core.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.Printf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
+ tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Printf("SIOCSIFMTU fallback failed: %v.", err)
- tun.core.log.Println(string(output))
+ tun.core.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
+ tun.core.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.Printf("Error in SIOCSIFADDR_IN6: %v", errno)
+ tun.core.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.Printf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
+ tun.core.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Printf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
- tun.core.log.Println(string(output))
+ tun.core.log.Errorf("SIOCSIFADDR_IN6 fallback failed: %v.", err)
+ tun.core.log.Traceln(string(output))
}
}
diff --git a/src/yggdrasil/tun_darwin.go b/src/yggdrasil/tun_darwin.go
index 943468e..7ec1b8b 100644
--- a/src/yggdrasil/tun_darwin.go
+++ b/src/yggdrasil/tun_darwin.go
@@ -1,3 +1,5 @@
+// +build !mobile
+
package yggdrasil
// The darwin platform specific tun parts
@@ -16,7 +18,7 @@ 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 {
if iftapmode {
- tun.core.log.Printf("TAP mode is not supported on this platform, defaulting to TUN")
+ tun.core.log.Warnln("TAP mode is not supported on this platform, defaulting to TUN")
}
config := water.Config{DeviceType: water.TUN}
iface, err := water.New(config)
@@ -96,19 +98,19 @@ func (tun *tunAdapter) setupAddress(addr string) error {
copy(ir.ifr_name[:], tun.iface.Name())
ir.ifru_mtu = uint32(tun.mtu)
- tun.core.log.Printf("Interface name: %s", ar.ifra_name)
- tun.core.log.Printf("Interface IPv6: %s", addr)
- tun.core.log.Printf("Interface MTU: %d", ir.ifru_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)
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
err = errno
- tun.core.log.Printf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
+ tun.core.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.Printf("Error in SIOCSIFMTU: %v", errno)
+ tun.core.log.Errorf("Error in SIOCSIFMTU: %v", errno)
return err
}
diff --git a/src/yggdrasil/tun_dummy.go b/src/yggdrasil/tun_dummy.go
new file mode 100644
index 0000000..234ab1d
--- /dev/null
+++ b/src/yggdrasil/tun_dummy.go
@@ -0,0 +1,19 @@
+// +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/tun_linux.go b/src/yggdrasil/tun_linux.go
index 7a7c9cb..30ada23 100644
--- a/src/yggdrasil/tun_linux.go
+++ b/src/yggdrasil/tun_linux.go
@@ -1,3 +1,5 @@
+// +build !mobile
+
package yggdrasil
// The linux platform specific tun parts
@@ -38,9 +40,9 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
}
}
// Friendly output
- tun.core.log.Printf("Interface name: %s", tun.iface.Name())
- tun.core.log.Printf("Interface IPv6: %s", addr)
- tun.core.log.Printf("Interface MTU: %d", tun.mtu)
+ 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)
return tun.setupAddress(addr)
}
diff --git a/src/yggdrasil/tun_other.go b/src/yggdrasil/tun_other.go
index 625f9cd..07ec25f 100644
--- a/src/yggdrasil/tun_other.go
+++ b/src/yggdrasil/tun_other.go
@@ -1,4 +1,4 @@
-// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd
+// +build !linux,!darwin,!windows,!openbsd,!freebsd,!netbsd,!mobile
package yggdrasil
@@ -28,6 +28,6 @@ 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.Println("Platform not supported, you must set the address of", tun.iface.Name(), "to", addr)
+ tun.core.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/yggdrasil/tun_windows.go
index 150a976..1c89a43 100644
--- a/src/yggdrasil/tun_windows.go
+++ b/src/yggdrasil/tun_windows.go
@@ -15,7 +15,7 @@ import (
// delegate the hard work to "netsh".
func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int) error {
if !iftapmode {
- tun.core.log.Printf("TUN mode is not supported on this platform, defaulting to TAP")
+ tun.core.log.Warnln("TUN mode is not supported on this platform, defaulting to TAP")
}
config := water.Config{DeviceType: water.TAP}
config.PlatformSpecificParams.ComponentID = "tap0901"
@@ -34,16 +34,16 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Printf("Windows netsh failed: %v.", err)
- tun.core.log.Println(string(output))
+ tun.core.log.Errorf("Windows netsh failed: %v.", err)
+ tun.core.log.Traceln(string(output))
return err
}
cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED")
tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
output, err = cmd.CombinedOutput()
if err != nil {
- tun.core.log.Printf("Windows netsh failed: %v.", err)
- tun.core.log.Println(string(output))
+ tun.core.log.Errorf("Windows netsh failed: %v.", err)
+ tun.core.log.Traceln(string(output))
return err
}
// Get a new iface
@@ -58,9 +58,9 @@ func (tun *tunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int
panic(err)
}
// Friendly output
- tun.core.log.Printf("Interface name: %s", tun.iface.Name())
- tun.core.log.Printf("Interface IPv6: %s", addr)
- tun.core.log.Printf("Interface MTU: %d", tun.mtu)
+ 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)
return tun.setupAddress(addr)
}
@@ -71,11 +71,11 @@ func (tun *tunAdapter) setupMTU(mtu int) error {
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("mtu=%d", mtu),
"store=active")
- tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
+ tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Printf("Windows netsh failed: %v.", err)
- tun.core.log.Println(string(output))
+ tun.core.log.Errorf("Windows netsh failed: %v.", err)
+ tun.core.log.Traceln(string(output))
return err
}
return nil
@@ -88,11 +88,11 @@ func (tun *tunAdapter) setupAddress(addr string) error {
fmt.Sprintf("interface=%s", tun.iface.Name()),
fmt.Sprintf("addr=%s", addr),
"store=active")
- tun.core.log.Printf("netsh command: %v", strings.Join(cmd.Args, " "))
+ tun.core.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
- tun.core.log.Printf("Windows netsh failed: %v.", err)
- tun.core.log.Println(string(output))
+ tun.core.log.Errorf("Windows netsh failed: %v.", err)
+ tun.core.log.Traceln(string(output))
return err
}
return nil