Skip to main content

rws_proxy/traffic/
hosting.rs

1//! Cloud and server hosting networks, from a table compiled into the binary
2//! (the `geo` feature): DB-IP's "IP to ASN Lite" (CC BY 4.0) reduced by
3//! `ci/gen_hosting.py` to the ranges of hosting providers, in
4//! [`geo::Table`](super::geo::Table)'s format with a provider code where
5//! that has a country (~60 KB). A browser User-Agent from one of these is
6//! a program — a link scanner, a preview, a headless crawler — not a
7//! person, so the Traffic page counts it as a bot ([`Kind::Hosting`]).
8//! Consumer VPN and CDN egress (Cloudflare WARP, iCloud Private Relay, VPN
9//! services) is left out: people browse from it.
10//!
11//! [`Kind::Hosting`]: super::Kind::Hosting
12
13use std::net::IpAddr;
14use std::sync::OnceLock;
15
16use super::geo::Table;
17
18#[cfg(feature = "geo")]
19static BUNDLED: &[u8] = include_bytes!("hosting.bin");
20
21/// The provider codes `ci/gen_hosting.py` writes, and their names.
22const PROVIDERS: [(&[u8; 2], &str); 20] = [
23    (b"AW", "AWS"),
24    (b"GC", "Google Cloud"),
25    (b"AZ", "Azure"),
26    (b"OR", "Oracle Cloud"),
27    (b"AL", "Alibaba Cloud"),
28    (b"TC", "Tencent Cloud"),
29    (b"HW", "Huawei Cloud"),
30    (b"DO", "DigitalOcean"),
31    (b"LI", "Linode"),
32    (b"VU", "Vultr"),
33    (b"OV", "OVHcloud"),
34    (b"HZ", "Hetzner"),
35    (b"CB", "Contabo"),
36    (b"SC", "Scaleway"),
37    (b"LW", "Leaseweb"),
38    (b"IO", "IONOS"),
39    (b"HG", "Hostinger"),
40    (b"IB", "IBM Cloud"),
41    (b"RS", "Rackspace"),
42    (b"DC", "other hosting"),
43];
44
45/// The table, decoded once; `None` without the `geo` feature.
46pub fn table() -> Option<&'static Table> {
47    static TABLE: OnceLock<Option<Table>> = OnceLock::new();
48    TABLE.get_or_init(bundled).as_ref()
49}
50
51#[cfg(feature = "geo")]
52fn bundled() -> Option<Table> {
53    match Table::decode(BUNDLED) {
54        Ok(table) => Some(table),
55        Err(e) => {
56            eprintln!("traffic: built-in hosting table: {e}");
57            None
58        }
59    }
60}
61
62#[cfg(not(feature = "geo"))]
63fn bundled() -> Option<Table> {
64    None
65}
66
67/// The hosting provider `ip` belongs to (`"AWS"`, `"other hosting"`);
68/// `None` for other networks, or without the table.
69pub fn provider(ip: IpAddr) -> Option<&'static str> {
70    provider_in(table()?, ip)
71}
72
73/// [`provider`] in a given table.
74#[doc(hidden)]
75pub fn provider_in(table: &Table, ip: IpAddr) -> Option<&'static str> {
76    let code = table.country(ip)?;
77    Some(PROVIDERS.iter().find(|(c, _)| **c == code).map_or("other hosting", |(_, name)| name))
78}