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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion doc/reference/devices_nic.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ Key | Type | Default | Description
`ipv4.address` | string | - | Comma-delimited list of IPv4 static addresses to add to the instance
`ipv4.gateway` | string | `auto` | Whether to add an automatic default IPv4 gateway (can be `auto` or `none`)
`ipv4.host_address` | string | `169.254.0.1` | The IPv4 address to add to the host-side `veth` interface
`ipv4.host_table` | integer | - | The custom policy routing table ID to add IPv4 static routes to (in addition to the main routing table)
`ipv4.host_table` | integer | - | The custom policy routing table ID to add IPv4 static routes to (in addition to the main/vrf routing table)
`ipv4.neighbor_probe` | bool | `true` | Whether to probe the parent network for IP address availability
`ipv4.routes` | string | - | Comma-delimited list of IPv4 static routes to add on host to NIC (without L2 ARP/NDP proxy)
`ipv6.address` | string | - | Comma-delimited list of IPv6 static addresses to add to the instance
Expand All @@ -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
23 changes: 20 additions & 3 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 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,21 @@ 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/vrf routing table.
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 @@ -466,9 +482,10 @@ func (d *nicRouted) Start() (*deviceConfig.RunConfig, error) {
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
20 changes: 18 additions & 2 deletions internal/server/ip/link_veth.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
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
}
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