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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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