]> go.fuhry.dev Git - runtime.git/commitdiff
machines/agent: support dns_server_v[46] fields with interface name stand ins
authorDan Fuhry <dan@fuhry.com>
Fri, 13 Sep 2024 01:04:26 +0000 (21:04 -0400)
committerDan Fuhry <dan@fuhry.com>
Fri, 13 Sep 2024 01:04:26 +0000 (21:04 -0400)
Add support for the `dns_server_v4` and `dns_server_v6` domain fields.

If either is a unix network interface name, use the primary IP address of that interface as the address.

machines/templates/dhcpd4.conf.j2
machines/templates/dhcpd6.conf.j2
machines/types.go

index 49ef12cd3b4d0a24f4255114c104946bb23ec87c..67dbc09496157a56982400c0b18a399464edc735 100644 (file)
@@ -24,9 +24,14 @@ on commit {
 {% if (sites[domain.Site.ID()].Name == siteName) and ('dhcp4' in domain.Features) and domain.IPv4Address and domain.DefaultRange.Defined() and domain.DefaultRange.Get().IPv4Start and domain.DefaultRange.Get().IPv4End %}
 # {{ domain.Name }}
 subnet {{ domain.IPv4Address }} netmask {{ domain.IPv4PrefixLength.Mask() }} {
+       {% if domain.IPv4DNSServer.AsIP() %}
+       option domain-name-servers {{ domain.IPv4DNSServer.AsIP().String() }};
+       {% elif routeraddresses.IPv4[domain.IPv4Address] is defined %}
+       option domain-name-servers {{ routeraddresses.IPv4[domain.IPv4Address].Address }};
+       {% endif %}
+
        {% if routeraddresses.IPv4[domain.IPv4Address] is defined %}
        option routers {{ routeraddresses.IPv4[domain.IPv4Address].Address }};
-       option domain-name-servers {{ routeraddresses.IPv4[domain.IPv4Address].Address }};
        {% endif %}
 
        option domain-name "{{ domain.DNSSearch[0] }}";
index 4a669a41e8ccc28f6f6797d6b7a5dbab3bcc2915..683088eeafaba77feac86f4a03f54fe6c42d600a 100644 (file)
@@ -35,11 +35,13 @@ on commit {
        {% if sites[domain.Site.ID()].Name == siteName and 'dhcp6' in domain.Features and domain.IPv6Address is defined and domain.DefaultRange.Defined() and domain.DefaultRange.Get().IPv6Start and domain.DefaultRange.Get().IPv6End %}
                # {{ domain.Name }}
                subnet6 {{ domain.IPv6Address }}/{{ domain.IPv6PrefixLength }} {
-                       {% if routeraddresses.IPv6[domain.IPv6Address] -%}
+                       {% if domain.IPv6DNSServer.AsIP() -%}
+                       option dhcp6.name-servers {{ domain.IPv6DNSServer.AsIP().String() }};
+                       {% elif routeraddresses.IPv6[domain.IPv6Address] -%}
                        option dhcp6.name-servers {{ routeraddresses.IPv6[domain.IPv6Address].Address }};
                        {%- endif %}
-                       # option dhcp6.domain-name {{ domain.DNSSearch[0] }};
-                       # option dhcp6.domain-search {{ domain.DNSSearch[0] }};
+                       # option dhcp6.domain-name "{{ domain.DNSSearch[0] }}";
+                       # option dhcp6.domain-search "{{ domain.DNSSearch[0] }}";
                        option dhcp6.domain-search "{{ domain.DNSSearch | join("\", \"") }}";
 
                        {% for id, range in domain.Ranges -%}
index 2ab2e634c6dedc4427bb8b5c9ca5a2f3b930e95f..b9e19238f2b68f7793e7b0001ab90c546d8c5929 100644 (file)
@@ -8,6 +8,7 @@ import (
        "net"
        "slices"
        "strings"
+       "sync"
        "time"
 
        "github.com/miekg/dns"
@@ -19,12 +20,17 @@ const StaleHostTTL = 1440 * time.Hour
 
 type Timestamp uint64
 type IPString string
+type IPv4OrInterfaceString string
+type IPv6OrInterfaceString string
 type HexEncoded string
 type IPv4PrefixLength uint8
 type IPv6PrefixLength uint8
 
 type ErrUnhandledRecordType string
 
+var netIfaces []net.Interface
+var netIfacesOnce sync.Once
+
 func (e ErrUnhandledRecordType) Error() string {
        return string(e)
 }
@@ -128,6 +134,18 @@ type DNSRecord struct {
        TTL    uint            `json:"ttl"`
 }
 
+func getNetInterfaces() []net.Interface {
+       netIfacesOnce.Do(func() {
+               var err error
+               netIfaces, err = net.Interfaces()
+               if err != nil {
+                       logger.Fatalf("failed to get network interfaces: %v")
+               }
+       })
+
+       return netIfaces
+}
+
 func (r *DNSRecord) ToRR() (dns.RR, error) {
        var msg dns.RR
 
@@ -281,13 +299,15 @@ type Domain struct {
        Site   *Sparse[Site] `json:"site"`
        VlanID uint          `json:"vlan_id"`
 
-       IPv4Address       IPString         `json:"inet4_address"`
-       IPv4PrefixLength  IPv4PrefixLength `json:"inet4_prefixlen"`
-       IPv4RouterAddress IPString         `json:"inet4_routeraddr"`
+       IPv4Address       IPString              `json:"inet4_address"`
+       IPv4PrefixLength  IPv4PrefixLength      `json:"inet4_prefixlen"`
+       IPv4RouterAddress IPString              `json:"inet4_routeraddr"`
+       IPv4DNSServer     IPv4OrInterfaceString `json:"dns_server_v4"`
 
-       IPv6Address       IPString         `json:"inet6_address"`
-       IPv6PrefixLength  IPv6PrefixLength `json:"inet6_prefixlen"`
-       IPv6RouterAddress IPString         `json:"inet6_routeraddr"`
+       IPv6Address       IPString              `json:"inet6_address"`
+       IPv6PrefixLength  IPv6PrefixLength      `json:"inet6_prefixlen"`
+       IPv6RouterAddress IPString              `json:"inet6_routeraddr"`
+       IPv6DNSServer     IPv6OrInterfaceString `json:"dns_server_v6"`
 
        PXEServerIPv4   IPString `json:"pxe4_server"`
        PXEServerIPv6   IPString `json:"pxe6_server"`
@@ -360,6 +380,80 @@ func (ip IPString) AsIP() net.IP {
        return net.ParseIP(string(ip))
 }
 
+func (s IPv4OrInterfaceString) AsIP() net.IP {
+       if string(s) == "" {
+               return nil
+       }
+
+       if ip := net.ParseIP(string(s)); ip.To4() != nil {
+               return ip
+       }
+
+       netifaces := getNetInterfaces()
+       for _, iface := range netifaces {
+               if iface.Name != string(s) {
+                       continue
+               }
+
+               if addrs, err := iface.Addrs(); err == nil {
+                       for _, addr := range addrs {
+                               if addr.Network() != "ip+net" {
+                                       continue
+                               }
+
+                               ipaddr, _, err := net.ParseCIDR(addr.String())
+                               if err != nil {
+                                       continue
+                               }
+
+                               if ipv4 := ipaddr.To4(); ipv4 != nil {
+                                       return ipv4
+                               }
+                       }
+               }
+       }
+
+       return nil
+}
+
+func (s IPv6OrInterfaceString) AsIP() net.IP {
+       if ip := net.ParseIP(string(s)); ip.To4() == nil && ip.To16() != nil {
+               return ip
+       }
+
+       netifaces := getNetInterfaces()
+       for _, iface := range netifaces {
+               if iface.Name != string(s) {
+                       continue
+               }
+
+               if addrs, err := iface.Addrs(); err == nil {
+                       for _, addr := range addrs {
+                               if addr.Network() != "ip+net" {
+                                       continue
+                               }
+
+                               ipaddr, _, err := net.ParseCIDR(addr.String())
+                               if err != nil {
+                                       continue
+                               }
+
+                               if bytes.Equal(ipaddr[0:2], []byte{0xfe, 0x80}) {
+                                       continue
+                               }
+
+                               if ipaddr.To4() != nil {
+                                       continue
+                               }
+
+                               return ipaddr
+                       }
+               }
+       }
+
+       return nil
+}
+
 func (ip IPString) AsInt32() int32 {
        netip := ip.AsIP().To4()
        if netip == nil {