Update the spec
This commit is contained in:
parent
69246c262d
commit
5fa7189858
37
README.md
37
README.md
@ -10,8 +10,8 @@ domain names for server to server communications.
|
|||||||
|
|
||||||
Self-organized and trustless networks like CJDNS and Yggdrasil Network are
|
Self-organized and trustless networks like CJDNS and Yggdrasil Network are
|
||||||
using public-key cryptography for IP address allocation. Every network node owns
|
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
|
a globally unique IPv6 address. Binary form of that address can be encoded with
|
||||||
translated to a globally unique domain name.
|
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
|
Since there is no need for a global authority or consensus, such a naming system
|
||||||
will reliably work in any network split scenarios.
|
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
|
".mesh.arpa" is ment to be used by machines, not by humans. A human-readable
|
||||||
naming system would require a lot more engineering effort.
|
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
|
In order to resolve a domain in "xxx.mesh.arpa." space, the client derives IPv6
|
||||||
return empty response.
|
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"
|
"xxx.mesh.arpa" name is itself managed by the DNS server derived from "xxx" and
|
||||||
resolves to the same IPv6 address as "xxx.mesh.arpa".
|
can point to any other IPv6 address.
|
||||||
|
|
||||||
## Why not .ip6.arpa
|
## Why not .ip6.arpa
|
||||||
|
|
||||||
There is a special domain for reverse DNS lookups, but it takes 72 characters to
|
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.
|
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"
|
"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 "aicrxoqgun7siwm42akzfsox7m.mesh.arpa"
|
versus "aiag7sesed2aaxgcgbnevruwpy.mesh.arpa."
|
||||||
|
|
||||||
This saves twice amount of bandwidth and storage space. It is also arguably more
|
This saves twice amount of bandwidth and storage space. It is also arguably more
|
||||||
aesthetically appealing, even though that's not a goal.
|
aesthetically appealing, even though that's not a goal.
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
port=53
|
port=53
|
||||||
domain-needed
|
domain-needed
|
||||||
bogus-priv
|
bogus-priv
|
||||||
server=/mesh.arpa/127.0.0.1#53535
|
server=/mesh.arpa/::1#53535
|
||||||
server=8.8.8.8
|
server=8.8.8.8
|
||||||
interface=lo
|
|
||||||
|
9
meshnamed.conf
Normal file
9
meshnamed.conf
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
164
meshnamed.go
164
meshnamed.go
@ -2,35 +2,61 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"fmt"
|
"encoding/json"
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
const domainZone = "mesh.arpa."
|
const domainZone = "mesh.arpa."
|
||||||
const maxTtl = 4294967295
|
|
||||||
|
|
||||||
var _, validSubnet, _ = net.ParseCIDR("::/0")
|
var _, validSubnet, _ = net.ParseCIDR("::/0")
|
||||||
|
var zoneConfigPath = ""
|
||||||
|
var zoneConfig = map[string][]dns.RR{}
|
||||||
|
var dnsClient = new(dns.Client)
|
||||||
|
|
||||||
var srvPortMap = map[string]uint16{
|
func loadConfig() {
|
||||||
"_xmpp-client._tcp": 5222,
|
if zoneConfigPath == "" {
|
||||||
"_xmpp-server._tcp": 5269,
|
return
|
||||||
"_submission._tcp": 587, // rfc6186
|
}
|
||||||
"_imap._tcp": 143,
|
|
||||||
"_imaps._tcp": 993,
|
reader, err := os.Open(zoneConfigPath)
|
||||||
"_pop3._tcp": 110,
|
if err != nil {
|
||||||
"_pop3s._tcp": 995,
|
fmt.Println("Can't open config:", err)
|
||||||
"_matrix._tcp": 8448, // https://matrix.org/docs/spec/server_server/unstable#server-discovery
|
return
|
||||||
"_sip._tcp": 5060, // rfc3263
|
}
|
||||||
"_sip._udp": 5060,
|
|
||||||
"_sips._tcp": 5061,
|
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) {
|
func lookup(domain string) (net.IP, error) {
|
||||||
name := strings.ToUpper(domain) + "======"
|
name := strings.ToUpper(domain) + "======"
|
||||||
data, err := base32.StdEncoding.DecodeString(name)
|
data, err := base32.StdEncoding.DecodeString(name)
|
||||||
@ -50,15 +76,25 @@ func lookup(domain string) (net.IP, error) {
|
|||||||
return ipAddr, nil
|
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) {
|
func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
var remoteLookups = map[string][]dns.Question{}
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetReply(r)
|
m.SetReply(r)
|
||||||
|
|
||||||
for _, v := range r.Question {
|
for _, q := range r.Question {
|
||||||
if v.Qclass != dns.ClassINET {
|
labels := dns.SplitDomainName(q.Name)
|
||||||
continue
|
|
||||||
}
|
|
||||||
labels := dns.SplitDomainName(v.Name)
|
|
||||||
if len(labels) < 3 {
|
if len(labels) < 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -68,48 +104,72 @@ func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if v.Qtype == dns.TypeAAAA {
|
if records, ok := zoneConfig[subDomain]; ok {
|
||||||
r := new(dns.AAAA)
|
for _, rec := range records {
|
||||||
r.Hdr = dns.RR_Header{Name: v.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: maxTtl}
|
if h := rec.Header(); h.Name == q.Name && h.Rrtype == q.Qtype && h.Class == q.Qclass {
|
||||||
r.AAAA = resolvedAddr
|
m.Answer = append(m.Answer, rec)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
} 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)
|
w.WriteMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := "127.0.0.1:53535"
|
helpMessage := "Usage:\nmeshnamed genconf [IP] > /etc/meshnamed.conf\nmeshnamed daemon /etc/meshnamed.conf"
|
||||||
if os.Getenv("LISTEN_ADDR") != "" {
|
if len(os.Args) < 2 {
|
||||||
addr = os.Getenv("LISTEN_ADDR")
|
fmt.Println(helpMessage)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("MESH_SUBNET") != "" {
|
action := os.Args[1]
|
||||||
_, meshSubnet, err := net.ParseCIDR(os.Getenv("MESH_SUBNET"))
|
if action == "genconf" && len(os.Args) == 3 {
|
||||||
|
confString, err := genConf(os.Args[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
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"}
|
addr := "[::1]:53535"
|
||||||
fmt.Println("Started meshnamed on:", addr)
|
if os.Getenv("LISTEN_ADDR") != "" {
|
||||||
dns.HandleFunc(domainZone, handleRequest)
|
addr = os.Getenv("LISTEN_ADDR")
|
||||||
server.ListenAndServe()
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,9 @@ Group=nogroup
|
|||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
ProtectSystem=true
|
ProtectSystem=true
|
||||||
SyslogIdentifier=meshnamed
|
SyslogIdentifier=meshnamed
|
||||||
Environment="LISTEN_ADDR=127.0.0.1:53535"
|
Environment="LISTEN_ADDR=[::1]:53535"
|
||||||
Environment="MESH_SUBNET=::/0"
|
Environment="MESH_SUBNET=::/0"
|
||||||
ExecStart=/usr/local/bin/meshnamed
|
ExecStart=/usr/local/bin/meshnamed daemon /etc/meshnamed.conf
|
||||||
Restart=always
|
Restart=always
|
||||||
TimeoutStopSec=5
|
TimeoutStopSec=5
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user