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

Use the same output rules for both meshed and not meshed networks #2264

Merged
merged 9 commits into from
Mar 7, 2024
1 change: 1 addition & 0 deletions changelog.d/2255.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use the same `OUTPUT` rules for both meshed and not meshed networks
aviramha marked this conversation as resolved.
Show resolved Hide resolved
13 changes: 6 additions & 7 deletions mirrord/agent/src/steal/ip_tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use crate::{
steal::ip_tables::{
flush_connections::FlushConnections,
mesh::{MeshRedirect, MeshVendorExt},
redirect::{PreroutingRedirect, Redirect},
prerouting::PreroutingRedirect,
redirect::Redirect,
standard::StandardRedirect,
},
};
Expand Down Expand Up @@ -51,6 +52,8 @@ mod iptables {
pub(crate) mod chain;
pub(crate) mod flush_connections;
pub(crate) mod mesh;
pub(crate) mod output;
pub(crate) mod prerouting;
pub(crate) mod redirect;
pub(crate) mod standard;

Expand Down Expand Up @@ -504,9 +507,7 @@ mod tests {
mock.expect_insert_rule()
.with(
str::starts_with("MIRRORD_OUTPUT_"),
eq(
"-o lo -m owner --uid-owner 2102 -m tcp -p tcp --dport 69 -j REDIRECT --to-ports 420",
),
eq("-o lo -m tcp -p tcp --dport 69 -j REDIRECT --to-ports 420"),
eq(2),
)
.times(1)
Expand All @@ -523,9 +524,7 @@ mod tests {
mock.expect_remove_rule()
.with(
str::starts_with("MIRRORD_OUTPUT_"),
eq(
"-o lo -m owner --uid-owner 2102 -m tcp -p tcp --dport 69 -j REDIRECT --to-ports 420",
),
eq("-o lo -m tcp -p tcp --dport 69 -j REDIRECT --to-ports 420"),
)
.times(1)
.returning(|_, _| Ok(()));
Expand Down
110 changes: 30 additions & 80 deletions mirrord/agent/src/steal/ip_tables/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,96 +3,61 @@ use std::sync::{Arc, LazyLock};
use async_trait::async_trait;
use fancy_regex::Regex;
use mirrord_protocol::{MeshVendor, Port};
use nix::unistd::getgid;
use tracing::warn;

use crate::{
error::Result,
steal::ip_tables::{
chain::IPTableChain,
redirect::{PreroutingRedirect, Redirect},
IPTables, IPTABLE_MESH,
output::OutputRedirect, prerouting::PreroutingRedirect, redirect::Redirect, IPTables,
IPTABLE_MESH,
},
};

/// [`Regex`] used to select the `owner` rule from the list of `iptables` rules.
static UID_LOOKUP_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"-m owner --uid-owner \d+").unwrap());

static LINKERD_SKIP_PORTS_LOOKUP_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"-p tcp -m multiport --dports ([\d:,]+)").unwrap());

static ISTIO_SKIP_PORTS_LOOKUP_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"-p tcp -m tcp --dport ([\d:,]+)").unwrap());

pub(crate) struct MeshRedirect<IPT: IPTables> {
preroute: PreroutingRedirect<IPT>,
managed: IPTableChain<IPT>,
own_packet_filter: String,
prerouteing: PreroutingRedirect<IPT>,
output: OutputRedirect<IPT>,
}

