Document yggdrasil.go

This commit is contained in:
Neil Alexander 2018-05-24 12:40:02 +01:00
parent f40525693e
commit b019297458
No known key found for this signature in database
GPG key ID: A02A2019A2BB0944

View file

@ -31,10 +31,20 @@ type node struct {
core Core 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 { func generateConfig(isAutoconf bool) *nodeConfig {
// Create a new core.
core := Core{} core := Core{}
// Generate encryption keys.
bpub, bpriv := core.NewEncryptionKeys() bpub, bpriv := core.NewEncryptionKeys()
spub, spriv := core.NewSigningKeys() spub, spriv := core.NewSigningKeys()
// Create a node configuration and populate it.
cfg := nodeConfig{} cfg := nodeConfig{}
if isAutoconf { if isAutoconf {
cfg.Listen = "[::]:0" cfg.Listen = "[::]:0"
@ -57,6 +67,8 @@ func generateConfig(isAutoconf bool) *nodeConfig {
return &cfg return &cfg
} }
// Generates a new configuration and returns it in HJSON format. This is used
// with -genconf.
func doGenconf() string { func doGenconf() string {
cfg := generateConfig(false) cfg := generateConfig(false)
bs, err := hjson.Marshal(cfg) bs, err := hjson.Marshal(cfg)
@ -66,37 +78,51 @@ func doGenconf() string {
return string(bs) return string(bs)
} }
var pprof = flag.Bool("pprof", false, "Run pprof, see http://localhost:6060/debug/pprof/") // The main function is responsible for configuring and starting Yggdrasil.
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)")
func main() { 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() flag.Parse()
var cfg *nodeConfig var cfg *nodeConfig
switch { switch {
case *autoconf: case *autoconf:
// Use an autoconf-generated config, this will give us random keys and
// port numbers, and will use an automatically selected TUN/TAP interface.
cfg = generateConfig(true) cfg = generateConfig(true)
case *useconffile != "" || *useconf: 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 config []byte
var err error var err error
if *useconffile != "" { if *useconffile != "" {
// Read the file from the filesystem
config, err = ioutil.ReadFile(*useconffile) config, err = ioutil.ReadFile(*useconffile)
} else { } else {
// Read the file from stdin.
config, err = ioutil.ReadAll(os.Stdin) config, err = ioutil.ReadAll(os.Stdin)
} }
if err != nil { if err != nil {
panic(err) 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) cfg = generateConfig(false)
var dat map[string]interface{} var dat map[string]interface{}
if err := hjson.Unmarshal(config, &dat); err != nil { if err := hjson.Unmarshal(config, &dat); err != nil {
panic(err) panic(err)
} }
// For now we will do a little bit to help the user adjust their // 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{ changes := map[string]string{
"Multicast": "", "Multicast": "",
"LinkLocal": "MulticastInterfaces", "LinkLocal": "MulticastInterfaces",
@ -106,6 +132,7 @@ func main() {
"SigPriv": "SigningPrivateKey", "SigPriv": "SigningPrivateKey",
"AllowedBoxPubs": "AllowedEncryptionPublicKeys", "AllowedBoxPubs": "AllowedEncryptionPublicKeys",
} }
// Loop over the mappings aove and see if we have anything to fix.
for from, to := range changes { for from, to := range changes {
if _, ok := dat[from]; ok { if _, ok := dat[from]; ok {
if to == "" { if to == "" {
@ -116,15 +143,24 @@ func main() {
if !*normaliseconf { if !*normaliseconf {
log.Println("Warning: Deprecated config option", from, "- please rename to", to) 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 { if _, ok := dat[to]; !ok {
dat[to] = dat[from] 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 { if err = mapstructure.Decode(dat, &cfg); err != nil {
panic(err) 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 { if *normaliseconf {
bs, err := hjson.Marshal(cfg) bs, err := hjson.Marshal(cfg)
if err != nil { if err != nil {
@ -134,23 +170,35 @@ func main() {
return return
} }
case *genconf: case *genconf:
// Generate a new configuration and print it to stdout.
fmt.Println(doGenconf()) fmt.Println(doGenconf())
default: default:
// No flags were provided, therefore print the list of flags to stdout.
flag.PrintDefaults() 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 { if cfg == nil {
return return
} }
// Create a new logger that logs output to stdout.
logger := log.New(os.Stdout, "", log.Flags()) logger := log.New(os.Stdout, "", log.Flags())
// If the -pprof flag was provided then start the pprof service on port 6060.
if *pprof { if *pprof {
runtime.SetBlockProfileRate(1) runtime.SetBlockProfileRate(1)
go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() 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{} 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 { for _, pBoxStr := range cfg.AllowedEncryptionPublicKeys {
n.core.AddAllowedEncryptionPublicKey(pBoxStr) 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 { for _, ll := range cfg.MulticastInterfaces {
ifceExpr, err := regexp.Compile(ll) ifceExpr, err := regexp.Compile(ll)
if err != nil { if err != nil {
@ -158,10 +206,17 @@ func main() {
} }
n.core.AddMulticastInterfaceExpr(ifceExpr) 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 { if err := n.core.Start(cfg, logger); err != nil {
logger.Println("An error occured during startup") logger.Println("An error occured during startup")
panic(err) 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() { go func() {
if len(cfg.Peers) == 0 { if len(cfg.Peers) == 0 {
return return
@ -174,22 +229,27 @@ func main() {
time.Sleep(time.Minute) time.Sleep(time.Minute)
} }
}() }()
// The Stop function ensures that the TUN/TAP adapter is correctly shut down
// before the program exits.
defer func() { defer func() {
n.core.Stop() 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())[:] address := (*n.core.GetAddress())[:]
subnet := (*n.core.GetSubnet())[:] subnet := (*n.core.GetSubnet())[:]
subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) 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 address is %s", net.IP(address).String())
logger.Printf("Your IPv6 subnet is %s/64", net.IP(subnet).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) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) 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() { winTerminate := func() {
c <- os.Interrupt c <- os.Interrupt
} }
minwinsvc.SetOnExit(winTerminate) 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 <-c
} }