Add files

This commit is contained in:
George 2020-01-19 08:01:31 -05:00
commit 737e1d3edd
5 changed files with 283 additions and 0 deletions

12
Makefile Normal file
View File

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

62
README.md Normal file
View File

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

75
meshname.go Normal file
View File

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

115
meshnamed.go Normal file
View File

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

19
meshnamed.service Normal file
View File

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