Skip to content

Commit ea5e3a5

Browse files
committed
feat(shadowsocks-service): ACL support outbound_allow_list
- [outbound_allow_all] BlackList mode, allow all outbound addresses by default - [outbound_block_all] WhiteList mode, blocked all outbound addresses by default - [outbound_block_list] Addresses in this list will be blocked - [outbound_allow_list] Addresses in this list will be allowed fixes #1967 Breaking Changes: - ACL IP rules now checking both allow_list and block_list - Hostnames, IPs that didn't match any rules will fallback to default mode
1 parent 46dd649 commit ea5e3a5

File tree

1 file changed

+83
-19
lines changed
  • crates/shadowsocks-service/src/acl

1 file changed

+83
-19
lines changed

crates/shadowsocks-service/src/acl/mod.rs

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,10 @@ impl ParsingRules {
256256
// Remove the last `.` of FQDN
257257
Ok(str.trim_end_matches('.'))
258258
} else {
259-
Err(Error::other(
260-
format!("{} parsing error: Unicode not allowed here `{}`", self.name, str),
261-
))
259+
Err(Error::other(format!(
260+
"{} parsing error: Unicode not allowed here `{}`",
261+
self.name, str
262+
)))
262263
}
263264
}
264265

@@ -309,11 +310,14 @@ impl ParsingRules {
309310
/// * `[bypass_list]` - Rules for connecting directly
310311
/// * `[proxy_list]` - Rules for connecting through proxies
311312
/// - For remote servers (`ssserver`)
312-
/// * `[reject_all]` - ACL runs in `BlackList` mode.
313-
/// * `[accept_all]` - ACL runs in `WhiteList` mode.
313+
/// * `[reject_all]` - ACL runs in `WhiteList` mode.
314+
/// * `[accept_all]` - ACL runs in `BlackList` mode.
314315
/// * `[black_list]` - Rules for rejecting
315316
/// * `[white_list]` - Rules for allowing
317+
/// * `[outbound_block_all]` - ACL runs in `WhiteList` mode for outbound addresses.
318+
/// * `[outbound_allow_all]` - ACL runs in `BlackList` mode for outbound addresses.
316319
/// * `[outbound_block_list]` - Rules for blocking outbound addresses.
320+
/// * `[outbound_allow_list]` - Rules for allowing outbound addresses.
317321
///
318322
/// ## Mode
319323
///
@@ -334,9 +338,11 @@ impl ParsingRules {
334338
#[derive(Debug, Clone)]
335339
pub struct AccessControl {
336340
outbound_block: Rules,
341+
outbound_allow: Rules,
337342
black_list: Rules,
338343
white_list: Rules,
339344
mode: Mode,
345+
outbound_mode: Mode,
340346
file_path: PathBuf,
341347
}
342348

@@ -352,8 +358,10 @@ impl AccessControl {
352358
let r = BufReader::new(fp);
353359

354360
let mut mode = Mode::BlackList;
361+
let mut outbound_mode = Mode::BlackList;
355362

356363
let mut outbound_block = ParsingRules::new("[outbound_block_list]");
364+
let mut outbound_allow = ParsingRules::new("[outbound_allow_list]");
357365
let mut bypass = ParsingRules::new("[black_list] or [bypass_list]");
358366
let mut proxy = ParsingRules::new("[white_list] or [proxy_list]");
359367
let mut curr = &mut bypass;
@@ -397,10 +405,22 @@ impl AccessControl {
397405
mode = Mode::BlackList;
398406
trace!("switch to mode {:?}", mode);
399407
}
408+
"[outbound_block_all]" => {
409+
outbound_mode = Mode::WhiteList;
410+
trace!("switch to outbound_mode {:?}", outbound_mode);
411+
}
412+
"[outbound_allow_all]" => {
413+
outbound_mode = Mode::BlackList;
414+
trace!("switch to outbound_mode {:?}", outbound_mode);
415+
}
400416
"[outbound_block_list]" => {
401417
curr = &mut outbound_block;
402418
trace!("loading outbound_block_list");
403419
}
420+
"[outbound_allow_list]" => {
421+
curr = &mut outbound_allow;
422+
trace!("loading outbound_allow_list");
423+
}
404424
"[black_list]" | "[bypass_list]" => {
405425
curr = &mut bypass;
406426
trace!("loading black_list / bypass_list");
@@ -438,9 +458,11 @@ impl AccessControl {
438458

439459
Ok(Self {
440460
outbound_block: outbound_block.into_rules()?,
461+
outbound_allow: outbound_allow.into_rules()?,
441462
black_list: bypass.into_rules()?,
442463
white_list: proxy.into_rules()?,
443464
mode,
465+
outbound_mode,
444466
file_path,
445467
})
446468
}
@@ -482,31 +504,36 @@ impl AccessControl {
482504
}
483505

484506
/// If there are no IP rules
507+
#[inline]
485508
pub fn is_ip_empty(&self) -> bool {
486-
match self.mode {
487-
Mode::BlackList => self.black_list.is_ip_empty(),
488-
Mode::WhiteList => self.white_list.is_ip_empty(),
489-
}
509+
self.black_list.is_ip_empty() && self.white_list.is_ip_empty()
490510
}
491511

492512
/// If there are no domain name rules
513+
#[inline]
493514
pub fn is_host_empty(&self) -> bool {
494515
self.black_list.is_host_empty() && self.white_list.is_host_empty()
495516
}
496517

497518
/// Check if `IpAddr` should be proxied
498519
pub fn check_ip_in_proxy_list(&self, ip: &IpAddr) -> bool {
499-
match self.mode {
500-
Mode::BlackList => !self.black_list.check_ip_matched(ip),
501-
Mode::WhiteList => self.white_list.check_ip_matched(ip),
520+
if self.black_list.check_ip_matched(ip) {
521+
// If IP is in black_list, it should be bypassed
522+
return false;
523+
}
524+
if self.white_list.check_ip_matched(ip) {
525+
// If IP is in white_list, it should be proxied
526+
return true;
502527
}
528+
self.is_default_in_proxy_list()
503529
}
504530

505531
/// Default mode
506532
///
507533
/// Default behavior for hosts that are not configured
508534
/// - `true` - Proxied
509535
/// - `false` - Bypassed
536+
#[inline]
510537
pub fn is_default_in_proxy_list(&self) -> bool {
511538
match self.mode {
512539
Mode::BlackList => true,
@@ -543,7 +570,7 @@ impl AccessControl {
543570
}
544571
}
545572
}
546-
false
573+
!self.is_default_in_proxy_list()
547574
}
548575
}
549576
}
@@ -556,7 +583,7 @@ impl AccessControl {
556583
self.black_list.check_ip_matched(&addr.ip())
557584
}
558585
Mode::WhiteList => {
559-
// Only clients in white_list will be proxied
586+
// Only clients not in white_list will be blocked
560587
!self.white_list.check_ip_matched(&addr.ip())
561588
}
562589
}
@@ -568,22 +595,59 @@ impl AccessControl {
568595
/// resolved addresses are checked in the `lookup_outbound_then!` macro
569596
pub async fn check_outbound_blocked(&self, context: &Context, outbound: &Address) -> bool {
570597
match outbound {
571-
Address::SocketAddress(saddr) => self.outbound_block.check_ip_matched(&saddr.ip()),
598+
Address::SocketAddress(saddr) => self.check_outbound_ip_blocked(&saddr.ip()),
572599
Address::DomainNameAddress(host, port) => {
573-
if self.outbound_block.check_host_matched(&Self::convert_to_ascii(host)) {
574-
return true;
600+
let ascii_host = Self::convert_to_ascii(host);
601+
if self.outbound_block.check_host_matched(&ascii_host) {
602+
return true; // Blocked by config
603+
}
604+
if self.outbound_allow.check_host_matched(&ascii_host) {
605+
return false; // Allowed by config
606+
}
607+
608+
// If no domain name rules matched,
609+
// we need to resolve the hostname to IP addresses
610+
if self.is_outbound_ip_empty() {
611+
// If there are no IP rules, use the default mode
612+
return self.is_outbound_default_blocked();
575613
}
576614

577615
if let Ok(vaddr) = context.dns_resolve(host, *port).await {
578616
for addr in vaddr {
579-
if self.outbound_block.check_ip_matched(&addr.ip()) {
617+
if self.check_outbound_ip_blocked(&addr.ip()) {
580618
return true;
581619
}
582620
}
583621
}
584622

585-
false
623+
self.is_outbound_default_blocked()
586624
}
587625
}
588626
}
627+
628+
fn check_outbound_ip_blocked(&self, ip: &IpAddr) -> bool {
629+
if self.outbound_block.check_ip_matched(ip) {
630+
// If IP is in outbound_block, it should be blocked
631+
return true;
632+
}
633+
if self.outbound_allow.check_ip_matched(ip) {
634+
// If IP is in outbound_allow, it should be allowed
635+
return false;
636+
}
637+
// If IP is not in any list, check the default mode
638+
self.is_outbound_default_blocked()
639+
}
640+
641+
#[inline]
642+
fn is_outbound_default_blocked(&self) -> bool {
643+
match self.outbound_mode {
644+
Mode::BlackList => false,
645+
Mode::WhiteList => true,
646+
}
647+
}
648+
649+
#[inline]
650+
fn is_outbound_ip_empty(&self) -> bool {
651+
self.outbound_block.is_ip_empty() && self.outbound_allow.is_ip_empty()
652+
}
589653
}

0 commit comments

Comments
 (0)