whois_cli/
servers.rs

1use std::env;
2
3pub const IANA_WHOIS_SERVER: &str = "whois.iana.org";
4pub const DEFAULT_WHOIS_SERVER: &str = "whois.ripe.net";
5pub const DEFAULT_WHOIS_PORT: u16 = 43;
6pub const DN42_WHOIS_SERVER: &str = "lantian.pub";
7pub const DN42_WHOIS_PORT: u16 = 43;
8pub const BGPTOOLS_WHOIS_SERVER: &str = "bgp.tools";
9pub const BGPTOOLS_WHOIS_PORT: u16 = 43;
10pub const RADB_WHOIS_SERVER: &str = "whois.radb.net";
11pub const RADB_WHOIS_PORT: u16 = 43;
12
13#[derive(Debug, Clone)]
14pub struct WhoisServer {
15    pub host: String,
16    pub port: u16,
17    pub name: String,
18}
19
20impl WhoisServer {
21    pub fn new(host: impl Into<String>, port: u16, name: impl Into<String>) -> Self {
22        Self {
23            host: host.into(),
24            port,
25            name: name.into(),
26        }
27    }
28
29    pub fn iana() -> Self {
30        Self::new(IANA_WHOIS_SERVER, DEFAULT_WHOIS_PORT, "IANA")
31    }
32
33    pub fn default() -> Self {
34        Self::new(DEFAULT_WHOIS_SERVER, DEFAULT_WHOIS_PORT, "RIPE")
35    }
36
37    pub fn dn42() -> Self {
38        Self::new(DN42_WHOIS_SERVER, DN42_WHOIS_PORT, "DN42")
39    }
40
41    pub fn bgptools() -> Self {
42        Self::new(BGPTOOLS_WHOIS_SERVER, BGPTOOLS_WHOIS_PORT, "BGP.tools")
43    }
44
45    pub fn radb() -> Self {
46        Self::new(RADB_WHOIS_SERVER, RADB_WHOIS_PORT, "RADB")
47    }
48
49    pub fn custom(host: impl Into<String>, port: u16) -> Self {
50        Self::new(host.into(), port, "Custom")
51    }
52
53    pub fn address(&self) -> String {
54        format!("{}:{}", self.host, self.port)
55    }
56}
57
58pub struct ServerSelector;
59
60impl ServerSelector {
61    /// Extract WHOIS server from IANA response
62    pub fn extract_whois_server(response: &str) -> Option<String> {
63        for line in response.lines() {
64            let line = line.trim();
65            
66            // Look for "whois:" field
67            if line.starts_with("whois:") {
68                return Some(line.split_whitespace()
69                    .nth(1)?
70                    .trim()
71                    .to_string());
72            }
73            // Also look for "refer:" field as a fallback
74            if line.starts_with("refer:") {
75                return Some(line.split_whitespace()
76                    .nth(1)?
77                    .trim()
78                    .to_string());
79            }
80        }
81        None
82    }
83
84    /// Get server from environment variable if available
85    pub fn from_env() -> Option<String> {
86        env::var("WHOIS_SERVER").ok()
87    }
88
89    /// Select appropriate server based on query and options
90    pub fn select_server(
91        domain: &str,
92        use_dn42: bool,
93        use_bgptools: bool,
94        explicit_server: Option<&str>,
95        port: u16,
96    ) -> WhoisServer {
97        // Priority: special flags > explicit server > environment > default
98        if use_dn42 || domain.to_uppercase().starts_with("AS42424") {
99            return WhoisServer::dn42();
100        }
101
102        if use_bgptools {
103            return WhoisServer::bgptools();
104        }
105
106        if let Some(server) = explicit_server {
107            return WhoisServer::custom(server, port);
108        }
109
110        if let Some(env_server) = Self::from_env() {
111            return WhoisServer::custom(env_server, port);
112        }
113
114        // Default: use IANA for referral
115        WhoisServer::iana()
116    }
117}