add mDNS responder for mixed in hostname lookup and key based lookup

This commit is contained in:
FPS 2022-02-07 17:40:35 +01:00
parent 559e31c502
commit ac72653627
11 changed files with 355 additions and 3 deletions

2
build
View file

@ -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

View file

@ -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.

258
cmd/yggmdns/main.go Normal file
View file

@ -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
}
}
}
}

View file

@ -171,6 +171,39 @@ cat > wix.xml << EOF
Remove="uninstall" />
</Component>
<Component Id="mDNSExecutable" Guid="c2119231-2aa3-4962-867a-9759c87beb25">
<File
Id="YggdrasilmDNS"
Name="yggmdns.exe"
DiskId="1"
Source="yggmdns.exe"
KeyPath="yes" />
<ServiceInstall
Id="ServiceInstallermDNS"
Account="LocalSystem"
Description="Yggdrasil mDNS responder"
DisplayName="Yggdrasil mDNS Service"
ErrorControl="normal"
LoadOrderGroup="NetworkProvider"
Name="YggdrasilmDNS"
Start="auto"
Type="ownProcess"
Arguments='-interface Yggdrasil -useconffile "%ALLUSERSPROFILE%\\Yggdrasil\\yggdrasil.conf" -logto "%ALLUSERSPROFILE%\\Yggdrasil\\yggmdns.log"'
Vital="no">
<ServiceDependency
Id="Yggdrasil"
Group="no" />
</ServiceInstall>
<ServiceControl
Id="ServiceControlmDNS"
Name="yggdrasilmdns"
Start="install"
Stop="both"
Remove="uninstall" />
</Component>
<Component Id="CtrlExecutable" Guid="a916b730-974d-42a1-b687-d9d504cbb86a">
<File
Id="Yggdrasilctl"
@ -194,6 +227,7 @@ cat > wix.xml << EOF
<Feature Id="YggdrasilFeature" Title="Yggdrasil" Level="1">
<ComponentRef Id="MainExecutable" />
<ComponentRef Id="mDNSExecutable" />
<ComponentRef Id="CtrlExecutable" />
<ComponentRef Id="ConfigScript" />
</Feature>

View file

@ -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

1
go.mod
View file

@ -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

9
go.sum
View file

@ -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=

View file

@ -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."`

View file

@ -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

View file

@ -39,6 +39,7 @@ func GenerateConfig() *config.NodeConfig {
cfg.IfName = GetDefaults().DefaultIfName
cfg.IfMTU = GetDefaults().DefaultIfMTU
cfg.NodeInfoPrivacy = false
cfg.MixinHostname = false
return cfg
}

View file

@ -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)
}