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 } else if ip > range.end {
81 Ordering::Less } else {
83 Ordering::Equal }
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};