Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vrf parameter for routed-nic devices #1615

Merged
merged 7 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ jobs:
libsqlite3-dev \
libtool \
libudev-dev \
linux-modules-extra-$(uname -r) \
make \
pkg-config\
acl \
Expand Down
1 change: 1 addition & 0 deletions doc/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ VMs
VPD
VPN
VPS
VRF
vSwitch
VXLAN
WebSocket
Expand Down
3 changes: 3 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions doc/reference/devices_nic.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions internal/server/device/device_utils_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand Down
24 changes: 20 additions & 4 deletions internal/server/device/nic_routed.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
22 changes: 20 additions & 2 deletions internal/server/ip/link_veth.go
Original file line number Diff line number Diff line change
@@ -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
}
33 changes: 31 additions & 2 deletions internal/server/ip/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Route struct {
Proto string
Family string
Via string
VRF string
}

// Add adds new route.
Expand All @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
8 changes: 8 additions & 0 deletions internal/server/ip/tuntap.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Tuntap struct {
Name string
Mode string
MultiQueue bool
Master string
}

// Add adds new tuntap interface.
Expand All @@ -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
}
1 change: 1 addition & 0 deletions internal/version/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions test/suites/container_devices_nic_routed.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down