mirror of
https://github.com/jwhited/wgsd.git
synced 2025-11-26 21:35:34 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eaacc000b | ||
|
|
d9845d72b8 | ||
|
|
734608346a | ||
|
|
e068f9d9d2 | ||
|
|
a700f38f3e | ||
|
|
77622af207 | ||
|
|
6f78170fbe | ||
|
|
7d03ee7041 | ||
|
|
a928f85a58 | ||
|
|
016a366d0f | ||
|
|
401ad4ea47 | ||
|
|
fd4b7d8879 | ||
|
|
ce787925be | ||
|
|
dfd8346f69 |
24
.github/workflows/build.yml
vendored
Normal file
24
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.14.x, 1.15.x]
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Build coredns
|
||||
run: go build cmd/coredns/main.go
|
||||
- name: Build wgsd-client
|
||||
run: go build cmd/wgsd-client/main.go
|
||||
49
README.md
49
README.md
@@ -1,9 +1,19 @@
|
||||
# wgsd
|
||||
`wgsd` is a [CoreDNS](https://github.com/coredns/coredns) plugin that serves WireGuard peer information via DNS-SD ([RFC6763](https://tools.ietf.org/html/rfc6763)) semantics. This enables dynamic discovery of WireGuard Endpoint addressing (both IP address and port number) with the added benefit of NAT-to-NAT WireGuard connectivity where [UDP hole punching](https://en.wikipedia.org/wiki/UDP_hole_punching) is supported.
|
||||
`wgsd` is a [CoreDNS](https://github.com/coredns/coredns) plugin that serves WireGuard peer information via DNS-SD ([RFC6763](https://tools.ietf.org/html/rfc6763)) semantics. This enables use cases such as:
|
||||
* Building a mesh of WireGuard peers from a central registry
|
||||
* Dynamic discovery of WireGuard Endpoint addressing (both IP address and port number)
|
||||
* NAT-to-NAT WireGuard connectivity where [UDP hole punching](https://en.wikipedia.org/wiki/UDP_hole_punching) is supported.
|
||||
|
||||
See [this blog post](https://www.jordanwhited.com/posts/wireguard-endpoint-discovery-nat-traversal/) for a deep dive on the underlying techniques and development thought.
|
||||
|
||||
## Installation
|
||||
Binary releases are available [here](https://github.com/jwhited/wgsd/releases).
|
||||
|
||||
Each release contains 2 binaries:
|
||||
* `coredns` - CoreDNS server with all the "internal" plugins + `wgsd`
|
||||
* `wgsd-client` - A sample client
|
||||
|
||||
## Building from source
|
||||
External CoreDNS plugins can be enabled in one of two ways:
|
||||
|
||||
1. [Build with compile-time configuration file](https://coredns.io/2017/07/25/compile-time-enabling-or-disabling-plugins/#build-with-compile-time-configuration-file)
|
||||
@@ -25,9 +35,20 @@ A basic client is available under [cmd/wgsd-client](cmd/wgsd-client).
|
||||
wgsd ZONE DEVICE
|
||||
```
|
||||
|
||||
* `ZONE` is the zone name wgsd should be authoritative for, e.g. example.com.
|
||||
* `DEVICE` is the name of the WireGuard interface, e.g. wg0
|
||||
|
||||
```
|
||||
wgsd ZONE DEVICE {
|
||||
self [ ENDPOINT ] [ ALLOWED-IPS ... ]
|
||||
}
|
||||
```
|
||||
|
||||
* Supplying the `self` option enables serving data about the local WireGuard device in addition to its peers. The optional `ENDPOINT` argument enables setting a custom endpoint in ip:port form. If `ENDPOINT` is omitted wgsd will default to the local IP address for the DNS query and `ListenPort` of the WireGuard device. This can be useful if your host is behind NAT. The optional, variadic `ALLOWED-IPS` argument sets allowed-ips to be served for the local WireGuard device.
|
||||
|
||||
## Querying
|
||||
|
||||
Following RFC6763 this plugin provides a listing of peers via PTR records at the namespace `_wireguard._udp.<zone>`. The target for the PTR records is `<base32PubKey>._wireguard._udp.<zone>` which corresponds to SRV records. SRV targets are of the format `<base32PubKey>.<zone>`. When querying the SRV record for a peer, the target A/AAAA records will be included in the "additional" section of the response. Public keys are represented in Base32 rather than Base64 to allow for their use in node names where they are treated as case-insensitive by the DNS.
|
||||
Following RFC6763 this plugin provides a listing of peers via PTR records at the namespace `_wireguard._udp.<zone>`. The target for the PTR records is of the format `<base32PubKey>._wireguard._udp.<zone>`. This same format is used for the accompanying SRV, A/AAAA, and TXT records. When querying the SRV record for a peer, the target A/AAAA & TXT records will be included in the "additional" section of the response. TXT records include Base64 public key and allowed IPs. Public keys are represented in Base32 rather than Base64 in record names as they are treated as case-insensitive by the DNS.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -35,7 +56,9 @@ This configuration:
|
||||
```
|
||||
$ cat Corefile
|
||||
.:5353 {
|
||||
wgsd example.com. wg0
|
||||
wgsd example.com. wg0 {
|
||||
self 192.0.2.1:51820 10.0.0.254/32
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -65,14 +88,22 @@ Will respond with:
|
||||
$ dig @127.0.0.1 -p 5353 _wireguard._udp.example.com. PTR +noall +answer +additional
|
||||
_wireguard._udp.example.com. 0 IN PTR yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.example.com.
|
||||
_wireguard._udp.example.com. 0 IN PTR wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====._wireguard._udp.example.com.
|
||||
_wireguard._udp.example.com. 0 IN PTR extglt26a3znqnigvb5gvg26cqwblbgynf5re5pukdhx53cqwvda====._wireguard._udp.example.com.
|
||||
$
|
||||
$ dig @127.0.0.1 -p 5353 yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.example.com. SRV +noall +answer +additional
|
||||
yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.example.com. 0 IN SRV 0 0 7777 yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====.example.com.
|
||||
yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====.example.com. 0 IN A 203.0.113.1
|
||||
yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.example.com. 0 IN SRV 0 0 7777 yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.example.com.
|
||||
yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.example.com. 0 IN A 203.0.113.1
|
||||
yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.example.com. 0 IN TXT "txtvers=1" "pub=xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=" "allowed=10.0.0.1/32"
|
||||
$
|
||||
$ dig @127.0.0.1 -p 5353 wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====._wireguard._udp.example.com. SRV +noall +answer +additional
|
||||
wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====._wireguard._udp.example.com. 0 IN SRV 0 0 8888 wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====.example.com.
|
||||
wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====.example.com. 0 IN A 198.51.100.1
|
||||
wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====._wireguard._udp.example.com. 0 IN SRV 0 0 8888 wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====._wireguard._udp.example.com.
|
||||
wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====._wireguard._udp.example.com. 0 IN A 198.51.100.1
|
||||
wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q====._wireguard._udp.example.com. 0 IN TXT "txtvers=1" "pub=syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=" "allowed=10.0.0.2/32"
|
||||
$
|
||||
$ dig @127.0.0.1 -p 5353 extglt26a3znqnigvb5gvg26cqwblbgynf5re5pukdhx53cqwvda====._wireguard._udp.example.com. SRV +noall +answer +additional
|
||||
extglt26a3znqnigvb5gvg26cqwblbgynf5re5pukdhx53cqwvda====._wireguard._udp.example.com. 0 IN SRV 0 0 51820 extglt26a3znqnigvb5gvg26cqwblbgynf5re5pukdhx53cqwvda====._wireguard._udp.example.com.
|
||||
extglt26a3znqnigvb5gvg26cqwblbgynf5re5pukdhx53cqwvda====._wireguard._udp.example.com. 0 IN A 192.0.2.1
|
||||
extglt26a3znqnigvb5gvg26cqwblbgynf5re5pukdhx53cqwvda====._wireguard._udp.example.com. 0 IN TXT "txtvers=1" "pub=JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=" "allowed=10.0.0.254/32"
|
||||
```
|
||||
|
||||
Converting public keys to Base64 with coreutils:
|
||||
@@ -81,9 +112,11 @@ $ echo yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha==== | tr '[:lower:]'
|
||||
xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=
|
||||
$ echo wmrid55v4enhxqx2jstyoyvkicj5pihkb2tr7r42smiu3t5l4i5q==== | tr '[:lower:]' '[:upper:]' | base32 -d | base64
|
||||
syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=
|
||||
$ echo extglt26a3znqnigvb5gvg26cqwblbgynf5re5pukdhx53cqwvda==== | tr '[:lower:]' '[:upper:]' | base32 -d | base64
|
||||
JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=
|
||||
```
|
||||
|
||||
## TODOs
|
||||
- [x] unit tests
|
||||
- [ ] SOA record support
|
||||
- [ ] CI & release binaries
|
||||
- [x] CI & release binaries
|
||||
|
||||
6
go.mod
6
go.mod
@@ -3,8 +3,8 @@ module github.com/jwhited/wgsd
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/caddyserver/caddy v1.0.5
|
||||
github.com/coredns/coredns v1.6.9
|
||||
github.com/miekg/dns v1.1.29
|
||||
github.com/coredns/caddy v1.1.0
|
||||
github.com/coredns/coredns v1.8.0
|
||||
github.com/miekg/dns v1.1.34
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200511024508-91d9787b944f
|
||||
)
|
||||
|
||||
103
setup.go
103
setup.go
@@ -2,8 +2,10 @@ package wgsd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/caddyserver/caddy"
|
||||
"github.com/coredns/caddy"
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/miekg/dns"
|
||||
@@ -11,45 +13,98 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
plugin.Register("wgsd", setup)
|
||||
plugin.Register(pluginName, setup)
|
||||
}
|
||||
|
||||
func parse(c *caddy.Controller) (Zones, error) {
|
||||
z := make(map[string]*Zone)
|
||||
names := []string{}
|
||||
|
||||
for c.Next() {
|
||||
// wgsd zone device
|
||||
args := c.RemainingArgs()
|
||||
if len(args) != 2 {
|
||||
return Zones{}, fmt.Errorf("expected 2 args, got %d", len(args))
|
||||
}
|
||||
zone := &Zone{
|
||||
name: dns.Fqdn(args[0]),
|
||||
device: args[1],
|
||||
}
|
||||
names = append(names, zone.name)
|
||||
_, ok := z[zone.name]
|
||||
if ok {
|
||||
return Zones{}, fmt.Errorf("duplicate zone name %s",
|
||||
zone.name)
|
||||
}
|
||||
z[zone.name] = zone
|
||||
|
||||
for c.NextBlock() {
|
||||
switch c.Val() {
|
||||
case "self":
|
||||
// self [endpoint] [allowed-ips ... ]
|
||||
zone.serveSelf = true
|
||||
args = c.RemainingArgs()
|
||||
if len(args) < 1 {
|
||||
break
|
||||
}
|
||||
|
||||
// assume first arg is endpoint
|
||||
host, portS, err := net.SplitHostPort(args[0])
|
||||
if err == nil {
|
||||
port, err := strconv.Atoi(portS)
|
||||
if err != nil {
|
||||
return Zones{}, fmt.Errorf("error converting self endpoint port: %v", err)
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return Zones{}, fmt.Errorf("invalid self endpoint IP address: %s", host)
|
||||
}
|
||||
zone.selfEndpoint = &net.UDPAddr{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
}
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
zone.selfAllowedIPs = make([]net.IPNet, 0)
|
||||
}
|
||||
for _, allowedIPString := range args {
|
||||
_, prefix, err := net.ParseCIDR(allowedIPString)
|
||||
if err != nil {
|
||||
return Zones{}, fmt.Errorf("invalid self allowed-ip '%s' err: %v", allowedIPString, err)
|
||||
}
|
||||
zone.selfAllowedIPs = append(zone.selfAllowedIPs, *prefix)
|
||||
}
|
||||
default:
|
||||
return Zones{}, c.ArgErr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Zones{Z: z, Names: names}, nil
|
||||
}
|
||||
|
||||
func setup(c *caddy.Controller) error {
|
||||
c.Next() // Ignore "wgsd" and give us the next token.
|
||||
|
||||
// return an error if there is no zone specified
|
||||
if !c.NextArg() {
|
||||
return plugin.Error("wgsd", c.ArgErr())
|
||||
zones, err := parse(c)
|
||||
if err != nil {
|
||||
return plugin.Error(pluginName, err)
|
||||
}
|
||||
zone := dns.Fqdn(c.Val())
|
||||
|
||||
// return an error if there is no device name specified
|
||||
if !c.NextArg() {
|
||||
return plugin.Error("wgsd", c.ArgErr())
|
||||
}
|
||||
device := c.Val()
|
||||
|
||||
// return an error if there are more tokens on this line
|
||||
if c.NextArg() {
|
||||
return plugin.Error("wgsd", c.ArgErr())
|
||||
}
|
||||
|
||||
client, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return plugin.Error("wgsd",
|
||||
return plugin.Error(pluginName,
|
||||
fmt.Errorf("error constructing wgctrl client: %v",
|
||||
err))
|
||||
}
|
||||
c.OnFinalShutdown(client.Close)
|
||||
|
||||
// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
|
||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
||||
return &WGSD{
|
||||
Next: next,
|
||||
Zones: zones,
|
||||
client: client,
|
||||
zone: zone,
|
||||
device: device,
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
137
setup_test.go
137
setup_test.go
@@ -1,40 +1,167 @@
|
||||
package wgsd
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy"
|
||||
"github.com/coredns/caddy"
|
||||
)
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
_, prefix1, _ := net.ParseCIDR("1.1.1.1/32")
|
||||
_, prefix2, _ := net.ParseCIDR("2.2.2.2/32")
|
||||
_, prefix3, _ := net.ParseCIDR("3.3.3.3/32")
|
||||
_, prefix4, _ := net.ParseCIDR("4.4.4.4/32")
|
||||
endpoint1 := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 51820}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expectErr bool
|
||||
shouldErr bool
|
||||
expectedZones Zones
|
||||
}{
|
||||
{
|
||||
"valid input",
|
||||
"wgsd example.com. wg0",
|
||||
false,
|
||||
Zones{
|
||||
Z: map[string]*Zone{
|
||||
"example.com.": {
|
||||
name: "example.com.",
|
||||
device: "wg0",
|
||||
},
|
||||
},
|
||||
Names: []string{"example.com."},
|
||||
},
|
||||
},
|
||||
{
|
||||
"missing token",
|
||||
"wgsd example.com.",
|
||||
true,
|
||||
Zones{},
|
||||
},
|
||||
{
|
||||
"too many tokens",
|
||||
"wgsd example.com. wg0 extra",
|
||||
true,
|
||||
Zones{},
|
||||
},
|
||||
{
|
||||
"valid self allowed-ips",
|
||||
`wgsd example.com. wg0 {
|
||||
self 1.1.1.1/32 2.2.2.2/32
|
||||
}`,
|
||||
false,
|
||||
Zones{
|
||||
Z: map[string]*Zone{
|
||||
"example.com.": {
|
||||
name: "example.com.",
|
||||
device: "wg0",
|
||||
serveSelf: true,
|
||||
selfAllowedIPs: []net.IPNet{*prefix1, *prefix2},
|
||||
},
|
||||
},
|
||||
Names: []string{"example.com."},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid self-allowed-ips",
|
||||
`wgsd example.com. wg0 {
|
||||
self 1.1.11/32 2.2.2.2/32
|
||||
}`,
|
||||
true,
|
||||
Zones{},
|
||||
},
|
||||
{
|
||||
"valid self-endpoint",
|
||||
`wgsd example.com. wg0 {
|
||||
self 127.0.0.1:51820
|
||||
}`,
|
||||
false,
|
||||
Zones{
|
||||
Z: map[string]*Zone{
|
||||
"example.com.": {
|
||||
name: "example.com.",
|
||||
device: "wg0",
|
||||
serveSelf: true,
|
||||
selfEndpoint: endpoint1,
|
||||
},
|
||||
},
|
||||
Names: []string{"example.com."},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid self-endpoint",
|
||||
`wgsd example.com. wg0 {
|
||||
self hostname:51820
|
||||
}`,
|
||||
true,
|
||||
Zones{},
|
||||
},
|
||||
{
|
||||
"multiple blocks",
|
||||
`wgsd example.com. wg0 {
|
||||
self 127.0.0.1:51820 1.1.1.1/32 2.2.2.2/32
|
||||
}
|
||||
wgsd example2.com. wg1 {
|
||||
self 127.0.0.1:51820 3.3.3.3/32 4.4.4.4/32
|
||||
}`,
|
||||
false,
|
||||
Zones{
|
||||
Z: map[string]*Zone{
|
||||
"example.com.": {
|
||||
name: "example.com.",
|
||||
device: "wg0",
|
||||
serveSelf: true,
|
||||
selfEndpoint: endpoint1,
|
||||
selfAllowedIPs: []net.IPNet{*prefix1, *prefix2},
|
||||
},
|
||||
"example2.com.": {
|
||||
name: "example2.com.",
|
||||
device: "wg1",
|
||||
serveSelf: true,
|
||||
selfEndpoint: endpoint1,
|
||||
selfAllowedIPs: []net.IPNet{*prefix3, *prefix4},
|
||||
},
|
||||
},
|
||||
Names: []string{"example.com.", "example2.com."},
|
||||
},
|
||||
},
|
||||
{
|
||||
"all options",
|
||||
`wgsd example.com. wg0 {
|
||||
self 127.0.0.1:51820 1.1.1.1/32 2.2.2.2/32
|
||||
}`,
|
||||
false,
|
||||
Zones{
|
||||
Z: map[string]*Zone{
|
||||
"example.com.": {
|
||||
name: "example.com.",
|
||||
device: "wg0",
|
||||
serveSelf: true,
|
||||
selfEndpoint: endpoint1,
|
||||
selfAllowedIPs: []net.IPNet{*prefix1, *prefix2},
|
||||
},
|
||||
},
|
||||
Names: []string{"example.com."},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := caddy.NewTestController("dns", tc.input)
|
||||
err := setup(c)
|
||||
if (err != nil) != tc.expectErr {
|
||||
t.Fatalf("expectErr: %v, got err=%v", tc.expectErr, err)
|
||||
zones, err := parse(c)
|
||||
|
||||
if err == nil && tc.shouldErr {
|
||||
t.Fatal("expected errors, but got no error")
|
||||
} else if err != nil && !tc.shouldErr {
|
||||
t.Fatalf("expected no errors, but got '%v'", err)
|
||||
} else {
|
||||
if !reflect.DeepEqual(tc.expectedZones, zones) {
|
||||
t.Fatalf("expected %v, got %v", tc.expectedZones, zones)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
271
wgsd.go
271
wgsd.go
@@ -3,6 +3,7 @@ package wgsd
|
||||
import (
|
||||
"context"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@@ -15,19 +16,31 @@ import (
|
||||
)
|
||||
|
||||
// coredns plugin-specific logger
|
||||
var logger = clog.NewWithPlugin("wgsd")
|
||||
var logger = clog.NewWithPlugin(pluginName)
|
||||
|
||||
// WGSD is a CoreDNS plugin that provides Wireguard peer information via DNS-SD
|
||||
const (
|
||||
pluginName = "wgsd"
|
||||
)
|
||||
|
||||
// WGSD is a CoreDNS plugin that provides WireGuard peer information via DNS-SD
|
||||
// semantics. WGSD implements the plugin.Handler interface.
|
||||
type WGSD struct {
|
||||
Next plugin.Handler
|
||||
Zones
|
||||
client wgctrlClient // the client for retrieving WireGuard peer information
|
||||
}
|
||||
|
||||
// the client for retrieving Wireguard peer information
|
||||
client wgctrlClient
|
||||
// the DNS zone we are serving records for
|
||||
zone string
|
||||
// the Wireguard device name, e.g. wg0
|
||||
device string
|
||||
type Zones struct {
|
||||
Z map[string]*Zone // a mapping from zone name to zone data
|
||||
Names []string // all keys from the map z as a string slice
|
||||
}
|
||||
|
||||
type Zone struct {
|
||||
name string // the name of the zone we are authoritative for
|
||||
device string // the WireGuard device name, e.g. wg0
|
||||
serveSelf bool // flag to enable serving data about self
|
||||
selfEndpoint *net.UDPAddr // overrides the self endpoint value
|
||||
selfAllowedIPs []net.IPNet // self allowed IPs
|
||||
}
|
||||
|
||||
type wgctrlClient interface {
|
||||
@@ -35,47 +48,33 @@ type wgctrlClient interface {
|
||||
}
|
||||
|
||||
const (
|
||||
keyLen = 56 // the number of characters in a base32-encoded Wireguard public key
|
||||
keyLen = 56 // the number of characters in a base32-encoded WireGuard public key
|
||||
spPrefix = "_wireguard._udp."
|
||||
serviceInstanceLen = keyLen + len(".") + len(spPrefix)
|
||||
spSubPrefix = "." + spPrefix
|
||||
serviceInstanceLen = keyLen + len(spSubPrefix)
|
||||
)
|
||||
|
||||
func (p *WGSD) ServeDNS(ctx context.Context, w dns.ResponseWriter,
|
||||
r *dns.Msg) (int, error) {
|
||||
// request.Request is a convenience struct we wrap around the msg and
|
||||
// ResponseWriter.
|
||||
state := request.Request{W: w, Req: r}
|
||||
|
||||
// Check if the request is for the zone we are serving. If it doesn't match
|
||||
// we pass the request on to the next plugin.
|
||||
if plugin.Zones([]string{p.zone}).Matches(state.Name()) == "" {
|
||||
return plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
|
||||
}
|
||||
|
||||
// strip zone from name
|
||||
name := strings.TrimSuffix(state.Name(), p.zone)
|
||||
qtype := state.QType()
|
||||
|
||||
logger.Debugf("received query for: %s type: %s", name,
|
||||
dns.TypeToString[qtype])
|
||||
|
||||
device, err := p.client.Device(p.device)
|
||||
if err != nil {
|
||||
return dns.RcodeServerFailure, err
|
||||
}
|
||||
if len(device.Peers) == 0 {
|
||||
return nxDomain(p.zone, w, r)
|
||||
}
|
||||
|
||||
// setup our reply message
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Authoritative = true
|
||||
type handlerFn func(state request.Request, peers []wgtypes.Peer) (int, error)
|
||||
|
||||
func getHandlerFn(queryType uint16, name string) handlerFn {
|
||||
switch {
|
||||
// TODO: handle SOA
|
||||
case name == spPrefix && qtype == dns.TypePTR:
|
||||
for _, peer := range device.Peers {
|
||||
case name == spPrefix && queryType == dns.TypePTR:
|
||||
return handlePTR
|
||||
case len(name) == serviceInstanceLen && queryType == dns.TypeSRV:
|
||||
return handleSRV
|
||||
case len(name) == len(spSubPrefix)+keyLen && (queryType == dns.TypeA ||
|
||||
queryType == dns.TypeAAAA || queryType == dns.TypeTXT):
|
||||
return handleHostOrTXT
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func handlePTR(state request.Request, peers []wgtypes.Peer) (int, error) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(state.Req)
|
||||
m.Authoritative = true
|
||||
for _, peer := range peers {
|
||||
if peer.Endpoint == nil {
|
||||
continue
|
||||
}
|
||||
@@ -88,22 +87,31 @@ func (p *WGSD) ServeDNS(ctx context.Context, w dns.ResponseWriter,
|
||||
},
|
||||
Ptr: fmt.Sprintf("%s.%s%s",
|
||||
strings.ToLower(base32.StdEncoding.EncodeToString(peer.PublicKey[:])),
|
||||
spPrefix, p.zone),
|
||||
spPrefix, state.Zone),
|
||||
})
|
||||
}
|
||||
w.WriteMsg(m) // nolint: errcheck
|
||||
state.W.WriteMsg(m) // nolint: errcheck
|
||||
return dns.RcodeSuccess, nil
|
||||
case len(name) == serviceInstanceLen && qtype == dns.TypeSRV:
|
||||
pubKey := name[:keyLen]
|
||||
for _, peer := range device.Peers {
|
||||
}
|
||||
|
||||
func handleSRV(state request.Request, peers []wgtypes.Peer) (int, error) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(state.Req)
|
||||
m.Authoritative = true
|
||||
pubKey := state.Name()[:keyLen]
|
||||
for _, peer := range peers {
|
||||
if strings.EqualFold(
|
||||
base32.StdEncoding.EncodeToString(peer.PublicKey[:]), pubKey) {
|
||||
endpoint := peer.Endpoint
|
||||
hostRR := getHostRR(pubKey, p.zone, endpoint)
|
||||
if hostRR == nil {
|
||||
return nxDomain(p.zone, w, r)
|
||||
if endpoint == nil {
|
||||
return nxDomain(state)
|
||||
}
|
||||
m.Extra = append(m.Extra, hostRR)
|
||||
hostRR := getHostRR(state.Name(), endpoint)
|
||||
if hostRR == nil {
|
||||
return nxDomain(state)
|
||||
}
|
||||
txtRR := getTXTRR(state.Name(), peer)
|
||||
m.Extra = append(m.Extra, hostRR, txtRR)
|
||||
m.Answer = append(m.Answer, &dns.SRV{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: state.Name(),
|
||||
@@ -114,41 +122,118 @@ func (p *WGSD) ServeDNS(ctx context.Context, w dns.ResponseWriter,
|
||||
Priority: 0,
|
||||
Weight: 0,
|
||||
Port: uint16(endpoint.Port),
|
||||
Target: fmt.Sprintf("%s.%s",
|
||||
strings.ToLower(pubKey), p.zone),
|
||||
Target: state.Name(),
|
||||
})
|
||||
w.WriteMsg(m) // nolint: errcheck
|
||||
state.W.WriteMsg(m) // nolint: errcheck
|
||||
return dns.RcodeSuccess, nil
|
||||
}
|
||||
}
|
||||
return nxDomain(p.zone, w, r)
|
||||
case len(name) == keyLen+1 && (qtype == dns.TypeA ||
|
||||
qtype == dns.TypeAAAA):
|
||||
pubKey := name[:keyLen]
|
||||
for _, peer := range device.Peers {
|
||||
return nxDomain(state)
|
||||
}
|
||||
|
||||
func handleHostOrTXT(state request.Request, peers []wgtypes.Peer) (int, error) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(state.Req)
|
||||
m.Authoritative = true
|
||||
pubKey := state.Name()[:keyLen]
|
||||
for _, peer := range peers {
|
||||
if strings.EqualFold(
|
||||
base32.StdEncoding.EncodeToString(peer.PublicKey[:]), pubKey) {
|
||||
endpoint := peer.Endpoint
|
||||
hostRR := getHostRR(pubKey, p.zone, endpoint)
|
||||
if endpoint == nil {
|
||||
return nxDomain(state)
|
||||
}
|
||||
if state.QType() == dns.TypeA || state.QType() == dns.TypeAAAA {
|
||||
hostRR := getHostRR(state.Name(), endpoint)
|
||||
if hostRR == nil {
|
||||
return nxDomain(p.zone, w, r)
|
||||
return nxDomain(state)
|
||||
}
|
||||
m.Answer = append(m.Answer, hostRR)
|
||||
w.WriteMsg(m) // nolint: errcheck
|
||||
} else {
|
||||
txtRR := getTXTRR(state.Name(), peer)
|
||||
m.Answer = append(m.Answer, txtRR)
|
||||
}
|
||||
state.W.WriteMsg(m) // nolint: errcheck
|
||||
return dns.RcodeSuccess, nil
|
||||
}
|
||||
}
|
||||
return nxDomain(p.zone, w, r)
|
||||
default:
|
||||
return nxDomain(p.zone, w, r)
|
||||
}
|
||||
return nxDomain(state)
|
||||
}
|
||||
|
||||
func getHostRR(pubKey, zone string, endpoint *net.UDPAddr) dns.RR {
|
||||
if endpoint == nil || endpoint.IP == nil {
|
||||
return nil
|
||||
func getSelfPeer(zone *Zone, device *wgtypes.Device, state request.Request) (wgtypes.Peer, error) {
|
||||
self := wgtypes.Peer{
|
||||
PublicKey: device.PublicKey,
|
||||
}
|
||||
name := fmt.Sprintf("%s.%s", strings.ToLower(pubKey), zone)
|
||||
if zone.selfEndpoint != nil {
|
||||
self.Endpoint = zone.selfEndpoint
|
||||
} else {
|
||||
self.Endpoint = &net.UDPAddr{
|
||||
IP: net.ParseIP(state.LocalIP()),
|
||||
Port: device.ListenPort,
|
||||
}
|
||||
}
|
||||
self.AllowedIPs = zone.selfAllowedIPs
|
||||
return self, nil
|
||||
}
|
||||
|
||||
func getPeers(client wgctrlClient, zone *Zone, state request.Request) (
|
||||
[]wgtypes.Peer, error) {
|
||||
peers := make([]wgtypes.Peer, 0)
|
||||
device, err := client.Device(zone.device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peers = append(peers, device.Peers...)
|
||||
if zone.serveSelf {
|
||||
self, err := getSelfPeer(zone, device, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peers = append(peers, self)
|
||||
}
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
func (p *WGSD) ServeDNS(ctx context.Context, w dns.ResponseWriter,
|
||||
r *dns.Msg) (int, error) {
|
||||
// request.Request is a convenience struct we wrap around the msg and
|
||||
// ResponseWriter.
|
||||
state := request.Request{W: w, Req: r}
|
||||
|
||||
// Check if the request is for a zone we are serving. If it doesn't match we
|
||||
// pass the request on to the next plugin.
|
||||
zoneName := plugin.Zones(p.Names).Matches(state.Name())
|
||||
if zoneName == "" {
|
||||
return plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
|
||||
}
|
||||
state.Zone = zoneName
|
||||
|
||||
zone, ok := p.Z[zoneName]
|
||||
if !ok {
|
||||
return dns.RcodeServerFailure, nil
|
||||
}
|
||||
|
||||
// strip zone from name
|
||||
name := strings.TrimSuffix(state.Name(), zoneName)
|
||||
queryType := state.QType()
|
||||
|
||||
logger.Debugf("received query for: %s type: %s", name,
|
||||
dns.TypeToString[queryType])
|
||||
|
||||
handler := getHandlerFn(queryType, name)
|
||||
if handler == nil {
|
||||
return nxDomain(state)
|
||||
}
|
||||
|
||||
peers, err := getPeers(p.client, zone, state)
|
||||
if err != nil {
|
||||
return dns.RcodeServerFailure, err
|
||||
}
|
||||
|
||||
return handler(state, peers)
|
||||
}
|
||||
|
||||
func getHostRR(name string, endpoint *net.UDPAddr) dns.RR {
|
||||
switch {
|
||||
case endpoint.IP.To4() != nil:
|
||||
return &dns.A{
|
||||
@@ -176,13 +261,45 @@ func getHostRR(pubKey, zone string, endpoint *net.UDPAddr) dns.RR {
|
||||
}
|
||||
}
|
||||
|
||||
func nxDomain(zone string, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
const (
|
||||
// txtVersion is the first key/value pair in the TXT RR. Its serves to aid
|
||||
// clients with maintaining backwards compatibility.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc6763#section-6.7
|
||||
txtVersion = 1
|
||||
)
|
||||
|
||||
func getTXTRR(name string, peer wgtypes.Peer) *dns.TXT {
|
||||
var allowedIPs string
|
||||
for i, prefix := range peer.AllowedIPs {
|
||||
if i != 0 {
|
||||
allowedIPs += ","
|
||||
}
|
||||
allowedIPs += prefix.String()
|
||||
}
|
||||
return &dns.TXT{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: name,
|
||||
Rrtype: dns.TypeTXT,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 0,
|
||||
},
|
||||
Txt: []string{
|
||||
fmt.Sprintf("txtvers=%d", txtVersion),
|
||||
fmt.Sprintf("pub=%s",
|
||||
base64.StdEncoding.EncodeToString(peer.PublicKey[:])),
|
||||
fmt.Sprintf("allowed=%s", allowedIPs),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func nxDomain(state request.Request) (int, error) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.SetReply(state.Req)
|
||||
m.Authoritative = true
|
||||
m.Rcode = dns.RcodeNameError
|
||||
m.Ns = []dns.RR{soa(zone)}
|
||||
w.WriteMsg(m) // nolint: errcheck
|
||||
m.Ns = []dns.RR{soa(state.Zone)}
|
||||
state.W.WriteMsg(m) // nolint: errcheck
|
||||
return dns.RcodeSuccess, nil
|
||||
}
|
||||
|
||||
@@ -205,5 +322,5 @@ func soa(zone string) dns.RR {
|
||||
}
|
||||
|
||||
func (p *WGSD) Name() string {
|
||||
return "wgsd"
|
||||
return pluginName
|
||||
}
|
||||
|
||||
172
wgsd_test.go
172
wgsd_test.go
@@ -3,6 +3,7 @@ package wgsd
|
||||
import (
|
||||
"context"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@@ -15,44 +16,94 @@ import (
|
||||
)
|
||||
|
||||
type mockClient struct {
|
||||
peers []wgtypes.Peer
|
||||
devices map[string]*wgtypes.Device
|
||||
}
|
||||
|
||||
func (m *mockClient) Device(d string) (*wgtypes.Device, error) {
|
||||
return &wgtypes.Device{
|
||||
Name: d,
|
||||
Peers: m.peers,
|
||||
}, nil
|
||||
return m.devices[d], nil
|
||||
}
|
||||
|
||||
func constructAllowedIPs(t *testing.T, prefixes []string) ([]net.IPNet, string) {
|
||||
var allowed []net.IPNet
|
||||
var allowedString string
|
||||
for i, s := range prefixes {
|
||||
_, prefix, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing cidr: %v", err)
|
||||
}
|
||||
allowed = append(allowed, *prefix)
|
||||
if i != 0 {
|
||||
allowedString += ","
|
||||
}
|
||||
allowedString += prefix.String()
|
||||
}
|
||||
return allowed, allowedString
|
||||
}
|
||||
|
||||
func TestWGSD(t *testing.T) {
|
||||
selfKey := [32]byte{}
|
||||
selfKey[0] = 99
|
||||
selfb32 := strings.ToLower(base32.StdEncoding.EncodeToString(selfKey[:]))
|
||||
selfb64 := base64.StdEncoding.EncodeToString(selfKey[:])
|
||||
selfAllowed, selfAllowedString := constructAllowedIPs(t, []string{"10.0.0.99/32", "10.0.0.100/32"})
|
||||
key1 := [32]byte{}
|
||||
key1[0] = 1
|
||||
peer1Allowed, peer1AllowedString := constructAllowedIPs(t, []string{"10.0.0.1/32", "10.0.0.2/32"})
|
||||
peer1 := wgtypes.Peer{
|
||||
Endpoint: &net.UDPAddr{
|
||||
IP: net.ParseIP("1.1.1.1"),
|
||||
Port: 1,
|
||||
},
|
||||
PublicKey: key1,
|
||||
AllowedIPs: peer1Allowed,
|
||||
}
|
||||
peer1b32 := strings.ToLower(base32.StdEncoding.EncodeToString(peer1.PublicKey[:]))
|
||||
peer1b64 := base64.StdEncoding.EncodeToString(peer1.PublicKey[:])
|
||||
key2 := [32]byte{}
|
||||
key2[0] = 2
|
||||
peer2Allowed, peer2AllowedString := constructAllowedIPs(t, []string{"10.0.0.3/32", "10.0.0.4/32"})
|
||||
peer2 := wgtypes.Peer{
|
||||
Endpoint: &net.UDPAddr{
|
||||
IP: net.ParseIP("::2"),
|
||||
Port: 2,
|
||||
},
|
||||
PublicKey: key2,
|
||||
AllowedIPs: peer2Allowed,
|
||||
}
|
||||
peer2b32 := strings.ToLower(base32.StdEncoding.EncodeToString(peer2.PublicKey[:]))
|
||||
peer2b64 := base64.StdEncoding.EncodeToString(peer2.PublicKey[:])
|
||||
key3 := [32]byte{}
|
||||
key3[0] = 3
|
||||
peer3Allowed, _ := constructAllowedIPs(t, []string{"10.0.0.5/32", "10.0.0.6/32"})
|
||||
peer3 := wgtypes.Peer{
|
||||
Endpoint: nil,
|
||||
PublicKey: key3,
|
||||
AllowedIPs: peer3Allowed,
|
||||
}
|
||||
peer3b32 := strings.ToLower(base32.StdEncoding.EncodeToString(peer3.PublicKey[:]))
|
||||
p := &WGSD{
|
||||
Next: test.ErrorHandler(),
|
||||
client: &mockClient{
|
||||
peers: []wgtypes.Peer{peer1, peer2},
|
||||
},
|
||||
zone: "example.com.",
|
||||
Zones: Zones{
|
||||
Names: []string{"example.com."},
|
||||
Z: map[string]*Zone{
|
||||
"example.com.": {
|
||||
name: "example.com.",
|
||||
device: "wg0",
|
||||
serveSelf: true,
|
||||
selfAllowedIPs: selfAllowed,
|
||||
},
|
||||
},
|
||||
},
|
||||
client: &mockClient{
|
||||
devices: map[string]*wgtypes.Device{
|
||||
"wg0": {
|
||||
Name: "wg0",
|
||||
PublicKey: selfKey,
|
||||
ListenPort: 51820,
|
||||
Peers: []wgtypes.Peer{peer1, peer2, peer3},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []test.Case{
|
||||
@@ -63,6 +114,19 @@ func TestWGSD(t *testing.T) {
|
||||
Answer: []dns.RR{
|
||||
test.PTR(fmt.Sprintf("_wireguard._udp.example.com. 0 IN PTR %s._wireguard._udp.example.com.", peer1b32)),
|
||||
test.PTR(fmt.Sprintf("_wireguard._udp.example.com. 0 IN PTR %s._wireguard._udp.example.com.", peer2b32)),
|
||||
test.PTR(fmt.Sprintf("_wireguard._udp.example.com. 0 IN PTR %s._wireguard._udp.example.com.", selfb32)),
|
||||
},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", selfb32),
|
||||
Qtype: dns.TypeSRV,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.SRV(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN SRV 0 0 51820 %s._wireguard._udp.example.com.", selfb32, selfb32)),
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
test.A(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN A %s", selfb32, "127.0.0.1")),
|
||||
test.TXT(fmt.Sprintf(`%s._wireguard._udp.example.com. 0 IN TXT "txtvers=%d" "pub=%s" "allowed=%s"`, selfb32, txtVersion, selfb64, selfAllowedString)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,10 +134,11 @@ func TestWGSD(t *testing.T) {
|
||||
Qtype: dns.TypeSRV,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.SRV(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN SRV 0 0 1 %s.example.com.", peer1b32, peer1b32)),
|
||||
test.SRV(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN SRV 0 0 1 %s._wireguard._udp.example.com.", peer1b32, peer1b32)),
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
test.A(fmt.Sprintf("%s.example.com. 0 IN A %s", peer1b32, peer1.Endpoint.IP.String())),
|
||||
test.A(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN A %s", peer1b32, peer1.Endpoint.IP.String())),
|
||||
test.TXT(fmt.Sprintf(`%s._wireguard._udp.example.com. 0 IN TXT "txtvers=%d" "pub=%s" "allowed=%s"`, peer1b32, txtVersion, peer1b64, peer1AllowedString)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -81,26 +146,59 @@ func TestWGSD(t *testing.T) {
|
||||
Qtype: dns.TypeSRV,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.SRV(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN SRV 0 0 2 %s.example.com.", peer2b32, peer2b32)),
|
||||
test.SRV(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN SRV 0 0 2 %s._wireguard._udp.example.com.", peer2b32, peer2b32)),
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
test.AAAA(fmt.Sprintf("%s.example.com. 0 IN AAAA %s", peer2b32, peer2.Endpoint.IP.String())),
|
||||
test.AAAA(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN AAAA %s", peer2b32, peer2.Endpoint.IP.String())),
|
||||
test.TXT(fmt.Sprintf(`%s._wireguard._udp.example.com. 0 IN TXT "txtvers=%d" "pub=%s" "allowed=%s"`, peer2b32, txtVersion, peer2b64, peer2AllowedString)),
|
||||
},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s.example.com.", peer1b32),
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", selfb32),
|
||||
Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.A(fmt.Sprintf("%s.example.com. 0 IN A %s", peer1b32, peer1.Endpoint.IP.String())),
|
||||
test.A(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN A %s", selfb32, "127.0.0.1")),
|
||||
},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s.example.com.", peer2b32),
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", peer1b32),
|
||||
Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.A(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN A %s", peer1b32, peer1.Endpoint.IP.String())),
|
||||
},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", peer2b32),
|
||||
Qtype: dns.TypeAAAA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.AAAA(fmt.Sprintf("%s.example.com. 0 IN AAAA %s", peer2b32, peer2.Endpoint.IP.String())),
|
||||
test.AAAA(fmt.Sprintf("%s._wireguard._udp.example.com. 0 IN AAAA %s", peer2b32, peer2.Endpoint.IP.String())),
|
||||
},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", selfb32),
|
||||
Qtype: dns.TypeTXT,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.TXT(fmt.Sprintf(`%s._wireguard._udp.example.com. 0 IN TXT "txtvers=%d" "pub=%s" "allowed=%s"`, selfb32, txtVersion, selfb64, selfAllowedString)),
|
||||
},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", peer1b32),
|
||||
Qtype: dns.TypeTXT,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.TXT(fmt.Sprintf(`%s._wireguard._udp.example.com. 0 IN TXT "txtvers=%d" "pub=%s" "allowed=%s"`, peer1b32, txtVersion, peer1b64, peer1AllowedString)),
|
||||
},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", peer2b32),
|
||||
Qtype: dns.TypeTXT,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.TXT(fmt.Sprintf(`%s._wireguard._udp.example.com. 0 IN TXT "txtvers=%d" "pub=%s" "allowed=%s"`, peer2b32, txtVersion, peer2b64, peer2AllowedString)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -116,6 +214,46 @@ func TestWGSD(t *testing.T) {
|
||||
Qtype: dns.TypeAAAA,
|
||||
Rcode: dns.RcodeServerFailure,
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", peer3b32),
|
||||
Qtype: dns.TypeSRV,
|
||||
Rcode: dns.RcodeNameError,
|
||||
Ns: []dns.RR{
|
||||
test.SOA(soa("example.com.").String()),
|
||||
},
|
||||
Answer: []dns.RR{},
|
||||
Extra: []dns.RR{},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", peer3b32),
|
||||
Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeNameError,
|
||||
Ns: []dns.RR{
|
||||
test.SOA(soa("example.com.").String()),
|
||||
},
|
||||
Answer: []dns.RR{},
|
||||
Extra: []dns.RR{},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", peer3b32),
|
||||
Qtype: dns.TypeAAAA,
|
||||
Rcode: dns.RcodeNameError,
|
||||
Ns: []dns.RR{
|
||||
test.SOA(soa("example.com.").String()),
|
||||
},
|
||||
Answer: []dns.RR{},
|
||||
Extra: []dns.RR{},
|
||||
},
|
||||
{
|
||||
Qname: fmt.Sprintf("%s._wireguard._udp.example.com.", peer3b32),
|
||||
Qtype: dns.TypeTXT,
|
||||
Rcode: dns.RcodeNameError,
|
||||
Ns: []dns.RR{
|
||||
test.SOA(soa("example.com.").String()),
|
||||
},
|
||||
Answer: []dns.RR{},
|
||||
Extra: []dns.RR{},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%s %s", tc.Qname, dns.TypeToString[tc.Qtype]), func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user