1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4#[cfg(not(feature = "compat-0-1-1"))]
5compile_error!(
6 "The feature `compat-0-1-1` must be enabled to ensure \
7 forward compatibility with future versions of this crate"
8);
9
10use ipnet::IpNet;
11use ipnet::Ipv4Net;
12use ipnet::Ipv6Net;
13use memmap2::Mmap;
14use std::error::Error;
15use std::fmt;
16use std::fs::File;
17use std::io;
18use std::net::IpAddr;
19use std::net::Ipv4Addr;
20use std::net::Ipv6Addr;
21use std::path::Path;
22use std::str;
23use yoke::Yoke;
24use zerocopy::FromBytes;
25
26mod format;
27
28#[derive(Debug)]
30#[non_exhaustive]
31pub enum OpenError {
32 Open(io::Error),
38 Mmap(io::Error),
40 InvalidMagic,
42 UnsupportedVersion(u8),
44 CouldntReadHeader,
46 InvalidAsRange,
48 InvalidNetworkRange,
50 InvalidNetworkNodeRange,
52 InvalidCountryRange,
54 InvalidStringPoolRange,
56}
57
58impl Error for OpenError {
59 fn source(&self) -> Option<&(dyn Error + 'static)> {
60 use self::OpenError::*;
61 match self {
62 Open(e) => Some(e),
63 Mmap(e) => Some(e),
64 InvalidMagic
65 | UnsupportedVersion(_)
66 | CouldntReadHeader
67 | InvalidAsRange
68 | InvalidNetworkRange
69 | InvalidNetworkNodeRange
70 | InvalidCountryRange
71 | InvalidStringPoolRange => None,
72 }
73 }
74}
75
76impl fmt::Display for OpenError {
77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 use self::OpenError::*;
79 match self {
80 Open(e) => write!(f, "error opening database file: {}", e),
81 Mmap(e) => write!(f, "error memory-mapping database file: {}", e),
82 InvalidMagic => "invalid database file magic, likely not the correct format".fmt(f),
83 UnsupportedVersion(ver) => write!(f, "unsupported database version {}", ver),
84 CouldntReadHeader => "couldn't read database file header, database corrupted".fmt(f),
85 InvalidAsRange => "invalid database header field: as, database corrupted".fmt(f),
86 InvalidNetworkRange => {
87 "invalid database header field: network, database corrupted".fmt(f)
88 }
89 InvalidNetworkNodeRange => {
90 "invalid database header field: network_node, database corrupted".fmt(f)
91 }
92 InvalidCountryRange => {
93 "invalid database header field: country, database corrupted".fmt(f)
94 }
95 InvalidStringPoolRange => {
96 "invalid database header field: string_pool, database corrupted".fmt(f)
97 }
98 }
99 }
100}
101
102#[derive(Debug)]
108pub struct As<'a> {
109 asn: u32,
110 name: &'a str,
111}
112
113#[derive(Debug)]
117pub struct Network<'a> {
118 inner: NetworkInner<'a>,
119 addrs: IpNet,
120}
121
122#[derive(Debug)]
126pub struct NetworkV4<'a> {
127 inner: NetworkInner<'a>,
128 addrs: Ipv4Net,
129}
130
131#[derive(Debug)]
135pub struct NetworkV6<'a> {
136 inner: NetworkInner<'a>,
137 addrs: Ipv6Net,
138}
139
140#[derive(Debug)]
141struct NetworkInner<'a> {
142 country_code: &'a str,
144 asn: u32,
146 flags: u16,
147}
148
149#[derive(Debug)]
153pub struct Country<'a> {
154 code: &'a str,
155 continent_code: &'a str,
156 name: &'a str,
157}
158
159impl<'a> As<'a> {
160 fn from(inner: &LocationsInner<'a>, as_: &'a format::As) -> As<'a> {
161 As {
162 asn: as_.id.get(),
163 name: inner.string(as_.name),
164 }
165 }
166 pub fn asn(&self) -> u32 {
171 self.asn
172 }
173 pub fn name(&self) -> &'a str {
175 self.name
176 }
177}
178
179impl<'a> NetworkInner<'a> {
180 fn from(_inner: &LocationsInner<'a>, network: &'a format::Network) -> NetworkInner<'a> {
181 NetworkInner {
182 country_code: str::from_utf8(&network.country_code).unwrap_or_else(|e| {
183 panic!(
184 "corrupt libloc db: invalid UTF-8 in network country code: {}",
185 e,
186 );
187 }),
188 asn: network.asn.get(),
189 flags: network.flags.get(),
190 }
191 }
192}
193
194impl<'a> Network<'a> {
195 pub fn country_code(&self) -> &'a str {
212 self.inner.country_code
213 }
214 pub fn asn(&self) -> u32 {
230 self.inner.asn
231 }
232 pub fn is_anonymous_proxy(&self) -> bool {
246 self.inner.flags & format::NETWORK_FLAG_ANONYMOUS_PROXY != 0
247 }
248 pub fn is_satellite_provider(&self) -> bool {
262 self.inner.flags & format::NETWORK_FLAG_SATTELITE_PROVIDER != 0
263 }
264 pub fn is_anycast(&self) -> bool {
278 self.inner.flags & format::NETWORK_FLAG_ANYCAST != 0
279 }
280 #[allow(missing_docs)]
281 pub fn is_drop(&self) -> bool {
282 self.inner.flags & format::NETWORK_FLAG_DROP != 0
283 }
284 pub fn addrs(&self) -> IpNet {
298 self.addrs
299 }
300}
301
302impl<'a> From<NetworkV4<'a>> for Network<'a> {
303 fn from(network: NetworkV4<'a>) -> Network<'a> {
304 Network {
305 inner: network.inner,
306 addrs: network.addrs.into(),
307 }
308 }
309}
310
311impl<'a> From<NetworkV6<'a>> for Network<'a> {
312 fn from(network: NetworkV6<'a>) -> Network<'a> {
313 Network {
314 inner: network.inner,
315 addrs: network.addrs.into(),
316 }
317 }
318}
319
320impl<'a> NetworkV4<'a> {
321 pub fn country_code(&self) -> &'a str {
323 self.inner.country_code
324 }
325 pub fn asn(&self) -> u32 {
327 self.inner.asn
328 }
329 pub fn is_anonymous_proxy(&self) -> bool {
331 self.inner.flags & format::NETWORK_FLAG_ANONYMOUS_PROXY != 0
332 }
333 pub fn is_satellite_provider(&self) -> bool {
335 self.inner.flags & format::NETWORK_FLAG_SATTELITE_PROVIDER != 0
336 }
337 pub fn is_anycast(&self) -> bool {
339 self.inner.flags & format::NETWORK_FLAG_ANYCAST != 0
340 }
341 pub fn is_drop(&self) -> bool {
343 self.inner.flags & format::NETWORK_FLAG_DROP != 0
344 }
345 pub fn addrs(&self) -> Ipv4Net {
347 self.addrs
348 }
349}
350
351impl<'a> NetworkV6<'a> {
352 pub fn country_code(&self) -> &'a str {
354 self.inner.country_code
355 }
356 pub fn asn(&self) -> u32 {
358 self.inner.asn
359 }
360 pub fn is_anonymous_proxy(&self) -> bool {
362 self.inner.flags & format::NETWORK_FLAG_ANONYMOUS_PROXY != 0
363 }
364 pub fn is_satellite_provider(&self) -> bool {
366 self.inner.flags & format::NETWORK_FLAG_SATTELITE_PROVIDER != 0
367 }
368 pub fn is_anycast(&self) -> bool {
370 self.inner.flags & format::NETWORK_FLAG_ANYCAST != 0
371 }
372 pub fn is_drop(&self) -> bool {
374 self.inner.flags & format::NETWORK_FLAG_DROP != 0
375 }
376 pub fn addrs(&self) -> Ipv6Net {
378 self.addrs
379 }
380}
381
382impl<'a> Country<'a> {
383 fn from(inner: &LocationsInner<'a>, country: &'a format::Country) -> Country<'a> {
384 Country {
385 code: str::from_utf8(&country.code).unwrap_or_else(|e| {
386 panic!("corrupt libloc db: invalid UTF-8 in country code: {}", e);
387 }),
388 continent_code: str::from_utf8(&country.continent_code).unwrap_or_else(|e| {
389 panic!(
390 "corrupt libloc db: invalid UTF-8 in country continent code: {}",
391 e,
392 );
393 }),
394 name: inner.string(country.name),
395 }
396 }
397 pub fn code(&self) -> &'a str {
403 self.code
404 }
405 pub fn continent_code(&self) -> &'a str {
417 self.continent_code
418 }
419 pub fn name(&self) -> &'a str {
421 self.name
422 }
423}
424
425pub struct Locations {
427 inner: Yoke<LocationsInner<'static>, Mmap>,
428}
429
430#[cfg_attr(feature = "verified", derive(yoke_derive::Yokeable))]
431struct LocationsInner<'a> {
432 header: &'a format::Header,
433 as_: &'a [format::As],
434 networks: &'a [format::Network],
435 network_nodes: &'a [format::NetworkNode],
436 countries: &'a [format::Country],
437 string_pool: &'a [u8],
438 ipv4_network_node: Option<u32>,
439}
440
441#[cfg(not(feature = "verified"))]
442unsafe impl<'a> yoke::Yokeable<'a> for LocationsInner<'static> {
443 type Output = LocationsInner<'a>;
444 fn transform(&'a self) -> &'a LocationsInner<'a> {
445 self
446 }
447 fn transform_owned(self) -> LocationsInner<'a> {
448 self
449 }
450 unsafe fn make(from: LocationsInner<'a>) -> LocationsInner<'static> {
451 assert!(
455 std::mem::size_of::<LocationsInner<'a>>()
456 == std::mem::size_of::<LocationsInner<'static>>()
457 );
458 let ptr: *const LocationsInner<'static> = (&from as *const LocationsInner<'a>).cast();
459 std::mem::forget(from);
460 std::ptr::read(ptr)
461 }
462 fn transform_mut<F: FnOnce(&'a mut LocationsInner<'a>) + 'static>(&'a mut self, f: F) {
463 unsafe {
464 f(std::mem::transmute::<
465 &mut LocationsInner<'static>,
466 &mut LocationsInner<'a>,
467 >(self))
468 }
469 }
470}
471
472impl<'a> LocationsInner<'a> {
473 fn find_network(&self, root: u32, bits_reverse: u128, num_bits: u32) -> Option<(u8, u32)> {
474 let mut used_bits = 0;
476 let mut bits = bits_reverse;
477 let mut cur = self.network_node(root);
478 let mut last_network = None;
479 for _ in 0..num_bits {
480 let next_index = cur.children[(bits & 1 != 0) as usize].get();
481 if next_index == 0 {
482 break;
483 }
484 last_network = cur.network().map(|n| (used_bits, n)).or(last_network);
485 bits >>= 1;
486 used_bits += 1;
487 cur = self.network_node(next_index);
488 }
489 last_network = cur.network().map(|n| (used_bits, n)).or(last_network);
490 last_network
491 }
492 fn find_network_node(&self, root: u32, bits_reverse: u128, num_bits: u32) -> Option<u32> {
493 let mut bits = bits_reverse;
495 let mut cur_index = root;
496 for _ in 0..num_bits {
497 cur_index = self.network_node(cur_index).children[(bits & 1 != 0) as usize].get();
498 if cur_index == 0 {
499 return None;
500 }
501 bits >>= 1;
502 }
503 Some(cur_index)
504 }
505 fn as_(&self, index: u32) -> &'a format::As {
506 let index = index as usize;
507 if index >= self.as_.len() {
508 panic!(
509 "corrupt libloc db: invalid as index: {} > {}",
510 index,
511 self.as_.len(),
512 );
513 }
514 &self.as_[index]
515 }
516 fn network(&self, index: u32) -> &'a format::Network {
517 let index = index as usize;
518 if index >= self.networks.len() {
519 panic!(
520 "corrupt libloc db: invalid network index: {} > {}",
521 index,
522 self.networks.len(),
523 );
524 }
525 &self.networks[index]
526 }
527 fn network_node(&self, index: u32) -> &'a format::NetworkNode {
528 let index = index as usize;
529 if index >= self.network_nodes.len() {
530 panic!(
531 "corrupt libloc db: invalid network node index: {} > {}",
532 index,
533 self.network_nodes.len(),
534 );
535 }
536 &self.network_nodes[index]
537 }
538 fn country(&self, index: u32) -> &'a format::Country {
539 let index = index as usize;
540 if index >= self.countries.len() {
541 panic!(
542 "corrupt libloc db: invalid country index: {} > {}",
543 index,
544 self.countries.len(),
545 );
546 }
547 &self.countries[index]
548 }
549 fn string(&self, str_ref: format::StrRef) -> &'a str {
550 let offset = str_ref.offset.get() as usize;
551 if offset > self.string_pool.len() {
552 panic!(
553 "corrupt libloc db: invalid str_ref: {} > {}",
554 offset,
555 self.string_pool.len(),
556 );
557 }
558 let bytes = &self.string_pool[offset..];
559 let bytes = &bytes[..bytes
560 .iter()
561 .copied()
562 .position(|b| b == 0)
563 .unwrap_or_else(|| {
564 panic!(
565 "corrupt libloc db: missing null termination for str_ref: {}",
566 offset,
567 );
568 })];
569 str::from_utf8(bytes).unwrap_or_else(|e| {
570 panic!(
571 "corrupt libloc db: invalid UTF-8 for str_ref: {}: {}",
572 offset, e,
573 )
574 })
575 }
576}
577
578trait ByteSliceExt {
579 fn get_range(&self, range: format::FileRange) -> Option<&[u8]>;
580 fn get_typed_range<T: FromBytes>(&self, range: format::FileRange) -> Option<&[T]>;
581}
582impl<'a> ByteSliceExt for [u8] {
583 fn get_range(&self, range: format::FileRange) -> Option<&[u8]> {
584 let start = range.offset.get();
585 let end = range.offset.get().checked_add(range.length.get())?;
586 self.get(start as usize..end as usize)
587 }
588 fn get_typed_range<T: FromBytes>(&self, range: format::FileRange) -> Option<&[T]> {
589 self.get_range(range).and_then(T::slice_from)
590 }
591}
592
593impl Locations {
594 pub fn open<P: AsRef<Path>>(path: P) -> Result<Locations, OpenError> {
633 fn inner(path: &Path) -> Result<Locations, OpenError> {
634 use self::OpenError as Error;
635 let file = File::open(path).map_err(Error::Open)?;
636 let mmap = unsafe { Mmap::map(&file) }.map_err(Error::Mmap)?;
637
638 if !mmap.starts_with(&format::MAGIC) {
639 return Err(Error::InvalidMagic);
640 }
641
642 #[cfg(unix)]
644 let _ = mmap.advise(memmap2::Advice::Random);
645
646 let inner = Yoke::try_attach_to_cart(mmap, |mmap| -> Result<_, Error> {
647 let header =
648 format::Header::ref_from_prefix(&mmap).ok_or(Error::CouldntReadHeader)?;
649 if header.version != format::VERSION {
650 return Err(Error::UnsupportedVersion(header.version));
651 }
652
653 let mut inner = LocationsInner {
654 as_: mmap
655 .get_typed_range(header.as_)
656 .ok_or(Error::InvalidAsRange)?,
657 networks: mmap
658 .get_typed_range(header.networks)
659 .ok_or(Error::InvalidNetworkRange)?,
660 network_nodes: mmap
661 .get_typed_range(header.network_nodes)
662 .ok_or(Error::InvalidNetworkNodeRange)?,
663 countries: mmap
664 .get_typed_range(header.countries)
665 .ok_or(Error::InvalidCountryRange)?,
666 string_pool: mmap
667 .get_range(header.string_pool)
668 .ok_or(Error::InvalidStringPoolRange)?,
669
670 header,
671
672 ipv4_network_node: Some(u32::MAX), };
674 let ipv4_mapped_prefix = u128::from(Ipv4Addr::from(0).to_ipv6_mapped());
675 inner.ipv4_network_node =
676 inner.find_network_node(0, ipv4_mapped_prefix.reverse_bits(), 96);
677 Ok(inner)
678 })?;
679 Ok(Locations { inner })
680 }
681 inner(path.as_ref())
682 }
683 #[cfg(feature = "time")]
694 pub fn created_at(&self) -> chrono::DateTime<chrono::offset::Utc> {
695 let inner = self.inner.get();
696 let created_at = inner.header.created_at.get();
697 chrono::DateTime::from_timestamp(
698 created_at.try_into().unwrap_or_else(|_| {
699 panic!(
700 "corrupt libloc db: invalid created_at header: {}",
701 created_at,
702 )
703 }),
704 0,
705 )
706 .unwrap_or_else(|| {
707 panic!(
708 "corrupt libloc db: invalid created_at header: {}",
709 created_at,
710 )
711 })
712 }
713 pub fn vendor(&self) -> &str {
724 let inner = self.inner.get();
725 inner.string(inner.header.vendor)
726 }
727 pub fn description(&self) -> &str {
738 let inner = self.inner.get();
739 inner.string(inner.header.description)
740 }
741 pub fn license(&self) -> &str {
752 let inner = self.inner.get();
753 inner.string(inner.header.license)
754 }
755 pub fn as_(&self, asn: u32) -> Option<As<'_>> {
772 let inner = self.inner.get();
773
774 let index = inner
777 .as_
778 .binary_search_by_key(&asn, |as_| as_.id.get())
779 .ok()?;
780 Some(As::from(inner, inner.as_(index.try_into().unwrap())))
781 }
782 pub fn lookup(&self, addr: IpAddr) -> Option<Network<'_>> {
794 match addr {
795 IpAddr::V4(addr) => self.lookup_v4(addr).map(Into::into),
796 IpAddr::V6(addr) => self.lookup_v6(addr).map(Into::into),
797 }
798 }
799 pub fn lookup_v4(&self, addr: Ipv4Addr) -> Option<NetworkV4<'_>> {
803 let inner = self.inner.get();
804
805 let (num_bits, network_idx) = inner.find_network(
806 inner.ipv4_network_node?,
807 u32::from(addr).reverse_bits().into(),
808 32,
809 )?;
810 let addrs = Ipv4Net::new(addr, num_bits).unwrap().trunc();
811
812 Some(NetworkV4 {
813 inner: NetworkInner::from(inner, inner.network(network_idx)),
814 addrs,
815 })
816 }
817 pub fn lookup_v6(&self, addr: Ipv6Addr) -> Option<NetworkV6<'_>> {
821 let inner = self.inner.get();
822
823 let (num_bits, network_idx) =
824 inner.find_network(0, u128::from(addr).reverse_bits(), 128)?;
825 let addrs = Ipv6Net::new(addr, num_bits).unwrap().trunc();
826
827 Some(NetworkV6 {
828 inner: NetworkInner::from(inner, inner.network(network_idx)),
829 addrs,
830 })
831 }
832 pub fn country(&self, code: &str) -> Option<Country<'_>> {
846 let inner = self.inner.get();
847
848 if code.len() != 2 {
849 return None;
850 }
851 let code = code.as_bytes();
852 let code = [code[0], code[1]];
853 let index = inner
856 .countries
857 .binary_search_by_key(&code, |c| c.code)
858 .ok()?;
859 Some(Country::from(
860 inner,
861 inner.country(index.try_into().unwrap()),
862 ))
863 }
864}