Skip to content

Commit 37331f9

Browse files
author
mugi
committed
Add nftables helper functions and queue processing for ICMPv6 Router Advertisement filtering.
1 parent 7190419 commit 37331f9

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed

src/master.rs

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
use log::info;
2+
use std::borrow::Cow;
3+
use std::net::Ipv6Addr;
4+
use ipnet::Ipv6Net;
5+
use nfq::{Queue, Verdict};
6+
use pnet::packet::{
7+
icmpv6::{
8+
ndp::{NdpOptionTypes::PrefixInformation, RouterAdvertPacket},
9+
Icmpv6Packet,
10+
Icmpv6Types::RouterAdvert,
11+
},
12+
ipv6::Ipv6Packet,
13+
Packet,
14+
};
15+
use nftables::{
16+
batch::Batch,
17+
expr::{Expression, NamedExpression, Meta, MetaKey, Payload, PayloadField},
18+
helper::apply_ruleset,
19+
schema::{Chain, NfListObject, NfObject, Rule, Table},
20+
stmt::{Match, Operator, Queue as NftQueue, Statement},
21+
types::{NfChainPolicy, NfChainType, NfFamily, NfHook},
22+
};
23+
24+
use crate::AppState;
25+
26+
// --- nft.rs content ---
27+
28+
fn create_nftables_objects(queue_num: u16, interface_name: Option<String>) -> Vec<NfObject<'static>> {
29+
let table = Table {
30+
family: NfFamily::IP6,
31+
name: Cow::from("rafilter"),
32+
handle: None,
33+
};
34+
let chain = Chain {
35+
family: NfFamily::IP6,
36+
table: table.name.clone(),
37+
name: Cow::from("input"),
38+
_type: Some(NfChainType::Filter),
39+
hook: Some(NfHook::Input),
40+
prio: Some(0),
41+
policy: Some(NfChainPolicy::Accept),
42+
..Default::default()
43+
};
44+
45+
let mut rule_expr = vec![
46+
Statement::Match(Match {
47+
left: Expression::Named(NamedExpression::Payload(Payload::PayloadField(
48+
PayloadField {
49+
protocol: Cow::from("icmpv6"),
50+
field: Cow::from("type"),
51+
},
52+
))),
53+
right: Expression::Number(134),
54+
op: Operator::EQ,
55+
}),
56+
Statement::Queue(NftQueue {
57+
num: Expression::Number(queue_num as u32),
58+
flags: None,
59+
}),
60+
];
61+
62+
if let Some(the_name) = interface_name {
63+
rule_expr.insert(0,
64+
Statement::Match(Match {
65+
left: Expression::Named(NamedExpression::Meta(Meta { key: MetaKey::Iifname })),
66+
right: Expression::String(Cow::from(the_name)),
67+
op: Operator::EQ,
68+
}),
69+
);
70+
}
71+
let rule = Rule {
72+
family: NfFamily::IP6,
73+
table: table.name.clone(),
74+
chain: chain.name.clone(),
75+
expr: Cow::from(rule_expr),
76+
comment: Some(Cow::from("Queue ICMPv6 Router Advertisement packets".to_string())),
77+
..Default::default()
78+
};
79+
80+
vec![
81+
NfObject::ListObject(NfListObject::Table(table)),
82+
NfObject::ListObject(NfListObject::Chain(chain)),
83+
NfObject::ListObject(NfListObject::Rule(rule)),
84+
]
85+
}
86+
87+
pub fn setup_nftables(state: &AppState) -> Result<(), Box<dyn std::error::Error>> {
88+
let ruleset = create_nftables_objects(state.queue_num, state.interface.as_ref().map(|i| i.name.clone()));
89+
let mut batch = Batch::new();
90+
batch.add_all(ruleset);
91+
apply_ruleset(&batch.to_nftables())?;
92+
Ok(())
93+
}
94+
95+
pub fn delete_nftables() -> Result<(), Box<dyn std::error::Error>> {
96+
let mut batch = Batch::new();
97+
batch.delete(NfListObject::Table(Table {
98+
family: NfFamily::IP6,
99+
name: Cow::from("rafilter"),
100+
handle: None,
101+
}));
102+
apply_ruleset(&batch.to_nftables())?;
103+
Ok(())
104+
}
105+
106+
// --- queue.rs content ---
107+
108+
pub fn process_queue(state: AppState) {
109+
let mut queue = match Queue::open() {
110+
Ok(q) => q,
111+
Err(err) => {
112+
eprintln!("Failed to open NFQUEUE: {}", err); // 无法打开 NFQUEUE
113+
return;
114+
}
115+
};
116+
if let Err(err) = queue.bind(state.queue_num) {
117+
eprintln!("Failed to bind to queue {}: {}", state.queue_num, err); // 无法绑定到队列
118+
return;
119+
}
120+
if let Err(err) = queue.set_fail_open(state.queue_num, false) {
121+
eprintln!("Failed to set fail-open behavior: {}", err); // 无法设置 fail-open 行为
122+
return;
123+
}
124+
125+
ctrlc::set_handler(move || {
126+
println!("Signal received, shutting down gracefully..."); // 接收到信号,正在优雅地关闭程序...
127+
let _ = delete_nftables();
128+
std::process::exit(0);
129+
}).expect("Error setting Ctrl-C handler");
130+
131+
loop {
132+
match queue.recv() {
133+
Ok(mut msg) => {
134+
let data = msg.get_payload();
135+
let prefixes = extract_prefixes(data);
136+
137+
let verdict = if prefixes.is_empty() {
138+
Verdict::Accept
139+
} else {
140+
let mut all_ok = true;
141+
for prefix in &prefixes {
142+
let is_in_list = state.prefixes.iter().any(|p| p.contains(prefix));
143+
let allowed = if state.blacklist_mode { !is_in_list } else { is_in_list };
144+
145+
if !allowed {
146+
info!("Dropped! prefix {}!", prefix);
147+
all_ok = false;
148+
break;
149+
}
150+
info!("Accepted prefix {}!", prefix);
151+
}
152+
if all_ok { Verdict::Accept } else { Verdict::Drop }
153+
};
154+
155+
msg.set_verdict(verdict);
156+
if let Err(e) = queue.verdict(msg) {
157+
eprintln!("Failed to send verdict: {}", e); // 发送判决失败
158+
}
159+
}
160+
Err(e) => {
161+
eprintln!("Failed to receive packet from NFQUEUE: {}", e); // NFQUEUE 接收数据包失败
162+
break;
163+
}
164+
}
165+
}
166+
}
167+
168+
fn extract_prefixes(data: &[u8]) -> Vec<Ipv6Net> {
169+
let mut prefixes = Vec::new();
170+
171+
// Parse IPv6 header
172+
let ipv6_packet = match Ipv6Packet::new(data) {
173+
Some(packet) => packet,
174+
None => return prefixes,
175+
};
176+
177+
// Parse ICMPv6 header from IPv6 payload
178+
let icmp6_packet = match Icmpv6Packet::new(ipv6_packet.payload()) {
179+
Some(packet) => packet,
180+
None => return prefixes,
181+
};
182+
183+
// We only care about Router Advertisement (RA) packets
184+
if icmp6_packet.get_icmpv6_type() != RouterAdvert {
185+
return prefixes;
186+
}
187+
188+
// Parse Router Advertisement packet
189+
let ra_packet = match RouterAdvertPacket::new(icmp6_packet.packet()) {
190+
Some(packet) => packet,
191+
None => return prefixes,
192+
};
193+
194+
// Iterate through all NDP options in the RA packet
195+
for op in ra_packet.get_options() {
196+
// We only care about Prefix Information options
197+
if op.option_type != PrefixInformation {
198+
continue;
199+
}
200+
201+
// Prefix Information Option (Type 3) structure:
202+
// Offset 0: Type (3)
203+
// Offset 1: Length (4, in units of 8 octets)
204+
// Offset 2: Prefix Length (1 octet)
205+
// ...
206+
// Offset 16: Prefix (16 octets)
207+
//
208+
// pnet's NdpOption.data starts AFTER the Type and Length fields (2 bytes).
209+
// So in op.data:
210+
// Offset 0 (original 2): Prefix Length
211+
// Offset 14 (original 16): Prefix
212+
if op.data.len() >= 30 {
213+
let prefix_len = op.data[0];
214+
let addr_bytes: [u8; 16] = op.data[14..30].try_into().unwrap_or([0u8; 16]);
215+
let addr = Ipv6Addr::from(addr_bytes);
216+
217+
// Create Ipv6Net from the extracted address and prefix length
218+
if let Ok(net) = Ipv6Net::new(addr, prefix_len) {
219+
prefixes.push(net);
220+
}
221+
}
222+
}
223+
prefixes
224+
}

0 commit comments

Comments
 (0)