Skip to content

Commit

Permalink
Merge pull request #1615 from ibot3/routed-nic-vrf
Browse files Browse the repository at this point in the history
Add `vrf` parameter for routed-nic devices
  • Loading branch information
stgraber authored Feb 11, 2025
2 parents b1d4d6a + adede11 commit 06a3136
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 8 deletions.
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

0 comments on commit 06a3136

Please sign in to comment.