From c2aa5236002c661fa375b7903a2fd80748be3a4f Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Sat, 7 Mar 2026 23:23:22 +0000 Subject: [PATCH] squelch router advertisements on uplink ports --- common/src/illumos.rs | 48 +++++++++++++++++++++++++++++++++++++++++++ uplinkd/src/main.rs | 32 ++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/common/src/illumos.rs b/common/src/illumos.rs index 0785c59c..5eca9c2b 100644 --- a/common/src/illumos.rs +++ b/common/src/illumos.rs @@ -177,6 +177,54 @@ pub async fn vlan_create(over: &str, vlan_id: u16, vlan: &str) -> Result<()> { dladm_quiet(&["create-vlan", "-t", "-v", &vlan_id, "-l", over, vlan]).await } +/// What address family to use when enabling/disabling route exchange for an +/// interface. +#[derive(Debug, Clone, Copy)] +pub enum AddressFamily { + Ipv4, + Ipv6, +} + +impl AddressFamily { + fn as_str(&self) -> &'static str { + match self { + Self::Ipv4 => "ipv4", + Self::Ipv6 => "ipv6", + } + } +} + +/// Enable or disable route exchange for a particular interface. Disabling +/// has the following implications. +/// +/// IPv6: +/// - The host OS will ignore all NDP router solicitation (RS) and router +/// advertisement (RA) messages. +/// - The host OS will suppress RIPng send/receive +/// +/// IPv4: +/// - The host OS discards inbound RIP packets +/// - The host OS suppresses ICMP router discovery +pub async fn set_interface_exchange_routes( + iface: &str, + enabled: bool, + address_family: AddressFamily, +) -> Result<()> { + let value = + if enabled { "exchange_routes=on" } else { "exchange_routes=off" }; + + ipadm_quiet(&[ + "set-ifprop", + "-t", + "-p", + value, + "-m", + address_family.as_str(), + iface, + ]) + .await +} + /// Remove a vlan link pub async fn vlan_delete(vlan: &str) -> Result<()> { dladm_quiet(&["delete-vlan", vlan]).await diff --git a/uplinkd/src/main.rs b/uplinkd/src/main.rs index 128521d5..7ac1d757 100644 --- a/uplinkd/src/main.rs +++ b/uplinkd/src/main.rs @@ -39,6 +39,7 @@ use anyhow::Context; use anyhow::Result; use anyhow::anyhow; use clap::Parser; +use common::illumos::AddressFamily; use libc::c_int; use oxnet::IpNet; use oxnet::Ipv4Net; @@ -452,7 +453,36 @@ async fn create_addrobj( // some point. error!(log, "failed to create {addr}: {e:?}"); e - }) + })?; + + // The uplink ports on the switch are router ports. This means that we + // should not be modifying the switch zone OS routing tables in response to + // router advertisements we receive. Router advertisements are for hosts, + // routers are not supposed to respond to them on router ports. By disabling + // route exchange in the illumos host, we are asking the OS not to respond + // to router advertisements as a host. Maghemite takes responsibility + // for generating and handling router advertisements and solicitations as + // a router. + illumos::set_interface_exchange_routes(iface, false, AddressFamily::Ipv6) + .await + .map_err(|e| { + error!( + log, + "failed to disable ipv6 route exchange on interface {iface}: {e:?}" + ); + e + })?; + illumos::set_interface_exchange_routes(iface, false, AddressFamily::Ipv4) + .await + .map_err(|e| { + error!( + log, + "failed to disable ipv4 route exchange on interface {iface}: {e:?}" + ); + e + })?; + + Ok(()) } // Query illumos for all of the addresses on the interfaces we've been asked to