mirror of
https://github.com/siderolabs/sidero.git
synced 2025-11-01 17:01:08 +01:00
268 lines
6.2 KiB
Go
268 lines
6.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 main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
|
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
|
"github.com/jsimonetti/rtnetlink"
|
|
"github.com/siderolabs/gen/xslices"
|
|
"github.com/siderolabs/go-procfs/procfs"
|
|
"github.com/siderolabs/go-retry/retry"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/siderolabs/sidero/app/sidero-controller-manager/pkg/constants"
|
|
)
|
|
|
|
func setupNetworking() error {
|
|
var bootMACAddress string
|
|
|
|
if found := procfs.ProcCmdline().Get(constants.AgentMACArg).First(); found != nil {
|
|
bootMACAddress = *found
|
|
} else {
|
|
return fmt.Errorf("no MAC found")
|
|
}
|
|
|
|
link, err := waitForLink(bootMACAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = brinkLinkUp(link.Index); err != nil {
|
|
return err
|
|
}
|
|
|
|
return runDHCP(link)
|
|
}
|
|
|
|
func waitForLink(hwaddr string) (net.Interface, error) {
|
|
log.Printf("waiting for network link with MAC %q...", hwaddr)
|
|
|
|
var foundLink net.Interface
|
|
|
|
err := retry.Constant(time.Minute, retry.WithUnits(time.Second)).Retry(func() error {
|
|
links, err := net.Interfaces()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, link := range links {
|
|
if link.HardwareAddr.String() == hwaddr {
|
|
foundLink = link
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return retry.ExpectedErrorf("link with MAC %q not found", hwaddr)
|
|
})
|
|
|
|
return foundLink, err
|
|
}
|
|
|
|
func brinkLinkUp(linkIndex int) error {
|
|
log.Printf("bringing link up...")
|
|
|
|
conn, err := rtnetlink.Dial(nil)
|
|
if err != nil {
|
|
return fmt.Errorf("error dialing rtnetlink socket: %w", err)
|
|
}
|
|
|
|
defer conn.Close() //nolint:errcheck
|
|
|
|
if err := conn.Link.Set(&rtnetlink.LinkMessage{
|
|
Family: unix.AF_UNSPEC,
|
|
Type: unix.ARPHRD_ETHER,
|
|
Index: uint32(linkIndex),
|
|
Flags: unix.IFF_UP,
|
|
Change: unix.IFF_UP,
|
|
}); err != nil {
|
|
return fmt.Errorf("error setting link up: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runDHCP(link net.Interface) error {
|
|
log.Printf("running DHCP on %q...", link.Name)
|
|
|
|
var lease *nclient4.Lease
|
|
|
|
if err := retry.Constant(5*time.Minute,
|
|
retry.WithUnits(10*time.Second),
|
|
retry.WithAttemptTimeout(30*time.Second),
|
|
).RetryWithContext(context.Background(), func(ctx context.Context) error {
|
|
var err error
|
|
|
|
lease, err = acquireLease(ctx, link.Name)
|
|
if err != nil {
|
|
return retry.ExpectedError(err)
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("got DHCP lease: %s", lease.ACK.Summary())
|
|
|
|
return configureNetworking(link.Index, lease)
|
|
}
|
|
|
|
func acquireLease(ctx context.Context, linkName string) (*nclient4.Lease, error) {
|
|
opts := []dhcpv4.OptionCode{
|
|
dhcpv4.OptionClasslessStaticRoute,
|
|
dhcpv4.OptionDomainNameServer,
|
|
dhcpv4.OptionInterfaceMTU,
|
|
dhcpv4.OptionHostName,
|
|
}
|
|
|
|
mods := []dhcpv4.Modifier{dhcpv4.WithRequestedOptions(opts...)}
|
|
|
|
cli, err := nclient4.New(linkName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating DHCP client: %w", err)
|
|
}
|
|
|
|
//nolint:errcheck
|
|
defer cli.Close()
|
|
|
|
lease, err := cli.Request(ctx, mods...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error requesting DHCP lease: %w", err)
|
|
}
|
|
|
|
return lease, nil
|
|
}
|
|
|
|
func configureNetworking(linkIndex int, lease *nclient4.Lease) error {
|
|
conn, err := rtnetlink.Dial(nil)
|
|
if err != nil {
|
|
return fmt.Errorf("error dialing rtnetlink socket: %w", err)
|
|
}
|
|
|
|
defer conn.Close() //nolint:errcheck
|
|
|
|
if err := conn.Link.Set(&rtnetlink.LinkMessage{
|
|
Family: unix.AF_UNSPEC,
|
|
Type: unix.ARPHRD_ETHER,
|
|
Index: uint32(linkIndex),
|
|
Flags: unix.IFF_UP,
|
|
Change: unix.IFF_UP,
|
|
}); err != nil {
|
|
return fmt.Errorf("error setting link up: %w", err)
|
|
}
|
|
|
|
prefixLen, _ := lease.ACK.SubnetMask().Size()
|
|
|
|
log.Printf("assigning address %s/%d", lease.ACK.YourIPAddr, prefixLen)
|
|
|
|
if err := conn.Address.New(&rtnetlink.AddressMessage{
|
|
Family: unix.AF_INET,
|
|
PrefixLength: uint8(prefixLen),
|
|
Scope: unix.RT_SCOPE_UNIVERSE,
|
|
Index: uint32(linkIndex),
|
|
Attributes: &rtnetlink.AddressAttributes{
|
|
Address: lease.ACK.YourIPAddr,
|
|
Local: lease.ACK.YourIPAddr,
|
|
},
|
|
}); err != nil {
|
|
return fmt.Errorf("error adding address: %w", err)
|
|
}
|
|
|
|
mtu, err := dhcpv4.GetUint16(dhcpv4.OptionInterfaceMTU, lease.ACK.Options)
|
|
if err == nil {
|
|
log.Printf("setting MTU to %d", mtu)
|
|
|
|
if err := conn.Link.Set(&rtnetlink.LinkMessage{
|
|
Family: unix.AF_UNSPEC,
|
|
Type: unix.ARPHRD_ETHER,
|
|
Index: uint32(linkIndex),
|
|
Attributes: &rtnetlink.LinkAttributes{
|
|
MTU: uint32(mtu),
|
|
},
|
|
}); err != nil {
|
|
return fmt.Errorf("error setting MTU: %w", err)
|
|
}
|
|
}
|
|
|
|
addRoute := func(destination *net.IPNet, gateway net.IP) error {
|
|
log.Printf("adding route %s via %s", destination, gateway)
|
|
|
|
var dstLength int
|
|
|
|
if destination != nil {
|
|
dstLength, _ = destination.Mask.Size()
|
|
}
|
|
|
|
var dstAddr net.IP
|
|
|
|
if destination != nil {
|
|
dstAddr = destination.IP
|
|
}
|
|
|
|
if err := conn.Route.Add(&rtnetlink.RouteMessage{
|
|
Family: unix.AF_INET,
|
|
DstLength: uint8(dstLength),
|
|
Scope: unix.RT_SCOPE_UNIVERSE,
|
|
Table: unix.RT_TABLE_MAIN,
|
|
Protocol: unix.RTPROT_BOOT,
|
|
Type: unix.RTN_UNICAST,
|
|
Attributes: rtnetlink.RouteAttributes{
|
|
Dst: dstAddr,
|
|
Gateway: gateway,
|
|
OutIface: uint32(linkIndex),
|
|
},
|
|
}); err != nil {
|
|
return fmt.Errorf("error adding route: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if len(lease.ACK.ClasslessStaticRoute()) > 0 {
|
|
for _, route := range lease.ACK.ClasslessStaticRoute() {
|
|
if err := addRoute(route.Dest, route.Router); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
for _, router := range lease.ACK.Router() {
|
|
if err := addRoute(nil, router); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if lease.ACK.HostName() != "" {
|
|
log.Printf("setting hostname to %q", lease.ACK.HostName())
|
|
|
|
unix.Sethostname([]byte(lease.ACK.HostName())) //nolint:errcheck
|
|
}
|
|
|
|
if len(lease.ACK.DNS()) > 0 {
|
|
log.Printf("setting DNS servers to %s", lease.ACK.DNS())
|
|
|
|
contents := strings.Join(xslices.Map(lease.ACK.DNS(),
|
|
func(ns net.IP) string {
|
|
return fmt.Sprintf("nameserver %s\n", ns)
|
|
}), "")
|
|
|
|
if err := os.WriteFile("/etc/resolv.conf", []byte(contents), 0o777); err != nil {
|
|
return fmt.Errorf("error writing /etc/resolv.conf: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|