Initial support for VLANs
This commit is contained in:
parent
850a45a727
commit
ccf6b29423
|
@ -3,6 +3,9 @@ external = "eth[0-9]+"
|
||||||
internal = [
|
internal = [
|
||||||
"enp1s0"
|
"enp1s0"
|
||||||
]
|
]
|
||||||
|
vlan = [
|
||||||
|
"vlan[0-9]+"
|
||||||
|
]
|
||||||
tunnel = [
|
tunnel = [
|
||||||
"wg[0-9]+"
|
"wg[0-9]+"
|
||||||
]
|
]
|
||||||
|
|
169
src/iptables.rs
169
src/iptables.rs
|
@ -25,43 +25,7 @@ impl fmt::Display for Proto {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn input_accept(
|
pub(crate) async fn input(
|
||||||
proto: Proto,
|
|
||||||
external_interface: &str,
|
|
||||||
external_ip: Ipv4Addr,
|
|
||||||
external_port: u16,
|
|
||||||
external_mask: u8,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
input(
|
|
||||||
proto,
|
|
||||||
external_interface,
|
|
||||||
external_ip,
|
|
||||||
external_port,
|
|
||||||
external_mask,
|
|
||||||
move |cmd| cmd.arg("-I"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn delete_input_accept(
|
|
||||||
proto: Proto,
|
|
||||||
external_interface: &str,
|
|
||||||
external_ip: Ipv4Addr,
|
|
||||||
external_port: u16,
|
|
||||||
external_mask: u8,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
input(
|
|
||||||
proto,
|
|
||||||
external_interface,
|
|
||||||
external_ip,
|
|
||||||
external_port,
|
|
||||||
external_mask,
|
|
||||||
move |cmd| cmd.arg("-D"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn input(
|
|
||||||
proto: Proto,
|
proto: Proto,
|
||||||
external_interface: &str,
|
external_interface: &str,
|
||||||
external_ip: Ipv4Addr,
|
external_ip: Ipv4Addr,
|
||||||
|
@ -93,39 +57,36 @@ async fn input(
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn forward_accept(
|
pub(crate) async fn outbound_forward_established(
|
||||||
external_interface: &str,
|
external_interface: &str,
|
||||||
internal_interface: &str,
|
internal_interface: &str,
|
||||||
proto: Proto,
|
proto: Proto,
|
||||||
internal_port: u16,
|
internal_port: u16,
|
||||||
|
func: impl Fn(&mut Command) -> &mut Command,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
forward(
|
iptables_filter(move |cmd| {
|
||||||
external_interface,
|
func(cmd).args(&[
|
||||||
internal_interface,
|
"FORWARD",
|
||||||
proto,
|
"-i",
|
||||||
internal_port,
|
internal_interface,
|
||||||
move |cmd| cmd.arg("-I"),
|
"-o",
|
||||||
)
|
external_interface,
|
||||||
|
"-p",
|
||||||
|
proto.as_iptables_str(),
|
||||||
|
"--sport",
|
||||||
|
&internal_port.to_string(),
|
||||||
|
"-m",
|
||||||
|
"conntrack",
|
||||||
|
"--ctstate",
|
||||||
|
"ESTABLISHED,RELATED",
|
||||||
|
"-j",
|
||||||
|
"ACCEPT",
|
||||||
|
])
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn delete_forward_accept(
|
pub(crate) async fn forward(
|
||||||
external_interface: &str,
|
|
||||||
internal_interface: &str,
|
|
||||||
proto: Proto,
|
|
||||||
internal_port: u16,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
forward(
|
|
||||||
external_interface,
|
|
||||||
internal_interface,
|
|
||||||
proto,
|
|
||||||
internal_port,
|
|
||||||
move |cmd| cmd.arg("-D"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn forward(
|
|
||||||
external_interface: &str,
|
external_interface: &str,
|
||||||
internal_interface: &str,
|
internal_interface: &str,
|
||||||
proto: Proto,
|
proto: Proto,
|
||||||
|
@ -154,47 +115,7 @@ async fn forward(
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn forward_postrouting(
|
pub(crate) async fn forward_postrouting_snat(
|
||||||
proto: Proto,
|
|
||||||
internal_ip: Ipv4Addr,
|
|
||||||
internal_mask: u8,
|
|
||||||
internal_port: u16,
|
|
||||||
external_ip: Ipv4Addr,
|
|
||||||
destination_ip: Ipv4Addr,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
forward_postrouting_snat(
|
|
||||||
proto,
|
|
||||||
internal_ip,
|
|
||||||
internal_mask,
|
|
||||||
internal_port,
|
|
||||||
external_ip,
|
|
||||||
destination_ip,
|
|
||||||
|cmd| cmd.arg("-I"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn delete_forward_postrouting(
|
|
||||||
proto: Proto,
|
|
||||||
internal_ip: Ipv4Addr,
|
|
||||||
internal_mask: u8,
|
|
||||||
internal_port: u16,
|
|
||||||
external_ip: Ipv4Addr,
|
|
||||||
destination_ip: Ipv4Addr,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
forward_postrouting_snat(
|
|
||||||
proto,
|
|
||||||
internal_ip,
|
|
||||||
internal_mask,
|
|
||||||
internal_port,
|
|
||||||
external_ip,
|
|
||||||
destination_ip,
|
|
||||||
|cmd| cmd.arg("-D"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn forward_postrouting_snat(
|
|
||||||
proto: Proto,
|
proto: Proto,
|
||||||
internal_ip: Ipv4Addr,
|
internal_ip: Ipv4Addr,
|
||||||
internal_mask: u8,
|
internal_mask: u8,
|
||||||
|
@ -229,47 +150,7 @@ async fn forward_postrouting_snat(
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn forward_prerouting(
|
pub(crate) async fn forward_prerouting_dnat(
|
||||||
proto: Proto,
|
|
||||||
external_ip: Ipv4Addr,
|
|
||||||
external_mask: u8,
|
|
||||||
external_port: u16,
|
|
||||||
destination_ip: Ipv4Addr,
|
|
||||||
destination_port: u16,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
forward_prerouting_dnat(
|
|
||||||
proto,
|
|
||||||
external_ip,
|
|
||||||
external_mask,
|
|
||||||
external_port,
|
|
||||||
destination_ip,
|
|
||||||
destination_port,
|
|
||||||
|cmd| cmd.arg("-I"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn delete_forward_prerouting(
|
|
||||||
proto: Proto,
|
|
||||||
external_ip: Ipv4Addr,
|
|
||||||
external_mask: u8,
|
|
||||||
external_port: u16,
|
|
||||||
destination_ip: Ipv4Addr,
|
|
||||||
destination_port: u16,
|
|
||||||
) -> Result<(), anyhow::Error> {
|
|
||||||
forward_prerouting_dnat(
|
|
||||||
proto,
|
|
||||||
external_ip,
|
|
||||||
external_mask,
|
|
||||||
external_port,
|
|
||||||
destination_ip,
|
|
||||||
destination_port,
|
|
||||||
|cmd| cmd.arg("-D"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn forward_prerouting_dnat(
|
|
||||||
proto: Proto,
|
proto: Proto,
|
||||||
external_ip: Ipv4Addr,
|
external_ip: Ipv4Addr,
|
||||||
external_mask: u8,
|
external_mask: u8,
|
||||||
|
|
243
src/rules.rs
243
src/rules.rs
|
@ -78,69 +78,103 @@ pub(crate) async fn delete(db: &Db, rule_id: &str) -> Result<Rule, anyhow::Error
|
||||||
Ok(rule)
|
Ok(rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn unset(interfaces: &Interfaces, rule: Rule) -> Result<(), anyhow::Error> {
|
async fn set_rule(
|
||||||
|
interfaces: &Interfaces,
|
||||||
|
rule: Rule,
|
||||||
|
func: impl Fn(&mut async_process::Command) -> &mut async_process::Command + Copy,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
match rule.kind {
|
match rule.kind {
|
||||||
RuleKind::Accept => {
|
RuleKind::Accept => {
|
||||||
iptables::delete_input_accept(
|
iptables::input(
|
||||||
rule.proto,
|
rule.proto,
|
||||||
&interfaces.external.interface,
|
&interfaces.external.interface,
|
||||||
interfaces.external.ip,
|
interfaces.external.ip,
|
||||||
rule.port,
|
rule.port,
|
||||||
interfaces.external.mask,
|
interfaces.external.mask,
|
||||||
|
func,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
RuleKind::Forward { dest_ip, dest_port } => {
|
RuleKind::Forward { dest_ip, dest_port } => {
|
||||||
for info in &interfaces.internal {
|
let internal_iface = interfaces
|
||||||
iptables::delete_forward_accept(
|
.internal
|
||||||
&interfaces.external.interface,
|
.iter()
|
||||||
&info.interface,
|
.chain(&interfaces.tunnel)
|
||||||
rule.proto,
|
.chain(&interfaces.vlan)
|
||||||
dest_port,
|
.find(|info| mask_matches(info.ip, info.mask, dest_ip));
|
||||||
)
|
|
||||||
.await?;
|
let internal_iface = if let Some(internal_iface) = internal_iface {
|
||||||
}
|
internal_iface
|
||||||
iptables::delete_forward_prerouting(
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
iptables::forward(
|
||||||
|
&interfaces.external.interface,
|
||||||
|
&internal_iface.interface,
|
||||||
|
rule.proto,
|
||||||
|
dest_port,
|
||||||
|
func,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
iptables::forward_prerouting_dnat(
|
||||||
rule.proto,
|
rule.proto,
|
||||||
interfaces.external.ip,
|
interfaces.external.ip,
|
||||||
interfaces.external.mask,
|
interfaces.external.mask,
|
||||||
rule.port,
|
rule.port,
|
||||||
dest_ip,
|
dest_ip,
|
||||||
dest_port,
|
dest_port,
|
||||||
|
func,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
iptables::forward_postrouting_snat(
|
||||||
|
rule.proto,
|
||||||
|
internal_iface.ip,
|
||||||
|
internal_iface.mask,
|
||||||
|
dest_port,
|
||||||
|
interfaces.external.ip,
|
||||||
|
dest_ip,
|
||||||
|
func,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
for iface in interfaces.internal.iter().chain(interfaces.tunnel.iter()) {
|
|
||||||
let is_nat_iface = interfaces
|
|
||||||
.nats
|
|
||||||
.iter()
|
|
||||||
.any(|nat_iface| *nat_iface == iface.interface);
|
|
||||||
|
|
||||||
|
for iface in interfaces
|
||||||
|
.internal
|
||||||
|
.iter()
|
||||||
|
.chain(&interfaces.tunnel)
|
||||||
|
.chain(&interfaces.vlan)
|
||||||
|
.filter(|iface| *iface != internal_iface)
|
||||||
|
{
|
||||||
let has_nat_subnet = interfaces.nats.iter().any(|nat_iface| {
|
let has_nat_subnet = interfaces.nats.iter().any(|nat_iface| {
|
||||||
*nat_iface != iface.interface
|
*nat_iface == iface.interface
|
||||||
&& interfaces
|
|| *nat_iface != iface.interface
|
||||||
.internal
|
&& interfaces
|
||||||
.iter()
|
.internal
|
||||||
.chain(interfaces.tunnel.iter())
|
.iter()
|
||||||
.any(|other_iface| {
|
.chain(&interfaces.tunnel)
|
||||||
*nat_iface == other_iface.interface
|
.chain(&interfaces.vlan)
|
||||||
&& other_iface.ip == iface.ip
|
.any(|other_iface| {
|
||||||
&& other_iface.mask == iface.mask
|
*nat_iface == other_iface.interface
|
||||||
})
|
&& other_iface.ip == iface.ip
|
||||||
|
&& other_iface.mask == iface.mask
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if is_nat_iface {
|
if !has_nat_subnet {
|
||||||
iptables::delete_forward_prerouting(
|
iptables::forward(
|
||||||
rule.proto, iface.ip, iface.mask, rule.port, dest_ip, dest_port,
|
&iface.interface,
|
||||||
|
&internal_iface.interface,
|
||||||
|
rule.proto,
|
||||||
|
dest_port,
|
||||||
|
func,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else if !has_nat_subnet {
|
iptables::outbound_forward_established(
|
||||||
iptables::delete_forward_postrouting(
|
&iface.interface,
|
||||||
|
&internal_iface.interface,
|
||||||
rule.proto,
|
rule.proto,
|
||||||
iface.ip,
|
|
||||||
iface.mask,
|
|
||||||
dest_port,
|
dest_port,
|
||||||
interfaces.external.ip,
|
func,
|
||||||
dest_ip,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@ -151,6 +185,14 @@ pub(crate) async fn unset(interfaces: &Interfaces, rule: Rule) -> Result<(), any
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn apply(interfaces: &Interfaces, rule: Rule) -> Result<(), anyhow::Error> {
|
||||||
|
set_rule(interfaces, rule, |cmd| cmd.arg("-I")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn unset(interfaces: &Interfaces, rule: Rule) -> Result<(), anyhow::Error> {
|
||||||
|
set_rule(interfaces, rule, |cmd| cmd.arg("-D")).await
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn save(db: &Db, rule: &Rule) -> Result<(), anyhow::Error> {
|
pub(crate) async fn save(db: &Db, rule: &Rule) -> Result<(), anyhow::Error> {
|
||||||
let tree = rules_tree(db);
|
let tree = rules_tree(db);
|
||||||
|
|
||||||
|
@ -164,77 +206,29 @@ pub(crate) async fn save(db: &Db, rule: &Rule) -> Result<(), anyhow::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn apply(interfaces: &Interfaces, rule: Rule) -> Result<(), anyhow::Error> {
|
// 255 - 2^n where n is the index
|
||||||
match rule.kind {
|
const MASK_BITS: [u8; 7] = [254, 252, 248, 240, 224, 192, 128];
|
||||||
RuleKind::Accept => {
|
|
||||||
iptables::input_accept(
|
|
||||||
rule.proto,
|
|
||||||
&interfaces.external.interface,
|
|
||||||
interfaces.external.ip,
|
|
||||||
rule.port,
|
|
||||||
interfaces.external.mask,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
RuleKind::Forward { dest_ip, dest_port } => {
|
|
||||||
for info in &interfaces.internal {
|
|
||||||
iptables::forward_accept(
|
|
||||||
&interfaces.external.interface,
|
|
||||||
&info.interface,
|
|
||||||
rule.proto,
|
|
||||||
dest_port,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
iptables::forward_prerouting(
|
|
||||||
rule.proto,
|
|
||||||
interfaces.external.ip,
|
|
||||||
interfaces.external.mask,
|
|
||||||
rule.port,
|
|
||||||
dest_ip,
|
|
||||||
dest_port,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
for iface in interfaces.internal.iter().chain(interfaces.tunnel.iter()) {
|
|
||||||
let is_nat_iface = interfaces
|
|
||||||
.nats
|
|
||||||
.iter()
|
|
||||||
.any(|nat_iface| *nat_iface == iface.interface);
|
|
||||||
|
|
||||||
let has_nat_subnet = interfaces.nats.iter().any(|nat_iface| {
|
fn mask_matches(mask_ip: Ipv4Addr, netmask: u8, rule_ip: Ipv4Addr) -> bool {
|
||||||
*nat_iface != iface.interface
|
let mut count: u8 = 8;
|
||||||
&& interfaces
|
let mut matches = true;
|
||||||
.internal
|
|
||||||
.iter()
|
|
||||||
.chain(interfaces.tunnel.iter())
|
|
||||||
.any(|other_iface| {
|
|
||||||
*nat_iface == other_iface.interface
|
|
||||||
&& other_iface.ip == iface.ip
|
|
||||||
&& other_iface.mask == iface.mask
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_nat_iface {
|
for (mask_byte, rule_byte) in mask_ip.octets().iter().zip(&rule_ip.octets()) {
|
||||||
iptables::forward_prerouting(
|
if count <= netmask {
|
||||||
rule.proto, iface.ip, iface.mask, rule.port, dest_ip, dest_port,
|
matches = matches && mask_byte == rule_byte
|
||||||
)
|
} else {
|
||||||
.await?;
|
let remaining = count.saturating_sub(netmask);
|
||||||
} else if !has_nat_subnet {
|
if remaining < 8 {
|
||||||
iptables::forward_postrouting(
|
let mask = MASK_BITS[remaining.saturating_sub(1) as usize];
|
||||||
rule.proto,
|
|
||||||
iface.ip,
|
matches = matches && (mask_byte & mask) == (rule_byte & mask);
|
||||||
iface.mask,
|
|
||||||
dest_port,
|
|
||||||
interfaces.external.ip,
|
|
||||||
dest_ip,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
matches
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rule_id(id: u64) -> String {
|
fn rule_id(id: u64) -> String {
|
||||||
|
@ -243,11 +237,58 @@ fn rule_id(id: u64) -> String {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::mask_matches;
|
||||||
use crate::{
|
use crate::{
|
||||||
iptables::Proto,
|
iptables::Proto,
|
||||||
rules::{Rule, RuleKind},
|
rules::{Rule, RuleKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ips_match_mask() {
|
||||||
|
let tests = [
|
||||||
|
("192.168.6.0", 24, "192.168.6.1"),
|
||||||
|
("192.168.6.0", 24, "192.168.6.254"),
|
||||||
|
("192.168.6.0", 25, "192.168.6.1"),
|
||||||
|
("192.168.6.0", 26, "192.168.6.1"),
|
||||||
|
("192.168.6.0", 27, "192.168.6.1"),
|
||||||
|
("192.168.6.0", 31, "192.168.6.1"),
|
||||||
|
("192.168.6.0", 32, "192.168.6.0"),
|
||||||
|
("192.168.0.0", 16, "192.168.255.0"),
|
||||||
|
("192.168.0.0", 16, "192.168.0.255"),
|
||||||
|
("192.168.0.0", 16, "192.168.1.0"),
|
||||||
|
("192.168.0.0", 16, "192.168.0.1"),
|
||||||
|
("192.168.0.0", 16, "192.168.128.0"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (mask_ip, mask, rule_ip) in tests {
|
||||||
|
let matches = mask_matches(mask_ip.parse().unwrap(), mask, rule_ip.parse().unwrap());
|
||||||
|
|
||||||
|
assert!(matches);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ips_dont_match_mask() {
|
||||||
|
let tests = [
|
||||||
|
("192.168.6.0", 24, "192.168.5.0"),
|
||||||
|
("192.168.6.0", 25, "192.168.6.128"),
|
||||||
|
("192.168.6.0", 31, "192.168.6.2"),
|
||||||
|
("192.169.0.0", 16, "192.168.0.1"),
|
||||||
|
("192.1.0.0", 16, "192.0.0.0"),
|
||||||
|
("192.0.0.0", 16, "192.1.0.0"),
|
||||||
|
("192.255.0.0", 16, "192.0.0.0"),
|
||||||
|
("192.0.0.0", 16, "192.255.0.0"),
|
||||||
|
("192.168.0.0", 17, "192.168.128.0"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (mask_ip, mask, rule_ip) in tests {
|
||||||
|
println!("comparing: {}/{} to {}", mask_ip, mask, rule_ip);
|
||||||
|
let matches = mask_matches(mask_ip.parse().unwrap(), mask, rule_ip.parse().unwrap());
|
||||||
|
|
||||||
|
assert!(!matches);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_serialize() {
|
fn can_serialize() {
|
||||||
let rule = Rule {
|
let rule = Rule {
|
||||||
|
|
|
@ -20,6 +20,7 @@ struct InterfaceConfig {
|
||||||
external: String,
|
external: String,
|
||||||
internal: Vec<String>,
|
internal: Vec<String>,
|
||||||
tunnel: Vec<String>,
|
tunnel: Vec<String>,
|
||||||
|
vlan: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
|
@ -39,11 +40,13 @@ struct ServerConfig {
|
||||||
pub struct Interfaces {
|
pub struct Interfaces {
|
||||||
pub(crate) external: InterfaceInfo,
|
pub(crate) external: InterfaceInfo,
|
||||||
pub(crate) internal: Vec<InterfaceInfo>,
|
pub(crate) internal: Vec<InterfaceInfo>,
|
||||||
|
pub(crate) vlan: Vec<InterfaceInfo>,
|
||||||
pub(crate) tunnel: Vec<InterfaceInfo>,
|
pub(crate) tunnel: Vec<InterfaceInfo>,
|
||||||
pub(crate) shared_internal: bool,
|
pub(crate) shared_internal: bool,
|
||||||
pub(crate) nats: Vec<String>,
|
pub(crate) nats: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub(crate) struct InterfaceInfo {
|
pub(crate) struct InterfaceInfo {
|
||||||
pub(crate) interface: String,
|
pub(crate) interface: String,
|
||||||
pub(crate) ip: Ipv4Addr,
|
pub(crate) ip: Ipv4Addr,
|
||||||
|
@ -90,6 +93,11 @@ impl Interfaces {
|
||||||
ip: "192.168.6.1".parse()?,
|
ip: "192.168.6.1".parse()?,
|
||||||
mask: 24,
|
mask: 24,
|
||||||
}],
|
}],
|
||||||
|
vlan: vec![InterfaceInfo {
|
||||||
|
interface: String::from("vlan20@enp1s0"),
|
||||||
|
ip: "192.168.6.20".parse()?,
|
||||||
|
mask: 24,
|
||||||
|
}],
|
||||||
tunnel: vec![
|
tunnel: vec![
|
||||||
InterfaceInfo {
|
InterfaceInfo {
|
||||||
interface: String::from("wg0"),
|
interface: String::from("wg0"),
|
||||||
|
@ -135,6 +143,12 @@ impl Interfaces {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut vlan = Vec::new();
|
||||||
|
|
||||||
|
for iface in &config.interface.vlan {
|
||||||
|
vlan.extend(parse_interface_info(&output, &iface)?);
|
||||||
|
}
|
||||||
|
|
||||||
let mut tunnel = Vec::new();
|
let mut tunnel = Vec::new();
|
||||||
|
|
||||||
for iface in &config.interface.tunnel {
|
for iface in &config.interface.tunnel {
|
||||||
|
@ -144,6 +158,7 @@ impl Interfaces {
|
||||||
Ok(Interfaces {
|
Ok(Interfaces {
|
||||||
external,
|
external,
|
||||||
internal,
|
internal,
|
||||||
|
vlan,
|
||||||
tunnel,
|
tunnel,
|
||||||
shared_internal: config.network.shared_internal,
|
shared_internal: config.network.shared_internal,
|
||||||
nats: config.network.nats.clone(),
|
nats: config.network.nats.clone(),
|
||||||
|
@ -218,13 +233,45 @@ mod tests {
|
||||||
valid_lft forever preferred_lft forever
|
valid_lft forever preferred_lft forever
|
||||||
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
|
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
|
||||||
link/ether ca:b8:a7:51:07:3d brd ff:ff:ff:ff:ff:ff
|
link/ether ca:b8:a7:51:07:3d brd ff:ff:ff:ff:ff:ff
|
||||||
inet 136.49.5.58/20 brd 136.49.15.255 scope global dynamic eth1
|
inet 70.112.254.176/19 brd 70.112.255.255 scope global dynamic eth1
|
||||||
valid_lft 85973sec preferred_lft 85973sec
|
valid_lft 45780sec preferred_lft 45780sec
|
||||||
|
inet6 2605:6000:ffc0:77:f985:3af7:8be2:ba44/128 scope global dynamic noprefixroute
|
||||||
|
valid_lft 584496sec preferred_lft 584496sec
|
||||||
inet6 fe80::c8b8:a7ff:fe51:73d/64 scope link
|
inet6 fe80::c8b8:a7ff:fe51:73d/64 scope link
|
||||||
valid_lft forever preferred_lft forever
|
valid_lft forever preferred_lft forever
|
||||||
4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
|
4: vlan40@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
||||||
|
link/ether 24:4b:fe:37:ad:b1 brd ff:ff:ff:ff:ff:ff
|
||||||
|
inet 192.168.40.1/24 brd 192.168.40.255 scope global vlan40
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 fe80::264b:feff:fe37:adb1/64 scope link
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
5: vlan30@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
||||||
|
link/ether 24:4b:fe:37:ad:b1 brd ff:ff:ff:ff:ff:ff
|
||||||
|
inet 192.168.30.1/24 brd 192.168.30.255 scope global vlan30
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 fe80::264b:feff:fe37:adb1/64 scope link
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
6: vlan20@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
||||||
|
link/ether 24:4b:fe:37:ad:b1 brd ff:ff:ff:ff:ff:ff
|
||||||
|
inet 192.168.20.1/24 brd 192.168.20.255 scope global vlan20
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 fe80::264b:feff:fe37:adb1/64 scope link
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
7: wg1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||||
link/none
|
link/none
|
||||||
inet 192.168.5.0/24 scope global wg0
|
inet 10.42.6.1/24 scope global wg1
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
8: wg2: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||||
|
link/none
|
||||||
|
inet 10.42.6.1/24 scope global wg2
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
9: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||||
|
link/none
|
||||||
|
inet 192.168.5.1/24 scope global wg0
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
10: wg3: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||||
|
link/none
|
||||||
|
inet 192.168.4.1/24 scope global wg3
|
||||||
valid_lft forever preferred_lft forever
|
valid_lft forever preferred_lft forever
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
@ -236,8 +283,8 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(info.interface, "eth1");
|
assert_eq!(info.interface, "eth1");
|
||||||
assert_eq!(info.ip.to_string(), "136.49.5.58");
|
assert_eq!(info.ip.to_string(), "70.112.254.176");
|
||||||
assert_eq!(info.mask, 20);
|
assert_eq!(info.mask, 19);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -251,4 +298,28 @@ mod tests {
|
||||||
assert_eq!(info.ip.to_string(), "192.168.6.1");
|
assert_eq!(info.ip.to_string(), "192.168.6.1");
|
||||||
assert_eq!(info.mask, 24);
|
assert_eq!(info.mask, 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_tunnel() {
|
||||||
|
let info = parse_interface_info(OUTPUT, "wg[0-9]+")
|
||||||
|
.unwrap()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(info.interface, "wg1");
|
||||||
|
assert_eq!(info.ip.to_string(), "10.42.6.1");
|
||||||
|
assert_eq!(info.mask, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_vlan() {
|
||||||
|
let info = parse_interface_info(OUTPUT, "vlan[0-9]+")
|
||||||
|
.unwrap()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(info.interface, "vlan40");
|
||||||
|
assert_eq!(info.ip.to_string(), "192.168.40.1");
|
||||||
|
assert_eq!(info.mask, 24);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allow internal machines to connect to anything
|
// Allow internal machines to connect to anything
|
||||||
for iface in &interfaces.internal {
|
for iface in interfaces.internal.iter().chain(&interfaces.vlan) {
|
||||||
filter += &format!(
|
filter += &format!(
|
||||||
"-A INPUT -i {intif} -s {intip}/{intmask} -d {universe} -j ACCEPT\n",
|
"-A INPUT -i {intif} -s {intip}/{intmask} -d {universe} -j ACCEPT\n",
|
||||||
intif = iface.interface,
|
intif = iface.interface,
|
||||||
|
@ -38,7 +38,7 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
|
|
||||||
// Disallow IP spoofing, internal IPs should only come from the internal network
|
// Disallow IP spoofing, internal IPs should only come from the internal network
|
||||||
// If an internal IP is seen by the external interface, it's BAD!!!!
|
// If an internal IP is seen by the external interface, it's BAD!!!!
|
||||||
for iface in &interfaces.internal {
|
for iface in interfaces.internal.iter().chain(&interfaces.vlan) {
|
||||||
filter += &format!(
|
filter += &format!(
|
||||||
"-A INPUT -i {extif} -s {intip}/{intmask} -d {universe} -j REJECT\n",
|
"-A INPUT -i {extif} -s {intip}/{intmask} -d {universe} -j REJECT\n",
|
||||||
extif = interfaces.external.interface,
|
extif = interfaces.external.interface,
|
||||||
|
@ -67,7 +67,7 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allow DHCP traffic
|
// Allow DHCP traffic
|
||||||
for iface in &interfaces.internal {
|
for iface in interfaces.internal.iter().chain(&interfaces.vlan) {
|
||||||
filter += &format!(
|
filter += &format!(
|
||||||
"-A INPUT -i {intif} -p tcp --sport 68 --dport 67 -j ACCEPT\n",
|
"-A INPUT -i {intif} -p tcp --sport 68 --dport 67 -j ACCEPT\n",
|
||||||
intif = iface.interface,
|
intif = iface.interface,
|
||||||
|
@ -102,6 +102,27 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
universe = UNIVERSE,
|
universe = UNIVERSE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Allow VLAN traffic only on VLAN
|
||||||
|
for iface in &interfaces.vlan {
|
||||||
|
// Allow internal traffic only on network associated with interface
|
||||||
|
filter += &format!(
|
||||||
|
"-A OUTPUT -o {intif} -s {extip}/{extmask} -d {intip}/{intmask} -j ACCEPT\n",
|
||||||
|
intif = iface.interface,
|
||||||
|
extip = interfaces.external.ip,
|
||||||
|
extmask = interfaces.external.mask,
|
||||||
|
intip = iface.ip,
|
||||||
|
intmask = iface.mask,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Allow traffic from self to networks associated with interface
|
||||||
|
filter += &format!(
|
||||||
|
"-A OUTPUT -o {intif} -s {intip}/32 -d {intip}/{intmask} -j ACCEPT\n",
|
||||||
|
intif = iface.interface,
|
||||||
|
intip = iface.ip,
|
||||||
|
intmask = iface.mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if interfaces.shared_internal {
|
if interfaces.shared_internal {
|
||||||
for iface in &interfaces.internal {
|
for iface in &interfaces.internal {
|
||||||
// jface (jeans iface)
|
// jface (jeans iface)
|
||||||
|
@ -148,7 +169,7 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for iface in &interfaces.internal {
|
for iface in interfaces.internal.iter().chain(&interfaces.vlan) {
|
||||||
// Deny traffic to internal networks on external interface
|
// Deny traffic to internal networks on external interface
|
||||||
filter += &format!(
|
filter += &format!(
|
||||||
"-A OUTPUT -o {extif} -s {universe} -d {intip}/{intmask} -j REJECT\n",
|
"-A OUTPUT -o {extif} -s {universe} -d {intip}/{intmask} -j REJECT\n",
|
||||||
|
@ -175,7 +196,7 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
universe = UNIVERSE,
|
universe = UNIVERSE,
|
||||||
);
|
);
|
||||||
|
|
||||||
for iface in &interfaces.internal {
|
for iface in interfaces.internal.iter().chain(&interfaces.vlan) {
|
||||||
// Allow DHCP traffic out from internal interfaces
|
// Allow DHCP traffic out from internal interfaces
|
||||||
filter += &format!(
|
filter += &format!(
|
||||||
"-A OUTPUT -o {intif} -p tcp -s {intip} --sport 67 -d 255.255.255.255 --dport 68 -j ACCEPT\n",
|
"-A OUTPUT -o {intif} -p tcp -s {intip} --sport 67 -d 255.255.255.255 --dport 68 -j ACCEPT\n",
|
||||||
|
@ -203,6 +224,7 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
.internal
|
.internal
|
||||||
.iter()
|
.iter()
|
||||||
.chain(interfaces.tunnel.iter())
|
.chain(interfaces.tunnel.iter())
|
||||||
|
.chain(interfaces.vlan.iter())
|
||||||
.find(|iface| iface.interface == *nat_iface)
|
.find(|iface| iface.interface == *nat_iface)
|
||||||
{
|
{
|
||||||
filter += &format!(
|
filter += &format!(
|
||||||
|
@ -213,13 +235,19 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for iface in interfaces.internal.iter().chain(interfaces.tunnel.iter()) {
|
for iface in interfaces
|
||||||
|
.internal
|
||||||
|
.iter()
|
||||||
|
.chain(interfaces.tunnel.iter())
|
||||||
|
.chain(interfaces.vlan.iter())
|
||||||
|
{
|
||||||
let is_nat_iface = *nat_iface == iface.interface;
|
let is_nat_iface = *nat_iface == iface.interface;
|
||||||
|
|
||||||
let has_nat_subnet = interfaces
|
let has_nat_subnet = interfaces
|
||||||
.internal
|
.internal
|
||||||
.iter()
|
.iter()
|
||||||
.chain(interfaces.tunnel.iter())
|
.chain(interfaces.tunnel.iter())
|
||||||
|
.chain(interfaces.vlan.iter())
|
||||||
.any(|other_iface| {
|
.any(|other_iface| {
|
||||||
*nat_iface == other_iface.interface
|
*nat_iface == other_iface.interface
|
||||||
&& other_iface.ip == iface.ip
|
&& other_iface.ip == iface.ip
|
||||||
|
@ -257,7 +285,7 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept TCP packets
|
// Accept TCP packets
|
||||||
for iface in &interfaces.internal {
|
for iface in interfaces.internal.iter().chain(&interfaces.vlan) {
|
||||||
filter += &format!(
|
filter += &format!(
|
||||||
"-A FORWARD -i {extif} -o {intif} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT\n",
|
"-A FORWARD -i {extif} -o {intif} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT\n",
|
||||||
extif = interfaces.external.interface,
|
extif = interfaces.external.interface,
|
||||||
|
@ -265,6 +293,14 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for iface in &interfaces.vlan {
|
||||||
|
// Allow packets across vlan interface
|
||||||
|
filter += &format!(
|
||||||
|
"-A FORWARD -i {intif} -o {intif} -j ACCEPT\n",
|
||||||
|
intif = iface.interface,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if interfaces.shared_internal {
|
if interfaces.shared_internal {
|
||||||
for iface in &interfaces.internal {
|
for iface in &interfaces.internal {
|
||||||
// jface (jeans interface)
|
// jface (jeans interface)
|
||||||
|
@ -288,7 +324,7 @@ fn filter(interfaces: &Interfaces) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward packets to the internet
|
// Forward packets to the internet
|
||||||
for iface in &interfaces.internal {
|
for iface in interfaces.internal.iter().chain(&interfaces.vlan) {
|
||||||
filter += &format!(
|
filter += &format!(
|
||||||
"-A FORWARD -i {intif} -o {extif} -j ACCEPT\n",
|
"-A FORWARD -i {intif} -o {extif} -j ACCEPT\n",
|
||||||
intif = iface.interface,
|
intif = iface.interface,
|
||||||
|
@ -326,6 +362,7 @@ fn nat(interfaces: &Interfaces) -> String {
|
||||||
.internal
|
.internal
|
||||||
.iter()
|
.iter()
|
||||||
.chain(interfaces.tunnel.iter())
|
.chain(interfaces.tunnel.iter())
|
||||||
|
.chain(interfaces.vlan.iter())
|
||||||
.any(|other_iface| {
|
.any(|other_iface| {
|
||||||
*nat_iface == other_iface.interface
|
*nat_iface == other_iface.interface
|
||||||
&& other_iface.ip == iface.ip
|
&& other_iface.ip == iface.ip
|
||||||
|
|
Loading…
Reference in a new issue