Skip to content

Commit f086abf

Browse files
committed
Support setting SO_MARK on Linux
1 parent 8b313a2 commit f086abf

File tree

7 files changed

+73
-5
lines changed

7 files changed

+73
-5
lines changed

src/bin/local.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ fn main() {
8181

8282
(@arg PROTOCOL: --protocol +takes_value default_value("socks5") possible_values(AVAILABLE_PROTOCOLS) +next_line_help "Protocol that for communicating with clients")
8383

84-
(@arg NO_DELAY: --("no-delay") !takes_value "Set no-delay option for socket")
84+
(@arg NO_DELAY: --("no-delay") !takes_value "Set TCP_NODELAY option for socket")
8585
(@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit (only for *nix systems)")
8686
(@arg ACL: --acl +takes_value "Path to ACL (Access Control List)")
8787

@@ -101,6 +101,13 @@ fn main() {
101101
.help("Resolve hostname to IPv6 address first"),
102102
);
103103

104+
#[cfg(any(target_os = "linux", target_os = "android"))]
105+
{
106+
app = clap_app!(@app (app)
107+
(@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket")
108+
);
109+
}
110+
104111
#[cfg(feature = "local-redir")]
105112
{
106113
let available_redir_types = RedirType::available_types();
@@ -118,7 +125,8 @@ fn main() {
118125
}
119126
}
120127

121-
if cfg!(target_os = "android") {
128+
#[cfg(target_os = "android")]
129+
{
122130
app = clap_app!(@app (app)
123131
(@arg VPN_MODE: --vpn "Enable VPN mode (only for Android)")
124132
);
@@ -239,7 +247,8 @@ fn main() {
239247
config.server.push(svr_addr);
240248
}
241249

242-
if cfg!(target_os = "android") {
250+
#[cfg(target_os = "android")]
251+
{
243252
config.local_dns_path = Some(From::from("local_dns_path"));
244253

245254
if matches.is_present("VPN_MODE") {
@@ -294,6 +303,11 @@ fn main() {
294303
config.no_delay = true;
295304
}
296305

306+
#[cfg(any(target_os = "linux", target_os = "android"))]
307+
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
308+
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
309+
}
310+
297311
if let Some(nofile) = matches.value_of("NOFILE") {
298312
config.nofile = Some(nofile.parse::<u64>().expect("an unsigned integer for `nofile`"));
299313
}

src/bin/manager.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ fn main() {
5555
(@arg BIND_ADDR: -b --("bind-addr") +takes_value "Bind address, outbound socket will bind this address")
5656
(@arg SERVER_HOST: -s --("server-host") +takes_value "Host name or IP address of your remote server")
5757

58-
(@arg NO_DELAY: --("no-delay") !takes_value "Set no-delay option for socket")
58+
(@arg NO_DELAY: --("no-delay") !takes_value "Set TCP_NODELAY option for socket")
5959

6060
(@arg MANAGER_ADDRESS: --("manager-address") +takes_value {validator::validate_manager_addr} "ShadowSocks Manager (ssmgr) address, could be ip:port, domain:port or /path/to/unix.sock")
6161
(@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value possible_values(&available_ciphers) +next_line_help "Default encryption method")
@@ -76,6 +76,13 @@ fn main() {
7676
);
7777
}
7878

79+
#[cfg(any(target_os = "linux", target_os = "android"))]
80+
{
81+
app = clap_app!(@app (app)
82+
(@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket")
83+
);
84+
}
85+
7986
let matches = app
8087
.arg(
8188
Arg::with_name("IPV6_FIRST")
@@ -130,6 +137,11 @@ fn main() {
130137
config.no_delay = true;
131138
}
132139

140+
#[cfg(any(target_os = "linux", target_os = "android"))]
141+
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
142+
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
143+
}
144+
133145
if let Some(m) = matches.value_of("MANAGER_ADDRESS") {
134146
if let Some(ref mut manager_config) = config.manager {
135147
manager_config.addr = m.parse::<ManagerAddr>().expect("manager-address");

src/bin/server.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ fn main() {
6262

6363
(@arg MANAGER_ADDRESS: --("manager-address") +takes_value "ShadowSocks Manager (ssmgr) address, could be \"IP:Port\", \"Domain:Port\" or \"/path/to/unix.sock\"")
6464

65-
(@arg NO_DELAY: --("no-delay") !takes_value "Set no-delay option for socket")
65+
(@arg NO_DELAY: --("no-delay") !takes_value "Set TCP_NODELAY option for socket")
6666
(@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit (only for *nix systems)")
6767
(@arg ACL: --acl +takes_value "Path to ACL (Access Control List)")
6868

@@ -81,6 +81,13 @@ fn main() {
8181
);
8282
}
8383

84+
#[cfg(any(target_os = "linux", target_os = "android"))]
85+
{
86+
app = clap_app!(@app (app)
87+
(@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket")
88+
);
89+
}
90+
8491
let matches = app
8592
.arg(
8693
Arg::with_name("IPV6_FIRST")
@@ -163,6 +170,11 @@ fn main() {
163170
config.no_delay = true;
164171
}
165172

173+
#[cfg(any(target_os = "linux", target_os = "android"))]
174+
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
175+
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
176+
}
177+
166178
if let Some(m) = matches.value_of("MANAGER_ADDRESS") {
167179
config.manager = Some(ManagerConfig::new(m.parse::<ManagerAddr>().expect("manager address")));
168180
}

src/bin/validator/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ validate_type!(
3030
"should be either ip:port, domain:port or /path/to/unix.sock"
3131
);
3232
validate_type!(validate_u64, u64, "should be unsigned integer");
33+
validate_type!(validate_u32, u32, "should be unsigned integer");
3334

3435
pub fn validate_server_url(v: String) -> Result<(), String> {
3536
match ServerConfig::from_url(&v) {

src/config.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,9 @@ pub struct Config {
10521052
pub mode: Mode,
10531053
/// Set `TCP_NODELAY` socket option
10541054
pub no_delay: bool,
1055+
/// Set `SO_MARK` socket option for outbound sockets
1056+
#[cfg(any(target_os = "linux", target_os = "android"))]
1057+
pub outbound_fwmark: Option<u32>,
10551058
/// Manager's configuration
10561059
pub manager: Option<ManagerConfig>,
10571060
/// Config is for Client or Server
@@ -1178,6 +1181,8 @@ impl Config {
11781181
dns: None,
11791182
mode: Mode::TcpOnly,
11801183
no_delay: false,
1184+
#[cfg(any(target_os = "linux", target_os = "android"))]
1185+
outbound_fwmark: None,
11811186
manager: None,
11821187
config_type,
11831188
udp_timeout: None,

src/relay/manager.rs

+6
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,12 @@ impl ManagerService {
476476
config.no_delay = self.context.config().no_delay;
477477
}
478478

479+
// SO_MARK
480+
#[cfg(any(target_os = "linux", target_os = "android"))]
481+
{
482+
config.outbound_fwmark = self.context.config().outbound_fwmark;
483+
}
484+
479485
// UDP configurations
480486
config.udp_timeout = self.context.config().udp_timeout;
481487
config.udp_max_associations = self.context.config().udp_max_associations;

src/relay/sys/unix/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ pub async fn tcp_stream_connect(saddr: &SocketAddr, context: &Context) -> io::Re
8787
}
8888
}
8989

90+
// Set SO_MARK for mark-based routing on Linux (since 2.6.25)
91+
// NOTE: This will require CAP_NET_ADMIN capability (root in most cases)
92+
#[cfg(any(target_os = "linux", target_os = "android"))]
93+
if let Some(mark) = context.config().outbound_fwmark {
94+
let ret = unsafe {
95+
libc::setsockopt(
96+
socket.as_raw_fd(),
97+
libc::SOL_SOCKET,
98+
libc::SO_MARK,
99+
&mark as *const _ as *const _,
100+
mem::size_of_val(&mark) as libc::socklen_t,
101+
)
102+
};
103+
if ret != 0 {
104+
return Err(Error::last_os_error());
105+
}
106+
}
107+
90108
// it's important that the socket is protected before connecting
91109
let stream = socket.into_tcp_stream();
92110
TcpStream::connect_std(stream, &saddr).await

0 commit comments

Comments
 (0)