diff --git a/docs/website/content/v0.7/en/configuration/v1alpha1.md b/docs/website/content/v0.7/en/configuration/v1alpha1.md index 4c90fac0a..1a9cf8a63 100644 --- a/docs/website/content/v0.7/en/configuration/v1alpha1.md +++ b/docs/website/content/v0.7/en/configuration/v1alpha1.md @@ -1320,6 +1320,22 @@ Type: `bool` Indicates if the interface is a dummy interface. Type: `bool` +#### dhcpOptions + +DHCP specific options. +DHCP *must* be set to true for these to take effect. + +Type: `DHCPOptions` + +--- + +### DHCPOptions + +#### routeMetric + +The priority of all routes received via DHCP +Type: `uint32` + --- ### Bond diff --git a/internal/app/networkd/pkg/address/dhcp.go b/internal/app/networkd/pkg/address/dhcp.go index 286e7790a..af2a5fea5 100644 --- a/internal/app/networkd/pkg/address/dhcp.go +++ b/internal/app/networkd/pkg/address/dhcp.go @@ -18,13 +18,15 @@ import ( "github.com/talos-systems/go-procfs/procfs" + "github.com/talos-systems/talos/pkg/machinery/config" "github.com/talos-systems/talos/pkg/machinery/constants" ) // DHCP implements the Addressing interface. type DHCP struct { - Ack *dhcpv4.DHCPv4 - NetIf *net.Interface + Ack *dhcpv4.DHCPv4 + NetIf *net.Interface + DHCPOptions config.DHCPOptions } // Name returns back the name of the address method. diff --git a/internal/app/networkd/pkg/networkd/netconf.go b/internal/app/networkd/pkg/networkd/netconf.go index 2ddabf58c..a5f27c456 100644 --- a/internal/app/networkd/pkg/networkd/netconf.go +++ b/internal/app/networkd/pkg/networkd/netconf.go @@ -46,7 +46,7 @@ func buildOptions(device config.Device, hostname string) (name string, opts []ni opts = append(opts, nic.WithAddressing(s)) case device.DHCP(): - d := &address.DHCP{} + d := &address.DHCP{DHCPOptions: device.DHCPOptions()} opts = append(opts, nic.WithAddressing(d)) default: // Allow master interface without any addressing if VLANs exist diff --git a/internal/app/networkd/pkg/nic/nic.go b/internal/app/networkd/pkg/nic/nic.go index 4529a522e..f8b60cdbf 100644 --- a/internal/app/networkd/pkg/nic/nic.go +++ b/internal/app/networkd/pkg/nic/nic.go @@ -17,6 +17,7 @@ import ( "time" "github.com/hashicorp/go-multierror" + "github.com/insomniacslk/dhcp/dhcpv4" "github.com/jsimonetti/rtnetlink" "github.com/jsimonetti/rtnetlink/rtnl" "github.com/mdlayher/netlink" @@ -338,7 +339,9 @@ func (n *NetworkInterface) configureInterface(method address.Addressing, link *n if method.Address() != nil { // Check to see if we need to configure the address - addrs, err := n.rtnlConn.Addrs(method.Link(), method.Family()) + var addrs []*net.IPNet + + addrs, err = n.rtnlConn.Addrs(method.Link(), method.Family()) if err != nil { return err } @@ -368,20 +371,9 @@ func (n *NetworkInterface) configureInterface(method address.Addressing, link *n // Add any routes for _, r := range method.Routes() { - // If gateway/router is 0.0.0.0 we'll set to nil so route scope decision will be correct - gw := r.Router - if net.IPv4zero.Equal(gw) { - gw = nil - } - - src := method.Address() - // if destination is the ipv6 route,and gateway is LL do not pass a src address to set the default geteway - if net.IPv6zero.Equal(r.Dest.IP) && gw.IsLinkLocalUnicast() { - src = nil - } - - if err := n.rtnlConn.RouteAddSrc(method.Link(), *r.Dest, src, gw); err != nil { - log.Println("failed to configure route: " + err.Error()) + err = n.addRoute(method, r) + if err != nil { + return err } } @@ -414,3 +406,89 @@ func (n *NetworkInterface) Reset() { // nolint: errcheck n.rtnlConn.LinkDown(link) } + +// addRoute is a function loosely copied from https://github.com/jsimonetti/rtnetlink/blob/154ecd417600f79d7847278a1e984056dc647acc/rtnl/route.go +// and merged with our logic on determining gw, src, dst, etc. +// we need this b/c we need to craft the route message ourselves to add attributes. +// nolint: gocyclo +func (n *NetworkInterface) addRoute(method address.Addressing, r *dhcpv4.Route) error { + dst := *r.Dest + + // If gateway/router is 0.0.0.0 we'll set to nil so route scope decision will be correct + gw := r.Router + if net.IPv4zero.Equal(gw) { + gw = nil + } + + src := method.Address() + // if destination is the ipv6 route,and gateway is LL do not pass a src address to set the default geteway + if net.IPv6zero.Equal(r.Dest.IP) && gw.IsLinkLocalUnicast() { + src = nil + } + + // determine if this is ipv4 or 6 + var af int + if dst.IP.To4() != nil { + af = unix.AF_INET + } else if len(dst.IP) == net.IPv6len { + af = unix.AF_INET6 + } + + ifc := method.Link() + + // Determine scope + var scope uint8 + + switch { + case gw != nil: + scope = unix.RT_SCOPE_UNIVERSE + + case len(dst.IP) == net.IPv6len && dst.IP.To4() == nil: + scope = unix.RT_SCOPE_UNIVERSE + + default: + // Set default scope to LINK + scope = unix.RT_SCOPE_LINK + } + + attr := rtnetlink.RouteAttributes{ + Dst: dst.IP, + OutIface: uint32(ifc.Index), + } + + // Set DHCP specific options + if dhcpObj, ok := method.(*address.DHCP); ok { + if dhcpObj.DHCPOptions != nil { + attr.Priority = dhcpObj.DHCPOptions.RouteMetric() + } + + if attr.Priority == uint32(0) { + attr.Priority = uint32(1024) + } + } + + if gw != nil { + attr.Gateway = gw + } + + var srclen int + if src != nil { + srclen, _ = src.Mask.Size() + attr.Src = src.IP + } + + dstlen, _ := dst.Mask.Size() + + tx := &rtnetlink.RouteMessage{ + Family: uint8(af), + Table: unix.RT_TABLE_MAIN, + Protocol: unix.RTPROT_BOOT, + Type: unix.RTN_UNICAST, + Scope: scope, + DstLength: uint8(dstlen), + SrcLength: uint8(srclen), + Attributes: attr, + } + + return n.rtnlConn.Conn.Route.Add(tx) +} diff --git a/pkg/machinery/config/provider.go b/pkg/machinery/config/provider.go index 27dc33371..0e45b3a31 100644 --- a/pkg/machinery/config/provider.go +++ b/pkg/machinery/config/provider.go @@ -115,6 +115,12 @@ type Device interface { DHCP() bool Ignore() bool Dummy() bool + DHCPOptions() DHCPOptions +} + +// DHCPOptions represents a set of DHCP options. +type DHCPOptions interface { + RouteMetric() uint32 } // Bond contains the various options for configuring a diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go index c5351fc00..3861f0241 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go @@ -749,6 +749,23 @@ func (d *Device) Dummy() bool { return d.DeviceDummy } +// DHCPOptions implements the MachineNetwork interface. +func (d *Device) DHCPOptions() config.DHCPOptions { + // Default route metric on systemd is 1024. This sets the same. + if d.DeviceDHCPOptions == nil { + return &DHCPOptions{ + DHCPRouteMetric: uint32(0), + } + } + + return d.DeviceDHCPOptions +} + +// RouteMetric implements the MachineNetwork interface. +func (d *DHCPOptions) RouteMetric() uint32 { + return d.DHCPRouteMetric +} + // Network implements the MachineNetwork interface. func (r *Route) Network() string { return r.RouteNetwork diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go index 6083bf09b..58910687a 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go @@ -832,6 +832,16 @@ type Device struct { DeviceIgnore bool `yaml:"ignore"` // description: Indicates if the interface is a dummy interface. DeviceDummy bool `yaml:"dummy"` + // description: | + // DHCP specific options. + // DHCP *must* be set to true for these to take effect. + DeviceDHCPOptions *DHCPOptions `yaml:"dhcpOptions"` +} + +// DHCPOptions contains options for configuring the DHCP settings for a given interface. +type DHCPOptions struct { + // description: The priority of all routes received via DHCP + DHCPRouteMetric uint32 `yaml:"routeMetric"` } // Bond contains the various options for configuring a