Browse Source
Parse bgp attributes, use flate2 for gz, mrt-rs to parse RIB entries * Use gzdecoder, mrt-rs * Parse AS path from BGP attributes * Write Address and associated bottleneck to file * Use structopt for cli args * Add logging * Create Bottleneck struct * Handle ipv6 as well as ipv4pull/13/head
RJ Rybarczyk
4 years ago
committed by
GitHub
15 changed files with 2383 additions and 378 deletions
@ -1,2 +1,6 @@ |
|||
/target |
|||
**/*.rs.bk |
|||
data/ |
|||
notes |
|||
dump/ |
|||
bottleneck/ |
|||
|
File diff suppressed because it is too large
@ -0,0 +1,86 @@ |
|||
# asmap-rs |
|||
A tool to assist the [asmap](https://github.com/sipa/asmap) project read and parse RIS raw data from the [RIPE NCC](https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris/ris-raw-data). |
|||
It may be extended to support other data sources. |
|||
|
|||
The data is collected using Quagga routing software and stored in MRT format. |
|||
|
|||
## Run |
|||
``` |
|||
Parse mrt formatted files and find asn bottleneck |
|||
|
|||
USAGE: |
|||
asmap-rs <SUBCOMMAND> |
|||
|
|||
FLAGS: |
|||
-h, --help Prints help information |
|||
-V, --version Prints version information |
|||
|
|||
SUBCOMMANDS: |
|||
find-bottleneck Reads and decompresses the MRT gz files, parses the AS Paths, determines the AS bottleneck, saves result |
|||
download Downloads and saves the MRT formatted gz files |
|||
help Prints this message or the help of the given subcommand(s) |
|||
``` |
|||
|
|||
### Download RIS Raw Data |
|||
``` |
|||
asmap-rs-download 0.1.0 |
|||
Downloads and saves the MRT formatted gz files |
|||
|
|||
USAGE: |
|||
asmap-rs download [OPTIONS] |
|||
|
|||
FLAGS: |
|||
-h, --help Prints help information |
|||
-V, --version Prints version information |
|||
|
|||
OPTIONS: |
|||
-o, --out <OUT> Directory to write MRT formatted gz files [default: dump] |
|||
-n, --ripe_collector_number <RIPE_COLLECTOR_NUMBER>... |
|||
Range of specific RIS files to download [default: [00, 24]] |
|||
``` |
|||
|
|||
#### Download RIS Raw Data Examples |
|||
|
|||
Download all files from RIPE NCC (`rrc00-latest-bview.gz` through `rrc24-latest-bview.gz`) and saves the MRT formatted gz files to default `dump` directory. |
|||
``` |
|||
cargo run --release download |
|||
``` |
|||
|
|||
Download `rrc03-latest-bview.gz` and `rrc14-latest-bview.gz` files from RIPE NCC and save the MRT formatted gz files to `dump-dir` directory. |
|||
Will create `dump-dir` if dne. |
|||
``` |
|||
cargo run --release download -n 3,14 -o dump-dir |
|||
``` |
|||
|
|||
Download `rrc03-latest-bview.gz` file from RIPE NCC and save the MRT formatted gz files to default `dump` directory. |
|||
``` |
|||
cargo run --release download -n 3 |
|||
``` |
|||
|
|||
### Find ASN Bottleneck |
|||
``` |
|||
asmap-rs find-bottleneck 0.1.0 |
|||
Reads and decompresses the MRT gz files, parses the AS Paths, determines the AS bottleneck, saves result |
|||
|
|||
USAGE: |
|||
asmap-rs find-bottleneck [OPTIONS] |
|||
|
|||
FLAGS: |
|||
-h, --help Prints help information |
|||
-V, --version Prints version information |
|||
|
|||
OPTIONS: |
|||
-d, --dir <DIRECTORY> Directory path of the MRT formatted gz files to find bottleneck of |
|||
-o, --out <OUT> Directory to write result [default: print to stdout] |
|||
``` |
|||
|
|||
### Find Bottleneck ASN Example |
|||
Finds bottleneck from the data located in the `dump` and prints bottleneck results to stdout. |
|||
``` |
|||
$ cargo run --release find-bottleneck -d dump |
|||
``` |
|||
|
|||
Finds bottleneck from the data located in the `dump` directory and writes the bottleneck results to `bottleneck/bottleneck.<epoch>.txt`. |
|||
``` |
|||
$ cargo run --release find-bottleneck -d dump -o bottleneck |
|||
``` |
@ -1,200 +0,0 @@ |
|||
223.255.245.0/24|31742 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.245.0/24|35266 2914 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|8607 3356 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|2914 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|286 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|25160 3356 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|328145 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|64271 62240 1299 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|42473 1299 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|8896 3356 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|56730 3356 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|12779 2914 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|60501 3257 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|39122 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|207044 3356 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|57695 62240 1299 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|57111 6762 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|14630 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|51185 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|8218 6461 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|6894 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|31742 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.246.0/24|35266 2914 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|8607 2914 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|2914 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|286 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|25160 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|328145 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|64271 62240 1299 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|42473 1299 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|8896 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|56730 3356 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|12779 2914 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|60501 3257 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|39122 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|207044 3356 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|57695 62240 1299 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|57111 6762 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|14630 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|51185 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|8218 6461 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|6894 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|31742 174 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.247.0/24|35266 2914 6453 4755 45820 45954 45954 45954 45954 |
|||
223.255.248.0/24|8607 2914 3491 63199 |
|||
223.255.248.0/24|2914 6453 63199 |
|||
223.255.248.0/24|13030 3491 63199 |
|||
223.255.248.0/24|286 3491 63199 |
|||
223.255.248.0/24|25160 174 6453 63199 |
|||
223.255.248.0/24|328145 174 3491 63199 |
|||
223.255.248.0/24|64271 62240 1299 3491 63199 |
|||
223.255.248.0/24|42473 1299 3491 63199 |
|||
223.255.248.0/24|8896 174 6453 63199 |
|||
223.255.248.0/24|56730 3356 3491 63199 |
|||
223.255.248.0/24|12779 2914 6453 63199 |
|||
223.255.248.0/24|60501 3257 6453 63199 |
|||
223.255.248.0/24|39122 174 3491 63199 |
|||
223.255.248.0/24|207044 3356 3491 63199 |
|||
223.255.248.0/24|57695 62240 1299 3491 63199 |
|||
223.255.248.0/24|57111 6762 6453 63199 |
|||
223.255.248.0/24|14630 8220 63199 |
|||
223.255.248.0/24|51185 3356 6453 63199 |
|||
223.255.248.0/24|8218 6461 6453 63199 |
|||
223.255.248.0/24|6894 3257 6453 63199 |
|||
223.255.248.0/24|31742 174 3491 63199 |
|||
223.255.248.0/24|35266 2914 3491 63199 |
|||
223.255.249.0/24|8607 2914 3491 63199 |
|||
223.255.249.0/24|2914 6453 63199 |
|||
223.255.249.0/24|13030 3491 63199 |
|||
223.255.249.0/24|286 3491 63199 |
|||
223.255.249.0/24|25160 174 6453 63199 |
|||
223.255.249.0/24|328145 174 3491 63199 |
|||
223.255.249.0/24|64271 62240 1299 3491 63199 |
|||
223.255.249.0/24|42473 1299 3491 63199 |
|||
223.255.249.0/24|8896 174 6453 63199 |
|||
223.255.249.0/24|56730 3356 3491 63199 |
|||
223.255.249.0/24|12779 2914 6453 63199 |
|||
223.255.249.0/24|60501 3257 6453 63199 |
|||
223.255.249.0/24|39122 174 3491 63199 |
|||
223.255.249.0/24|207044 3356 3491 63199 |
|||
223.255.249.0/24|57695 62240 1299 3491 63199 |
|||
223.255.249.0/24|57111 6762 6453 63199 |
|||
223.255.249.0/24|14630 8220 63199 |
|||
223.255.249.0/24|51185 3356 6453 63199 |
|||
223.255.249.0/24|8218 6461 6453 63199 |
|||
223.255.249.0/24|6894 3257 6453 63199 |
|||
223.255.249.0/24|31742 174 3491 63199 |
|||
223.255.249.0/24|35266 2914 3491 63199 |
|||
223.255.250.0/24|8607 2914 3491 63199 |
|||
223.255.250.0/24|2914 6453 63199 |
|||
223.255.250.0/24|13030 3491 63199 |
|||
223.255.250.0/24|286 3491 63199 |
|||
223.255.250.0/24|25160 174 6453 63199 |
|||
223.255.250.0/24|328145 174 3491 63199 |
|||
223.255.250.0/24|64271 62240 1299 3491 63199 |
|||
223.255.250.0/24|42473 1299 3491 63199 |
|||
223.255.250.0/24|8896 174 6453 63199 |
|||
223.255.250.0/24|56730 3356 3491 63199 |
|||
223.255.250.0/24|12779 2914 6453 63199 |
|||
223.255.250.0/24|60501 3257 6453 63199 |
|||
223.255.250.0/24|39122 174 3491 63199 |
|||
223.255.250.0/24|207044 3356 3491 63199 |
|||
223.255.250.0/24|57695 62240 1299 3491 63199 |
|||
223.255.250.0/24|57111 6762 6453 63199 |
|||
223.255.250.0/24|14630 8220 63199 |
|||
223.255.250.0/24|51185 3356 6453 63199 |
|||
223.255.250.0/24|8218 6461 6453 63199 |
|||
223.255.250.0/24|6894 3257 6453 63199 |
|||
223.255.250.0/24|31742 174 3491 63199 |
|||
223.255.250.0/24|35266 2914 3491 63199 |
|||
223.255.251.0/24|8607 2914 3491 63199 |
|||
223.255.251.0/24|2914 6453 63199 |
|||
223.255.251.0/24|13030 3491 63199 |
|||
223.255.251.0/24|286 3491 63199 |
|||
223.255.251.0/24|25160 174 6453 63199 |
|||
223.255.251.0/24|328145 174 3491 63199 |
|||
223.255.251.0/24|64271 62240 1299 3491 63199 |
|||
223.255.251.0/24|42473 1299 3491 63199 |
|||
223.255.251.0/24|8896 174 6453 63199 |
|||
223.255.251.0/24|56730 3356 3491 63199 |
|||
223.255.251.0/24|12779 2914 6453 63199 |
|||
223.255.251.0/24|60501 3257 6453 63199 |
|||
223.255.251.0/24|39122 174 3491 63199 |
|||
223.255.251.0/24|207044 3356 3491 63199 |
|||
223.255.251.0/24|57695 62240 1299 3491 63199 |
|||
223.255.251.0/24|57111 6762 6453 63199 |
|||
223.255.251.0/24|14630 8220 63199 |
|||
223.255.251.0/24|51185 3356 6453 63199 |
|||
223.255.251.0/24|8218 6461 6453 63199 |
|||
223.255.251.0/24|6894 3257 6453 63199 |
|||
223.255.251.0/24|31742 174 3491 63199 |
|||
223.255.251.0/24|35266 2914 3491 63199 |
|||
223.255.252.0/24|8607 2914 4134 58519 |
|||
223.255.252.0/24|2914 4134 58519 |
|||
223.255.252.0/24|286 4134 58519 |
|||
223.255.252.0/24|25160 174 4134 58519 |
|||
223.255.252.0/24|328145 174 4134 58519 |
|||
223.255.252.0/24|64271 62240 1299 4134 58519 |
|||
223.255.252.0/24|42473 1299 4134 58519 |
|||
223.255.252.0/24|8896 174 4134 58519 |
|||
223.255.252.0/24|56730 174 4134 58519 |
|||
223.255.252.0/24|12779 174 4134 58519 |
|||
223.255.252.0/24|60501 4134 58519 |
|||
223.255.252.0/24|39122 1299 4134 58519 |
|||
223.255.252.0/24|207044 3257 4134 58519 |
|||
223.255.252.0/24|57111 6762 4134 58519 |
|||
223.255.252.0/24|14630 6461 4134 58519 |
|||
223.255.252.0/24|51185 1273 4134 58519 |
|||
223.255.252.0/24|8218 4134 58519 |
|||
223.255.252.0/24|6894 174 4134 58519 |
|||
223.255.252.0/24|31742 174 4134 58519 |
|||
223.255.252.0/24|35266 2914 4134 58519 |
|||
223.255.253.0/24|8607 2914 4134 58519 |
|||
223.255.253.0/24|2914 4134 58519 |
|||
223.255.253.0/24|286 4134 58519 |
|||
223.255.253.0/24|25160 1299 4134 58519 |
|||
223.255.253.0/24|328145 9498 6762 4134 58519 |
|||
223.255.253.0/24|64271 62240 1299 4134 58519 |
|||
223.255.253.0/24|42473 1299 4134 58519 |
|||
223.255.253.0/24|8896 1299 4134 58519 |
|||
223.255.253.0/24|56730 51945 1299 4134 58519 |
|||
223.255.253.0/24|12779 2914 4134 58519 |
|||
223.255.253.0/24|60501 4134 58519 |
|||
223.255.253.0/24|39122 1299 4134 58519 |
|||
223.255.253.0/24|207044 3257 4134 58519 |
|||
223.255.253.0/24|57111 6762 4134 58519 |
|||
223.255.253.0/24|14630 6461 4134 58519 |
|||
223.255.253.0/24|51185 1273 4134 58519 |
|||
223.255.253.0/24|8218 4134 58519 |
|||
223.255.253.0/24|6894 3257 4134 58519 |
|||
223.255.253.0/24|31742 1299 4134 58519 |
|||
223.255.253.0/24|35266 2914 4134 58519 |
|||
223.255.254.0/24|8607 7473 3758 55415 |
|||
223.255.254.0/24|28792 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|2914 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|13030 7473 3758 55415 |
|||
223.255.254.0/24|4637 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|286 7473 3758 55415 |
|||
223.255.254.0/24|6461 7473 3758 55415 |
|||
223.255.254.0/24|25160 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|328145 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|64271 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|42473 1299 7473 3758 55415 |
|||
223.255.254.0/24|8896 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|37721 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|56730 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|12779 6461 7473 3758 55415 |
|||
223.255.254.0/24|60501 6461 7473 3758 55415 |
|||
223.255.254.0/24|39122 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|207044 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|57695 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|57111 174 3257 7473 3758 55415 |
|||
223.255.254.0/24|14630 6461 7473 3758 55415 |
|||
223.255.254.0/24|37680 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|51185 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|28792 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|8218 6461 7473 3758 55415 |
|||
223.255.254.0/24|6894 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|31742 4657 55415 55415 55415 55415 55415 55415 55415 |
|||
223.255.254.0/24|35266 4657 55415 55415 55415 55415 55415 55415 55415 |
@ -0,0 +1,313 @@ |
|||
use crate::common::*; |
|||
|
|||
#[derive(PartialEq, Debug)] |
|||
pub(crate) struct AsPathParser<'buffer> { |
|||
buffer: &'buffer [u8], |
|||
next: usize, |
|||
} |
|||
|
|||
impl<'buffer> AsPathParser<'buffer> { |
|||
/// Given a `buffer` with lifetime `'buffer`, constructs a new `AsPathParser` and parses the
|
|||
/// attributes.
|
|||
pub(crate) fn parse(buffer: &'buffer [u8]) -> Result<Vec<u32>> { |
|||
if buffer.is_empty() { |
|||
info!("Error::MissingPathAttribute, buffer: {:?}", buffer); |
|||
return Err(Error::MissingPathAttribute { |
|||
missing_attribute: "all attributes".to_string(), |
|||
}); |
|||
} |
|||
Self::new(buffer).parse_attributes() |
|||
} |
|||
|
|||
/// Given a `buffer` with lifetime `'buffer`, constructs a new `AsPathParser`
|
|||
fn new(buffer: &'buffer [u8]) -> AsPathParser { |
|||
AsPathParser { next: 0, buffer } |
|||
} |
|||
|
|||
/// Advances forward one in the buffer and returns that byte. Error if `buffer` is already exhausted.
|
|||
fn advance(&mut self) -> Result<u8> { |
|||
if self.done() { |
|||
info!("Error::UnexpectedEndOfBuffer {:?}", &self.buffer); |
|||
Err(Error::UnexpectedEndOfBuffer) |
|||
} else { |
|||
let byte = self.buffer[self.next]; |
|||
self.next += 1; |
|||
Ok(byte) |
|||
} |
|||
} |
|||
|
|||
/// Advances forward four in the buffer and returns the fours bytes as a u32.
|
|||
fn parse_u32(&mut self) -> Result<u32> { |
|||
let a = self.advance()?; |
|||
let b = self.advance()?; |
|||
let c = self.advance()?; |
|||
let d = self.advance()?; |
|||
Ok(u32::from_be_bytes([a, b, c, d])) |
|||
} |
|||
|
|||
/// Returns true if buffer is exhausted, false otherwise.
|
|||
fn done(&self) -> bool { |
|||
self.next == self.buffer.len() |
|||
} |
|||
|
|||
fn parse_attributes(mut self) -> Result<Vec<u32>> { |
|||
let mut paths = Vec::new(); |
|||
|
|||
while !self.done() { |
|||
if let Some(path) = self.parse_attribute()? { |
|||
// if there are no asn's in the as path
|
|||
if path.is_empty() { |
|||
info!("Error::NoAsPathInAttributePath {:?}", &self.buffer); |
|||
return Err(Error::NoAsPathInAttributePath); |
|||
} |
|||
paths.push(path); |
|||
} |
|||
} |
|||
|
|||
if paths.len() > 1 { |
|||
// Too many asn paths in path attributes
|
|||
info!("Error::MultipleAsPaths {:?}", &self.buffer); |
|||
Err(Error::MultipleAsPaths) |
|||
} else if let Some(path) = paths.pop() { |
|||
Ok(path) |
|||
} else { |
|||
info!("Error::NoAsPathInAttributePath{:?}", &self.buffer); |
|||
Err(Error::NoAsPathInAttributePath) |
|||
} |
|||
} |
|||
|
|||
/// Parses all attributes. If attribute is AS_PATH, p
|
|||
fn parse_attribute(&mut self) -> Result<Option<Vec<u32>>> { |
|||
let flag = self.advance()?; |
|||
let type_code = self.advance()?; |
|||
let mut attribute_length: u16 = self.advance()?.into(); |
|||
|
|||
if (flag >> 4) & 1 == 1 { |
|||
attribute_length <<= 8; |
|||
attribute_length |= self.advance()? as u16; |
|||
} |
|||
|
|||
if type_code == 2 { |
|||
let asn_attr_position_end = &self.next + attribute_length as usize; |
|||
|
|||
let asn_path = self.parse_as_path(); |
|||
|
|||
if asn_attr_position_end != self.next { |
|||
let leftover_attr_count = asn_attr_position_end - self.next; |
|||
for _ in 0..leftover_attr_count { |
|||
self.advance()?; |
|||
} |
|||
} |
|||
|
|||
asn_path |
|||
} else { |
|||
for _ in 0..attribute_length { |
|||
self.advance()?; |
|||
} |
|||
Ok(None) |
|||
} |
|||
} |
|||
|
|||
/// Takes self and returns a vec of asn
|
|||
fn parse_as_path(&mut self) -> Result<Option<Vec<u32>>> { |
|||
let as_set_indicator = self.advance()?; |
|||
|
|||
// Determine if asn's are listed as an unordered AS_SET (1) or an ordered AS_SEQUENCE (2)
|
|||
// Only add asn's to as_path vector if they are listed in an ordered AS_SEQUENCE
|
|||
match as_set_indicator { |
|||
1 => { |
|||
println!("AS_SET's are not factored into the bottleneck calculation."); |
|||
let num_asn = self.advance()?; |
|||
|
|||
for _ in 0..num_asn { |
|||
self.parse_u32()?; |
|||
} |
|||
|
|||
Ok(None) |
|||
} |
|||
2 => { |
|||
let mut as_path = Vec::new(); |
|||
|
|||
let num_asn = self.advance()?; |
|||
|
|||
for _ in 0..num_asn { |
|||
as_path.push(self.parse_u32()?); |
|||
} |
|||
|
|||
Ok(Some(as_path)) |
|||
} |
|||
_ => Err(Error::UnknownAsValue { |
|||
unknown_as_value: as_set_indicator, |
|||
}), |
|||
} |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::*; |
|||
|
|||
#[test] |
|||
fn creates_new_as_path_parser() -> Result<()> { |
|||
let buffer = &[0, 1, 2, 3, 4]; |
|||
|
|||
let want = AsPathParser { |
|||
buffer: buffer, |
|||
next: 0, |
|||
}; |
|||
|
|||
let have = AsPathParser::new(buffer); |
|||
|
|||
assert_eq!(want, have); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn returns_true_if_buffer_exhausted() -> Result<()> { |
|||
let want = true; |
|||
|
|||
let have = AsPathParser { |
|||
buffer: &[0, 1, 2, 3, 4], |
|||
next: 5, |
|||
} |
|||
.done(); |
|||
|
|||
assert_eq!(want, have); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn returns_false_if_buffer_has_contents() -> Result<()> { |
|||
let have = AsPathParser { |
|||
buffer: &[0, 1, 2, 3, 4], |
|||
next: 1, |
|||
} |
|||
.done(); |
|||
|
|||
let want = false; |
|||
|
|||
assert_eq!(want, have); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn advances_buffer_one_forward() -> Result<()> { |
|||
let want = AsPathParser { |
|||
buffer: &[0, 1, 2, 3, 4], |
|||
next: 1, |
|||
}; |
|||
|
|||
let mut have = AsPathParser { |
|||
buffer: &[0, 1, 2, 3, 4], |
|||
next: 0, |
|||
}; |
|||
let next_byte = have.advance()?; |
|||
|
|||
assert_eq!(have, want); |
|||
assert_eq!(0, next_byte); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn advances_buffer_four_forward_returns_u32() -> Result<()> { |
|||
let want = AsPathParser { |
|||
buffer: &[0, 1, 2, 3, 4], |
|||
next: 4, |
|||
}; |
|||
|
|||
let mut have = AsPathParser { |
|||
buffer: &[0, 1, 2, 3, 4], |
|||
next: 0, |
|||
}; |
|||
let new_u32 = have.parse_u32()?; |
|||
|
|||
assert_eq!(have, want); |
|||
assert_eq!(66051, new_u32); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn parses_attributes_0() -> Result<()> { |
|||
let bgp_attributes = &[ |
|||
64, 1, 1, 0, 80, 2, 0, 10, 2, 2, 0, 0, 251, 15, 0, 0, 243, 32, 64, 3, 4, 195, 66, 225, |
|||
77, |
|||
]; |
|||
let have = AsPathParser::parse(bgp_attributes)?; |
|||
let want = &[64271u32, 62240u32]; |
|||
assert_eq!(have, want); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn parses_attributes_1() -> Result<(), Error> { |
|||
let bgp_attributes = &[ |
|||
64, 1, 1, 0, 64, 2, 14, 2, 3, 0, 0, 12, 231, 0, 0, 50, 74, 0, 3, 49, 30, 64, 3, 4, 195, |
|||
66, 224, 110, 192, 8, 28, 12, 231, 3, 232, 12, 231, 3, 238, 12, 231, 3, 252, 12, 231, |
|||
12, 21, 50, 74, 2, 188, 50, 74, 3, 243, 50, 74, 11, 210, |
|||
]; |
|||
let have = AsPathParser::parse(bgp_attributes)?; |
|||
let want = &[3303, 12874, 209182]; |
|||
|
|||
assert_eq!(have, want); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn parses_attributes_2() -> Result<(), Error> { |
|||
let bgp_attributes = &[ |
|||
64, 1, 1, 0, 64, 2, 10, 2, 2, 0, 0, 165, 233, 0, 0, 5, 19, 64, 3, 4, 195, 66, 226, 113, |
|||
128, 4, 4, 0, 0, 0, 0, 192, 8, 24, 184, 43, 5, 222, 184, 43, 7, 208, 184, 43, 8, 64, |
|||
184, 43, 8, 252, 184, 43, 9, 112, 184, 43, 10, 40, |
|||
]; |
|||
let have = AsPathParser::parse(bgp_attributes)?; |
|||
let want = &[42473u32, 1299u32]; |
|||
|
|||
assert_eq!(have, want); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn parses_attributes_3() -> Result<(), Error> { |
|||
let bgp_attributes = &[ |
|||
64, 1, 1, 0, 80, 2, 0, 10, 2, 2, 0, 2, 1, 149, 0, 0, 229, 255, 64, 3, 4, 103, 102, 5, |
|||
1, 192, 16, 8, 2, 2, 0, 2, 1, 149, 0, 200, |
|||
]; |
|||
let have = AsPathParser::parse(bgp_attributes)?; |
|||
let want = &[131477u32, 58879u32]; |
|||
|
|||
assert_eq!(have, want); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn parse_long_as_path_attr() -> Result<(), Error> { |
|||
let bgp_attributes = &[ |
|||
64, 1, 1, 2, 64, 2, 24, 2, 4, 0, 0, 58, 59, 0, 0, 11, 98, 0, 0, 25, 53, 0, 0, 50, 49, |
|||
1, 1, 0, 0, 50, 49, 64, 3, 4, 27, 111, 228, 186, 192, 7, 8, 0, 0, 50, 49, 213, 57, 3, |
|||
1, 192, 8, 32, 11, 98, 1, 164, 11, 98, 5, 125, 11, 98, 9, 102, 11, 98, 13, 72, 25, 53, |
|||
7, 208, 25, 53, 8, 52, 25, 53, 8, 57, 58, 59, 0, 4, |
|||
]; |
|||
|
|||
// 0, 0, 58, 59, 0, 0, 11, 98, 0, 0, 25, 53, 0, 0, 50, 49,
|
|||
|
|||
let have = AsPathParser::parse(bgp_attributes)?; |
|||
let want = &[14907u32, 2914u32, 6453u32, 12849u32]; |
|||
|
|||
assert_eq!(have, want); |
|||
Ok(()) |
|||
} |
|||
|
|||
#[ignore] |
|||
#[test] |
|||
fn returns_err_if_buffer_empty() -> Result<()> { |
|||
let _have = match AsPathParser::parse(&[]) { |
|||
Ok(_) => panic!("exepceted errr"), |
|||
Err(e) => e, |
|||
}; |
|||
let _want = Error::MissingPathAttribute { |
|||
missing_attribute: "all attributes".to_string(), |
|||
}; |
|||
// assert_eq!(want, have);
|
|||
Ok(()) |
|||
} |
|||
} |
@ -1,90 +0,0 @@ |
|||
pub(crate) use crate::common::*; |
|||
|
|||
#[derive(Debug, PartialEq)] |
|||
pub(crate) struct BGPPath { |
|||
pub addr: Address, |
|||
pub as_path: Vec<u32>, |
|||
} |
|||
|
|||
impl FromStr for BGPPath { |
|||
type Err = Error; |
|||
|
|||
fn from_str(text: &str) -> Result<Self, Self::Err> { |
|||
match text.find('|') { |
|||
Some(_) => (), |
|||
None => { |
|||
return Err(Error::NoPipe { |
|||
bad_quagga: text.to_owned(), |
|||
}) |
|||
} |
|||
}; |
|||
|
|||
//
|
|||
// TODO: Count number of fields and if num fields is wrong throw error instead of splitting
|
|||
// first and then counting vector length
|
|||
|
|||
// Get Address from IP and mask str
|
|||
let record_vec: Vec<&str> = text.split('|').collect(); |
|||
if record_vec.len() != 2 { |
|||
panic!("TODO: handle err for could not parse line correctly"); |
|||
} |
|||
|
|||
let addr = Address::from_str(record_vec[0])?; |
|||
|
|||
// Get BGPPath from ASN array
|
|||
let as_vec_str: Vec<&str> = record_vec[1].split(' ').collect(); |
|||
let mut as_path: Vec<u32> = as_vec_str |
|||
.into_iter() |
|||
.map(|s| { |
|||
s.parse().map_err(|e| Error::ParseInt { |
|||
bad_num: s.to_string(), |
|||
error: e, |
|||
}) |
|||
}) |
|||
.collect::<Result<Vec<u32>, Error>>()?; |
|||
as_path.dedup(); |
|||
|
|||
Ok(BGPPath { addr, as_path }) |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::*; |
|||
|
|||
#[test] |
|||
// "223.255.245.0/24|31742 174 6453 4755 45820 45954 45954 45954 45954"
|
|||
fn bgp_path_from_str() -> Result<(), Error> { |
|||
let ip = "223.255.245.0"; |
|||
let mask = 24; |
|||
let asn_list = "31742 174 6453 4755 45820 45954 45954 45954 45954"; |
|||
let text = format!("{}/{}|{}", ip, mask, asn_list); |
|||
let have = BGPPath::from_str(&text).unwrap(); |
|||
|
|||
let addr = Address { |
|||
ip: IpAddr::from_str(ip).unwrap(), |
|||
mask, |
|||
}; |
|||
|
|||
let asn_list_dedup: Vec<u32> = vec![31742, 174, 6453, 4755, 45820, 45954]; |
|||
let want = BGPPath { |
|||
addr, |
|||
as_path: asn_list_dedup, |
|||
}; |
|||
|
|||
assert_eq!(have, want); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn parse_int() { |
|||
let text = "223.255.245.0/24|31742 174 4294967296 4755 45820 45954 45954 45954 45954"; |
|||
let have = BGPPath::from_str(&text).unwrap_err(); |
|||
|
|||
match have { |
|||
Error::ParseInt { .. } => {} |
|||
_ => panic!("Expected BGPPath ParseInt Error type"), |
|||
}; |
|||
} |
|||
} |
@ -1,9 +1,23 @@ |
|||
pub(crate) use crate::{address::Address, bgp_path::BGPPath, error::Error}; |
|||
pub(crate) use std::collections::{HashMap, HashSet}; |
|||
pub(crate) use std::fs::File; |
|||
pub(crate) use std::io::{BufRead, BufReader}; |
|||
pub(crate) use std::{ |
|||
collections::{HashMap, HashSet}, |
|||
fmt::{self, Display, Formatter}, |
|||
fs::{self, File}, |
|||
io::{self, prelude::*, BufReader, BufWriter}, |
|||
net::IpAddr, |
|||
path::{Path, PathBuf}, |
|||
str::FromStr, |
|||
time::SystemTime, |
|||
}; |
|||
|
|||
pub(crate) use flate2::read::GzDecoder; |
|||
pub(crate) use log::*; |
|||
pub(crate) use mrt_rs::{tabledump::TABLE_DUMP_V2, Reader, Record}; |
|||
pub(crate) use pretty_env_logger; |
|||
pub(crate) use structopt::StructOpt; |
|||
|
|||
pub(crate) use crate::{ |
|||
address::Address, as_path_parser::AsPathParser, error::Error, find_bottleneck::FindBottleneck, |
|||
opt::Opt, subcommand::Subcommand, |
|||
}; |
|||
|
|||
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>; |
|||
|
@ -0,0 +1,343 @@ |
|||
use crate::common::*; |
|||
|
|||
/// Contains the mapping of each prefix to its bottleneck asn.
|
|||
#[derive(Debug, PartialEq)] |
|||
pub(crate) struct FindBottleneck { |
|||
prefix_asn: HashMap<Address, u32>, |
|||
} |
|||
|
|||
impl FindBottleneck { |
|||
/// Creates a new `FindBottleneck`, reads and parses mrt files, locates prefix and asn bottleneck
|
|||
pub(crate) fn locate(dir: &PathBuf) -> Result<Self> { |
|||
let mut mrt_hm = HashMap::new(); |
|||
// Walk the directory and read its contents
|
|||
if dir.is_dir() { |
|||
for entry in fs::read_dir(dir).map_err(|io_error| Error::IoError { |
|||
io_error, |
|||
path: "path".into(), |
|||
})? { |
|||
let entry = entry.map_err(|io_error| Error::IoError { |
|||
io_error, |
|||
path: "path".into(), |
|||
})?; |
|||
let path = entry.path(); |
|||
println!("Reading in and parsing `{}`", &path.display()); |
|||
let buffer = |
|||
BufReader::new(File::open(&path).map_err(|io_error| Error::IoError { |
|||
io_error, |
|||
path: path.into(), |
|||
})?); |
|||
|
|||
let mut decoder = GzDecoder::new(buffer); |
|||
Self::parse_mrt(&mut decoder, &mut mrt_hm)?; |
|||
} |
|||
} |
|||
|
|||
let mut bottleneck = FindBottleneck { |
|||
prefix_asn: HashMap::new(), |
|||
}; |
|||
bottleneck.find_as_bottleneck(&mut mrt_hm)?; |
|||
|
|||
Ok(bottleneck) |
|||
} |
|||
|
|||
/// Creates a mapping between a prefix and all of its asn paths, gets the common asns from
|
|||
/// those paths, and considers the last asn (the asn farthest from the originating hop) from
|
|||
/// the common asns to be the bottleneck.
|
|||
fn find_as_bottleneck( |
|||
&mut self, |
|||
mrt_hm: &mut HashMap<Address, HashSet<Vec<u32>>>, |
|||
) -> Result<(), Error> { |
|||
// In the vector value, the first element is the final AS (so the actual AS of the IP,
|
|||
// not some AS on the path). The last element is the critical AS on the path that
|
|||
// determines the bottleneck.
|
|||
let mut prefix_to_common_suffix: HashMap<Address, Vec<u32>> = HashMap::new(); |
|||
|
|||
Self::find_common_suffix(mrt_hm, &mut prefix_to_common_suffix)?; |
|||
|
|||
for (addr, mut as_path) in prefix_to_common_suffix { |
|||
let asn = match as_path.pop() { |
|||
Some(a) => a, |
|||
None => panic!("ERROR: No ASN"), // TODO: Handle error
|
|||
}; |
|||
self.prefix_asn.insert(addr, asn); |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
/// Logic that finds the mapping of each prefix and the asns common to all of the prefix's asn
|
|||
/// paths.
|
|||
fn find_common_suffix( |
|||
mrt_hm: &mut HashMap<Address, HashSet<Vec<u32>>>, |
|||
prefix_to_common_suffix: &mut HashMap<Address, Vec<u32>>, |
|||
) -> Result<(), Error> { |
|||
'outer: for (prefix, as_paths) in mrt_hm.iter() { |
|||
let mut as_paths_sorted: Vec<&Vec<u32>> = as_paths.iter().collect(); |
|||
|
|||
as_paths_sorted.sort_by(|a, b| a.len().cmp(&b.len())); // descending
|
|||
|
|||
let mut rev_common_suffix: Vec<u32> = as_paths_sorted[0].to_vec(); |
|||
rev_common_suffix.reverse(); |
|||
|
|||
for as_path in as_paths_sorted.iter().skip(1) { |
|||
// first one is already in rev_common_suffix
|
|||
let mut rev_as_path: Vec<u32> = as_path.to_vec(); |
|||
rev_as_path.reverse(); |
|||
|
|||
// Every IP should always belong to only one AS
|
|||
if rev_common_suffix.first() != rev_as_path.first() { |
|||
warn!( |
|||
"Every IP should belong to one AS. Prefix: `{:?}` has anomalous AS paths: `{:?}`.", |
|||
&prefix, &as_paths |
|||
); |
|||
continue 'outer; |
|||
} |
|||
|
|||
// first element is already checked
|
|||
for i in 1..rev_common_suffix.len() { |
|||
if rev_as_path[i] != rev_common_suffix[i] { |
|||
rev_common_suffix.truncate(i); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
// rev_common_suffix.reverse();
|
|||
prefix_to_common_suffix |
|||
.entry(*prefix) |
|||
.or_insert(rev_common_suffix); |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
/// Parses the mrt formatted data, extracting the pertinent `PEER_INDEX_TABLE` values
|
|||
/// containing the prefix and associated as paths.
|
|||
fn parse_mrt( |
|||
reader: &mut dyn Read, |
|||
mrt_hm: &mut HashMap<Address, HashSet<Vec<u32>>>, |
|||
) -> Result<()> { |
|||
let mut reader = Reader { stream: reader }; |
|||
|
|||
loop { |
|||
match reader.read() { |
|||
Ok(header_record) => match header_record { |
|||
Some((_, record)) => match record { |
|||
Record::TABLE_DUMP_V2(tdv2_entry) => match tdv2_entry { |
|||
TABLE_DUMP_V2::RIB_IPV4_UNICAST(entry) => { |
|||
let ip = Self::format_ip(&entry.prefix, true)?; |
|||
let mask = entry.prefix_length; |
|||
Self::match_rib_entry(entry.entries, ip, mask, mrt_hm)?; |
|||
} |
|||
TABLE_DUMP_V2::RIB_IPV6_UNICAST(entry) => { |
|||
let ip = Self::format_ip(&entry.prefix, false)?; |
|||
let mask = entry.prefix_length; |
|||
Self::match_rib_entry(entry.entries, ip, mask, mrt_hm)?; |
|||
} |
|||
_ => continue, |
|||
}, |
|||
_ => continue, |
|||
}, |
|||
None => break, |
|||
}, |
|||
Err(e) => match e.kind() { |
|||
std::io::ErrorKind::InvalidInput => { |
|||
println!("Invalid gzip header. Skipping file.") |
|||
} |
|||
other_error => println!( |
|||
"Problem with gzip mrt file. `{:?}`. Skipping file.", |
|||
other_error |
|||
), |
|||
}, |
|||
} |
|||
} |
|||
Ok(()) |
|||
} |
|||
|
|||
/// Format IPV4 and IPV6 from slice.
|
|||
fn format_ip(ip: &[u8], is_ipv4: bool) -> Result<IpAddr> { |
|||
let pad = &[0; 17]; |
|||
let ip = [ip, pad].concat(); |
|||
if is_ipv4 { |
|||
Ok(IpAddr::V4(std::net::Ipv4Addr::new( |
|||
ip[0], ip[1], ip[2], ip[3], |
|||
))) |
|||
} else { |
|||
Ok(IpAddr::V6(std::net::Ipv6Addr::new( |
|||
u16::from_be_bytes([ip[0], ip[1]]), |
|||
u16::from_be_bytes([ip[2], ip[3]]), |
|||
u16::from_be_bytes([ip[4], ip[5]]), |
|||
u16::from_be_bytes([ip[7], ip[8]]), |
|||
u16::from_be_bytes([ip[9], ip[10]]), |
|||
u16::from_be_bytes([ip[11], ip[12]]), |
|||
u16::from_be_bytes([ip[13], ip[14]]), |
|||
u16::from_be_bytes([ip[15], ip[16]]), |
|||
))) |
|||
} |
|||
} |
|||
|
|||
/// Parse each RIB Entry.
|
|||
fn match_rib_entry( |
|||
entries: Vec<mrt_rs::records::tabledump::RIBEntry>, |
|||
ip: IpAddr, |
|||
mask: u8, |
|||
mrt_hm: &mut HashMap<Address, HashSet<Vec<u32>>>, |
|||
) -> Result<()> { |
|||
let addr = Address { ip, mask }; |
|||
|
|||
for rib_entry in entries { |
|||
match AsPathParser::parse(&rib_entry.attributes) { |
|||
Ok(mut as_path) => { |
|||
as_path.dedup(); |
|||
mrt_hm |
|||
.entry(addr) |
|||
.or_insert_with(HashSet::new) |
|||
.insert(as_path); |
|||
} |
|||
Err(e) => info!("ERROR: {:?}. ", e), // TODO: Handle error
|
|||
}; |
|||
} |
|||
Ok(()) |
|||
} |
|||
/// Writes the asn bottleneck result to a stdout or a time stamped file
|
|||
pub(crate) fn write(self, out: Option<&Path>) -> Result<()> { |
|||
if let Some(path) = out { |
|||
let epoch = SystemTime::now() |
|||
.duration_since(SystemTime::UNIX_EPOCH) |
|||
.unwrap(); |
|||
let now = epoch.as_secs(); |
|||
let dst = path.join(format!("bottleneck.{}.txt", now)); |
|||
let mut file = File::create(&dst).map_err(|io_error| Error::IoError { |
|||
io_error, |
|||
path: dst.to_path_buf(), |
|||
})?; |
|||
|
|||
self.write_bottleneck(&mut file)?; |
|||
} else { |
|||
self.write_bottleneck(&mut io::stdout())?; |
|||
}; |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
/// Helper write function
|
|||
fn write_bottleneck(self, out: &mut dyn Write) -> Result<(), Error> { |
|||
for (key, value) in self.prefix_asn { |
|||
let text = format!("{}/{}|{:?}", key.ip, key.mask, value); |
|||
writeln!(out, "{}", &text).unwrap(); |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::*; |
|||
|
|||
fn setup_mrt_hm() -> Result<HashMap<Address, HashSet<Vec<u32>>>, Error> { |
|||
let mut mrt_hm: HashMap<Address, HashSet<Vec<u32>>> = HashMap::new(); |
|||
let ip_str = "1.0.139.0"; |
|||
let addr = Address { |
|||
ip: IpAddr::from_str(ip_str).map_err(|addr_parse| Error::AddrParse { |
|||
addr_parse, |
|||
bad_addr: ip_str.to_string(), |
|||
})?, |
|||
mask: 24, |
|||
}; |
|||
|
|||
let mut asn_paths = HashSet::new(); |
|||
asn_paths.insert(vec![2497, 38040, 23969]); |
|||
asn_paths.insert(vec![25152, 6939, 4766, 38040, 23969]); |
|||
asn_paths.insert(vec![4777, 6939, 4766, 38040, 23969]); |
|||
mrt_hm.insert(addr, asn_paths); |
|||
|
|||
let ip_str = "1.0.204.0"; |
|||
let addr = Address { |
|||
ip: IpAddr::from_str(ip_str).map_err(|addr_parse| Error::AddrParse { |
|||
addr_parse, |
|||
bad_addr: ip_str.to_string(), |
|||
})?, |
|||
mask: 22, |
|||
}; |
|||
let mut asn_paths = HashSet::new(); |
|||
asn_paths.insert(vec![2497, 38040, 23969]); |
|||
asn_paths.insert(vec![4777, 6939, 4766, 38040, 23969]); |
|||
asn_paths.insert(vec![25152, 2914, 38040, 23969]); |
|||
mrt_hm.insert(addr, asn_paths); |
|||
|
|||
let ip_str = "1.0.6.0"; |
|||
let addr = Address { |
|||
ip: IpAddr::from_str(ip_str).map_err(|addr_parse| Error::AddrParse { |
|||
addr_parse, |
|||
bad_addr: ip_str.to_string(), |
|||
})?, |
|||
mask: 24, |
|||
}; |
|||
let mut asn_paths = HashSet::new(); |
|||
asn_paths.insert(vec![2497, 4826, 38803, 56203]); |
|||
asn_paths.insert(vec![25152, 6939, 4826, 38803, 56203]); |
|||
asn_paths.insert(vec![4777, 6939, 4826, 38803, 56203]); |
|||
mrt_hm.insert(addr, asn_paths); |
|||
|
|||
Ok(mrt_hm) |
|||
} |
|||
|
|||
#[test] |
|||
fn finds_common_suffix_from_mrt_hashmap() -> Result<(), Error> { |
|||
let mut want: HashMap<Address, Vec<u32>> = HashMap::new(); |
|||
want.insert(Address::from_str("1.0.139.0/24")?, vec![23969, 38040]); |
|||
want.insert(Address::from_str("1.0.204.0/22")?, vec![23969, 38040]); |
|||
want.insert(Address::from_str("1.0.6.0/24")?, vec![56203, 38803, 4826]); |
|||
|
|||
let mut mrt_hm = setup_mrt_hm()?; |
|||
let mut have: HashMap<Address, Vec<u32>> = HashMap::new(); |
|||
|
|||
assert_eq!( |
|||
FindBottleneck::find_common_suffix(&mut mrt_hm, &mut have)?, |
|||
() |
|||
); |
|||
assert_eq!(have, want); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn finds_as_bottleneck_from_mrt_hashmap() -> Result<(), Error> { |
|||
let mut want = FindBottleneck { |
|||
prefix_asn: HashMap::new(), |
|||
}; |
|||
want.prefix_asn |
|||
.insert(Address::from_str("1.0.139.0/24")?, 38040); |
|||
want.prefix_asn |
|||
.insert(Address::from_str("1.0.204.0/22")?, 38040); |
|||
want.prefix_asn |
|||
.insert(Address::from_str("1.0.6.0/24")?, 4826); |
|||
|
|||
let mut have = FindBottleneck { |
|||
prefix_asn: HashMap::new(), |
|||
}; |
|||
let mut mrt_hm = setup_mrt_hm()?; |
|||
have.find_as_bottleneck(&mut mrt_hm)?; |
|||
|
|||
assert_eq!(have, want); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn ipaddr_from_ipv6_short() -> Result<(), Error> { |
|||
let have = FindBottleneck::format_ip(&[32, 1, 3, 24], false)?; |
|||
assert_eq!("2001:318::".parse(), Ok(have)); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
#[test] |
|||
fn ipaddr_from_ipv6_long() -> Result<(), Error> { |
|||
let have = FindBottleneck::format_ip(&[32, 1, 2, 248, 16, 8], false)?; |
|||
assert_eq!("2001:2f8:1008::".parse(), Ok(have)); |
|||
|
|||
Ok(()) |
|||
} |
|||
} |
@ -1,53 +1,13 @@ |
|||
mod address; |
|||
mod bgp_path; |
|||
mod as_path_parser; |
|||
mod common; |
|||
mod error; |
|||
|
|||
pub(crate) use crate::common::*; |
|||
|
|||
fn main() -> Result<(), Error> { |
|||
let path = "./data/dump_01_2019-12-28"; |
|||
// let input = File::open(path).unwrap();
|
|||
let input = File::open(path).map_err(|error| Error::IoError { io_error: error })?; |
|||
let buffered = BufReader::new(input); |
|||
|
|||
let mut prefix_to_as_paths: HashMap<Address, HashSet<Vec<u32>>> = HashMap::new(); |
|||
|
|||
for line in buffered.lines() { |
|||
let l = line.unwrap(); |
|||
let bgp_line = BGPPath::from_str(&l)?; |
|||
prefix_to_as_paths |
|||
.entry(bgp_line.addr) |
|||
.or_insert_with(HashSet::new) |
|||
.insert(bgp_line.as_path); |
|||
} |
|||
|
|||
let mut prefix_to_common_suffix: HashMap<&Address, Vec<u32>> = HashMap::new(); |
|||
for (prefix, as_paths) in prefix_to_as_paths.iter() { |
|||
let mut as_paths_sorted: Vec<&Vec<u32>> = as_paths.iter().collect(); |
|||
as_paths_sorted.sort_by(|a, b| a.len().cmp(&b.len())); // descending
|
|||
let mut rev_common_suffix: Vec<u32> = as_paths_sorted[0].to_vec(); |
|||
rev_common_suffix.reverse(); |
|||
for as_path in as_paths_sorted.iter().skip(1) { |
|||
// first one is already in rev_common_suffix
|
|||
let mut rev_as_path: Vec<u32> = as_path.to_vec(); |
|||
rev_as_path.reverse(); |
|||
|
|||
// every IP should always belong to only one AS
|
|||
assert!(rev_common_suffix.first() == rev_as_path.first()); |
|||
|
|||
// first element is already checked
|
|||
for i in 1..rev_common_suffix.len() { |
|||
if rev_as_path[i] != rev_common_suffix[i] { |
|||
rev_common_suffix.truncate(i); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
rev_common_suffix.reverse(); |
|||
prefix_to_common_suffix |
|||
.entry(prefix) |
|||
.or_insert(rev_common_suffix); |
|||
} |
|||
Ok(()) |
|||
mod find_bottleneck; |
|||
mod opt; |
|||
mod subcommand; |
|||
|
|||
use crate::common::*; |
|||
fn main() -> Result<()> { |
|||
pretty_env_logger::init(); |
|||
Opt::from_args().run() |
|||
} |
|||
|
@ -0,0 +1,38 @@ |
|||
use crate::common::*; |
|||
|
|||
#[derive(Debug, PartialEq, StructOpt)] |
|||
#[structopt(
|
|||
name = "asmap", |
|||
about = "Parse mrt formatted files and find asn bottleneck" |
|||
)] |
|||
pub(crate) struct Opt { |
|||
#[structopt(subcommand)] |
|||
pub(crate) cmd: Subcommand, |
|||
} |
|||
|
|||
impl Opt { |
|||
pub(crate) fn run(self) -> Result<(), Error> { |
|||
self.cmd.run() |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod tests { |
|||
use super::*; |
|||
|
|||
#[test] |
|||
fn cli_download_basic() -> Result<(), structopt::clap::Error> { |
|||
let have = |
|||
Opt::from_iter_safe(vec!["asmap", "download", "--ripe_collector_number", "1,2"])?; |
|||
|
|||
let want = Opt { |
|||
cmd: Subcommand::Download { |
|||
out: "dump".into(), |
|||
ripe_collector_number: vec![1, 2], |
|||
}, |
|||
}; |
|||
|
|||
assert_eq!(have, want); |
|||
Ok(()) |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
use crate::common::*; |
|||
|
|||
#[derive(Debug, PartialEq, StructOpt)] |
|||
pub(crate) enum Subcommand { |
|||
/// Downloads and saves the MRT formatted gz files
|
|||
Download { |
|||
/// Range of specific RIS files to download [default: [00, 24]]
|
|||
#[structopt(
|
|||
name = "RIPE_COLLECTOR_NUMBER", |
|||
long = "ripe_collector_number", |
|||
short = "n", |
|||
use_delimiter(true) |
|||
)] |
|||
ripe_collector_number: Vec<u32>, |
|||
|
|||
/// Directory to write MRT formatted gz files
|
|||
#[structopt(name = "OUT", long = "out", short = "o", default_value = "dump")] |
|||
out: PathBuf, |
|||
}, |
|||
/// Reads and decompresses the MRT gz files, parses the AS Paths, determines the AS bottleneck, saves result
|
|||
FindBottleneck { |
|||
/// Directory path of the MRT formatted gz files to find bottleneck of
|
|||
#[structopt(name = "DIRECTORY", long = "dir", short = "d")] |
|||
dir: PathBuf, |
|||
|
|||
/// Directory to write result [default: print to stdout]
|
|||
#[structopt(name = "OUT", long = "out", short = "o")] |
|||
out: Option<PathBuf>, |
|||
}, |
|||
} |
|||
|
|||
impl Subcommand { |
|||
pub(crate) fn run(self) -> Result<(), Error> { |
|||
match self { |
|||
Self::Download { |
|||
out, |
|||
ripe_collector_number, |
|||
} => Self::download(&out, &ripe_collector_number), |
|||
Self::FindBottleneck { dir, out } => Self::find_bottleneck(&dir, out.as_deref()), |
|||
} |
|||
} |
|||
|
|||
/// Downloads the gz file from data.ris.ripe.net and save to the corresponding directory.
|
|||
fn download(out: &Path, ripe_collector_number: &[u32]) -> Result<()> { |
|||
// Create target directory
|
|||
fs::create_dir_all(out).map_err(|io_error| Error::IoError { |
|||
io_error, |
|||
path: out.into(), |
|||
})?; |
|||
|
|||
if ripe_collector_number.is_empty() { |
|||
for i in 0..=24 { |
|||
Self::download_file(out, i)?; |
|||
} |
|||
} else { |
|||
for i in ripe_collector_number { |
|||
Self::download_file(out, *i)?; |
|||
} |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
fn download_file(out: &Path, number: u32) -> Result<()> { |
|||
let url = format!("http://data.ris.ripe.net/rrc{:02}/latest-bview.gz", number); |
|||
let mut res = reqwest::blocking::get(&url).map_err(|reqwest_error| Error::Reqwest { |
|||
url: url.to_string(), |
|||
reqwest_error, |
|||
})?; |
|||
|
|||
let dst = out.join(format!("rrc{:02}-latest-bview.gz", number)); |
|||
let file = File::create(&dst).map_err(|io_error| Error::IoError { |
|||
io_error, |
|||
path: dst.to_path_buf(), |
|||
})?; |
|||
|
|||
let mut buf_write = BufWriter::new(file); |
|||
io::copy(&mut res, &mut buf_write).map_err(|io_error| Error::IoError { |
|||
io_error, |
|||
path: out.to_path_buf(), |
|||
})?; |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
/// Reads gz mrt data from urls defined by range, decompresses them, parses mrt output, finds bottleneck.
|
|||
fn find_bottleneck(dump: &PathBuf, out: Option<&Path>) -> Result<()> { |
|||
let bottleneck = FindBottleneck::locate(dump)?; |
|||
bottleneck.write(out)?; |
|||
|
|||
Ok(()) |
|||
} |
|||
} |
Loading…
Reference in new issue