Skip to content

Commit

Permalink
feat: support link altnames/aliases
Browse files Browse the repository at this point in the history
At the moment, we don't use/support aliases, but we might in the future.

Altnames are filled out by `systemd-udevd`.

This PR has two parts:

* show aliases & altnames in `LinkStatus`
* match links by aliases/altnames when we configure
  addresses/routes/links

This should make a transition to `systemd-udevd` less painful if the
previous link name is in `altNames`.

Forked rtnetlink for jsimonetti/rtnetlink#241

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
smira committed Dec 17, 2024
1 parent 5bfd829 commit 284ab11
Show file tree
Hide file tree
Showing 19 changed files with 1,001 additions and 458 deletions.
2 changes: 2 additions & 0 deletions api/resource/definitions/network/network.proto
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ message LinkStatusSpec {
BondMasterSpec bond_master = 28;
WireguardSpec wireguard = 29;
bytes permanent_addr = 30;
string alias = 31;
repeated string alt_names = 32;
}

// NfTablesAddressMatch describes the match on the IP address.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ require (
github.com/hetznercloud/hcloud-go/v2 v2.17.0
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
github.com/jeromer/syslogparser v1.1.0
github.com/jsimonetti/rtnetlink/v2 v2.0.2
github.com/jsimonetti/rtnetlink/v2 v2.0.3-0.20241216183107-2d6e9f8ad3f2
github.com/jxskiss/base62 v1.1.0
github.com/klauspost/compress v1.17.11
github.com/klauspost/cpuid/v2 v2.2.9
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmK
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA=
github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00=
github.com/jsimonetti/rtnetlink/v2 v2.0.2 h1:ZKlbCujrIpp4/u3V2Ka0oxlf4BCkt6ojkvpy3nZoCBY=
github.com/jsimonetti/rtnetlink/v2 v2.0.2/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
github.com/jsimonetti/rtnetlink/v2 v2.0.3-0.20241216183107-2d6e9f8ad3f2 h1:4pspWog/mjnfv+B3rjEUfCoFL80T7J8ojK9ay8ApPCM=
github.com/jsimonetti/rtnetlink/v2 v2.0.3-0.20241216183107-2d6e9f8ad3f2/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
Expand Down
18 changes: 18 additions & 0 deletions internal/app/machined/pkg/controllers/network/address_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import (
"net"
"net/netip"
"os"
"slices"

"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/jsimonetti/rtnetlink/v2"
"github.com/mdlayher/arp"
"github.com/siderolabs/go-pointer"
"go.uber.org/zap"
"go4.org/netipx"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -117,12 +119,28 @@ func (ctrl *AddressSpecController) Run(ctx context.Context, r controller.Runtime
}

func resolveLinkName(links []rtnetlink.LinkMessage, linkName string) uint32 {
if linkName == "" {
return 0 // should never match
}

// first, lookup by name
for _, link := range links {
if link.Attributes.Name == linkName {
return link.Index
}
}

// then, lookup by alias/altname
for _, link := range links {
if pointer.SafeDeref(link.Attributes.Alias) == linkName {
return link.Index
}

if slices.Index(link.Attributes.AltNames, linkName) != -1 {
return link.Index
}
}

return 0
}

Expand Down
68 changes: 68 additions & 0 deletions internal/app/machined/pkg/controllers/network/address_spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,74 @@ func (suite *AddressSpecSuite) TestDummy() {
}
}

func (suite *AddressSpecSuite) TestDummyAlias() {
dummyInterface := suite.uniqueDummyInterface()
dummyAlias := suite.uniqueDummyInterface()

suite.T().Logf("dummyInterface: %s, dummyAlias: %s", dummyInterface, dummyAlias)

conn, err := rtnetlink.Dial(nil)
suite.Require().NoError(err)

defer conn.Close() //nolint:errcheck

dummy := network.NewAddressSpec(network.NamespaceName, "dummy/10.0.0.5/8")
*dummy.TypedSpec() = network.AddressSpecSpec{
Address: netip.MustParsePrefix("10.0.0.5/8"),
LinkName: dummyAlias, // use alias name instead of the actual interface name
Family: nethelpers.FamilyInet4,
Scope: nethelpers.ScopeGlobal,
ConfigLayer: network.ConfigDefault,
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
}

// it's fine to create the address before the interface is actually created
for _, res := range []resource.Resource{dummy} {
suite.Require().NoError(suite.state.Create(suite.ctx, res), "%v", res.Spec())
}

// create dummy interface
suite.Require().NoError(
conn.Link.New(
&rtnetlink.LinkMessage{
Type: unix.ARPHRD_ETHER,
Attributes: &rtnetlink.LinkAttributes{
Name: dummyInterface,
MTU: 1400,
Info: &rtnetlink.LinkInfo{
Kind: "dummy",
},
},
},
),
)

iface, err := net.InterfaceByName(dummyInterface)
suite.Require().NoError(err)

// set alias name
suite.Require().NoError(
conn.Link.Set(
&rtnetlink.LinkMessage{
Index: uint32(iface.Index),
Attributes: &rtnetlink.LinkAttributes{
Alias: &dummyAlias,
},
},
),
)

defer conn.Link.Delete(uint32(iface.Index)) //nolint:errcheck

suite.Assert().NoError(
retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
func() error {
return suite.assertLinkAddress(dummyInterface, "10.0.0.5/8")
},
),
)
}

