Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian committed Feb 2, 2019
1 parent 0c78991 commit cfef946
Show file tree
Hide file tree
Showing 10 changed files with 780 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# IDE specific ignores
/.idea/
32 changes: 32 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
language: rust
cache: cargo
sudo: required

rust:
- stable
- beta

before_script:
- rustup component add clippy

addons:
apt:
packages:
- libcurl4-openssl-dev
- libelf-dev
- libdw-dev
- cmake
- gcc
- binutils-dev
- libiberty-dev

script:
- cargo build --verbose
- cargo test --verbose
- cargo doc --verbose
- cargo clippy --verbose

after_success:
- wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && tar xzf master.tar.gz && cd kcov-master && mkdir build && cd build && cmake .. && make && make install DESTDIR=../../kcov-build && cd ../.. && rm -rf kcov-master
- for file in target/debug/*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done;
- bash <(curl -s https://codecov.io/bash)
33 changes: 33 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "bgp-rs"
version = "0.1.0"
authors = ["Christian Veenman <[email protected]>"]
edition = '2018'
readme = "README.md"
keywords = ["bgp", "parser"]
categories = ["parsing", "network-programming"]
repository = "https://github.com/DevQps/bgp-rs"
homepage = "https://github.com/DevQps/bgp-rs"
documentation = "https://docs.rs/bgp-rs"
description = "A library for parsing Border Gateway Protocol (BGP) formatted streams."
license = "GPL-3.0"
exclude = [
"README.md",
"res/*",
"tests/*",
".travis.yml"
]

[badges]
travis-ci = { repository = "DevQps/bgp-rs", branch = "master" }
codecov = { repository = "DevQps/bgp-rs", branch = "master", service = "github" }
maintenance = { status = "actively-developed" }

[dependencies.byteorder]
version = "1.3.1"
features = ["i128"]

[dev-dependencies]
libflate = "0.1"
mrt-rs = "0.3"

10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
# bgp-rs
A parser for the Border Gateway Protocol (BGP) in Rust
# Border Gateway Protocol in Rust (bgp-rs)
[![Build Status](https://travis-ci.com/DevQps/bgp-rs.svg?branch=master)](https://travis-ci.com/DevQps/bgp-rs) [![codecov](https://codecov.io/gh/DevQps/bgp-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/DevQps/bgp-rs)

A library for parsing Border Gateway Protocol (BGP) formatted streams in Rust.
Messages such as UPDATE, OPEN, KEEPALIVE and

## Examples & Documentation
For examples and documentation look [here](https://docs.rs/bgp-rs/).
Binary file added res/bview.20100101.0759.gz
Binary file not shown.
Binary file added res/updates.20190101.0000.gz
Binary file not shown.
272 changes: 272 additions & 0 deletions src/attributes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
use crate::{Prefix, AFI};
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Cursor, Error, ErrorKind, Read};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum Attribute {
ORIGIN(Origin),
AS_PATH(ASPath),
NEXT_HOP(IpAddr),
MULTI_EXIT_DISC(u32),
LOCAL_PREF(u32),
ATOMIC_AGGREGATOR,
AGGREGATOR((u32, Ipv4Addr)),
COMMUNITY(Vec<u32>),
ORIGINATOR_ID(u32),
CLUSTER_LIST,
DPA,
ADVERTISER,
CLUSTER_ID,
MP_REACH_NLRI(MPReachNLRI),
MP_UNREACH_NLRI(MPUnreachNLRI),
EXTENDED_COMMUNITIES(Vec<u64>),
AS4_PATH,
AS4_AGGREGATOR,
SSA,
CONNECTOR,
AS_PATHLIMIT,
PMSI_TUNNEL,
TUNNEL_ENCAPSULATION,
TRAFFIC_ENGINEERING,
IPV6_SPECIFIC_EXTENDED_COMMUNITY,
AIGP,
PE_DISTINGUISHER_LABELS,
BGP_LS,
LARGE_COMMUNITY(Vec<(u32, u32, u32)>),
BGPSEC_PATH,
BGP_PREFIX_SID,
ATTR_SET,
}

impl Attribute {
pub fn parse(stream: &mut Read) -> Result<Attribute, Error> {
let flags = stream.read_u8()?;
let code = stream.read_u8()?;

// Check if the Extended Length bit is set.
let length: u16 = if flags & (1 << 4) == 0 {
stream.read_u8()? as u16
} else {
stream.read_u16::<BigEndian>()?
};

match code {
1 => Ok(Attribute::ORIGIN(Origin::parse(stream)?)),
2 => Ok(Attribute::AS_PATH(ASPath::parse(stream, length)?)),
3 => {
let ip: IpAddr = if length == 4 {
IpAddr::V4(Ipv4Addr::from(stream.read_u32::<BigEndian>()?))
} else {
IpAddr::V6(Ipv6Addr::from(stream.read_u128::<BigEndian>()?))
};

Ok(Attribute::NEXT_HOP(ip))
}
4 => Ok(Attribute::MULTI_EXIT_DISC(stream.read_u32::<BigEndian>()?)),
5 => Ok(Attribute::LOCAL_PREF(stream.read_u32::<BigEndian>()?)),
6 => Ok(Attribute::ATOMIC_AGGREGATOR),
7 => {
let asn = if length == 6 {
stream.read_u16::<BigEndian>()? as u32
} else {
stream.read_u32::<BigEndian>()?
};

let ip = Ipv4Addr::from(stream.read_u32::<BigEndian>()?);
Ok(Attribute::AGGREGATOR((asn, ip)))
}
8 => {
let mut communities = Vec::with_capacity((length / 4) as usize);
for _ in 0..(length / 4) {
communities.push(stream.read_u32::<BigEndian>()?)
}

Ok(Attribute::COMMUNITY(communities))
}
9 => Ok(Attribute::ORIGINATOR_ID(stream.read_u32::<BigEndian>()?)),
14 => Ok(Attribute::MP_REACH_NLRI(MPReachNLRI::parse(
stream, length,
)?)),
15 => Ok(Attribute::MP_UNREACH_NLRI(MPUnreachNLRI::parse(
stream, length,
)?)),
16 => {
let mut communities = Vec::with_capacity((length / 8) as usize);
for _ in 0..(length / 8) {
communities.push(stream.read_u64::<BigEndian>()?)
}

Ok(Attribute::EXTENDED_COMMUNITIES(communities))
}
32 => {
let mut communities: Vec<(u32, u32, u32)> =
Vec::with_capacity((length / 12) as usize);
for _ in 0..(length / 12) {
let admin = stream.read_u32::<BigEndian>()?;
let part1 = stream.read_u32::<BigEndian>()?;
let part2 = stream.read_u32::<BigEndian>()?;
communities.push((admin, part1, part2))
}

Ok(Attribute::LARGE_COMMUNITY(communities))
}
x => {
let mut buffer = vec![0; length as usize];
stream.read_exact(&mut buffer);

Err(Error::new(
ErrorKind::Other,
format!("Unknown path attribute type found: {}", x),
))
}
}
}
}

#[derive(Debug)]
pub enum Origin {
IGP,
EGP,
INCOMPLETE,
}

impl Origin {
pub fn parse(stream: &mut Read) -> Result<Origin, Error> {
match stream.read_u8()? {
0 => Ok(Origin::IGP),
1 => Ok(Origin::EGP),
2 => Ok(Origin::INCOMPLETE),
_ => Err(Error::new(ErrorKind::Other, "Unknown origin type found.")),
}
}
}

#[derive(Debug)]
pub struct ASPath {
pub segments: Vec<Segment>,
}

impl ASPath {
// TODO: Give argument that determines the AS size.
pub fn parse(stream: &mut Read, length: u16) -> Result<ASPath, Error> {
// Create an AS_PATH struct with a capacity of 1, since AS_SETs
// or multiple AS_SEQUENCES, are not seen often anymore.
let mut path = ASPath {
segments: Vec::with_capacity(1),
};

// While there are multiple AS_PATH segments, parse the segments.
let mut size = length;
while size != 0 {
let segment_type = stream.read_u8()?;
let length = stream.read_u8()?;
let mut values: Vec<u32> = Vec::with_capacity(length as usize);

for _ in 0..length {
values.push(stream.read_u32::<BigEndian>()?);
}

match segment_type {
1 => path.segments.push(Segment::AS_SEQUENCE(values)),
2 => path.segments.push(Segment::AS_SET(values)),
x => {
return Err(Error::new(
ErrorKind::Other,
format!("Unknown AS_PATH segment type found: {}", x),
));
}
}

size -= 2 + (length as u16 * 4);
}

Ok(path)
}
}

#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum Segment {
AS_SEQUENCE(Vec<u32>),
AS_SET(Vec<u32>),
}

#[derive(Debug)]
pub struct MPReachNLRI {
pub afi: AFI,
pub safi: u8,
pub next_hop: Vec<u8>,
pub announced_routes: Vec<Prefix>,
}

impl MPReachNLRI {
// TODO: Give argument that determines the AS size.
pub fn parse(stream: &mut Read, length: u16) -> Result<MPReachNLRI, Error> {
let afi = AFI::from(stream.read_u16::<BigEndian>()?)?;
let safi = stream.read_u8()?;

let next_hop_length = stream.read_u8()?;
let mut next_hop = vec![0; next_hop_length as usize];
stream.read_exact(&mut next_hop)?;

let _reserved = stream.read_u8()?;

// ----------------------------
// Read NLRI
// ----------------------------
let size = length - (5 + next_hop_length) as u16;

let mut buffer = vec![0; size as usize];
stream.read_exact(&mut buffer);
let mut cursor = Cursor::new(buffer);
let mut announced_routes: Vec<Prefix> = Vec::with_capacity(4);

while cursor.position() < size as u64 {
announced_routes.push(Prefix::parse(&mut cursor)?);
}

Ok(MPReachNLRI {
afi,
safi,
next_hop,
announced_routes,
})
}
}

#[derive(Debug)]
pub struct MPUnreachNLRI {
pub afi: AFI,
pub safi: u8,
pub withdrawn_routes: Vec<Prefix>,
}

impl MPUnreachNLRI {
// TODO: Give argument that determines the AS size.
pub fn parse(stream: &mut Read, length: u16) -> Result<MPUnreachNLRI, Error> {
let afi = AFI::from(stream.read_u16::<BigEndian>()?)?;
let safi = stream.read_u8()?;

// ----------------------------
// Read NLRI
// ----------------------------
let size = length - 3 as u16;

let mut buffer = vec![0; size as usize];
stream.read_exact(&mut buffer);
let mut cursor = Cursor::new(buffer);
let mut withdrawn_routes: Vec<Prefix> = Vec::with_capacity(4);

while cursor.position() < size as u64 {
withdrawn_routes.push(Prefix::parse(&mut cursor)?);
}

Ok(MPUnreachNLRI {
afi,
safi,
withdrawn_routes,
})
}
}
Loading

0 comments on commit cfef946

Please sign in to comment.