aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHimbeer <himbeer@disroot.org>2024-08-20 15:27:11 +0200
committerHimbeer <himbeer@disroot.org>2024-08-20 15:27:11 +0200
commitfd91cfa85a704cc02346aa7131901858e36a0fff (patch)
treece98d8477c7a036548e25e3b467cf6e17146ed94 /src
parent4023acc763120b29190437f6622d3a2129cb1fbc (diff)
Implement dynamic NPT for VPNs
Diffstat (limited to 'src')
-rw-r--r--src/error.rs4
-rw-r--r--src/main.rs297
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(())
}