Update the spec

This commit is contained in:
George 2020-01-26 14:20:22 -05:00
parent 69246c262d
commit 5fa7189858
5 changed files with 144 additions and 73 deletions

View File

@ -10,8 +10,8 @@ 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.
a globally unique IPv6 address. Binary form of that address can be encoded with
base32 notation for deriving a globally unique name space managed by that node.
Since there is no need for a global authority or consensus, such a naming system
will reliably work in any network split scenarios.
@ -19,37 +19,40 @@ 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
## How .mesh.arpa domains work
Every third level domain in ".mesh.arpa" space represents a single IPv6 address.
Each mesh node can manage its own unique name space in "mesh.arpa." zone.
The name space is derived from its IPv6 address as follows:
Domain "aicrxoqgun7siwm42akzfsox7m.mesh.arpa" is resolved as follows:
1) IPv6 address is converted to its binary form of 16 bytes:
1) Append base32 padding "======" to the upper cased third level domain token;
IPv6Address('200:6fc8:9220:f400:5cc2:305a:4ac6:967e')
AICRXOQGUN7SIWM42AKZFSOX7M======
b'\x02\x00o\xc8\x92 \xf4\x00\\\xc20ZJ\xc6\x96~'
2) Decode base32 string to a binary IPv6 representation;
2) The binary value is encoded to base32:
b'\x02\x05\x1b\xba\x06\xa3\x7f$Y\x9c\xd0\x15\x92\xc9\xd7\xfb'
AIAG7SESED2AAXGCGBNEVRUWPY======
3) Convert the resulting 16 bytes to a IPv6 address structure.
3) Padding symbols "======" are removed from the end of the string.
IPv6Address('205:1bba:6a3:7f24:599c:d015:92c9:d7fb')
The resulting name space managed by '200:6fc8:9220:f400:5cc2:305a:4ac6:967e'
is "aiag7sesed2aaxgcgbnevruwpy.mesh.arpa."
If the server cannot translate a given domain name to IP address it should
return empty response.
In order to resolve a domain in "xxx.mesh.arpa." space, the client derives IPv6
address from the third level domain "xxx" and use it as authoritative DNS server
for that zone.
Every additional subdomain, e.g. "mail.xxx.mesh.arpa, xmpp.xxx.mesh.arpa"
resolves to the same IPv6 address as "xxx.mesh.arpa".
"xxx.mesh.arpa" name is itself managed by the DNS server derived from "xxx" and
can point to any other IPv6 address.
## 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"
"e.7.6.9.6.c.a.4.a.5.0.3.2.c.c.5.0.0.4.f.0.2.2.9.8.c.f.6.0.0.2.0.ip6.arpa"
versus "aiag7sesed2aaxgcgbnevruwpy.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.

View File

@ -1,6 +1,5 @@
port=53
domain-needed
bogus-priv
server=/mesh.arpa/127.0.0.1#53535
server=/mesh.arpa/::1#53535
server=8.8.8.8
interface=lo

9
meshnamed.conf Normal file
View File

@ -0,0 +1,9 @@
{
"Domain":"aiag7sesed2aaxgcgbnevruwpy",
"Records": [
"aiag7sesed2aaxgcgbnevruwpy.mesh.arpa. AAAA 200:6fc8:9220:f400:5cc2:305a:4ac6:967e",
"_xmpp-client._tcp.aiag7sesed2aaxgcgbnevruwpy.mesh.arpa. SRV 5 0 5222 xmpp.aiag7sesed2aaxgcgbnevruwpy.mesh.arpa",
"_xmpp-server._tcp.aiag7sesed2aaxgcgbnevruwpy.mesh.arpa. SRV 5 0 5269 xmpp.aiag7sesed2aaxgcgbnevruwpy.mesh.arpa",
"xmpp.aiag7sesed2aaxgcgbnevruwpy.mesh.arpa. AAAA 300:6fc8:9220:f400::1"
]
}

View File

