Skip to content

Commit 5de65b2

Browse files
committed
Security Fixes (this session + previous)
SAFETY comments on all 4 unsafe { Mmap::map() } calls documenting the contract db_year <= 20 time bomb removed in both LocationDB::read_header and ProxyDB::read_header — now checks product_code only, so databases from 2021+ work correctly Header size validation — DB::from_file rejects files < 32 bytes before parsing Bounds checking on all Source::read_* methods (done in previous round) Removed stale commented-out code (//path: db.path, //let index_buffer = ...) Performance (previous session, verified this session) from_ne_bytes → from_le_bytes — correct on all platforms Single-mmap DB::from_file — reads product code from one mmap to pick the right variant
1 parent e496266 commit 5de65b2

File tree

13 files changed

+466
-197
lines changed

13 files changed

+466
-197
lines changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ip2location"
3-
version = "0.6.0"
3+
version = "0.6.1"
44
authors = ["MARIRS <marirs@gmail.com>"]
55
description = "Find geo information & proxy information based on the given IP using IP2Location BIN databases"
66
keywords = ["ip2location", "geoip", "geolocation", "ip", "proxy"]
@@ -13,13 +13,13 @@ exclude = [
1313
]
1414
repository = "https://github.com/marirs/rust-ip2location"
1515
homepage = "https://github.com/marirs/rust-ip2location"
16-
edition = "2021"
16+
edition = "2024"
1717

1818
[dependencies]
1919
memmap2 = "0.9"
2020
serde = { version = "1", features = ["derive"] }
2121
serde_json = "1"
22-
serde_with = "3.11"
22+
serde_with = "3.12"
2323

2424
[profile.dev]
2525
opt-level = 3

README.md

Lines changed: 31 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@
1111

1212
This library reads the IP2Location DB format for both IP2Location and IP2Proxy and returns geo information for the given IP.
1313

14+
### Features
15+
- **Zero-copy** — string fields borrow directly from the memory-mapped file
16+
- **Memory-mapped I/O** — database is mmap’d at open time, no heap allocation on the lookup path
17+
- Supports both **IP2Location** (geolocation) and **IP2Proxy** (proxy detection) BIN databases
18+
- Handles **IPv4**, **IPv6**, **6to4**, **Teredo**, and **IPv4-mapped IPv6** addresses
19+
1420
### Requirements
15-
- `Rust 1.60.0` and above (edition 2021)
21+
- Rust **1.85+** (edition 2024)
1622

1723
### Building
1824
- debug
@@ -32,7 +38,7 @@ cargo t -v
3238
### Usage
3339
```toml
3440
[dependencies]
35-
ip2location = "0.6.0"
41+
ip2location = "0.6"
3642
```
3743

3844
### Example
@@ -43,158 +49,50 @@ const IPV4BIN: &str = "data/IP2LOCATION-LITE-DB1.BIN";
4349
const IPV6BIN: &str = "data/IP2LOCATION-LITE-DB1.IPV6.BIN";
4450
const IP2PROXYBIN: &str = "data/IP2PROXY-IP-COUNTRY.BIN";
4551

46-
// Lookup an IP v4 in the IP2Location V6 BIN Database
52+
// Lookup an IPv4 in the IP2Location IPv6 BIN Database
4753
fn ip_lookup_in_ipv6bin() -> Result<(), error::Error> {
48-
let mut db = DB::from_file(IPV6BIN)?;
54+
let db = DB::from_file(IPV6BIN)?;
4955
let record = db.ip_lookup("43.224.159.155".parse().unwrap())?;
50-
let record = if let Record::LocationDb(rec) = record {
51-
Some(rec)
52-
} else {
53-
None
54-
};
55-
assert!(record.is_some());
56-
let record = record.unwrap();
57-
assert!(!record.country.is_none());
58-
assert_eq!(record.country.clone().unwrap().short_name, "IN");
59-
assert_eq!(record.country.unwrap().long_name, "India");
56+
if let Record::LocationDb(rec) = record {
57+
assert!(rec.country.is_some());
58+
assert_eq!(rec.country.as_ref().unwrap().short_name, "IN");
59+
assert_eq!(rec.country.as_ref().unwrap().long_name, "India");
60+
}
6061
Ok(())
6162
}
6263

63-
// Lookup an IP v4 in the IP2Location V4 BIN Database
64+
// Lookup an IPv4 in the IP2Location IPv4 BIN Database
6465
fn ip_lookup_in_ipv4bin() -> Result<(), error::Error> {
65-
let mut db = DB::from_file(IPV4BIN)?;
66+
let db = DB::from_file(IPV4BIN)?;
6667
let record = db.ip_lookup("43.224.159.155".parse().unwrap())?;
67-
let record = if let Record::LocationDb(rec) = record {
68-
Some(rec)
69-
} else {
70-
None
71-
};
72-
assert!(record.is_some());
73-
let record = record.unwrap();
74-
assert!(!record.country.is_none());
75-
assert_eq!(record.country.clone().unwrap().short_name, "IN");
76-
assert_eq!(record.country.unwrap().long_name, "India");
68+
if let Record::LocationDb(rec) = record {
69+
assert!(rec.country.is_some());
70+
assert_eq!(rec.country.as_ref().unwrap().short_name, "IN");
71+
assert_eq!(rec.country.as_ref().unwrap().long_name, "India");
72+
}
7773
Ok(())
7874
}
7975

8076
// Lookup an IP in the Proxy Database
8177
fn ip_lookup_in_proxy_bin() -> Result<(), error::Error> {
82-
let mut db = DB::from_file(IP2PROXYBIN)?;
78+
let db = DB::from_file(IP2PROXYBIN)?;
8379
let record = db.ip_lookup("1.1.1.1".parse().unwrap())?;
84-
let record = if let Record::ProxyDb(rec) = record {
85-
Some(rec)
86-
} else {
87-
None
88-
};
89-
assert!(record.is_some());
90-
let record = record.unwrap();
91-
assert!(!record.country.is_none());
80+
if let Record::ProxyDb(rec) = record {
81+
assert!(rec.country.is_some());
82+
}
9283
Ok(())
9384
}
9485
```
9586

9687
### Executing the Example
9788
```bash
98-
cargo b --example
99-
100-
# IP2Lcoation Example
89+
cargo build --examples
10190

91+
# IP2Location Example
10292
./target/debug/examples/lookup data/IP2LOCATION-LITE-DB1.IPV6.BIN 2a01:cb08:8d14::
103-
Db Path: data/IP2LOCATION-LITE-DB1.IPV6.BIN
104-
|- Db Type: 1
105-
|- Db Column: 2
106-
|- Db Date (YY/MM/DD): 20/12/28
107-
108-
Ok(
109-
Record {
110-
ip: "2a01:cb08:8d14::",
111-
latitude: None,
112-
longitude: None,
113-
country: Some(
114-
Country {
115-
short_name: "FR",
116-
long_name: "France",
117-
},
118-
),
119-
region: None,
120-
city: None,
121-
isp: None,
122-
domain: None,
123-
zip_code: None,
124-
time_zone: None,
125-
net_speed: None,
126-
idd_code: None,
127-
area_code: None,
128-
weather_station_code: None,
129-
weather_station_name: None,
130-
mcc: None,
131-
mnc: None,
132-
mobile_brand: None,
133-
elevation: None,
134-
usage_type: None,
135-
address_type: None,
136-
category: None,
137-
district: None,
138-
asn: None,
139-
as_name: None,
140-
},
141-
)
142-
143-
# IP2Proxy Example
144-
145-
./target/debug/examples/lookup data/sample.bin.px11/IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER.BIN 194.59.249.19
146-
Db Path: data/sample.bin.px11/IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER.BIN
147-
|- Db Type: 11
148-
|- Db Column: 13
149-
|- Db Date (YY/MM/DD): 21/5/28
150-
151-
ProxyDb(
152-
ProxyRecord {
153-
ip: 1.1.1.1,
154-
country: Some(
155-
Country {
156-
short_name: "US",
157-
long_name: "United States of America",
158-
},
159-
),
160-
region: Some(
161-
"California",
162-
),
163-
city: Some(
164-
"Los Angeles",
165-
),
166-
isp: Some(
167-
"APNIC and CloudFlare DNS Resolver Project",
168-
),
169-
domain: Some(
170-
"cloudflare.com",
171-
),
172-
is_proxy: Some(
173-
IsAProxy,
174-
),
175-
proxy_type: Some(
176-
"DCH",
177-
),
178-
asn: Some(
179-
"13335",
180-
),
181-
as_: Some(
182-
"CloudFlare Inc",
183-
),
184-
last_seen: Some(
185-
"27",
186-
),
187-
threat: Some(
188-
"-",
189-
),
190-
provider: Some(
191-
"-",
192-
),
193-
usage_type: Some(
194-
"CDN",
195-
),
196-
},
197-
)
93+
94+
# IP2Proxy Example
95+
./target/debug/examples/lookup data/IP2PROXY-IP-COUNTRY.BIN 1.1.1.1
19896
```
19997

20098
### License

examples/lookup.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
1+
//! CLI example: look up an IP address in an IP2Location or IP2Proxy BIN database.
2+
//!
3+
//! Usage:
4+
//! cargo run --example lookup -- <path/to/db.bin> <ip_address>
5+
//!
6+
//! Example:
7+
//! cargo run --example lookup -- data/IP2LOCATION-LITE-DB1.IPV6.BIN 2a01:cb08:8d14::
8+
19
use std::net::IpAddr;
210

311
use ip2location::DB;
412

513
fn main() -> Result<(), String> {
614
let mut args = std::env::args().skip(1);
7-
let db = match DB::from_file(&*args.next().ok_or("First argument is the path to db")?) {
8-
Ok(db) => db,
9-
Err(e) => {
10-
println!("{:?}", e);
11-
std::process::exit(1)
12-
}
13-
};
15+
16+
let db_path = args.next().ok_or("Usage: lookup <db_path> <ip_address>")?;
17+
let db = DB::from_file(&db_path).map_err(|e| format!("Failed to open {db_path}: {e}"))?;
1418

1519
let ip: IpAddr = args
1620
.next()
17-
.ok_or("Second argument must be the IP address, like 128.101.101.101")?
21+
.ok_or("Usage: lookup <db_path> <ip_address>")?
1822
.parse()
19-
.unwrap();
23+
.map_err(|e| format!("Invalid IP address: {e}"))?;
2024

21-
// print the db information
2225
db.print_db_info();
2326
println!();
2427

25-
// print the IP information
2628
match db.ip_lookup(ip) {
2729
Ok(record) => println!("{:#?}", record),
28-
Err(e) => println!("{:?}", e),
29-
};
30+
Err(e) => eprintln!("Lookup failed: {e}"),
31+
}
3032

3133
Ok(())
3234
}

0 commit comments

Comments
 (0)