shadowsocks_service/acl/
mod.rs

1//! Access Control List (ACL) for shadowsocks
2//!
3//! This is for advance controlling server behaviors in both local and proxy servers.
4
5use std::{
6    borrow::Cow,
7    collections::HashSet,
8    fmt,
9    fs::File,
10    io::{self, BufRead, BufReader, Error},
11    net::{IpAddr, SocketAddr},
12    path::{Path, PathBuf},
13    str,
14    sync::LazyLock,
15};
16
17use ipnet::{IpNet, Ipv4Net, Ipv6Net};
18use iprange::IpRange;
19use log::{trace, warn};
20use regex::bytes::{Regex, RegexBuilder, RegexSet, RegexSetBuilder};
21
22use shadowsocks::{context::Context, relay::socks5::Address};
23
24use self::sub_domains_tree::SubDomainsTree;
25
26mod sub_domains_tree;
27
28/// Strategy mode that ACL is running
29#[derive(Debug, Copy, Clone, Eq, PartialEq)]
30pub enum Mode {
31    /// BlackList mode, rejects or bypasses all requests by default
32    BlackList,
33    /// WhiteList mode, accepts or proxies all requests by default
34    WhiteList,
35}
36
37#[derive(Clone)]
38struct Rules {
39    ipv4: IpRange<Ipv4Net>,
40    ipv6: IpRange<Ipv6Net>,
41    rule_regex: RegexSet,
42    rule_set: HashSet<String>,
43    rule_tree: SubDomainsTree,
44}
45
46impl fmt::Debug for Rules {
47    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48        write!(
49            f,
50            "Rules {{ ipv4: {:?}, ipv6: {:?}, rule_regex: [",
51            self.ipv4, self.ipv6
52        )?;
53
54        let max_len = 2;
55        let has_more = self.rule_regex.len() > max_len;
56
57        for (idx, r) in self.rule_regex.patterns().iter().take(max_len).enumerate() {
58            if idx > 0 {
59                f.write_str(", ")?;
60            }
61            f.write_str(r)?;
62        }
63
64        if has_more {
65            f.write_str(", ...")?;
66        }
67
68        write!(f, "], rule_set: [")?;
69
70        let has_more = self.rule_set.len() > max_len;
71        for (idx, r) in self.rule_set.iter().take(max_len).enumerate() {
72            if idx > 0 {
73                f.write_str(", ")?;
74            }
75            f.write_str(r)?;
76        }
77
78        if has_more {
79            f.write_str(", ...")?;
80        }
81
82        write!(f, "], rule_tree: {:?} }}", self.rule_tree)
83    }
84}
85
86impl Rules {
87    /// Create a new rule
88    fn new(
89        mut ipv4: IpRange<Ipv4Net>,
90        mut ipv6: IpRange<Ipv6Net>,
91        rule_regex: RegexSet,
92        rule_set: HashSet<String>,
93        rule_tree: SubDomainsTree,
94    ) -> Self {
95        // Optimization, merging networks
96        ipv4.simplify();
97        ipv6.simplify();
98
99        Self {
100            ipv4,
101            ipv6,
102            rule_regex,
103            rule_set,
104            rule_tree,
105        }
106    }
107
108    /// Check if the specified address matches these rules
109    #[allow(dead_code)]
110    fn check_address_matched(&self, addr: &Address) -> bool {
111        match *addr {
112            Address::SocketAddress(ref saddr) => self.check_ip_matched(&saddr.ip()),
113            Address::DomainNameAddress(ref domain, ..) => self.check_host_matched(domain),
114        }
115    }
116
117    /// Check if the specified address matches any rules
118    fn check_ip_matched(&self, addr: &IpAddr) -> bool {
119        match addr {
120            IpAddr::V4(v4) => {
121                if self.ipv4.contains(v4) {
122                    return true;
123                }
124
125                let mapped_ipv6 = v4.to_ipv6_mapped();
126                self.ipv6.contains(&mapped_ipv6)
127            }
128            IpAddr::V6(v6) => {
129                if self.ipv6.contains(v6) {
130                    return true;
131                }
132
133                if let Some(mapped_ipv4) = v6.to_ipv4_mapped() {
134                    return self.ipv4.contains(&mapped_ipv4);
135                }
136
137                false
138            }
139        }
140    }
141
142    /// Check if the specified ASCII host matches any rules
143    fn check_host_matched(&self, host: &str) -> bool {
144        let host = host.trim_end_matches('.'); // FQDN, removes the last `.`
145        self.rule_set.contains(host) || self.rule_tree.contains(host) || self.rule_regex.is_match(host.as_bytes())
146    }
147
148    /// Check if there are no rules for IP addresses
149    fn is_ip_empty(&self) -> bool {
150        self.ipv4.is_empty() && self.ipv6.is_empty()
151    }
152
153    /// Check if there are no rules for domain names
154    fn is_host_empty(&self) -> bool {
155        self.rule_set.is_empty() && self.rule_tree.is_empty() && self.rule_regex.is_empty()
156    }
157}
158
159struct ParsingRules {
160    name: &'static str,
161    ipv4: IpRange<Ipv4Net>,
162    ipv6: IpRange<Ipv6Net>,
163    rules_regex: Vec<String>,
164    rules_set: HashSet<String>,
165    rules_tree: SubDomainsTree,
166}
167
168impl ParsingRules {
169    fn new(name: &'static str) -> Self {
170        Self {
171            name,
172            ipv4: IpRange::new(),
173            ipv6: IpRange::new(),
174            rules_regex: Vec::new(),
175            rules_set: HashSet::new(),
176            rules_tree: SubDomainsTree::new(),
177        }
178    }
179
180    fn add_ipv4_rule(&mut self, rule: impl Into<Ipv4Net>) {
181        let rule = rule.into();
182        trace!("IPV4-RULE {}", rule);
183        self.ipv4.add(rule);
184    }
185
186    fn add_ipv6_rule(&mut self, rule: impl Into<Ipv6Net>) {
187        let rule = rule.into();
188        trace!("IPV6-RULE {}", rule);
189        self.ipv6.add(rule);
190    }
191
192    fn add_regex_rule(&mut self, mut rule: String) {
193        static TREE_SET_RULE_EQUIV: LazyLock<Regex> = LazyLock::new(|| {
194            RegexBuilder::new(
195                r#"^(?:(?:\((?:\?:)?\^\|\\\.\)|(?:\^\.(?:\+|\*))?\\\.)((?:[\w-]+(?:\\\.)?)+)|\^((?:[\w-]+(?:\\\.)?)+))\$?$"#,
196            )
197            .unicode(false)
198            .build()
199            .unwrap()
200        });
201
202        if let Some(caps) = TREE_SET_RULE_EQUIV.captures(rule.as_bytes()) {
203            if let Some(tree_rule) = caps.get(1) {
204                if let Ok(tree_rule) = str::from_utf8(tree_rule.as_bytes()) {
205                    let tree_rule = tree_rule.replace("\\.", ".");
206                    if self.add_tree_rule_inner(&tree_rule).is_ok() {
207                        trace!("REGEX-RULE {} => TREE-RULE {}", rule, tree_rule);
208                        return;
209                    }
210                }
211            } else if let Some(set_rule) = caps.get(2)
212                && let Ok(set_rule) = str::from_utf8(set_rule.as_bytes()) {
213                    let set_rule = set_rule.replace("\\.", ".");
214                    if self.add_set_rule_inner(&set_rule).is_ok() {
215                        trace!("REGEX-RULE {} => SET-RULE {}", rule, set_rule);
216                        return;
217                    }
218                }
219        }
220
221        trace!("REGEX-RULE {}", rule);
222
223        rule.make_ascii_lowercase();
224
225        // Handle it as a normal REGEX
226        // FIXME: If this line is not a valid regex, how can we know without actually compile it?
227        self.rules_regex.push(rule);
228    }
229
230    #[inline]
231    fn add_set_rule(&mut self, rule: &str) -> io::Result<()> {
232        trace!("SET-RULE {}", rule);
233        self.add_set_rule_inner(rule)
234    }
235
236    fn add_set_rule_inner(&mut self, rule: &str) -> io::Result<()> {
237        self.rules_set.insert(self.check_is_ascii(rule)?.to_ascii_lowercase());
238        Ok(())
239    }
240
241    #[inline]
242    fn add_tree_rule(&mut self, rule: &str) -> io::Result<()> {
243        trace!("TREE-RULE {}", rule);
244        self.add_tree_rule_inner(rule)
245    }
246
247    fn add_tree_rule_inner(&mut self, rule: &str) -> io::Result<()> {
248        // SubDomainsTree do lowercase conversion inside insert
249        self.rules_tree.insert(self.check_is_ascii(rule)?);
250        Ok(())
251    }
252
253    fn check_is_ascii<'a>(&self, str: &'a str) -> io::Result<&'a str> {
254        if str.is_ascii() {
255            // Remove the last `.` of FQDN
256            Ok(str.trim_end_matches('.'))
257        } else {
258            Err(Error::other(format!(
259                "{} parsing error: Unicode not allowed here `{}`",
260                self.name, str
261            )))
262        }
263    }
264
265    fn compile_regex(name: &'static str, regex_rules: Vec<String>) -> io::Result<RegexSet> {
266        const REGEX_SIZE_LIMIT: usize = usize::MAX;
267        RegexSetBuilder::new(regex_rules)
268            .size_limit(REGEX_SIZE_LIMIT)
269            .unicode(false)
270            .build()
271            .map_err(|err| Error::other(format!("{name} regex error: {err}")))
272    }
273
274    fn into_rules(self) -> io::Result<Rules> {
275        Ok(Rules::new(
276            self.ipv4,
277            self.ipv6,
278            Self::compile_regex(self.name, self.rules_regex)?,
279            self.rules_set,
280            self.rules_tree,
281        ))
282    }
283}
284
285/// ACL rules
286///
287/// ## Sections
288///
289/// ACL File is formatted in sections, each section has a name with surrounded by brackets `[` and `]`
290/// followed by Rules line by line.
291///
292/// ```plain
293/// [SECTION-1]
294/// RULE-1
295/// RULE-2
296/// RULE-3
297///
298/// [SECTION-2]
299/// RULE-1
300/// RULE-2
301/// RULE-3
302/// ```
303///
304/// Available sections are
305///
306/// - For local servers (`sslocal`, `ssredir`, ...)
307///     * `[bypass_all]` - ACL runs in `WhiteList` mode.
308///     * `[proxy_all]` - ACL runs in `BlackList` mode.
309///     * `[bypass_list]` - Rules for connecting directly
310///     * `[proxy_list]` - Rules for connecting through proxies
311/// - For remote servers (`ssserver`)
312///     * `[reject_all]` - ACL runs in `WhiteList` mode.
313///     * `[accept_all]` - ACL runs in `BlackList` mode.
314///     * `[black_list]` - Rules for rejecting
315///     * `[white_list]` - Rules for allowing
316///     * `[outbound_block_all]` - ACL runs in `WhiteList` mode for outbound addresses.
317///     * `[outbound_allow_all]` - ACL runs in `BlackList` mode for outbound addresses.
318///     * `[outbound_block_list]` - Rules for blocking outbound addresses.
319///     * `[outbound_allow_list]` - Rules for allowing outbound addresses.
320///
321/// ## Mode
322///
323/// Mode is the default ACL strategy for those addresses that are not in configuration file.
324///
325/// - `WhiteList` - Bypasses / Rejects all addresses except those in `[proxy_list]` or `[white_list]`
326/// - `BlackList` - Proxies / Accepts all addresses except those in `[bypass_list]` or `[black_list]`
327///
328/// ## Rules
329///
330/// Rules can be either
331///
332/// - CIDR form network addresses, like `10.9.0.32/16`
333/// - IP addresses, like `127.0.0.1` or `::1`
334/// - Regular Expression for matching hosts, like `(^|\.)gmail\.com$`
335/// - Domain with preceding `|` for exact matching, like `|google.com`
336/// - Domain with preceding `||` for matching with subdomains, like `||google.com`
337#[derive(Debug, Clone)]
338pub struct AccessControl {
339    outbound_block: Rules,
340    outbound_allow: Rules,
341    black_list: Rules,
342    white_list: Rules,
343    mode: Mode,
344    outbound_mode: Mode,
345    file_path: PathBuf,
346}
347
348impl AccessControl {
349    /// Load ACL rules from a file
350    pub fn load_from_file<P: AsRef<Path>>(p: P) -> io::Result<Self> {
351        trace!("ACL loading from {:?}", p.as_ref());
352
353        let file_path_ref = p.as_ref();
354        let file_path = file_path_ref.to_path_buf();
355
356        let fp = File::open(file_path_ref)?;
357        let r = BufReader::new(fp);
358
359        let mut mode = Mode::BlackList;
360        let mut outbound_mode = Mode::BlackList;
361
362        let mut outbound_block = ParsingRules::new("[outbound_block_list]");
363        let mut outbound_allow = ParsingRules::new("[outbound_allow_list]");
364        let mut bypass = ParsingRules::new("[black_list] or [bypass_list]");
365        let mut proxy = ParsingRules::new("[white_list] or [proxy_list]");
366        let mut curr = &mut bypass;
367
368        trace!("ACL parsing start from mode {:?} and black_list / bypass_list", mode);
369
370        for line in r.lines() {
371            let line = line?;
372            if line.is_empty() {
373                continue;
374            }
375
376            // Comments
377            if line.starts_with('#') {
378                continue;
379            }
380
381            let line = line.trim();
382
383            if !line.is_ascii() {
384                warn!("ACL rule {} containing non-ASCII characters, skipped", line);
385                continue;
386            }
387
388            if let Some(rule) = line.strip_prefix("||") {
389                curr.add_tree_rule(rule)?;
390                continue;
391            }
392
393            if let Some(rule) = line.strip_prefix('|') {
394                curr.add_set_rule(rule)?;
395                continue;
396            }
397
398            match line {
399                "[reject_all]" | "[bypass_all]" => {
400                    mode = Mode::WhiteList;
401                    trace!("switch to mode {:?}", mode);
402                }
403                "[accept_all]" | "[proxy_all]" => {
404                    mode = Mode::BlackList;
405                    trace!("switch to mode {:?}", mode);
406                }
407                "[outbound_block_all]" => {
408                    outbound_mode = Mode::WhiteList;
409                    trace!("switch to outbound_mode {:?}", outbound_mode);
410                }
411                "[outbound_allow_all]" => {
412                    outbound_mode = Mode::BlackList;
413                    trace!("switch to outbound_mode {:?}", outbound_mode);
414                }
415                "[outbound_block_list]" => {
416                    curr = &mut outbound_block;
417                    trace!("loading outbound_block_list");
418                }
419                "[outbound_allow_list]" => {
420                    curr = &mut outbound_allow;
421                    trace!("loading outbound_allow_list");
422                }
423                "[black_list]" | "[bypass_list]" => {
424                    curr = &mut bypass;
425                    trace!("loading black_list / bypass_list");
426                }
427                "[white_list]" | "[proxy_list]" => {
428                    curr = &mut proxy;
429                    trace!("loading white_list / proxy_list");
430                }
431                _ => {
432                    match line.parse::<IpNet>() {
433                        Ok(IpNet::V4(v4)) => {
434                            curr.add_ipv4_rule(v4);
435                        }
436                        Ok(IpNet::V6(v6)) => {
437                            curr.add_ipv6_rule(v6);
438                        }
439                        Err(..) => {
440                            // Maybe it is a pure IpAddr
441                            match line.parse::<IpAddr>() {
442                                Ok(IpAddr::V4(v4)) => {
443                                    curr.add_ipv4_rule(v4);
444                                }
445                                Ok(IpAddr::V6(v6)) => {
446                                    curr.add_ipv6_rule(v6);
447                                }
448                                Err(..) => {
449                                    curr.add_regex_rule(line.to_owned());
450                                }
451                            }
452                        }
453                    }
454                }
455            }
456        }
457
458        Ok(Self {
459            outbound_block: outbound_block.into_rules()?,
460            outbound_allow: outbound_allow.into_rules()?,
461            black_list: bypass.into_rules()?,
462            white_list: proxy.into_rules()?,
463            mode,
464            outbound_mode,
465            file_path,
466        })
467    }
468
469    /// Get ACL file path
470    pub fn file_path(&self) -> &Path {
471        &self.file_path
472    }
473
474    /// Check if domain name is in proxy_list.
475    /// If so, it should be resolved from remote (for Android's DNS relay)
476    ///
477    /// Return
478    /// - `Some(true)` if `host` is in `white_list` (should be proxied)
479    /// - `Some(false)` if `host` is in `black_list` (should be bypassed)
480    /// - `None` if `host` doesn't match any rules
481    pub fn check_host_in_proxy_list(&self, host: &str) -> Option<bool> {
482        let host = Self::convert_to_ascii(host);
483        self.check_ascii_host_in_proxy_list(&host)
484    }
485
486    /// Check if ASCII domain name is in proxy_list.
487    /// If so, it should be resolved from remote (for Android's DNS relay)
488    ///
489    /// Return
490    /// - `Some(true)` if `host` is in `white_list` (should be proxied)
491    /// - `Some(false)` if `host` is in `black_list` (should be bypassed)
492    /// - `None` if `host` doesn't match any rules
493    pub fn check_ascii_host_in_proxy_list(&self, host: &str) -> Option<bool> {
494        // Addresses in proxy_list will be proxied
495        if self.white_list.check_host_matched(host) {
496            return Some(true);
497        }
498        // Addresses in bypass_list will be bypassed
499        if self.black_list.check_host_matched(host) {
500            return Some(false);
501        }
502        None
503    }
504
505    /// If there are no IP rules
506    #[inline]
507    pub fn is_ip_empty(&self) -> bool {
508        self.black_list.is_ip_empty() && self.white_list.is_ip_empty()
509    }
510
511    /// If there are no domain name rules
512    #[inline]
513    pub fn is_host_empty(&self) -> bool {
514        self.black_list.is_host_empty() && self.white_list.is_host_empty()
515    }
516
517    /// Check if `IpAddr` should be proxied
518    pub fn check_ip_in_proxy_list(&self, ip: &IpAddr) -> bool {
519        if self.black_list.check_ip_matched(ip) {
520            // If IP is in black_list, it should be bypassed
521            return false;
522        }
523        if self.white_list.check_ip_matched(ip) {
524            // If IP is in white_list, it should be proxied
525            return true;
526        }
527        self.is_default_in_proxy_list()
528    }
529
530    /// Default mode
531    ///
532    /// Default behavior for hosts that are not configured
533    /// - `true` - Proxied
534    /// - `false` - Bypassed
535    #[inline]
536    pub fn is_default_in_proxy_list(&self) -> bool {
537        match self.mode {
538            Mode::BlackList => true,
539            Mode::WhiteList => false,
540        }
541    }
542
543    /// Returns the ASCII representation a domain name,
544    /// if conversion fails returns original string
545    fn convert_to_ascii(host: &str) -> Cow<'_, str> {
546        idna::domain_to_ascii(host)
547            .map(From::from)
548            .unwrap_or_else(|_| host.into())
549    }
550
551    /// Check if target address should be bypassed (for client)
552    ///
553    /// This function may perform a DNS resolution
554    pub async fn check_target_bypassed(&self, context: &Context, addr: &Address) -> bool {
555        match *addr {
556            Address::SocketAddress(ref addr) => !self.check_ip_in_proxy_list(&addr.ip()),
557            // Resolve hostname and check the list
558            Address::DomainNameAddress(ref host, port) => {
559                if let Some(value) = self.check_host_in_proxy_list(host) {
560                    return !value;
561                }
562
563                // If mode is BlackList, host is proxied by default. If it has any resolved IPs in black_list, then it should be bypassed.
564                // If mode is WhiteList, host is bypassed by default. If it has any resolved IPs in white_list, then it should be proxied.
565                let (check_list, bypass_if_matched) = match self.mode {
566                    Mode::BlackList => (&self.black_list, true),
567                    Mode::WhiteList => (&self.white_list, false),
568                };
569
570                if check_list.is_ip_empty() {
571                    return !self.is_default_in_proxy_list();
572                }
573
574                if let Ok(vaddr) = context.dns_resolve(host, port).await {
575                    for addr in vaddr {
576                        let ip = addr.ip();
577                        if check_list.check_ip_matched(&ip) {
578                            return bypass_if_matched;
579                        }
580                    }
581                }
582
583                !self.is_default_in_proxy_list()
584            }
585        }
586    }
587
588    /// Check if client address should be blocked (for server)
589    pub fn check_client_blocked(&self, addr: &SocketAddr) -> bool {
590        match self.mode {
591            Mode::BlackList => {
592                // Only clients in black_list will be blocked
593                self.black_list.check_ip_matched(&addr.ip())
594            }
595            Mode::WhiteList => {
596                // Only clients not in white_list will be blocked
597                !self.white_list.check_ip_matched(&addr.ip())
598            }
599        }
600    }
601
602    /// Check if outbound address is blocked (for server)
603    ///
604    /// NOTE: `Address::DomainName` is only validated by regex rules,
605    ///       resolved addresses are checked in the `lookup_outbound_then!` macro
606    pub async fn check_outbound_blocked(&self, context: &Context, outbound: &Address) -> bool {
607        match outbound {
608            Address::SocketAddress(saddr) => self.check_outbound_ip_blocked(&saddr.ip()),
609            Address::DomainNameAddress(host, port) => {
610                let ascii_host = Self::convert_to_ascii(host);
611                if self.outbound_block.check_host_matched(&ascii_host) {
612                    return true; // Blocked by config
613                }
614                if self.outbound_allow.check_host_matched(&ascii_host) {
615                    return false; // Allowed by config
616                }
617
618                // If no domain name rules matched,
619                // we need to resolve the hostname to IP addresses
620
621                // If mode is BlackList, host is allowed by default. If any of its' resolved IPs in outboud_block, then it is blocked.
622                // If mode is WhiteList, host is blocked by default. If any of its' resolved IPs in outbound_allow, then it is allowed.
623                let (check_rule, block_if_matched) = match self.outbound_mode {
624                    Mode::BlackList => (&self.outbound_block, true),
625                    Mode::WhiteList => (&self.outbound_allow, false),
626                };
627
628                if check_rule.is_ip_empty() {
629                    // If there are no IP rules, use the default mode
630                    return self.is_outbound_default_blocked();
631                }
632
633                if let Ok(vaddr) = context.dns_resolve(host, *port).await {
634                    for addr in vaddr {
635                        let ip = addr.ip();
636                        if check_rule.check_ip_matched(&ip) {
637                            return block_if_matched;
638                        }
639                    }
640                }
641
642                self.is_outbound_default_blocked()
643            }
644        }
645    }
646
647    fn check_outbound_ip_blocked(&self, ip: &IpAddr) -> bool {
648        if self.outbound_block.check_ip_matched(ip) {
649            // If IP is in outbound_block, it should be blocked
650            return true;
651        }
652        if self.outbound_allow.check_ip_matched(ip) {
653            // If IP is in outbound_allow, it should be allowed
654            return false;
655        }
656        // If IP is not in any list, check the default mode
657        self.is_outbound_default_blocked()
658    }
659
660    #[inline]
661    fn is_outbound_default_blocked(&self) -> bool {
662        match self.outbound_mode {
663            Mode::BlackList => false,
664            Mode::WhiteList => true,
665        }
666    }
667}