Coverage Report

Created: 2025-12-28 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hickory-server-0.25.1/src/access.rs
Line
Count
Source
1
use std::net::IpAddr;
2
3
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
4
use prefix_trie::{Prefix, PrefixSet};
5
6
/// Type to evaluate access from a source address for accessing the server.
7
///
8
/// Allowed networks will override denied networks, i.e. if a network is allowed, the deny rules will not be evaluated.
9
/// Allowed networks are processed in the context of denied networks, that is, if there are no denied networks, then
10
///   the allowed list will effectively deny access to anything that's not in the allowed list. On the other hand, if
11
///   denied networks are specified, then allowed networks will only apply if the deny rule matched, but otherwise the
12
///   address will be allowed.
13
#[derive(Default)]
14
pub(crate) struct AccessControl {
15
    ipv4: InnerAccessControl<Ipv4Net>,
16
    ipv6: InnerAccessControl<Ipv6Net>,
17
}
18
19
impl AccessControl {
20
    /// Insert a new network that is denied access to the server
21
0
    pub(crate) fn insert_deny(&mut self, networks: &[IpNet]) {
22
0
        for network in networks {
23
0
            match network {
24
0
                IpNet::V4(v4) => {
25
0
                    self.ipv4.deny.insert(*v4);
26
0
                }
27
0
                IpNet::V6(v6) => {
28
0
                    self.ipv6.deny.insert(*v6);
29
0
                }
30
            }
31
        }
32
0
    }
33
34
    /// Insert a new network that is allowed access to the server
35
0
    pub(crate) fn insert_allow(&mut self, networks: &[IpNet]) {
36
0
        for network in networks {
37
0
            match network {
38
0
                IpNet::V4(v4) => {
39
0
                    self.ipv4.allow.insert(*v4);
40
0
                }
41
0
                IpNet::V6(v6) => {
42
0
                    self.ipv6.allow.insert(*v6);
43
0
                }
44
            }
45
        }
46
0
    }
47
48
    /// Evaluate the IP address against the allowed networks
49
    ///
50
    /// # Arguments
51
    ///
52
    /// * `ip` - source ip address to evaluate
53
    ///
54
    /// # Return
55
    ///
56
    /// Ok if access is granted, Err otherwise
57
    #[must_use]
58
0
    pub(crate) fn allow(&self, ip: IpAddr) -> bool {
59
0
        match ip {
60
0
            IpAddr::V4(v4) => {
61
0
                let v4 = Ipv4Net::from(v4);
62
63
0
                self.ipv4.allow(&v4)
64
            }
65
0
            IpAddr::V6(v6) => {
66
0
                let v6 = Ipv6Net::from(v6);
67
68
0
                self.ipv6.allow(&v6)
69
            }
70
        }
71
0
    }
72
}
73
74
#[derive(Default)]
75
struct InnerAccessControl<I: Prefix> {
76
    allow: PrefixSet<I>,
77
    deny: PrefixSet<I>,
78
}
79
80
impl<I: Prefix> InnerAccessControl<I> {
81
    /// Evaluate the IP address against the allowed networks
82
    ///
83
    /// This allows for generic evaluation over IPv4 or IPv6 address spaces
84
    ///
85
    /// # Arguments
86
    ///
87
    /// * `ip` - source ip address to evaluate
88
    ///
89
    /// # Return
90
    ///
91
    /// Ok if access is granted, Err otherwise
92
    #[must_use]
93
0
    fn allow(&self, ip: &I) -> bool {
94
        // If there are no allows or denies specified, we will always default to allow.
95
        // Allows without denies always translate to deny all except those in the allow list.
96
        // Denies without allows only deny those in the specified deny list.
97
        // If there are both allow and deny lists, then the deny list takes precedent with the allow list
98
        //  overriding the deny if it is more specific.
99
0
        match (self.deny.get_lpm(ip), self.allow.get_lpm(ip)) {
100
0
            (Some(denied), Some(allowed)) => allowed.prefix_len() > denied.prefix_len(),
101
0
            (Some(_denied), None) => false,
102
0
            (None, Some(_allowed)) => true,
103
0
            (None, None) => match (
104
0
                self.deny.iter().next().is_some(),
105
0
                self.allow.iter().next().is_some(),
106
0
            ) {
107
0
                (true, _) => true,      // there are deny entries, but this isn't one
108
0
                (false, true) => false, // there are only allow entries, but this isn't one
109
0
                (false, false) => true, // there are no entries
110
            },
111
        }
112
0
    }
Unexecuted instantiation: <hickory_server::access::InnerAccessControl<ipnet::ipnet::Ipv4Net>>::allow
Unexecuted instantiation: <hickory_server::access::InnerAccessControl<ipnet::ipnet::Ipv6Net>>::allow
113
}
114
115
#[cfg(test)]
116
mod tests {
117
    use super::*;
118
119
    #[test]
120
    fn test_none() {
121
        let access = AccessControl::default();
122
        assert!(access.allow("192.168.1.1".parse().unwrap()));
123
        assert!(access.allow("fd00::1".parse().unwrap()));
124
    }
125
126
    #[test]
127
    fn test_v4() {
128
        let mut access = AccessControl::default();
129
        access.insert_allow(&["192.168.1.0/24".parse().unwrap()]);
130
131
        assert!(access.allow("192.168.1.1".parse().unwrap()));
132
        assert!(access.allow("192.168.1.255".parse().unwrap()));
133
        assert!(!access.allow("192.168.2.1".parse().unwrap()));
134
        assert!(!access.allow("192.168.0.0".parse().unwrap()));
135
    }
136
137
    #[test]
138
    fn test_v6() {
139
        let mut access = AccessControl::default();
140
        access.insert_allow(&["fd00::/120".parse().unwrap()]);
141
142
        assert!(access.allow("fd00::1".parse().unwrap()));
143
        assert!(access.allow("fd00::00ff".parse().unwrap()));
144
        assert!(!access.allow("fd00::ffff".parse().unwrap()));
145
        assert!(!access.allow("fd00::1:1".parse().unwrap()));
146
    }
147
148
    #[test]
149
    fn test_deny_v4() {
150
        let mut access = AccessControl::default();
151
        access.insert_deny(&["192.168.1.0/24".parse().unwrap()]);
152
153
        assert!(!access.allow("192.168.1.1".parse().unwrap()));
154
        assert!(!access.allow("192.168.1.255".parse().unwrap()));
155
        assert!(access.allow("192.168.2.1".parse().unwrap()));
156
        assert!(access.allow("192.168.0.0".parse().unwrap()));
157
    }
158
159
    #[test]
160
    fn test_deny_v6() {
161
        let mut access = AccessControl::default();
162
        access.insert_deny(&["fd00::/120".parse().unwrap()]);
163
164
        assert!(!access.allow("fd00::1".parse().unwrap()));
165
        assert!(!access.allow("fd00::00ff".parse().unwrap()));
166
        assert!(access.allow("fd00::ffff".parse().unwrap()));
167
        assert!(access.allow("fd00::1:1".parse().unwrap()));
168
    }
169
170
    #[test]
171
    fn test_deny_allow_v4() {
172
        let mut access = AccessControl::default();
173
        access.insert_deny(&["192.168.0.0/16".parse().unwrap()]);
174
        access.insert_allow(&["192.168.1.0/24".parse().unwrap()]);
175
176
        assert!(access.allow("192.168.1.1".parse().unwrap()));
177
        assert!(access.allow("192.168.1.255".parse().unwrap()));
178
        assert!(!access.allow("192.168.2.1".parse().unwrap()));
179
        assert!(!access.allow("192.168.0.0".parse().unwrap()));
180
181
        // but all other networks should be allowed
182
        assert!(access.allow("10.0.0.1".parse().unwrap()));
183
    }
184
}