From 57bf9d0a47b5d25a1019d2336e0daaa389d303f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 2 Dec 2025 12:28:52 -0500 Subject: [PATCH 1/5] incus-osd/api: Add StrictHwaddr interface flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- incus-osd/api/system_network.go | 1 + 1 file changed, 1 insertion(+) diff --git a/incus-osd/api/system_network.go b/incus-osd/api/system_network.go index 19db720e..9f972cc0 100644 --- a/incus-osd/api/system_network.go +++ b/incus-osd/api/system_network.go @@ -47,6 +47,7 @@ type SystemNetworkInterface struct { Hwaddr string `json:"hwaddr" yaml:"hwaddr"` Roles []string `json:"roles,omitempty" yaml:"roles,omitempty"` LLDP bool `json:"lldp" yaml:"lldp"` + StrictHwaddr bool `json:"strict_hwaddr" yaml:"strict_hwaddr"` } // SystemNetworkBond contains information about a network bond. From f6ae80e8d233e560b73676e20236b98a0bd76057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 2 Dec 2025 12:29:11 -0500 Subject: [PATCH 2/5] incus-osd/nftables: Add initial package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- incus-osd/internal/nftables/doc.go | 2 ++ incus-osd/internal/nftables/nft.go | 58 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 incus-osd/internal/nftables/doc.go create mode 100644 incus-osd/internal/nftables/nft.go diff --git a/incus-osd/internal/nftables/doc.go b/incus-osd/internal/nftables/doc.go new file mode 100644 index 00000000..a640011c --- /dev/null +++ b/incus-osd/internal/nftables/doc.go @@ -0,0 +1,2 @@ +// Package nftables offers functions to manage the nftables firewall. +package nftables diff --git a/incus-osd/internal/nftables/nft.go b/incus-osd/internal/nftables/nft.go new file mode 100644 index 00000000..1f4829de --- /dev/null +++ b/incus-osd/internal/nftables/nft.go @@ -0,0 +1,58 @@ +package nftables + +import ( + "context" + "strings" + + "github.com/lxc/incus/v6/shared/subprocess" + + "github.com/lxc/incus-os/incus-osd/api" +) + +// SetupChains creates the initial system-wide chains. +func SetupChains(ctx context.Context) error { + // Ensure we have a bridge table. + _, err := subprocess.RunCommandContext(ctx, "nft", "add", "table", "bridge", "incus-osd") + if err != nil { + return err + } + + // Ensure we have a MAC filtering chain. + _, err = subprocess.RunCommandContext(ctx, "nft", "add", "chain", "bridge", "incus-osd", "mac-filters", "{ type filter hook output priority 0 ; policy accept ; }") + if err != nil { + return err + } + + return nil +} + +// ApplyHwaddrFilters ensures that all interfaces with the StrictHwaddr flag set get a suitable MAC filter in place. +func ApplyHwaddrFilters(ctx context.Context, networkCfg *api.SystemNetworkConfig) error { + // Make sure we have the expected chains. + err := SetupChains(ctx) + if err != nil { + return err + } + + // Empty the chain. + _, err = subprocess.RunCommandContext(ctx, "nft", "flush", "chain", "bridge", "incus-osd", "mac-filters") + if err != nil { + return err + } + + // Apply the filters. + for _, iface := range networkCfg.Interfaces { + if !iface.StrictHwaddr { + continue + } + + underlyingDevice := "_p" + strings.ToLower(strings.ReplaceAll(iface.Hwaddr, ":", "")) + + _, err = subprocess.RunCommandContext(ctx, "nft", "add", "rule", "bridge", "incus-osd", "mac-filters", "oif", underlyingDevice, "ether", "saddr", "!=", iface.Hwaddr, "drop") + if err != nil { + return err + } + } + + return nil +} From a74d36193c10ac74888bad0716bdfb40279a4bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 2 Dec 2025 12:29:28 -0500 Subject: [PATCH 3/5] incus-osd/rest: Call nftables MAC filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- incus-osd/internal/rest/api_system_network.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/incus-osd/internal/rest/api_system_network.go b/incus-osd/internal/rest/api_system_network.go index 84a0664b..8a140c39 100644 --- a/incus-osd/internal/rest/api_system_network.go +++ b/incus-osd/internal/rest/api_system_network.go @@ -8,6 +8,7 @@ import ( "time" "github.com/lxc/incus-os/incus-osd/api" + "github.com/lxc/incus-os/incus-osd/internal/nftables" "github.com/lxc/incus-os/incus-osd/internal/providers" "github.com/lxc/incus-os/incus-osd/internal/rest/response" "github.com/lxc/incus-os/incus-osd/internal/seed" @@ -124,6 +125,14 @@ func (s *Server) apiSystemNetwork(w http.ResponseWriter, r *http.Request) { slog.InfoContext(r.Context(), "Applying new network configuration") + err = nftables.ApplyHwaddrFilters(r.Context(), newConfig.Config) + if err != nil { + slog.ErrorContext(r.Context(), "Failed to update network configuration: "+err.Error()) + _ = response.InternalError(err).Render(w) + + return + } + err = systemd.ApplyNetworkConfiguration(r.Context(), s.state, newConfig.Config, 30*time.Second, false, providers.Refresh, false) if err != nil { slog.ErrorContext(r.Context(), "Failed to update network configuration: "+err.Error()) From 2a129e1d7339ad3b7e71b0d3365a1e4fad7bcdcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 2 Dec 2025 12:29:35 -0500 Subject: [PATCH 4/5] incus-osd: Call nftables MAC filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- incus-osd/cmd/incus-osd/main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/incus-osd/cmd/incus-osd/main.go b/incus-osd/cmd/incus-osd/main.go index 68eed670..7234ef49 100644 --- a/incus-osd/cmd/incus-osd/main.go +++ b/incus-osd/cmd/incus-osd/main.go @@ -21,6 +21,7 @@ import ( "github.com/lxc/incus-os/incus-osd/internal/applications" "github.com/lxc/incus-os/incus-osd/internal/install" "github.com/lxc/incus-os/incus-osd/internal/keyring" + "github.com/lxc/incus-os/incus-osd/internal/nftables" "github.com/lxc/incus-os/incus-osd/internal/providers" "github.com/lxc/incus-os/incus-osd/internal/recovery" "github.com/lxc/incus-os/incus-osd/internal/rest" @@ -359,6 +360,11 @@ func startup(ctx context.Context, s *state.State, t *tui.TUI) error { //nolint:r // Perform network configuration. slog.InfoContext(ctx, "Bringing up the network") + err = nftables.ApplyHwaddrFilters(ctx, s.System.Network.Config) + if err != nil { + return err + } + err = systemd.ApplyNetworkConfiguration(ctx, s, s.System.Network.Config, 30*time.Second, s.OS.SuccessfulBoot, providers.Refresh, delayInitialUpdateCheck) if err != nil { return err From e7c29dc9bebf9c0a8ffdd13abbef04cf1243280b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 2 Dec 2025 12:30:36 -0500 Subject: [PATCH 5/5] incus-osd/api: Make more network fields omitempty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- incus-osd/api/system_network.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/incus-osd/api/system_network.go b/incus-osd/api/system_network.go index 9f972cc0..7987f147 100644 --- a/incus-osd/api/system_network.go +++ b/incus-osd/api/system_network.go @@ -46,8 +46,8 @@ type SystemNetworkInterface struct { Routes []SystemNetworkRoute `json:"routes,omitempty" yaml:"routes,omitempty"` Hwaddr string `json:"hwaddr" yaml:"hwaddr"` Roles []string `json:"roles,omitempty" yaml:"roles,omitempty"` - LLDP bool `json:"lldp" yaml:"lldp"` - StrictHwaddr bool `json:"strict_hwaddr" yaml:"strict_hwaddr"` + LLDP bool `json:"lldp,omitempty" yaml:"lldp,omitempty"` + StrictHwaddr bool `json:"strict_hwaddr,omitempty" yaml:"strict_hwaddr,omitempty"` } // SystemNetworkBond contains information about a network bond. @@ -62,7 +62,7 @@ type SystemNetworkBond struct { Hwaddr string `json:"hwaddr,omitempty" yaml:"hwaddr,omitempty"` Members []string `json:"members,omitempty" yaml:"members,omitempty"` Roles []string `json:"roles,omitempty" yaml:"roles,omitempty"` - LLDP bool `json:"lldp" yaml:"lldp"` + LLDP bool `json:"lldp,omitempty" yaml:"lldp,omitempty"` } // SystemNetworkVLAN contains information about a network vlan.