impl<IPT> MeshRedirect<IPT>
where
IPT: IPTables,
{
const ENTRYPOINT: &'static str = "OUTPUT";

pub fn create(ipt: Arc<IPT>, vendor: MeshVendor) -> Result<Self> {
let preroute = PreroutingRedirect::create(ipt.clone())?;
let own_packet_filter = Self::get_own_packet_filter(&ipt, &vendor)?;
let prerouteing = PreroutingRedirect::create(ipt.clone())?;

for port in Self::get_skip_ports(&ipt, &vendor)? {
preroute.add_rule(&format!("-m multiport -p tcp ! --dports {port} -j RETURN"))?;
prerouteing.add_rule(&format!("-m multiport -p tcp ! --dports {port} -j RETURN"))?;
}

let managed = IPTableChain::create(ipt, IPTABLE_MESH.to_string())?;

let gid = getgid();
managed.add_rule(&format!("-m owner --gid-owner {gid} -p tcp -j RETURN"))?;
let output = OutputRedirect::create(ipt, IPTABLE_MESH.to_string())?;

Ok(MeshRedirect {
preroute,
managed,
own_packet_filter,
prerouteing,
output,
})
}

pub fn load(ipt: Arc<IPT>, vendor: MeshVendor) -> Result<Self> {
let own_packet_filter = Self::get_own_packet_filter(&ipt, &vendor)?;
let preroute = PreroutingRedirect::load(ipt.clone())?;
let managed = IPTableChain::load(ipt, IPTABLE_MESH.to_string())?;
pub fn load(ipt: Arc<IPT>, _vendor: MeshVendor) -> Result<Self> {
let prerouteing = PreroutingRedirect::load(ipt.clone())?;
let output = OutputRedirect::load(ipt, IPTABLE_MESH.to_string())?;

Ok(MeshRedirect {
preroute,
managed,
own_packet_filter,
prerouteing,
output,
})
}

fn get_own_packet_filter(ipt: &IPT, vendor: &MeshVendor) -> Result<String> {
let chain_name = vendor.output_chain();

let own_packet_filter = ipt
.list_rules(chain_name)?
.iter()
.find_map(|rule| UID_LOOKUP_REGEX.find(rule).ok().flatten())
.map(|m| format!("-o lo {}", m.as_str()))
.unwrap_or_else(|| {
warn!(
"Couldn't find --uid-owner of meshed chain {chain_name:?} falling back on \"-o lo\" rule",
);

"-o lo".to_owned()
});

Ok(own_packet_filter)
}

fn get_skip_ports(ipt: &IPT, vendor: &MeshVendor) -> Result<Vec<String>> {
let chain_name = vendor.input_chain();
let lookup_regex = vendor.skip_ports_regex();

let skipped_ports = ipt
.list_rules(vendor.input_chain())?
.list_rules(chain_name)?
.iter()
.filter_map(|rule| {
lookup_regex
Expand All @@ -114,53 +79,37 @@ where
IPT: IPTables + Send + Sync,
{
async fn mount_entrypoint(&self) -> Result<()> {
self.preroute.mount_entrypoint().await?;

self.managed.inner().add_rule(
Self::ENTRYPOINT,
&format!("-j {}", self.managed.chain_name()),
)?;
self.prerouteing.mount_entrypoint().await?;
self.output.mount_entrypoint().await?;

Ok(())
}

async fn unmount_entrypoint(&self) -> Result<()> {
self.preroute.unmount_entrypoint().await?;

self.managed.inner().remove_rule(
Self::ENTRYPOINT,
&format!("-j {}", self.managed.chain_name()),
)?;
self.prerouteing.unmount_entrypoint().await?;
self.output.unmount_entrypoint().await?;

Ok(())
}

async fn add_redirect(&self, redirected_port: Port, target_port: Port) -> Result<()> {
self.preroute
self.prerouteing
.add_redirect(redirected_port, target_port)
.await?;
self.output
.add_redirect(redirected_port, target_port)
.await?;

let redirect_rule = format!(
"{} -m tcp -p tcp --dport {redirected_port} -j REDIRECT --to-ports {target_port}",
self.own_packet_filter
);

self.managed.add_rule(&redirect_rule)?;

Ok(())
}

async fn remove_redirect(&self, redirected_port: Port, target_port: Port) -> Result<()> {
self.preroute
self.prerouteing
.remove_redirect(redirected_port, target_port)
.await?;
self.output
.remove_redirect(redirected_port, target_port)
.await?;

let redirect_rule = format!(
"{} -m tcp -p tcp --dport {redirected_port} -j REDIRECT --to-ports {target_port}",
self.own_packet_filter
);

self.managed.remove_rule(&redirect_rule)?;

Ok(())
}
Expand Down Expand Up @@ -214,6 +163,7 @@ impl MeshVendorExt for MeshVendor {
#[cfg(test)]
mod tests {
use mockall::predicate::*;
use nix::unistd::getgid;

use super::*;
use crate::steal::ip_tables::{MockIPTables, IPTABLE_PREROUTING};
Expand Down Expand Up @@ -283,7 +233,7 @@ mod tests {
mock.expect_insert_rule()
.with(
eq(IPTABLE_MESH.as_str()),
eq("-o lo -m owner --uid-owner 2102 -m tcp -p tcp --dport 69 -j REDIRECT --to-ports 420"),
eq("-o lo -m tcp -p tcp --dport 69 -j REDIRECT --to-ports 420"),
eq(2),
)
.times(1)
Expand Down
87 changes: 87 additions & 0 deletions mirrord/agent/src/steal/ip_tables/output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::sync::Arc;

use async_trait::async_trait;
use mirrord_protocol::Port;
use nix::unistd::getgid;
use tracing::warn;

use crate::{
error::Result,
steal::ip_tables::{chain::IPTableChain, IPTables, Redirect},
};

pub(crate) struct OutputRedirect<IPT: IPTables> {
pub(crate) managed: IPTableChain<IPT>,
}

impl<IPT> OutputRedirect<IPT>
where
IPT: IPTables,
{
const ENTRYPOINT: &'static str = "OUTPUT";

pub fn create(ipt: Arc<IPT>, chain_name: String) -> Result<Self> {
let managed = IPTableChain::create(ipt, chain_name)?;

let gid = getgid();
managed
.add_rule(&format!("-m owner --gid-owner {gid} -p tcp -j RETURN"))
.inspect_err(|_| {
warn!("Unable to create iptable rule with \"--gid-owner {gid}\" filter")
})?;

Ok(OutputRedirect { managed })
}

pub fn load(ipt: Arc<IPT>, chain_name: String) -> Result<Self> {
let managed = IPTableChain::create(ipt, chain_name)?;

Ok(OutputRedirect { managed })
}
}

/// This wrapper adds a new rule to the NAT OUTPUT chain to redirect "localhost" traffic as well
/// Note: OUTPUT chain is only traversed for packets produced by local applications
#[async_trait]
impl<IPT> Redirect for OutputRedirect<IPT>
where
IPT: IPTables + Send + Sync,
{
async fn mount_entrypoint(&self) -> Result<()> {
self.managed.inner().add_rule(
Self::ENTRYPOINT,
&format!("-j {}", self.managed.chain_name()),
)?;

Ok(())
}

async fn unmount_entrypoint(&self) -> Result<()> {
self.managed.inner().remove_rule(
Self::ENTRYPOINT,
&format!("-j {}", self.managed.chain_name()),
)?;

Ok(())
}

async fn add_redirect(&self, redirected_port: Port, target_port: Port) -> Result<()> {
let redirect_rule = format!(
"-o lo -m tcp -p tcp --dport {redirected_port} -j REDIRECT --to-ports {target_port}"
);

self.managed.add_rule(&redirect_rule)?;

Ok(())
}

async fn remove_redirect(&self, redirected_port: Port, target_port: Port) -> Result<()> {
let redirect_rule = format!(
"-o lo -m tcp -p tcp --dport {redirected_port} -j REDIRECT --to-ports {target_port}"
);

self.managed.remove_rule(&redirect_rule)?;

Ok(())
}
}
Loading
Loading