func (suite *AddressSpecSuite) TearDownTest() {
suite.T().Log("tear down")

Expand Down
47 changes: 31 additions & 16 deletions internal/app/machined/pkg/controllers/network/link_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,27 +183,42 @@ func (ctrl *LinkConfigController) Run(ctx context.Context, r controller.Runtime,
return fmt.Errorf("error listing link statuses: %w", err)
}

outer:
for _, item := range list.Items {
linkStatus := item.(*network.LinkStatus) //nolint:forcetypeassert

if _, configured := configuredLinks[linkStatus.Metadata().ID()]; !configured {
if linkStatus.TypedSpec().Physical() {
var ids []string
if _, configured := configuredLinks[linkStatus.Metadata().ID()]; configured {
continue
}

ids, err = ctrl.apply(ctx, r, []network.LinkSpecSpec{
{
Name: linkStatus.Metadata().ID(),
Up: true,
ConfigLayer: network.ConfigDefault,
},
})
if err != nil {
return fmt.Errorf("error applying default link up: %w", err)
}
if linkStatus.TypedSpec().Alias != "" {
if _, configured := configuredLinks[linkStatus.TypedSpec().Alias]; configured {
continue
}
}

for _, id := range ids {
touchedIDs[id] = struct{}{}
}
for _, altName := range linkStatus.TypedSpec().AltNames {
if _, configured := configuredLinks[altName]; configured {
continue outer
}
}

if linkStatus.TypedSpec().Physical() {
var ids []string

ids, err = ctrl.apply(ctx, r, []network.LinkSpecSpec{
{
Name: linkStatus.Metadata().ID(),
Up: true,
ConfigLayer: network.ConfigDefault,
},
})
if err != nil {
return fmt.Errorf("error applying default link up: %w", err)
}

for _, id := range ids {
touchedIDs[id] = struct{}{}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,15 @@ func (suite *LinkConfigSuite) TestDefaultUp() {
),
)

for _, link := range []string{"eth0", "eth1", "eth2", "eth3", "eth4"} {
for _, link := range []string{"eth5", "eth1", "eth2", "eth3", "eth4"} {
linkStatus := network.NewLinkStatus(network.NamespaceName, link)
linkStatus.TypedSpec().Type = nethelpers.LinkEther
linkStatus.TypedSpec().LinkState = true

if link == "eth5" {
linkStatus.TypedSpec().AltNames = []string{"eth0"}
}

suite.Require().NoError(suite.state.Create(suite.ctx, linkStatus))
}

Expand Down
33 changes: 26 additions & 7 deletions internal/app/machined/pkg/controllers/network/link_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"errors"
"fmt"
"slices"

"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource"
Expand Down Expand Up @@ -159,13 +160,31 @@ func SortBonds(items *safe.List[*network.LinkSpec]) {
})
}

func findLink(links []rtnetlink.LinkMessage, name string) *rtnetlink.LinkMessage {
func findLink(links []rtnetlink.LinkMessage, name string, allowAliases bool) *rtnetlink.LinkMessage {
if name == "" {
return nil // should never match
}

for i, link := range links {
if link.Attributes.Name == name {
return &links[i]
}
}

if !allowAliases {
return nil
}

for i, link := range links {
if pointer.SafeDeref(link.Attributes.Alias) == name {
return &links[i]
}

if slices.Index(link.Attributes.AltNames, name) != -1 {
return &links[i]
}
}

return nil
}

Expand Down Expand Up @@ -202,7 +221,7 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti
case resource.PhaseTearingDown:
// TODO: should we bring link down if it's physical and the spec was torn down?
if link.TypedSpec().Logical {
existing := findLink(*links, link.TypedSpec().Name)
existing := findLink(*links, link.TypedSpec().Name, false) // logical links don't have aliases

if existing != nil {
if err := conn.Link.Delete(existing.Index); err != nil {
Expand All @@ -226,7 +245,7 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti
return fmt.Errorf("error removing finalizer: %w", err)
}
case resource.PhaseRunning:
existing := findLink(*links, link.TypedSpec().Name)
existing := findLink(*links, link.TypedSpec().Name, !link.TypedSpec().Logical) // allow aliases for physical links

var existingRawLinkData []byte

Expand Down Expand Up @@ -312,7 +331,7 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti

// VLAN settings should be set on interface creation (parent + VLAN settings)
if link.TypedSpec().ParentName != "" {
parent := findLink(*links, link.TypedSpec().ParentName)
parent := findLink(*links, link.TypedSpec().ParentName, true) // allow aliases for physical links/parents
if parent == nil {
// parent doesn't exist yet, skip it
return nil
Expand Down Expand Up @@ -353,7 +372,7 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti
return fmt.Errorf("error listing links: %w", err)
}

existing = findLink(*links, link.TypedSpec().Name)
existing = findLink(*links, link.TypedSpec().Name, false) // link is created by name
if existing == nil {
return fmt.Errorf("created link %q not found in the link list", link.TypedSpec().Name)
}
Expand Down Expand Up @@ -596,15 +615,15 @@ func (ctrl *LinkSpecController) syncLink(ctx context.Context, r controller.Runti

bondMasterName := link.TypedSpec().BondSlave.MasterName
if bondMasterName != "" {
if master := findLink(*links, bondMasterName); master != nil {
if master := findLink(*links, bondMasterName, false); master != nil { // bond master can't be an alias
masterName = bondMasterName
masterIndex = master.Index
}
}

bridgeMasterName := link.TypedSpec().BridgeSlave.MasterName
if bridgeMasterName != "" {
if master := findLink(*links, bridgeMasterName); master != nil {
if master := findLink(*links, bridgeMasterName, false); master != nil { // bridge master can't be an alias
masterName = bridgeMasterName
masterIndex = master.Index
}
Expand Down
Loading

0 comments on commit 284ab11

Please sign in to comment.