package main import ( "context" "crypto/ed25519" "encoding/hex" "encoding/json" "flag" "fmt" "net" "os" "os/signal" "regexp" "strings" "syscall" "suah.dev/protect" "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() { // Not all operations are coverable with pledge(2), so immediately // limit file system access with unveil(2), effectively preventing // "proc exec" promises right from the start: // // - read arbitrary config file // - create/write arbitrary log file // - read/write/chmod/remove admin socket, if at all if err := protect.Unveil("/", "rwc"); err != nil { panic(fmt.Sprintf("unveil: / rwc: %v", err)) } if err := protect.UnveilBlock(); err != nil { panic(fmt.Sprintf("unveil: %v", err)) } 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") 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, "use in combination with either -useconf or -useconffile, outputs your IPv6 address") getsnet := flag.Bool("subnet", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 subnet") getpkey := flag.Bool("publickey", false, "use in combination with either -useconf or -useconffile, outputs your public key") loglevel := flag.String("loglevel", "info", "loglevel to enable") chuserto := flag.String("user", "", "user (and, optionally, group) to set UID/GID to") flag.Parse() done := make(chan struct{}) defer close(done) // Catch interrupts from the operating system to exit gracefully. ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) // 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()&^(log.Ldate|log.Ltime)) } 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: cfg.AdminListen = "" 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.") } return } 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 *getpkey: fmt.Println(hex.EncodeToString(publicKey)) return case *normaliseconf: cfg.AdminListen = "" if cfg.PrivateKeyPath != "" { cfg.PrivateKey = nil } 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 } n := &node{} // Set up the Yggdrasil node itself. { iprange := net.IPNet{ IP: net.ParseIP("200::"), Mask: net.CIDRMask(7, 128), } options := []core.SetupOption{ core.NodeInfo(cfg.NodeInfo), core.NodeInfoPrivacy(cfg.NodeInfoPrivacy), core.PeerFilter(func(ip net.IP) bool { return !iprange.Contains(ip) }), } 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 _, 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) } address, subnet := n.core.Address(), n.core.Subnet() logger.Printf("Your public key is %s", hex.EncodeToString(n.core.PublicKey())) logger.Printf("Your IPv6 address is %s", address.String()) logger.Printf("Your IPv6 subnet is %s", subnet.String()) } // Set up the admin socket. { options := []admin.SetupOption{ admin.ListenAddress(cfg.AdminListen), } if cfg.LogLookups { options = append(options, admin.LogLookups{}) } if n.admin, err = admin.New(n.core, logger, options...); err != nil { panic(err) } if n.admin != nil { n.admin.SetupAdminHandlers() } } // Set up 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), Password: intf.Password, }) } 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) } } // Set up 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) } } //Windows service shutdown minwinsvc.SetOnExit(func() { logger.Infof("Shutting down service ...") cancel() // Wait for all parts to shutdown properly <-done }) // Change user if requested if *chuserto != "" { err = chuser(*chuserto) if err != nil { panic(err) } } // 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 } } }