diff --git a/yggdrasil.go b/yggdrasil.go index 87f7627d..385878a5 100644 --- a/yggdrasil.go +++ b/yggdrasil.go @@ -31,10 +31,20 @@ type node struct { core Core } +// Generates default configuration. This is used when outputting the -genconf +// parameter and also when using -autoconf. The isAutoconf flag is used to +// determine whether the operating system should select a free port by itself +// (which guarantees that there will not be a conflict with any other services) +// or whether to generate a random port number. The only side effect of setting +// isAutoconf is that the TCP and UDP ports will likely end up with different +// port numbers. func generateConfig(isAutoconf bool) *nodeConfig { + // Create a new core. core := Core{} + // Generate encryption keys. bpub, bpriv := core.NewEncryptionKeys() spub, spriv := core.NewSigningKeys() + // Create a node configuration and populate it. cfg := nodeConfig{} if isAutoconf { cfg.Listen = "[::]:0" @@ -57,6 +67,8 @@ func generateConfig(isAutoconf bool) *nodeConfig { return &cfg } +// Generates a new configuration and returns it in HJSON format. This is used +// with -genconf. func doGenconf() string { cfg := generateConfig(false) bs, err := hjson.Marshal(cfg) @@ -66,37 +78,51 @@ func doGenconf() string { return string(bs) } -var pprof = flag.Bool("pprof", false, "Run pprof, see http://localhost:6060/debug/pprof/") -var genconf = flag.Bool("genconf", false, "print a new config to stdout") -var useconf = flag.Bool("useconf", false, "read config from stdin") -var useconffile = flag.String("useconffile", "", "read config from specified file path") -var normaliseconf = flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") -var autoconf = flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") - +// The main function is responsible for configuring and starting Yggdrasil. func main() { + // Configure the command line parameters. + pprof := flag.Bool("pprof", false, "Run pprof, see http://localhost:6060/debug/pprof/") + genconf := flag.Bool("genconf", false, "print a new config to stdout") + useconf := flag.Bool("useconf", false, "read config from stdin") + useconffile := flag.String("useconffile", "", "read config from specified file path") + normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised") + autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") flag.Parse() + var cfg *nodeConfig switch { case *autoconf: + // Use an autoconf-generated config, this will give us random keys and + // port numbers, and will use an automatically selected TUN/TAP interface. cfg = generateConfig(true) case *useconffile != "" || *useconf: + // Use a configuration file. If -useconf, the configuration will be read + // from stdin. If -useconffile, the configuration will be read from the + // filesystem. var config []byte var err error if *useconffile != "" { + // Read the file from the filesystem config, err = ioutil.ReadFile(*useconffile) } else { + // Read the file from stdin. config, err = ioutil.ReadAll(os.Stdin) } if err != nil { panic(err) } + // Generate a new configuration - this gives us a set of sane defaults - + // then parse the configuration we loaded above on top of it. The effect + // of this is that any configuration item that is missing from the provided + // configuration will use a sane default. cfg = generateConfig(false) var dat map[string]interface{} if err := hjson.Unmarshal(config, &dat); err != nil { panic(err) } // For now we will do a little bit to help the user adjust their - // configuration to match the new configuration format + // configuration to match the new configuration format, as some of the key + // names have changed recently. changes := map[string]string{ "Multicast": "", "LinkLocal": "MulticastInterfaces", @@ -106,6 +132,7 @@ func main() { "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 == "" { @@ -116,15 +143,24 @@ func main() { 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] } } } } + // Overlay our newly mapped configuration onto the autoconf node config that + // we generated above. if err = mapstructure.Decode(dat, &cfg); err != nil { panic(err) } + // 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 *normaliseconf { bs, err := hjson.Marshal(cfg) if err != nil { @@ -134,23 +170,35 @@ func main() { return } case *genconf: + // Generate a new configuration and print it to stdout. fmt.Println(doGenconf()) default: + // No flags were provided, therefore print the list of flags to stdout. flag.PrintDefaults() } + // 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 cfg == nil { return } + // Create a new logger that logs output to stdout. logger := log.New(os.Stdout, "", log.Flags()) + // If the -pprof flag was provided then start the pprof service on port 6060. if *pprof { runtime.SetBlockProfileRate(1) go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() } - // Setup + // 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 allowed encryption keys were provided in the config. + // If they were then set them now. for _, pBoxStr := range cfg.AllowedEncryptionPublicKeys { n.core.AddAllowedEncryptionPublicKey(pBoxStr) } + // 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 { @@ -158,10 +206,17 @@ func main() { } n.core.AddMulticastInterfaceExpr(ifceExpr) } + // Now that we have a working configuration, and we have provided any allowed + // encryption keys or multicast interface expressions, 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 occured during startup") panic(err) } + // 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 { return @@ -174,22 +229,27 @@ func main() { time.Sleep(time.Minute) } }() + // The Stop function ensures that the TUN/TAP adapter is correctly shut down + // before the program exits. defer func() { n.core.Stop() }() + // Make some nice output that tells us what our IPv6 address and subnet are. + // This is just logged to stdout for the user. address := (*n.core.GetAddress())[:] subnet := (*n.core.GetSubnet())[:] subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) logger.Printf("Your IPv6 address is %s", net.IP(address).String()) logger.Printf("Your IPv6 subnet is %s/64", net.IP(subnet).String()) - // Catch interrupt to exit gracefully + // Catch interrupts from the operating system to exit gracefully. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) - // Create a function to capture the service being stopped on Windows + // Create a function to capture the service being stopped on Windows. winTerminate := func() { c <- os.Interrupt } minwinsvc.SetOnExit(winTerminate) - // Wait for the terminate/interrupt signal + // 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 }