diff --git a/{{project-name}}-ebpf/Cargo.toml b/{{project-name}}-ebpf/Cargo.toml index ea32147..8822dfb 100644 --- a/{{project-name}}-ebpf/Cargo.toml +++ b/{{project-name}}-ebpf/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] aya-bpf = { git = "http://github.com/alessandrod/aya", branch = "main" } {{ project-name }}-common = { path = "../{{ project-name }}-common" } +memoffset = "0.6" [[bin]] name = "{{ project-name }}" diff --git a/{{project-name}}-ebpf/src/main.rs b/{{project-name}}-ebpf/src/main.rs index 997377a..73b92a1 100644 --- a/{{project-name}}-ebpf/src/main.rs +++ b/{{project-name}}-ebpf/src/main.rs @@ -1,7 +1,80 @@ #![no_std] #![no_main] +use core::mem; +use memoffset::offset_of; + +use aya_bpf::{ + bindings::xdp_action::{XDP_DROP, XDP_PASS}, + macros::{map, xdp}, + maps::HashMap, + programs::XdpContext, +}; + +mod net; +use net::{iphdr, tcphdr}; + +use crate::net::ethhdr; + +#[map] +static mut BLOCK_PORTS: HashMap = HashMap::with_max_entries(1024, 0); + +#[xdp(name = "xdp_fw")] +pub fn xdp_firewall(ctx: XdpContext) -> u32 { + match try_xdp_firewall(ctx) { + Ok(ret) => ret, + Err(_) => XDP_PASS, + } +} + +fn try_xdp_firewall(ctx: XdpContext) -> Result { + if let Some(port) = tcp_dest_port(&ctx)? { + if block_port(port) { + return Ok(XDP_DROP); + } + } + + Ok(XDP_PASS) +} + +fn tcp_dest_port(ctx: &XdpContext) -> Result, ()> { + let h_proto = u16::from_be(unsafe { *ptr_at(&ctx, offset_of!(ethhdr, h_proto))? }); + let ip_proto: u8 = unsafe { *ptr_at(&ctx, ETH_HDR_LEN + offset_of!(iphdr, protocol))? }; + + if h_proto != ETH_P_IP || ip_proto != IPPROTO_TCP { + return Ok(None); + } + + let dest = u16::from_be(unsafe { + *ptr_at(&ctx, ETH_HDR_LEN + IP_HDR_LEN + offset_of!(tcphdr, dest))? + }); + + Ok(Some(dest)) +} + +#[inline(always)] +unsafe fn ptr_at(ctx: &XdpContext, offset: usize) -> Result<*const T, ()> { + let start = ctx.data(); + let end = ctx.data_end(); + let len = mem::size_of::(); + + if start + offset + len > end { + return Err(()); + } + + Ok((start + offset) as *const T) +} + +fn block_port(port: u16) -> bool { + unsafe { BLOCK_PORTS.get(&port).is_some() } +} + #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { unreachable!() -} \ No newline at end of file +} + +const ETH_P_IP: u16 = 0x0800; +const IPPROTO_TCP: u8 = 6; +const ETH_HDR_LEN: usize = mem::size_of::(); +const IP_HDR_LEN: usize = mem::size_of::(); diff --git a/{{project-name}}-ebpf/src/net.rs b/{{project-name}}-ebpf/src/net.rs new file mode 100644 index 0000000..0a7399d --- /dev/null +++ b/{{project-name}}-ebpf/src/net.rs @@ -0,0 +1,337 @@ +#![allow(non_camel_case_types, dead_code)] +/* automatically generated by rust-bindgen 0.57.0 */ + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct __BindgenBitfieldUnit { + pub storage: Storage, +} +impl __BindgenBitfieldUnit { + #[inline] + pub const fn new(storage: Storage) -> Self { + Self { storage } + } +} +impl __BindgenBitfieldUnit +where + Storage: AsRef<[u8]> + AsMut<[u8]>, +{ + #[inline] + pub fn get_bit(&self, index: usize) -> bool { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = self.storage.as_ref()[byte_index]; + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + byte & mask == mask + } + #[inline] + pub fn set_bit(&mut self, index: usize, val: bool) { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = &mut self.storage.as_mut()[byte_index]; + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + if val { + *byte |= mask; + } else { + *byte &= !mask; + } + } + #[inline] + pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + let mut val = 0; + for i in 0..(bit_width as usize) { + if self.get_bit(i + bit_offset) { + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + val |= 1 << index; + } + } + val + } + #[inline] + pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + for i in 0..(bit_width as usize) { + let mask = 1 << i; + let val_bit_is_set = val & mask == mask; + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + self.set_bit(index + bit_offset, val_bit_is_set); + } + } +} +pub type __u8 = ::aya_bpf::cty::c_uchar; +pub type __u16 = ::aya_bpf::cty::c_ushort; +pub type __u32 = ::aya_bpf::cty::c_uint; +pub type __be16 = __u16; +pub type __be32 = __u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ethhdr { + pub h_dest: [::aya_bpf::cty::c_uchar; 6usize], + pub h_source: [::aya_bpf::cty::c_uchar; 6usize], + pub h_proto: __be16, +} +pub type __sum16 = __u16; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct tcphdr { + pub source: __be16, + pub dest: __be16, + pub seq: __be32, + pub ack_seq: __be32, + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, + pub window: __be16, + pub check: __sum16, + pub urg_ptr: __be16, +} +impl tcphdr { + #[inline] + pub fn res1(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(0usize, 4u8) as u16) } + } + #[inline] + pub fn set_res1(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(0usize, 4u8, val as u64) + } + } + #[inline] + pub fn doff(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(4usize, 4u8) as u16) } + } + #[inline] + pub fn set_doff(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(4usize, 4u8, val as u64) + } + } + #[inline] + pub fn fin(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u16) } + } + #[inline] + pub fn set_fin(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(8usize, 1u8, val as u64) + } + } + #[inline] + pub fn syn(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(9usize, 1u8) as u16) } + } + #[inline] + pub fn set_syn(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(9usize, 1u8, val as u64) + } + } + #[inline] + pub fn rst(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(10usize, 1u8) as u16) } + } + #[inline] + pub fn set_rst(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(10usize, 1u8, val as u64) + } + } + #[inline] + pub fn psh(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(11usize, 1u8) as u16) } + } + #[inline] + pub fn set_psh(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(11usize, 1u8, val as u64) + } + } + #[inline] + pub fn ack(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(12usize, 1u8) as u16) } + } + #[inline] + pub fn set_ack(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(12usize, 1u8, val as u64) + } + } + #[inline] + pub fn urg(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(13usize, 1u8) as u16) } + } + #[inline] + pub fn set_urg(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(13usize, 1u8, val as u64) + } + } + #[inline] + pub fn ece(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(14usize, 1u8) as u16) } + } + #[inline] + pub fn set_ece(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(14usize, 1u8, val as u64) + } + } + #[inline] + pub fn cwr(&self) -> __u16 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(15usize, 1u8) as u16) } + } + #[inline] + pub fn set_cwr(&mut self, val: __u16) { + unsafe { + let val: u16 = ::core::mem::transmute(val); + self._bitfield_1.set(15usize, 1u8, val as u64) + } + } + #[inline] + pub fn new_bitfield_1( + res1: __u16, + doff: __u16, + fin: __u16, + syn: __u16, + rst: __u16, + psh: __u16, + ack: __u16, + urg: __u16, + ece: __u16, + cwr: __u16, + ) -> __BindgenBitfieldUnit<[u8; 2usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 4u8, { + let res1: u16 = unsafe { ::core::mem::transmute(res1) }; + res1 as u64 + }); + __bindgen_bitfield_unit.set(4usize, 4u8, { + let doff: u16 = unsafe { ::core::mem::transmute(doff) }; + doff as u64 + }); + __bindgen_bitfield_unit.set(8usize, 1u8, { + let fin: u16 = unsafe { ::core::mem::transmute(fin) }; + fin as u64 + }); + __bindgen_bitfield_unit.set(9usize, 1u8, { + let syn: u16 = unsafe { ::core::mem::transmute(syn) }; + syn as u64 + }); + __bindgen_bitfield_unit.set(10usize, 1u8, { + let rst: u16 = unsafe { ::core::mem::transmute(rst) }; + rst as u64 + }); + __bindgen_bitfield_unit.set(11usize, 1u8, { + let psh: u16 = unsafe { ::core::mem::transmute(psh) }; + psh as u64 + }); + __bindgen_bitfield_unit.set(12usize, 1u8, { + let ack: u16 = unsafe { ::core::mem::transmute(ack) }; + ack as u64 + }); + __bindgen_bitfield_unit.set(13usize, 1u8, { + let urg: u16 = unsafe { ::core::mem::transmute(urg) }; + urg as u64 + }); + __bindgen_bitfield_unit.set(14usize, 1u8, { + let ece: u16 = unsafe { ::core::mem::transmute(ece) }; + ece as u64 + }); + __bindgen_bitfield_unit.set(15usize, 1u8, { + let cwr: u16 = unsafe { ::core::mem::transmute(cwr) }; + cwr as u64 + }); + __bindgen_bitfield_unit + } +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iphdr { + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, + pub tos: __u8, + pub tot_len: __be16, + pub id: __be16, + pub frag_off: __be16, + pub ttl: __u8, + pub protocol: __u8, + pub check: __sum16, + pub saddr: __be32, + pub daddr: __be32, +} +impl iphdr { + #[inline] + pub fn ihl(&self) -> __u8 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(0usize, 4u8) as u8) } + } + #[inline] + pub fn set_ihl(&mut self, val: __u8) { + unsafe { + let val: u8 = ::core::mem::transmute(val); + self._bitfield_1.set(0usize, 4u8, val as u64) + } + } + #[inline] + pub fn version(&self) -> __u8 { + unsafe { ::core::mem::transmute(self._bitfield_1.get(4usize, 4u8) as u8) } + } + #[inline] + pub fn set_version(&mut self, val: __u8) { + unsafe { + let val: u8 = ::core::mem::transmute(val); + self._bitfield_1.set(4usize, 4u8, val as u64) + } + } + #[inline] + pub fn new_bitfield_1(ihl: __u8, version: __u8) -> __BindgenBitfieldUnit<[u8; 1usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 4u8, { + let ihl: u8 = unsafe { ::core::mem::transmute(ihl) }; + ihl as u64 + }); + __bindgen_bitfield_unit.set(4usize, 4u8, { + let version: u8 = unsafe { ::core::mem::transmute(version) }; + version as u64 + }); + __bindgen_bitfield_unit + } +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct udphdr { + pub source: __be16, + pub dest: __be16, + pub len: __be16, + pub check: __sum16, +} diff --git a/{{project-name}}/Cargo.toml b/{{project-name}}/Cargo.toml index 95015ba..52291ba 100644 --- a/{{project-name}}/Cargo.toml +++ b/{{project-name}}/Cargo.toml @@ -8,6 +8,7 @@ publish = false aya = { git = "https://github.com/alessandrod/aya", branch="main" } {{project-name}}-common = { path = "../{{project-name}}-common", features=["userspace"] } anyhow = "1.0.42" +ctrlc = "3.1" [[bin]] name = "{{project-name}}" diff --git a/{{project-name}}/src/main.rs b/{{project-name}}/src/main.rs index c0e24fd..e12d537 100644 --- a/{{project-name}}/src/main.rs +++ b/{{project-name}}/src/main.rs @@ -1,9 +1,52 @@ +use std::{ + convert::{TryFrom, TryInto}, + process, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use aya::{ + maps::HashMap, + programs::{Xdp, XdpFlags}, + Bpf, Btf, +}; + fn main() { if let Err(e) = try_main() { eprintln!("error: {:#}", e); + process::exit(1); } } fn try_main() -> Result<(), anyhow::Error> { - Ok(()) -} \ No newline at end of file + // load the eBPF code + let code = include_bytes!("../../target/bpfel-unknown-none/debug/{{ project-name }}").to_vec(); + let mut bpf = Bpf::load(&code, Btf::from_sys_fs().ok().as_ref())?; + + // insert port 8080 in the list of ports to be blocked + let mut map = HashMap::<_, u16, u16>::try_from(bpf.map_mut("BLOCK_PORTS")?)?; + map.insert(8080, 1, 0)?; + + // load the XDP program + let prog: &mut Xdp = bpf.program_mut("xdp_fw")?.try_into()?; + prog.load()?; + prog.attach("eth0", XdpFlags::SKB_MODE)?; + + // wait for SIGINT or SIGTERM + Ok(loop_until_terminated()) +} + +fn loop_until_terminated() { + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + + ctrlc::set_handler(move || { + r.store(false, Ordering::SeqCst); + }) + .expect("Error setting signal handler"); + + while running.load(Ordering::SeqCst) {} + println!("Exiting..."); +}