diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 43cf77a7..f67bbcef 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -6,16 +6,14 @@ import ( "flag" "fmt" "io/ioutil" - "log" "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" @@ -31,6 +29,119 @@ type node struct { core Core } +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 { + // 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": "", + "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) + } + + return cfg +} + // Generates a new configuration and returns it in HJSON format. This is used // with -genconf. func doGenconf(isjson bool) string { @@ -58,9 +169,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()) @@ -71,114 +184,8 @@ func main() { // port numbers, and will use an automatically selected TUN/TAP interface. 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 configjson []byte - var err error - if *useconffile != "" { - // Read the file from the filesystem - configjson, err = ioutil.ReadFile(*useconffile) - } else { - // Read the file from stdin. - configjson, 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(configjson[0:2], []byte{0xFF, 0xFE}) == 0 || - bytes.Compare(configjson[0:2], []byte{0xFE, 0xFF}) == 0 { - utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) - decoder := utf.NewDecoder() - configjson, err = decoder.Bytes(configjson) - 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(configjson, &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 @@ -211,51 +218,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() { @@ -265,11 +251,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 @@ -277,5 +265,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 00000000..7df2e588 --- /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/go.mod b/go.mod index 53a5a2b9..3e8db512 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 + github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 github.com/yggdrasil-network/water v0.0.0-20180615095340-f732c88f34ae golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 diff --git a/go.sum b/go.sum index 1695daff..17b1017a 100644 --- a/go.sum +++ b/go.sum @@ -18,3 +18,5 @@ golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dz golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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= diff --git a/src/yggdrasil/adapter.go b/src/yggdrasil/adapter.go index 7fb6a19e..3ce80d2b 100644 --- a/src/yggdrasil/adapter.go +++ b/src/yggdrasil/adapter.go @@ -3,9 +3,10 @@ package yggdrasil // Defines the minimum required struct members for an adapter type (this is // now the base type for tunAdapter in tun.go) type Adapter struct { - core *Core - send chan<- []byte - recv <-chan []byte + core *Core + send chan<- []byte + recv <-chan []byte + reconfigure chan chan error } // Initialises the adapter. @@ -13,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 2496b309..b54ac5cf 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 { @@ -331,7 +348,10 @@ func (a *admin) init(c *Core, listenaddr string) { } var box_pub_key, coords string if in["box_pub_key"] == nil && in["coords"] == nil { - nodeinfo := []byte(a.core.nodeinfo.getNodeInfo()) + 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 @@ -378,7 +398,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 { @@ -386,7 +406,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!") } } } @@ -400,10 +420,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() @@ -430,9 +450,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() } @@ -745,35 +765,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. @@ -842,7 +847,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 } } @@ -860,14 +865,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 index 633d5f9c..190d0258 100644 --- a/src/yggdrasil/awdl.go +++ b/src/yggdrasil/awdl.go @@ -50,24 +50,24 @@ func (l *awdl) create(fromAWDL chan []byte, toAWDL chan []byte /*boxPubKey *cryp meta.sig = l.core.sigPub meta.link = *myLinkPub metaBytes := meta.encode() - l.core.log.Println("toAWDL <- metaBytes") + l.core.log.Traceln("toAWDL <- metaBytes") toAWDL <- metaBytes - l.core.log.Println("metaBytes = <-fromAWDL") + l.core.log.Traceln("metaBytes = <-fromAWDL") metaBytes = <-fromAWDL - l.core.log.Println("version_metadata{}") + l.core.log.Traceln("version_metadata{}") meta = version_metadata{} if !meta.decode(metaBytes) || !meta.check() { return nil, errors.New("Metadata decode failure") } - l.core.log.Println("version_getBaseMetadata{}") + l.core.log.Traceln("version_getBaseMetadata{}") base := version_getBaseMetadata() if meta.ver > base.ver || meta.ver == base.ver && meta.minorVer > base.minorVer { return nil, errors.New("Failed to connect to node: " + name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) } - l.core.log.Println("crypto.GetSharedKey") + l.core.log.Traceln("crypto.GetSharedKey") shared := crypto.GetSharedKey(myLinkPriv, &meta.link) //shared := crypto.GetSharedKey(&l.core.boxPriv, boxPubKey) - l.core.log.Println("l.core.peers.newPeer") + l.core.log.Traceln("l.core.peers.newPeer") intf.peer = l.core.peers.newPeer(&meta.box, &meta.sig, shared, name) if intf.peer != nil { intf.peer.linkOut = make(chan []byte, 1) // protocol traffic diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index a3df891e..03bc5718 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 a1c2127d..c78bc1fb 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,17 +44,12 @@ type Core struct { admin admin searches searches multicast multicast - nodeinfo nodeinfo tcp tcpInterface awdl awdl 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 @@ -51,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" @@ -89,52 +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 { - 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.configMutex.Lock() + c.config = *nc + c.configOld = c.config + c.configMutex.Unlock() - c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) - c.admin.init(c, nc.AdminListen) + c.init() - 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.tcp.init(c); err != nil { + c.log.Errorln("Failed to start TCP interface") return err } if err := c.awdl.init(c); err != nil { - c.log.Println("Failed to start AWDL interface") + c.log.Errorln("Failed to start AWDL interface") return err } @@ -143,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() } @@ -250,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 @@ -270,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 4a32eb64..f7319e46 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() } @@ -350,7 +363,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 +371,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 +451,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 +499,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 +518,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 b52a820b..5427aca9 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/mobile.go b/src/yggdrasil/mobile.go index 2ffeffb9..220e6ca7 100644 --- a/src/yggdrasil/mobile.go +++ b/src/yggdrasil/mobile.go @@ -77,9 +77,7 @@ func (c *Core) StartJSON(configjson []byte) error { return err } nc.IfName = "dummy" - //c.log.Println(nc.MulticastInterfaces) for _, ll := range nc.MulticastInterfaces { - //c.log.Println("Processing MC", ll) ifceExpr, err := regexp.Compile(ll) if err != nil { panic(err) diff --git a/src/yggdrasil/multicast.go b/src/yggdrasil/multicast.go index c0a676a8..42651deb 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 { diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index b9076328..963a2fc2 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 333561e5..2cd1afe8 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,7 +112,7 @@ 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. +// 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) *peer { now := time.Now() p := peer{box: *box, @@ -342,7 +357,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 87da8829..d5059369 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,6 +63,7 @@ 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... @@ -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 c85b719c..c391dda2 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 4f395b07..cdabaf2b 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/switch.go b/src/yggdrasil/switch.go index 3c1dae61..f2adf3fb 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{}) @@ -559,7 +563,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 } @@ -808,6 +812,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 8d8fee34..a83213d4 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -39,8 +39,11 @@ const tcp_ping_interval = (default_tcp_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 + serv_stop chan bool tcp_timeout time.Duration + tcp_addr string mutex sync.Mutex // Protecting the below calls map[string]struct{} conns map[tcpInfo](chan struct{}) @@ -81,10 +84,37 @@ 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.serv_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.serv_stop <- true + iface.serv.Close() + e <- iface.listen() + } else { + e <- nil + } + } + }() + + return iface.listen() +} + +func (iface *tcpInterface) listen() error { + var err error + + iface.core.configMutex.RLock() + iface.tcp_addr = iface.core.config.Listen + iface.tcp_timeout = time.Duration(iface.core.config.ReadTimeout) * time.Millisecond + iface.core.configMutex.RUnlock() - 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 } @@ -93,11 +123,14 @@ func (iface *tcpInterface) init(core *Core, addr string, readTimeout int32) (err lc := net.ListenConfig{ Control: iface.tcpContext, } - iface.serv, err = lc.Listen(ctx, "tcp", addr) + iface.serv, err = lc.Listen(ctx, "tcp", iface.tcp_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 @@ -106,16 +139,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.serv_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. @@ -127,25 +186,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(saddr) { 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_tcp_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 { @@ -176,35 +230,50 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) { 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 @@ -244,17 +313,27 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { 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) + iface.core.log.Errorln("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)) + iface.core.log.Errorln("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 } + remoteAddr, _, e1 := net.SplitHostPort(sock.RemoteAddr().String()) + localAddr, _, e2 := net.SplitHostPort(sock.LocalAddr().String()) + if e1 != nil || e2 != nil { + return + } info := tcpInfo{ // used as a map key, so don't include ephemeral link key - box: meta.box, - sig: meta.sig, + box: meta.box, + sig: meta.sig, + localAddr: localAddr, + remoteAddr: remoteAddr, + } + if iface.isAlreadyConnected(info) { + return } // Quit the parent call if this is a connection to ourself equiv := func(k1, k2 []byte) bool { @@ -265,14 +344,14 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { } return true } - if equiv(info.box[:], iface.core.boxPub[:]) { + if equiv(meta.box[:], iface.core.boxPub[:]) { return } - if equiv(info.sig[:], iface.core.sigPub[:]) { + if equiv(meta.sig[:], iface.core.sigPub[:]) { return } // Check if we're authorized to connect to this key / IP - if incoming && !iface.core.peers.isAllowedEncryptionPublicKey(&info.box) { + if incoming && !iface.core.peers.isAllowedEncryptionPublicKey(&meta.box) { // Allow unauthorized peers if they're link-local raddrStr, _, _ := net.SplitHostPort(sock.RemoteAddr().String()) raddr := net.ParseIP(raddrStr) @@ -281,15 +360,13 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { } } // 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 { + /*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() @@ -301,7 +378,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { }() // 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 := iface.core.peers.newPeer(&meta.box, &meta.sig, crypto.GetSharedKey(myLinkPriv, &meta.link), sock.RemoteAddr().String()) p.linkOut = make(chan []byte, 1) in := func(bs []byte) { p.handlePacket(bs) @@ -363,16 +440,16 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { }() us, _, _ := net.SplitHostPort(sock.LocalAddr().String()) them, _, _ := net.SplitHostPort(sock.RemoteAddr().String()) - themNodeID := crypto.GetNodeID(&info.box) + themNodeID := crypto.GetNodeID(&meta.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) + iface.core.log.Infof("Connected: %s, source: %s", themString, us) err = iface.reader(sock, in) // In this goroutine, because of defers if err == nil { - iface.core.log.Println("Disconnected:", themString, "source", us) + iface.core.log.Infof("Disconnected: %s, source: %s", themString, us) } else { - iface.core.log.Println("Disconnected:", themString, "source", us, "with error:", err) + iface.core.log.Infof("Disconnected: %s, source: %s, error: %s", themString, us, err) } return } diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index 8c0f91d5..465cbb1c 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,11 +44,33 @@ 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 { +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 @@ -58,8 +82,8 @@ func (tun *tunAdapter) start(ifname string, iftapmode bool, addr string, mtu int 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 620c79db..81e2c46c 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 828c01ea..7ec1b8b9 100644 --- a/src/yggdrasil/tun_darwin.go +++ b/src/yggdrasil/tun_darwin.go @@ -18,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) @@ -98,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_linux.go b/src/yggdrasil/tun_linux.go index 8ccdd30b..30ada235 100644 --- a/src/yggdrasil/tun_linux.go +++ b/src/yggdrasil/tun_linux.go @@ -40,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 22058c11..07ec25fd 100644 --- a/src/yggdrasil/tun_other.go +++ b/src/yggdrasil/tun_other.go @@ -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 150a9766..1c89a437 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