Skip to content

Commit

Permalink
Implementing ipv6
Browse files Browse the repository at this point in the history
  • Loading branch information
containerscrew committed Dec 6, 2024
1 parent 89ae2c1 commit 22ee4a0
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 77 deletions.
83 changes: 73 additions & 10 deletions nflux-ebpf/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use aya_ebpf::{
programs::XdpContext,
};
use core::mem;
use network_types::ip::IpProto;
use network_types::ip::{IpProto, Ipv6Hdr};
use network_types::{
eth::{EthHdr, EtherType},
ip::Ipv4Hdr,
Expand Down Expand Up @@ -97,7 +97,6 @@ fn start_nflux(ctx: XdpContext) -> Result<u32, ()> {
let ack = unsafe { (*tcphdr).ack() };

if syn == 1 && ack == 0 {
// SYN packet: Apply rules for incoming or outgoing connections
if rule.ports.contains(&dst_port) {
if rule.action == 1 {
log_new_connection(ctx, source_ip, dst_port, 6);
Expand All @@ -108,14 +107,11 @@ fn start_nflux(ctx: XdpContext) -> Result<u32, ()> {
}
}
} else if syn == 1 && ack == 1 {
// SYN-ACK packets: Allow for outgoing connection responses
return Ok(xdp_action::XDP_PASS);
} else if ack == 1 {
// ACK packets: Allow for established connections
return Ok(xdp_action::XDP_PASS);
}

// For other TCP packets, apply rules
if rule.ports.contains(&dst_port) {
if rule.action == 1 {
log_new_connection(ctx, source_ip, dst_port, 6);
Expand All @@ -134,27 +130,94 @@ fn start_nflux(ctx: XdpContext) -> Result<u32, ()> {
log_new_connection(ctx, source_ip, dst_port, 17);
return Ok(xdp_action::XDP_PASS);
}
// By the moment, allow every UDP packet
// Necessary to allow DNS UDP packets (internet browsing, for example)
return Ok(xdp_action::XDP_PASS);
}
IpProto::Icmp => {
// Read from EBPF map
if let Some(&icmp_ping) = ICMP_RULE.get(0) {
if icmp_ping == 1 {
// Allow ICMP packets if enabled
log_new_connection(ctx, source_ip, 0, 1);
return Ok(xdp_action::XDP_PASS);
}
}
// Block ICMP packets by default
return Ok(xdp_action::XDP_DROP);
}
_ => return Ok(xdp_action::XDP_DROP),
}
}
}
Ok(xdp_action::XDP_DROP)
}
EtherType::Ipv6 => {
let ipv6hdr: *const Ipv6Hdr = unsafe { ptr_at(&ctx, EthHdr::LEN)? };
let proto = unsafe { (*ipv6hdr).next_hdr };
let source_ip = unsafe { (*ipv6hdr).src_addr.in6_u.u6_addr8 };

for prefix_len in (1..=128).rev() {
let key = Key::new(
prefix_len,
LpmKeyIpv6 {
prefix_len,
ip: source_ip,
},
);

if let Some(rule) = IPV6_RULES.get(&key) {
match proto {
IpProto::Tcp => {
let tcphdr: *const TcpHdr =
unsafe { ptr_at(&ctx, EthHdr::LEN + Ipv6Hdr::LEN)? };
let dst_port = u16::from_be(unsafe { (*tcphdr).dest });
let syn = unsafe { (*tcphdr).syn() };
let ack = unsafe { (*tcphdr).ack() };

if syn == 1 && ack == 0 {
if rule.ports.contains(&dst_port) {
if rule.action == 1 {
log_new_connection(ctx, 0, dst_port, 6);
return Ok(xdp_action::XDP_PASS);
} else {
log_new_connection(ctx, 0, dst_port, 6);
return Ok(xdp_action::XDP_DROP);
}
}
} else if syn == 1 && ack == 1 {
return Ok(xdp_action::XDP_PASS);
} else if ack == 1 {
return Ok(xdp_action::XDP_PASS);
}

if rule.ports.contains(&dst_port) {
if rule.action == 1 {
log_new_connection(ctx, 0, dst_port, 6);
return Ok(xdp_action::XDP_PASS);
}
}
return Ok(xdp_action::XDP_DROP);
}
IpProto::Udp => {
let udphdr: *const UdpHdr =
unsafe { ptr_at(&ctx, EthHdr::LEN + Ipv6Hdr::LEN)? };
let dst_port = u16::from_be(unsafe { (*udphdr).dest });

if rule.ports.contains(&dst_port) && rule.action == 1 {
log_new_connection(ctx, 0, dst_port, 17);
return Ok(xdp_action::XDP_PASS);
}
return Ok(xdp_action::XDP_PASS);
}
IpProto::Icmp => {
if let Some(&icmp_ping) = ICMP_RULE.get(0) {
if icmp_ping == 1 {
log_new_connection(ctx, 0, 0, 1);
return Ok(xdp_action::XDP_PASS);
}
}
return Ok(xdp_action::XDP_DROP);
}
_ => return Ok(xdp_action::XDP_DROP),
}
}
}
Ok(xdp_action::XDP_DROP)
}
_ => Ok(xdp_action::XDP_DROP),
Expand Down
17 changes: 9 additions & 8 deletions nflux.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ log_type = "text" # text or json. Defaults to text if not set

