├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── USAGE.md ├── cmd └── meshnamed │ └── main.go ├── contrib ├── gentoo │ └── meshnamed-0.1.0.ebuild └── meshnamed.initd ├── docker-entrypoint.sh ├── go.mod ├── go.sum ├── img ├── logo-maximum.ai ├── logo-maximum.png ├── logo-maximum.svg ├── logo-medium.ai ├── logo-medium.png ├── logo-medium.svg ├── logo-minimum.ai ├── logo-minimum.png ├── logo-minimum.svg └── readme.md ├── meshnamed.service ├── pkg └── meshname │ ├── domain.go │ ├── domain_test.go │ └── server.go └── protocol.md /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/golang:alpine as builder 2 | 3 | COPY . /src 4 | WORKDIR /src 5 | RUN apk add git make && make 6 | 7 | FROM docker.io/alpine 8 | 9 | LABEL maintainer="George " 10 | 11 | COPY --from=builder /src/meshnamed /usr/bin/meshnamed 12 | 13 | USER nobody 14 | 15 | COPY docker-entrypoint.sh /usr/local/bin/ 16 | ENTRYPOINT ["docker-entrypoint.sh"] 17 | 18 | EXPOSE 53535/udp 19 | CMD ["meshnamed"] 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOARCH := $(GOARCH) 2 | GOOS := $(GOOS) 3 | FLAGS := -ldflags "-s -w" 4 | 5 | all: 6 | GOARCH=$$GOARCH GOOS=$$GOOS go build $(FLAGS) ./cmd/meshnamed 7 | 8 | clean: 9 | $(RM) meshnamed meshnamed.exe 10 | 11 | test: 12 | go test pkg/meshname/*_test.go 13 | 14 | .PHONY: all clean test 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # meshname 2 | 3 | 4 | 5 | A universal naming system for all IPv6-based mesh networks, including CJDNS and Yggdrasil. 6 | Implements the [Meshname protocol](https://github.com/zhoreeq/meshname/blob/master/protocol.md). 7 | 8 | ## F.A.Q. 9 | 10 | - Q: *Is it like a decentralized DNS thing?* 11 | - A: Yeah, sort of. With it you can host your own meshname domains and resolve domains of others. 12 | 13 | - Q: *Meshname domains are ugly.* 14 | - A: Yes, if you want decentralization, you either have ugly names or a blockchain. Meshname has ugly names, but it works at least! 15 | 16 | ## How to use meshname domains? 17 | 18 | Use a full-featured DNS server with the meshname protocol support, i.e. [PopuraDNS](https://github.com/popura-network/PopuraDNS). 19 | 20 | For a standalone .meshname stub resolver see `USAGE.md` 21 | 22 | ## Alternative implementations 23 | 24 | [Mario DNS](https://notabug.org/acetone/mario-dns) by acetone, a C++ implementation with a web interface. 25 | 26 | [Ruby gem](https://rubygems.org/gems/meshname) by marek22k, [source](https://github.com/marek22k/meshname). 27 | 28 | ## See also 29 | 30 | [YggNS](https://github.com/russian-meshnet/YggNS/blob/master/README.md) 31 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # How to install and use 2 | 3 | Minimum go version 1.12 is required. 4 | 5 | 1) Get the source code and compile 6 | ``` 7 | git clone https://github.com/zhoreeq/meshname.git 8 | cd meshname 9 | make 10 | ``` 11 | 2) Run the daemon 12 | ``` 13 | ./meshnamed 14 | ``` 15 | 3) Optionally, set configuration flags 16 | ``` 17 | ./meshnamed -listenaddr [::1]:53535 -debug 18 | ``` 19 | 4) See the list of all available flags 20 | ``` 21 | ./meshnamed -help 22 | ``` 23 | 24 | ## Get meshname subdomain from an IPv6 address 25 | 26 | ``` 27 | ./meshnamed -getname 200:f8b1:f974:967f:dd32:145d:1cc0:3679 28 | aiaprmpzoslh7xjscrorzqbwpe 29 | ``` 30 | 31 | Use this subdomain with a .meshname TLD to configure DNS records 32 | on your authoritative server, (i.e. dnsmasq, bind or PopuraDNS). 33 | 34 | ## systemd unit 35 | 36 | Look for `meshnamed.service` in the source directory for a systemd unit file. 37 | 38 | ## Configure dnsmasq as a primary DNS resolver with "meshname." support 39 | 40 | `/etc/dnsmasq.conf` 41 | 42 | port=53 43 | domain-needed 44 | bogus-priv 45 | server=/meshname/::1#53535 46 | server=8.8.8.8 47 | 48 | ## Custom top level domains (TLD) and subnet filtering 49 | 50 | meshnamed can be configured to resolve custom TLDs. 51 | To run meshnamed for TLD `.newmesh` with addresses in `fd00::/8` 52 | set a flag `-networks newmesh=fd00::/8`. 53 | 54 | By default, in addition to `.meshname` it also resolves `.ygg` for IPv6 addresses in 55 | `200::/7` subnet and `.cjd` for `fc00::/8`. 56 | 57 | Requests are filtered by subnet validation. Request is ignored if a decoded 58 | IPv6 address doesn't match the specified subnet for a TLD. 59 | -------------------------------------------------------------------------------- /cmd/meshnamed/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | "syscall" 11 | 12 | "github.com/gologme/log" 13 | 14 | "github.com/zhoreeq/meshname/pkg/meshname" 15 | ) 16 | 17 | func parseNetworks(networksconf string) (map[string]*net.IPNet, error) { 18 | networks := make(map[string]*net.IPNet) 19 | for _, item := range strings.Split(networksconf, ",") { 20 | if tokens := strings.SplitN(item, "=", 2); len(tokens) == 2 { 21 | if _, validSubnet, err := net.ParseCIDR(tokens[1]); err == nil { 22 | networks[tokens[0]] = validSubnet 23 | } else { 24 | return nil, err 25 | } 26 | } 27 | } 28 | return networks, nil 29 | } 30 | 31 | var ( 32 | listenAddr, networksconf string 33 | getName, getIP string 34 | debug, noMeshIP bool 35 | ) 36 | 37 | func init() { 38 | flag.StringVar(&listenAddr, "listenaddr", "[::1]:53535", "address to listen on") 39 | flag.StringVar(&networksconf, "networks", "ygg=200::/7,cjd=fc00::/8,meshname=::/0,popura=::/0", "TLD=subnet list separated by comma") 40 | flag.BoolVar(&noMeshIP, "nomeship", false, "disable .meship resolver") 41 | flag.StringVar(&getName, "getname", "", "convert IPv6 address to a name") 42 | flag.StringVar(&getIP, "getip", "", "convert a name to IPv6 address") 43 | flag.BoolVar(&debug, "debug", false, "enable debug logging") 44 | } 45 | 46 | func main() { 47 | flag.Parse() 48 | 49 | logger := log.New(os.Stdout, "", log.Flags()) 50 | 51 | logger.EnableLevel("error") 52 | logger.EnableLevel("warn") 53 | logger.EnableLevel("info") 54 | if debug { 55 | logger.EnableLevel("debug") 56 | } 57 | 58 | if getName != "" { 59 | ip := net.ParseIP(getName) 60 | if ip == nil { 61 | logger.Fatal("Invalid IP address") 62 | } 63 | subDomain := meshname.DomainFromIP(&ip) 64 | fmt.Println(subDomain) 65 | return 66 | } else if getIP != "" { 67 | ip, err := meshname.IPFromDomain(&getIP) 68 | if err != nil { 69 | logger.Fatal(err) 70 | } 71 | fmt.Println(ip) 72 | return 73 | } 74 | 75 | networks, err := parseNetworks(networksconf) 76 | if err != nil { 77 | logger.Fatalln(err) 78 | } 79 | 80 | s := meshname.New(logger, listenAddr, networks, !noMeshIP) 81 | 82 | if err := s.Start(); err != nil { 83 | logger.Fatal(err) 84 | } 85 | logger.Infoln("Listening on:", listenAddr) 86 | 87 | c := make(chan os.Signal, 1) 88 | r := make(chan os.Signal, 1) 89 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 90 | signal.Notify(r, os.Interrupt, syscall.SIGHUP) 91 | defer s.Stop() 92 | for { 93 | select { 94 | case <-c: 95 | return 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contrib/gentoo/meshnamed-0.1.0.ebuild: -------------------------------------------------------------------------------- 1 | # Distributed under the terms of the GNU General Public License v2 2 | 3 | EAPI="7" 4 | inherit go-module 5 | 6 | DESCRIPTION="Meshname, a universal naming system for all IPv6-based mesh networks, including CJDNS and Yggdrasil" 7 | HOMEPAGE="https://github.com/zhoreeq/meshname" 8 | 9 | EGO_SUM=( 10 | "github.com/gologme/log v1.2.0" 11 | "github.com/gologme/log v1.2.0/go.mod" 12 | "github.com/miekg/dns v1.1.27" 13 | "github.com/miekg/dns v1.1.27/go.mod" 14 | "golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod" 15 | "golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550" 16 | "golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod" 17 | "golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod" 18 | "golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod" 19 | "golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod" 20 | "golang.org/x/net v0.0.0-20190923162816-aa69164e4478" 21 | "golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod" 22 | "golang.org/x/sync v0.0.0-20190423024810-112230192c58" 23 | "golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod" 24 | "golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod" 25 | "golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod" 26 | "golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe" 27 | "golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod" 28 | "golang.org/x/text v0.3.0/go.mod" 29 | "golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod" 30 | "golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod" 31 | ) 32 | go-module_set_globals 33 | 34 | if [[ ${PV} != *9999* ]]; then 35 | SRC_URI="https://github.com/zhoreeq/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz" 36 | KEYWORDS="amd64 ~amd64 x86 ~x86" 37 | else 38 | EGIT_REPO_URI="https://github.com/zhoreeq/${PN}.git" 39 | KEYWORDS="amd64 x86" 40 | fi 41 | SRC_URI+="${EGO_SUM_SRC_URI}" 42 | 43 | LICENSE="MIT" 44 | SLOT="0" 45 | IUSE="systemd" 46 | 47 | DEPEND=">dev-lang/go-1.12" 48 | 49 | src_install() { 50 | echo "" 51 | exeinto /usr/bin 52 | doexe meshnamed 53 | dodoc README.md protocol.md 54 | 55 | if use systemd ; then 56 | systemd_newunit "${PN}d.service" ${PN}d.service 57 | else 58 | newinitd "${FILESDIR}/${PN}d.initd" ${PN}d 59 | fi 60 | } 61 | 62 | pkg_postinst() { 63 | elog "The meshname daemon will have to be started before use:" 64 | if use systemd ; then 65 | elog " # systemctl start meshnamed" 66 | else 67 | elog " # rc-service meshnamed start" 68 | fi 69 | } 70 | -------------------------------------------------------------------------------- /contrib/meshnamed.initd: -------------------------------------------------------------------------------- 1 | #!/sbin/openrc-run 2 | # Distributed under the terms of the GNU General Public License v2 3 | 4 | extra_started_commands="reload" 5 | command="/usr/bin/meshnamed" 6 | description="Distributed naming system for IPv6 mesh networks" 7 | pidfile="/run/meshnamed.pid" 8 | logfile="/var/run/meshnamed.log" 9 | start_stop_daemon_args="--user nobody --group nobody -listenaddr '[::1]:53535'" 10 | 11 | start() { 12 | ebegin "Starting Distributed naming system for IPv6 mesh networks" 13 | start-stop-daemon --start --exec "${command}" --pidfile "${pidfile}" --background \ 14 | --stdout "${logfile}" --stderr "${logfile}" 15 | eend $? 16 | } 17 | 18 | stop() { 19 | ebegin "Distributed naming system for IPv6 mesh networks" 20 | start-stop-daemon --stop --exec "${command}" --pidfile "${pidfile}" 21 | eend $? 22 | } 23 | 24 | reload() { 25 | stop 26 | sleep 5 27 | start 28 | } 29 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | exec meshnamed "$@" 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zhoreeq/meshname 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/gologme/log v1.2.0 7 | github.com/miekg/dns v1.1.27 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= 2 | github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= 3 | github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= 4 | github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 5 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 6 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= 7 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 8 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 9 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 10 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 11 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= 12 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 13 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 14 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 15 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 16 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 17 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= 18 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 19 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 20 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 21 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 22 | -------------------------------------------------------------------------------- /img/logo-maximum.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhoreeq/meshname/38781e39fe2ab2fd388119ef688a4077ae64ac09/img/logo-maximum.ai -------------------------------------------------------------------------------- /img/logo-maximum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhoreeq/meshname/38781e39fe2ab2fd388119ef688a4077ae64ac09/img/logo-maximum.png -------------------------------------------------------------------------------- /img/logo-maximum.svg: -------------------------------------------------------------------------------- 1 | logo-ver2 -------------------------------------------------------------------------------- /img/logo-medium.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhoreeq/meshname/38781e39fe2ab2fd388119ef688a4077ae64ac09/img/logo-medium.ai -------------------------------------------------------------------------------- /img/logo-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhoreeq/meshname/38781e39fe2ab2fd388119ef688a4077ae64ac09/img/logo-medium.png -------------------------------------------------------------------------------- /img/logo-medium.svg: -------------------------------------------------------------------------------- 1 | logo-ver1 -------------------------------------------------------------------------------- /img/logo-minimum.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhoreeq/meshname/38781e39fe2ab2fd388119ef688a4077ae64ac09/img/logo-minimum.ai -------------------------------------------------------------------------------- /img/logo-minimum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhoreeq/meshname/38781e39fe2ab2fd388119ef688a4077ae64ac09/img/logo-minimum.png -------------------------------------------------------------------------------- /img/logo-minimum.svg: -------------------------------------------------------------------------------- 1 | logo -------------------------------------------------------------------------------- /img/readme.md: -------------------------------------------------------------------------------- 1 | Логотип проекта, не облагаемый какими-либо лицензионными обязательствами. 2 | -------------------------------------------------------------------------------- /meshnamed.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Distributed naming system for IPv6 mesh networks 3 | Wants=network.target 4 | After=network.target 5 | 6 | [Service] 7 | User=nobody 8 | Group=nogroup 9 | ProtectHome=true 10 | ProtectSystem=true 11 | SyslogIdentifier=meshnamed 12 | ExecStart=/usr/local/bin/meshnamed -listenaddr [::1]:53535 13 | Restart=always 14 | TimeoutStopSec=5 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /pkg/meshname/domain.go: -------------------------------------------------------------------------------- 1 | package meshname 2 | 3 | import ( 4 | "encoding/base32" 5 | "errors" 6 | "net" 7 | "strings" 8 | ) 9 | 10 | // DomainFromIP derives a meshname subdomain for the authoritative DNS server address 11 | func DomainFromIP(target *net.IP) string { 12 | return strings.ToLower(base32.StdEncoding.EncodeToString(*target)[0:26]) 13 | } 14 | 15 | // IPFromDomain derives authoritative DNS server address from the meshname subdomain 16 | func IPFromDomain(domain *string) (net.IP, error) { 17 | name := strings.ToUpper(*domain) + "======" 18 | data, err := base32.StdEncoding.DecodeString(name) 19 | if err != nil { 20 | return nil, err 21 | } 22 | if len(data) != 16 { 23 | return nil, errors.New("can't decode IP address, invalid subdomain") 24 | } 25 | ipAddr := net.IP(data) 26 | if ipAddr == nil { 27 | return nil, errors.New("can't decode IP address, invalid data") 28 | } 29 | return ipAddr, nil 30 | } 31 | -------------------------------------------------------------------------------- /pkg/meshname/domain_test.go: -------------------------------------------------------------------------------- 1 | package meshname 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "testing" 8 | 9 | "github.com/zhoreeq/meshname/pkg/meshname" 10 | ) 11 | 12 | func TestIPFromDomain(t *testing.T) { 13 | test_subdomain := "aib7cwwdeob2vtnqf2cfnm7ilq" 14 | test_ip := net.ParseIP("203:f15a:c323:83aa:cdb0:2e84:56b3:e85c") 15 | 16 | if ip, err := meshname.IPFromDomain(&test_subdomain); err != nil { 17 | t.Fatal(err) 18 | } else if bytes.Compare(ip, test_ip) != 0 { 19 | t.Fatalf("Decoding IP error %s != %s", ip.String(), test_ip.String()) 20 | } 21 | } 22 | 23 | func TestDomainFromIP(t *testing.T) { 24 | test_subdomain := "aib7cwwdeob2vtnqf2cfnm7ilq" 25 | test_ip := net.ParseIP("203:f15a:c323:83aa:cdb0:2e84:56b3:e85c") 26 | 27 | subdomain := meshname.DomainFromIP(&test_ip) 28 | if subdomain != test_subdomain { 29 | t.Fatalf("Encoding domain error: %s != %s", subdomain, test_subdomain) 30 | } 31 | } 32 | 33 | func ExampleIPFromDomain() { 34 | test_subdomain := "aib7cwwdeob2vtnqf2cfnm7ilq" 35 | 36 | if ip, err := meshname.IPFromDomain(&test_subdomain); err == nil { 37 | fmt.Println(ip) 38 | } 39 | // Output: 203:f15a:c323:83aa:cdb0:2e84:56b3:e85c 40 | } 41 | 42 | func ExampleDomainFromIP() { 43 | test_ip := net.ParseIP("203:f15a:c323:83aa:cdb0:2e84:56b3:e85c") 44 | 45 | fmt.Println(meshname.DomainFromIP(&test_ip)) 46 | // Output: aib7cwwdeob2vtnqf2cfnm7ilq 47 | } 48 | -------------------------------------------------------------------------------- /pkg/meshname/server.go: -------------------------------------------------------------------------------- 1 | package meshname 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sync" 7 | 8 | "github.com/gologme/log" 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | type MeshnameServer struct { 13 | log *log.Logger 14 | listenAddr string 15 | dnsClient *dns.Client 16 | dnsServer *dns.Server 17 | networks map[string]*net.IPNet 18 | enableMeshIP bool 19 | 20 | startedLock sync.RWMutex 21 | started bool 22 | } 23 | 24 | // New is a constructor for MeshnameServer 25 | func New(log *log.Logger, listenAddr string, networks map[string]*net.IPNet, enableMeshIP bool) *MeshnameServer { 26 | dnsClient := new(dns.Client) 27 | dnsClient.Timeout = 5000000000 // increased 5 seconds timeout 28 | 29 | return &MeshnameServer{ 30 | log: log, 31 | listenAddr: listenAddr, 32 | networks: networks, 33 | dnsClient: dnsClient, 34 | enableMeshIP: enableMeshIP, 35 | } 36 | } 37 | 38 | func (s *MeshnameServer) Stop() { 39 | s.startedLock.Lock() 40 | defer s.startedLock.Unlock() 41 | 42 | if s.started { 43 | if err := s.dnsServer.Shutdown(); err != nil { 44 | s.log.Debugln(err) 45 | } 46 | s.started = false 47 | } 48 | } 49 | 50 | func (s *MeshnameServer) Start() error { 51 | s.startedLock.Lock() 52 | defer s.startedLock.Unlock() 53 | 54 | if !s.started { 55 | waitStarted := make(chan struct{}) 56 | s.dnsServer = &dns.Server{ 57 | Addr: s.listenAddr, 58 | Net: "udp", 59 | NotifyStartedFunc: func() { close(waitStarted) }, 60 | } 61 | for tld, subnet := range s.networks { 62 | dns.HandleFunc(tld, s.handleMeshnameRequest) 63 | s.log.Debugln("Handling:", tld, subnet) 64 | } 65 | if s.enableMeshIP { 66 | dns.HandleFunc("meship", s.handleMeshIPRequest) 67 | s.log.Debugln("Handling: meship ::/0") 68 | } 69 | 70 | go func() { 71 | if err := s.dnsServer.ListenAndServe(); err != nil { 72 | s.log.Fatalln("MeshnameServer failed to start:", err) 73 | } 74 | }() 75 | <-waitStarted 76 | 77 | s.log.Debugln("MeshnameServer started") 78 | s.started = true 79 | return nil 80 | } else { 81 | return errors.New("MeshnameServer is already started") 82 | } 83 | } 84 | 85 | func (s *MeshnameServer) handleMeshnameRequest(w dns.ResponseWriter, r *dns.Msg) { 86 | var remoteLookups = make(map[string][]dns.Question) 87 | m := new(dns.Msg) 88 | m.SetReply(r) 89 | s.log.Debugln(r.String()) 90 | 91 | for _, q := range r.Question { 92 | labels := dns.SplitDomainName(q.Name) 93 | if len(labels) < 2 { 94 | s.log.Debugln("Error: invalid domain requested") 95 | continue 96 | } 97 | subDomain := labels[len(labels)-2] 98 | 99 | resolvedAddr, err := IPFromDomain(&subDomain) 100 | if err != nil { 101 | s.log.Debugln(err) 102 | continue 103 | } 104 | // check subnet validity 105 | tld := labels[len(labels)-1] 106 | 107 | if subnet, ok := s.networks[tld]; ok && subnet.Contains(resolvedAddr) { 108 | remoteLookups[resolvedAddr.String()] = append(remoteLookups[resolvedAddr.String()], q) 109 | } else { 110 | s.log.Debugln("Error: subnet doesn't match") 111 | } 112 | } 113 | 114 | for remoteServer, questions := range remoteLookups { 115 | rm := new(dns.Msg) 116 | rm.RecursionDesired = true 117 | rm.Question = questions 118 | resp, _, err := s.dnsClient.Exchange(rm, "["+remoteServer+"]:53") // no retries 119 | if err != nil { 120 | s.log.Debugln(err) 121 | continue 122 | } 123 | s.log.Debugln(resp.String()) 124 | m.Answer = append(m.Answer, resp.Answer...) 125 | m.Ns = append(m.Ns, resp.Ns...) 126 | m.Extra = append(m.Extra, resp.Extra...) 127 | } 128 | 129 | if err := w.WriteMsg(m); err != nil { 130 | s.log.Debugln("Error writing response:", err) 131 | } 132 | } 133 | 134 | func (s *MeshnameServer) handleMeshIPRequest(w dns.ResponseWriter, r *dns.Msg) { 135 | m := new(dns.Msg) 136 | m.SetReply(r) 137 | 138 | for _, q := range r.Question { 139 | labels := dns.SplitDomainName(q.Name) 140 | // resolve only 2nd level domains and AAAA type 141 | if len(labels) != 2 || q.Qtype != dns.TypeAAAA || q.Qclass != dns.ClassINET { 142 | s.log.Debugln("Error: invalid resource requested") 143 | continue 144 | } 145 | 146 | if resolvedAddr, err := IPFromDomain(&labels[0]); err == nil { 147 | answer := new(dns.AAAA) 148 | answer.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600} 149 | answer.AAAA = resolvedAddr 150 | 151 | m.Answer = append(m.Answer, answer) 152 | } else { 153 | s.log.Debugln(err) 154 | } 155 | } 156 | 157 | if err := w.WriteMsg(m); err != nil { 158 | s.log.Debugln("Error writing response:", err) 159 | } 160 | } 161 | 162 | func (s *MeshnameServer) IsStarted() bool { 163 | s.startedLock.RLock() 164 | started := s.started 165 | s.startedLock.RUnlock() 166 | return started 167 | } 168 | -------------------------------------------------------------------------------- /protocol.md: -------------------------------------------------------------------------------- 1 | # meshname 2 | 3 | Special-use naming system for self-organized IPv6 mesh networks. 4 | 5 | ## Motivation 6 | 7 | Having a naming system is a common requirement for deploying preexisting 8 | decentralized applications. I.e., applications for e-mail, XMPP and ActivityPub 9 | require domain names for server to server communications. 10 | 11 | Self-organized networks like CJDNS and Yggdrasil Network use public-key 12 | cryptography for IP address allocation. Every network node owns 13 | a globally unique IPv6 address. Binary form of that address can be encoded with 14 | base32 notation for deriving a globally unique name space managed by that node. 15 | 16 | Since there is no need for a global authority or consensus, such a naming system 17 | will reliably work in any network split scenarios. 18 | 19 | ".meshname" is meant to be used by machines, not by humans. A human-readable 20 | naming system would require a lot more engineering effort. 21 | 22 | ## How .meshname domains work 23 | 24 | Each mesh node can manage its own unique name space in "meshname." zone. 25 | The name space is derived from its IPv6 address as follows: 26 | 27 | 1) IPv6 address is converted to its binary form of 16 bytes: 28 | 29 | IPv6Address('200:6fc8:9220:f400:5cc2:305a:4ac6:967e') 30 | 31 | b'\x02\x00o\xc8\x92 \xf4\x00\\\xc20ZJ\xc6\x96~' 32 | 33 | 2) The binary value is encoded to base32 (RFC 4648): 34 | 35 | AIAG7SESED2AAXGCGBNEVRUWPY====== 36 | 37 | 3) Padding symbols "======" are removed from the end of the string. 38 | 39 | The resulting name space managed by '200:6fc8:9220:f400:5cc2:305a:4ac6:967e' 40 | is "aiag7sesed2aaxgcgbnevruwpy.meshname." 41 | 42 | In order to resolve a domain in "xxx.meshname." space, the client derives IPv6 43 | address from the second level domain "xxx" and use it as authoritative DNS server 44 | for that zone. 45 | 46 | "xxx.meshname" name is itself managed by the DNS server derived from "xxx" and 47 | can point to any other IPv6 address. 48 | 49 | ## Resolving process explained 50 | 51 | 1) A client application makes a request to a resolver. 52 | I.e. request AAAA record for "test.aiag7sesed2aaxgcgbnevruwpy.meshname.". 53 | 54 | 2) When a resolver detects "meshname." domain, it extracts the second level 55 | domain from it. In this example, "aiag7sesed2aaxgcgbnevruwpy.meshname.". 56 | 57 | 3) If the resolver is configured as an authoritative server for that 58 | domain, it sends back a response as a regular DNS server would do. 59 | 60 | 4) If it's not, the resolver derives IPv6 address of the corresponding 61 | authoritative DNS server from the second level domain. 62 | For "aiag7sesed2aaxgcgbnevruwpy.meshname." the authoritative server is 63 | "200:6fc8:9220:f400:5cc2:305a:4ac6:967e". 64 | The resolver then relays clients request to a derived server address and 65 | relays a response back to the client. 66 | --------------------------------------------------------------------------------