Skip to content

Commit 49ef9be

Browse files
authored
Support IPv6 addresses in the OPTE zone setup service (#11092)
- Add the new `--create-v6` parameter to the `zone-setup` binary for setting up OPTE ports in a service zone. Also adds stronger types around IPv4 addresses for the gateway / private IP arguments. Includes a number of tests for those types, and all combinations of arguments we could provide. - Ensure we create both IPv4 and IPv6 DHCP address objects in the current zone, depending on how the SMF properties are set. These properties are now set in the sled-agent for both IPv4 and IPv6. - Move the code from the `RunningZone` which does the `route(8)` shenanigans to work around stlouis#326 (setting up a default route to the OPTE virtual gateway), and which also waits for the DHCPv6 address on the link. This was all duplicated, and moving it here means we can delete the workaround in one place in the future.
1 parent 8679d8d commit 49ef9be

10 files changed

Lines changed: 611 additions & 235 deletions

File tree

‎illumos-utils/src/ipadm.rs‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ const INTERFACE_ALREADY_EXISTS: &str = "Interface already exists";
2828
const ADDROBJ_ALREADY_EXISTS: &str = "Address object already exists";
2929

3030
pub enum AddrObjType {
31-
DHCP,
31+
// NOTE: This can result in more than one address, if there is a DHCPv6
32+
// server on the same link as the addrobj. That happens most often for OPTE
33+
// ports used in zones that need external connectivity.
3234
AddrConf,
3335
Static(IpAddr),
3436
}
@@ -62,7 +64,6 @@ impl Ipadm {
6264
let mut cmd = Command::new(PFEXEC);
6365
let cmd = cmd.args(&[IPADM, "create-addr", "-t", "-T"]);
6466
let cmd = match addrtype {
65-
AddrObjType::DHCP => cmd.args(&["dhcp"]),
6667
AddrObjType::AddrConf => cmd.args(&["addrconf"]),
6768
AddrObjType::Static(addr) => {
6869
cmd.args(&["static", "-a", &addr.to_string()])
@@ -83,6 +84,9 @@ impl Ipadm {
8384
/// Remove any scope from an IPv6 address.
8485
/// e.g. fe80::8:20ff:fed0:8687%oxControlService1/10 ->
8586
/// fe80::8:20ff:fed0:8687/10
87+
//
88+
// TODO-cleanup: This could directly parse the line into an IpNet if
89+
// possible, rather than emitting a new string.
8690
fn remove_addr_scope(input: &str) -> String {
8791
if let Some(pos) = input.find('%') {
8892
let (base, rest) = input.split_at(pos);
@@ -98,6 +102,10 @@ impl Ipadm {
98102

99103
/// Return the IP network associated with an address object, or None if
100104
/// there is no address object with this name.
105+
//
106+
// TODO-correctness: There can be many addresses associated with an addrobj,
107+
// e.g., for IPv6 where we have link-local + DHCPv6 addresses. This will
108+
// ignore all but the first.
101109
pub async fn addrobj_addr(
102110
addrobj: &str,
103111
) -> Result<Option<IpNet>, ExecutionError> {
@@ -173,6 +181,7 @@ impl Ipadm {
173181
Ok(())
174182
}
175183

184+
/// Create a link-local IPv6 addrconf address and a static IPv6 address.
176185
pub async fn create_static_and_autoconfigured_addrs(
177186
datalink: &str,
178187
listen_addr: &Ipv6Addr,
@@ -192,15 +201,6 @@ impl Ipadm {
192201
Ok(())
193202
}
194203

195-
// Create gateway on the IP interface if it doesn't already exist
196-
pub async fn create_opte_gateway(
197-
opte_iface: &String,
198-
) -> Result<(), ExecutionError> {
199-
let addrobj = format!("{}/public", opte_iface);
200-
Self::ensure_ip_addrobj_exists(&addrobj, AddrObjType::DHCP).await?;
201-
Ok(())
202-
}
203-
204204
/// Set TCP recv_buf to 1 MB.
205205
pub async fn set_tcp_recv_buf() -> Result<(), ExecutionError> {
206206
let mut cmd = Command::new(PFEXEC);

‎illumos-utils/src/opte/mod.rs‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ pub use port_manager::MulticastGroupCfg;
3838
pub use port_manager::PortCreateParams;
3939
pub use port_manager::PortManager;
4040
pub use port_manager::PortTicket;
41-
use std::net::IpAddr;
4241
use std::net::Ipv4Addr;
4342
use std::net::Ipv6Addr;
4443

4544
/// Information about the gateway for an OPTE port
45+
///
46+
/// TODO-remove: This only exists to communicate the destination for a default
47+
/// IPv4 route from the port's private IP to the OPTE "virtual gateway". We can
48+
/// remove this entirely when we resolve
49+
/// <https://github.com/oxidecomputer/omicron/issues/2931>.
4650
#[derive(Debug, Clone, Copy)]
4751
#[allow(dead_code)]
4852
pub struct Gateway {
@@ -100,18 +104,6 @@ impl Gateway {
100104
GatewayIps::V4(_) => None,
101105
}
102106
}
103-
104-
/// Return the IPv4 address, if it exists, or the IPv6 address.
105-
///
106-
/// At least one of these always exists.
107-
pub fn ipv4_or_ipv6_addr(&self) -> IpAddr {
108-
match &self.ips {
109-
GatewayIps::V4(v4) | GatewayIps::DualStack { v4, .. } => {
110-
IpAddr::V4(*v4)
111-
}
112-
GatewayIps::V6(v6) => IpAddr::V6(*v6),
113-
}
114-
}
115107
}
116108

117109
/// Convert a nexus [IpNet] to an OPTE [IpCidr].

‎illumos-utils/src/opte/port.rs‎

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use omicron_common::api::internal::shared::RouterKind;
1515
use oxnet::Ipv4Net;
1616
use oxnet::Ipv6Net;
1717
use sled_agent_types::inventory::NetworkInterfaceKind;
18-
use std::net::IpAddr;
1918
use std::net::Ipv4Addr;
2019
use std::net::Ipv6Addr;
2120
use std::sync::Arc;
@@ -24,17 +23,17 @@ use uuid::Uuid;
2423
#[derive(Debug)]
2524
pub struct PortData {
2625
/// Name of the port as identified by OPTE
27-
pub(crate) name: String,
26+
name: String,
2827
/// The VPC-private IP configuration for the port.
29-
pub(crate) ip: PrivateIpConfig,
28+
ip: PrivateIpConfig,
3029
/// VPC-private MAC address
31-
pub(crate) mac: MacAddr6,
30+
mac: MacAddr6,
3231
/// Emulated PCI slot for the guest NIC, passed to Propolis
33-
pub(crate) slot: u8,
32+
slot: u8,
3433
/// Geneve VNI for the VPC
35-
pub(crate) vni: Vni,
34+
vni: Vni,
3635
/// Information about the virtual gateway, aka OPTE
37-
pub(crate) gateway: Gateway,
36+
gateway: Gateway,
3837
}
3938

4039
#[derive(Debug)]
@@ -80,7 +79,15 @@ pub struct Port {
8079
}
8180

8281
impl Port {
83-
pub fn new(data: PortData) -> Self {
82+
pub fn new(
83+
name: String,
84+
ip: PrivateIpConfig,
85+
mac: MacAddr6,
86+
slot: u8,
87+
vni: Vni,
88+
) -> Self {
89+
let gateway = Gateway::from_ip_config(&ip);
90+
let data = PortData { name, ip, mac, slot, vni, gateway };
8491
Self { inner: Arc::new(PortInner(data)) }
8592
}
8693

@@ -94,28 +101,23 @@ impl Port {
94101
self.inner.ip.ipv6_addr()
95102
}
96103

97-
/// Return the VPC-private IPv4 address, if it exits, or the IPv6 address.
98-
///
99-
/// One of these always exists.
100-
pub fn ipv4_or_ipv6_addr(&self) -> IpAddr {
101-
self.inner.ip.ipv4_addr().copied().map(IpAddr::V4).unwrap_or_else(
102-
|| {
103-
self.inner
104-
.ip
105-
.ipv6_addr()
106-
.copied()
107-
.expect("At least one address always exists")
108-
.into()
109-
},
110-
)
111-
}
112-
113104
pub fn name(&self) -> &str {
114105
&self.inner.name
115106
}
116107

117-
pub fn gateway(&self) -> &Gateway {
118-
&self.inner.gateway
108+
/// Return the OPTE gateway IPv4 address and the private IPv4 address.
109+
///
110+
/// If the port is not configured for IPv4, None is returned.
111+
// TODO-remove: <https://github.com/oxidecomputer/omicron/issues/2931>
112+
pub fn gateway_and_private_ipv4(&self) -> Option<(&Ipv4Addr, &Ipv4Addr)> {
113+
match (self.inner.gateway.ipv4_addr(), self.ipv4_addr()) {
114+
(None, None) => None,
115+
(None, Some(_)) => unreachable!(),
116+
(Some(_), None) => unreachable!(),
117+
(Some(gateway_ip), Some(private_ip)) => {
118+
Some((gateway_ip, private_ip))
119+
}
120+
}
119121
}
120122

121123
#[allow(dead_code)]

‎illumos-utils/src/opte/port_manager.rs‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use crate::opte::Handle;
1313
use crate::opte::Port;
1414
use crate::opte::Vni;
1515
use crate::opte::opte_firewall_rules;
16-
use crate::opte::port::PortData;
1716
use ipnetwork::Ipv4Network;
1817
use ipnetwork::Ipv6Network;
1918
use macaddr::MacAddr6;
@@ -419,14 +418,13 @@ impl PortManager {
419418
};
420419
let (port, ticket) = {
421420
let ticket = PortTicket::new(nic.id, nic.kind, self.inner.clone());
422-
let port = Port::new(PortData {
423-
name: port_name.clone(),
424-
ip: nic.ip_config.clone(),
421+
let port = Port::new(
422+
port_name.clone(),
423+
nic.ip_config.clone(),
425424
mac,
426-
slot: nic.slot,
425+
nic.slot,
427426
vni,
428-
gateway,
429-
});
427+
);
430428

431429
// NOTE: We may add external IPs below, which can fail. If that
432430
// does, we drop the `ticket` on the way out of this block. That

0 commit comments

Comments
 (0)