Add IPv6 support for Dnsmasq and NetworkManager

Signed-off-by: Timothy Middelkoop <tmiddelkoop@internet2.edu>
This commit is contained in:
Timothy Middelkoop
2025-11-27 01:56:18 +00:00
parent 07eee48a6a
commit 4f58845a9e
25 changed files with 417 additions and 106 deletions

View File

@@ -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. - Absolute paths specified with `{{ file }}` in an overlay now write to that absolute path.
- Use opencontainers/selinux to manage SELinux in wwclient. - Use opencontainers/selinux to manage SELinux in wwclient.
- Replace unused/unneeded IPv6net with IpCIDR6 to align with IPv4 - Replace unused/unneeded IPv6net with IpCIDR6 to align with IPv4
- Add IPv6 DHCP range - Improved IPv6 support
- Add IPv6 functionality for dnsmasq - 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 ### Fixed

View File

@@ -114,7 +114,7 @@ nodes:
n01: n01:
network devices: network devices:
default: default:
ip6addr: fdaa::1 ipaddr6: fdaa::1
`}, `},
{name: "single node with ipaddr", {name: "single node with ipaddr",
args: []string{"--ipaddr=10.0.0.1", "n01"}, args: []string{"--ipaddr=10.0.0.1", "n01"},

View File

@@ -11,8 +11,8 @@ type DHCPConf struct {
Template string `yaml:"template,omitempty" default:"default"` Template string `yaml:"template,omitempty" default:"default"`
RangeStart string `yaml:"range start,omitempty"` RangeStart string `yaml:"range start,omitempty"`
RangeEnd string `yaml:"range end,omitempty"` RangeEnd string `yaml:"range end,omitempty"`
Range6Start string `yaml:"range6 start,omitempty"` Range6Start string `yaml:"range6 start,omitempty"`
Range6End string `yaml:"range6 end,omitempty"` Range6End string `yaml:"range6 end,omitempty"`
SystemdName string `yaml:"systemd name,omitempty" default:"dhcpd"` SystemdName string `yaml:"systemd name,omitempty" default:"dhcpd"`
} }

View File

@@ -8,10 +8,10 @@ package config
import ( import (
"bytes" "bytes"
"fmt"
"net" "net"
"os" "os"
"reflect" "reflect"
"strconv"
"github.com/creasty/defaults" "github.com/creasty/defaults"
"github.com/warewulf/warewulf/internal/pkg/wwlog" "github.com/warewulf/warewulf/internal/pkg/wwlog"
@@ -26,11 +26,11 @@ var cachedConf WarewulfYaml
type WarewulfYaml struct { type WarewulfYaml struct {
Comment string `yaml:"comment,omitempty"` Comment string `yaml:"comment,omitempty"`
Ipaddr string `yaml:"ipaddr,omitempty"` Ipaddr string `yaml:"ipaddr,omitempty"`
Ipaddr6 string `yaml:"ipaddr6,omitempty"`
Netmask string `yaml:"netmask,omitempty"` Netmask string `yaml:"netmask,omitempty"`
Network string `yaml:"network,omitempty"` Network string `yaml:"network,omitempty"`
IpCIDR6 string `yaml:"-"`
Fqdn string `yaml:"fqdn,omitempty"` Fqdn string `yaml:"fqdn,omitempty"`
Ipaddr6 string `yaml:"ipaddr6,omitempty"`
PrefixLen6 string `yaml:"prefixlen6,omitempty"`
Warewulf *WarewulfConf `yaml:"warewulf,omitempty"` Warewulf *WarewulfConf `yaml:"warewulf,omitempty"`
API *APIConf `yaml:"api,omitempty"` API *APIConf `yaml:"api,omitempty"`
DHCP *DHCPConf `yaml:"dhcp,omitempty"` DHCP *DHCPConf `yaml:"dhcp,omitempty"`
@@ -140,18 +140,19 @@ func (conf *WarewulfYaml) Parse(data []byte, autodetect bool) error {
} }
} }
if conf.Ipaddr6 != "" { if ip := net.ParseIP(conf.Ipaddr6); ip != nil {
if ip, _, err := net.ParseCIDR(conf.Ipaddr6); err == nil { if prefix, err := strconv.Atoi(conf.PrefixLen6); err == nil {
if ip.To4() != nil {
return fmt.Errorf("invalid ipv6 address: ip address is ipv4: %s", conf.Ipaddr6)
}
conf.IpCIDR6 = conf.Ipaddr6
conf.Ipaddr6 = ip.String() conf.Ipaddr6 = ip.String()
} else { conf.PrefixLen6 = strconv.Itoa(prefix)
return fmt.Errorf("invalid ipv6 address: must use CIDR notation: %s", conf.Ipaddr6)
} }
} }
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 return nil
} }
@@ -183,6 +184,28 @@ func (config *WarewulfYaml) IpCIDR() string {
return cidr.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 // InitializedFromFile returns true if [WarewulfYaml] memory was read from
// a file, or false otherwise. // a file, or false otherwise.
func (conf *WarewulfYaml) InitializedFromFile() bool { func (conf *WarewulfYaml) InitializedFromFile() bool {

View File

@@ -153,6 +153,7 @@ ipaddr6: "2001:db8::1/64"
`, `,
result: ` result: `
ipaddr6: "2001:db8::1" ipaddr6: "2001:db8::1"
prefixlen6: "64"
warewulf: warewulf:
autobuild overlays: true autobuild overlays: true
grubboot: false 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())
})
}
}

