Implement ACL (#1140)
1. get acl stats
```
./easytier-cli acl stats
AclStats:
Global:
CacheHits: 4
CacheMaxSize: 10000
CacheSize: 5
DefaultAllows: 3
InboundPacketsAllowed: 2
InboundPacketsTotal: 2
OutboundPacketsAllowed: 7
OutboundPacketsTotal: 7
PacketsAllowed: 9
PacketsTotal: 9
RuleMatches: 2
ConnTrack:
[src: 10.14.11.1:57444, dst: 10.14.11.2:1000, proto: Tcp, state: New, pkts: 1, bytes: 60, created: 2025-07-24 10:13:39 +08:00, last_seen: 2025-07-24 10:13:39 +08:00]
Rules:
[name: 'tcp_whitelist', prio: 1000, action: Allow, enabled: true, proto: Tcp, ports: ["1000"], src_ports: [], src_ips: [], dst_ips: [], stateful: true, rate: 0, burst: 0] [pkts: 2, bytes: 120]
```
2. use tcp/udp whitelist to block unexpected traffic.
`sudo ./easytier-core -d --tcp-whitelist 1000`
3. use complete acl ability with config file:
```
[[acl.acl_v1.chains]]
name = "inbound_whitelist"
chain_type = 1
description = "Auto-generated inbound whitelist from CLI"
enabled = true
default_action = 2
[[acl.acl_v1.chains.rules]]
name = "tcp_whitelist"
description = "Auto-generated TCP whitelist rule"
priority = 1000
enabled = true
protocol = 1
ports = ["1000"]
source_ips = []
destination_ips = []
source_ports = []
action = 1
rate_limit = 0
burst_limit = 0
stateful = true
```
2025-07-24 22:13:45 +08:00
|
|
|
use std::{
|
|
|
|
|
fmt::{self, Display},
|
|
|
|
|
str::FromStr,
|
|
|
|
|
};
|
2024-09-18 21:55:28 +08:00
|
|
|
|
2024-10-10 10:28:48 +08:00
|
|
|
use anyhow::Context;
|
|
|
|
|
|
2024-11-16 11:23:18 +08:00
|
|
|
use crate::tunnel::packet_def::CompressorAlgo;
|
|
|
|
|
|
2024-09-18 21:55:28 +08:00
|
|
|
include!(concat!(env!("OUT_DIR"), "/common.rs"));
|
|
|
|
|
|
|
|
|
|
impl From<uuid::Uuid> for Uuid {
|
|
|
|
|
fn from(uuid: uuid::Uuid) -> Self {
|
|
|
|
|
let (high, low) = uuid.as_u64_pair();
|
2024-11-08 23:33:17 +08:00
|
|
|
Uuid {
|
|
|
|
|
part1: (high >> 32) as u32,
|
|
|
|
|
part2: (high & 0xFFFFFFFF) as u32,
|
|
|
|
|
part3: (low >> 32) as u32,
|
|
|
|
|
part4: (low & 0xFFFFFFFF) as u32,
|
|
|
|
|
}
|
2024-09-18 21:55:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Uuid> for uuid::Uuid {
|
|
|
|
|
fn from(uuid: Uuid) -> Self {
|
2024-11-08 23:33:17 +08:00
|
|
|
uuid::Uuid::from_u64_pair(
|
|
|
|
|
(u64::from(uuid.part1) << 32) | u64::from(uuid.part2),
|
|
|
|
|
(u64::from(uuid.part3) << 32) | u64::from(uuid.part4),
|
|
|
|
|
)
|
2024-09-18 21:55:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 15:13:19 +08:00
|
|
|
impl From<String> for Uuid {
|
|
|
|
|
fn from(value: String) -> Self {
|
|
|
|
|
uuid::Uuid::parse_str(&value).unwrap().into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-21 11:24:35 +08:00
|
|
|
impl fmt::Display for Uuid {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
write!(f, "{}", uuid::Uuid::from(self.clone()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Debug for Uuid {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2024-09-18 21:55:28 +08:00
|
|
|
write!(f, "{}", uuid::Uuid::from(self.clone()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<std::net::Ipv4Addr> for Ipv4Addr {
|
|
|
|
|
fn from(value: std::net::Ipv4Addr) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
addr: u32::from_be_bytes(value.octets()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Ipv4Addr> for std::net::Ipv4Addr {
|
|
|
|
|
fn from(value: Ipv4Addr) -> Self {
|
|
|
|
|
std::net::Ipv4Addr::from(value.addr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToString for Ipv4Addr {
|
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
|
std::net::Ipv4Addr::from(self.addr).to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<std::net::Ipv6Addr> for Ipv6Addr {
|
|
|
|
|
fn from(value: std::net::Ipv6Addr) -> Self {
|
|
|
|
|
let b = value.octets();
|
|
|
|
|
Self {
|
2024-09-27 02:05:24 +08:00
|
|
|
part1: u32::from_be_bytes([b[0], b[1], b[2], b[3]]),
|
|
|
|
|
part2: u32::from_be_bytes([b[4], b[5], b[6], b[7]]),
|
|
|
|
|
part3: u32::from_be_bytes([b[8], b[9], b[10], b[11]]),
|
|
|
|
|
part4: u32::from_be_bytes([b[12], b[13], b[14], b[15]]),
|
2024-09-18 21:55:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Ipv6Addr> for std::net::Ipv6Addr {
|
|
|
|
|
fn from(value: Ipv6Addr) -> Self {
|
2024-09-27 02:05:24 +08:00
|
|
|
let part1 = value.part1.to_be_bytes();
|
|
|
|
|
let part2 = value.part2.to_be_bytes();
|
|
|
|
|
let part3 = value.part3.to_be_bytes();
|
|
|
|
|
let part4 = value.part4.to_be_bytes();
|
2024-09-18 21:55:28 +08:00
|
|
|
std::net::Ipv6Addr::from([
|
2024-10-10 10:28:48 +08:00
|
|
|
part1[0], part1[1], part1[2], part1[3], part2[0], part2[1], part2[2], part2[3],
|
|
|
|
|
part3[0], part3[1], part3[2], part3[3], part4[0], part4[1], part4[2], part4[3],
|
2024-09-18 21:55:28 +08:00
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToString for Ipv6Addr {
|
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
|
std::net::Ipv6Addr::from(self.clone()).to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 10:28:48 +08:00
|
|
|
impl From<cidr::Ipv4Inet> for Ipv4Inet {
|
|
|
|
|
fn from(value: cidr::Ipv4Inet) -> Self {
|
|
|
|
|
Ipv4Inet {
|
|
|
|
|
address: Some(value.address().into()),
|
|
|
|
|
network_length: value.network_length() as u32,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Ipv4Inet> for cidr::Ipv4Inet {
|
|
|
|
|
fn from(value: Ipv4Inet) -> Self {
|
2025-02-07 17:59:30 +08:00
|
|
|
cidr::Ipv4Inet::new(
|
|
|
|
|
value.address.unwrap_or_default().into(),
|
|
|
|
|
value.network_length as u8,
|
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
2024-10-10 10:28:48 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-21 11:24:35 +08:00
|
|
|
impl fmt::Display for Ipv4Inet {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2024-10-10 10:28:48 +08:00
|
|
|
write!(f, "{}", cidr::Ipv4Inet::from(self.clone()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for Ipv4Inet {
|
|
|
|
|
type Err = anyhow::Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
Ok(Ipv4Inet::from(
|
|
|
|
|
cidr::Ipv4Inet::from_str(s).with_context(|| "Failed to parse Ipv4Inet")?,
|
2025-07-04 22:43:30 +07:00
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<cidr::Ipv6Inet> for Ipv6Inet {
|
|
|
|
|
fn from(value: cidr::Ipv6Inet) -> Self {
|
|
|
|
|
Ipv6Inet {
|
|
|
|
|
address: Some(value.address().into()),
|
|
|
|
|
network_length: value.network_length() as u32,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Ipv6Inet> for cidr::Ipv6Inet {
|
|
|
|
|
fn from(value: Ipv6Inet) -> Self {
|
|
|
|
|
cidr::Ipv6Inet::new(
|
|
|
|
|
value.address.unwrap_or_default().into(),
|
|
|
|
|
value.network_length as u8,
|
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for Ipv6Inet {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
write!(f, "{}", cidr::Ipv6Inet::from(self.clone()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for Ipv6Inet {
|
|
|
|
|
type Err = anyhow::Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
Ok(Ipv6Inet::from(
|
|
|
|
|
cidr::Ipv6Inet::from_str(s).with_context(|| "Failed to parse Ipv6Inet")?,
|
2024-10-10 10:28:48 +08:00
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Implement ACL (#1140)
1. get acl stats
```
./easytier-cli acl stats
AclStats:
Global:
CacheHits: 4
CacheMaxSize: 10000
CacheSize: 5
DefaultAllows: 3
InboundPacketsAllowed: 2
InboundPacketsTotal: 2
OutboundPacketsAllowed: 7
OutboundPacketsTotal: 7
PacketsAllowed: 9
PacketsTotal: 9
RuleMatches: 2
ConnTrack:
[src: 10.14.11.1:57444, dst: 10.14.11.2:1000, proto: Tcp, state: New, pkts: 1, bytes: 60, created: 2025-07-24 10:13:39 +08:00, last_seen: 2025-07-24 10:13:39 +08:00]
Rules:
[name: 'tcp_whitelist', prio: 1000, action: Allow, enabled: true, proto: Tcp, ports: ["1000"], src_ports: [], src_ips: [], dst_ips: [], stateful: true, rate: 0, burst: 0] [pkts: 2, bytes: 120]
```
2. use tcp/udp whitelist to block unexpected traffic.
`sudo ./easytier-core -d --tcp-whitelist 1000`
3. use complete acl ability with config file:
```
[[acl.acl_v1.chains]]
name = "inbound_whitelist"
chain_type = 1
description = "Auto-generated inbound whitelist from CLI"
enabled = true
default_action = 2
[[acl.acl_v1.chains.rules]]
name = "tcp_whitelist"
description = "Auto-generated TCP whitelist rule"
priority = 1000
enabled = true
protocol = 1
ports = ["1000"]
source_ips = []
destination_ips = []
source_ports = []
action = 1
rate_limit = 0
burst_limit = 0
stateful = true
```
2025-07-24 22:13:45 +08:00
|
|
|
impl From<cidr::IpInet> for IpInet {
|
|
|
|
|
fn from(value: cidr::IpInet) -> Self {
|
|
|
|
|
match value {
|
|
|
|
|
cidr::IpInet::V4(v4) => IpInet {
|
|
|
|
|
ip: Some(ip_inet::Ip::Ipv4(Ipv4Inet::from(v4))),
|
|
|
|
|
},
|
|
|
|
|
cidr::IpInet::V6(v6) => IpInet {
|
|
|
|
|
ip: Some(ip_inet::Ip::Ipv6(Ipv6Inet::from(v6))),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<IpInet> for cidr::IpInet {
|
|
|
|
|
fn from(value: IpInet) -> Self {
|
|
|
|
|
match value.ip {
|
|
|
|
|
Some(ip_inet::Ip::Ipv4(v4)) => cidr::IpInet::V4(v4.into()),
|
|
|
|
|
Some(ip_inet::Ip::Ipv6(v6)) => cidr::IpInet::V6(v6.into()),
|
|
|
|
|
None => panic!("IpInet is None"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Display for IpInet {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
write!(f, "{}", cidr::IpInet::from(self.clone()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for IpInet {
|
|
|
|
|
type Err = anyhow::Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
Ok(IpInet::from(cidr::IpInet::from_str(s)?))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 21:55:28 +08:00
|
|
|
impl From<url::Url> for Url {
|
|
|
|
|
fn from(value: url::Url) -> Self {
|
|
|
|
|
Url {
|
|
|
|
|
url: value.to_string(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Url> for url::Url {
|
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
|
url::Url::parse(&value.url).unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for Url {
|
|
|
|
|
type Err = url::ParseError;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
Ok(Url {
|
|
|
|
|
url: s.parse::<url::Url>()?.to_string(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-21 11:24:35 +08:00
|
|
|
impl fmt::Display for Url {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2024-09-18 21:55:28 +08:00
|
|
|
write!(f, "{}", self.url)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<std::net::SocketAddr> for SocketAddr {
|
|
|
|
|
fn from(value: std::net::SocketAddr) -> Self {
|
|
|
|
|
match value {
|
|
|
|
|
std::net::SocketAddr::V4(v4) => SocketAddr {
|
|
|
|
|
ip: Some(socket_addr::Ip::Ipv4(v4.ip().clone().into())),
|
|
|
|
|
port: v4.port() as u32,
|
|
|
|
|
},
|
|
|
|
|
std::net::SocketAddr::V6(v6) => SocketAddr {
|
|
|
|
|
ip: Some(socket_addr::Ip::Ipv6(v6.ip().clone().into())),
|
|
|
|
|
port: v6.port() as u32,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<SocketAddr> for std::net::SocketAddr {
|
|
|
|
|
fn from(value: SocketAddr) -> Self {
|
2025-02-07 17:59:30 +08:00
|
|
|
if value.ip.is_none() {
|
|
|
|
|
return "0.0.0.0:0".parse().unwrap();
|
|
|
|
|
}
|
2024-09-18 21:55:28 +08:00
|
|
|
match value.ip.unwrap() {
|
|
|
|
|
socket_addr::Ip::Ipv4(ip) => std::net::SocketAddr::V4(std::net::SocketAddrV4::new(
|
|
|
|
|
std::net::Ipv4Addr::from(ip),
|
|
|
|
|
value.port as u16,
|
|
|
|
|
)),
|
|
|
|
|
socket_addr::Ip::Ipv6(ip) => std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
|
|
|
|
|
std::net::Ipv6Addr::from(ip),
|
|
|
|
|
value.port as u16,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-16 11:23:18 +08:00
|
|
|
|
2025-04-01 09:59:53 +08:00
|
|
|
impl ToString for SocketAddr {
|
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
|
std::net::SocketAddr::from(self.clone()).to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-16 11:23:18 +08:00
|
|
|
impl TryFrom<CompressionAlgoPb> for CompressorAlgo {
|
|
|
|
|
type Error = anyhow::Error;
|
|
|
|
|
|
|
|
|
|
fn try_from(value: CompressionAlgoPb) -> Result<Self, Self::Error> {
|
|
|
|
|
match value {
|
|
|
|
|
CompressionAlgoPb::Zstd => Ok(CompressorAlgo::ZstdDefault),
|
|
|
|
|
CompressionAlgoPb::None => Ok(CompressorAlgo::None),
|
|
|
|
|
_ => Err(anyhow::anyhow!("Invalid CompressionAlgoPb")),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TryFrom<CompressorAlgo> for CompressionAlgoPb {
|
|
|
|
|
type Error = anyhow::Error;
|
|
|
|
|
|
|
|
|
|
fn try_from(value: CompressorAlgo) -> Result<Self, Self::Error> {
|
|
|
|
|
match value {
|
|
|
|
|
CompressorAlgo::ZstdDefault => Ok(CompressionAlgoPb::Zstd),
|
|
|
|
|
CompressorAlgo::None => Ok(CompressionAlgoPb::None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-21 11:24:35 +08:00
|
|
|
|
|
|
|
|
impl fmt::Debug for Ipv4Addr {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
let std_ipv4_addr = std::net::Ipv4Addr::from(self.clone());
|
|
|
|
|
write!(f, "{}", std_ipv4_addr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Debug for Ipv6Addr {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
let std_ipv6_addr = std::net::Ipv6Addr::from(self.clone());
|
|
|
|
|
write!(f, "{}", std_ipv6_addr)
|
|
|
|
|
}
|
|
|
|
|
}
|