mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-04 01:55:41 +00:00
feat(socket): add Linux SO_MARK (fwmark) support for underlay sockets (#2288)
Adds a Linux-only socket_mark u32 config flag (CLI: --socket-mark, env:
ET_SOCKET_MARK, TOML/proto: flags.socket_mark, 0 = disabled) that is
applied as SO_MARK to every outbound underlay socket EasyTier creates:
TCP, UDP, QUIC, WebSocket, WireGuard connectors and listeners, plus the
FakeTCP decoy socket. Lets the host policy-route or filter EasyTier
underlay traffic with 'ip rule fwmark ...' or iptables -m mark.
Plumbing mirrors the existing bind_device pattern:
- FlagsInConfig.socket_mark (proto) + default 0 in gen_default_flags
- bind() builder gets a socket_mark arg; setup_socket2_ext calls
apply_socket_mark which is a no-op for mark=0 and on non-Linux
- TunnelConnector trait gets set_socket_mark(u32) default-no-op method
- IP-based connectors override; create_listener_by_url and the connector
factory pass mark from global_ctx flags
- QUIC threads mark through QuicEndpointManager::{server,connect}
- WebSocket/FakeTCP/TCP default-bind bypass paths apply mark via
socket2::SockRef::from(&tokio_socket)
- ForeignNetworkEntry propagates parent socket_mark into its derived ctx
Includes a Linux smoke test plus a CAP_NET_ADMIN-gated test that does a
getsockopt(SO_MARK) round-trip to confirm the kernel applied the value.
SO_MARK requires CAP_NET_ADMIN; ignored silently on non-Linux. FakeTCP's
TUN-written segments are not covered (kernel doesn't tag raw TUN
writes); operators relying on fwmark for FakeTCP must apply an iptables
rule on the FakeTCP TUN device separately.
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -567,6 +567,7 @@ pub struct UdpTunnelListener {
|
||||
data: UdpTunnelListenerData,
|
||||
forward_tasks: Arc<std::sync::Mutex<JoinSet<()>>>,
|
||||
close_event_recv: Option<UdpCloseEventReceiver>,
|
||||
socket_mark: Option<u32>,
|
||||
}
|
||||
|
||||
impl UdpTunnelListener {
|
||||
@@ -580,9 +581,14 @@ impl UdpTunnelListener {
|
||||
data: UdpTunnelListenerData::new(addr, conn_send, close_event_send),
|
||||
forward_tasks: Arc::new(std::sync::Mutex::new(JoinSet::new())),
|
||||
close_event_recv: Some(close_event_recv),
|
||||
socket_mark: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_socket_mark(&mut self, socket_mark: Option<u32>) {
|
||||
self.socket_mark = socket_mark;
|
||||
}
|
||||
|
||||
pub fn new_with_socket(addr: url::Url, socket: Arc<UdpSocket>) -> Self {
|
||||
let mut listener = Self::new(addr);
|
||||
listener.socket = Some(socket);
|
||||
@@ -605,6 +611,7 @@ impl TunnelListener for UdpTunnelListener {
|
||||
.addr(addr)
|
||||
.only_v6(true)
|
||||
.maybe_dev(tunnel_url.bind_dev())
|
||||
.maybe_socket_mark(self.socket_mark)
|
||||
.call()?,
|
||||
));
|
||||
}
|
||||
@@ -683,6 +690,7 @@ pub struct UdpTunnelConnector {
|
||||
bind_addrs: Vec<SocketAddr>,
|
||||
ip_version: IpVersion,
|
||||
resolved_addr: Option<SocketAddr>,
|
||||
socket_mark: Option<u32>,
|
||||
}
|
||||
|
||||
impl UdpTunnelConnector {
|
||||
@@ -692,6 +700,7 @@ impl UdpTunnelConnector {
|
||||
bind_addrs: vec![],
|
||||
ip_version: IpVersion::Both,
|
||||
resolved_addr: None,
|
||||
socket_mark: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,11 +885,18 @@ impl UdpTunnelConnector {
|
||||
&self,
|
||||
addr: SocketAddr,
|
||||
) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||
let socket = if addr.is_ipv4() {
|
||||
UdpSocket::bind("0.0.0.0:0").await?
|
||||
// Route through bind() so socket_mark is applied consistently for
|
||||
// both the None (no-op) and Some(_) paths.
|
||||
let bind_addr: SocketAddr = if addr.is_ipv4() {
|
||||
"0.0.0.0:0".parse().unwrap()
|
||||
} else {
|
||||
UdpSocket::bind("[::]:0").await?
|
||||
"[::]:0".parse().unwrap()
|
||||
};
|
||||
let socket = bind::<UdpSocket>()
|
||||
.addr(bind_addr)
|
||||
.only_v6(true)
|
||||
.maybe_socket_mark(self.socket_mark)
|
||||
.call()?;
|
||||
|
||||
return self.try_connect_with_socket(Arc::new(socket), addr).await;
|
||||
}
|
||||
@@ -893,7 +909,12 @@ impl UdpTunnelConnector {
|
||||
|
||||
for bind_addr in self.bind_addrs.iter() {
|
||||
tracing::info!(?bind_addr, ?addr, "bind addr");
|
||||
match bind().addr(*bind_addr).only_v6(true).call() {
|
||||
match bind()
|
||||
.addr(*bind_addr)
|
||||
.only_v6(true)
|
||||
.maybe_socket_mark(self.socket_mark)
|
||||
.call()
|
||||
{
|
||||
Ok(socket) => futures.push(self.try_connect_with_socket(Arc::new(socket), addr)),
|
||||
Err(error) => {
|
||||
tracing::error!(?error, ?bind_addr, ?addr, "bind addr fail");
|
||||
@@ -934,6 +955,10 @@ impl super::TunnelConnector for UdpTunnelConnector {
|
||||
fn set_resolved_addr(&mut self, addr: SocketAddr) {
|
||||
self.resolved_addr = Some(addr);
|
||||
}
|
||||
|
||||
fn set_socket_mark(&mut self, socket_mark: Option<u32>) {
|
||||
self.socket_mark = socket_mark;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user