@ -2,35 +2,61 @@ package main
import (
"encoding/base32"
"fmt"
"net"
"strings"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"os"
"strings"
"github.com/miekg/dns"
)
const domainZone = "mesh.arpa."
const maxTtl = 4294967295
var _, validSubnet, _ = net.ParseCIDR("::/0")
var zoneConfigPath = ""
var zoneConfig = map[string][]dns.RR{}
var dnsClient = new(dns.Client)
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 loadConfig() {
if zoneConfigPath == "" {
return
}
reader, err := os.Open(zoneConfigPath)
if err != nil {
fmt.Println("Can't open config:", err)
return
}
type Zone struct {
Domain string
Records []string
}
dec := json.NewDecoder(reader)
for {
var m Zone
if err := dec.Decode(&m); err == io.EOF {
break
} else if err != nil {
fmt.Println("Syntax error in config:", err)
return
}
for _, v := range m.Records {
rr, err := dns.NewRR(v)
if err != nil {
fmt.Println("Invalid DNS record:", v)
continue
}
zoneConfig[m.Domain] = append(zoneConfig[m.Domain], rr)
}
}
fmt.Println("Config loaded:", zoneConfigPath)
}
func lookup(domain string) (net.IP, error) {
name := strings.ToUpper(domain) + "======"
data, err := base32.StdEncoding.DecodeString(name)
@ -50,15 +76,25 @@ func lookup(domain string) (net.IP, error) {
return ipAddr, nil
}
func genConf(target string) (string, error) {
ip := net.ParseIP(target)
if ip == nil {
return "", errors.New("Invalid IP address")
}
zone := strings.ToLower(base32.StdEncoding.EncodeToString(ip)[0:26])
selfRecord := fmt.Sprintf("\t\t\"%s.%s AAAA %s\"\n", zone, domainZone, target)
confString := fmt.Sprintf("{\n\t\"Domain\":\"%s\",\n\t\"Records\":[\n%s\t]\n}", zone, selfRecord)
return confString, nil
}
func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
var remoteLookups = map[string][]dns.Question{}
m := new(dns.Msg)
m.SetReply(r)
for _, v := range r.Question {
if v.Qclass != dns.ClassINET {
continue
}
labels := dns.SplitDomainName(v.Name)
for _, q := range r.Question {
labels := dns.SplitDomainName(q.Name)
if len(labels) < 3 {
continue
}
@ -68,48 +104,72 @@ func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
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)
if records, ok := zoneConfig[subDomain]; ok {
for _, rec := range records {
if h := rec.Header(); h.Name == q.Name && h.Rrtype == q.Qtype && h.Class == q.Qclass {
m.Answer = append(m.Answer, rec)
}
}
} else if ra := w.RemoteAddr().String(); strings.HasPrefix(ra, "[::1]:") || strings.HasPrefix(ra, "127.0.0.1:") {
// do remote lookups only for local clients
remoteLookups[resolvedAddr.String()] = append(remoteLookups[resolvedAddr.String()], q)
}
}
for remoteServer, questions := range remoteLookups {
rm := new(dns.Msg)
rm.Question = questions
resp, _, err := dnsClient.Exchange(rm, "["+remoteServer+"]:53") // no retries
if err != nil {
continue
}
m.Answer = append(m.Answer, resp.Answer...)
}
w.WriteMsg(m)
}
func main() {
addr := "127.0.0.1:53535"
if os.Getenv("LISTEN_ADDR") != "" {
addr = os.Getenv("LISTEN_ADDR")
helpMessage := "Usage:\nmeshnamed genconf [IP] > /etc/meshnamed.conf\nmeshnamed daemon /etc/meshnamed.conf"
if len(os.Args) < 2 {
fmt.Println(helpMessage)
return
}
if os.Getenv("MESH_SUBNET") != "" {
_, meshSubnet, err := net.ParseCIDR(os.Getenv("MESH_SUBNET"))
action := os.Args[1]
if action == "genconf" && len(os.Args) == 3 {
confString, err := genConf(os.Args[2])
if err != nil {
fmt.Println(err)
os.Exit(1)
} else {
fmt.Println(confString)
}
} else if action == "daemon" {
if len(os.Args) == 3 {
zoneConfigPath = os.Args[2]
loadConfig()
}
validSubnet = meshSubnet
}
server := &dns.Server{Addr: addr, Net: "udp"}
fmt.Println("Started meshnamed on:", addr)
dns.HandleFunc(domainZone, handleRequest)
server.ListenAndServe()
addr := "[::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
}
dnsClient.Timeout = 5000000000 // increased 5 seconds timeout
dnsServer := &dns.Server{Addr: addr, Net: "udp"}
fmt.Println("Started meshnamed on:", addr)
dns.HandleFunc(domainZone, handleRequest)
dnsServer.ListenAndServe()
} else {
fmt.Println(helpMessage)
}
}

View File

@ -9,9 +9,9 @@ Group=nogroup
ProtectHome=true
ProtectSystem=true
SyslogIdentifier=meshnamed
Environment="LISTEN_ADDR=127.0.0.1:53535"
Environment="LISTEN_ADDR=[::1]:53535"
Environment="MESH_SUBNET=::/0"
ExecStart=/usr/local/bin/meshnamed
ExecStart=/usr/local/bin/meshnamed daemon /etc/meshnamed.conf
Restart=always
TimeoutStopSec=5