Skip to main content

netray_common/
ip_filter.rs

1use std::net::{IpAddr, Ipv4Addr};
2
3/// Returns true if the IP address should be blocked for outbound requests
4/// (SSRF prevention). Covers all address families and special ranges.
5pub fn is_blocked_ip(ip: IpAddr) -> bool {
6    match ip {
7        IpAddr::V4(v4) => is_blocked_v4(v4),
8        IpAddr::V6(v6) => {
9            // IPv4-mapped: ::ffff:x.x.x.x — delegate to IPv4 check
10            if let Some(v4) = v6.to_ipv4_mapped() {
11                return is_blocked_v4(v4);
12            }
13            is_blocked_v6(v6)
14        }
15    }
16}
17
18fn is_blocked_v4(v4: Ipv4Addr) -> bool {
19    if v4.is_loopback() {
20        return true;
21    }
22    if v4.is_unspecified() {
23        return true;
24    }
25    if v4.is_multicast() {
26        return true;
27    }
28    // RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
29    if v4.is_private() {
30        return true;
31    }
32    // Link-local: 169.254.0.0/16
33    if v4.is_link_local() {
34        return true;
35    }
36    // CGNAT: 100.64.0.0/10 (RFC 6598)
37    let o = v4.octets();
38    if o[0] == 100 && (o[1] & 0xC0) == 64 {
39        return true;
40    }
41    // Documentation: 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 (RFC 5737)
42    if (o[0] == 192 && o[1] == 0 && o[2] == 2)
43        || (o[0] == 198 && o[1] == 51 && o[2] == 100)
44        || (o[0] == 203 && o[1] == 0 && o[2] == 113)
45    {
46        return true;
47    }
48    false
49}
50
51fn is_blocked_v6(v6: std::net::Ipv6Addr) -> bool {
52    if v6.is_loopback() {
53        return true;
54    }
55    if v6.is_unspecified() {
56        return true;
57    }
58    if v6.is_multicast() {
59        return true;
60    }
61    let segs = v6.segments();
62    // Link-local: fe80::/10
63    if (segs[0] & 0xFFC0) == 0xFE80 {
64        return true;
65    }
66    // ULA: fc00::/7
67    if (segs[0] & 0xFE00) == 0xFC00 {
68        return true;
69    }
70    // Documentation: 2001:db8::/32
71    if segs[0] == 0x2001 && segs[1] == 0x0DB8 {
72        return true;
73    }
74    // 6to4: 2002::/16 — check embedded IPv4
75    if segs[0] == 0x2002 {
76        let embedded = Ipv4Addr::new(
77            (segs[1] >> 8) as u8,
78            (segs[1] & 0xFF) as u8,
79            (segs[2] >> 8) as u8,
80            (segs[2] & 0xFF) as u8,
81        );
82        return is_blocked_v4(embedded);
83    }
84    // NAT64 well-known prefix: 64:ff9b::/96
85    if segs[0] == 0x0064
86        && segs[1] == 0xFF9B
87        && segs[2] == 0
88        && segs[3] == 0
89        && segs[4] == 0
90        && segs[5] == 0
91    {
92        return true;
93    }
94    false
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use std::net::{Ipv4Addr, Ipv6Addr};
101
102    // ---- loopback ----
103
104    #[test]
105    fn blocks_ipv4_loopback() {
106        assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::LOCALHOST)));
107        assert!(is_blocked_ip("127.0.0.1".parse().unwrap()));
108        assert!(is_blocked_ip("127.255.255.255".parse().unwrap()));
109    }
110
111    #[test]
112    fn blocks_ipv6_loopback() {
113        assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::LOCALHOST)));
114        assert!(is_blocked_ip("::1".parse().unwrap()));
115    }
116
117    // ---- unspecified ----
118
119    #[test]
120    fn blocks_ipv4_unspecified() {
121        assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED)));
122        assert!(is_blocked_ip("0.0.0.0".parse().unwrap()));
123    }
124
125    #[test]
126    fn blocks_ipv6_unspecified() {
127        assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::UNSPECIFIED)));
128        assert!(is_blocked_ip("::".parse().unwrap()));
129    }
130
131    // ---- multicast ----
132
133    #[test]
134    fn blocks_ipv4_multicast() {
135        assert!(is_blocked_ip("224.0.0.0".parse().unwrap()));
136        assert!(is_blocked_ip("224.0.0.1".parse().unwrap()));
137        assert!(is_blocked_ip("239.255.255.255".parse().unwrap()));
138    }
139
140    #[test]
141    fn blocks_ipv6_multicast() {
142        assert!(is_blocked_ip("ff02::1".parse().unwrap()));
143        assert!(is_blocked_ip("ff00::".parse().unwrap()));
144    }
145
146    // ---- RFC 1918 ----
147
148    #[test]
149    fn blocks_rfc1918_10() {
150        assert!(is_blocked_ip("10.0.0.0".parse().unwrap()));
151        assert!(is_blocked_ip("10.0.0.1".parse().unwrap()));
152        assert!(is_blocked_ip("10.255.255.255".parse().unwrap()));
153    }
154
155    #[test]
156    fn blocks_rfc1918_172_16() {
157        assert!(is_blocked_ip("172.16.0.0".parse().unwrap()));
158        assert!(is_blocked_ip("172.16.0.1".parse().unwrap()));
159        assert!(is_blocked_ip("172.31.255.255".parse().unwrap()));
160    }
161
162    #[test]
163    fn blocks_rfc1918_192_168() {
164        assert!(is_blocked_ip("192.168.0.0".parse().unwrap()));
165        assert!(is_blocked_ip("192.168.1.1".parse().unwrap()));
166        assert!(is_blocked_ip("192.168.255.255".parse().unwrap()));
167    }
168
169    // ---- link-local ----
170
171    #[test]
172    fn blocks_ipv4_link_local() {
173        assert!(is_blocked_ip("169.254.0.0".parse().unwrap()));
174        assert!(is_blocked_ip("169.254.1.1".parse().unwrap()));
175        assert!(is_blocked_ip("169.254.255.255".parse().unwrap()));
176    }
177
178    #[test]
179    fn blocks_ipv6_link_local() {
180        assert!(is_blocked_ip("fe80::1".parse().unwrap()));
181        assert!(is_blocked_ip("febf::ffff".parse().unwrap()));
182    }
183
184    // ---- CGNAT ----
185
186    #[test]
187    fn blocks_cgnat() {
188        assert!(is_blocked_ip("100.64.0.0".parse().unwrap()));
189        assert!(is_blocked_ip("100.64.0.1".parse().unwrap()));
190        assert!(is_blocked_ip("100.127.255.255".parse().unwrap()));
191    }
192
193    #[test]
194    fn allows_100_outside_cgnat() {
195        assert!(!is_blocked_ip("100.63.255.255".parse().unwrap()));
196        assert!(!is_blocked_ip("100.128.0.0".parse().unwrap()));
197    }
198
199    // ---- documentation ----
200
201    #[test]
202    fn blocks_documentation_ipv4() {
203        assert!(is_blocked_ip("192.0.2.0".parse().unwrap()));
204        assert!(is_blocked_ip("192.0.2.1".parse().unwrap()));
205        assert!(is_blocked_ip("198.51.100.0".parse().unwrap()));
206        assert!(is_blocked_ip("198.51.100.1".parse().unwrap()));
207        assert!(is_blocked_ip("203.0.113.0".parse().unwrap()));
208        assert!(is_blocked_ip("203.0.113.1".parse().unwrap()));
209    }
210
211    #[test]
212    fn blocks_documentation_ipv6() {
213        assert!(is_blocked_ip("2001:db8::1".parse().unwrap()));
214        assert!(is_blocked_ip("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff".parse().unwrap()));
215    }
216
217    // ---- ULA ----
218
219    #[test]
220    fn blocks_ipv6_ula() {
221        assert!(is_blocked_ip("fc00::1".parse().unwrap()));
222        assert!(is_blocked_ip("fd00::1".parse().unwrap()));
223        assert!(is_blocked_ip("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff".parse().unwrap()));
224    }
225
226    // ---- IPv4-mapped IPv6 ----
227
228    #[test]
229    fn blocks_ipv4_mapped_private() {
230        assert!(is_blocked_ip("::ffff:10.0.0.1".parse().unwrap()));
231        assert!(is_blocked_ip("::ffff:192.168.1.1".parse().unwrap()));
232    }
233
234    #[test]
235    fn blocks_ipv4_mapped_loopback() {
236        assert!(is_blocked_ip("::ffff:127.0.0.1".parse().unwrap()));
237    }
238
239    #[test]
240    fn blocks_ipv4_mapped_cgnat() {
241        assert!(is_blocked_ip("::ffff:100.64.0.1".parse().unwrap()));
242    }
243
244    #[test]
245    fn allows_ipv4_mapped_public() {
246        assert!(!is_blocked_ip("::ffff:1.1.1.1".parse().unwrap()));
247    }
248
249    // ---- 6to4 ----
250
251    #[test]
252    fn blocks_6to4_private() {
253        // 2002:c0a8:0101:: embeds 192.168.1.1
254        assert!(is_blocked_ip("2002:c0a8:0101::".parse().unwrap()));
255    }
256
257    #[test]
258    fn blocks_6to4_loopback() {
259        // 2002:7f00:0001:: embeds 127.0.0.1
260        assert!(is_blocked_ip("2002:7f00:0001::".parse().unwrap()));
261    }
262
263    #[test]
264    fn allows_6to4_public() {
265        // 2002:0101:0101:: embeds 1.1.1.1
266        assert!(!is_blocked_ip("2002:0101:0101::".parse().unwrap()));
267    }
268
269    // ---- NAT64 ----
270
271    #[test]
272    fn blocks_nat64_prefix() {
273        assert!(is_blocked_ip("64:ff9b::".parse().unwrap()));
274        assert!(is_blocked_ip("64:ff9b::1".parse().unwrap()));
275        assert!(is_blocked_ip("64:ff9b::7f00:1".parse().unwrap()));
276    }
277
278    // ---- public IPs ----
279
280    #[test]
281    fn allows_public_ipv4() {
282        assert!(!is_blocked_ip("1.1.1.1".parse().unwrap()));
283        assert!(!is_blocked_ip("8.8.8.8".parse().unwrap()));
284        assert!(!is_blocked_ip("9.9.9.9".parse().unwrap()));
285    }
286
287    #[test]
288    fn allows_public_ipv6() {
289        assert!(!is_blocked_ip("2001:4860:4860::8888".parse().unwrap()));
290        assert!(!is_blocked_ip("2606:4700:4700::1111".parse().unwrap()));
291    }
292
293    // ---- boundary tests ----
294
295    #[test]
296    fn boundary_172_rfc1918() {
297        assert!(is_blocked_ip("172.31.255.255".parse().unwrap()));
298        assert!(!is_blocked_ip("172.32.0.0".parse().unwrap()));
299        assert!(!is_blocked_ip("172.15.255.255".parse().unwrap()));
300    }
301
302    #[test]
303    fn boundary_loopback_v4() {
304        assert!(is_blocked_ip("127.0.0.0".parse().unwrap()));
305        assert!(!is_blocked_ip("126.255.255.255".parse().unwrap()));
306        assert!(!is_blocked_ip("128.0.0.0".parse().unwrap()));
307    }
308
309    #[test]
310    fn boundary_multicast_v4() {
311        assert!(is_blocked_ip("224.0.0.0".parse().unwrap()));
312        assert!(!is_blocked_ip("223.255.255.255".parse().unwrap()));
313    }
314}