From 8be919abba2b21b1f9919612b8e6e81939ed5f49 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 9 Feb 2022 18:43:18 +0000 Subject: [PATCH] Refactor node setup, config, defaults --- cmd/yggdrasil/main.go | 327 ++++--------------------------- go.mod | 3 + go.sum | 6 + src/config/config.go | 108 ++++++++++ src/core/core_test.go | 3 +- src/defaults/defaults.go | 31 +-- src/defaults/defaults_darwin.go | 5 +- src/defaults/defaults_freebsd.go | 4 +- src/defaults/defaults_linux.go | 4 +- src/defaults/defaults_openbsd.go | 4 +- src/defaults/defaults_other.go | 4 +- src/defaults/defaults_windows.go | 4 +- src/setup/setup.go | 223 +++++++++++++++++++++ 13 files changed, 395 insertions(+), 331 deletions(-) create mode 100644 src/setup/setup.go diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 58b8230d..4e5fa6f3 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -1,235 +1,36 @@ package main import ( - "bytes" - "context" "crypto/ed25519" "encoding/hex" "encoding/json" "flag" "fmt" - "io/ioutil" "net" "os" "os/signal" - "strings" "syscall" - "golang.org/x/text/encoding/unicode" - "github.com/gologme/log" gsyslog "github.com/hashicorp/go-syslog" "github.com/hjson/hjson-go" - "github.com/kardianos/minwinsvc" - "github.com/mitchellh/mapstructure" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" + "github.com/yggdrasil-network/yggdrasil-go/src/setup" - "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" - "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) -type node struct { - core core.Core - config *config.NodeConfig - tuntap *tuntap.TunAdapter - multicast *multicast.Multicast - admin *admin.AdminSocket -} - -func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf bool) *config.NodeConfig { - // Use a configuration file. If -useconf, the configuration will be read - // from stdin. If -useconffile, the configuration will be read from the - // filesystem. - var conf []byte - var err error - if useconffile != "" { - // Read the file from the filesystem - conf, err = ioutil.ReadFile(useconffile) - } else { - // Read the file from stdin. - conf, err = ioutil.ReadAll(os.Stdin) - } - if err != nil { - panic(err) - } - // If there's a byte order mark - which Windows 10 is now incredibly fond of - // throwing everywhere when it's converting things into UTF-16 for the hell - // of it - remove it and decode back down into UTF-8. This is necessary - // because hjson doesn't know what to do with UTF-16 and will panic - if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) || - bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) { - utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) - decoder := utf.NewDecoder() - conf, err = decoder.Bytes(conf) - if err != nil { - panic(err) - } - } - // Generate a new configuration - this gives us a set of sane defaults - - // then parse the configuration we loaded above on top of it. The effect - // of this is that any configuration item that is missing from the provided - // configuration will use a sane default. - cfg := defaults.GenerateConfig() - var dat map[string]interface{} - if err := hjson.Unmarshal(conf, &dat); err != nil { - panic(err) - } - // Check if we have old field names - if _, ok := dat["TunnelRouting"]; ok { - log.Warnln("WARNING: Tunnel routing is no longer supported") - } - if old, ok := dat["SigningPrivateKey"]; ok { - log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"") - if _, ok := dat["PrivateKey"]; !ok { - if privstr, err := hex.DecodeString(old.(string)); err == nil { - priv := ed25519.PrivateKey(privstr) - pub := priv.Public().(ed25519.PublicKey) - dat["PrivateKey"] = hex.EncodeToString(priv[:]) - dat["PublicKey"] = hex.EncodeToString(pub[:]) - } else { - log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option contains an invalid value and will be ignored") - } - } - } - if oldmc, ok := dat["MulticastInterfaces"]; ok { - if oldmcvals, ok := oldmc.([]interface{}); ok { - var newmc []config.MulticastInterfaceConfig - for _, oldmcval := range oldmcvals { - if str, ok := oldmcval.(string); ok { - newmc = append(newmc, config.MulticastInterfaceConfig{ - Regex: str, - Beacon: true, - Listen: true, - }) - } - } - if newmc != nil { - if oldport, ok := dat["LinkLocalTCPPort"]; ok { - // numbers parse to float64 by default - if port, ok := oldport.(float64); ok { - for idx := range newmc { - newmc[idx].Port = uint16(port) - } - } - } - dat["MulticastInterfaces"] = newmc - } - } - } - // Sanitise the config - confJson, err := json.Marshal(dat) - if err != nil { - panic(err) - } - if err := json.Unmarshal(confJson, &cfg); err != nil { - panic(err) - } - // Overlay our newly mapped configuration onto the autoconf node config that - // we generated above. - if err = mapstructure.Decode(dat, &cfg); err != nil { - panic(err) - } - return cfg -} - -// Generates a new configuration and returns it in HJSON format. This is used -// with -genconf. -func doGenconf(isjson bool) string { - cfg := defaults.GenerateConfig() - var bs []byte - var err error - if isjson { - bs, err = json.MarshalIndent(cfg, "", " ") - } else { - bs, err = hjson.Marshal(cfg) - } - if err != nil { - panic(err) - } - return string(bs) -} - -func setLogLevel(loglevel string, logger *log.Logger) { - levels := [...]string{"error", "warn", "info", "debug", "trace"} - loglevel = strings.ToLower(loglevel) - - contains := func() bool { - for _, l := range levels { - if l == loglevel { - return true - } - } - return false - } - - if !contains() { // set default log level - logger.Infoln("Loglevel parse failed. Set default level(info)") - loglevel = "info" - } - - for _, l := range levels { - logger.EnableLevel(l) - if l == loglevel { - break - } - } -} - -type yggArgs struct { - genconf bool - useconf bool - normaliseconf bool - confjson bool - autoconf bool - ver bool - getaddr bool - getsnet bool - useconffile string - logto string - loglevel string -} - -func getArgs() yggArgs { - genconf := flag.Bool("genconf", false, "print a new config to stdout") - useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin") - useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path") - normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") - confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") - autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") - ver := flag.Bool("version", false, "prints the version of this build") - logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") - getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration") - getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration") - loglevel := flag.String("loglevel", "info", "loglevel to enable") - flag.Parse() - return yggArgs{ - genconf: *genconf, - useconf: *useconf, - useconffile: *useconffile, - normaliseconf: *normaliseconf, - confjson: *confjson, - autoconf: *autoconf, - ver: *ver, - logto: *logto, - getaddr: *getaddr, - getsnet: *getsnet, - loglevel: *loglevel, - } -} - // The main function is responsible for configuring and starting Yggdrasil. -func run(args yggArgs, ctx context.Context, done chan struct{}) { - defer close(done) +func main() { + args := setup.ParseArguments() + // Create a new logger that logs output to stdout. var logger *log.Logger - switch args.logto { + switch args.LogTo { case "stdout": logger = log.New(os.Stdout, "", log.Flags()) case "syslog": @@ -237,7 +38,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { logger = log.New(syslogger, "", log.Flags()) } default: - if logfd, err := os.OpenFile(args.logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { + if logfd, err := os.OpenFile(args.LogTo, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { logger = log.New(logfd, "", log.Flags()) } } @@ -246,33 +47,27 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { logger.Warnln("Logging defaulting to stdout") } - if args.normaliseconf { - setLogLevel("error", logger) - } else { - setLogLevel(args.loglevel, logger) - } - var cfg *config.NodeConfig var err error switch { - case args.ver: + case args.Version: fmt.Println("Build name:", version.BuildName()) fmt.Println("Build version:", version.BuildVersion()) return - case args.autoconf: + case args.AutoConf: // Use an autoconf-generated config, this will give us random keys and // port numbers, and will use an automatically selected TUN/TAP interface. - cfg = defaults.GenerateConfig() - case args.useconffile != "" || args.useconf: + cfg = config.GenerateConfig() + case args.UseConfFile != "" || args.UseConf: // Read the configuration from either stdin or from the filesystem - cfg = readConfig(logger, args.useconf, args.useconffile, args.normaliseconf) + cfg = setup.ReadConfig(logger, args.UseConf, args.UseConfFile, args.NormaliseConf) // If the -normaliseconf option was specified then remarshal the above // configuration and print it back to stdout. This lets the user update // their configuration file with newly mapped names (like above) or to // convert from plain JSON to commented HJSON. - if args.normaliseconf { + if args.NormaliseConf { var bs []byte - if args.confjson { + if args.ConfJSON { bs, err = json.MarshalIndent(cfg, "", " ") } else { bs, err = hjson.Marshal(cfg) @@ -283,9 +78,9 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { fmt.Println(string(bs)) return } - case args.genconf: + case args.GenConf: // Generate a new configuration and print it to stdout. - fmt.Println(doGenconf(args.confjson)) + fmt.Println(config.GenerateConfigJSON(args.ConfJSON)) return default: // No flags were provided, therefore print the list of flags to stdout. @@ -297,6 +92,11 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { if cfg == nil { return } + + // Create a new standalone node + n := setup.NewNode(cfg, logger) + n.SetLogLevel(args.LogLevel) + // Have we been asked for the node address yet? If so, print it and then stop. getNodeKey := func() ed25519.PublicKey { if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil { @@ -305,14 +105,14 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { return nil } switch { - case args.getaddr: + case args.GetAddr: if key := getNodeKey(); key != nil { addr := address.AddrForKey(key) ip := net.IP(addr[:]) fmt.Println(ip.String()) } return - case args.getsnet: + case args.GetSubnet: if key := getNodeKey(); key != nil { snet := address.SubnetForKey(key) ipnet := net.IPNet{ @@ -325,84 +125,39 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { default: } - // Setup the Yggdrasil node itself. The node{} type includes a Core, so we - // don't need to create this manually. - n := node{config: cfg} // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate - if err = n.core.Start(cfg, logger); err != nil { - logger.Errorln("An error occurred during startup") - panic(err) + if err = n.Run(args); err != nil { + logger.Fatalln(err) } - // Register the session firewall gatekeeper function - // Allocate our modules - n.admin = &admin.AdminSocket{} - n.multicast = &multicast.Multicast{} - n.tuntap = &tuntap.TunAdapter{} - // Start the admin socket - if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil { - logger.Errorln("An error occurred initialising admin socket:", err) - } else if err := n.admin.Start(); err != nil { - logger.Errorln("An error occurred starting admin socket:", err) - } - n.admin.SetupAdminHandlers(n.admin) - // Start the multicast interface - if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil { - logger.Errorln("An error occurred initialising multicast:", err) - } else if err := n.multicast.Start(); err != nil { - logger.Errorln("An error occurred starting multicast:", err) - } - n.multicast.SetupAdminHandlers(n.admin) + // Start the TUN/TAP interface - rwc := ipv6rwc.NewReadWriteCloser(&n.core) - if err := n.tuntap.Init(rwc, cfg, logger, nil); err != nil { + tuntap := &tuntap.TunAdapter{} + rwc := ipv6rwc.NewReadWriteCloser(&n.Core) + if err := tuntap.Init(rwc, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising TUN/TAP:", err) - } else if err := n.tuntap.Start(); err != nil { + } else if err := tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } - n.tuntap.SetupAdminHandlers(n.admin) + tuntap.SetupAdminHandlers(n.Admin()) + // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. - address := n.core.Address() - subnet := n.core.Subnet() - public := n.core.GetSelf().Key + address := n.Address() + subnet := n.Subnet() + public := n.GetSelf().Key logger.Infof("Your public key is %s", hex.EncodeToString(public[:])) logger.Infof("Your IPv6 address is %s", address.String()) logger.Infof("Your IPv6 subnet is %s", subnet.String()) - // Catch interrupts from the operating system to exit gracefully. - <-ctx.Done() - // Capture the service being stopped on Windows. - minwinsvc.SetOnExit(n.shutdown) - n.shutdown() -} -func (n *node) shutdown() { - _ = n.admin.Stop() - _ = n.multicast.Stop() - _ = n.tuntap.Stop() - n.core.Stop() -} - -func main() { - args := getArgs() - hup := make(chan os.Signal, 1) - //signal.Notify(hup, os.Interrupt, syscall.SIGHUP) term := make(chan os.Signal, 1) signal.Notify(term, os.Interrupt, syscall.SIGTERM) - for { - done := make(chan struct{}) - ctx, cancel := context.WithCancel(context.Background()) - go run(args, ctx, done) - select { - case <-hup: - cancel() - <-done - case <-term: - cancel() - <-done - return - case <-done: - return - } + + select { + case <-n.Done(): + case <-term: } + + n.Close() + _ = tuntap.Stop() } diff --git a/go.mod b/go.mod index 9c957553..9c0178b5 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/cheggaaa/pb/v3 v3.0.8 github.com/fatih/color v1.12.0 // indirect github.com/gologme/log v1.2.0 + github.com/google/btree v1.0.1 // indirect github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v3.1.0+incompatible github.com/kardianos/minwinsvc v1.0.0 @@ -21,6 +22,8 @@ require ( golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b + golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a golang.zx2c4.com/wireguard/windows v0.4.12 + inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c ) diff --git a/go.sum b/go.sum index 063b5998..e1162e78 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw= @@ -89,6 +91,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0= golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -104,3 +108,5 @@ golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wE golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA= golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w= +inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c h1:nr31qYr+91rWD8klUkPx3eGTZzumCC414UJG1QRKZTc= +inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c/go.mod h1:KOJdAzQzMLKzwFEdOOnrnSrLIhaFVB+NQoME/e5wllA= diff --git a/src/config/config.go b/src/config/config.go index 041147b8..02f9f0ee 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -19,7 +19,12 @@ package config import ( "crypto/ed25519" "encoding/hex" + "encoding/json" "sync" + + "github.com/hjson/hjson-go" + "github.com/mitchellh/mapstructure" + "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) // NodeConfig is the main configuration structure, containing configuration @@ -59,3 +64,106 @@ func (cfg *NodeConfig) NewKeys() { cfg.PublicKey = hex.EncodeToString(spub[:]) cfg.PrivateKey = hex.EncodeToString(spriv[:]) } + +// Generates default configuration and returns a pointer to the resulting +// NodeConfig. This is used when outputting the -genconf parameter and also when +// using -autoconf. +func GenerateConfig() *NodeConfig { + defaults := defaults.GetDefaults() + cfg := &NodeConfig{} + cfg.NewKeys() + cfg.Listen = []string{} + cfg.AdminListen = defaults.DefaultAdminListen + cfg.Peers = []string{} + cfg.InterfacePeers = map[string][]string{} + cfg.AllowedPublicKeys = []string{} + for _, regex := range defaults.DefaultMulticastInterfaces { + cfg.MulticastInterfaces = append(cfg.MulticastInterfaces, MulticastInterfaceConfig{ + Regex: regex, + Beacon: true, + Listen: true, + }) + } + cfg.IfName = defaults.DefaultIfName + cfg.IfMTU = defaults.DefaultIfMTU + cfg.NodeInfoPrivacy = false + return cfg +} + +func GenerateConfigJSON(isjson bool) []byte { + // Generates a new configuration and returns it in HJSON or JSON format. + cfg := GenerateConfig() + var bs []byte + var err error + if isjson { + bs, err = json.MarshalIndent(cfg, "", " ") + } else { + bs, err = hjson.Marshal(cfg) + } + if err != nil { + panic(err) + } + return bs +} + +func ReadConfig(conf []byte) *NodeConfig { + // 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() + var dat map[string]interface{} + if err := hjson.Unmarshal(conf, &dat); err != nil { + panic(err) + } + // Check if we have old field names + if old, ok := dat["SigningPrivateKey"]; ok { + if _, ok := dat["PrivateKey"]; !ok { + if privstr, err := hex.DecodeString(old.(string)); err == nil { + priv := ed25519.PrivateKey(privstr) + pub := priv.Public().(ed25519.PublicKey) + dat["PrivateKey"] = hex.EncodeToString(priv[:]) + dat["PublicKey"] = hex.EncodeToString(pub[:]) + } + } + } + if oldmc, ok := dat["MulticastInterfaces"]; ok { + if oldmcvals, ok := oldmc.([]interface{}); ok { + var newmc []MulticastInterfaceConfig + for _, oldmcval := range oldmcvals { + if str, ok := oldmcval.(string); ok { + newmc = append(newmc, MulticastInterfaceConfig{ + Regex: str, + Beacon: true, + Listen: true, + }) + } + } + if newmc != nil { + if oldport, ok := dat["LinkLocalTCPPort"]; ok { + // numbers parse to float64 by default + if port, ok := oldport.(float64); ok { + for idx := range newmc { + newmc[idx].Port = uint16(port) + } + } + } + dat["MulticastInterfaces"] = newmc + } + } + } + // Sanitise the config + confJson, err := json.Marshal(dat) + if err != nil { + panic(err) + } + if err := json.Unmarshal(confJson, &cfg); err != nil { + panic(err) + } + // Overlay our newly mapped configuration onto the autoconf node config that + // we generated above. + if err = mapstructure.Decode(dat, &cfg); err != nil { + panic(err) + } + return cfg +} diff --git a/src/core/core_test.go b/src/core/core_test.go index fcfe2e31..26b597c8 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -11,12 +11,11 @@ import ( "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) // GenerateConfig produces default configuration with suitable modifications for tests. func GenerateConfig() *config.NodeConfig { - cfg := defaults.GenerateConfig() + cfg := config.GenerateConfig() cfg.AdminListen = "none" cfg.Listen = []string{"tcp://127.0.0.1:0"} cfg.IfName = "none" diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index 7912fc76..905feeab 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -1,8 +1,11 @@ package defaults -import "github.com/yggdrasil-network/yggdrasil-go/src/config" - -type MulticastInterfaceConfig = config.MulticastInterfaceConfig +type MulticastInterfaceConfig struct { + Regex string + Beacon bool + Listen bool + Port uint16 +} // Defines which parameters are expected by default for configuration on a // specific platform. These values are populated in the relevant defaults_*.go @@ -15,30 +18,10 @@ type platformDefaultParameters struct { DefaultConfigFile string // Multicast interfaces - DefaultMulticastInterfaces []MulticastInterfaceConfig + DefaultMulticastInterfaces []string // TUN/TAP MaximumIfMTU uint64 DefaultIfMTU uint64 DefaultIfName string } - -// Generates default configuration and returns a pointer to the resulting -// NodeConfig. This is used when outputting the -genconf parameter and also when -// using -autoconf. -func GenerateConfig() *config.NodeConfig { - // Create a node configuration and populate it. - cfg := new(config.NodeConfig) - cfg.NewKeys() - cfg.Listen = []string{} - cfg.AdminListen = GetDefaults().DefaultAdminListen - cfg.Peers = []string{} - cfg.InterfacePeers = map[string][]string{} - cfg.AllowedPublicKeys = []string{} - cfg.MulticastInterfaces = GetDefaults().DefaultMulticastInterfaces - cfg.IfName = GetDefaults().DefaultIfName - cfg.IfMTU = GetDefaults().DefaultIfMTU - cfg.NodeInfoPrivacy = false - - return cfg -} diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 060ce814..57f5676b 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -14,10 +14,7 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: "en.*", Beacon: true, Listen: true}, - {Regex: "bridge.*", Beacon: true, Listen: true}, - }, + DefaultMulticastInterfaces: []string{"en.*", "bridge.*"}, // TUN/TAP MaximumIfMTU: 65535, diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index 84df48ad..8570f979 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -14,9 +14,7 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/usr/local/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Beacon: true, Listen: true}, - }, + DefaultMulticastInterfaces: []string{".*"}, // TUN/TAP MaximumIfMTU: 32767, diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index c7f5f119..7bac4bf9 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -14,9 +14,7 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Beacon: true, Listen: true}, - }, + DefaultMulticastInterfaces: []string{".*"}, // TUN/TAP MaximumIfMTU: 65535, diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index 0ec877ca..e4aaaf94 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -14,9 +14,7 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Beacon: true, Listen: true}, - }, + DefaultMulticastInterfaces: []string{".*"}, // TUN/TAP MaximumIfMTU: 16384, diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index 37637425..837bd763 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -14,9 +14,7 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "/etc/yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Beacon: true, Listen: true}, - }, + DefaultMulticastInterfaces: []string{".*"}, // TUN/TAP MaximumIfMTU: 65535, diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index c1ea9689..987059ba 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -14,9 +14,7 @@ func GetDefaults() platformDefaultParameters { DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf", // Multicast interfaces - DefaultMulticastInterfaces: []MulticastInterfaceConfig{ - {Regex: ".*", Beacon: true, Listen: true}, - }, + DefaultMulticastInterfaces: []string{".*"}, // TUN/TAP MaximumIfMTU: 65535, diff --git a/src/setup/setup.go b/src/setup/setup.go new file mode 100644 index 00000000..204e7293 --- /dev/null +++ b/src/setup/setup.go @@ -0,0 +1,223 @@ +package setup + +import ( + "bytes" + "context" + "crypto/ed25519" + "encoding/hex" + "flag" + "fmt" + "io/ioutil" + "net" + "os" + "strings" + + "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/admin" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/core" + "github.com/yggdrasil-network/yggdrasil-go/src/multicast" + "golang.org/x/text/encoding/unicode" +) + +type Node struct { + core.Core + ctx context.Context + cancel context.CancelFunc + logger *log.Logger + config *config.NodeConfig + multicast *multicast.Multicast + admin *admin.AdminSocket +} + +func NewNode(cfg *config.NodeConfig, logger *log.Logger) *Node { + ctx, cancel := context.WithCancel(context.Background()) + return &Node{ + ctx: ctx, + cancel: cancel, + logger: logger, + config: cfg, + multicast: &multicast.Multicast{}, + admin: &admin.AdminSocket{}, + } +} + +func (n *Node) Close() { + n.cancel() + _ = n.multicast.Stop() + _ = n.admin.Stop() + _ = n.Core.Close() +} + +func (n *Node) Done() <-chan struct{} { + return n.ctx.Done() +} + +func (n *Node) Admin() *admin.AdminSocket { + return n.admin +} + +// The main function is responsible for configuring and starting Yggdrasil. +func (n *Node) Run(args Arguments) error { + // Have we got a working configuration? If we don't then it probably means + // that neither -autoconf, -useconf or -useconffile were set above. Stop + // if we don't. + if n.config == nil { + return fmt.Errorf("no configuration supplied") + } + // Have we been asked for the node address yet? If so, print it and then stop. + getNodeKey := func() ed25519.PublicKey { + if pubkey, err := hex.DecodeString(n.config.PrivateKey); err == nil { + return ed25519.PrivateKey(pubkey).Public().(ed25519.PublicKey) + } + return nil + } + switch { + case args.GetAddr: + if key := getNodeKey(); key != nil { + addr := address.AddrForKey(key) + ip := net.IP(addr[:]) + fmt.Println(ip.String()) + } + return nil + case args.GetSubnet: + if key := getNodeKey(); key != nil { + snet := address.SubnetForKey(key) + ipnet := net.IPNet{ + IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0), + Mask: net.CIDRMask(len(snet)*8, 128), + } + fmt.Println(ipnet.String()) + } + return nil + default: + } + + // Now start Yggdrasil - this starts the DHT, router, switch and other core + // components needed for Yggdrasil to operate + if err := n.Core.Start(n.config, n.logger); err != nil { + return fmt.Errorf("n.core.Start: %w", err) + } + // Register the session firewall gatekeeper function + // Allocate our modules + + // Start the admin socket + n.admin = &admin.AdminSocket{} + if err := n.admin.Init(&n.Core, n.config, n.logger, nil); err != nil { + return fmt.Errorf("n.admin.Init: %w", err) + } else if err := n.admin.Start(); err != nil { + return fmt.Errorf("n.admin.Start: %w", err) + } + n.admin.SetupAdminHandlers(n.admin) + + // Start the multicast interface + if err := n.multicast.Init(&n.Core, n.config, n.logger, nil); err != nil { + return fmt.Errorf("n.multicast.Init: %w", err) + } else if err := n.multicast.Start(); err != nil { + return fmt.Errorf("n.admin.Start: %w", err) + } + n.multicast.SetupAdminHandlers(n.admin) + + return nil +} + +func (n *Node) SetLogLevel(loglevel string) { + levels := [...]string{"error", "warn", "info", "debug", "trace"} + loglevel = strings.ToLower(loglevel) + + contains := func() bool { + for _, l := range levels { + if l == loglevel { + return true + } + } + return false + } + + if !contains() { // set default log level + n.logger.Infoln("Loglevel parse failed. Set default level(info)") + loglevel = "info" + } + + for _, l := range levels { + n.logger.EnableLevel(l) + if l == loglevel { + break + } + } +} + +func ReadConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf bool) *config.NodeConfig { + // Use a configuration file. If -useconf, the configuration will be read + // from stdin. If -useconffile, the configuration will be read from the + // filesystem. + var conf []byte + var err error + if useconffile != "" { + // Read the file from the filesystem + conf, err = ioutil.ReadFile(useconffile) + } else { + // Read the file from stdin. + conf, err = ioutil.ReadAll(os.Stdin) + } + if err != nil { + panic(err) + } + // If there's a byte order mark - which Windows 10 is now incredibly fond of + // throwing everywhere when it's converting things into UTF-16 for the hell + // of it - remove it and decode back down into UTF-8. This is necessary + // because hjson doesn't know what to do with UTF-16 and will panic + if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) || + bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) { + utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) + decoder := utf.NewDecoder() + conf, err = decoder.Bytes(conf) + if err != nil { + panic(err) + } + } + return config.ReadConfig(conf) +} + +type Arguments struct { + GenConf bool + UseConf bool + NormaliseConf bool + ConfJSON bool + AutoConf bool + Version bool + GetAddr bool + GetSubnet bool + UseConfFile string + LogTo string + LogLevel string +} + +func ParseArguments() Arguments { + genconf := flag.Bool("genconf", false, "print a new config to stdout") + useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin") + useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path") + normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") + confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON") + autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") + ver := flag.Bool("version", false, "prints the version of this build") + logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") + getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration") + getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration") + loglevel := flag.String("loglevel", "info", "loglevel to enable") + flag.Parse() + return Arguments{ + GenConf: *genconf, + UseConf: *useconf, + UseConfFile: *useconffile, + NormaliseConf: *normaliseconf, + ConfJSON: *confjson, + AutoConf: *autoconf, + Version: *ver, + LogTo: *logto, + GetAddr: *getaddr, + GetSubnet: *getsnet, + LogLevel: *loglevel, + } +}