[ip_rules]
# The /32 CIDR block is used to represent a single IP address rather than a range
"192.168.0.174/32" = { priority = 1, action = "allow", ports = [22], protocol = "tcp", log = false, description = "Allow SSH for specific IP" }
"192.168.0.0/24" = { priority = 2, action = "deny", ports = [8081], protocol = "tcp", log = false, description = "Deny SSH for entire subnet" }
"192.168.0.0/24" = { priority = 1, action = "allow", ports = [22], protocol = "tcp", log = false, description = "Allow SSH for entire local net" }

# todo: ipv6 support
# "2001:0db8:85a3:0000:0000:8a2e:0370:7334" = { action = "deny", ports = [80], protocol = "tcp" }
# curl -6 -v http://\[::ffff:192.168.0.26\]:80
"fe80::5bc2:662b:ac2f:7e8b/128" = { priority = 2, action = "allow", ports = [80], protocol = "tcp", log = false, description = "Deny HTTP for specific IPv6 address" }

# [mac_rules]
# # Rules for MAC address filtering
# "00:0a:95:9d:68:16" = { action = "allow" }
# "00:0a:95:9d:68:17" = { action = "deny" }
#[mac_rules]
# todo: MAC address filtering
# This is not implemented. Not necessary by the moment
# Rules for MAC address filtering
#"00:0a:95:9d:68:16" = { action = "allow" }
#"00:0a:95:9d:68:17" = { action = "deny" }
133 changes: 74 additions & 59 deletions nflux/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use bytes::BytesMut;
use config::{Action, Nflux, Protocol, IpRules};
use core::set_mem_limit;
use logger::setup_logger;
use nflux_common::{convert_protocol, ConnectionEvent, IpRule, LpmKeyIpv4};
use nflux_common::{convert_protocol, ConnectionEvent, IpRule, LpmKeyIpv4, LpmKeyIpv6};
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::ptr;
use tokio::task;
use tracing::{error, info};
Expand Down Expand Up @@ -45,9 +45,8 @@ async fn main() -> anyhow::Result<()> {
let mut bpf = Ebpf::load(include_bytes_aligned!(concat!(env!("OUT_DIR"), "/nflux")))?;

// Populate eBPF maps with configuration data
populate_ipv4_rules(&mut bpf, &config.ip_rules)?;
populate_ip_rules(&mut bpf, &config.ip_rules)?;
populate_icmp_rule(&mut bpf, config.nflux.icmp_ping)?;
// populate_ipv6_rules(&mut bpf, &config.ip_rules)?;

// Attach XDP program
let program: &mut Xdp = bpf.program_mut("nflux").unwrap().try_into()?;
Expand Down Expand Up @@ -123,36 +122,6 @@ fn parse_connection_event(buf: &BytesMut) -> anyhow::Result<ConnectionEvent> {
}
}

fn populate_ipv4_rules(bpf: &mut Ebpf, ip_rules: &HashMap<String, IpRules>) -> anyhow::Result<()> {
let mut ipv4_map: LpmTrie<&mut MapData, LpmKeyIpv4, IpRule> = LpmTrie::try_from(
bpf.map_mut("IPV4_RULES")
.context("Failed to find IPV4_RULES map")?,
)?;

// Sort rules by priority
let mut sorted_rules: Vec<_> = ip_rules.iter().collect();
sorted_rules.sort_by_key(|(_, rule)| rule.priority);

for (cidr, rule) in sorted_rules {
let (ip, prefix_len) = parse_cidr_v4(cidr)?;
let ip_rule = prepare_ip_rule(rule)?;

let key = Key::new(
prefix_len,
LpmKeyIpv4 {
prefix_len,
ip: ip.into(),
},
);

ipv4_map
.insert(&key, &ip_rule, 0)
.context("Failed to insert IPv4 rule")?;
}

Ok(())
}

