Create a package

This commit is contained in:
George 2020-02-09 04:25:38 -05:00
parent 1950e95d0f
commit 2d9042a983
7 changed files with 244 additions and 208 deletions

View File

@ -10,16 +10,15 @@ make
```
2) Generate the default config for your host
```
./meshnamed genconf 200:6fc8:9220:f400:5cc2:305a:4ac6:967e | tee /tmp/meshnamed.conf
./meshnamed -genconf 200:6fc8:9220:f400:5cc2:305a:4ac6:967e | tee /tmp/meshnamed.conf
```
3) Optionally, set the configuration with environment variables
3) Run the daemon
```
export LISTEN_ADDR=[::1]:53535
export MESH_SUBNET=200::/7
./meshnamed -useconffile /tmp/meshnamed.conf
```
4) Run the daemon
4) Optionally, set the configuration flags
```
./meshnamed daemon /tmp/meshnamed.conf
./meshnamed -listenaddr [::1]:53535 -meshsubnet 200::/7 -debug -useconffile /tmp/meshnamed.conf
```
Add new DNS records to configuration file and restart the daemon to apply settings.
A record can be of any valid string form parsed by [miekg/dns](https://godoc.org/github.com/miekg/dns#NewRR).
@ -61,9 +60,9 @@ In this example, meshnamed is configured as authoritative for two domain zones:
## Using meshnamed as a standalone DNS server
Set environment varialbe to listen on all interfaces and a standard DNS server port
Set the flag to listen on all interfaces and a standard DNS server port
export LISTEN_ADDR=[::]:53
./meshnamed -listenaddr [::]:53 -useconffile /tmp/meshnamed.conf
Allow incoming connections to port 53/UDP in firewall settings.
Run as root and allow incoming connections to port 53/UDP in firewall settings.

View File

@ -1,47 +1,17 @@
package main
import (
"encoding/base32"
"fmt"
"net"
"strings"
"os"
"errors"
"github.com/zhoreeq/meshname/src/meshname"
)
var domainZone = ".meshname"
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) < 2 || strings.HasSuffix(domainZone, target) {
return "", errors.New("Invalid domain")
}
subDomain := labels[len(labels) - 2]
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() {
domainZone := strings.TrimSuffix(meshname.DomainZone, ".")
usage := "Usage:\n\nmeshname lookup DOMAIN\nmeshname reverse_lookup IP"
if len(os.Args) != 3 {
fmt.Println(usage)
@ -53,20 +23,32 @@ func main() {
switch action {
case "lookup":
result, err := lookup(target)
labels := strings.Split(target, ".")
if len(labels) < 2 || !strings.HasSuffix(target, domainZone) {
fmt.Println("Invalid domain")
return
}
subDomain := labels[len(labels) - 2]
if len(subDomain) != 26 {
fmt.Println("Invalid subdomain length")
return
}
result, err := meshname.IPFromDomain(subDomain)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(result)
fmt.Println(result.String())
return
case "reverse_lookup":
result, err := reverse_lookup(target)
if err != nil {
fmt.Println("Error:", err)
ip := net.ParseIP(target)
if ip == nil {
fmt.Println("Invalid IP address")
return
}
fmt.Println(result)
result := meshname.DomainFromIP(ip)
fmt.Println(result + "." + domainZone)
return
default:
fmt.Println(usage)

View File

@ -1,175 +1,55 @@
package main
import (
"encoding/base32"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"os"
"strings"
"fmt"
"flag"
"github.com/miekg/dns"
"github.com/gologme/log"
"github.com/zhoreeq/meshname/src/meshname"
)
const domainZone = "meshname."
var _, validSubnet, _ = net.ParseCIDR("::/0")
var zoneConfigPath = ""
var zoneConfig = map[string][]dns.RR{}
var dnsClient = new(dns.Client)
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)
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 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 _, q := range r.Question {
labels := dns.SplitDomainName(q.Name)
if len(labels) < 2 {
continue
}
subDomain := labels[len(labels)-2]
resolvedAddr, err := lookup(subDomain)
if err != nil {
continue
}
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() {
helpMessage := "Usage:\nmeshnamed genconf [IP] > /etc/meshnamed.conf\nmeshnamed daemon /etc/meshnamed.conf"
if len(os.Args) < 2 {
fmt.Println(helpMessage)
return
genconf := flag.String("genconf", "", "generate a new config for IP address")
useconffile := flag.String("useconffile", "", "run daemon with a config file")
listenAddr := flag.String("listenaddr", "[::1]:53535", "address to listen on")
meshSubnetStr := flag.String("meshsubnet", "::/0", "valid IPv6 address space")
debug := flag.Bool("debug", false, "enable debug logging")
flag.Parse()
var logger *log.Logger
logger = log.New(os.Stdout, "", log.Flags())
logger.EnableLevel("error")
logger.EnableLevel("warn")
logger.EnableLevel("info")
if *debug {
logger.EnableLevel("debug")
}
action := os.Args[1]
if action == "genconf" && len(os.Args) == 3 {
confString, err := genConf(os.Args[2])
switch {
case *genconf != "":
confString, err := meshname.GenConf(*genconf)
if err != nil {
fmt.Println(err)
logger.Errorln(err)
} else {
fmt.Println(confString)
}
} else if action == "daemon" {
if len(os.Args) == 3 {
zoneConfigPath = os.Args[2]
loadConfig()
case *useconffile != "":
s := new(meshname.MeshnameServer)
_, validSubnet, err := net.ParseCIDR(*meshSubnetStr)
if err != nil {
logger.Errorln(err)
os.Exit(1)
}
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)
s.Init(logger, meshname.MeshnameOptions{ListenAddr: *listenAddr, ConfigPath: *useconffile, ValidSubnet: validSubnet})
s.Start()
default:
flag.PrintDefaults()
}
}

5
go.mod
View File

@ -2,4 +2,7 @@ module github.com/zhoreeq/meshname
go 1.13
require github.com/miekg/dns v1.1.27
require (
github.com/gologme/log v1.2.0
github.com/miekg/dns v1.1.27
)

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c=
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

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

172
src/meshname/server.go Normal file
View File

@ -0,0 +1,172 @@
package meshname
import (
"encoding/base32"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"os"
"strings"
"github.com/gologme/log"
"github.com/miekg/dns"
)
const DomainZone = "meshname."
func DomainFromIP(target net.IP) string {
return strings.ToLower(base32.StdEncoding.EncodeToString(target)[0:26])
}
func IPFromDomain(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")
}
return ipAddr, nil
}
func GenConf(target string) (string, error) {
ip := net.ParseIP(target)
if ip == nil {
return "", errors.New("Invalid IP address")
}
zone := DomainFromIP(ip)
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
}
type MeshnameServer struct {
validSubnet *net.IPNet
log *log.Logger
listenAddr, zoneConfigPath string
zoneConfig map[string][]dns.RR
dnsClient *dns.Client
}
type MeshnameOptions struct {
ListenAddr, ConfigPath string
ValidSubnet *net.IPNet
}
func (s *MeshnameServer) Init(log *log.Logger, options interface{}) {
mnoptions := options.(MeshnameOptions)
s.log = log
s.listenAddr = mnoptions.ListenAddr
s.validSubnet = mnoptions.ValidSubnet
s.zoneConfigPath = mnoptions.ConfigPath
s.zoneConfig = make(map[string][]dns.RR)
if s.dnsClient == nil {
s.dnsClient = new(dns.Client)
s.dnsClient.Timeout = 5000000000 // increased 5 seconds timeout
}
s.LoadConfig()
}
func (s *MeshnameServer) LoadConfig() {
if s.zoneConfigPath == "" {
return
}
for k := range s.zoneConfig {
delete(s.zoneConfig, k)
}
reader, err := os.Open(s.zoneConfigPath)
if err != nil {
s.log.Errorln("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 {
s.log.Errorln("Syntax error in config:", err)
return
}
for _, v := range m.Records {
rr, err := dns.NewRR(v)
if err != nil {
s.log.Errorln("Invalid DNS record:", v)
continue
}
s.zoneConfig[m.Domain] = append(s.zoneConfig[m.Domain], rr)
}
}
s.log.Infoln("Meshname config loaded:", s.zoneConfigPath)
}
func (s *MeshnameServer) Start() {
dnsServer := &dns.Server{Addr: s.listenAddr, Net: "udp"}
s.log.Infoln("Started meshnamed on:", s.listenAddr)
dns.HandleFunc(DomainZone, s.handleRequest)
dnsServer.ListenAndServe()
}
func (s *MeshnameServer) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
var remoteLookups = make(map[string][]dns.Question)
m := new(dns.Msg)
m.SetReply(r)
for _, q := range r.Question {
labels := dns.SplitDomainName(q.Name)
if len(labels) < 2 {
s.log.Debugln("Error: invalid domain requested")
continue
}
subDomain := labels[len(labels)-2]
resolvedAddr, err := IPFromDomain(subDomain)
if err != nil {
s.log.Debugln(err)
continue
}
if !s.validSubnet.Contains(resolvedAddr) {
s.log.Debugln("Error: subnet doesn't match")
continue
}
if records, ok := s.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:") {
// TODO prefix whitelists ?
// 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 := s.dnsClient.Exchange(rm, "["+remoteServer+"]:53") // no retries
if err != nil {
s.log.Debugln(err)
continue
}
m.Answer = append(m.Answer, resp.Answer...)
}
w.WriteMsg(m)
}