package main import ( "context" "crypto/ed25519" "encoding/hex" "encoding/json" "flag" "fmt" "net" "os" "os/signal" "regexp" "strings" "syscall" "github.com/gologme/log" gsyslog "github.com/hashicorp/go-syslog" "github.com/hjson/hjson-go/v4" "github.com/kardianos/minwinsvc" "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/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tun" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) type node struct { core *core.Core tun *tun.TunAdapter multicast *multicast.Multicast admin *admin.AdminSocket } // The main function is responsible for configuring and starting Yggdrasil. func main() { 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") exportkey := flag.Bool("exportkey", false, "use in combination with either -useconf or -useconffile, outputs your private key in PEM format") exportcsr := flag.Bool("exportcsr", false, "use in combination with either -useconf or -useconffile, outputs your self-signed certificate request in PEM format") exportcert := flag.Bool("exportcert", false, "use in combination with either -useconf or -useconffile, outputs your self-signed certificate in PEM format") 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() // Catch interrupts from the operating system to exit gracefully. ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) // Capture the service being stopped on Windows. minwinsvc.SetOnExit(cancel) // Create a new logger that logs output to stdout. var logger *log.Logger switch *logto { case "stdout": logger = log.New(os.Stdout, "", log.Flags()) case "syslog": if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil { logger = log.New(syslogger, "", log.Flags()) } default: if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { logger = log.New(logfd, "", log.Flags()) } } if logger == nil { logger = log.New(os.Stdout, "", log.Flags()) logger.Warnln("Logging defaulting to stdout") } if *normaliseconf { setLogLevel("error", logger) } else { setLogLevel(*loglevel, logger) } cfg := config.GenerateConfig() var err error switch { case *ver: fmt.Println("Build name:", version.BuildName()) fmt.Println("Build version:", version.BuildVersion()) return case *autoconf: // Use an autoconf-generated config, this will give us random keys and // port numbers, and will use an automatically selected TUN interface. case *useconf: if _, err := cfg.ReadFrom(os.Stdin); err != nil { panic(err) } case *useconffile != "": f, err := os.Open(*useconffile) if err != nil { panic(err) } if _, err := cfg.ReadFrom(f); err != nil { panic(err) } _ = f.Close() case *genconf: var bs []byte if *confjson { bs, err = json.MarshalIndent(cfg, "", " ") } else { bs, err = hjson.Marshal(cfg) } if err != nil { panic(err) } fmt.Println(string(bs)) return default: fmt.Println("Usage:") flag.PrintDefaults() if *getaddr || *getsnet { fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.") } } privateKey := ed25519.PrivateKey(cfg.PrivateKey) publicKey := privateKey.Public().(ed25519.PublicKey) switch { case *getaddr: addr := address.AddrForKey(publicKey) ip := net.IP(addr[:]) fmt.Println(ip.String()) return case *getsnet: snet := address.SubnetForKey(publicKey) 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 case *normaliseconf: var bs []byte if *confjson { bs, err = json.MarshalIndent(cfg, "", " ") } else { bs, err = hjson.Marshal(cfg) } if err != nil { panic(err) } fmt.Println(string(bs)) return case *exportkey: pem, err := cfg.MarshalPEMPrivateKey() if err != nil { panic(err) } fmt.Println(string(pem)) return case *exportcsr: pem, err := cfg.GenerateCertificateSigningRequest() if err != nil { panic(err) } fmt.Println(string(pem)) return case *exportcert: pem, err := cfg.MarshalPEMCertificate() if err != nil { panic(err) } fmt.Println(string(pem)) return } n := &node{} // Setup the Yggdrasil node itself. { options := []core.SetupOption{ core.NodeInfo(cfg.NodeInfo), core.NodeInfoPrivacy(cfg.NodeInfoPrivacy), } for _, addr := range cfg.Listen { options = append(options, core.ListenAddress(addr)) } for _, peer := range cfg.Peers { options = append(options, core.Peer{URI: peer}) } for intf, peers := range cfg.InterfacePeers { for _, peer := range peers { options = append(options, core.Peer{URI: peer, SourceInterface: intf}) } } for _, root := range cfg.RootCertificates { options = append(options, core.RootCertificate(*root)) } for _, allowed := range cfg.AllowedPublicKeys { k, err := hex.DecodeString(allowed) if err != nil { panic(err) } options = append(options, core.AllowedPublicKey(k[:])) } if n.core, err = core.New(cfg.Certificate, logger, options...); err != nil { panic(err) } } // Setup the admin socket. { options := []admin.SetupOption{ admin.ListenAddress(cfg.AdminListen), } if n.admin, err = admin.New(n.core, logger, options...); err != nil { panic(err) } if n.admin != nil { n.admin.SetupAdminHandlers() } } // Setup the multicast module. { options := []multicast.SetupOption{} for _, intf := range cfg.MulticastInterfaces { options = append(options, multicast.MulticastInterface{ Regex: regexp.MustCompile(intf.Regex), Beacon: intf.Beacon, Listen: intf.Listen, Port: intf.Port, Priority: uint8(intf.Priority), }) } if n.multicast, err = multicast.New(n.core, logger, options...); err != nil { panic(err) } if n.admin != nil && n.multicast != nil { n.multicast.SetupAdminHandlers(n.admin) } } // Setup the TUN module. { options := []tun.SetupOption{ tun.InterfaceName(cfg.IfName), tun.InterfaceMTU(cfg.IfMTU), } if n.tun, err = tun.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil { panic(err) } if n.admin != nil && n.tun != nil { n.tun.SetupAdminHandlers(n.admin) } } // Block until we are told to shut down. <-ctx.Done() // Shut down the node. _ = n.admin.Stop() _ = n.multicast.Stop() _ = n.tun.Stop() n.core.Stop() } 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 } } }