mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-08-08 05:29:47 +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:
@@ -73,6 +73,7 @@ pub fn gen_default_flags() -> Flags {
|
||||
disable_upnp: false,
|
||||
disable_relay_data: false,
|
||||
enable_udp_broadcast_relay: false,
|
||||
socket_mark: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1259,6 +1260,57 @@ pub mod tests {
|
||||
use std::path::PathBuf;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn socket_mark_config_file_roundtrip_none_some_and_zero() {
|
||||
// Omitting the flag leaves socket_mark unset (None) -> SO_MARK untouched.
|
||||
let cfg = TomlConfigLoader::new_from_str(
|
||||
r#"
|
||||
[network_identity]
|
||||
network_name = "n"
|
||||
network_secret = "s"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cfg.get_flags().socket_mark, None);
|
||||
|
||||
// socket_mark = 0 is a legitimate value distinct from "unset".
|
||||
let cfg = TomlConfigLoader::new_from_str(
|
||||
r#"
|
||||
[network_identity]
|
||||
network_name = "n"
|
||||
network_secret = "s"
|
||||
|
||||
[flags]
|
||||
socket_mark = 0
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cfg.get_flags().socket_mark, Some(0));
|
||||
|
||||
// A non-zero mark round-trips as Some(v).
|
||||
let cfg = TomlConfigLoader::new_from_str(
|
||||
r#"
|
||||
[network_identity]
|
||||
network_name = "n"
|
||||
network_secret = "s"
|
||||
|
||||
[flags]
|
||||
socket_mark = 66
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cfg.get_flags().socket_mark, Some(66));
|
||||
|
||||
// set_flags(None) must serialize back through gen_config without
|
||||
// resurrecting a value (guards the gen_flags merge against dropping
|
||||
// the key when the serialized default is null).
|
||||
cfg.set_flags(Flags {
|
||||
socket_mark: None,
|
||||
..cfg.get_flags()
|
||||
});
|
||||
assert_eq!(cfg.get_flags().socket_mark, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stun_servers_config() {
|
||||
let config = TomlConfigLoader::default();
|
||||
|
||||
Reference in New Issue
Block a user