mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-09 22:51:12 +02:00
This includes a healthy refactor of the networkd code as well. - Move netlink functionality to nic package - Networkd facilitates the orchestration of the underlying interface configuration - Networkd now stores the state of each interface configuration. This should allow us to expose this information via api in the future. Signed-off-by: Brad Beam <brad.beam@talos-systems.com>
306 lines
7.2 KiB
Go
306 lines
7.2 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
// Package nic provides a way to describe and configure a network interface.
|
|
package nic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/jsimonetti/rtnetlink"
|
|
"github.com/jsimonetti/rtnetlink/rtnl"
|
|
"github.com/mdlayher/netlink"
|
|
|
|
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
|
|
"github.com/talos-systems/talos/internal/pkg/kernel"
|
|
"github.com/talos-systems/talos/pkg/constants"
|
|
"github.com/talos-systems/talos/pkg/retry"
|
|
)
|
|
|
|
const (
|
|
// ref: https://tools.ietf.org/html/rfc791
|
|
|
|
// MinimumMTU is the lowest allowed MTU for an interface
|
|
MinimumMTU = 68
|
|
// MaximumMTU is the highest allowed MTU for an interface
|
|
MaximumMTU = 65536
|
|
)
|
|
|
|
// NetworkInterface provides an abstract configuration representation for a
|
|
// network interface.
|
|
type NetworkInterface struct {
|
|
Name string
|
|
Type int
|
|
Ignore bool
|
|
Bonded bool
|
|
MTU uint32
|
|
Link *net.Interface
|
|
SubInterfaces []*net.Interface
|
|
AddressMethod []address.Addressing
|
|
BondSettings *netlink.AttributeEncoder
|
|
|
|
rtConn *rtnetlink.Conn
|
|
rtnlConn *rtnl.Conn
|
|
}
|
|
|
|
// New returns a NetworkInterface with all of the given setter options applied.
|
|
func New(setters ...Option) (*NetworkInterface, error) {
|
|
// Default interface setup
|
|
iface := defaultOptions()
|
|
|
|
// Configure interface with any specified options
|
|
var result *multierror.Error
|
|
for _, setter := range setters {
|
|
result = multierror.Append(result, setter(iface))
|
|
}
|
|
|
|
// TODO: May need to look at switching this around to filter by Interface.HardwareAddr
|
|
// Ensure we have an interface name defined
|
|
if iface.Name == "" {
|
|
result = multierror.Append(result, errors.New("interface must have a name"))
|
|
}
|
|
|
|
// If no addressing methods have been configured, default to DHCP
|
|
if len(iface.AddressMethod) == 0 {
|
|
iface.AddressMethod = append(iface.AddressMethod, &address.DHCP{})
|
|
}
|
|
|
|
// Handle netlink connection
|
|
conn, err := rtnl.Dial(nil)
|
|
if err != nil {
|
|
result = multierror.Append(result, err)
|
|
return nil, result.ErrorOrNil()
|
|
}
|
|
|
|
iface.rtnlConn = conn
|
|
|
|
// Need rtnetlink for MTU and bond settings
|
|
nlConn, err := rtnetlink.Dial(nil)
|
|
if err != nil {
|
|
result = multierror.Append(result, err)
|
|
return nil, result.ErrorOrNil()
|
|
}
|
|
|
|
iface.rtConn = nlConn
|
|
|
|
return iface, result.ErrorOrNil()
|
|
}
|
|
|
|
// IsIgnored checks the network interface to see if it should be ignored and not configured.
|
|
func (n *NetworkInterface) IsIgnored() bool {
|
|
if n.Ignore || kernel.ProcCmdline().Get(constants.KernelParamNetworkInterfaceIgnore).Contains(n.Name) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Create creates the underlying link if it does not already exist.
|
|
func (n *NetworkInterface) Create() error {
|
|
iface, err := net.InterfaceByName(n.Name)
|
|
if err == nil {
|
|
n.Link = iface
|
|
return err
|
|
}
|
|
|
|
if err = n.createLink(); err != nil {
|
|
return err
|
|
}
|
|
|
|
iface, err = net.InterfaceByName(n.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
n.Link = iface
|
|
|
|
return nil
|
|
}
|
|
|
|
// Configure is used to set the link state and configure any necessary
|
|
// bond settings ( ex, mode ).
|
|
func (n *NetworkInterface) Configure() (err error) {
|
|
if n.IsIgnored() {
|
|
return err
|
|
}
|
|
|
|
if n.Bonded {
|
|
if err = n.configureBond(n.Link.Index, n.BondSettings); err != nil {
|
|
return err
|
|
}
|
|
|
|
bondIndex := proto.Uint32(uint32(n.Link.Index))
|
|
|
|
if err = n.enslaveLink(bondIndex, n.SubInterfaces...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err = n.rtnlConn.LinkUp(n.Link); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Wait for link to report up
|
|
err = retry.Exponential(30*time.Second, retry.WithUnits(250*time.Millisecond), retry.WithJitter(50*time.Millisecond)).Retry(func() error {
|
|
var link *net.Interface
|
|
|
|
link, err = n.rtnlConn.LinkByIndex(n.Link.Index)
|
|
if err != nil {
|
|
// nolint: errcheck
|
|
retry.UnexpectedError(err)
|
|
}
|
|
|
|
if link.Flags&net.FlagUp != net.FlagUp {
|
|
return retry.ExpectedError(fmt.Errorf("link is not up %s", n.Link.Name))
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to bring up interface%s: %w", n.Link.Name, err)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Addressing handles the address method for a configured interface ( dhcp/static ).
|
|
// This is inclusive of the address itself as well as any defined routes.
|
|
func (n *NetworkInterface) Addressing() error {
|
|
if n.IsIgnored() {
|
|
return nil
|
|
}
|
|
|
|
for _, method := range n.AddressMethod {
|
|
if err := n.configureInterface(method); err != nil {
|
|
// Treat as non fatal error when failing to configure an interface
|
|
return nil
|
|
}
|
|
|
|
if !method.Valid() {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Renew is the mechanism for keeping a dhcp lease active.
|
|
func (n *NetworkInterface) Renew() {
|
|
for _, method := range n.AddressMethod {
|
|
if method.TTL() == 0 {
|
|
continue
|
|
}
|
|
|
|
go n.renew(method)
|
|
}
|
|
}
|
|
|
|
// renew sets up the looping to ensure we keep the addressing information
|
|
// up to date. We attempt to do our first reconfiguration halfway through
|
|
// address TTL. If that fails, we'll continue to attempt to retry every
|
|
// halflife.
|
|
func (n *NetworkInterface) renew(method address.Addressing) {
|
|
renewDuration := method.TTL() / 2
|
|
|
|
var err error
|
|
|
|
for {
|
|
<-time.After(renewDuration)
|
|
|
|
if err = n.configureInterface(method); err != nil {
|
|
renewDuration = (renewDuration / 2)
|
|
} else {
|
|
renewDuration = method.TTL() / 2
|
|
}
|
|
}
|
|
}
|
|
|
|
// configureInterface handles the actual address discovery mechanism and
|
|
// netlink interaction to configure the interface.
|
|
// nolint: gocyclo
|
|
func (n *NetworkInterface) configureInterface(method address.Addressing) error {
|
|
var err error
|
|
if err = method.Discover(context.Background(), n.Link); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set link MTU if we got a response
|
|
if err = n.setMTU(method.Link().Index, method.MTU()); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check to see if we need to configure the address
|
|
addrs, err := n.rtnlConn.Addrs(method.Link(), method.Family())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
addressExists := false
|
|
|
|
for _, addr := range addrs {
|
|
if method.Address().String() == addr.String() {
|
|
addressExists = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !addressExists {
|
|
if err = n.rtnlConn.AddrAdd(method.Link(), method.Address()); err != nil {
|
|
switch err := err.(type) {
|
|
case *netlink.OpError:
|
|
if !os.IsExist(err.Err) && err.Err != syscall.ESRCH {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("failed to add address (already exists) %+v to %s: %v", method.Address(), method.Link().Name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add any routes
|
|
for _, r := range method.Routes() {
|
|
// Any errors here would be non-fatal, so we'll just ignore them
|
|
// nolint: errcheck
|
|
n.rtnlConn.RouteAddSrc(method.Link(), *r.Dest, method.Address(), r.Router)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reset removes addressing configuration from a given link.
|
|
func (n *NetworkInterface) Reset() {
|
|
var (
|
|
err error
|
|
link *net.Interface
|
|
nets []*net.IPNet
|
|
)
|
|
|
|
link, err = net.InterfaceByName(n.Name)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if nets, err = n.rtnlConn.Addrs(link, 0); err != nil {
|
|
return
|
|
}
|
|
|
|
for _, ipnet := range nets {
|
|
if err = n.rtnlConn.AddrDel(link, ipnet); err != nil {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// nolint: errcheck
|
|
n.rtnlConn.LinkDown(link)
|
|
}
|