From 4f58845a9e9b43d79e8ed6c3c6c3f63273a2e9e1 Mon Sep 17 00:00:00 2001 From: Timothy Middelkoop Date: Thu, 27 Nov 2025 01:56:18 +0000 Subject: [PATCH] Add IPv6 support for Dnsmasq and NetworkManager Signed-off-by: Timothy Middelkoop --- CHANGELOG.md | 12 +++- internal/app/wwctl/node/add/main_test.go | 2 +- internal/pkg/config/dhcp.go | 4 +- internal/pkg/config/root.go | 45 ++++++++++---- internal/pkg/config/root_test.go | 54 ++++++++++++++++ internal/pkg/node/datastructure.go | 25 ++++---- internal/pkg/node/fields_test.go | 10 +-- internal/pkg/node/methods.go | 23 +++++++ internal/pkg/node/methods_test.go | 53 ++++++++++++++++ internal/pkg/overlay/datastructure.go | 16 ++--- internal/pkg/upgrade/node.go | 48 +++++++++----- internal/pkg/upgrade/node_test.go | 25 ++++++++ internal/pkg/warewulfd/provision.go | 8 +-- internal/pkg/warewulfd/provision_test.go | 2 +- .../internal/networkmanager_test.go | 62 ++++++++++++------- .../system-connections/ww4-managed.ww | 12 ++-- overlays/debug/internal/debug_test.go | 32 ++++++++-- overlays/debug/internal/nodes.conf | 6 ++ overlays/debug/rootfs/tstruct.md.ww | 11 ++-- overlays/host/internal/host_test.go | 23 ++++++- overlays/host/internal/nodes.conf | 6 +- overlays/host/internal/warewulf.conf-ipv6 | 3 +- .../rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww | 2 + .../rootfs/etc/dnsmasq.d/ww4-hosts6.conf.ww | 32 +++++++++- .../rootfs/etc/dnsmasq.d/ww4-listen.conf.ww | 7 +++ 25 files changed, 417 insertions(+), 106 deletions(-) create mode 100644 overlays/host/rootfs/etc/dnsmasq.d/ww4-listen.conf.ww diff --git a/CHANGELOG.md b/CHANGELOG.md index 989a27d8..fa17f4c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,8 +21,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Absolute paths specified with `{{ file }}` in an overlay now write to that absolute path. - Use opencontainers/selinux to manage SELinux in wwclient. - Replace unused/unneeded IPv6net with IpCIDR6 to align with IPv4 -- Add IPv6 DHCP range -- Add IPv6 functionality for dnsmasq +- Improved IPv6 support + - Add PrefixLen6 for prefix length, Gateway6, and IPv6 DHCP range + - IPv6 support for Dnsmasq and NetworkManager + - Rename Netdev ip6addr to ipaddr6 for consistency + - Set `addr-gen-mode=eui64` +- Set dnsmasq to listen to the Warewulf interface to prevent to binding to localhost:53 + +### Removed + +- Remove unused Netdev `Prefix` field. ### Fixed diff --git a/internal/app/wwctl/node/add/main_test.go b/internal/app/wwctl/node/add/main_test.go index b0734826..96f1134f 100644 --- a/internal/app/wwctl/node/add/main_test.go +++ b/internal/app/wwctl/node/add/main_test.go @@ -114,7 +114,7 @@ nodes: n01: network devices: default: - ip6addr: fdaa::1 + ipaddr6: fdaa::1 `}, {name: "single node with ipaddr", args: []string{"--ipaddr=10.0.0.1", "n01"}, diff --git a/internal/pkg/config/dhcp.go b/internal/pkg/config/dhcp.go index 851a87a9..cfaec439 100644 --- a/internal/pkg/config/dhcp.go +++ b/internal/pkg/config/dhcp.go @@ -11,8 +11,8 @@ type DHCPConf struct { Template string `yaml:"template,omitempty" default:"default"` RangeStart string `yaml:"range start,omitempty"` RangeEnd string `yaml:"range end,omitempty"` - Range6Start string `yaml:"range6 start,omitempty"` - Range6End string `yaml:"range6 end,omitempty"` + Range6Start string `yaml:"range6 start,omitempty"` + Range6End string `yaml:"range6 end,omitempty"` SystemdName string `yaml:"systemd name,omitempty" default:"dhcpd"` } diff --git a/internal/pkg/config/root.go b/internal/pkg/config/root.go index 5a2d2eda..142d6914 100644 --- a/internal/pkg/config/root.go +++ b/internal/pkg/config/root.go @@ -8,10 +8,10 @@ package config import ( "bytes" - "fmt" "net" "os" "reflect" + "strconv" "github.com/creasty/defaults" "github.com/warewulf/warewulf/internal/pkg/wwlog" @@ -26,11 +26,11 @@ var cachedConf WarewulfYaml type WarewulfYaml struct { Comment string `yaml:"comment,omitempty"` Ipaddr string `yaml:"ipaddr,omitempty"` - Ipaddr6 string `yaml:"ipaddr6,omitempty"` Netmask string `yaml:"netmask,omitempty"` Network string `yaml:"network,omitempty"` - IpCIDR6 string `yaml:"-"` Fqdn string `yaml:"fqdn,omitempty"` + Ipaddr6 string `yaml:"ipaddr6,omitempty"` + PrefixLen6 string `yaml:"prefixlen6,omitempty"` Warewulf *WarewulfConf `yaml:"warewulf,omitempty"` API *APIConf `yaml:"api,omitempty"` DHCP *DHCPConf `yaml:"dhcp,omitempty"` @@ -140,18 +140,19 @@ func (conf *WarewulfYaml) Parse(data []byte, autodetect bool) error { } } - if conf.Ipaddr6 != "" { - if ip, _, err := net.ParseCIDR(conf.Ipaddr6); err == nil { - if ip.To4() != nil { - return fmt.Errorf("invalid ipv6 address: ip address is ipv4: %s", conf.Ipaddr6) - } - conf.IpCIDR6 = conf.Ipaddr6 + if ip := net.ParseIP(conf.Ipaddr6); ip != nil { + if prefix, err := strconv.Atoi(conf.PrefixLen6); err == nil { conf.Ipaddr6 = ip.String() - } else { - return fmt.Errorf("invalid ipv6 address: must use CIDR notation: %s", conf.Ipaddr6) + conf.PrefixLen6 = strconv.Itoa(prefix) } } + if ip, net, err := net.ParseCIDR(conf.Ipaddr6); err == nil { + conf.Ipaddr6 = ip.String() + prefixLen6, _ := net.Mask.Size() + conf.PrefixLen6 = strconv.Itoa(prefixLen6) + } + return nil } @@ -183,6 +184,28 @@ func (config *WarewulfYaml) IpCIDR() string { return cidr.String() } +func (config *WarewulfYaml) IpCIDR6() string { + if config.Ipaddr6 == "" || config.PrefixLen6 == "" { + return "" + } + ip := net.ParseIP(config.Ipaddr6) + if ip == nil || ip.To4() != nil { + return "" + } + prefix, err := strconv.Atoi(config.PrefixLen6) + if err != nil { + return "" + } + cidr := net.IPNet{ + IP: ip, + Mask: net.CIDRMask(prefix, 128), + } + if cidr.IP == nil || cidr.Mask == nil { + return "" + } + return cidr.String() +} + // InitializedFromFile returns true if [WarewulfYaml] memory was read from // a file, or false otherwise. func (conf *WarewulfYaml) InitializedFromFile() bool { diff --git a/internal/pkg/config/root_test.go b/internal/pkg/config/root_test.go index 9f9d9fd7..bbbdc016 100644 --- a/internal/pkg/config/root_test.go +++ b/internal/pkg/config/root_test.go @@ -153,6 +153,7 @@ ipaddr6: "2001:db8::1/64" `, result: ` ipaddr6: "2001:db8::1" +prefixlen6: "64" warewulf: autobuild overlays: true grubboot: false @@ -421,3 +422,56 @@ func TestNetworkCIDR(t *testing.T) { }) } } + +func TestIpCIDR6(t *testing.T) { + tests := map[string]struct { + ipaddr6 string + prefixlen6 string + cidr string + }{ + "blank": { + ipaddr6: "", + prefixlen6: "", + cidr: "", + }, + "ip only": { + ipaddr6: "2001:db8::1", + prefixlen6: "", + cidr: "", + }, + "prefix only": { + ipaddr6: "", + prefixlen6: "64", + cidr: "", + }, + "full": { + ipaddr6: "2001:db8::1", + prefixlen6: "64", + cidr: "2001:db8::1/64", + }, + "invalid ip": { + ipaddr6: "asdf", + prefixlen6: "64", + cidr: "", + }, + "invalid prefix": { + ipaddr6: "2001:db8::1", + prefixlen6: "asdf", + cidr: "", + }, + "ipv4 address": { + ipaddr6: "192.168.0.1", + prefixlen6: "24", + cidr: "", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + conf := New() + conf.Ipaddr6 = tt.ipaddr6 + conf.PrefixLen6 = tt.prefixlen6 + assert.Equal(t, tt.cidr, conf.IpCIDR6()) + }) + } +} diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index 4fe1be96..2d67c379 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -75,18 +75,19 @@ type KernelConf struct { } type NetDev struct { - Type string `yaml:"type,omitempty" json:"type,omitempty" lopt:"type" sopt:"T" comment:"Set device type of given network"` - OnBoot wwtype.WWbool `yaml:"onboot,omitempty" json:"onbot,omitempty" lopt:"onboot" comment:"Enable/disable network device (true/false)"` - Device string `yaml:"device,omitempty" json:"device,omitempty" lopt:"netdev" sopt:"N" comment:"Set the device for given network"` - Hwaddr string `yaml:"hwaddr,omitempty" json:"hwaddr,omitempty" lopt:"hwaddr" sopt:"H" comment:"Set the device's HW address for given network" type:"MAC"` - Ipaddr net.IP `yaml:"ipaddr,omitempty" json:"ipaddr,omitempty" lopt:"ipaddr" sopt:"I" comment:"IPv4 address in given network" type:"IP"` - Ipaddr6 net.IP `yaml:"ip6addr,omitempty" json:"ip6addr,omitempty" lopt:"ipaddr6" comment:"IPv6 address" type:"IP"` - Prefix net.IP `yaml:"prefix,omitempty" json:"prefix,omitempty"` - Netmask net.IP `yaml:"netmask,omitempty" json:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask" type:"IP"` - Gateway net.IP `yaml:"gateway,omitempty" json:"gateway,omitempty" lopt:"gateway" sopt:"G" comment:"Set the node's network device gateway" type:"IP"` - MTU string `yaml:"mtu,omitempty" json:"mtu,omitempty" lopt:"mtu" comment:"Set the mtu" type:"uint"` - Tags map[string]string `yaml:"tags,omitempty" json:"tags,omitempty"` - primary bool + Type string `yaml:"type,omitempty" json:"type,omitempty" lopt:"type" sopt:"T" comment:"Set device type of given network"` + OnBoot wwtype.WWbool `yaml:"onboot,omitempty" json:"onbot,omitempty" lopt:"onboot" comment:"Enable/disable network device (true/false)"` + Device string `yaml:"device,omitempty" json:"device,omitempty" lopt:"netdev" sopt:"N" comment:"Set the device for given network"` + Hwaddr string `yaml:"hwaddr,omitempty" json:"hwaddr,omitempty" lopt:"hwaddr" sopt:"H" comment:"Set the device's HW address for given network" type:"MAC"` + Ipaddr net.IP `yaml:"ipaddr,omitempty" json:"ipaddr,omitempty" lopt:"ipaddr" sopt:"I" comment:"IPv4 address in given network" type:"IP"` + Netmask net.IP `yaml:"netmask,omitempty" json:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask" type:"IP"` + Gateway net.IP `yaml:"gateway,omitempty" json:"gateway,omitempty" lopt:"gateway" sopt:"G" comment:"Set the node's IPv4 network device gateway" type:"IP"` + Ipaddr6 net.IP `yaml:"ipaddr6,omitempty" json:"ipaddr6,omitempty" lopt:"ipaddr6" comment:"IPv6 address in given network" type:"IP"` + PrefixLen6 string `yaml:"prefixlen6,omitempty" json:"prefixlen6,omitempty" lopt:"prefixlen6" comment:"Set the networks IPv6 prefix length" type:"uint"` + Gateway6 net.IP `yaml:"gateway6,omitempty" json:"gateway6,omitempty" lopt:"gateway6" comment:"Set the node's IPv6 network device gateway" type:"IP"` + MTU string `yaml:"mtu,omitempty" json:"mtu,omitempty" lopt:"mtu" comment:"Set the mtu" type:"uint"` + Tags map[string]string `yaml:"tags,omitempty" json:"tags,omitempty"` + primary bool } /* diff --git a/internal/pkg/node/fields_test.go b/internal/pkg/node/fields_test.go index 9bae729a..ccd69476 100644 --- a/internal/pkg/node/fields_test.go +++ b/internal/pkg/node/fields_test.go @@ -191,10 +191,11 @@ func Test_listFields(t *testing.T) { "NetDevs[default].Device", "NetDevs[default].Hwaddr", "NetDevs[default].Ipaddr", - "NetDevs[default].Ipaddr6", - "NetDevs[default].Prefix", "NetDevs[default].Netmask", "NetDevs[default].Gateway", + "NetDevs[default].Ipaddr6", + "NetDevs[default].PrefixLen6", + "NetDevs[default].Gateway6", "NetDevs[default].MTU", "NetDevs[default].Tags[nettag]", "Tags[tag]", @@ -245,10 +246,11 @@ func Test_listFields(t *testing.T) { "NetDevs[default].Device", "NetDevs[default].Hwaddr", "NetDevs[default].Ipaddr", - "NetDevs[default].Ipaddr6", - "NetDevs[default].Prefix", "NetDevs[default].Netmask", "NetDevs[default].Gateway", + "NetDevs[default].Ipaddr6", + "NetDevs[default].PrefixLen6", + "NetDevs[default].Gateway6", "NetDevs[default].MTU", "NetDevs[default].Tags[nettag]", "Tags[tag]", diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index b51e1759..a5ae6a5c 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -4,6 +4,7 @@ import ( "net" "reflect" "sort" + "strconv" "strings" "github.com/warewulf/warewulf/internal/pkg/util" @@ -426,6 +427,28 @@ func (netdev *NetDev) IpCIDR() string { return ipCIDR.String() } +/* +Return the ipv6 address with prefix CIDR format. Aimed for the use in +template, symmetric with IpCIDR. +*/ +func (netdev *NetDev) IpCIDR6() string { + if netdev.Ipaddr6 == nil || netdev.Ipaddr6.IsUnspecified() || netdev.PrefixLen6 == "" { + return "" + } + if netdev.Ipaddr6.To4() != nil { + return "" + } + prefix, err := strconv.Atoi(netdev.PrefixLen6) + if err != nil || prefix < 0 || prefix > 128 { + return "" + } + ipCIDR6 := net.IPNet{ + IP: netdev.Ipaddr6, + Mask: net.CIDRMask(prefix, 128), + } + return ipCIDR6.String() +} + var unsetValues = []string{"UNSET", "UNDEF"} func isUnsetValue(value string) bool { diff --git a/internal/pkg/node/methods_test.go b/internal/pkg/node/methods_test.go index 9a4fb29d..a8cb8de2 100644 --- a/internal/pkg/node/methods_test.go +++ b/internal/pkg/node/methods_test.go @@ -45,6 +45,59 @@ func Test_IpCIDR(t *testing.T) { } } +func Test_IpCIDR6(t *testing.T) { + tests := map[string]struct { + ipaddr net.IP + prefix string + cidr string + }{ + "nil": { + ipaddr: nil, + prefix: "", + cidr: "", + }, + "ip only": { + ipaddr: net.ParseIP("fd00:10::1"), + prefix: "", + cidr: "", + }, + "netmask only": { + ipaddr: nil, + prefix: "64", + cidr: "", + }, + "ipv4": { + ipaddr: net.ParseIP("10.0.0.1"), + prefix: "64", + cidr: "", + }, + "invalid prefix type": { + ipaddr: net.ParseIP("fd00:10::1"), + prefix: "string", + cidr: "", + }, + "invalid prefix": { + ipaddr: net.ParseIP("fd00:10::1"), + prefix: "129", + cidr: "", + }, + "working": { + ipaddr: net.ParseIP("fd00:10::1"), + prefix: "64", + cidr: "fd00:10::1/64", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + n := new(NetDev) + n.Ipaddr6 = tt.ipaddr + n.PrefixLen6 = tt.prefix + assert.Equal(t, tt.cidr, n.IpCIDR6()) + }) + } +} + func Test_Empty(t *testing.T) { var netdev NetDev var netdevPtr *NetDev diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go index 0d64572e..31750504 100644 --- a/internal/pkg/overlay/datastructure.go +++ b/internal/pkg/overlay/datastructure.go @@ -25,12 +25,13 @@ type TemplateStruct struct { BuildSource string Ipaddr string IpCIDR string - Ipaddr6 string - IpCIDR6 string Netmask string Network string NetworkCIDR string Overlay string + Ipaddr6 string + IpCIDR6 string + PrefixLen6 string Ipv6 bool Dhcp warewulfconf.DHCPConf Nfs warewulfconf.NFSConf @@ -70,14 +71,15 @@ func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (T tstruct.Warewulf = *controller.Warewulf tstruct.Ipaddr = controller.Ipaddr tstruct.IpCIDR = controller.IpCIDR() - tstruct.Ipaddr6 = controller.Ipaddr6 - tstruct.IpCIDR6 = controller.IpCIDR6 - if controller.Ipaddr6 != "" && controller.IpCIDR6 != "" { - tstruct.Ipv6 = true - } tstruct.Netmask = controller.Netmask tstruct.Network = controller.Network tstruct.NetworkCIDR = controller.NetworkCIDR() + tstruct.Ipaddr6 = controller.Ipaddr6 + tstruct.IpCIDR6 = controller.IpCIDR6() + tstruct.PrefixLen6 = controller.PrefixLen6 + if controller.Ipaddr6 != "" && controller.PrefixLen6 != "" { + tstruct.Ipv6 = true + } // init some convenience vars tstruct.Id = nodeData.Id() tstruct.Hostname = nodeData.Id() diff --git a/internal/pkg/upgrade/node.go b/internal/pkg/upgrade/node.go index 72cd4861..a086819b 100644 --- a/internal/pkg/upgrade/node.go +++ b/internal/pkg/upgrade/node.go @@ -633,21 +633,24 @@ func (legacy *KernelConf) Upgrade(imageName string) (upgraded *node.KernelConf) } type NetDev struct { - Default string `yaml:"default"` - Device string `yaml:"device,omitempty"` - Gateway string `yaml:"gateway,omitempty"` - Hwaddr string `yaml:"hwaddr,omitempty"` - IpCIDR string `yaml:"ipcidr,omitempty"` - Ipaddr string `yaml:"ipaddr,omitempty"` - Ipaddr6 string `yaml:"ip6addr,omitempty"` - MTU string `yaml:"mtu,omitempty"` - Netmask string `yaml:"netmask,omitempty"` - OnBoot string `yaml:"onboot,omitempty"` - Prefix string `yaml:"prefix,omitempty"` - Primary string `yaml:"primary,omitempty"` - Tags map[string]string `yaml:"tags,omitempty"` - TagsDel []string `yaml:"tagsdel,omitempty"` - Type string `yaml:"type,omitempty"` + Default string `yaml:"default"` + Device string `yaml:"device,omitempty"` + Gateway string `yaml:"gateway,omitempty"` + Gateway6 string `yaml:"gateway6,omitempty"` + Hwaddr string `yaml:"hwaddr,omitempty"` + IpCIDR string `yaml:"ipcidr,omitempty"` + Ip6addr string `yaml:"ip6addr,omitempty"` + Ipaddr string `yaml:"ipaddr,omitempty"` + Ipaddr6 string `yaml:"ipaddr6,omitempty"` + MTU string `yaml:"mtu,omitempty"` + Netmask string `yaml:"netmask,omitempty"` + OnBoot string `yaml:"onboot,omitempty"` + Prefix string `yaml:"prefix,omitempty"` + PrefixLen6 string `yaml:"prefixlen6,omitempty"` + Primary string `yaml:"primary,omitempty"` + Tags map[string]string `yaml:"tags,omitempty"` + TagsDel []string `yaml:"tagsdel,omitempty"` + Type string `yaml:"type,omitempty"` } func (legacy *NetDev) Upgrade(addDefaults bool) (upgraded *node.NetDev) { @@ -655,9 +658,17 @@ func (legacy *NetDev) Upgrade(addDefaults bool) (upgraded *node.NetDev) { upgraded.Tags = make(map[string]string) upgraded.Device = legacy.Device upgraded.Gateway = net.ParseIP(legacy.Gateway) + upgraded.Gateway6 = net.ParseIP(legacy.Gateway6) upgraded.Hwaddr = legacy.Hwaddr upgraded.Ipaddr = net.ParseIP(legacy.Ipaddr) - upgraded.Ipaddr6 = net.ParseIP(legacy.Ipaddr6) + if legacy.Ip6addr != "" { + if legacy.Ipaddr6 != legacy.Ip6addr { + wwlog.Error("Ipaddr6 (%s) and Ip6addr (%s) are both set and not the same, remove Ip6addr", legacy.Ipaddr6, legacy.Ip6addr) + } + upgraded.Ipaddr6 = net.ParseIP(legacy.Ip6addr) + } else { + upgraded.Ipaddr6 = net.ParseIP(legacy.Ipaddr6) + } upgraded.MTU = legacy.MTU upgraded.Netmask = net.ParseIP(legacy.Netmask) if legacy.IpCIDR != "" { @@ -676,7 +687,10 @@ func (legacy *NetDev) Upgrade(addDefaults bool) (upgraded *node.NetDev) { if legacy.OnBoot != "" { warnError(upgraded.OnBoot.Set(legacy.OnBoot)) } - upgraded.Prefix = net.ParseIP(legacy.Prefix) + if legacy.Prefix != "" { + logIgnore("Prefix", legacy.Prefix, "obsolete") + } + upgraded.PrefixLen6 = legacy.PrefixLen6 if legacy.Tags != nil { for key, value := range legacy.Tags { upgraded.Tags[key] = value diff --git a/internal/pkg/upgrade/node_test.go b/internal/pkg/upgrade/node_test.go index b42aee14..5d6339c1 100644 --- a/internal/pkg/upgrade/node_test.go +++ b/internal/pkg/upgrade/node_test.go @@ -220,6 +220,31 @@ nodes: interface: lanplus escapechar: "~" write: "true" +`, + }, + { + name: "Rename ip6addr to ipaddr6", + addDefaults: false, + replaceOverlays: false, + legacyYaml: ` +nodeprofiles: {} +nodes: + n1: + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ip6addr: 2001:db8::111 + `, + upgradedYaml: ` +nodeprofiles: {} +nodes: + n1: + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr6: 2001:db8::111 `, }, { diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go index 0c75f925..afe842ab 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go @@ -120,8 +120,8 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } authority := fmt.Sprintf("%s:%d", conf.Ipaddr, conf.Warewulf.Port) ipaddr6 := "" - if confIpaddr6, err := netip.ParsePrefix(conf.Ipaddr6); err == nil { - ipaddr6 = confIpaddr6.Addr().String() + if confIpaddr6, err := netip.ParseAddr(conf.Ipaddr6); err == nil { + ipaddr6 = confIpaddr6.String() } if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil { if rinfoIpaddr.Is6() { @@ -226,8 +226,8 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } authority := fmt.Sprintf("%s:%d", conf.Ipaddr, conf.Warewulf.Port) ipaddr6 := "" - if confIpaddr6, err := netip.ParsePrefix(conf.Ipaddr6); err == nil { - ipaddr6 = confIpaddr6.Addr().String() + if confIpaddr6, err := netip.ParseAddr(conf.Ipaddr6); err == nil { + ipaddr6 = confIpaddr6.String() } if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil { if rinfoIpaddr.Is6() { diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go index 6078fa44..769d5ea7 100644 --- a/internal/pkg/warewulfd/provision_test.go +++ b/internal/pkg/warewulfd/provision_test.go @@ -89,7 +89,7 @@ nodes: secureFalse := false conf.Warewulf.SecureP = &secureFalse conf.Ipaddr = "10.10.0.1" - conf.Ipaddr6 = "fd00:10::1/64" + conf.Ipaddr6 = "fd00:10::1" assert.NoError(t, os.MkdirAll(path.Join(conf.Paths.OverlayProvisiondir(), "n1"), 0700)) assert.NoError(t, os.WriteFile(path.Join(conf.Paths.OverlayProvisiondir(), "n1", "__SYSTEM__.img"), []byte("system overlay"), 0600)) assert.NoError(t, os.WriteFile(path.Join(conf.Paths.OverlayProvisiondir(), "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600)) diff --git a/overlays/NetworkManager/internal/networkmanager_test.go b/overlays/NetworkManager/internal/networkmanager_test.go index 498cb7ea..1bef7a38 100644 --- a/overlays/NetworkManager/internal/networkmanager_test.go +++ b/overlays/NetworkManager/internal/networkmanager_test.go @@ -76,6 +76,12 @@ nodes: DNS1: 8.8.8.8 DNS2: 8.8.4.4 DNSSEARCH: "example.com;example.net;" + tertiary: + device: wwnet3 + hwaddr: 9a:77:29:73:14:f8 + ipaddr6: fd00:10::8 + prefixlen6: 64 + gateway6: fd00:10::1 `, args: []string{"--render", "node1", "NetworkManager", "etc/NetworkManager/system-connections/ww4-managed.ww"}, log: `backupFile: true @@ -98,9 +104,8 @@ address=192.168.3.21/24 gateway=192.168.3.1 [ipv6] -method=ignore -addr-gen-mode=stable-privacy -never-default=true +addr-gen-mode=eui64 +method=disabled backupFile: true writeFile: true Filename: warewulf-secondary.conf @@ -122,9 +127,29 @@ dns=8.8.8.8;8.8.4.4; dns-search=example.com;example.net; [ipv6] -method=ignore -addr-gen-mode=stable-privacy -never-default=true +addr-gen-mode=eui64 +method=disabled +backupFile: true +writeFile: true +Filename: warewulf-tertiary.conf +# This file is autogenerated by warewulf + +[connection] +id=tertiary +interface-name=wwnet3 +type=ethernet +autoconnect=true +[ethernet] +mac-address=9a:77:29:73:14:f8 + +[ipv4] +method=disabled + +[ipv6] +addr-gen-mode=eui64 +method=manual +addresses=fd00:10::8/64 +gateway=fd00:10::1 `, }, "NetworkManager:ww4-managed.ww without device": { @@ -169,9 +194,8 @@ address=192.168.3.21/24 gateway=192.168.3.1 [ipv6] -method=ignore -addr-gen-mode=stable-privacy -never-default=true +addr-gen-mode=eui64 +method=disabled backupFile: true writeFile: true Filename: warewulf-secondary.conf @@ -192,9 +216,8 @@ dns=8.8.8.8;8.8.4.4; dns-search=example.com;example.net; [ipv6] -method=ignore -addr-gen-mode=stable-privacy -never-default=true +addr-gen-mode=eui64 +method=disabled `, }, "NetworkManager:ww4-unmanaged.ww with empty mac address": { @@ -269,9 +292,8 @@ method=manual route1=192.168.1.0/24,192.168.2.254 [ipv6] -method=ignore -addr-gen-mode=stable-privacy -never-default=true +addr-gen-mode=eui64 +method=disabled [vlan] interface-name=eth0.902 parent=eth0 @@ -292,9 +314,8 @@ autoconnect=true method=disabled [ipv6] -method=ignore -addr-gen-mode=stable-privacy -never-default=true +addr-gen-mode=eui64 +method=disabled `, }, "NetworkManager:ww4-managed.ww with bond": { @@ -342,9 +363,8 @@ method=manual address=192.168.3.110/24 [ipv6] -method=ignore -addr-gen-mode=stable-privacy -never-default=true +addr-gen-mode=eui64 +method=disabled backupFile: true writeFile: true Filename: warewulf-en1.conf diff --git a/overlays/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww b/overlays/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww index f3781a7f..82328a2c 100644 --- a/overlays/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww +++ b/overlays/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww @@ -74,11 +74,15 @@ dns-search={{ join ";" (without (regexSplit "[ ;]+" $netdev.Tags.DNSSEARCH -1) " {{- if $netdev.Tags.master }} method=disabled {{- else }} -method=ignore -addr-gen-mode=stable-privacy -never-default=true +addr-gen-mode=eui64 {{- if $netdev.Ipaddr6 }} -ipaddr="{{ $netdev.Ipaddr6 }}" +method=manual +addresses={{ $netdev.IpCIDR6 }} +{{- if $netdev.Gateway6 }} +gateway={{ $netdev.Gateway6 }} +{{- end }} +{{- else }} +method=disabled {{- end }} {{- end }} diff --git a/overlays/debug/internal/debug_test.go b/overlays/debug/internal/debug_test.go index 58cb1259..2f27d531 100644 --- a/overlays/debug/internal/debug_test.go +++ b/overlays/debug/internal/debug_test.go @@ -90,11 +90,12 @@ data from other structures. - Ipaddr: 192.168.0.1 - IpCIDR: 192.168.0.1/24 -- Ipaddr6: -- IpCIDR6: - Netmask: 255.255.255.0 - Network: 192.168.0.0 - NetworkCIDR: 192.168.0.0/24 +- Ipaddr6: +- IpCIDR6: +- PrefixLen6: - Ipv6: false ### DHCP @@ -176,10 +177,12 @@ node methods in addition to its fields. - Device: wwnet0 - Hwaddr: e6:92:39:49:7b:03 - Ipaddr: 192.168.3.21 - - Ipaddr6: - - Prefix: - Netmask: 255.255.255.0 - Gateway: 192.168.3.1 + - Ipaddr6: + - IpCIDR6: + - PrefixLen6: + - Gateway6: - MTU: - Primary: false - Tags: @@ -189,15 +192,32 @@ node methods in addition to its fields. - Device: wwnet1 - Hwaddr: 9a:77:29:73:14:f1 - Ipaddr: 192.168.3.22 - - Ipaddr6: - - Prefix: - Netmask: 255.255.255.0 - Gateway: 192.168.3.1 + - Ipaddr6: + - IpCIDR6: + - PrefixLen6: + - Gateway6: - MTU: - Primary: false - Tags: - DNS1=8.8.8.8 - DNS2=8.8.4.4 +- NetDevs[tertiary]: + - Type: + - OnBoot: (true) + - Device: wwnet3 + - Hwaddr: 9a:77:29:73:14:f8 + - Ipaddr: + - Netmask: + - Gateway: + - Ipaddr6: fd00:10::3 + - IpCIDR6: fd00:10::3/64 + - PrefixLen6: 64 + - Gateway6: fd00:10::1 + - MTU: + - Primary: false + - Tags: ## Other nodes diff --git a/overlays/debug/internal/nodes.conf b/overlays/debug/internal/nodes.conf index 22f3bca5..65354ac8 100644 --- a/overlays/debug/internal/nodes.conf +++ b/overlays/debug/internal/nodes.conf @@ -51,6 +51,12 @@ nodes: tags: DNS1: 8.8.8.8 DNS2: 8.8.4.4 + tertiary: + device: wwnet3 + hwaddr: 9a:77:29:73:14:f8 + ipaddr6: fd00:10::3 + prefixlen6: 64 + gateway6: fd00:10::1 ipmi: username: user password: password diff --git a/overlays/debug/rootfs/tstruct.md.ww b/overlays/debug/rootfs/tstruct.md.ww index 58540fb7..100a4525 100644 --- a/overlays/debug/rootfs/tstruct.md.ww +++ b/overlays/debug/rootfs/tstruct.md.ww @@ -30,11 +30,12 @@ data from other structures. - Ipaddr: {{ .Ipaddr }} - IpCIDR: {{ .IpCIDR }} -- Ipaddr6: {{ .Ipaddr6 }} -- IpCIDR6: {{ .IpCIDR6 }} - Netmask: {{ .Netmask }} - Network: {{ .Network }} - NetworkCIDR: {{ .NetworkCIDR }} +- Ipaddr6: {{ .Ipaddr6 }} +- IpCIDR6: {{ .IpCIDR6 }} +- PrefixLen6: {{ .PrefixLen6 }} - Ipv6: {{ .Ipv6 }} ### DHCP @@ -121,10 +122,12 @@ node methods in addition to its fields. - Device: {{ $netdev.Device }} - Hwaddr: {{ $netdev.Hwaddr }} - Ipaddr: {{ $netdev.Ipaddr }} - - Ipaddr6: {{ $netdev.Ipaddr6 }} - - Prefix: {{ $netdev.Prefix }} - Netmask: {{ $netdev.Netmask }} - Gateway: {{ $netdev.Gateway }} + - Ipaddr6: {{ $netdev.Ipaddr6 }} + - IpCIDR6: {{ $netdev.IpCIDR6 }} + - PrefixLen6: {{ $netdev.PrefixLen6 }} + - Gateway6: {{ $netdev.Gateway6 }} - MTU: {{ $netdev.MTU }} - Primary: {{ $netdev.Primary }} - Tags: diff --git a/overlays/host/internal/host_test.go b/overlays/host/internal/host_test.go index c48a3df9..265bc0ce 100644 --- a/overlays/host/internal/host_test.go +++ b/overlays/host/internal/host_test.go @@ -284,10 +284,27 @@ writeFile: true Filename: etc/dnsmasq.d/ww4-hosts6.conf # This file was autgenerated by warewulf -dhcp-range=2001:db8::100,2001:db8::0:1FF,slaac,6h -dhcp-option=option6:bootfile-url,tftp://[2001:db8::1]/ -dhcp-option=option6:ntp-server,[2001:db8::1] +# enable services +enable-ra +enable-tftp +# match archtecture (client-arch=61) +dhcp-match=set:x86PC,option6:61,0007 # EFI x86-64 +dhcp-match=set:aarch64,option6:61,0011 # EFI aarch64 +dhcp-vendorclass=set:x86PC,enterprise:343,PXEClient:Arch:00007 # EFI x86-64 +dhcp-vendorclass=set:aarch64,enterprise:343,PXEClient:Arch:00011 # EFI aarch64 + +# match iPXE +dhcp-userclass=set:iPXE,iPXE + +# send iPXE EFI binary +dhcp-option=tag:x86PC,option6:bootfile-url,"tftp://[2001:db8::1]/warewulf/ipxe-snponly-x86_64.efi" +dhcp-option=tag:aarch64,option6:bootfile-url,"tftp://[2001:db8::1]/warewulf/snponly.efi" +# iPXE binary will get the following configuration file +dhcp-option=tag:iPXE,option6:bootfile-url,"http://[2001:db8::1]:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}" + +# DHCP range and entries +dhcp-range=2001:db8::100,2001:db8::0:1FF,6h dhcp-host=e6:92:39:49:7b:03,set:warewulf,node1,2001:db8::111,infinite dhcp-host=9a:77:29:73:14:f1,set:warewulf,node1,2001:db8::112,infinite dhcp-host=e6:92:39:49:7b:04,set:warewulf,node2,2001:db8::121,infinite diff --git a/overlays/host/internal/nodes.conf b/overlays/host/internal/nodes.conf index a5d99ffc..8d3d1e17 100644 --- a/overlays/host/internal/nodes.conf +++ b/overlays/host/internal/nodes.conf @@ -5,16 +5,16 @@ nodes: device: wwnet0 hwaddr: e6:92:39:49:7b:03 ipaddr: 192.168.3.21 - ip6addr: 2001:db8::111 + ipaddr6: 2001:db8::111 secondary: device: wwnet1 hwaddr: 9a:77:29:73:14:f1 ipaddr: 192.168.3.22 - ip6addr: 2001:db8::112 + ipaddr6: 2001:db8::112 node2: network devices: default: device: wwnet0 hwaddr: e6:92:39:49:7b:04 ipaddr: 192.168.3.23 - ip6addr: 2001:db8::121 + ipaddr6: 2001:db8::121 diff --git a/overlays/host/internal/warewulf.conf-ipv6 b/overlays/host/internal/warewulf.conf-ipv6 index 3f6e1232..af5334bd 100644 --- a/overlays/host/internal/warewulf.conf-ipv6 +++ b/overlays/host/internal/warewulf.conf-ipv6 @@ -1,4 +1,5 @@ -ipaddr6: 2001:db8::1/64 +ipaddr6: 2001:db8::1 +prefixlen6: 64 dhcp: enabled: true range6 start: 2001:db8::100 diff --git a/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww b/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww index 5b6715ae..e6b8a3e8 100644 --- a/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww +++ b/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww @@ -30,8 +30,10 @@ dhcp-no-override enable-tftp tftp-root={{ $.Tftp.TftpRoot }} {{- end }} +{{ if and $.Dhcp.RangeStart $.Dhcp.RangeEnd -}} # define the the range dhcp-range={{$.Dhcp.RangeStart}},{{$.Dhcp.RangeEnd}},{{$.Netmask}},6h +{{ end -}} {{ range $node := $.AllNodes -}} {{ range $devname, $netdev := $node.NetDevs -}} {{ if and $netdev.Ipaddr $netdev.Hwaddr -}} diff --git a/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts6.conf.ww b/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts6.conf.ww index 084041a5..76bdd584 100644 --- a/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts6.conf.ww +++ b/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts6.conf.ww @@ -1,9 +1,34 @@ # This file was autgenerated by warewulf {{ nobackup }} -dhcp-range={{$.Dhcp.Range6Start}},{{$.Dhcp.Range6End}},slaac,6h -dhcp-option=option6:bootfile-url,tftp://[{{$.Ipaddr6}}]/ -dhcp-option=option6:ntp-server,[{{$.Ipaddr6}}] +{{ if .Ipv6 -}} +# enable services +enable-ra +enable-tftp +# match archtecture (client-arch=61) +dhcp-match=set:x86PC,option6:61,0007 # EFI x86-64 +dhcp-match=set:aarch64,option6:61,0011 # EFI aarch64 +dhcp-vendorclass=set:x86PC,enterprise:343,PXEClient:Arch:00007 # EFI x86-64 +dhcp-vendorclass=set:aarch64,enterprise:343,PXEClient:Arch:00011 # EFI aarch64 + +# match iPXE +dhcp-userclass=set:iPXE,iPXE + +# send iPXE EFI binary +{{ with (index $.Tftp.IpxeBinaries "00:07" ) -}} +dhcp-option=tag:x86PC,option6:bootfile-url,"tftp://[{{$.Ipaddr6}}]/warewulf/{{ index $.Tftp.IpxeBinaries "00:07" }}" +{{ end -}} +{{ with (index $.Tftp.IpxeBinaries "00:0B" ) -}} +dhcp-option=tag:aarch64,option6:bootfile-url,"tftp://[{{$.Ipaddr6}}]/warewulf/{{ index $.Tftp.IpxeBinaries "00:0B" | basename }}" +{{ end -}} + +# iPXE binary will get the following configuration file +dhcp-option=tag:iPXE,option6:bootfile-url,"http://[{{$.Ipaddr6}}]:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}" + +# DHCP range and entries +{{ if and $.Dhcp.Range6Start $.Dhcp.Range6End -}} +dhcp-range={{$.Dhcp.Range6Start}},{{$.Dhcp.Range6End}},6h +{{ end -}} {{ range $node := $.AllNodes -}} {{ range $devname, $netdev := $node.NetDevs -}} {{ if and $netdev.Ipaddr6 $netdev.Hwaddr -}} @@ -11,3 +36,4 @@ dhcp-host={{$netdev.Hwaddr}},set:warewulf,{{$node.Id}},{{$netdev.Ipaddr6}},infin {{ end -}} {{ end -}} {{ end -}} +{{ end -}} diff --git a/overlays/host/rootfs/etc/dnsmasq.d/ww4-listen.conf.ww b/overlays/host/rootfs/etc/dnsmasq.d/ww4-listen.conf.ww new file mode 100644 index 00000000..bfea3ef6 --- /dev/null +++ b/overlays/host/rootfs/etc/dnsmasq.d/ww4-listen.conf.ww @@ -0,0 +1,7 @@ +# This file was autgenerated by warewulf +{{ nobackup }} +listen-address={{ .Ipaddr }} +{{ if .Ipaddr6 -}} +listen-address={{ .Ipaddr6 }} +interface=* +{{ end -}}