Skip to content

Commit b1af2be

Browse files
authored
Merge pull request #558 from GyulyVGC/ipinfo-mmdb
Add support for IPinfo MMDB format
2 parents 2872a2d + 33454da commit b1af2be

File tree

15 files changed

+368
-43
lines changed

15 files changed

+368
-43
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ All Sniffnet releases with the relative changes are documented in this file.
44

55
## [UNRELEASED]
66
- Thumbnail mode improvements ([#512](https://github.com/GyulyVGC/sniffnet/pull/512))
7+
- Support IPinfo ASN and Country databases ([#558](https://github.com/GyulyVGC/sniffnet/pull/558) — fixes [#533](https://github.com/GyulyVGC/sniffnet/issues/533))
78
- Added keyboard shortcuts to change zoom level (fixes [#554](https://github.com/GyulyVGC/sniffnet/issues/554))
89
- Increased the range of selectable zoom values (fixes [#542](https://github.com/GyulyVGC/sniffnet/issues/542))
9-
- Reduced `String` allocations in translation code ([#524](https://github.com/GyulyVGC/sniffnet/pull/524))
1010
- Updated some of the existing translations to v1.3:
1111
- French - [#494](https://github.com/GyulyVGC/sniffnet/pull/494)
1212
- German - [#495](https://github.com/GyulyVGC/sniffnet/pull/495)
@@ -16,7 +16,8 @@ All Sniffnet releases with the relative changes are documented in this file.
1616
- Japanese - [#504](https://github.com/GyulyVGC/sniffnet/pull/504)
1717
- Uzbek - [#510](https://github.com/GyulyVGC/sniffnet/pull/510)
1818
- Swedish - [#522](https://github.com/GyulyVGC/sniffnet/pull/522)
19-
- Fixed bug causing impossibility to exit thumbnail mode on Ubuntu (fixes [#505](https://github.com/GyulyVGC/sniffnet/pull/505))
19+
- Reduced `String` allocations in translation code ([#524](https://github.com/GyulyVGC/sniffnet/pull/524))
20+
- Fixed impossibility to exit thumbnail mode in some Linux distributions (fixes [#505](https://github.com/GyulyVGC/sniffnet/pull/505))
2021

2122
## [1.3.0] - 2024-04-08
2223
- Introduced thumbnail mode, enabling users to keep an eye on Sniffnet while doing other tasks ([#484](https://github.com/GyulyVGC/sniffnet/pull/484))

resources/DB/GeoLite2-ASN.mmdb

147 KB
Binary file not shown.

resources/DB/GeoLite2-Country.mmdb

164 KB
Binary file not shown.
23.5 KB
Binary file not shown.
34.5 KB
Binary file not shown.
29.6 KB
Binary file not shown.

src/gui/pages/connection_details_page.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ fn get_host_info_col(
270270
language: Language,
271271
) -> Column<'static, Message, StyleType> {
272272
let mut host_info_col = Column::new().spacing(4);
273-
if r_dns.parse::<IpAddr>().is_err() || (!host.asn.name.is_empty() && host.asn.number > 0) {
273+
if r_dns.parse::<IpAddr>().is_err() || (!host.asn.name.is_empty() && !host.asn.code.is_empty())
274+
{
274275
host_info_col = host_info_col.push(Rule::horizontal(10.0));
275276
}
276277
if r_dns.parse::<IpAddr>().is_err() {
@@ -280,10 +281,10 @@ fn get_host_info_col(
280281
font,
281282
));
282283
}
283-
if !host.asn.name.is_empty() && host.asn.number > 0 {
284+
if !host.asn.name.is_empty() && !host.asn.code.is_empty() {
284285
host_info_col = host_info_col.push(TextType::highlighted_subtitle_with_desc(
285286
administrative_entity_translation(language),
286-
&format!("{} (ASN {})", host.asn.name, host.asn.number),
287+
&format!("{} (ASN {})", host.asn.name, host.asn.code),
287288
font,
288289
));
289290
}

src/gui/pages/thumbnail_page.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ mod tests {
166166
domain: domain.to_string(),
167167
asn: Asn {
168168
name: asn.to_string(),
169-
number: 512,
169+
code: "512".to_string(),
170170
},
171171
country: Default::default(),
172172
}

src/mmdb/asn.rs

Lines changed: 134 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,143 @@
1-
use maxminddb::{geoip2, MaxMindDBError};
2-
1+
use crate::mmdb::types::mmdb_asn_entry::MmdbAsnEntry;
32
use crate::mmdb::types::mmdb_reader::MmdbReader;
43
use crate::networking::types::asn::Asn;
54

65
pub const ASN_MMDB: &[u8] = include_bytes!("../../resources/DB/GeoLite2-ASN.mmdb");
76

87
#[allow(clippy::module_name_repetitions)]
9-
pub fn get_asn(address_to_lookup: &str, asn_db_reader: &MmdbReader) -> Asn {
10-
let asn_result: Result<geoip2::Asn, MaxMindDBError> = match asn_db_reader {
11-
MmdbReader::Default(reader) => reader.lookup(address_to_lookup.parse().unwrap()),
12-
MmdbReader::Custom(reader) => reader.lookup(address_to_lookup.parse().unwrap()),
13-
};
14-
if let Ok(res) = asn_result {
15-
if res.autonomous_system_number.is_some() && res.autonomous_system_organization.is_some() {
16-
return Asn {
17-
number: res.autonomous_system_number.unwrap(),
18-
name: res.autonomous_system_organization.unwrap().to_string(),
19-
};
20-
}
8+
pub fn get_asn(address: &str, asn_db_reader: &MmdbReader) -> Asn {
9+
if let Ok(res) = asn_db_reader.lookup::<MmdbAsnEntry>(address.parse().unwrap()) {
10+
return res.get_asn();
2111
}
2212
Asn::default()
2313
}
14+
15+
#[cfg(test)]
16+
mod tests {
17+
use crate::mmdb::asn::{get_asn, ASN_MMDB};
18+
use crate::mmdb::types::mmdb_reader::MmdbReader;
19+
20+
#[test]
21+
fn test_get_asn_with_default_reader() {
22+
let reader_1 = MmdbReader::from(&String::from("unknown path"), ASN_MMDB);
23+
assert!(matches!(reader_1, MmdbReader::Default(_)));
24+
let reader_2 = MmdbReader::from(&String::new(), ASN_MMDB);
25+
assert!(matches!(reader_2, MmdbReader::Default(_)));
26+
let reader_3 = MmdbReader::from(&String::from("resources/repository/hr.png"), ASN_MMDB);
27+
assert!(matches!(reader_3, MmdbReader::Default(_)));
28+
let reader_4 = MmdbReader::from(&String::from("resources/DB/GeoLite2-ASN.mmdb"), ASN_MMDB);
29+
assert!(matches!(reader_4, MmdbReader::Custom(_)));
30+
let reader_5 = MmdbReader::from(&String::from("resources/DB/GeoLite2-ASN.mmdb"), &[]);
31+
assert!(matches!(reader_5, MmdbReader::Custom(_)));
32+
33+
for reader in vec![reader_1, reader_2, reader_3, reader_4, reader_5] {
34+
// known IP
35+
let res = get_asn("8.8.8.8", &reader);
36+
assert_eq!(res.code, "15169");
37+
assert_eq!(res.name, "GOOGLE");
38+
39+
// another known IP
40+
let res = get_asn("78.35.248.93", &reader);
41+
assert_eq!(res.code, "8422");
42+
assert_eq!(
43+
res.name,
44+
"NetCologne Gesellschaft fur Telekommunikation mbH"
45+
);
46+
47+
// known IPv6
48+
let res = get_asn("2806:230:2057::", &reader);
49+
assert_eq!(res.code, "11888");
50+
assert_eq!(res.name, "Television Internacional, S.A. de C.V.");
51+
52+
// unknown IP
53+
let res = get_asn("127.0.0.1", &reader);
54+
assert_eq!(res.code, "");
55+
assert_eq!(res.name, "");
56+
57+
// unknown IPv6
58+
let res = get_asn("::1", &reader);
59+
assert_eq!(res.code, "");
60+
assert_eq!(res.name, "");
61+
}
62+
}
63+
64+
#[test]
65+
fn test_get_asn_with_custom_ipinfo_single_reader() {
66+
let reader_1 = MmdbReader::from(
67+
&String::from("resources/test/ipinfo_asn_sample.mmdb"),
68+
ASN_MMDB,
69+
);
70+
let reader_2 =
71+
MmdbReader::from(&String::from("resources/test/ipinfo_asn_sample.mmdb"), &[]);
72+
73+
for reader in vec![reader_1, reader_2] {
74+
assert!(matches!(reader, MmdbReader::Custom(_)));
75+
76+
// known IP
77+
let res = get_asn("61.8.0.0", &reader);
78+
assert_eq!(res.code, "AS1221");
79+
assert_eq!(res.name, "Telstra Limited");
80+
81+
// another known IP
82+
let res = get_asn("206.180.34.99", &reader);
83+
assert_eq!(res.code, "AS63344");
84+
assert_eq!(res.name, "The Reynolds and Reynolds Company");
85+
86+
// known IPv6
87+
let res = get_asn("2806:230:2057::", &reader);
88+
assert_eq!(res.code, "AS11888");
89+
assert_eq!(res.name, "Television Internacional, S.A. de C.V.");
90+
91+
// unknown IP
92+
let res = get_asn("127.0.0.1", &reader);
93+
assert_eq!(res.code, "");
94+
assert_eq!(res.name, "");
95+
96+
// unknown IPv6
97+
let res = get_asn("::1", &reader);
98+
assert_eq!(res.code, "");
99+
assert_eq!(res.name, "");
100+
}
101+
}
102+
103+
#[test]
104+
fn test_get_asn_with_custom_ipinfo_combined_reader() {
105+
let reader_1 = MmdbReader::from(
106+
&String::from("resources/test/ipinfo_country_asn_sample.mmdb"),
107+
ASN_MMDB,
108+
);
109+
let reader_2 = MmdbReader::from(
110+
&String::from("resources/test/ipinfo_country_asn_sample.mmdb"),
111+
&[],
112+
);
113+
114+
for reader in vec![reader_1, reader_2] {
115+
assert!(matches!(reader, MmdbReader::Custom(_)));
116+
117+
// known IP
118+
let res = get_asn("31.171.144.141", &reader);
119+
assert_eq!(res.code, "AS197742");
120+
assert_eq!(res.name, "IBB Energie AG");
121+
122+
// another known IP
123+
let res = get_asn("103.112.220.111", &reader);
124+
assert_eq!(res.code, "AS134077");
125+
assert_eq!(res.name, "Magik Pivot Company Limited");
126+
127+
// known IPv6
128+
let res = get_asn("2a02:6ea0:f001::", &reader);
129+
assert_eq!(res.code, "AS60068");
130+
assert_eq!(res.name, "Datacamp Limited");
131+
132+
// unknown IP
133+
let res = get_asn("127.0.0.1", &reader);
134+
assert_eq!(res.code, "");
135+
assert_eq!(res.name, "");
136+
137+
// unknown IPv6
138+
let res = get_asn("::1", &reader);
139+
assert_eq!(res.code, "");
140+
assert_eq!(res.name, "");
141+
}
142+
}
143+
}

src/mmdb/country.rs

Lines changed: 122 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,131 @@
1-
use maxminddb::{geoip2, MaxMindDBError};
2-
31
use crate::countries::types::country::Country;
2+
use crate::mmdb::types::mmdb_country_entry::MmdbCountryEntry;
43
use crate::mmdb::types::mmdb_reader::MmdbReader;
54

65
pub const COUNTRY_MMDB: &[u8] = include_bytes!("../../resources/DB/GeoLite2-Country.mmdb");
76

87
#[allow(clippy::module_name_repetitions)]
9-
pub fn get_country(address_to_lookup: &str, country_db_reader: &MmdbReader) -> Country {
10-
let country_result: Result<geoip2::Country, MaxMindDBError> = match country_db_reader {
11-
MmdbReader::Default(reader) => reader.lookup(address_to_lookup.parse().unwrap()),
12-
MmdbReader::Custom(reader) => reader.lookup(address_to_lookup.parse().unwrap()),
13-
};
14-
if let Ok(res1) = country_result {
15-
if let Some(res2) = res1.country {
16-
if let Some(res3) = res2.iso_code {
17-
return Country::from_str(res3);
18-
}
19-
}
8+
pub fn get_country(address: &str, country_db_reader: &MmdbReader) -> Country {
9+
if let Ok(res) = country_db_reader.lookup::<MmdbCountryEntry>(address.parse().unwrap()) {
10+
return res.get_country();
2011
}
2112
Country::ZZ // unknown
2213
}
14+
15+
#[cfg(test)]
16+
mod tests {
17+
use crate::countries::types::country::Country;
18+
use crate::mmdb::country::{get_country, COUNTRY_MMDB};
19+
use crate::mmdb::types::mmdb_reader::MmdbReader;
20+
21+
#[test]
22+
fn test_get_country_with_default_reader() {
23+
let reader_1 = MmdbReader::from(&String::from("unknown path"), COUNTRY_MMDB);
24+
assert!(matches!(reader_1, MmdbReader::Default(_)));
25+
let reader_2 = MmdbReader::from(&String::new(), COUNTRY_MMDB);
26+
assert!(matches!(reader_2, MmdbReader::Default(_)));
27+
let reader_3 = MmdbReader::from(&String::from("resources/repository/hr.png"), COUNTRY_MMDB);
28+
assert!(matches!(reader_3, MmdbReader::Default(_)));
29+
let reader_4 = MmdbReader::from(
30+
&String::from("resources/DB/GeoLite2-Country.mmdb"),
31+
COUNTRY_MMDB,
32+
);
33+
assert!(matches!(reader_4, MmdbReader::Custom(_)));
34+
let reader_5 = MmdbReader::from(&String::from("resources/DB/GeoLite2-Country.mmdb"), &[]);
35+
assert!(matches!(reader_5, MmdbReader::Custom(_)));
36+
37+
for reader in vec![reader_1, reader_2, reader_3, reader_4, reader_5] {
38+
// known IP
39+
let res = get_country("8.8.8.8", &reader);
40+
assert_eq!(res, Country::US);
41+
42+
// another known IP
43+
let res = get_country("78.35.248.93", &reader);
44+
assert_eq!(res, Country::DE);
45+
46+
// known IPv6
47+
let res = get_country("2806:230:2057::", &reader);
48+
assert_eq!(res, Country::MX);
49+
50+
// unknown IP
51+
let res = get_country("127.0.0.1", &reader);
52+
assert_eq!(res, Country::ZZ);
53+
54+
// unknown IPv6
55+
let res = get_country("::1", &reader);
56+
assert_eq!(res, Country::ZZ);
57+
}
58+
}
59+
60+
#[test]
61+
fn test_get_country_with_custom_ipinfo_single_reader() {
62+
let reader_1 = MmdbReader::from(
63+
&String::from("resources/test/ipinfo_country_sample.mmdb"),
64+
COUNTRY_MMDB,
65+
);
66+
let reader_2 = MmdbReader::from(
67+
&String::from("resources/test/ipinfo_country_sample.mmdb"),
68+
&[],
69+
);
70+
71+
for reader in vec![reader_1, reader_2] {
72+
assert!(matches!(reader, MmdbReader::Custom(_)));
73+
74+
// known IP
75+
let res = get_country("2.2.146.0", &reader);
76+
assert_eq!(res, Country::GB);
77+
78+
// another known IP
79+
let res = get_country("23.193.112.81", &reader);
80+
assert_eq!(res, Country::US);
81+
82+
// known IPv6
83+
let res = get_country("2a0e:1d80::", &reader);
84+
assert_eq!(res, Country::RO);
85+
86+
// unknown IP
87+
let res = get_country("127.0.0.1", &reader);
88+
assert_eq!(res, Country::ZZ);
89+
90+
// unknown IPv6
91+
let res = get_country("::1", &reader);
92+
assert_eq!(res, Country::ZZ);
93+
}
94+
}
95+
96+
#[test]
97+
fn test_get_country_with_custom_ipinfo_combined_reader() {
98+
let reader_1 = MmdbReader::from(
99+
&String::from("resources/test/ipinfo_country_asn_sample.mmdb"),
100+
COUNTRY_MMDB,
101+
);
102+
let reader_2 = MmdbReader::from(
103+
&String::from("resources/test/ipinfo_country_asn_sample.mmdb"),
104+
&[],
105+
);
106+
107+
for reader in vec![reader_1, reader_2] {
108+
assert!(matches!(reader, MmdbReader::Custom(_)));
109+
110+
// known IP
111+
let res = get_country("31.171.144.141", &reader);
112+
assert_eq!(res, Country::IT);
113+
114+
// another known IP
115+
let res = get_country("103.112.220.111", &reader);
116+
assert_eq!(res, Country::TH);
117+
118+
// known IPv6
119+
let res = get_country("2a02:6ea0:f001::", &reader);
120+
assert_eq!(res, Country::AR);
121+
122+
// unknown IP
123+
let res = get_country("127.0.0.1", &reader);
124+
assert_eq!(res, Country::ZZ);
125+
126+
// unknown IPv6
127+
let res = get_country("::1", &reader);
128+
assert_eq!(res, Country::ZZ);
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)