diff --git a/build b/build index c7214438..b3ca1deb 100755 --- a/build +++ b/build @@ -28,7 +28,7 @@ if [ -z $TABLES ] && [ -z $DEBUG ]; then LDFLAGS="$LDFLAGS -s -w" fi -for CMD in yggdrasil yggdrasilctl ; do +for CMD in yggdrasil yggdrasilctl yggmdns ; do echo "Building: $CMD" go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 58b8230d..6f771b5c 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -32,6 +32,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" + "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/version" ) @@ -194,6 +195,7 @@ type yggArgs struct { useconffile string logto string loglevel string + hostname string } func getArgs() yggArgs { @@ -208,6 +210,12 @@ func getArgs() yggArgs { 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") + + h, err := os.Hostname() + if err != nil { + h = "" + } + hostname := flag.String("hostname", h, "hostname (used for mDNS)") flag.Parse() return yggArgs{ genconf: *genconf, @@ -221,6 +229,7 @@ func getArgs() yggArgs { getaddr: *getaddr, getsnet: *getsnet, loglevel: *loglevel, + hostname: *hostname, } } @@ -325,6 +334,14 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { default: } + if cfg.MixinHostname { + fmt.Println("Mixing hostname into private key") + sigPriv, _ := hex.DecodeString(cfg.PrivateKey) + newPriv := util.MixinHostname(sigPriv, args.hostname) + cfg.PrivateKey = hex.EncodeToString(newPriv) + cfg.PublicKey = hex.EncodeToString(newPriv.Public().(ed25519.PublicKey)) + } + // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. n := node{config: cfg} @@ -369,6 +386,7 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { logger.Infof("Your public key is %s", hex.EncodeToString(public[:])) logger.Infof("Your IPv6 address is %s", address.String()) logger.Infof("Your IPv6 subnet is %s", subnet.String()) + logger.Infof("Your hostname is %s", args.hostname) // Catch interrupts from the operating system to exit gracefully. <-ctx.Done() // Capture the service being stopped on Windows. diff --git a/cmd/yggmdns/main.go b/cmd/yggmdns/main.go new file mode 100644 index 00000000..b5e7cf6c --- /dev/null +++ b/cmd/yggmdns/main.go @@ -0,0 +1,258 @@ +package main + +import ( + "crypto/ed25519" + "encoding/base32" + "encoding/hex" + "flag" + "fmt" + "github.com/hjson/hjson-go" + "github.com/kardianos/minwinsvc" + "github.com/libp2p/go-reuseport" + "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/util" + "golang.org/x/net/dns/dnsmessage" + "golang.org/x/net/ipv6" + "io/ioutil" + "log" + "net" + "os" + "strings" +) + +type args struct { + useconffile string + port int + address string + hostnamesuffix string + keysuffix string + logto string + iface string +} + +func getArgs() args { + useconffile := flag.String("useconffile", "conf", "config file to read the private key from") + port := flag.Int("port", 5353, "port to listen on (UDP)") + address := flag.String("address", "ff02::fb", "the address to bind to") + hostnamesuffix := flag.String("hostnamesuffix", "-ygg.local.", "the hostnamesuffix to answer for - make sure it ends with a dot, e.g.: \"-ygg.local.\"") + keysuffix := flag.String("keysuffix", "-yggk.local.", "the keysuffix to answer for - make sure it ends with a dot, e.g.: \"-yggk.local.\"") + iface := flag.String("interface", "lo", "the interface to bind to") + logto := flag.String("logto", "stdout", "where to log") + + flag.Parse() + return args{ + useconffile: *useconffile, + port: *port, + address: *address, + hostnamesuffix: *hostnamesuffix, + keysuffix: *keysuffix, + iface: *iface, + logto: *logto, + } +} + +var privateKey []byte +var hostnamesuffix string +var keysuffix string + +func processHostnameQuery(q dnsmessage.Question, msg dnsmessage.Message) ([]byte, error) { + trimmed := strings.TrimSuffix(q.Name.String(), hostnamesuffix) + log.Println("Network be asking for:", q.Name.String(), "Trimmed:", trimmed, "Suffix: ", hostnamesuffix) + mixedPriv := util.MixinHostname(ed25519.PrivateKey(privateKey), trimmed) + resolved := address.AddrForKey(mixedPriv.Public().(ed25519.PublicKey)) + + rsp := dnsmessage.Message{ + Header: dnsmessage.Header{ID: msg.Header.ID, Response: true, Authoritative: true}, + Questions: []dnsmessage.Question{}, + Answers: []dnsmessage.Resource{ + { + Header: dnsmessage.ResourceHeader{ + Name: q.Name, + Type: dnsmessage.TypeAAAA, + Class: dnsmessage.ClassINET, + TTL: 10, + }, + Body: &dnsmessage.AAAAResource{AAAA: *resolved}, + }, + }, + } + + rspbuf, err := rsp.Pack() + if err != nil { + log.Println("Error packing: ", err) + return nil, err + } + + return rspbuf, nil +} + +func processKeyQuery(q dnsmessage.Question, msg dnsmessage.Message) ([]byte, error) { + trimmed := strings.TrimSuffix(q.Name.String(), keysuffix) + log.Println("Network be asking for:", q.Name.String(), "Trimmed:", trimmed, "Suffix: ", keysuffix) + + key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(trimmed) + if err != nil { + log.Println("Error decoding key:", err) + return nil, err + } + + resolved := address.AddrForKey(key) + + rsp := dnsmessage.Message{ + Header: dnsmessage.Header{ID: msg.Header.ID, Response: true, Authoritative: true}, + Questions: []dnsmessage.Question{}, + Answers: []dnsmessage.Resource{ + { + Header: dnsmessage.ResourceHeader{ + Name: q.Name, + Type: dnsmessage.TypeAAAA, + Class: dnsmessage.ClassINET, + TTL: 10, + }, + Body: &dnsmessage.AAAAResource{AAAA: *resolved}, + }, + }, + } + + rspbuf, err := rsp.Pack() + if err != nil { + log.Println("Error packing: ", err) + return nil, err + } + + return rspbuf, nil +} + +func processQuery(msg dnsmessage.Message, remote *net.UDPAddr, srvaddr string) ([]byte, error) { + for _, q := range msg.Questions { + if q.Type != dnsmessage.TypeAAAA { + continue + } + + var rsp []byte = nil + var err error = nil + + if strings.HasSuffix(q.Name.String(), hostnamesuffix) { + rsp, err = processHostnameQuery(q, msg) + if err != nil { + log.Println("Error processing hostname query:", err) + return nil, err + } + return rsp, nil + } + + if strings.HasSuffix(q.Name.String(), keysuffix) { + rsp, err = processKeyQuery(q, msg) + if err != nil { + log.Println("Error processing key query:", err) + return nil, err + } + return rsp, nil + } + } + return nil, fmt.Errorf("No question in query") +} + +func main() { + args := getArgs() + + if args.logto == "stdout" { + log.SetOutput(os.Stdout) + } else { + f, err := os.OpenFile(args.logto, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + fmt.Println("Failed to open log file:", err) + return + } + log.SetOutput(f) + } + + minwinsvc.SetOnExit(func() { + os.Exit(0) + }) + + conf, err := ioutil.ReadFile(args.useconffile) + if err != nil { + log.Println("Failed to read config:", err) + return + } + + var cfg map[string]interface{} + err = hjson.Unmarshal(conf, &cfg) + if err != nil { + log.Println("Failed to decode config:", err) + return + } + + sigPriv, _ := hex.DecodeString(cfg["PrivateKey"].(string)) + privateKey = sigPriv + hostnamesuffix = args.hostnamesuffix + keysuffix = args.keysuffix + + c, err := reuseport.ListenPacket("udp6", "[::]:5353") // mDNS over UDP + if err != nil { + log.Fatal(err) + } + defer c.Close() + p := ipv6.NewPacketConn(c) + + err = p.SetMulticastHopLimit(255) + if err != nil { + log.Println("Failed to set HOP LIMIT: ", err) + } + + err = p.SetMulticastLoopback(true) + if err != nil { + log.Println("Failed to turn on MulticastLoopback: ", err) + } + + en0, err := net.InterfaceByName(args.iface) + if err != nil { + log.Println("Failed to look up interface ", err) + return + } + + mDNSLinkLocal := net.UDPAddr{IP: net.ParseIP(args.address)} + + if err := p.JoinGroup(en0, &mDNSLinkLocal); err != nil { + log.Println("Failed to join multicast group:", err) + return + } + + defer p.LeaveGroup(en0, &mDNSLinkLocal) + + if err := p.SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true); err != nil { + log.Println("Failed to set control message:", err) + } + + log.Println("Listening...") + + var wcm ipv6.ControlMessage + b := make([]byte, 1500) + for { + n, _, remote, err := p.ReadFrom(b) + if err != nil { + log.Println("Read failed:", err) + } + + var dnsmsg dnsmessage.Message + err = dnsmsg.Unpack(b[:n]) + if err != nil { + log.Println("Error decoding:", err) + continue + } + + if len(dnsmsg.Questions) > 0 { + rsp, err := processQuery(dnsmsg, remote.(*net.UDPAddr), args.address) + if err != nil { + log.Println("Failed to process query:", err) + continue + } + + if _, err := p.WriteTo(rsp, &wcm, remote); err != nil { + log.Println("Failed to write response:", err) + continue + } + } + } +} diff --git a/contrib/msi/build-msi.sh b/contrib/msi/build-msi.sh index 888075c8..121f1c37 100644 --- a/contrib/msi/build-msi.sh +++ b/contrib/msi/build-msi.sh @@ -171,6 +171,39 @@ cat > wix.xml << EOF Remove="uninstall" /> + + + + + + + + + + wix.xml << EOF + diff --git a/contrib/systemd/yggmdns.service b/contrib/systemd/yggmdns.service new file mode 100644 index 00000000..9026e898 --- /dev/null +++ b/contrib/systemd/yggmdns.service @@ -0,0 +1,16 @@ +[Unit] +Description=yggdrasil +Wants=yggdrasil.service +After=yggdrasil.service + +[Service] +Group=yggdrasil +ProtectHome=true +ProtectSystem=true +SyslogIdentifier=yggmdns +ExecStart=/usr/bin/yggmdns -useconffile /etc/yggdrasil.conf +Restart=always +TimeoutStopSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/go.mod b/go.mod index 9c957553..8b104956 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v3.1.0+incompatible github.com/kardianos/minwinsvc v1.0.0 + github.com/libp2p/go-reuseport v0.1.0 github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/mapstructure v1.4.1 diff --git a/go.sum b/go.sum index 063b5998..3bed9405 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,7 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA= github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -19,6 +20,8 @@ github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8t github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v1.0.0 h1:+JfAi8IBJna0jY2dJGZqi7o15z13JelFIklJCAENALA= github.com/kardianos/minwinsvc v1.0.0/go.mod h1:Bgd0oc+D0Qo3bBytmNtyRKVlp85dAloLKhfxanPFFRc= +github.com/libp2p/go-reuseport v0.1.0 h1:0ooKOx2iwyIkf339WCZ2HN3ujTDbkK0PjC7JVoP1AiM= +github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= @@ -31,9 +34,12 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= @@ -67,6 +73,7 @@ golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -104,3 +111,5 @@ golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wE golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU= golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA= golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/config/config.go b/src/config/config.go index 041147b8..0184a763 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -35,6 +35,7 @@ type NodeConfig struct { AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."` PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"` + MixinHostname bool `comment:"Whether to mixin the hostname into the private key (used for mDNS lookup)"` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` diff --git a/src/core/proto.go b/src/core/proto.go index 3045972e..5af59cc8 100644 --- a/src/core/proto.go +++ b/src/core/proto.go @@ -47,9 +47,9 @@ func (p *protoHandler) init(core *Core) { p.core = core p.nodeinfo.init(p) - p.selfRequests = make(map[keyArray]*reqInfo) + p.selfRequests = make(map[keyArray]*reqInfo) p.peersRequests = make(map[keyArray]*reqInfo) - p.dhtRequests = make(map[keyArray]*reqInfo) + p.dhtRequests = make(map[keyArray]*reqInfo) } // Common functions diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index 7912fc76..bbbfcdd6 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -39,6 +39,7 @@ func GenerateConfig() *config.NodeConfig { cfg.IfName = GetDefaults().DefaultIfName cfg.IfMTU = GetDefaults().DefaultIfMTU cfg.NodeInfoPrivacy = false + cfg.MixinHostname = false return cfg } diff --git a/src/util/util.go b/src/util/util.go index 507426d0..3276c34c 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -5,6 +5,7 @@ package util // These are misc. utility functions that didn't really fit anywhere else import ( + "crypto/ed25519" "time" ) @@ -35,3 +36,16 @@ func FuncTimeout(timeout time.Duration, f func()) bool { return false } } + +func MixinHostname(masterKey ed25519.PrivateKey, hostname string) ed25519.PrivateKey { + if len(hostname) == 0 { + return masterKey + } + + sigPrivSlice := make([]byte, 32) + copy(sigPrivSlice, masterKey[0:32]) + for index := 0; index < len(sigPrivSlice); index++ { + sigPrivSlice[index] = sigPrivSlice[index] ^ hostname[index%len(hostname)] + } + return ed25519.NewKeyFromSeed(sigPrivSlice) +}