gethostid_rs/
lib.rs

1use local_ip_address::list_afinet_netifas;
2use std::fmt::Write;
3use std::fs::File;
4use std::io::BufReader;
5use std::io::Read;
6
7const HOSTIDFILE: &str = "/etc/hostid";
8const HOSTSFILE: &str = "/etc/hosts";
9
10#[inline]
11/// Return the host ID (in hexadecimal) of the current host.
12///
13/// # Examples
14///
15/// ```
16/// let host_id = gethostid_rs::gethostid();
17/// println!("{}", host_id)
18/// ```
19
20pub fn gethostid() -> String {
21    let fd = File::open(HOSTIDFILE);
22
23    if let Ok(f) = fd {
24        let mut reader = BufReader::new(f);
25
26        let mut buf: [u8; 4] = [0; 4];
27        reader.read_exact(&mut buf[..]).unwrap();
28        buf.reverse();
29
30        let mut s = String::new();
31        for byte in buf.bytes() {
32            write!(&mut s, "{:x}", byte.unwrap()).expect("Unable to write as hex");
33        }
34
35        s
36    }
37    // Check for host IP (not localhost but hostname associated IP !)
38    else {
39        let hostname = gethostname().trim().to_string();
40        // Open (read-only) hosts net config
41        let fd = File::open(HOSTSFILE);
42
43        let mut ip: Option<String> = None;
44        if let Ok(f) = fd {
45            let mut reader = BufReader::new(f);
46            let mut buf = String::new();
47            reader.read_to_string(&mut buf).unwrap();
48
49            for spl in buf.split('\n') {
50                if spl.contains(&hostname) {
51                    ip = Some(spl.split('\t').collect::<Vec<_>>()[0].to_string());
52                }
53            }
54        }
55
56        // else if IP is not found or "hosts" file does not exist we falloff to localhost IP
57        let ip = match ip {
58            Some(i) => i,
59            None => {
60                let network_interfaces = list_afinet_netifas().unwrap();
61                let (_, i) = local_ip_address::find_ifa(network_interfaces, "lo").unwrap();
62                i.to_string()
63            }
64        };
65
66        // Encode IP to hex with some shifting like in libc : https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/gethostid.c.html - L.130
67        let mut u_vec = Vec::with_capacity(4);
68        for c in ip.split('.') {
69            u_vec.push(c.parse().unwrap())
70        }
71        u_vec.reverse();
72
73        let to_shift = u32::from_le_bytes(u_vec.try_into().unwrap());
74        let shifted = to_shift << 16 | to_shift >> 16;
75
76        let mut s = String::with_capacity(4);
77        for byte in shifted.to_le_bytes() {
78            write!(&mut s, "{:02x}", byte).expect("Unable to write as hex");
79        }
80
81        s
82    }
83}
84
85const ETC_HOSTNAME: &str = "/etc/hostname";
86const PROC_HOSTNAME: &str = "/proc/sys/kernel/hostname";
87
88/// Return the Host name of the current host.
89/// With the hostname we can deduce the Host IP
90/// which is used when the hostid file (`/etc/hostid`) is not set.
91fn gethostname() -> String {
92    if let Ok(f) = File::open(ETC_HOSTNAME) {
93        _get_host_name(f)
94    } else if let Ok(f) = File::open(PROC_HOSTNAME) {
95        _get_host_name(f)
96    } else {
97        panic!("Host does not have hostname");
98    }
99}
100
101#[inline]
102fn _get_host_name(f: File) -> String {
103    let mut reader = BufReader::new(f);
104    let mut buf = String::new();
105    reader.read_to_string(&mut buf).unwrap();
106
107    buf
108}