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