View File

@@ -75,18 +75,19 @@ type KernelConf struct {
} }
type NetDev struct { type NetDev struct {
Type string `yaml:"type,omitempty" json:"type,omitempty" lopt:"type" sopt:"T" comment:"Set device type of given network"` 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)"` 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"` 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"` 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"` 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"` Netmask net.IP `yaml:"netmask,omitempty" json:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask" type:"IP"`
Prefix net.IP `yaml:"prefix,omitempty" json:"prefix,omitempty"` Gateway net.IP `yaml:"gateway,omitempty" json:"gateway,omitempty" lopt:"gateway" sopt:"G" comment:"Set the node's IPv4 network device gateway" type:"IP"`
Netmask net.IP `yaml:"netmask,omitempty" json:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask" type:"IP"` Ipaddr6 net.IP `yaml:"ipaddr6,omitempty" json:"ipaddr6,omitempty" lopt:"ipaddr6" comment:"IPv6 address in given network" 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"` PrefixLen6 string `yaml:"prefixlen6,omitempty" json:"prefixlen6,omitempty" lopt:"prefixlen6" comment:"Set the networks IPv6 prefix length" type:"uint"`
MTU string `yaml:"mtu,omitempty" json:"mtu,omitempty" lopt:"mtu" comment:"Set the mtu" type:"uint"` Gateway6 net.IP `yaml:"gateway6,omitempty" json:"gateway6,omitempty" lopt:"gateway6" comment:"Set the node's IPv6 network device gateway" type:"IP"`
Tags map[string]string `yaml:"tags,omitempty" json:"tags,omitempty"` MTU string `yaml:"mtu,omitempty" json:"mtu,omitempty" lopt:"mtu" comment:"Set the mtu" type:"uint"`
primary bool Tags map[string]string `yaml:"tags,omitempty" json:"tags,omitempty"`
primary bool
} }
/* /*

View File

@@ -191,10 +191,11 @@ func Test_listFields(t *testing.T) {
"NetDevs[default].Device", "NetDevs[default].Device",
"NetDevs[default].Hwaddr", "NetDevs[default].Hwaddr",
"NetDevs[default].Ipaddr", "NetDevs[default].Ipaddr",
"NetDevs[default].Ipaddr6",
"NetDevs[default].Prefix",
"NetDevs[default].Netmask", "NetDevs[default].Netmask",
"NetDevs[default].Gateway", "NetDevs[default].Gateway",
"NetDevs[default].Ipaddr6",
"NetDevs[default].PrefixLen6",
"NetDevs[default].Gateway6",
"NetDevs[default].MTU", "NetDevs[default].MTU",
"NetDevs[default].Tags[nettag]", "NetDevs[default].Tags[nettag]",
"Tags[tag]", "Tags[tag]",
@@ -245,10 +246,11 @@ func Test_listFields(t *testing.T) {
"NetDevs[default].Device", "NetDevs[default].Device",
"NetDevs[default].Hwaddr", "NetDevs[default].Hwaddr",
"NetDevs[default].Ipaddr", "NetDevs[default].Ipaddr",
"NetDevs[default].Ipaddr6",
"NetDevs[default].Prefix",
"NetDevs[default].Netmask", "NetDevs[default].Netmask",
"NetDevs[default].Gateway", "NetDevs[default].Gateway",
"NetDevs[default].Ipaddr6",
"NetDevs[default].PrefixLen6",
"NetDevs[default].Gateway6",
"NetDevs[default].MTU", "NetDevs[default].MTU",
"NetDevs[default].Tags[nettag]", "NetDevs[default].Tags[nettag]",
"Tags[tag]", "Tags[tag]",

View File

@@ -4,6 +4,7 @@ import (
"net" "net"
"reflect" "reflect"
"sort" "sort"
"strconv"
"strings" "strings"
"github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/util"
@@ -426,6 +427,28 @@ func (netdev *NetDev) IpCIDR() string {
return 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"} var unsetValues = []string{"UNSET", "UNDEF"}
func isUnsetValue(value string) bool { func isUnsetValue(value string) bool {

View File

@@ -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) { func Test_Empty(t *testing.T) {
var netdev NetDev var netdev NetDev
var netdevPtr *NetDev var netdevPtr *NetDev

View File

@@ -25,12 +25,13 @@ type TemplateStruct struct {
BuildSource string BuildSource string
Ipaddr string Ipaddr string
IpCIDR string IpCIDR string
Ipaddr6 string
IpCIDR6 string
Netmask string Netmask string
Network string Network string
NetworkCIDR string NetworkCIDR string
Overlay string Overlay string
Ipaddr6 string
IpCIDR6 string
PrefixLen6 string
Ipv6 bool Ipv6 bool
Dhcp warewulfconf.DHCPConf Dhcp warewulfconf.DHCPConf
Nfs warewulfconf.NFSConf Nfs warewulfconf.NFSConf
@@ -70,14 +71,15 @@ func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (T
tstruct.Warewulf = *controller.Warewulf tstruct.Warewulf = *controller.Warewulf
tstruct.Ipaddr = controller.Ipaddr tstruct.Ipaddr = controller.Ipaddr
tstruct.IpCIDR = controller.IpCIDR() 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.Netmask = controller.Netmask
tstruct.Network = controller.Network tstruct.Network = controller.Network
tstruct.NetworkCIDR = controller.NetworkCIDR() 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 // init some convenience vars
tstruct.Id = nodeData.Id() tstruct.Id = nodeData.Id()
tstruct.Hostname = nodeData.Id() tstruct.Hostname = nodeData.Id()

View File

@@ -633,21 +633,24 @@ func (legacy *KernelConf) Upgrade(imageName string) (upgraded *node.KernelConf)
} }
type NetDev struct { type NetDev struct {
Default string `yaml:"default"` Default string `yaml:"default"`
Device string `yaml:"device,omitempty"` Device string `yaml:"device,omitempty"`
Gateway string `yaml:"gateway,omitempty"` Gateway string `yaml:"gateway,omitempty"`
Hwaddr string `yaml:"hwaddr,omitempty"` Gateway6 string `yaml:"gateway6,omitempty"`
IpCIDR string `yaml:"ipcidr,omitempty"` Hwaddr string `yaml:"hwaddr,omitempty"`
Ipaddr string `yaml:"ipaddr,omitempty"` IpCIDR string `yaml:"ipcidr,omitempty"`
Ipaddr6 string `yaml:"ip6addr,omitempty"` Ip6addr string `yaml:"ip6addr,omitempty"`
MTU string `yaml:"mtu,omitempty"` Ipaddr string `yaml:"ipaddr,omitempty"`
Netmask string `yaml:"netmask,omitempty"` Ipaddr6 string `yaml:"ipaddr6,omitempty"`
OnBoot string `yaml:"onboot,omitempty"` MTU string `yaml:"mtu,omitempty"`
Prefix string `yaml:"prefix,omitempty"` Netmask string `yaml:"netmask,omitempty"`
Primary string `yaml:"primary,omitempty"` OnBoot string `yaml:"onboot,omitempty"`
Tags map[string]string `yaml:"tags,omitempty"` Prefix string `yaml:"prefix,omitempty"`
TagsDel []string `yaml:"tagsdel,omitempty"` PrefixLen6 string `yaml:"prefixlen6,omitempty"`
Type string `yaml:"type,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) { 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.Tags = make(map[string]string)
upgraded.Device = legacy.Device upgraded.Device = legacy.Device
upgraded.Gateway = net.ParseIP(legacy.Gateway) upgraded.Gateway = net.ParseIP(legacy.Gateway)
upgraded.Gateway6 = net.ParseIP(legacy.Gateway6)
upgraded.Hwaddr = legacy.Hwaddr upgraded.Hwaddr = legacy.Hwaddr
upgraded.Ipaddr = net.ParseIP(legacy.Ipaddr) 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.MTU = legacy.MTU
upgraded.Netmask = net.ParseIP(legacy.Netmask) upgraded.Netmask = net.ParseIP(legacy.Netmask)
if legacy.IpCIDR != "" { if legacy.IpCIDR != "" {
@@ -676,7 +687,10 @@ func (legacy *NetDev) Upgrade(addDefaults bool) (upgraded *node.NetDev) {
if legacy.OnBoot != "" { if legacy.OnBoot != "" {
warnError(upgraded.OnBoot.Set(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 { if legacy.Tags != nil {
for key, value := range legacy.Tags { for key, value := range legacy.Tags {
upgraded.Tags[key] = value upgraded.Tags[key] = value

View File

@@ -220,6 +220,31 @@ nodes:
interface: lanplus interface: lanplus
escapechar: "~" escapechar: "~"
write: "true" 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
`, `,
}, },
{ {

View File

@@ -120,8 +120,8 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
} }
authority := fmt.Sprintf("%s:%d", conf.Ipaddr, conf.Warewulf.Port) authority := fmt.Sprintf("%s:%d", conf.Ipaddr, conf.Warewulf.Port)
ipaddr6 := "" ipaddr6 := ""
if confIpaddr6, err := netip.ParsePrefix(conf.Ipaddr6); err == nil { if confIpaddr6, err := netip.ParseAddr(conf.Ipaddr6); err == nil {
ipaddr6 = confIpaddr6.Addr().String() ipaddr6 = confIpaddr6.String()
} }
if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil { if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil {
if rinfoIpaddr.Is6() { 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) authority := fmt.Sprintf("%s:%d", conf.Ipaddr, conf.Warewulf.Port)
ipaddr6 := "" ipaddr6 := ""
if confIpaddr6, err := netip.ParsePrefix(conf.Ipaddr6); err == nil { if confIpaddr6, err := netip.ParseAddr(conf.Ipaddr6); err == nil {
ipaddr6 = confIpaddr6.Addr().String() ipaddr6 = confIpaddr6.String()
} }
if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil { if rinfoIpaddr, err := netip.ParseAddr(rinfo.ipaddr); err == nil {
if rinfoIpaddr.Is6() { if rinfoIpaddr.Is6() {

View File

@@ -89,7 +89,7 @@ nodes:
secureFalse := false secureFalse := false
conf.Warewulf.SecureP = &secureFalse conf.Warewulf.SecureP = &secureFalse
conf.Ipaddr = "10.10.0.1" 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.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", "__SYSTEM__.img"), []byte("system overlay"), 0600))
assert.NoError(t, os.WriteFile(path.Join(conf.Paths.OverlayProvisiondir(), "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600)) assert.NoError(t, os.WriteFile(path.Join(conf.Paths.OverlayProvisiondir(), "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600))

View File

@@ -76,6 +76,12 @@ nodes:
DNS1: 8.8.8.8 DNS1: 8.8.8.8
DNS2: 8.8.4.4 DNS2: 8.8.4.4
DNSSEARCH: "example.com;example.net;" 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"}, args: []string{"--render", "node1", "NetworkManager", "etc/NetworkManager/system-connections/ww4-managed.ww"},
log: `backupFile: true log: `backupFile: true
@@ -98,9 +104,8 @@ address=192.168.3.21/24
gateway=192.168.3.1 gateway=192.168.3.1
[ipv6] [ipv6]
method=ignore addr-gen-mode=eui64
addr-gen-mode=stable-privacy method=disabled
never-default=true
backupFile: true backupFile: true
writeFile: true writeFile: true
Filename: warewulf-secondary.conf Filename: warewulf-secondary.conf
@@ -122,9 +127,29 @@ dns=8.8.8.8;8.8.4.4;
dns-search=example.com;example.net; dns-search=example.com;example.net;
[ipv6] [ipv6]
method=ignore addr-gen-mode=eui64
addr-gen-mode=stable-privacy method=disabled
never-default=true 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": { "NetworkManager:ww4-managed.ww without device": {
@@ -169,9 +194,8 @@ address=192.168.3.21/24
gateway=192.168.3.1 gateway=192.168.3.1
[ipv6] [ipv6]
method=ignore addr-gen-mode=eui64
addr-gen-mode=stable-privacy method=disabled
never-default=true
backupFile: true backupFile: true
writeFile: true writeFile: true
Filename: warewulf-secondary.conf Filename: warewulf-secondary.conf
@@ -192,9 +216,8 @@ dns=8.8.8.8;8.8.4.4;
dns-search=example.com;example.net; dns-search=example.com;example.net;
[ipv6] [ipv6]
method=ignore addr-gen-mode=eui64
addr-gen-mode=stable-privacy method=disabled
never-default=true
`, `,
}, },
"NetworkManager:ww4-unmanaged.ww with empty mac address": { "NetworkManager:ww4-unmanaged.ww with empty mac address": {
@@ -269,9 +292,8 @@ method=manual
route1=192.168.1.0/24,192.168.2.254 route1=192.168.1.0/24,192.168.2.254
[ipv6] [ipv6]
method=ignore addr-gen-mode=eui64
addr-gen-mode=stable-privacy method=disabled
never-default=true
[vlan] [vlan]
interface-name=eth0.902 interface-name=eth0.902
parent=eth0 parent=eth0
@@ -292,9 +314,8 @@ autoconnect=true
method=disabled method=disabled
[ipv6] [ipv6]
method=ignore addr-gen-mode=eui64
addr-gen-mode=stable-privacy method=disabled
never-default=true
`, `,
}, },
"NetworkManager:ww4-managed.ww with bond": { "NetworkManager:ww4-managed.ww with bond": {
@@ -342,9 +363,8 @@ method=manual
address=192.168.3.110/24 address=192.168.3.110/24
[ipv6] [ipv6]
method=ignore addr-gen-mode=eui64
addr-gen-mode=stable-privacy method=disabled
never-default=true
backupFile: true backupFile: true
writeFile: true writeFile: true
Filename: warewulf-en1.conf Filename: warewulf-en1.conf

View File

@@ -74,11 +74,15 @@ dns-search={{ join ";" (without (regexSplit "[ ;]+" $netdev.Tags.DNSSEARCH -1) "
{{- if $netdev.Tags.master }} {{- if $netdev.Tags.master }}
method=disabled method=disabled
{{- else }} {{- else }}
method=ignore addr-gen-mode=eui64
addr-gen-mode=stable-privacy
never-default=true
{{- if $netdev.Ipaddr6 }} {{- if $netdev.Ipaddr6 }}
ipaddr="{{ $netdev.Ipaddr6 }}" method=manual
addresses={{ $netdev.IpCIDR6 }}
{{- if $netdev.Gateway6 }}
gateway={{ $netdev.Gateway6 }}
{{- end }}
{{- else }}
method=disabled
{{- end }} {{- end }}
{{- end }} {{- end }}

View File

@@ -90,11 +90,12 @@ data from other structures.
- Ipaddr: 192.168.0.1 - Ipaddr: 192.168.0.1
- IpCIDR: 192.168.0.1/24 - IpCIDR: 192.168.0.1/24
- Ipaddr6:
- IpCIDR6:
- Netmask: 255.255.255.0 - Netmask: 255.255.255.0
- Network: 192.168.0.0 - Network: 192.168.0.0
- NetworkCIDR: 192.168.0.0/24 - NetworkCIDR: 192.168.0.0/24
- Ipaddr6:
- IpCIDR6:
- PrefixLen6:
- Ipv6: false - Ipv6: false
### DHCP ### DHCP
@@ -176,10 +177,12 @@ node methods in addition to its fields.
- Device: wwnet0 - Device: wwnet0
- Hwaddr: e6:92:39:49:7b:03 - Hwaddr: e6:92:39:49:7b:03
- Ipaddr: 192.168.3.21 - Ipaddr: 192.168.3.21
- Ipaddr6: <nil>
- Prefix: <nil>
- Netmask: 255.255.255.0 - Netmask: 255.255.255.0
- Gateway: 192.168.3.1 - Gateway: 192.168.3.1
- Ipaddr6: <nil>
- IpCIDR6:
- PrefixLen6:
- Gateway6: <nil>
- MTU: - MTU:
- Primary: false - Primary: false
- Tags: - Tags:
@@ -189,15 +192,32 @@ node methods in addition to its fields.
- Device: wwnet1 - Device: wwnet1
- Hwaddr: 9a:77:29:73:14:f1 - Hwaddr: 9a:77:29:73:14:f1
- Ipaddr: 192.168.3.22 - Ipaddr: 192.168.3.22
- Ipaddr6: <nil>
- Prefix: <nil>
- Netmask: 255.255.255.0 - Netmask: 255.255.255.0
- Gateway: 192.168.3.1 - Gateway: 192.168.3.1
- Ipaddr6: <nil>
- IpCIDR6:
- PrefixLen6:
- Gateway6: <nil>
- MTU: - MTU:
- Primary: false - Primary: false
- Tags: - Tags:
- DNS1=8.8.8.8 - DNS1=8.8.8.8
- DNS2=8.8.4.4 - DNS2=8.8.4.4
- NetDevs[tertiary]:
- Type:
- OnBoot: (true)
- Device: wwnet3
- Hwaddr: 9a:77:29:73:14:f8
- Ipaddr: <nil>
- Netmask: <nil>
- Gateway: <nil>
- Ipaddr6: fd00:10::3
- IpCIDR6: fd00:10::3/64
- PrefixLen6: 64
- Gateway6: fd00:10::1
- MTU:
- Primary: false
- Tags:
## Other nodes ## Other nodes

View File

@@ -51,6 +51,12 @@ nodes:
tags: tags:
DNS1: 8.8.8.8 DNS1: 8.8.8.8
DNS2: 8.8.4.4 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: ipmi:
username: user username: user
password: password password: password

View File

@@ -30,11 +30,12 @@ data from other structures.
- Ipaddr: {{ .Ipaddr }} - Ipaddr: {{ .Ipaddr }}
- IpCIDR: {{ .IpCIDR }} - IpCIDR: {{ .IpCIDR }}
- Ipaddr6: {{ .Ipaddr6 }}
- IpCIDR6: {{ .IpCIDR6 }}
- Netmask: {{ .Netmask }} - Netmask: {{ .Netmask }}
- Network: {{ .Network }} - Network: {{ .Network }}
- NetworkCIDR: {{ .NetworkCIDR }} - NetworkCIDR: {{ .NetworkCIDR }}
- Ipaddr6: {{ .Ipaddr6 }}
- IpCIDR6: {{ .IpCIDR6 }}
- PrefixLen6: {{ .PrefixLen6 }}
- Ipv6: {{ .Ipv6 }} - Ipv6: {{ .Ipv6 }}
### DHCP ### DHCP
@@ -121,10 +122,12 @@ node methods in addition to its fields.
- Device: {{ $netdev.Device }} - Device: {{ $netdev.Device }}
- Hwaddr: {{ $netdev.Hwaddr }} - Hwaddr: {{ $netdev.Hwaddr }}
- Ipaddr: {{ $netdev.Ipaddr }} - Ipaddr: {{ $netdev.Ipaddr }}
- Ipaddr6: {{ $netdev.Ipaddr6 }}
- Prefix: {{ $netdev.Prefix }}
- Netmask: {{ $netdev.Netmask }} - Netmask: {{ $netdev.Netmask }}
- Gateway: {{ $netdev.Gateway }} - Gateway: {{ $netdev.Gateway }}
- Ipaddr6: {{ $netdev.Ipaddr6 }}
- IpCIDR6: {{ $netdev.IpCIDR6 }}
- PrefixLen6: {{ $netdev.PrefixLen6 }}
- Gateway6: {{ $netdev.Gateway6 }}
- MTU: {{ $netdev.MTU }} - MTU: {{ $netdev.MTU }}
- Primary: {{ $netdev.Primary }} - Primary: {{ $netdev.Primary }}
- Tags: - Tags:

View File

@@ -284,10 +284,27 @@ writeFile: true
Filename: etc/dnsmasq.d/ww4-hosts6.conf Filename: etc/dnsmasq.d/ww4-hosts6.conf
# This file was autgenerated by warewulf # This file was autgenerated by warewulf
dhcp-range=2001:db8::100,2001:db8::0:1FF,slaac,6h # enable services
dhcp-option=option6:bootfile-url,tftp://[2001:db8::1]/ enable-ra
dhcp-option=option6:ntp-server,[2001:db8::1] 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=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=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 dhcp-host=e6:92:39:49:7b:04,set:warewulf,node2,2001:db8::121,infinite

View File

@@ -5,16 +5,16 @@ nodes:
device: wwnet0 device: wwnet0
hwaddr: e6:92:39:49:7b:03 hwaddr: e6:92:39:49:7b:03
ipaddr: 192.168.3.21 ipaddr: 192.168.3.21
ip6addr: 2001:db8::111 ipaddr6: 2001:db8::111
secondary: secondary:
device: wwnet1 device: wwnet1
hwaddr: 9a:77:29:73:14:f1 hwaddr: 9a:77:29:73:14:f1
ipaddr: 192.168.3.22 ipaddr: 192.168.3.22
ip6addr: 2001:db8::112 ipaddr6: 2001:db8::112
node2: node2:
network devices: network devices:
default: default:
device: wwnet0 device: wwnet0
hwaddr: e6:92:39:49:7b:04 hwaddr: e6:92:39:49:7b:04
ipaddr: 192.168.3.23 ipaddr: 192.168.3.23
ip6addr: 2001:db8::121 ipaddr6: 2001:db8::121

View File

@@ -1,4 +1,5 @@
ipaddr6: 2001:db8::1/64 ipaddr6: 2001:db8::1
prefixlen6: 64
dhcp: dhcp:
enabled: true enabled: true
range6 start: 2001:db8::100 range6 start: 2001:db8::100

View File

@@ -30,8 +30,10 @@ dhcp-no-override
enable-tftp enable-tftp
tftp-root={{ $.Tftp.TftpRoot }} tftp-root={{ $.Tftp.TftpRoot }}
{{- end }} {{- end }}
{{ if and $.Dhcp.RangeStart $.Dhcp.RangeEnd -}}
# define the the range # define the the range
dhcp-range={{$.Dhcp.RangeStart}},{{$.Dhcp.RangeEnd}},{{$.Netmask}},6h dhcp-range={{$.Dhcp.RangeStart}},{{$.Dhcp.RangeEnd}},{{$.Netmask}},6h
{{ end -}}
{{ range $node := $.AllNodes -}} {{ range $node := $.AllNodes -}}
{{ range $devname, $netdev := $node.NetDevs -}} {{ range $devname, $netdev := $node.NetDevs -}}
{{ if and $netdev.Ipaddr $netdev.Hwaddr -}} {{ if and $netdev.Ipaddr $netdev.Hwaddr -}}

View File

@@ -1,9 +1,34 @@
# This file was autgenerated by warewulf # This file was autgenerated by warewulf
{{ nobackup }} {{ nobackup }}
dhcp-range={{$.Dhcp.Range6Start}},{{$.Dhcp.Range6End}},slaac,6h {{ if .Ipv6 -}}
dhcp-option=option6:bootfile-url,tftp://[{{$.Ipaddr6}}]/ # enable services
dhcp-option=option6:ntp-server,[{{$.Ipaddr6}}] 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 $node := $.AllNodes -}}
{{ range $devname, $netdev := $node.NetDevs -}} {{ range $devname, $netdev := $node.NetDevs -}}
{{ if and $netdev.Ipaddr6 $netdev.Hwaddr -}} {{ 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 -}}
{{ end -}} {{ end -}}
{{ end -}}

View File

@@ -0,0 +1,7 @@
# This file was autgenerated by warewulf
{{ nobackup }}
listen-address={{ .Ipaddr }}
{{ if .Ipaddr6 -}}
listen-address={{ .Ipaddr6 }}
interface=*
{{ end -}}