Skip to content

Commit

Permalink
[sled-agent] Self assembling external DNS zone (#5059)
Browse files Browse the repository at this point in the history
### Overview

In addition to implementing the external DNS self assembling zone, this
PR contains a new SMF service called `opte-interface-setup`.

Closes: #2881
Related: #1898
### Implementation

This service makes use of the zone-network CLI tool to avoid having too
many CLIs doing things.

The CLI is now shipped independently so it can be called by two
different services.

The [`zone-networking opte-interface-set-up`](
https://github.com/oxidecomputer/omicron/pull/5059/files#diff-5fb7b70dc87176e02517181b0887ce250b6a4e4079e495990551deeca741dc8bR181-R202)
command sets up what the `ensure_address_for_port()` method used to set
up.

### Justification

The reasoning behind this new service is to avoid setting up too many
things via the method_script.sh file, and to avoid code duplication. The
Nexus zone will also be using this service to set up the OPTE interface.
  • Loading branch information
karencfv authored Feb 23, 2024
1 parent 119bcd1 commit 2088693
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 91 deletions.
25 changes: 25 additions & 0 deletions illumos-utils/src/ipadm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,29 @@ impl Ipadm {
};
Ok(())
}

// Create gateway on the IP interface if it doesn't already exist
pub fn create_opte_gateway(
opte_iface: &String,
) -> Result<(), ExecutionError> {
let addrobj = format!("{}/public", opte_iface);
let mut cmd = std::process::Command::new(PFEXEC);
let cmd = cmd.args(&[IPADM, "show-addr", &addrobj]);
match execute(cmd) {
Err(_) => {
let mut cmd = std::process::Command::new(PFEXEC);
let cmd = cmd.args(&[
IPADM,
"create-addr",
"-t",
"-T",
"dhcp",
&addrobj,
]);
execute(cmd)?;
}
Ok(_) => (),
};
Ok(())
}
}
8 changes: 6 additions & 2 deletions illumos-utils/src/opte/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct PortInner {
// Name of the port as identified by OPTE
name: String,
// IP address within the VPC Subnet
_ip: IpAddr,
ip: IpAddr,
// VPC-private MAC address
mac: MacAddr6,
// Emulated PCI slot for the guest NIC, passed to Propolis
Expand Down Expand Up @@ -95,7 +95,7 @@ impl Port {
Self {
inner: Arc::new(PortInner {
name,
_ip: ip,
ip,
mac,
slot,
vni,
Expand All @@ -105,6 +105,10 @@ impl Port {
}
}

pub fn ip(&self) -> &IpAddr {
&self.inner.ip
}

pub fn name(&self) -> &str {
&self.inner.name
}
Expand Down
67 changes: 59 additions & 8 deletions illumos-utils/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,76 @@
use crate::zone::ROUTE;
use crate::{execute, inner, output_to_exec_error, ExecutionError, PFEXEC};
use libc::ESRCH;
use std::net::Ipv6Addr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

/// Wraps commands for interacting with routing tables.
pub struct Route {}

pub enum Gateway {
Ipv4(Ipv4Addr),
Ipv6(Ipv6Addr),
}

#[cfg_attr(any(test, feature = "testing"), mockall::automock)]
impl Route {
pub fn ensure_default_route_with_gateway(
gateway: &Ipv6Addr,
gateway: Gateway,
) -> Result<(), ExecutionError> {
let inet;
let gw;
match gateway {
Gateway::Ipv4(addr) => {
inet = "-inet";
gw = addr.to_string();
}
Gateway::Ipv6(addr) => {
inet = "-inet6";
gw = addr.to_string();
}
}
// Add the desired route if it doesn't already exist
let destination = "default";
let mut cmd = std::process::Command::new(PFEXEC);
let cmd = cmd.args(&[ROUTE, "-n", "get", inet, destination, inet, &gw]);

let out =
cmd.output().map_err(|err| ExecutionError::ExecutionStart {
command: inner::to_string(cmd),
err,
})?;
match out.status.code() {
Some(0) => (),
// If the entry is not found in the table,
// the exit status of the command will be 3 (ESRCH).
// When that is the case, we'll add the route.
Some(ESRCH) => {
let mut cmd = std::process::Command::new(PFEXEC);
let cmd =
cmd.args(&[ROUTE, "add", inet, destination, inet, &gw]);
execute(cmd)?;
}
Some(_) | None => return Err(output_to_exec_error(cmd, &out)),
};
Ok(())
}

pub fn ensure_opte_route(
gateway: &Ipv4Addr,
iface: &String,
opte_ip: &IpAddr,
) -> Result<(), ExecutionError> {
// Add the desired route if it doesn't already exist
let mut cmd = std::process::Command::new(PFEXEC);
let cmd = cmd.args(&[
ROUTE,
"-n",
"get",
"-inet6",
destination,
"-inet6",
"-host",
&gateway.to_string(),
&opte_ip.to_string(),
"-interface",
"-ifp",
&iface.to_string(),
]);

let out =
Expand All @@ -45,10 +94,12 @@ impl Route {
let cmd = cmd.args(&[
ROUTE,
"add",
"-inet6",
destination,
"-inet6",
"-host",
&gateway.to_string(),
&opte_ip.to_string(),
"-interface",
"-ifp",
&iface.to_string(),
]);
execute(cmd)?;
}
Expand Down
7 changes: 6 additions & 1 deletion illumos-utils/src/running_zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ impl RunningZone {

/// Return references to the OPTE ports for this zone.
pub fn opte_ports(&self) -> impl Iterator<Item = &Port> {
self.inner.opte_ports.iter().map(|(port, _)| port)
self.inner.opte_ports()
}

/// Remove the OPTE ports on this zone from the port manager.
Expand Down Expand Up @@ -1130,6 +1130,11 @@ impl InstalledZone {
path.push("root/var/svc/profile/site.xml");
path
}

/// Returns references to the OPTE ports for this zone.
pub fn opte_ports(&self) -> impl Iterator<Item = &Port> {
self.opte_ports.iter().map(|(port, _)| port)
}
}

#[derive(Clone)]
Expand Down
56 changes: 47 additions & 9 deletions package-manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ setup_hint = """
service_name = "oximeter"
only_for_targets.image = "standard"
source.type = "composite"
source.packages = [ "oximeter-collector.tar.gz", "zone-network-setup.tar.gz" ]
source.packages = [ "oximeter-collector.tar.gz", "zone-network-setup.tar.gz", "zone-network-install.tar.gz" ]
output.type = "zone"

[package.oximeter-collector]
Expand All @@ -140,7 +140,12 @@ output.intermediate_only = true
service_name = "clickhouse"
only_for_targets.image = "standard"
source.type = "composite"
source.packages = [ "clickhouse_svc.tar.gz", "internal-dns-cli.tar.gz", "zone-network-setup.tar.gz" ]
source.packages = [
"clickhouse_svc.tar.gz",
"internal-dns-cli.tar.gz",
"zone-network-setup.tar.gz",
"zone-network-install.tar.gz"
]
output.type = "zone"

[package.clickhouse_svc]
Expand All @@ -161,7 +166,12 @@ setup_hint = "Run `./tools/ci_download_clickhouse` to download the necessary bin
service_name = "clickhouse_keeper"
only_for_targets.image = "standard"
source.type = "composite"
source.packages = [ "clickhouse_keeper_svc.tar.gz", "internal-dns-cli.tar.gz", "zone-network-setup.tar.gz" ]
source.packages = [
"clickhouse_keeper_svc.tar.gz",
"internal-dns-cli.tar.gz",
"zone-network-setup.tar.gz",
"zone-network-install.tar.gz"
]
output.type = "zone"

[package.clickhouse_keeper_svc]
Expand All @@ -182,7 +192,12 @@ setup_hint = "Run `./tools/ci_download_clickhouse` to download the necessary bin
service_name = "cockroachdb"
only_for_targets.image = "standard"
source.type = "composite"
source.packages = [ "cockroachdb-service.tar.gz", "internal-dns-cli.tar.gz", "zone-network-setup.tar.gz" ]
source.packages = [
"cockroachdb-service.tar.gz",
"internal-dns-cli.tar.gz",
"zone-network-setup.tar.gz",
"zone-network-install.tar.gz"
]
output.type = "zone"

[package.cockroachdb-service]
Expand Down Expand Up @@ -220,7 +235,13 @@ output.type = "zone"
service_name = "external_dns"
only_for_targets.image = "standard"
source.type = "composite"
source.packages = [ "dns-server.tar.gz", "external-dns-customizations.tar.gz" ]
source.packages = [
"dns-server.tar.gz",
"external-dns-customizations.tar.gz",
"zone-network-setup.tar.gz",
"zone-network-install.tar.gz",
"opte-interface-setup.tar.gz"
]
output.type = "zone"

[package.dns-server]
Expand Down Expand Up @@ -393,15 +414,15 @@ output.intermediate_only = true
service_name = "crucible"
only_for_targets.image = "standard"
source.type = "composite"
source.packages = [ "crucible.tar.gz", "zone-network-setup.tar.gz" ]
source.packages = [ "crucible.tar.gz", "zone-network-setup.tar.gz", "zone-network-install.tar.gz" ]
output.type = "zone"


[package.crucible-pantry-zone]
service_name = "crucible_pantry"
only_for_targets.image = "standard"
source.type = "composite"
source.packages = [ "crucible-pantry.tar.gz", "zone-network-setup.tar.gz" ]
source.packages = [ "crucible-pantry.tar.gz", "zone-network-setup.tar.gz", "zone-network-install.tar.gz" ]
output.type = "zone"

# Packages not built within Omicron, but which must be imported.
Expand Down Expand Up @@ -643,14 +664,31 @@ source.packages = [
]
output.type = "zone"

[package.zone-network-setup]
[package.zone-network-install]
service_name = "zone-network-setup"
only_for_targets.image = "standard"
source.type = "local"
source.paths = [
{ from = "smf/zone-network-setup/manifest.xml", to = "/var/svc/manifest/site/zone-network-setup/manifest.xml" },
]
output.type = "zone"
output.intermediate_only = true

[package.zone-network-setup]
service_name = "zone-network-cli"
only_for_targets.image = "standard"
source.type = "local"
source.rust.binary_names = ["zone-networking"]
source.rust.release = true
output.type = "zone"
output.intermediate_only = true

[package.opte-interface-setup]
service_name = "opte-interface-setup"
only_for_targets.image = "standard"
source.type = "local"
source.paths = [
{ from = "smf/zone-network-setup/manifest.xml", to = "/var/svc/manifest/site/zone-network-setup/manifest.xml" },
{ from = "smf/opte-interface-setup/manifest.xml", to = "/var/svc/manifest/site/opte-interface-setup/manifest.xml" },
]
output.type = "zone"
output.intermediate_only = true
Expand Down
Loading

0 comments on commit 2088693

Please sign in to comment.