ip_check/
lib.rs

1pub mod ip_lookup {
2
3    use std::net::Ipv4Addr;
4    use std::path::PathBuf;
5    use std::str::FromStr;
6    use std::cmp::Ordering;
7    use std::error::Error;
8    use csv::Reader;
9
10
11    #[derive(Debug, Clone)]
12    pub struct IpRange {
13        start: u32,
14        end: u32,
15        pub country: String,
16        pub region: String,
17        pub city: String,
18    }
19
20    #[derive(Debug)]
21    pub struct Looker {
22        pub file_path: PathBuf,
23        pub ip_ranges: Vec<IpRange>,
24    }
25
26    pub trait IpLookup {
27        fn look_up(&self, ip: &str) -> Option<IpRange>;
28        fn look_up_ipv4(&self, ip: &Ipv4Addr) -> Option<IpRange>;
29    }
30
31    impl Looker {
32
33        pub fn new(file_path: PathBuf) -> Self {
34
35            let mut rdr = Reader::from_path(&file_path).expect("IP CSV file not found");
36            let mut ip_ranges = Vec::new();
37
38            for result in rdr.records() {
39                let record = result.unwrap();
40                let start: u32 = record[0].parse().unwrap();
41                let end: u32 = record[1].parse().unwrap();
42                let country = record[2].to_string();
43                let region = record[4].to_string();
44                let city = record[5].to_string();
45
46                ip_ranges.push(IpRange { start, end, country, region, city });
47            }
48
49            Looker {
50                file_path,
51                ip_ranges,
52            }
53
54        }
55
56    }
57
58    fn read_ip_ranges(file_path: &str) -> Result<Vec<IpRange>, Box<dyn Error>> {
59        let mut rdr = Reader::from_path(file_path)?;
60        let mut ip_ranges = Vec::new();
61        
62        for result in rdr.records() {
63            let record = result?;
64            let start: u32 = record[0].parse()?;
65            let end: u32 = record[1].parse()?;
66            let country = record[2].to_string();
67            let region = record[4].to_string();
68            let city = record[5].to_string();
69            
70            ip_ranges.push(IpRange { start, end, country, region, city });
71        }
72
73        Ok(ip_ranges)
74    }
75
76    fn find_ip_range(ip: u32, ranges: &[IpRange]) -> Option<IpRange> {
77        ranges.binary_search_by(|range| {
78            if ip < range.start {
79                Ordering::Greater // Search the left side
80            } else if ip > range.end {
81                Ordering::Less // Search the right side
82            } else {
83                Ordering::Equal // IP is within this range
84            }
85        }).ok().map(|index| ranges[index].clone())
86    }
87
88    fn ip_string_to_decimal(ip: &str) -> Result<u32, String> {
89        let ip = Ipv4Addr::from_str(ip);
90        if ip.is_err() {
91            return Err("Invalid IP address".into());
92        }
93        let ip = ip.unwrap();
94        ip_to_decimal(&ip)
95    }
96
97    fn ip_to_decimal(ip: &Ipv4Addr) -> Result<u32,String> {
98        let octets = ip.octets();
99        let decimal = (octets[0] as u32) << 24 
100            | (octets[1] as u32) << 16 
101            | (octets[2] as u32) << 8 
102            | octets[3] as u32;
103        Ok(decimal)
104    }
105
106
107    pub fn look_up(ip: &str, file_path: &str) -> Option<IpRange> {
108        let ip_decimal_to_use = match ip_string_to_decimal(ip) {
109            Err(e) => {
110                log::error!("Error: {}", e);
111                return None;
112            },
113            Ok(ip_decimal) => {
114                ip_decimal
115            }
116        };
117         let ip_ranges_to_use = match read_ip_ranges(file_path) {
118            Err(e) => {
119                log::error!("Error: {}", e);
120                return None;
121            },
122            Ok(ip_ranges) => {
123                ip_ranges
124            }
125        };
126        
127        match find_ip_range(ip_decimal_to_use, &ip_ranges_to_use[..]) {
128            Some(range) => {
129                log::trace!("IP is in range: {:?}", range);
130                Some(range)
131            },
132            None => {
133                log::trace!("IP not found in any range");
134                None
135            }
136        }
137    }
138
139    impl IpLookup for Looker {
140
141        fn look_up(&self, ip: &str) -> Option<IpRange> {
142            let ip = Ipv4Addr::from_str(ip);
143            match ip {
144                Err(e) => {
145                    log::error!("Error: {}", e);
146                    None
147                },
148                Ok(ip) => {
149                    self.look_up_ipv4(&ip)
150                }
151            }
152 
153       }
154
155        fn look_up_ipv4(&self, ip: &Ipv4Addr) -> Option<IpRange> {
156
157            let ip_decimal_to_use = match ip_to_decimal(ip) {
158                Err(e) => {
159                    log::error!("Error: {}", e);
160                    return None;
161                },
162                Ok(ip_decimal) => {
163                    ip_decimal
164                }
165            };
166            let ip_ranges_to_use = &self.ip_ranges;
167
168            match find_ip_range(ip_decimal_to_use, &ip_ranges_to_use[..]) {
169                Some(range) => {
170                    log::trace!("IP is in range: {:?}", range);
171                    Some(range)
172                },
173                None => {
174                    log::trace!("IP not found in any range");
175                    None
176                }
177            }
178        }
179
180    }
181
182}
183
184pub use crate::ip_lookup::{look_up, Looker, IpLookup};