From 737e1d3edd1d66ab9105fe9969bf7f11f8ebd3a8 Mon Sep 17 00:00:00 2001 From: George Date: Sun, 19 Jan 2020 08:01:31 -0500 Subject: [PATCH] Add files --- Makefile | 12 +++++ README.md | 62 +++++++++++++++++++++++++ meshname.go | 75 ++++++++++++++++++++++++++++++ meshnamed.go | 115 ++++++++++++++++++++++++++++++++++++++++++++++ meshnamed.service | 19 ++++++++ 5 files changed, 283 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 meshname.go create mode 100644 meshnamed.go create mode 100644 meshnamed.service diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c631bb3 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +GOARCH := $(GOARCH) +GOOS := $(GOOS) +FLAGS := -ldflags "-s -w" + +all: + GOARCH=$$GOARCH GOOS=$$GOOS go build $(FLAGS) meshnamed.go + GOARCH=$$GOARCH GOOS=$$GOOS go build $(FLAGS) meshname.go + +clean: + $(RM) meshnamed meshname meshnamed.exe meshname.exe + +.PHONY: all clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..6deb9c7 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# meshname + +Special-use naming system for self-organized IPv6 mesh networks. + +## Motivation + +Having a naming system is a common requirement for deploying preexisting +decentralized applications. Protocols like e-mail, XMPP and ActivityPub require +domain names for server to server communications. + +Self-organized and trustless networks like CJDNS and Yggdrasil Network are +using public-key cryptography for IP address allocation. Every network node owns +a globally unique IPv6 address, and 16 bytes of that address can be +translated to a globally unique domain name. + +Since there is no need for a global authority or consensus, such a naming system +will reliably work in any network split scenarios. + +".mesh.arpa" is ment to be used by machines, not by humans. A human-readable +naming system would require a lot more engineering effort. + +## How to resolve .mesh.arpa domains + +Every third level domain in ".mesh.arpa" space represents a single IPv6 address. + +Domain "aicrxoqgun7siwm42akzfsox7m.mesh.arpa" is resolved as follows: + +1) Append base32 padding "======" to the upper cased third level domain token; + + AICRXOQGUN7SIWM42AKZFSOX7M====== + +2) Decode base32 string to a binary IPv6 representation; + + b'\x02\x05\x1b\xba\x06\xa3\x7f$Y\x9c\xd0\x15\x92\xc9\xd7\xfb' + +3) Convert the resulting 16 bytes to a IPv6 address structure. + + IPv6Address('205:1bba:6a3:7f24:599c:d015:92c9:d7fb') + +If the server cannot translate a given domain name to IP address it should +return empty response. + +Every additional subdomain, e.g. "mail.xxx.mesh.arpa, xmpp.xxx.mesh.arpa" +resolves to the same IPv6 address as "xxx.mesh.arpa". + +## Why not .ip6.arpa + +There is a special domain for reverse DNS lookups, but it takes 72 characters to +store a single value. The same value in .mesh.arpa takes 36 characters. + +"7.c.4.9.0.d.8.f.8.d.2.a.6.4.6.7.8.e.2.d.4.b.1.a.d.4.7.8.0.0.2.0.ip6.arpa" +versus "aicrxoqgun7siwm42akzfsox7m.mesh.arpa" + +This saves twice amount of bandwidth and storage space. It is also arguably more +aesthetically appealing, even though that's not a goal. + +## Why .arpa + +".arpa" is a special domain reserved for Internet infrastructure. There is a +similar special-use domain for home networks ".home.arpa" specified in RFC 8375. +If ".mesh.arpa" will become widely used it could also be standardized, otherwise +it won't break much. diff --git a/meshname.go b/meshname.go new file mode 100644 index 0000000..7d313f7 --- /dev/null +++ b/meshname.go @@ -0,0 +1,75 @@ +package main + +import ( + "encoding/base32" + "fmt" + "net" + "strings" + "os" + "errors" +) + +var domainZone = ".mesh.arpa" + +func reverse_lookup(target string) (string, error) { + ip := net.ParseIP(target) + if ip == nil { + return "", errors.New("Invalid IP address") + } + str := base32.StdEncoding.EncodeToString(ip)[0:26] + return strings.ToLower(str) + domainZone, nil +} + +func lookup(target string) (string, error) { + labels := strings.Split(target, ".") + if len(labels) < 3 || strings.HasSuffix(domainZone, target) { + return "", errors.New("Invalid domain") + } + subDomain := labels[len(labels) - 3] + if len(subDomain) != 26 { + return "", errors.New("Invalid subdomain length") + } + name := strings.ToUpper(subDomain) + "======" + data, err := base32.StdEncoding.DecodeString(name) + if err != nil { + return "", err + } + s := net.IP(data) + if s == nil { + return "", errors.New("Invalid IP address") + } + return s.String(), nil +} + +func main() { + usage := "Usage:\n\nmeshname lookup DOMAIN\nmeshname reverse_lookup IP" + if len(os.Args) != 3 { + fmt.Println(usage) + return + } + + action := os.Args[1] + target := os.Args[2] + + switch action { + case "lookup": + result, err := lookup(target) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println(result) + return + case "reverse_lookup": + result, err := reverse_lookup(target) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println(result) + return + default: + fmt.Println(usage) + return + } +} diff --git a/meshnamed.go b/meshnamed.go new file mode 100644 index 0000000..8e2bfd4 --- /dev/null +++ b/meshnamed.go @@ -0,0 +1,115 @@ +package main + +import ( + "encoding/base32" + "fmt" + "net" + "strings" + "errors" + "os" + + "github.com/miekg/dns" +) + +const domainZone = "mesh.arpa." +const maxTtl = 4294967295 + +var _, validSubnet, _ = net.ParseCIDR("::/0") + +var srvPortMap = map[string]uint16{ + "_xmpp-client._tcp": 5222, + "_xmpp-server._tcp": 5269, + "_submission._tcp": 587, // rfc6186 + "_imap._tcp": 143, + "_imaps._tcp": 993, + "_pop3._tcp": 110, + "_pop3s._tcp": 995, + "_matrix._tcp": 8448, // https://matrix.org/docs/spec/server_server/unstable#server-discovery + "_sip._tcp": 5060, // rfc3263 + "_sip._udp": 5060, + "_sips._tcp": 5061, +} + + +func lookup(domain string) (net.IP, error) { + name := strings.ToUpper(domain) + "======" + data, err := base32.StdEncoding.DecodeString(name) + if err != nil { + return net.IP{}, err + } + if len(data) != 16 { + return net.IP{}, errors.New("Invalid subdomain") + } + ipAddr := net.IP(data) + if ipAddr == nil { + return net.IP{}, errors.New("Invalid IP address") + } + if !validSubnet.Contains(ipAddr) { + return net.IP{}, errors.New("Address from invalid subnet") + } + return ipAddr, nil +} + +func handleRequest(w dns.ResponseWriter, r *dns.Msg) { + m := new(dns.Msg) + m.SetReply(r) + + for _, v := range r.Question { + if v.Qclass != dns.ClassINET { + continue + } + labels := dns.SplitDomainName(v.Name) + if len(labels) < 3 { + continue + } + subDomain := labels[len(labels)-3] + + resolvedAddr, err := lookup(subDomain) + if err != nil { + continue + } + if v.Qtype == dns.TypeAAAA { + r := new(dns.AAAA) + r.Hdr = dns.RR_Header{Name: v.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: maxTtl} + r.AAAA = resolvedAddr + m.Answer = append(m.Answer, r) + } else if v.Qtype == dns.TypeSRV { + if len(labels) < 5 { + continue + } + + if srvRec := labels[0] + "." + labels[1]; srvPortMap[srvRec] != 0 { + r := new(dns.SRV) + r.Hdr = dns.RR_Header{Name: v.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: maxTtl} + r.Priority = 0 + r.Weight = 0 + r.Port = srvPortMap[srvRec] + r.Target = subDomain + "." + domainZone + m.Answer = append(m.Answer, r) + } + } + } + + w.WriteMsg(m) +} + +func main() { + addr := "127.0.0.1:53535" + if os.Getenv("LISTEN_ADDR") != "" { + addr = os.Getenv("LISTEN_ADDR") + } + + if os.Getenv("MESH_SUBNET") != "" { + _, meshSubnet, err := net.ParseCIDR(os.Getenv("MESH_SUBNET")) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + validSubnet = meshSubnet + } + + server := &dns.Server{Addr: addr, Net: "udp"} + fmt.Println("Started meshnamed on:", addr) + dns.HandleFunc(domainZone, handleRequest) + server.ListenAndServe() +} diff --git a/meshnamed.service b/meshnamed.service new file mode 100644 index 0000000..5d6f7ea --- /dev/null +++ b/meshnamed.service @@ -0,0 +1,19 @@ +[Unit] +Description=Distributed naming system for IPv6 mesh networks +Wants=network.target +After=network.target + +[Service] +User=nobody +Group=nogroup +ProtectHome=true +ProtectSystem=true +SyslogIdentifier=meshnamed +Environment="LISTEN_ADDR=127.0.0.1:53535" +Environment="MESH_SUBNET=::/0" +ExecStart=/usr/local/bin/meshnamed +Restart=always +TimeoutStopSec=5 + +[Install] +WantedBy=multi-user.target