init support ip number strict

This commit is contained in:
lulin 2024-03-10 00:24:10 +08:00
commit 33f528a72d
9 changed files with 742 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

433
Cargo.lock generated Normal file
View File

@ -0,0 +1,433 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "ctrlc"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b"
dependencies = [
"nix",
"windows-sys",
]
[[package]]
name = "env_filter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iptables"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39f0d72d0feb83c9b7f4e1fbde2b4a629886f30841127b3f86383831dba2629"
dependencies = [
"lazy_static",
"nix",
"regex",
]
[[package]]
name = "itoa"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lru_time_cache"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "nfq"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9c8f4c88952507d9df9400a6a2e48640fb460e21dcb2b4716eb3ff156d6db9e"
dependencies = [
"libc",
]
[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "no-std-net"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
[[package]]
name = "pnet_base"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c"
dependencies = [
"no-std-net",
]
[[package]]
name = "pnet_macros"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "pnet_macros_support"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56"
dependencies = [
"pnet_base",
]
[[package]]
name = "pnet_packet"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba"
dependencies = [
"glob",
"pnet_base",
"pnet_macros",
"pnet_macros_support",
]
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rs_filter"
version = "0.1.0"
dependencies = [
"anyhow",
"ctrlc",
"env_logger",
"iptables",
"log",
"lru_time_cache",
"nfq",
"pnet_packet",
"serde",
"serde_json",
]
[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "rs_filter"
version = "0.1.0"
edition = "2021"
[dependencies]
lru_time_cache = "0.11.11"
nfq = "0.2.5"
pnet_packet = "0.34.0"
anyhow="1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
iptables = "0.5.1"
ctrlc = "3.4.2"
log = "0.4.21"
env_logger = "0.11.3"

13
src/config.rs Normal file
View File

@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct IPNumberStrict {
pub time_thr_sec: u64,
pub max_ip_number: usize,
pub port_range: (u16, u16),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub ip_num_strict: IPNumberStrict,
}

39
src/main.rs Normal file
View File

@ -0,0 +1,39 @@
mod config;
mod nfq_mng;
mod nfq_proc;
mod processor;
use std::path::PathBuf;
use config::Config;
use nfq_mng::NFQMng;
use nfq_proc::NfqRunBuilder;
use processor::ip_num_strict::IpNumStrict;
fn main() -> anyhow::Result<()> {
env_logger::init();
let cfg_file: PathBuf = std::env::args()
.nth(1)
.unwrap_or_else(|| "config.json".to_owned())
.parse()?;
let config: Config = serde_json::from_reader(std::fs::File::open(&cfg_file)?)?;
let mut ipt_mng = NFQMng::new();
ipt_mng.add__nfq_rule(
"mangle",
"PREROUTING",
&format!(
"-p tcp --dport {}:{} -m conntrack --ctstate NEW",
config.ip_num_strict.port_range.0, config.ip_num_strict.port_range.1
),
0,
);
ipt_mng.run()?;
let mut builder = NfqRunBuilder::new();
builder.add_processor(
0,
Box::new(IpNumStrict::new(
config.ip_num_strict.max_ip_number,
config.ip_num_strict.time_thr_sec,
)),
);
builder.run()?;
Ok(())
}

83
src/nfq_mng.rs Normal file
View File

@ -0,0 +1,83 @@
use std::{
process::exit,
sync::{Arc, Mutex},
};
use ctrlc::set_handler;
#[derive(Debug)]
struct Rule {
tbl: String,
chain: String,
rule: String,
}
pub struct NFQMng {
rules: Arc<Mutex<Vec<Rule>>>,
}
impl NFQMng {
pub fn new() -> NFQMng {
NFQMng {
rules: Default::default(),
}
}
pub fn add__nfq_rule(&mut self, tbl: &str, chain: &str, para: &str, queue_num: u32) {
let rule_str = format!("{para} -j NFQUEUE --queue-bypass --queue-num {queue_num}");
self.rules.lock().unwrap().push(Rule {
tbl: tbl.to_owned(),
chain: chain.to_owned(),
rule: rule_str,
});
}
pub fn add__comm_rule(&mut self, tbl: &str, chain: &str, para: &str) {
self.rules.lock().unwrap().push(Rule {
tbl: tbl.to_owned(),
chain: chain.to_owned(),
rule: para.to_owned(),
});
}
pub fn run(self) -> anyhow::Result<()> {
let notify = Arc::new(std::sync::Condvar::new());
let mutex = Mutex::new(());
let notifier = notify.clone();
let rules_clone = self.rules.clone();
std::thread::spawn(move || {
let ips = iptables::new(false).unwrap();
set_handler(move || {
let ips = iptables::new(false).unwrap();
for r in rules_clone.lock().unwrap().iter() {
if let Err(e) = ips.delete(&r.tbl, &r.chain, &r.rule) {
println!("clean iptables rule: {r:?} failed: {e:?}");
}
}
println!("unsetup iptables success");
exit(0);
})
.unwrap();
loop {
let mut has_failed = false;
for r in self.rules.lock().unwrap().iter() {
match ips.exists(&r.tbl, &r.chain, &r.rule) {
Ok(false) => {
if let Err(e) = ips.insert(&r.tbl, &r.chain, &r.rule, 1) {
println!("setup iptables rule: {r:?} failed: {e:?}");
has_failed = true;
}
}
Ok(true) => {}
Err(e) => {
println!("check iptables rule: {r:?} failed: {e:?}");
}
}
}
if !has_failed {
notifier.notify_all();
}
std::thread::sleep(std::time::Duration::from_secs(5 * 60));
}
});
let l = mutex.lock().unwrap();
let _l = notify.wait(l).unwrap();
Ok(())
}
}

98
src/nfq_proc.rs Normal file
View File

@ -0,0 +1,98 @@
use std::{collections::HashMap, net::Ipv4Addr};
use anyhow::anyhow;
use nfq::{Queue, Verdict};
use pnet_packet::{ipv4::Ipv4Packet, tcp::TcpPacket, udp::UdpPacket, Packet};
pub struct MessageData {
pub from_ip: Ipv4Addr,
pub dst_ip: Ipv4Addr,
pub src_port: u16,
pub dst_port: u16,
}
pub trait Processor {
fn run<'a>(&mut self, msg: &MessageData, orig_msg: &Ipv4Packet<'a>) -> Verdict;
fn message_type(&self) -> MessageType;
}
pub enum MessageType {
Tcp,
Udp,
}
pub struct NfqRunBuilder {
processor: HashMap<u16, (MessageType, Vec<Box<dyn Processor + Send + Sync + 'static>>)>,
}
impl NfqRunBuilder {
pub fn new() -> Self {
Self {
processor: Default::default(),
}
}
pub fn add_processor(
&mut self,
que_idx: u16,
processor: Box<dyn Processor + Send + Sync + 'static>,
) {
self.processor
.entry(que_idx)
.or_insert_with(|| (processor.message_type(), Vec::new()))
.1
.push(processor);
}
pub fn run(self) -> anyhow::Result<()> {
let mut handlers = Vec::new();
for (idx, (ty, mut all_processor)) in self.processor.into_iter() {
let parse2 = match ty {
MessageType::Tcp => move |ip_pkt: &Ipv4Packet| {
let tcp_pkt = TcpPacket::new(ip_pkt.payload())
.ok_or(anyhow!("can't parse tcp packet"))?;
Ok::<_, anyhow::Error>((tcp_pkt.get_source(), tcp_pkt.get_destination()))
},
MessageType::Udp => move |ip_pkt: &Ipv4Packet| {
let tcp_pkt = UdpPacket::new(ip_pkt.payload())
.ok_or(anyhow!("can't parse udp packet"))?;
Ok::<_, anyhow::Error>((tcp_pkt.get_source(), tcp_pkt.get_destination()))
},
};
let h = std::thread::spawn(move || {
let mut queue = Queue::open()?;
queue.bind(idx)?;
loop {
let mut msg = queue.recv()?;
let mut act = Verdict::Accept;
if let Some(ip_packet) = pnet_packet::ipv4::Ipv4Packet::new(msg.get_payload()) {
let from_ip = ip_packet.get_source();
let dst_ip = ip_packet.get_destination();
let (src_port, dst_port) = parse2(&ip_packet)?;
let msg_data = MessageData {
from_ip,
dst_ip,
src_port,
dst_port,
};
log::trace!("ip from: {from_ip:?}:{src_port} to {dst_ip:?}:{dst_port}");
for p in all_processor.iter_mut() {
let cur_rst = p.run(&msg_data, &ip_packet);
match cur_rst {
Verdict::Drop | Verdict::Stop => {
act = cur_rst;
break;
}
_ => {}
}
}
}
msg.set_verdict(act);
queue.verdict(msg)?;
}
Ok::<_, anyhow::Error>(())
});
handlers.push(h);
}
for h in handlers {
let _ = h.join();
}
Ok(())
}
}

View File

@ -0,0 +1,57 @@
use std::{collections::HashMap, net::Ipv4Addr};
use nfq::Verdict;
use pnet_packet::ipv4::Ipv4Packet;
use crate::nfq_proc::{MessageData, MessageType, Processor};
pub struct IpNumStrict {
ip_number: usize,
time_thr: u64,
data: HashMap<u16, lru_time_cache::LruCache<Ipv4Addr, ()>>,
}
impl IpNumStrict {
pub fn new(ip_number: usize, time_thr: u64) -> Self {
Self {
ip_number,
time_thr,
data: Default::default(),
}
}
}
impl Processor for IpNumStrict {
fn run<'a>(&mut self, msg: &MessageData, _orig_msg: &Ipv4Packet<'a>) -> Verdict {
let mut act = Verdict::Accept;
let from_ip = msg.from_ip;
let dst_ip = msg.dst_ip;
let src_port = msg.src_port;
let dst_port = msg.dst_port;
let port_record = self.data.entry(dst_port).or_insert_with(|| {
lru_time_cache::LruCache::with_expiry_duration(std::time::Duration::from_secs(
self.time_thr,
))
});
let cur_len = port_record.len();
let entry = port_record.entry(from_ip);
match entry {
lru_time_cache::Entry::Vacant(e) => {
// 不存在的ip检查数量限制
if cur_len >= self.ip_number {
// 达到了限制
log::warn!("{from_ip:?}:{src_port} -> {dst_ip:?}:{dst_port}, Ip num exceed");
act = Verdict::Drop;
} else {
// 新增
e.insert(());
}
}
lru_time_cache::Entry::Occupied(_e) => {
// 已经存在,直接放行
}
}
act
}
fn message_type(&self) -> MessageType {
MessageType::Tcp
}
}

2
src/processor/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod ip_num_strict;
use super::nfq_proc::Processor;