diff --git a/crates/shadowsocks-service/src/acl/mod.rs b/crates/shadowsocks-service/src/acl/mod.rs index 3327cf18..226f744d 100644 --- a/crates/shadowsocks-service/src/acl/mod.rs +++ b/crates/shadowsocks-service/src/acl/mod.rs @@ -256,9 +256,10 @@ impl ParsingRules { // Remove the last `.` of FQDN Ok(str.trim_end_matches('.')) } else { - Err(Error::other( - format!("{} parsing error: Unicode not allowed here `{}`", self.name, str), - )) + Err(Error::other(format!( + "{} parsing error: Unicode not allowed here `{}`", + self.name, str + ))) } } @@ -309,11 +310,14 @@ impl ParsingRules { /// * `[bypass_list]` - Rules for connecting directly /// * `[proxy_list]` - Rules for connecting through proxies /// - For remote servers (`ssserver`) -/// * `[reject_all]` - ACL runs in `BlackList` mode. -/// * `[accept_all]` - ACL runs in `WhiteList` mode. +/// * `[reject_all]` - ACL runs in `WhiteList` mode. +/// * `[accept_all]` - ACL runs in `BlackList` mode. /// * `[black_list]` - Rules for rejecting /// * `[white_list]` - Rules for allowing +/// * `[outbound_block_all]` - ACL runs in `WhiteList` mode for outbound addresses. +/// * `[outbound_allow_all]` - ACL runs in `BlackList` mode for outbound addresses. /// * `[outbound_block_list]` - Rules for blocking outbound addresses. +/// * `[outbound_allow_list]` - Rules for allowing outbound addresses. /// /// ## Mode /// @@ -334,9 +338,11 @@ impl ParsingRules { #[derive(Debug, Clone)] pub struct AccessControl { outbound_block: Rules, + outbound_allow: Rules, black_list: Rules, white_list: Rules, mode: Mode, + outbound_mode: Mode, file_path: PathBuf, } @@ -352,8 +358,10 @@ impl AccessControl { let r = BufReader::new(fp); let mut mode = Mode::BlackList; + let mut outbound_mode = Mode::BlackList; let mut outbound_block = ParsingRules::new("[outbound_block_list]"); + let mut outbound_allow = ParsingRules::new("[outbound_allow_list]"); let mut bypass = ParsingRules::new("[black_list] or [bypass_list]"); let mut proxy = ParsingRules::new("[white_list] or [proxy_list]"); let mut curr = &mut bypass; @@ -397,10 +405,22 @@ impl AccessControl { mode = Mode::BlackList; trace!("switch to mode {:?}", mode); } + "[outbound_block_all]" => { + outbound_mode = Mode::WhiteList; + trace!("switch to outbound_mode {:?}", outbound_mode); + } + "[outbound_allow_all]" => { + outbound_mode = Mode::BlackList; + trace!("switch to outbound_mode {:?}", outbound_mode); + } "[outbound_block_list]" => { curr = &mut outbound_block; trace!("loading outbound_block_list"); } + "[outbound_allow_list]" => { + curr = &mut outbound_allow; + trace!("loading outbound_allow_list"); + } "[black_list]" | "[bypass_list]" => { curr = &mut bypass; trace!("loading black_list / bypass_list"); @@ -438,9 +458,11 @@ impl AccessControl { Ok(Self { outbound_block: outbound_block.into_rules()?, + outbound_allow: outbound_allow.into_rules()?, black_list: bypass.into_rules()?, white_list: proxy.into_rules()?, mode, + outbound_mode, file_path, }) } @@ -482,24 +504,28 @@ impl AccessControl { } /// If there are no IP rules + #[inline] pub fn is_ip_empty(&self) -> bool { - match self.mode { - Mode::BlackList => self.black_list.is_ip_empty(), - Mode::WhiteList => self.white_list.is_ip_empty(), - } + self.black_list.is_ip_empty() && self.white_list.is_ip_empty() } /// If there are no domain name rules + #[inline] pub fn is_host_empty(&self) -> bool { self.black_list.is_host_empty() && self.white_list.is_host_empty() } /// Check if `IpAddr` should be proxied pub fn check_ip_in_proxy_list(&self, ip: &IpAddr) -> bool { - match self.mode { - Mode::BlackList => !self.black_list.check_ip_matched(ip), - Mode::WhiteList => self.white_list.check_ip_matched(ip), + if self.black_list.check_ip_matched(ip) { + // If IP is in black_list, it should be bypassed + return false; } + if self.white_list.check_ip_matched(ip) { + // If IP is in white_list, it should be proxied + return true; + } + self.is_default_in_proxy_list() } /// Default mode @@ -507,6 +533,7 @@ impl AccessControl { /// Default behavior for hosts that are not configured /// - `true` - Proxied /// - `false` - Bypassed + #[inline] pub fn is_default_in_proxy_list(&self) -> bool { match self.mode { Mode::BlackList => true, @@ -543,7 +570,7 @@ impl AccessControl { } } } - false + !self.is_default_in_proxy_list() } } } @@ -556,7 +583,7 @@ impl AccessControl { self.black_list.check_ip_matched(&addr.ip()) } Mode::WhiteList => { - // Only clients in white_list will be proxied + // Only clients not in white_list will be blocked !self.white_list.check_ip_matched(&addr.ip()) } } @@ -568,22 +595,59 @@ impl AccessControl { /// resolved addresses are checked in the `lookup_outbound_then!` macro pub async fn check_outbound_blocked(&self, context: &Context, outbound: &Address) -> bool { match outbound { - Address::SocketAddress(saddr) => self.outbound_block.check_ip_matched(&saddr.ip()), + Address::SocketAddress(saddr) => self.check_outbound_ip_blocked(&saddr.ip()), Address::DomainNameAddress(host, port) => { - if self.outbound_block.check_host_matched(&Self::convert_to_ascii(host)) { - return true; + let ascii_host = Self::convert_to_ascii(host); + if self.outbound_block.check_host_matched(&ascii_host) { + return true; // Blocked by config + } + if self.outbound_allow.check_host_matched(&ascii_host) { + return false; // Allowed by config + } + + // If no domain name rules matched, + // we need to resolve the hostname to IP addresses + if self.is_outbound_ip_empty() { + // If there are no IP rules, use the default mode + return self.is_outbound_default_blocked(); } if let Ok(vaddr) = context.dns_resolve(host, *port).await { for addr in vaddr { - if self.outbound_block.check_ip_matched(&addr.ip()) { + if self.check_outbound_ip_blocked(&addr.ip()) { return true; } } } - false + self.is_outbound_default_blocked() } } } + + fn check_outbound_ip_blocked(&self, ip: &IpAddr) -> bool { + if self.outbound_block.check_ip_matched(ip) { + // If IP is in outbound_block, it should be blocked + return true; + } + if self.outbound_allow.check_ip_matched(ip) { + // If IP is in outbound_allow, it should be allowed + return false; + } + // If IP is not in any list, check the default mode + self.is_outbound_default_blocked() + } + + #[inline] + fn is_outbound_default_blocked(&self) -> bool { + match self.outbound_mode { + Mode::BlackList => false, + Mode::WhiteList => true, + } + } + + #[inline] + fn is_outbound_ip_empty(&self) -> bool { + self.outbound_block.is_ip_empty() && self.outbound_allow.is_ip_empty() + } }