Skip to content

Commit cfef946

Browse files
author
Christian
committed
Initial version
1 parent 0c78991 commit cfef946

10 files changed

+780
-2
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ Cargo.lock
88

99
# These are backup files generated by rustfmt
1010
**/*.rs.bk
11+
12+
# IDE specific ignores
13+
/.idea/

.travis.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
language: rust
2+
cache: cargo
3+
sudo: required
4+
5+
rust:
6+
- stable
7+
- beta
8+
9+
before_script:
10+
- rustup component add clippy
11+
12+
addons:
13+
apt:
14+
packages:
15+
- libcurl4-openssl-dev
16+
- libelf-dev
17+
- libdw-dev
18+
- cmake
19+
- gcc
20+
- binutils-dev
21+
- libiberty-dev
22+
23+
script:
24+
- cargo build --verbose
25+
- cargo test --verbose
26+
- cargo doc --verbose
27+
- cargo clippy --verbose
28+
29+
after_success:
30+
- 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
31+
- 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;
32+
- bash <(curl -s https://codecov.io/bash)

Cargo.toml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[package]
2+
name = "bgp-rs"
3+
version = "0.1.0"
4+
authors = ["Christian Veenman <[email protected]>"]
5+
edition = '2018'
6+
readme = "README.md"
7+
keywords = ["bgp", "parser"]
8+
categories = ["parsing", "network-programming"]
9+
repository = "https://github.com/DevQps/bgp-rs"
10+
homepage = "https://github.com/DevQps/bgp-rs"
11+
documentation = "https://docs.rs/bgp-rs"
12+
description = "A library for parsing Border Gateway Protocol (BGP) formatted streams."
13+
license = "GPL-3.0"
14+
exclude = [
15+
"README.md",
16+
"res/*",
17+
"tests/*",
18+
".travis.yml"
19+
]
20+
21+
[badges]
22+
travis-ci = { repository = "DevQps/bgp-rs", branch = "master" }
23+
codecov = { repository = "DevQps/bgp-rs", branch = "master", service = "github" }
24+
maintenance = { status = "actively-developed" }
25+
26+
[dependencies.byteorder]
27+
version = "1.3.1"
28+
features = ["i128"]
29+
30+
[dev-dependencies]
31+
libflate = "0.1"
32+
mrt-rs = "0.3"
33+

README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1-
# bgp-rs
2-
A parser for the Border Gateway Protocol (BGP) in Rust
1+
# Border Gateway Protocol in Rust (bgp-rs)
2+
[![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)
3+
4+
A library for parsing Border Gateway Protocol (BGP) formatted streams in Rust.
5+
Messages such as UPDATE, OPEN, KEEPALIVE and
6+
7+
## Examples & Documentation
8+
For examples and documentation look [here](https://docs.rs/bgp-rs/).

res/bview.20100101.0759.gz

19.2 MB
Binary file not shown.

res/updates.20190101.0000.gz

1.23 MB
Binary file not shown.

src/attributes.rs

+272
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
use crate::{Prefix, AFI};
2+
use byteorder::{BigEndian, ReadBytesExt};
3+
use std::io::{Cursor, Error, ErrorKind, Read};
4+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
5+
6+
#[derive(Debug)]
7+
#[allow(non_camel_case_types)]
8+
pub enum Attribute {
9+
ORIGIN(Origin),
10+
AS_PATH(ASPath),
11+
NEXT_HOP(IpAddr),
12+
MULTI_EXIT_DISC(u32),
13+
LOCAL_PREF(u32),
14+
ATOMIC_AGGREGATOR,
15+
AGGREGATOR((u32, Ipv4Addr)),
16+
COMMUNITY(Vec<u32>),
17+
ORIGINATOR_ID(u32),
18+
CLUSTER_LIST,
19+
DPA,
20+
ADVERTISER,
21+
CLUSTER_ID,
22+
MP_REACH_NLRI(MPReachNLRI),
23+
MP_UNREACH_NLRI(MPUnreachNLRI),
24+
EXTENDED_COMMUNITIES(Vec<u64>),
25+
AS4_PATH,
26+
AS4_AGGREGATOR,
27+
SSA,
28+
CONNECTOR,
29+
AS_PATHLIMIT,
30+
PMSI_TUNNEL,
31+
TUNNEL_ENCAPSULATION,
32+
TRAFFIC_ENGINEERING,
33+
IPV6_SPECIFIC_EXTENDED_COMMUNITY,
34+
AIGP,
35+
PE_DISTINGUISHER_LABELS,
36+
BGP_LS,
37+
LARGE_COMMUNITY(Vec<(u32, u32, u32)>),
38+
BGPSEC_PATH,
39+
BGP_PREFIX_SID,
40+
ATTR_SET,
41+
}
42+
43+
impl Attribute {
44+
pub fn parse(stream: &mut Read) -> Result<Attribute, Error> {
45+
let flags = stream.read_u8()?;
46+
let code = stream.read_u8()?;
47+
48+
// Check if the Extended Length bit is set.
49+
let length: u16 = if flags & (1 << 4) == 0 {
50+
stream.read_u8()? as u16
51+
} else {
52+
stream.read_u16::<BigEndian>()?
53+
};
54+
55+
match code {
56+
1 => Ok(Attribute::ORIGIN(Origin::parse(stream)?)),
57+
2 => Ok(Attribute::AS_PATH(ASPath::parse(stream, length)?)),
58+
3 => {
59+
let ip: IpAddr = if length == 4 {
60+
IpAddr::V4(Ipv4Addr::from(stream.read_u32::<BigEndian>()?))
61+
} else {
62+
IpAddr::V6(Ipv6Addr::from(stream.read_u128::<BigEndian>()?))
63+
};
64+
65+
Ok(Attribute::NEXT_HOP(ip))
66+
}
67+
4 => Ok(Attribute::MULTI_EXIT_DISC(stream.read_u32::<BigEndian>()?)),
68+
5 => Ok(Attribute::LOCAL_PREF(stream.read_u32::<BigEndian>()?)),
69+
6 => Ok(Attribute::ATOMIC_AGGREGATOR),
70+
7 => {
71+
let asn = if length == 6 {
72+
stream.read_u16::<BigEndian>()? as u32
73+
} else {
74+
stream.read_u32::<BigEndian>()?
75+
};
76+
77+
let ip = Ipv4Addr::from(stream.read_u32::<BigEndian>()?);
78+
Ok(Attribute::AGGREGATOR((asn, ip)))
79+
}
80+
8 => {
81+
let mut communities = Vec::with_capacity((length / 4) as usize);
82+
for _ in 0..(length / 4) {
83+
communities.push(stream.read_u32::<BigEndian>()?)
84+
}
85+
86+
Ok(Attribute::COMMUNITY(communities))
87+
}
88+
9 => Ok(Attribute::ORIGINATOR_ID(stream.read_u32::<BigEndian>()?)),
89+
14 => Ok(Attribute::MP_REACH_NLRI(MPReachNLRI::parse(
90+
stream, length,
91+
)?)),
92+
15 => Ok(Attribute::MP_UNREACH_NLRI(MPUnreachNLRI::parse(
93+
stream, length,
94+
)?)),
95+
16 => {
96+
let mut communities = Vec::with_capacity((length / 8) as usize);
97+
for _ in 0..(length / 8) {
98+
communities.push(stream.read_u64::<BigEndian>()?)
99+
}
100+
101+
Ok(Attribute::EXTENDED_COMMUNITIES(communities))
102+
}
103+
32 => {
104+
let mut communities: Vec<(u32, u32, u32)> =
105+
Vec::with_capacity((length / 12) as usize);
106+
for _ in 0..(length / 12) {
107+
let admin = stream.read_u32::<BigEndian>()?;
108+
let part1 = stream.read_u32::<BigEndian>()?;
109+
let part2 = stream.read_u32::<BigEndian>()?;
110+
communities.push((admin, part1, part2))
111+
}
112+
113+
Ok(Attribute::LARGE_COMMUNITY(communities))
114+
}
115+
x => {
116+
let mut buffer = vec![0; length as usize];
117+
stream.read_exact(&mut buffer);
118+
119+
Err(Error::new(
120+
ErrorKind::Other,
121+
format!("Unknown path attribute type found: {}", x),
122+
))
123+
}
124+
}
125+
}
126+
}
127+
128+
#[derive(Debug)]
129+
pub enum Origin {
130+
IGP,
131+
EGP,
132+
INCOMPLETE,
133+
}
134+
135+
impl Origin {
136+
pub fn parse(stream: &mut Read) -> Result<Origin, Error> {
137+
match stream.read_u8()? {
138+
0 => Ok(Origin::IGP),
139+
1 => Ok(Origin::EGP),
140+
2 => Ok(Origin::INCOMPLETE),
141+
_ => Err(Error::new(ErrorKind::Other, "Unknown origin type found.")),
142+
}
143+
}
144+
}
145+
146+
#[derive(Debug)]
147+
pub struct ASPath {
148+
pub segments: Vec<Segment>,
149+
}
150+
151+
impl ASPath {
152+
// TODO: Give argument that determines the AS size.
153+
pub fn parse(stream: &mut Read, length: u16) -> Result<ASPath, Error> {
154+
// Create an AS_PATH struct with a capacity of 1, since AS_SETs
155+
// or multiple AS_SEQUENCES, are not seen often anymore.
156+
let mut path = ASPath {
157+
segments: Vec::with_capacity(1),
158+
};
159+
160+
// While there are multiple AS_PATH segments, parse the segments.
161+
let mut size = length;
162+
while size != 0 {
163+
let segment_type = stream.read_u8()?;
164+
let length = stream.read_u8()?;
165+
let mut values: Vec<u32> = Vec::with_capacity(length as usize);
166+
167+
for _ in 0..length {
168+
values.push(stream.read_u32::<BigEndian>()?);
169+
}
170+
171+
match segment_type {
172+
1 => path.segments.push(Segment::AS_SEQUENCE(values)),
173+
2 => path.segments.push(Segment::AS_SET(values)),
174+
x => {
175+
return Err(Error::new(
176+
ErrorKind::Other,
177+
format!("Unknown AS_PATH segment type found: {}", x),
178+
));
179+
}
180+
}
181+
182+
size -= 2 + (length as u16 * 4);
183+
}
184+
185+
Ok(path)
186+
}
187+
}
188+
189+
#[derive(Debug)]
190+
#[allow(non_camel_case_types)]
191+
pub enum Segment {
192+
AS_SEQUENCE(Vec<u32>),
193+
AS_SET(Vec<u32>),
194+
}
195+
196+
#[derive(Debug)]
197+
pub struct MPReachNLRI {
198+
pub afi: AFI,
199+
pub safi: u8,
200+
pub next_hop: Vec<u8>,
201+
pub announced_routes: Vec<Prefix>,
202+
}
203+
204+
impl MPReachNLRI {
205+
// TODO: Give argument that determines the AS size.
206+
pub fn parse(stream: &mut Read, length: u16) -> Result<MPReachNLRI, Error> {
207+
let afi = AFI::from(stream.read_u16::<BigEndian>()?)?;
208+
let safi = stream.read_u8()?;
209+
210+
let next_hop_length = stream.read_u8()?;
211+
let mut next_hop = vec![0; next_hop_length as usize];
212+
stream.read_exact(&mut next_hop)?;
213+
214+
let _reserved = stream.read_u8()?;
215+
216+
// ----------------------------
217+
// Read NLRI
218+
// ----------------------------
219+
let size = length - (5 + next_hop_length) as u16;
220+
221+
let mut buffer = vec![0; size as usize];
222+
stream.read_exact(&mut buffer);
223+
let mut cursor = Cursor::new(buffer);
224+
let mut announced_routes: Vec<Prefix> = Vec::with_capacity(4);
225+
226+
while cursor.position() < size as u64 {
227+
announced_routes.push(Prefix::parse(&mut cursor)?);
228+
}
229+
230+
Ok(MPReachNLRI {
231+
afi,
232+
safi,
233+
next_hop,
234+
announced_routes,
235+
})
236+
}
237+
}
238+
239+
#[derive(Debug)]
240+
pub struct MPUnreachNLRI {
241+
pub afi: AFI,
242+
pub safi: u8,
243+
pub withdrawn_routes: Vec<Prefix>,
244+
}
245+
246+
impl MPUnreachNLRI {
247+
// TODO: Give argument that determines the AS size.
248+
pub fn parse(stream: &mut Read, length: u16) -> Result<MPUnreachNLRI, Error> {
249+
let afi = AFI::from(stream.read_u16::<BigEndian>()?)?;
250+
let safi = stream.read_u8()?;
251+
252+
// ----------------------------
253+
// Read NLRI
254+
// ----------------------------
255+
let size = length - 3 as u16;
256+
257+
let mut buffer = vec![0; size as usize];
258+
stream.read_exact(&mut buffer);
259+
let mut cursor = Cursor::new(buffer);
260+
let mut withdrawn_routes: Vec<Prefix> = Vec::with_capacity(4);
261+
262+
while cursor.position() < size as u64 {
263+
withdrawn_routes.push(Prefix::parse(&mut cursor)?);
264+
}
265+
266+
Ok(MPUnreachNLRI {
267+
afi,
268+
safi,
269+
withdrawn_routes,
270+
})
271+
}
272+
}

0 commit comments

Comments
 (0)