From 7a9f080ceaa9fdcb2214305d6e8d362bc6f522ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Mon, 10 Feb 2025 22:54:10 -0500 Subject: [PATCH 1/7] github: Add linux-modules-extra (for vrf) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e6f9bd52f12..4bbfb085475 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -267,6 +267,7 @@ jobs: libsqlite3-dev \ libtool \ libudev-dev \ + linux-modules-extra-$(uname -r) \ make \ pkg-config\ acl \ From b0e83ef588d89bc1db9625a1d006cc3d1411fdfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Mon, 10 Feb 2025 23:09:23 -0500 Subject: [PATCH 2/7] api: instance_nic_routed_host_address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- doc/api-extensions.md | 3 +++ internal/version/api.go | 1 + 2 files changed, 4 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index e993538ad25..a5317878497 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -2699,3 +2699,6 @@ This API extension provides the ability to configure storage volumes in preseed ## `init_preseed_profile_project` This API extension provides the ability to specify the project as part of profile definitions in preseed init. + +## `instance_nic_routed_host_address` +Adds support for specifying the VRF to add the routes to. diff --git a/internal/version/api.go b/internal/version/api.go index 12506cc044b..82ed6e53fee 100644 --- a/internal/version/api.go +++ b/internal/version/api.go @@ -461,6 +461,7 @@ var APIExtensions = []string{ "instance_debug_memory", "init_preseed_storage_volumes", "init_preseed_profile_project", + `instance_nic_routed_host_address`, } // APIExtensionsCount returns the number of available API extensions. From b4eeafbf1e9100b8427ee327cfcf5cc810b24f11 Mon Sep 17 00:00:00 2001 From: Jakob Mueller Date: Wed, 29 Jan 2025 15:14:25 +0100 Subject: [PATCH 3/7] incus/server/ip: Add `Master` parameter for veth and tuntap Signed-off-by: Jakob Mueller --- internal/server/ip/link_veth.go | 22 ++++++++++++++++++++-- internal/server/ip/tuntap.go | 8 ++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/internal/server/ip/link_veth.go b/internal/server/ip/link_veth.go index bcd986e802e..13008f0fedc 100644 --- a/internal/server/ip/link_veth.go +++ b/internal/server/ip/link_veth.go @@ -1,12 +1,30 @@ package ip +import ( + "github.com/lxc/incus/v6/shared/subprocess" +) + // Veth represents arguments for link of type veth. type Veth struct { Link - Peer Link + Peer Link + Master string } // Add adds new virtual link. func (veth *Veth) Add() error { - return veth.Link.add("veth", append([]string{"peer"}, veth.Peer.args()...)) + err := veth.Link.add("veth", append([]string{"peer"}, veth.Peer.args()...)) + + if err != nil { + return err + } + + if veth.Master != "" { + _, err := subprocess.RunCommand("ip", "link", "set", veth.Name, "master", veth.Master) + if err != nil { + return err + } + } + + return nil } diff --git a/internal/server/ip/tuntap.go b/internal/server/ip/tuntap.go index a8ce7c63c44..83c88d1bbdb 100644 --- a/internal/server/ip/tuntap.go +++ b/internal/server/ip/tuntap.go @@ -9,6 +9,7 @@ type Tuntap struct { Name string Mode string MultiQueue bool + Master string } // Add adds new tuntap interface. @@ -23,5 +24,12 @@ func (t *Tuntap) Add() error { return err } + if t.Master != "" { + _, err := subprocess.RunCommand("ip", "link", "set", t.Name, "master", t.Master) + if err != nil { + return err + } + } + return nil } From 01d06f58f9158d8295e22c30e79a2086473f7ffd Mon Sep 17 00:00:00 2001 From: Jakob Mueller Date: Wed, 29 Jan 2025 15:15:19 +0100 Subject: [PATCH 4/7] incus/server/ip: Add VRF parameter for IP Route Signed-off-by: Jakob Mueller --- internal/server/ip/route.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/internal/server/ip/route.go b/internal/server/ip/route.go index 3d84ec9df59..feb020ab055 100644 --- a/internal/server/ip/route.go +++ b/internal/server/ip/route.go @@ -15,6 +15,7 @@ type Route struct { Proto string Family string Via string + VRF string } // Add adds new route. @@ -37,6 +38,10 @@ func (r *Route) Add() error { cmd = append(cmd, "proto", r.Proto) } + if r.VRF != "" { + cmd = append(cmd, "vrf", r.VRF) + } + _, err := subprocess.RunCommand("ip", cmd...) if err != nil { return err @@ -47,7 +52,15 @@ func (r *Route) Add() error { // Delete deletes routing table. func (r *Route) Delete() error { - _, err := subprocess.RunCommand("ip", r.Family, "route", "delete", "table", r.Table, r.Route, "dev", r.DevName) + cmd := []string{r.Family, "route", "delete", r.Route, "dev", r.DevName} + + if r.VRF != "" { + cmd = append(cmd, "vrf", r.VRF) + } else if r.Table != "" { + cmd = append(cmd, "table", r.Table) + } + + _, err := subprocess.RunCommand("ip", cmd...) if err != nil { return err } @@ -76,6 +89,10 @@ func (r *Route) Flush() error { cmd = append(cmd, "proto", r.Proto) } + if r.VRF != "" { + cmd = append(cmd, "vrf", r.VRF) + } + _, err := subprocess.RunCommand("ip", cmd...) if err != nil { return err @@ -87,6 +104,11 @@ func (r *Route) Flush() error { // Replace changes or adds new route. func (r *Route) Replace(routes []string) error { cmd := []string{r.Family, "route", "replace", "dev", r.DevName, "proto", r.Proto} + + if r.VRF != "" { + cmd = append(cmd, "vrf", r.VRF) + } + cmd = append(cmd, routes...) _, err := subprocess.RunCommand("ip", cmd...) if err != nil { @@ -99,7 +121,14 @@ func (r *Route) Replace(routes []string) error { // Show lists routes. func (r *Route) Show() ([]string, error) { routes := []string{} - out, err := subprocess.RunCommand("ip", r.Family, "route", "show", "dev", r.DevName, "proto", r.Proto) + + cmd := []string{r.Family, "route", "show", "dev", r.DevName, "proto", r.Proto} + + if r.VRF != "" { + cmd = append(cmd, "vrf", r.VRF) + } + + out, err := subprocess.RunCommand("ip", cmd...) if err != nil { return routes, err } From b3946e32c54b171b3b49f51084abe766412a5143 Mon Sep 17 00:00:00 2001 From: Jakob Mueller Date: Wed, 29 Jan 2025 15:17:05 +0100 Subject: [PATCH 5/7] incus/server/device: Add vrf parameter for routed NIC devices Signed-off-by: Jakob Mueller --- .../server/device/device_utils_network.go | 2 ++ internal/server/device/nic_routed.go | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/internal/server/device/device_utils_network.go b/internal/server/device/device_utils_network.go index c8ec9ceaa0c..1ff035ef35e 100644 --- a/internal/server/device/device_utils_network.go +++ b/internal/server/device/device_utils_network.go @@ -222,6 +222,7 @@ func networkCreateVethPair(hostName string, m deviceConfig.Device) (string, uint Peer: ip.Link{ Name: network.RandomDevName("veth"), }, + Master: m["vrf"], } // Set the MTU on both ends. @@ -308,6 +309,7 @@ func networkCreateTap(hostName string, m deviceConfig.Device) (uint32, error) { Name: hostName, Mode: "tap", MultiQueue: true, + Master: m["vrf"], } err := tuntap.Add() diff --git a/internal/server/device/nic_routed.go b/internal/server/device/nic_routed.go index f6ad089423e..ba9856f7ae4 100644 --- a/internal/server/device/nic_routed.go +++ b/internal/server/device/nic_routed.go @@ -80,6 +80,7 @@ func (d *nicRouted) validateConfig(instConf instance.ConfigReader) error { "ipv4.host_table", "ipv6.host_table", "gvrp", + "vrf", } rules := nicValidationRules(requiredFields, optionalFields, instConf) @@ -88,6 +89,7 @@ func (d *nicRouted) validateConfig(instConf instance.ConfigReader) error { rules["gvrp"] = validate.Optional(validate.IsBool) rules["ipv4.neighbor_probe"] = validate.Optional(validate.IsBool) rules["ipv6.neighbor_probe"] = validate.Optional(validate.IsBool) + rules["vrf"] = validate.Optional(validate.IsAny) err = d.config.Validate(rules) if err != nil { @@ -216,6 +218,13 @@ func (d *nicRouted) validateEnvironment() error { } } + if d.config["vrf"] != "" { + // Check if the vrf interface exists. + if !network.InterfaceExists(d.config["vrf"]) { + return fmt.Errorf("VRF %q doesn't exist", d.config["vrf"]) + } + } + return nil } @@ -405,14 +414,20 @@ func (d *nicRouted) Start() (*deviceConfig.RunConfig, error) { } } + table := "main" + if d.config["vrf"] != "" { + table = "" + } + // Perform per-address host-side configuration (static routes and neighbour proxy entries). for _, addrStr := range addresses { - // Apply host-side static routes to main routing table. + // Apply host-side static routes to main routing table or VRF. r := ip.Route{ DevName: saveData["host_name"], Route: fmt.Sprintf("%s/%d", addrStr, subnetSize), - Table: "main", + Table: table, Family: ipFamilyArg, + VRF: d.config["vrf"], } err = r.Add() @@ -462,13 +477,14 @@ func (d *nicRouted) Start() (*deviceConfig.RunConfig, error) { } // Add routes for _, routeStr := range routes { - // Apply host-side static routes to main routing table. + // Apply host-side static routes to main routing table or VRF. r := ip.Route{ DevName: saveData["host_name"], Route: routeStr, - Table: "main", + Table: table, Family: ipFamilyArg, Via: addresses[0], + VRF: d.config["vrf"], } err = r.Add() From 0477d9c3792ceaadc2adbe6bfbf30951799f9252 Mon Sep 17 00:00:00 2001 From: Jakob Mueller Date: Wed, 29 Jan 2025 15:17:32 +0100 Subject: [PATCH 6/7] tests: Add test for routed NIC with VRF Signed-off-by: Jakob Mueller --- test/suites/container_devices_nic_routed.sh | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/suites/container_devices_nic_routed.sh b/test/suites/container_devices_nic_routed.sh index edd672ebd3f..a8412f40587 100644 --- a/test/suites/container_devices_nic_routed.sh +++ b/test/suites/container_devices_nic_routed.sh @@ -217,6 +217,36 @@ test_container_devices_nic_routed() { ip -4 route show table 100 | grep "192.0.2.1${ipRand}" ip -6 route show table 101 | grep "2001:db8::1${ipRand}" + # Undo settings + incus stop -f "${ctName}" + incus config device unset "${ctName}" eth0 vlan + incus config device unset "${ctName}" eth0 ipv4.host_table + incus config device unset "${ctName}" eth0 ipv6.host_table + + # Add VRF + ip link add test_vrf type vrf table 120 + + # Check nic interface not in the vrf + ! ip link show master test_vrf | grep "veth" + + # Configure VRF on nic + incus config device set "${ctName}" eth0 vrf="test_vrf" + incus start "${ctName}" + + # Check nic interface is in the vrf + ip link show master test_vrf | grep "veth" + + # Check routes are in the vrf + ip -4 route show vrf test_vrf | grep "192.0.2.1${ipRand}" + ip -6 route show vrf test_vrf | grep "2001:db8::1${ipRand}" + + # Check no routes in the main table + ! ip -4 route show table main | grep "192.0.2.1${ipRand}" + ! ip -6 route show table main | grep "2001:db8::1${ipRand}" + + # Delete test VRF + ip link delete test_vrf + # Check volatile cleanup on stop. incus stop -f "${ctName}" if incus config show "${ctName}" | grep volatile.eth0 | grep -v volatile.eth0.hwaddr | grep -v volatile.eth0.name ; then From adede1116f008af1415b47ea9fb04e40cb4a451f Mon Sep 17 00:00:00 2001 From: Jakob Mueller Date: Wed, 29 Jan 2025 15:19:14 +0100 Subject: [PATCH 7/7] doc: Add description for routed-nic VRF parameter Signed-off-by: Jakob Mueller --- doc/.wordlist.txt | 1 + doc/reference/devices_nic.md | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/.wordlist.txt b/doc/.wordlist.txt index 67596265e59..899566351a4 100644 --- a/doc/.wordlist.txt +++ b/doc/.wordlist.txt @@ -326,6 +326,7 @@ VMs VPD VPN VPS +VRF vSwitch VXLAN WebSocket diff --git a/doc/reference/devices_nic.md b/doc/reference/devices_nic.md index 6a40f807db5..4d6133629ea 100644 --- a/doc/reference/devices_nic.md +++ b/doc/reference/devices_nic.md @@ -450,6 +450,7 @@ Key | Type | Default | Description `parent` | string | - | The name of the host device to join the instance to `queue.tx.length` | integer | - | The transmit queue length for the NIC `vlan` | integer | - | The VLAN ID to attach to +`vrf` | string | - | The VRF on the host in which the host-side interface and routes are created ## `bridged`, `macvlan` or `ipvlan` for connection to physical network