Add files
This commit is contained in:
commit
737e1d3edd
12
Makefile
Normal file
12
Makefile
Normal 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
62
README.md
Normal 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
75
meshname.go
Normal 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
115
meshnamed.go
Normal 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
19
meshnamed.service
Normal 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
|
Loading…
Reference in New Issue
Block a user