fn prepare_ip_rule(rule: &IpRules) -> anyhow::Result<IpRule> {
let mut ports = [0u16; 16];
for (i, &port) in rule.ports.iter().enumerate().take(16) {
Expand All @@ -173,22 +142,6 @@ fn prepare_ip_rule(rule: &IpRules) -> anyhow::Result<IpRule> {
})
}

// fn populate_ipv6_rules(bpf: &mut Ebpf, ip_rules: &HashMap<String, Rules>) -> anyhow::Result<()> {
// let mut ipv6_map: LpmTrie<&mut MapData, LpmKeyIpv6, IpRule> = LpmTrie::try_from(
// bpf.map_mut("IPV6_RULES").context("Failed to find IPV4_RULES map")?,
// )?;

// for (cidr, rule) in ip_rules {
// let (ip, prefix_len) = parse_cidr_v6(cidr)?;
// let ip_rule = prepare_ip_rule(rule)?;

// let key = Key::new(prefix_len, LpmKeyIpv6 { prefix_len, ip: ip.into() });
// ipv6_map.insert(&key, &ip_rule, 0).context("Failed to insert IPv6 rule")?;
// }

// Ok(())
// }

fn parse_cidr_v4(cidr: &str) -> anyhow::Result<(Ipv4Addr, u32)> {
let parts: Vec<&str> = cidr.split('/').collect();
if parts.len() != 2 {
Expand All @@ -199,12 +152,74 @@ fn parse_cidr_v4(cidr: &str) -> anyhow::Result<(Ipv4Addr, u32)> {
Ok((ip, prefix_len))
}

// fn parse_cidr_v6(cidr: &str) -> anyhow::Result<(Ipv6Addr, u32)> {
// let parts: Vec<&str> = cidr.split('/').collect();
// if parts.len() != 2 {
// return Err(anyhow::anyhow!("Invalid CIDR format: {}", cidr));
// }
// let ip = parts[0].parse::<Ipv6Addr>()?;
// let prefix_len = parts[1].parse::<u32>()?;
// Ok((ip, prefix_len))
// }
fn parse_cidr_v6(cidr: &str) -> anyhow::Result<(Ipv6Addr, u32)> {
let parts: Vec<&str> = cidr.split('/').collect();
if parts.len() != 2 {
return Err(anyhow::anyhow!("Invalid CIDR format: {}", cidr));
}
let ip = parts[0].parse::<Ipv6Addr>()?;
let prefix_len = parts[1].parse::<u32>()?;
Ok((ip, prefix_len))
}

fn populate_ip_rules(bpf: &mut Ebpf, ip_rules: &HashMap<String, IpRules>) -> anyhow::Result<()> {
{
// Populate IPv4 rules
let mut ipv4_map: LpmTrie<&mut MapData, LpmKeyIpv4, IpRule> = LpmTrie::try_from(
bpf.map_mut("IPV4_RULES")
.context("Failed to find IPV4_RULES map")?,
)?;

// Sort rules by priority
let mut sorted_rules: Vec<_> = ip_rules.iter().collect();
sorted_rules.sort_by_key(|(_, rule)| rule.priority);

for (cidr, rule) in &sorted_rules {
if let Ok((ip, prefix_len)) = parse_cidr_v4(cidr) {
// Handle IPv4 rules
let ip_rule = prepare_ip_rule(rule)?;
let key = Key::new(
prefix_len,
LpmKeyIpv4 {
prefix_len,
ip: ip.into(),
},
);
ipv4_map
.insert(&key, &ip_rule, 0)
.context("Failed to insert IPv4 rule")?;
}
}
}

{
// Populate IPv6 rules
let mut ipv6_map: LpmTrie<&mut MapData, LpmKeyIpv6, IpRule> = LpmTrie::try_from(
bpf.map_mut("IPV6_RULES")
.context("Failed to find IPV6_RULES map")?,
)?;

// Sort rules by priority
let mut sorted_rules: Vec<_> = ip_rules.iter().collect();
sorted_rules.sort_by_key(|(_, rule)| rule.priority);

for (cidr, rule) in &sorted_rules {
if let Ok((ip, prefix_len)) = parse_cidr_v6(cidr) {
// Handle IPv6 rules
let ip_rule = prepare_ip_rule(rule)?;
let key = Key::new(
prefix_len,
LpmKeyIpv6 {
prefix_len,
ip: ip.octets(),
},
);
ipv6_map
.insert(&key, &ip_rule, 0)
.context("Failed to insert IPv6 rule")?;
}
}
}

Ok(())
}

0 comments on commit 22ee4a0

Please sign in to comment.