mirror of
				https://github.com/yggdrasil-network/yggdrasil-go.git
				synced 2025-11-04 11:15:07 +03:00 
			
		
		
		
	sys/kern/kern_pledge.c r1.329[0] removed the unveil bypass for "dns",
so "rpath" is needed for Go's DNS to stat(2) it.
Since current "/ rwc" and "cpath" with the new "rpath" amount to full
read access, there is no point in unveiling anymore.
0:
8d49ad01ac
		
	
			
		
			
				
	
	
		
			350 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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() {
 | 
						|
	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)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Promise final modes of operation.  At this point, if at all:
 | 
						|
	// - raw socket is created/open
 | 
						|
	// - admin socket is created/open
 | 
						|
	// - privileges are dropped to non-root user
 | 
						|
	//
 | 
						|
	// Peers, InterfacePeers, Listen can be UNIX sockets;
 | 
						|
	// Go's net.Listen.Close() deletes files on shutdown.
 | 
						|
	promises := []string{"stdio", "rpath", "cpath", "inet", "unix", "dns"}
 | 
						|
	if len(cfg.MulticastInterfaces) > 0 {
 | 
						|
		promises = append(promises, "mcast")
 | 
						|
	}
 | 
						|
	if err := protect.Pledge(strings.Join(promises, " ")); err != nil {
 | 
						|
		panic(fmt.Sprintf("pledge: %v: %v", promises, 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
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |