summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-03-26 01:49:57 +0100
committerDavid Lönnhager <david.l@mullvad.net>2020-06-02 10:05:02 +0200
commitce2d1b6fd0fd430133441ddb3ab6440f9c8de743 (patch)
tree3a437f7f9dab4eca31432981005e9ddcf8b729f4
parent0961e1096cabc07916124c7c56987b959756c0d3 (diff)
downloadmullvadvpn-ce2d1b6fd0fd430133441ddb3ab6440f9c8de743.tar.xz
mullvadvpn-ce2d1b6fd0fd430133441ddb3ab6440f9c8de743.zip
Make mangle firewall rules compatible with older kernels
-rw-r--r--talpid-core/src/firewall/linux.rs114
1 files changed, 74 insertions, 40 deletions
diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs
index 1005bac337..47752d0dac 100644
--- a/talpid-core/src/firewall/linux.rs
+++ b/talpid-core/src/firewall/linux.rs
@@ -60,6 +60,11 @@ lazy_static! {
static ref TABLE_NAME: CString = CString::new("mullvad").unwrap();
static ref IN_CHAIN_NAME: CString = CString::new("input").unwrap();
static ref OUT_CHAIN_NAME: CString = CString::new("output").unwrap();
+
+ /// We need two separate tables for compatibility with older kernels,
+ /// where the base filter type may not be `nftnl::ChainType::Route` for inet tables.
+ static ref MANGLE_TABLE_NAME_V4: CString = CString::new("mullvadmangle4").unwrap();
+ static ref MANGLE_TABLE_NAME_V6: CString = CString::new("mullvadmangle6").unwrap();
static ref MANGLE_CHAIN_NAME: CString = CString::new("mangle").unwrap();
/// Allows controlling whether firewall rules should have packet counters or not from an env
@@ -82,39 +87,50 @@ enum End {
}
/// The Linux implementation for the firewall and DNS.
-pub struct Firewall {
- table_name: CString,
+pub struct Firewall;
+
+struct FirewallTables {
+ main: Table,
+ mangle_v4: Table,
+ mangle_v6: Table,
}
impl FirewallT for Firewall {
type Error = Error;
fn new(_args: FirewallArguments) -> Result<Self> {
- Ok(Firewall {
- table_name: TABLE_NAME.clone(),
- })
+ Ok(Firewall)
}
fn apply_policy(&mut self, policy: FirewallPolicy) -> Result<()> {
- let table = Table::new(&self.table_name, ProtoFamily::Inet);
- let batch = PolicyBatch::new(&table).finalize(&policy)?;
+ let tables = FirewallTables {
+ main: Table::new(&*TABLE_NAME, ProtoFamily::Inet),
+ mangle_v4: Table::new(&*MANGLE_TABLE_NAME_V4, ProtoFamily::Ipv4),
+ mangle_v6: Table::new(&*MANGLE_TABLE_NAME_V6, ProtoFamily::Ipv6),
+ };
+ let batch = PolicyBatch::new(&tables).finalize(&policy)?;
self.send_and_process(&batch)?;
- self.verify_tables(&[&TABLE_NAME])
+ self.verify_tables(&[&TABLE_NAME, &MANGLE_TABLE_NAME_V4, &MANGLE_TABLE_NAME_V6])
}
fn reset_policy(&mut self) -> Result<()> {
- let table = Table::new(&self.table_name, ProtoFamily::Inet);
- let batch = {
- let mut batch = Batch::new();
- // Our batch will add and remove the table even though the goal is just to remove it.
- // This because only removing it throws a strange error if the table does not exist.
- batch.add(&table, nftnl::MsgType::Add);
- batch.add(&table, nftnl::MsgType::Del);
- batch.finalize()
- };
-
+ let tables = [
+ Table::new(&*TABLE_NAME, ProtoFamily::Inet),
+ Table::new(&*MANGLE_TABLE_NAME_V4, ProtoFamily::Ipv4),
+ Table::new(&*MANGLE_TABLE_NAME_V6, ProtoFamily::Ipv6),
+ ];
+ let mut batch = Batch::new();
+ for table in &tables {
+ // Our batch will add and remove the table even though the goal is just to remove
+ // it. This because only removing it throws a strange error if the
+ // table does not exist.
+ batch.add(table, nftnl::MsgType::Add);
+ batch.add(table, nftnl::MsgType::Del);
+ }
+ let batch = batch.finalize();
log::debug!("Removing table and chain from netfilter");
- self.send_and_process(&batch)
+ self.send_and_process(&batch)?;
+ Ok(())
}
}
@@ -191,42 +207,57 @@ struct PolicyBatch<'a> {
batch: Batch,
in_chain: Chain<'a>,
out_chain: Chain<'a>,
- mangle_chain: Chain<'a>,
+ mangle_chain_v4: Chain<'a>,
+ mangle_chain_v6: Chain<'a>,
}
impl<'a> PolicyBatch<'a> {
/// Bootstrap a new nftnl message batch object and add the initial messages creating the
/// table and chains.
- pub fn new(table: &'a Table) -> Self {
+ pub fn new(tables: &'a FirewallTables) -> Self {
let mut batch = Batch::new();
- let mut out_chain = Chain::new(&*OUT_CHAIN_NAME, table);
- let mut in_chain = Chain::new(&*IN_CHAIN_NAME, table);
+ let mut out_chain = Chain::new(&*OUT_CHAIN_NAME, &tables.main);
+ let mut in_chain = Chain::new(&*IN_CHAIN_NAME, &tables.main);
out_chain.set_hook(nftnl::Hook::Out, 0);
in_chain.set_hook(nftnl::Hook::In, 0);
out_chain.set_policy(nftnl::Policy::Drop);
in_chain.set_policy(nftnl::Policy::Drop);
- // A little dance that will make sure the table exists, but is cleared.
- batch.add(table, nftnl::MsgType::Add);
- batch.add(table, nftnl::MsgType::Del);
- batch.add(table, nftnl::MsgType::Add);
+ Self::flush_table(&mut batch, &tables.main);
batch.add(&out_chain, nftnl::MsgType::Add);
batch.add(&in_chain, nftnl::MsgType::Add);
- let mut mangle_chain = Chain::new(&*MANGLE_CHAIN_NAME, table);
- mangle_chain.set_hook(nftnl::Hook::Out, MANGLE_CHAIN_PRIORITY);
- mangle_chain.set_type(nftnl::ChainType::Route);
- mangle_chain.set_policy(nftnl::Policy::Accept);
- batch.add(&mangle_chain, nftnl::MsgType::Add);
+ Self::flush_table(&mut batch, &tables.mangle_v4);
+ Self::flush_table(&mut batch, &tables.mangle_v6);
+
+ let mut add_mangle_chain = |table| {
+ let mut chain = Chain::new(&*MANGLE_CHAIN_NAME, table);
+ chain.set_hook(nftnl::Hook::Out, MANGLE_CHAIN_PRIORITY);
+ chain.set_type(nftnl::ChainType::Route);
+ chain.set_policy(nftnl::Policy::Accept);
+ batch.add(&chain, nftnl::MsgType::Add);
+
+ chain
+ };
+ let mangle_chain_v4 = add_mangle_chain(&tables.mangle_v4);
+ let mangle_chain_v6 = add_mangle_chain(&tables.mangle_v6);
PolicyBatch {
batch,
in_chain,
out_chain,
- mangle_chain,
+ mangle_chain_v4,
+ mangle_chain_v6,
}
}
+ /// Creates the table if it does not exist and clears it otherwise.
+ fn flush_table(batch: &mut Batch, table: &'a Table) {
+ batch.add(table, nftnl::MsgType::Add);
+ batch.add(table, nftnl::MsgType::Del);
+ batch.add(table, nftnl::MsgType::Add);
+ }
+
/// Finalize the nftnl message batch by adding every firewall rule needed to satisfy the given
/// policy.
pub fn finalize(mut self, policy: &FirewallPolicy) -> Result<FinalizedBatch> {
@@ -239,13 +270,16 @@ impl<'a> PolicyBatch<'a> {
}
fn add_split_tunneling_rules(&mut self) {
- let mut rule = Rule::new(&self.mangle_chain);
- rule.add_expr(&nft_expr!(meta cgroup));
- rule.add_expr(&nft_expr!(cmp == split::NETCLS_CLASSID));
- rule.add_expr(&nft_expr!(immediate data split::MARK));
- rule.add_expr(&nft_expr!(ct mark set));
- rule.add_expr(&nft_expr!(meta mark set));
- self.batch.add(&rule, nftnl::MsgType::Add);
+ let mangle_chains = [&self.mangle_chain_v4, &self.mangle_chain_v6];
+ for chain in &mangle_chains {
+ let mut rule = Rule::new(chain);
+ rule.add_expr(&nft_expr!(meta cgroup));
+ rule.add_expr(&nft_expr!(cmp == split::NETCLS_CLASSID));
+ rule.add_expr(&nft_expr!(immediate data split::MARK));
+ rule.add_expr(&nft_expr!(ct mark set));
+ rule.add_expr(&nft_expr!(meta mark set));
+ self.batch.add(&rule, nftnl::MsgType::Add);
+ }
let mut rule = Rule::new(&self.in_chain);
rule.add_expr(&nft_expr!(ct mark));