diff options
author | Himbeer <himbeer@disroot.org> | 2024-08-20 15:27:11 +0200 |
---|---|---|
committer | Himbeer <himbeer@disroot.org> | 2024-08-20 15:27:11 +0200 |
commit | fd91cfa85a704cc02346aa7131901858e36a0fff (patch) | |
tree | ce98d8477c7a036548e25e3b467cf6e17146ed94 /src | |
parent | 4023acc763120b29190437f6622d3a2129cb1fbc (diff) |
Implement dynamic NPT for VPNs
Diffstat (limited to 'src')
-rw-r--r-- | src/error.rs | 4 | ||||
-rw-r--r-- | src/main.rs | 297 |
2 files changed, 295 insertions, 6 deletions
diff --git a/src/error.rs b/src/error.rs index 3431850..4fcba44 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,8 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum Error { + #[error("io: {0}")] + Io(#[from] std::io::Error), #[error("parse ip address: {0}")] AddrParse(#[from] std::net::AddrParseError), #[error("ipnetwork: {0}")] @@ -10,6 +12,8 @@ pub enum Error { RustablesBuilder(#[from] rustables::error::BuilderError), #[error("rustables query: {0}")] RustablesQuery(#[from] rustables::error::QueryError), + #[error("serde_json: {0}")] + SerdeJson(#[from] serde_json::Error), } pub type Result<T> = std::result::Result<T, Error>; diff --git a/src/main.rs b/src/main.rs index 6398e66..5d660f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,32 @@ use rsdsl_netfilterd::error::Result; -use std::net::Ipv4Addr; -use std::thread; -use std::time::Duration; +use std::fs::File; +use std::net::{Ipv4Addr, Ipv6Addr}; -use ipnetwork::Ipv4Network; +use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; +use rsdsl_pd_config::PdConfig; +use rustables::expr::{ + Bitwise, HighLevelPayload, IPv6HeaderField, Nat, NatType, NetworkHeaderField, Register, +}; use rustables::{ Batch, Chain, ChainPolicy, ChainType, Hook, HookClass, MsgType, Protocol, ProtocolFamily, Rule, Table, }; +use signal_hook::{consts::SIGUSR1, iterator::Signals}; + +const ULA: Ipv6Addr = Ipv6Addr::new(0xfd0b, 0x9272, 0x534e, 0, 0, 0, 0, 0); +const VPN_ULA: Ipv6Addr = Ipv6Addr::new(0xfd0b, 0x9272, 0x534e, 6, 0, 0, 0, 0); +const EXPOSED_VPN_ULA: Ipv6Addr = Ipv6Addr::new(0xfd0b, 0x9272, 0x534e, 7, 0, 0, 0, 0); + +#[derive(Debug)] +struct Npt { + postrouting: Chain, + prerouting: Chain, + map_vpn_to_gua: Rule, + map_exposed_vpn_to_gua: Rule, + map_gua_to_vpn: Rule, + map_gua_to_exposed_vpn: Rule, +} fn nat() -> Result<()> { let mut batch = Batch::new(); @@ -395,6 +413,244 @@ fn filter() -> Result<()> { Ok(()) } +fn enable_npt(prefix: Ipv6Addr) -> Result<Npt> { + let vpn_net = IpNetwork::V6(Ipv6Network::new(VPN_ULA, 64).unwrap()); + let exposed_vpn_net: IpNetwork = IpNetwork::V6(Ipv6Network::new(EXPOSED_VPN_ULA, 64).unwrap()); + + let ifid_mask = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ]; + let everything_mask = vec![0xff; 16]; + + let mut vpn_segments = prefix.segments(); + vpn_segments[3] += 6; + let vpn_prefix = Ipv6Addr::from(vpn_segments); + let vpn_subnet = IpNetwork::V6(Ipv6Network::new(vpn_prefix, 64).unwrap()); + + let mut exposed_vpn_segments = prefix.segments(); + exposed_vpn_segments[3] += 7; + let exposed_vpn_prefix = Ipv6Addr::from(exposed_vpn_segments); + let exposed_vpn_subnet = IpNetwork::V6(Ipv6Network::new(exposed_vpn_prefix, 64).unwrap()); + + let mut batch = Batch::new(); + + let npt = Table::new(ProtocolFamily::Ipv6).with_name("npt"); + batch.add(&npt, MsgType::Add); + + // +-------------------+ + // | POSTROUTING chain | + // +-------------------+ + + let mut postrouting = Chain::new(&npt).with_name("POSTROUTING"); + + postrouting.set_type(ChainType::Nat); + postrouting.set_hook(Hook::new(HookClass::PostRouting, 100)); + postrouting.set_policy(ChainPolicy::Accept); + + batch.add(&postrouting, MsgType::Add); + + let map_vpn_to_gua = Rule::new(&postrouting)? + .oface("ppp0")? + .snetwork(vpn_net)? + .with_expr( + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Saddr)).build(), + ) + .with_expr(Bitwise::new(ifid_mask.clone(), 0u128.to_be_bytes())?) + .with_expr(Bitwise::new(everything_mask.clone(), vpn_prefix.octets())?) + .with_expr(Nat { + nat_type: Some(NatType::SNat), + family: Some(ProtocolFamily::Ipv6), + ip_register: Some(Register::Reg1), + port_register: None, + }); + batch.add(&map_vpn_to_gua, MsgType::Add); + + let map_exposed_vpn_to_gua = Rule::new(&postrouting)? + .oface("ppp0")? + .snetwork(exposed_vpn_net)? + .with_expr( + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Saddr)).build(), + ) + .with_expr(Bitwise::new(ifid_mask.clone(), 0u128.to_be_bytes())?) + .with_expr(Bitwise::new( + everything_mask.clone(), + exposed_vpn_prefix.octets(), + )?) + .with_expr(Nat { + nat_type: Some(NatType::SNat), + family: Some(ProtocolFamily::Ipv6), + ip_register: Some(Register::Reg1), + port_register: None, + }); + batch.add(&map_exposed_vpn_to_gua, MsgType::Add); + + // +------------------+ + // | PREROUTING chain | + // +------------------+ + + let mut prerouting = Chain::new(&npt).with_name("PREROUTING"); + + prerouting.set_type(ChainType::Nat); + prerouting.set_hook(Hook::new(HookClass::PreRouting, -100)); + prerouting.set_policy(ChainPolicy::Accept); + + batch.add(&prerouting, MsgType::Add); + + let map_gua_to_vpn = Rule::new(&prerouting)? + .iface("ppp0")? + .dnetwork(vpn_subnet)? + .with_expr( + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Daddr)).build(), + ) + .with_expr(Bitwise::new(ifid_mask.clone(), 0u128.to_be_bytes())?) + .with_expr(Bitwise::new(everything_mask.clone(), VPN_ULA.octets())?) + .with_expr(Nat { + nat_type: Some(NatType::DNat), + family: Some(ProtocolFamily::Ipv6), + ip_register: Some(Register::Reg1), + port_register: None, + }); + batch.add(&map_gua_to_vpn, MsgType::Add); + + let map_gua_to_exposed_vpn = Rule::new(&prerouting)? + .iface("ppp0")? + .dnetwork(exposed_vpn_subnet)? + .with_expr( + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Daddr)).build(), + ) + .with_expr(Bitwise::new(ifid_mask, 0u128.to_be_bytes())?) + .with_expr(Bitwise::new(everything_mask, EXPOSED_VPN_ULA.octets())?) + .with_expr(Nat { + nat_type: Some(NatType::DNat), + family: Some(ProtocolFamily::Ipv6), + ip_register: Some(Register::Reg1), + port_register: None, + }); + batch.add(&map_gua_to_exposed_vpn, MsgType::Add); + + batch.send()?; + Ok(Npt { + postrouting, + prerouting, + map_vpn_to_gua, + map_exposed_vpn_to_gua, + map_gua_to_vpn, + map_gua_to_exposed_vpn, + }) +} + +fn update_npt(npt: &mut Npt, prefix: Ipv6Addr) -> Result<()> { + let vpn_net = IpNetwork::V6(Ipv6Network::new(VPN_ULA, 64).unwrap()); + let exposed_vpn_net: IpNetwork = IpNetwork::V6(Ipv6Network::new(EXPOSED_VPN_ULA, 64).unwrap()); + + let ifid_mask = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ]; + let everything_mask = vec![0xff; 16]; + + let mut vpn_segments = prefix.segments(); + vpn_segments[3] += 6; + let vpn_prefix = Ipv6Addr::from(vpn_segments); + let vpn_subnet = IpNetwork::V6(Ipv6Network::new(vpn_prefix, 64).unwrap()); + + let mut exposed_vpn_segments = prefix.segments(); + exposed_vpn_segments[3] += 7; + let exposed_vpn_prefix = Ipv6Addr::from(exposed_vpn_segments); + let exposed_vpn_subnet = IpNetwork::V6(Ipv6Network::new(exposed_vpn_prefix, 64).unwrap()); + + let mut batch = Batch::new(); + + batch.add(&npt.map_vpn_to_gua, MsgType::Del); + batch.add(&npt.map_exposed_vpn_to_gua, MsgType::Del); + batch.add(&npt.map_gua_to_vpn, MsgType::Del); + batch.add(&npt.map_gua_to_exposed_vpn, MsgType::Del); + + // +-------------------+ + // | POSTROUTING chain | + // +-------------------+ + + npt.map_vpn_to_gua = Rule::new(&npt.postrouting)? + .oface("ppp0")? + .snetwork(vpn_net)? + .with_expr( + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Saddr)).build(), + ) + .with_expr(Bitwise::new(ifid_mask.clone(), 0u128.to_be_bytes())?) + .with_expr(Bitwise::new(everything_mask.clone(), vpn_prefix.octets())?) + .with_expr(Nat { + nat_type: Some(NatType::SNat), + family: Some(ProtocolFamily::Ipv6), + ip_register: Some(Register::Reg1), + port_register: None, + }); + batch.add(&npt.map_vpn_to_gua, MsgType::Add); + + npt.map_exposed_vpn_to_gua = Rule::new(&npt.postrouting)? + .oface("ppp0")? + .snetwork(exposed_vpn_net)? + .with_expr( + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Saddr)).build(), + ) + .with_expr(Bitwise::new(ifid_mask.clone(), 0u128.to_be_bytes())?) + .with_expr(Bitwise::new( + everything_mask.clone(), + exposed_vpn_prefix.octets(), + )?) + .with_expr(Nat { + nat_type: Some(NatType::SNat), + family: Some(ProtocolFamily::Ipv6), + ip_register: Some(Register::Reg1), + port_register: None, + }); + batch.add(&npt.map_exposed_vpn_to_gua, MsgType::Add); + + // +------------------+ + // | PREROUTING chain | + // +------------------+ + + npt.map_gua_to_vpn = Rule::new(&npt.prerouting)? + .iface("ppp0")? + .dnetwork(vpn_subnet)? + .with_expr( + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Daddr)).build(), + ) + .with_expr(Bitwise::new(ifid_mask.clone(), 0u128.to_be_bytes())?) + .with_expr(Bitwise::new(everything_mask.clone(), VPN_ULA.octets())?) + .with_expr(Nat { + nat_type: Some(NatType::DNat), + family: Some(ProtocolFamily::Ipv6), + ip_register: Some(Register::Reg1), + port_register: None, + }); + batch.add(&npt.map_gua_to_vpn, MsgType::Add); + + npt.map_gua_to_exposed_vpn = Rule::new(&npt.prerouting)? + .iface("ppp0")? + .dnetwork(exposed_vpn_subnet)? + .with_expr( + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Daddr)).build(), + ) + .with_expr(Bitwise::new(ifid_mask, 0u128.to_be_bytes())?) + .with_expr(Bitwise::new(everything_mask, EXPOSED_VPN_ULA.octets())?) + .with_expr(Nat { + nat_type: Some(NatType::DNat), + family: Some(ProtocolFamily::Ipv6), + ip_register: Some(Register::Reg1), + port_register: None, + }); + batch.add(&npt.map_gua_to_exposed_vpn, MsgType::Add); + + batch.send()?; + Ok(()) +} + +fn read_prefix() -> Result<Ipv6Addr> { + let mut file = File::open(rsdsl_pd_config::LOCATION)?; + let pdconfig: PdConfig = serde_json::from_reader(&mut file)?; + + Ok(pdconfig.prefix) +} + fn main() -> Result<()> { match nat() { Ok(_) => println!("enable nat"), @@ -412,7 +668,36 @@ fn main() -> Result<()> { } } - loop { - thread::sleep(Duration::MAX); + let prefix = read_prefix().unwrap_or(ULA); + + let mut npt = match enable_npt(prefix) { + Ok(npt) => { + println!("enable npt"); + npt + } + Err(e) => { + println!("can't enable npt: {}", e); + return Err(e); + } + }; + + let mut signals = match Signals::new([SIGUSR1]) { + Ok(signals) => signals, + Err(e) => { + println!("signal handling: {}", e); + return Err(e.into()); + } + }; + + for _ in signals.forever() { + let prefix = read_prefix().unwrap_or(ULA); + + match update_npt(&mut npt, prefix) { + Ok(_) => println!("update npt"), + Err(e) => println!("can't update npt: {}", e), + } } + + println!("no more signals"); + Ok(()) } |