pkg/mesh,pkg/wireguard: allow DNS name endpoints
This commit allows DNS names to be used when specifying the endpoint for a node in the WireGuard mesh. This is useful in many scenarios, in particular when operating an IoT device whose public IP is dynamic. This change allows the administrator to use a dynamic DNS name in the node's endpoint. One of the side-effects of this change is that the WireGuard port can now be specified individually for each node in the mesh, if the administrator wishes to do so. *Note*: this commit introduces a breaking change; the `force-external-ip` node annotation has been removed; its functionality has been ported over to the `force-endpoint` annotation. This annotation is documented in the annotations.md file. The expected content of this annotation is no longer a CIDR but rather a host:port. The host can be either a DNS name or an IP. Signed-off-by: Lucas Servén Marín <lserven@gmail.com>
This commit is contained in:
@@ -22,6 +22,8 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
type section string
|
||||
@@ -75,10 +77,34 @@ func (p *Peer) DeduplicateIPs() {
|
||||
|
||||
// Endpoint represents an `endpoint` key of a `peer` section.
|
||||
type Endpoint struct {
|
||||
IP net.IP
|
||||
DNSOrIP
|
||||
Port uint32
|
||||
}
|
||||
|
||||
// String prints the string representation of the endpoint.
|
||||
func (e *Endpoint) String() string {
|
||||
dnsOrIP := e.DNSOrIP.String()
|
||||
if e.IP != nil && len(e.IP) == net.IPv6len {
|
||||
dnsOrIP = "[" + dnsOrIP + "]"
|
||||
}
|
||||
return dnsOrIP + ":" + strconv.FormatUint(uint64(e.Port), 10)
|
||||
}
|
||||
|
||||
// DNSOrIP represents either a DNS name or an IP address.
|
||||
// IPs, as they are more specific, are preferred.
|
||||
type DNSOrIP struct {
|
||||
DNS string
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
// String prints the string representation of the struct.
|
||||
func (d DNSOrIP) String() string {
|
||||
if d.IP != nil {
|
||||
return d.IP.String()
|
||||
}
|
||||
return d.DNS
|
||||
}
|
||||
|
||||
// Parse parses a given WireGuard configuration file and produces a Conf struct.
|
||||
func Parse(buf []byte) *Conf {
|
||||
var (
|
||||
@@ -160,25 +186,30 @@ func Parse(buf []byte) *Conf {
|
||||
case endpointKey:
|
||||
// Reuse string slice.
|
||||
kv = strings.Split(v, ":")
|
||||
if len(kv) != 2 {
|
||||
if len(kv) < 2 {
|
||||
continue
|
||||
}
|
||||
ip = net.ParseIP(kv[0])
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
port, err = strconv.ParseUint(kv[1], 10, 32)
|
||||
port, err = strconv.ParseUint(kv[len(kv)-1], 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ip4 = ip.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
d := DNSOrIP{}
|
||||
ip = net.ParseIP(strings.Trim(strings.Join(kv[:len(kv)-1], ":"), "[]"))
|
||||
if ip == nil {
|
||||
if len(validation.IsDNS1123Subdomain(kv[0])) != 0 {
|
||||
continue
|
||||
}
|
||||
d.DNS = kv[0]
|
||||
} else {
|
||||
ip = ip.To16()
|
||||
if ip4 = ip.To4(); ip4 != nil {
|
||||
d.IP = ip4
|
||||
} else {
|
||||
d.IP = ip.To16()
|
||||
}
|
||||
}
|
||||
peer.Endpoint = &Endpoint{
|
||||
IP: ip,
|
||||
Port: uint32(port),
|
||||
DNSOrIP: d,
|
||||
Port: uint32(port),
|
||||
}
|
||||
case persistentKeepaliveKey:
|
||||
i, err = strconv.Atoi(v)
|
||||
@@ -242,7 +273,7 @@ func (c *Conf) Bytes() ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Equal checks if two WireGuare configurations are equivalent.
|
||||
// Equal checks if two WireGuard configurations are equivalent.
|
||||
func (c *Conf) Equal(b *Conf) bool {
|
||||
if (c.Interface == nil) != (b.Interface == nil) {
|
||||
return false
|
||||
@@ -272,7 +303,15 @@ func (c *Conf) Equal(b *Conf) bool {
|
||||
return false
|
||||
}
|
||||
if c.Peers[i].Endpoint != nil {
|
||||
if !c.Peers[i].Endpoint.IP.Equal(b.Peers[i].Endpoint.IP) || c.Peers[i].Endpoint.Port != b.Peers[i].Endpoint.Port {
|
||||
if c.Peers[i].Endpoint.Port != b.Peers[i].Endpoint.Port {
|
||||
return false
|
||||
}
|
||||
// IPs take priority, so check them first.
|
||||
if !c.Peers[i].Endpoint.IP.Equal(b.Peers[i].Endpoint.IP) {
|
||||
return false
|
||||
}
|
||||
// Only check the DNS name if the IP is empty.
|
||||
if c.Peers[i].Endpoint.IP == nil && c.Peers[i].Endpoint.DNS != b.Peers[i].Endpoint.DNS {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -352,13 +391,7 @@ func writeEndpoint(buf *bytes.Buffer, e *Endpoint) error {
|
||||
if err = writeKey(buf, endpointKey); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.WriteString(e.IP.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = buf.WriteByte(':'); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.WriteString(strconv.FormatUint(uint64(e.Port), 10)); err != nil {
|
||||
if _, err = buf.WriteString(e.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
return buf.WriteByte('\n')
|
||||
|
@@ -95,6 +95,28 @@ func TestCompareConf(t *testing.T) {
|
||||
`),
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
name: "different value",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key2
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
name: "section order",
|
||||
a: []byte(`[Interface]
|
||||
|
Reference in New Issue
Block a user