mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-06 17:59:11 +00:00
respond packet should not be dropped if request packet is already allowed (#1725)
This commit is contained in:
@@ -1,16 +1,19 @@
|
|||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
use std::sync::atomic::{AtomicU16, Ordering};
|
use std::sync::atomic::{AtomicU16, Ordering};
|
||||||
|
use std::time::Instant;
|
||||||
use std::{
|
use std::{
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
|
use dashmap::DashMap;
|
||||||
use pnet::packet::ipv6::Ipv6Packet;
|
use pnet::packet::ipv6::Ipv6Packet;
|
||||||
use pnet::packet::{
|
use pnet::packet::{
|
||||||
ip::IpNextHeaderProtocols, ipv4::Ipv4Packet, tcp::TcpPacket, udp::UdpPacket, Packet as _,
|
ip::IpNextHeaderProtocols, ipv4::Ipv4Packet, tcp::TcpPacket, udp::UdpPacket, Packet as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::common::scoped_task::ScopedTask;
|
||||||
use crate::proto::acl::{AclStats, Protocol};
|
use crate::proto::acl::{AclStats, Protocol};
|
||||||
use crate::tunnel::packet_def::PacketType;
|
use crate::tunnel::packet_def::PacketType;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -19,6 +22,37 @@ use crate::{
|
|||||||
tunnel::packet_def::ZCPacket,
|
tunnel::packet_def::ZCPacket,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||||
|
struct OutboundAllowRecord {
|
||||||
|
src_ip: IpAddr,
|
||||||
|
dst_ip: IpAddr,
|
||||||
|
src_port: Option<u16>,
|
||||||
|
dst_port: Option<u16>,
|
||||||
|
protocol: Protocol,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutboundAllowRecord {
|
||||||
|
fn new_from_inbound_packet(p: &PacketInfo) -> Self {
|
||||||
|
Self {
|
||||||
|
src_ip: p.src_ip,
|
||||||
|
dst_ip: p.dst_ip,
|
||||||
|
src_port: p.src_port,
|
||||||
|
dst_port: p.dst_port,
|
||||||
|
protocol: p.protocol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_from_outbound_packet(p: &PacketInfo) -> Self {
|
||||||
|
Self {
|
||||||
|
src_ip: p.dst_ip,
|
||||||
|
dst_ip: p.src_ip,
|
||||||
|
src_port: p.dst_port,
|
||||||
|
dst_port: p.src_port,
|
||||||
|
protocol: p.protocol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ACL filter that can be inserted into the packet processing pipeline
|
/// ACL filter that can be inserted into the packet processing pipeline
|
||||||
/// Optimized with lock-free hot reloading via atomic processor replacement
|
/// Optimized with lock-free hot reloading via atomic processor replacement
|
||||||
pub struct AclFilter {
|
pub struct AclFilter {
|
||||||
@@ -26,6 +60,11 @@ pub struct AclFilter {
|
|||||||
acl_processor: ArcSwap<AclProcessor>,
|
acl_processor: ArcSwap<AclProcessor>,
|
||||||
acl_enabled: Arc<AtomicBool>,
|
acl_enabled: Arc<AtomicBool>,
|
||||||
quic_udp_port: AtomicU16,
|
quic_udp_port: AtomicU16,
|
||||||
|
|
||||||
|
// Track allowed outbound packets and automatically allow their corresponding inbound response
|
||||||
|
// packets, even if they would normally be dropped by ACL rules
|
||||||
|
outbound_allow_records: Arc<DashMap<OutboundAllowRecord, Instant>>,
|
||||||
|
clean_task: ScopedTask<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AclFilter {
|
impl Default for AclFilter {
|
||||||
@@ -36,10 +75,21 @@ impl Default for AclFilter {
|
|||||||
|
|
||||||
impl AclFilter {
|
impl AclFilter {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let outbound_allow_records = Arc::new(DashMap::new());
|
||||||
|
let record_clone = outbound_allow_records.clone();
|
||||||
Self {
|
Self {
|
||||||
acl_processor: ArcSwap::from(Arc::new(AclProcessor::new(Acl::default()))),
|
acl_processor: ArcSwap::from(Arc::new(AclProcessor::new(Acl::default()))),
|
||||||
acl_enabled: Arc::new(AtomicBool::new(false)),
|
acl_enabled: Arc::new(AtomicBool::new(false)),
|
||||||
quic_udp_port: AtomicU16::new(0),
|
quic_udp_port: AtomicU16::new(0),
|
||||||
|
outbound_allow_records,
|
||||||
|
clean_task: tokio::spawn(async move {
|
||||||
|
let max_life = std::time::Duration::from_secs(30);
|
||||||
|
loop {
|
||||||
|
record_clone.retain(|_, v| v.elapsed() < max_life);
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,8 +386,32 @@ impl AclFilter {
|
|||||||
|
|
||||||
// Check if packet should be allowed
|
// Check if packet should be allowed
|
||||||
match acl_result.action {
|
match acl_result.action {
|
||||||
Action::Allow | Action::Noop => true,
|
Action::Allow | Action::Noop => {
|
||||||
|
if matches!(chain_type, ChainType::Outbound) {
|
||||||
|
self.outbound_allow_records.insert(
|
||||||
|
OutboundAllowRecord::new_from_outbound_packet(&packet_info),
|
||||||
|
Instant::now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
Action::Drop => {
|
Action::Drop => {
|
||||||
|
if is_in {
|
||||||
|
let record = OutboundAllowRecord::new_from_inbound_packet(&packet_info);
|
||||||
|
let entry = self.outbound_allow_records.entry(record);
|
||||||
|
if let dashmap::Entry::Occupied(mut entry) = entry {
|
||||||
|
entry.insert(Instant::now());
|
||||||
|
tracing::trace!(
|
||||||
|
"ACL: Allowing {:?} packet from {} to {} because of existing allow record, chain_type: {:?}",
|
||||||
|
packet_info.protocol,
|
||||||
|
packet_info.src_ip,
|
||||||
|
packet_info.dst_ip,
|
||||||
|
chain_type,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"ACL: Dropping {:?} packet from {} to {}, chain_type: {:?}",
|
"ACL: Dropping {:?} packet from {} to {}, chain_type: {:?}",
|
||||||
packet_info.protocol,
|
packet_info.protocol,
|
||||||
|
|||||||
@@ -2468,12 +2468,21 @@ pub async fn acl_group_self_test(
|
|||||||
#[rstest::rstest]
|
#[rstest::rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial]
|
#[serial_test::serial]
|
||||||
pub async fn whitelist_test(#[values("tcp", "udp")] protocol: &str) {
|
pub async fn whitelist_test(
|
||||||
|
#[values("tcp", "udp")] protocol: &str,
|
||||||
|
#[values(true, false)] test_outbound_allow_list: bool,
|
||||||
|
) {
|
||||||
let port = 44553;
|
let port = 44553;
|
||||||
|
let acl_configured_inst = if test_outbound_allow_list {
|
||||||
|
"inst1"
|
||||||
|
} else {
|
||||||
|
"inst3"
|
||||||
|
};
|
||||||
let insts = init_three_node_ex(
|
let insts = init_three_node_ex(
|
||||||
protocol,
|
protocol,
|
||||||
move |cfg| {
|
move |cfg| {
|
||||||
if cfg.get_inst_name() == "inst3" {
|
let port = if test_outbound_allow_list { 0 } else { port };
|
||||||
|
if cfg.get_inst_name() == acl_configured_inst {
|
||||||
if protocol == "tcp" {
|
if protocol == "tcp" {
|
||||||
cfg.set_tcp_whitelist(vec![format!("{}", port)]);
|
cfg.set_tcp_whitelist(vec![format!("{}", port)]);
|
||||||
} else if protocol == "udp" {
|
} else if protocol == "udp" {
|
||||||
@@ -2536,6 +2545,10 @@ pub async fn whitelist_test(#[values("tcp", "udp")] protocol: &str) {
|
|||||||
.unwrap_or_else(|_| panic!("{} should be allowed", p));
|
.unwrap_or_else(|_| panic!("{} should be allowed", p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if test_outbound_allow_list {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// test other port
|
// test other port
|
||||||
let other_port = port + 1;
|
let other_port = port + 1;
|
||||||
for p in ["tcp", "udp"] {
|
for p in ["tcp", "udp"] {
|
||||||
|
|||||||
Reference in New Issue
Block a user