Re-import repository.
This commit is contained in:
660
bgpd/src/bgp_packet/capabilities.rs
Normal file
660
bgpd/src/bgp_packet/capabilities.rs
Normal file
@ -0,0 +1,660 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use crate::bgp_packet::constants::SubsequentAddressFamilyIdentifier;
|
||||
use crate::bgp_packet::traits::BGPParserError;
|
||||
use crate::bgp_packet::traits::ParserContext;
|
||||
use crate::bgp_packet::traits::ReadablePacket;
|
||||
use crate::bgp_packet::traits::WritablePacket;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use nom::number::complete::{be_u16, be_u8};
|
||||
use nom::Err::Failure;
|
||||
use nom::IResult;
|
||||
use std::fmt;
|
||||
|
||||
/// BGPOpenOptionType represents the option types in the Open message.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
|
||||
pub struct BGPOpenOptionType(pub u8);
|
||||
|
||||
impl BGPOpenOptionType {
|
||||
pub fn new(val: u8) -> BGPOpenOptionType {
|
||||
BGPOpenOptionType(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for BGPOpenOptionType {
|
||||
fn into(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub mod BGPOpenOptionTypeValues {
|
||||
use super::BGPOpenOptionType;
|
||||
|
||||
pub const CAPABILITIES: BGPOpenOptionType = BGPOpenOptionType(2);
|
||||
}
|
||||
|
||||
/// OpenOptionValue represents something which can be in the payload of OpenOption.
|
||||
trait OpenOptionValue: ReadablePacket + WritablePacket + fmt::Debug {}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct OpenOption {
|
||||
pub option_type: BGPOpenOptionType,
|
||||
pub oval: OpenOptions,
|
||||
}
|
||||
|
||||
impl ReadablePacket for OpenOption {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], OpenOption, BGPParserError<&'a [u8]>> {
|
||||
let (buf, typ) = nom::combinator::complete(be_u8)(buf)?;
|
||||
let (buf, val) = match BGPOpenOptionType(typ) {
|
||||
BGPOpenOptionTypeValues::CAPABILITIES => {
|
||||
let (b, cap) = OpenOptionCapabilities::from_wire(ctx, buf)?;
|
||||
(b, OpenOptions::Capabilities(cap))
|
||||
}
|
||||
_ => {
|
||||
// TODO: This should gracefully degrrrrade and not fail the parser.
|
||||
return Err(Failure(BGPParserError::CustomText(
|
||||
"Unknown BGP OPEN option".to_string(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
IResult::Ok((
|
||||
buf,
|
||||
OpenOption {
|
||||
option_type: BGPOpenOptionType(typ),
|
||||
oval: val,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for OpenOption {
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf = Vec::new();
|
||||
match &self.oval {
|
||||
OpenOptions::Capabilities(c) => {
|
||||
buf.push(BGPOpenOptionTypeValues::CAPABILITIES.into());
|
||||
buf.append(&mut c.to_wire(ctx)?);
|
||||
}
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
|
||||
match &self.oval {
|
||||
OpenOptions::Capabilities(c) => {
|
||||
return Ok(2 + c.wire_len(ctx)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for OpenOption {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "OpenOption: {}", self.oval)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum OpenOptions {
|
||||
Capabilities(OpenOptionCapabilities),
|
||||
}
|
||||
|
||||
impl fmt::Display for OpenOptions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
return match &self {
|
||||
OpenOptions::Capabilities(c) => write!(f, "Capabilities: {}", c),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// CapabilityList represents a list of capabilities which can be present in an OpenOption.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct OpenOptionCapabilities {
|
||||
pub caps: Vec<BGPCapability>,
|
||||
}
|
||||
|
||||
impl ReadablePacket for OpenOptionCapabilities {
|
||||
// from wire reads the length and value of the TLV.
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], OpenOptionCapabilities, BGPParserError<&'a [u8]>> {
|
||||
let (buf, caps): (_, Vec<BGPCapability>) = nom::multi::length_value(
|
||||
be_u8,
|
||||
nom::multi::many0(|i| BGPCapability::from_wire(ctx, i)),
|
||||
)(buf)?;
|
||||
return IResult::Ok((buf, OpenOptionCapabilities { caps }));
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for OpenOptionCapabilities {
|
||||
// to_wire writes the length and value of the TLV.
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
buf.push(self.wire_len(ctx).unwrap() as u8);
|
||||
for cap in &self.caps {
|
||||
let mut result: Vec<u8> = (*cap).to_wire(ctx)?;
|
||||
buf.append(&mut result);
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
|
||||
let mut ttl: u16 = 0;
|
||||
for cap in &self.caps {
|
||||
ttl += (*cap).wire_len(ctx)?;
|
||||
}
|
||||
Ok(ttl)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for OpenOptionCapabilities {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Capabilities: [")?;
|
||||
for cap in &self.caps {
|
||||
std::fmt::Display::fmt(cap, f)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
||||
/// BGP Capabilities.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
|
||||
pub struct BGPCapabilityType(pub u8);
|
||||
|
||||
impl BGPCapabilityType {
|
||||
pub fn new(val: u8) -> BGPCapabilityType {
|
||||
BGPCapabilityType(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for BGPCapabilityType {
|
||||
fn into(self) -> u8 {
|
||||
return self.0;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub mod BGPCapabilityTypeValues {
|
||||
use super::BGPCapabilityType;
|
||||
|
||||
/// Multiprotocol Extensions for BGP-4 [RFC2858]
|
||||
pub const MULTPROTOCOL_BGP4: BGPCapabilityType = BGPCapabilityType(1);
|
||||
/// Route Refresh Capability for BGP-4 [RFC2918]
|
||||
pub const ROUTE_REFRESH_BGP4: BGPCapabilityType = BGPCapabilityType(2);
|
||||
/// Outbound Route Filtering Capability [RFC5291]
|
||||
pub const OUTBOUND_ROUTE_FILTERING: BGPCapabilityType = BGPCapabilityType(3);
|
||||
/// Extended Next Hop Encoding [RFC5549]
|
||||
pub const EXTENDED_NEXT_HOP: BGPCapabilityType = BGPCapabilityType(5);
|
||||
/// BGP Extended Message [RFC8654]
|
||||
pub const EXTENDED_MESSAGE: BGPCapabilityType = BGPCapabilityType(6);
|
||||
/// BGPsec Capability [RFC8205]
|
||||
pub const BGPSEC: BGPCapabilityType = BGPCapabilityType(7);
|
||||
/// Multiple Labels Capability [RFC8277]
|
||||
pub const MULTILABEL_COMPAT: BGPCapabilityType = BGPCapabilityType(8);
|
||||
|
||||
/// Graceful Restart Capability [RFC4724]
|
||||
pub const GRACEFUL_RESTART: BGPCapabilityType = BGPCapabilityType(64);
|
||||
/// Support for 4-octet AS number capability [RFC6793]
|
||||
pub const FOUR_BYTE_ASN: BGPCapabilityType = BGPCapabilityType(65);
|
||||
/// ADD-PATH Capability [RFC7911]
|
||||
pub const ADD_PATH: BGPCapabilityType = BGPCapabilityType(69);
|
||||
/// Enhanced Route Refresh Capability [RFC7313]
|
||||
pub const ENHANCED_ROUTE_REFRESH: BGPCapabilityType = BGPCapabilityType(70);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct BGPCapability {
|
||||
pub cap_type: BGPCapabilityType,
|
||||
pub val: BGPCapabilityValue,
|
||||
}
|
||||
|
||||
impl ReadablePacket for BGPCapability {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], BGPCapability, BGPParserError<&'a [u8]>> {
|
||||
let (buf, cap_type) = nom::combinator::peek(be_u8)(buf)?; // Peek the type, if we know it, consume.
|
||||
let (buf, val): (_, BGPCapabilityValue) =
|
||||
match BGPCapabilityType(cap_type) {
|
||||
BGPCapabilityTypeValues::FOUR_BYTE_ASN => {
|
||||
let (buf, _) = be_u8(buf)?; // Consume type
|
||||
let (buf, cap) = nom::multi::length_value(be_u8, |i| {
|
||||
FourByteASNCapability::from_wire(ctx, i)
|
||||
})(buf)?;
|
||||
(buf, BGPCapabilityValue::FourByteASN(cap))
|
||||
}
|
||||
BGPCapabilityTypeValues::MULTPROTOCOL_BGP4 => {
|
||||
let (buf, _) = be_u8(buf)?;
|
||||
let (buf, cap) = nom::multi::length_value(be_u8, |i| {
|
||||
MultiprotocolCapability::from_wire(ctx, i)
|
||||
})(buf)?;
|
||||
(buf, BGPCapabilityValue::Multiprotocol(cap))
|
||||
}
|
||||
BGPCapabilityTypeValues::ROUTE_REFRESH_BGP4 => {
|
||||
let (buf, _) = be_u8(buf)?;
|
||||
let (buf, cap) = nom::multi::length_value(be_u8, |i| {
|
||||
RouteRefreshCapability::from_wire(ctx, i)
|
||||
})(buf)?;
|
||||
(buf, BGPCapabilityValue::RouteRefresh(cap))
|
||||
}
|
||||
BGPCapabilityTypeValues::GRACEFUL_RESTART => {
|
||||
let (buf, _) = be_u8(buf)?;
|
||||
let (buf, cap) = nom::multi::length_value(be_u8, |i| {
|
||||
GracefulRestartCapability::from_wire(ctx, i)
|
||||
})(buf)?;
|
||||
(buf, BGPCapabilityValue::GracefulRestart(cap))
|
||||
}
|
||||
_ => {
|
||||
// If we do not know what this is, then put the bytes in an UnknownCapability.
|
||||
let (buf, cap) = UnknownCapability::from_wire(ctx, buf)?;
|
||||
(buf, BGPCapabilityValue::UnknownCapability(cap))
|
||||
}
|
||||
};
|
||||
IResult::Ok((
|
||||
buf,
|
||||
BGPCapability {
|
||||
cap_type: BGPCapabilityType(cap_type),
|
||||
val,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for BGPCapability {
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
buf.push(self.cap_type.into());
|
||||
match &self.val {
|
||||
BGPCapabilityValue::FourByteASN(v) => {
|
||||
buf.push(v.wire_len(ctx)? as u8);
|
||||
buf.extend_from_slice(&v.to_wire(ctx)?);
|
||||
}
|
||||
BGPCapabilityValue::Multiprotocol(v) => {
|
||||
buf.push(v.wire_len(ctx)? as u8);
|
||||
buf.extend_from_slice(&v.to_wire(ctx)?);
|
||||
}
|
||||
BGPCapabilityValue::RouteRefresh(v) => {
|
||||
buf.push(v.wire_len(ctx)? as u8);
|
||||
buf.extend_from_slice(&v.to_wire(ctx)?);
|
||||
}
|
||||
BGPCapabilityValue::GracefulRestart(v) => {
|
||||
buf.push(v.wire_len(ctx)? as u8);
|
||||
buf.extend_from_slice(&v.to_wire(ctx)?);
|
||||
}
|
||||
BGPCapabilityValue::UnknownCapability(v) => {
|
||||
buf.push(v.wire_len(ctx)? as u8);
|
||||
buf.extend_from_slice(&v.to_wire(ctx)?);
|
||||
}
|
||||
};
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
|
||||
// BGPCapabilityType(u8) + cap_len(u8) + val
|
||||
return match &self.val {
|
||||
BGPCapabilityValue::FourByteASN(v) => Ok(2 + v.wire_len(ctx)?),
|
||||
BGPCapabilityValue::Multiprotocol(v) => Ok(2 + v.wire_len(ctx)?),
|
||||
BGPCapabilityValue::RouteRefresh(v) => Ok(2 + v.wire_len(ctx)?),
|
||||
BGPCapabilityValue::GracefulRestart(v) => Ok(2 + v.wire_len(ctx)?),
|
||||
BGPCapabilityValue::UnknownCapability(v) => Ok(2 + v.wire_len(ctx)?),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BGPCapability {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
std::fmt::Display::fmt(&self.val, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum BGPCapabilityValue {
|
||||
FourByteASN(FourByteASNCapability),
|
||||
Multiprotocol(MultiprotocolCapability),
|
||||
RouteRefresh(RouteRefreshCapability),
|
||||
GracefulRestart(GracefulRestartCapability),
|
||||
UnknownCapability(UnknownCapability),
|
||||
}
|
||||
|
||||
impl fmt::Display for BGPCapabilityValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self {
|
||||
BGPCapabilityValue::FourByteASN(v) => std::fmt::Display::fmt(v, f),
|
||||
BGPCapabilityValue::Multiprotocol(v) => std::fmt::Display::fmt(v, f),
|
||||
BGPCapabilityValue::RouteRefresh(v) => std::fmt::Display::fmt(v, f),
|
||||
BGPCapabilityValue::GracefulRestart(v) => std::fmt::Display::fmt(v, f),
|
||||
BGPCapabilityValue::UnknownCapability(v) => std::fmt::Display::fmt(v, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct UnknownCapability {
|
||||
cap_code: u8,
|
||||
payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ReadablePacket for UnknownCapability {
|
||||
fn from_wire<'a>(
|
||||
_: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
let (buf, typ) = be_u8(buf)?;
|
||||
let (buf, len) = be_u8(buf)?;
|
||||
let (buf, payload) = nom::bytes::complete::take(len)(buf)?;
|
||||
Ok((
|
||||
buf,
|
||||
UnknownCapability {
|
||||
cap_code: typ,
|
||||
payload: payload.to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for UnknownCapability {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf = vec![];
|
||||
// No need to push the type or length on as that's done at a higher level.
|
||||
buf.extend(self.payload.to_owned());
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(self.payload.len() as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UnknownCapability {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "UnknownCapability type: {}", self.cap_code)
|
||||
}
|
||||
}
|
||||
|
||||
/// FourByteASNCapability represents the four byte BGP Capability value.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FourByteASNCapability {
|
||||
pub asn: u32,
|
||||
}
|
||||
|
||||
impl FourByteASNCapability {
|
||||
fn new(asn: u32) -> FourByteASNCapability {
|
||||
FourByteASNCapability { asn }
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadablePacket for FourByteASNCapability {
|
||||
fn from_wire<'a>(
|
||||
_: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
let (buf, asn) = nom::combinator::complete(nom::number::complete::be_u32)(buf)?;
|
||||
return IResult::Ok((buf, FourByteASNCapability::new(asn)));
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for FourByteASNCapability {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf: Vec<u8> = vec![0; 4];
|
||||
byteorder::NetworkEndian::write_u32(&mut buf, self.asn);
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(4)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourByteASNCapability {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "FourByteASN: asn: {}", self.asn)
|
||||
}
|
||||
}
|
||||
|
||||
/// MultiprotocolExtCapability represents support for RFC 4760.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct MultiprotocolCapability {
|
||||
pub afi: AddressFamilyIdentifier,
|
||||
pub safi: SubsequentAddressFamilyIdentifier,
|
||||
}
|
||||
|
||||
impl MultiprotocolCapability {
|
||||
fn new(
|
||||
afi: AddressFamilyIdentifier,
|
||||
safi: SubsequentAddressFamilyIdentifier,
|
||||
) -> MultiprotocolCapability {
|
||||
MultiprotocolCapability { afi, safi }
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadablePacket for MultiprotocolCapability {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], MultiprotocolCapability, BGPParserError<&'a [u8]>> {
|
||||
let (buf, (afi_raw, _, safi_raw)) = nom::combinator::complete(nom::sequence::tuple((
|
||||
|i| AddressFamilyIdentifier::from_wire(ctx, i),
|
||||
nom::bytes::complete::take(1u8),
|
||||
|i| SubsequentAddressFamilyIdentifier::from_wire(ctx, i),
|
||||
)))(buf)?;
|
||||
|
||||
let afi = AddressFamilyIdentifier::try_from(afi_raw)
|
||||
.map_err(|e| nom::Err::Error(BGPParserError::CustomText(e.to_string())))?;
|
||||
let safi = SubsequentAddressFamilyIdentifier::try_from(safi_raw)
|
||||
.map_err(|e| nom::Err::Error(BGPParserError::CustomText(e.to_string())))?;
|
||||
|
||||
IResult::Ok((buf, MultiprotocolCapability::new(afi, safi)))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for MultiprotocolCapability {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
// [ AFI: uint16, 0: uint8, SAFI: uint8 ]
|
||||
let mut res = [0u8; 4];
|
||||
byteorder::NetworkEndian::write_u16(&mut res[..2], self.afi.into());
|
||||
res[3] = self.safi.into();
|
||||
Ok(res.to_vec())
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(4)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MultiprotocolCapability {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "MultiprotocolCapbility: [ {} {} ]", self.afi, self.safi,)
|
||||
}
|
||||
}
|
||||
|
||||
// Route refresh capability
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RouteRefreshCapability {}
|
||||
|
||||
impl WritablePacket for RouteRefreshCapability {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadablePacket for RouteRefreshCapability {
|
||||
fn from_wire<'a>(
|
||||
_: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], RouteRefreshCapability, BGPParserError<&'a [u8]>> {
|
||||
IResult::Ok((buf, RouteRefreshCapability {}))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RouteRefreshCapability {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "RouteRefreshCapability")
|
||||
}
|
||||
}
|
||||
|
||||
// Graceful restart capability
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct GracefulRestartCapability {
|
||||
pub restart_state: bool, // 4 bits total, most sig bit here, rest reserved.
|
||||
pub restart_time_sec: u16, // 12 bits.
|
||||
pub payloads: Vec<GracefulRestartPayload>,
|
||||
}
|
||||
|
||||
// GracefulRestartPayload represents the contents of the graceful restart cap.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct GracefulRestartPayload {
|
||||
pub afi: AddressFamilyIdentifier,
|
||||
pub safi: SubsequentAddressFamilyIdentifier,
|
||||
pub af_flags: bool, // 8 bits total, most significant bit used here.
|
||||
}
|
||||
|
||||
impl ReadablePacket for GracefulRestartPayload {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], GracefulRestartPayload, BGPParserError<&'a [u8]>> {
|
||||
let (buf, (afi, safi, flags)) = nom::combinator::complete(nom::sequence::tuple((
|
||||
|i| AddressFamilyIdentifier::from_wire(ctx, i),
|
||||
|i| SubsequentAddressFamilyIdentifier::from_wire(ctx, i),
|
||||
be_u8,
|
||||
)))(buf)?;
|
||||
IResult::Ok((
|
||||
buf,
|
||||
GracefulRestartPayload {
|
||||
afi,
|
||||
safi,
|
||||
af_flags: (0x80 & flags) != 0,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for GracefulRestartPayload {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let afi: u16 = self.afi.into();
|
||||
let mut res = vec![0u8; 2];
|
||||
byteorder::NetworkEndian::write_u16(res.as_mut(), afi.into());
|
||||
res.push(self.safi.into());
|
||||
res.push(if self.af_flags { 0x80 } else { 0 });
|
||||
Ok(res)
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(4)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for GracefulRestartPayload {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"GracefulRestartPayload: [afi:{} safi:{} af_flags:{}]",
|
||||
self.afi, self.safi, self.af_flags
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadablePacket for GracefulRestartCapability {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
let (buf, state_rt) = nom::combinator::complete(be_u16)(buf)?;
|
||||
let (buf, payloads): (_, Vec<GracefulRestartPayload>) =
|
||||
nom::multi::many0(|i| GracefulRestartPayload::from_wire(ctx, i))(buf)?;
|
||||
let restart_time_sec: u16 = 0x0fff & state_rt; // Lower 14 bits.
|
||||
let restart_state: bool = (0x8000 & state_rt) != 0; // highest bit
|
||||
IResult::Ok((
|
||||
buf,
|
||||
GracefulRestartCapability {
|
||||
restart_state,
|
||||
restart_time_sec,
|
||||
payloads,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for GracefulRestartCapability {
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf: Vec<u8> = vec![0u8; 2];
|
||||
let state_rt: u16 = ((self.restart_state as u16) << 15) | (0xfff & self.restart_time_sec);
|
||||
NetworkEndian::write_u16(&mut buf, state_rt);
|
||||
for item in &self.payloads {
|
||||
buf.append(&mut item.to_wire(ctx)?);
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok((2 + self.payloads.len() * 4) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for GracefulRestartCapability {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "GracefulRestartCapability: [")?;
|
||||
for value in &self.payloads {
|
||||
fmt::Display::fmt(value, f)?;
|
||||
}
|
||||
write!(f, " ]")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::BGPCapability;
|
||||
use super::BGPCapabilityTypeValues;
|
||||
use super::BGPCapabilityValue;
|
||||
use super::FourByteASNCapability;
|
||||
use super::OpenOption;
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier::Ipv6;
|
||||
use crate::bgp_packet::traits::ParserContext;
|
||||
use crate::bgp_packet::traits::ReadablePacket;
|
||||
|
||||
#[test]
|
||||
fn test_four_byte_asn_capability() {
|
||||
let bytes: &[u8] = &[0x41, 0x04, 0x00, 0x00, 0x00, 0x2a];
|
||||
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
|
||||
let (buf, result) = BGPCapability::from_wire(ctx, bytes).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
BGPCapability {
|
||||
cap_type: BGPCapabilityTypeValues::FOUR_BYTE_ASN,
|
||||
val: BGPCapabilityValue::FourByteASN(FourByteASNCapability { asn: 42 })
|
||||
}
|
||||
);
|
||||
assert_eq!(buf.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_options<'a>() {
|
||||
let option_bytes: &[u8] = &[
|
||||
0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02, 0x02,
|
||||
0x02, 0x00, 0x02, 0x02, 0x46, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x00, 0x2a,
|
||||
];
|
||||
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
|
||||
let (_buf, result) =
|
||||
nom::multi::many0(|buf: &'a [u8]| OpenOption::from_wire(ctx, buf))(option_bytes)
|
||||
.unwrap();
|
||||
|
||||
let expected_str = "[OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(1), val: Multiprotocol(MultiprotocolCapability { afi: Ipv4, safi: Unicast }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(128), val: UnknownCapability(UnknownCapability { cap_code: 128, payload: [] }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(2), val: RouteRefresh(RouteRefreshCapability) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(70), val: UnknownCapability(UnknownCapability { cap_code: 70, payload: [] }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(65), val: FourByteASN(FourByteASNCapability { asn: 42 }) }] }) }]";
|
||||
assert_eq!(format!("{:?}", result), expected_str);
|
||||
}
|
||||
}
|
||||
132
bgpd/src/bgp_packet/constants.rs
Normal file
132
bgpd/src/bgp_packet/constants.rs
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use nom::IResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use super::traits::{BGPParserError, ParserContext, ReadablePacket};
|
||||
|
||||
// Address Family Identifiers as per
|
||||
// https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml
|
||||
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize, Hash)]
|
||||
pub enum AddressFamilyIdentifier {
|
||||
Ipv4,
|
||||
Ipv6,
|
||||
}
|
||||
|
||||
impl Into<u16> for AddressFamilyIdentifier {
|
||||
fn into(self) -> u16 {
|
||||
match self {
|
||||
Self::Ipv4 => 1,
|
||||
Self::Ipv6 => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for AddressFamilyIdentifier {
|
||||
type Error = std::io::Error;
|
||||
fn try_from(i: u16) -> Result<Self, Self::Error> {
|
||||
match i {
|
||||
1 => Ok(Self::Ipv4),
|
||||
2 => Ok(Self::Ipv6),
|
||||
_ => Err(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("Unknown AFI: {}", i),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This parser for AFI makes it easier to write the other message parsers.
|
||||
impl ReadablePacket for AddressFamilyIdentifier {
|
||||
fn from_wire<'a>(
|
||||
_: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], AddressFamilyIdentifier, BGPParserError<&'a [u8]>> {
|
||||
let (buf, afi_raw) = nom::number::complete::be_u16(buf)?;
|
||||
|
||||
let afi = AddressFamilyIdentifier::try_from(afi_raw)
|
||||
.map_err(|e| nom::Err::Error(BGPParserError::CustomText(e.to_string())))?;
|
||||
|
||||
IResult::Ok((buf, afi))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AddressFamilyIdentifier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Ipv4 => write!(f, "Ipv4"),
|
||||
Self::Ipv6 => write!(f, "Ipv6"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subsequent Address Family Identifiers as per
|
||||
// https://www.iana.org/assignments/safi-namespace/safi-namespace.xhtml
|
||||
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum SubsequentAddressFamilyIdentifier {
|
||||
Unicast,
|
||||
Multicast,
|
||||
}
|
||||
|
||||
impl Into<u8> for SubsequentAddressFamilyIdentifier {
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
Self::Unicast => 1,
|
||||
Self::Multicast => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SubsequentAddressFamilyIdentifier {
|
||||
type Error = std::io::Error;
|
||||
fn try_from(i: u8) -> Result<Self, Self::Error> {
|
||||
match i {
|
||||
1 => Ok(Self::Unicast),
|
||||
2 => Ok(Self::Multicast),
|
||||
_ => Err(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("Unknown SAFI value: {} ", i),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This parser for SAFI makes it easier to write the other message parsers.
|
||||
impl ReadablePacket for SubsequentAddressFamilyIdentifier {
|
||||
fn from_wire<'a>(
|
||||
_: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], SubsequentAddressFamilyIdentifier, BGPParserError<&'a [u8]>> {
|
||||
let (buf, safi_raw) = nom::number::complete::be_u8(buf)?;
|
||||
|
||||
let safi = SubsequentAddressFamilyIdentifier::try_from(safi_raw)
|
||||
.map_err(|e| nom::Err::Error(BGPParserError::CustomText(e.to_string())))?;
|
||||
|
||||
IResult::Ok((buf, safi))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubsequentAddressFamilyIdentifier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Unicast => write!(f, "Unicast"),
|
||||
Self::Multicast => write!(f, "Multicast"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const AS_TRANS: u16 = 23456;
|
||||
710
bgpd/src/bgp_packet/messages.rs
Normal file
710
bgpd/src/bgp_packet/messages.rs
Normal file
@ -0,0 +1,710 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::capabilities::OpenOption;
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use crate::bgp_packet::constants::SubsequentAddressFamilyIdentifier;
|
||||
use crate::bgp_packet::nlri::NLRI;
|
||||
use crate::bgp_packet::path_attributes::PathAttribute;
|
||||
use crate::bgp_packet::traits::BGPParserError;
|
||||
use crate::bgp_packet::traits::ParserContext;
|
||||
use crate::bgp_packet::traits::ReadablePacket;
|
||||
use crate::bgp_packet::traits::WritablePacket;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use bytes::Buf;
|
||||
use bytes::BufMut;
|
||||
use bytes::BytesMut;
|
||||
use nom::number::complete::{be_u16, be_u32, be_u8};
|
||||
use nom::Err::Failure;
|
||||
use nom::IResult;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::net::Ipv4Addr;
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
/// BGPMessageType represents the type of the top level BGP message.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)]
|
||||
pub struct BGPMessageType(pub u8);
|
||||
|
||||
impl BGPMessageType {
|
||||
pub fn new(val: u8) -> BGPMessageType {
|
||||
BGPMessageType(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for BGPMessageType {
|
||||
fn into(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl From<u8> for BGPMessageType {
|
||||
fn from(i: u8) -> BGPMessageType {
|
||||
BGPMessageType(i)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub mod BGPMessageTypeValues {
|
||||
use super::BGPMessageType;
|
||||
|
||||
pub const OPEN_MESSAGE: BGPMessageType = BGPMessageType(1);
|
||||
pub const UPDATE_MESSAGE: BGPMessageType = BGPMessageType(2);
|
||||
pub const NOTIFICATION_MESSAGE: BGPMessageType = BGPMessageType(3);
|
||||
pub const KEEPALIVE_MESSAGE: BGPMessageType = BGPMessageType(4);
|
||||
pub const REFRESH_MESSAGE: BGPMessageType = BGPMessageType(5);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BGPSubmessage {
|
||||
OpenMessage(OpenMessage),
|
||||
UpdateMessage(UpdateMessage),
|
||||
NotificationMessage(NotificationMessage),
|
||||
KeepaliveMessage(KeepaliveMessage),
|
||||
}
|
||||
|
||||
impl WritablePacket for BGPSubmessage {
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
match &self {
|
||||
BGPSubmessage::OpenMessage(m) => m.to_wire(ctx),
|
||||
BGPSubmessage::UpdateMessage(m) => m.to_wire(ctx),
|
||||
BGPSubmessage::NotificationMessage(m) => m.to_wire(ctx),
|
||||
BGPSubmessage::KeepaliveMessage(m) => m.to_wire(ctx),
|
||||
}
|
||||
}
|
||||
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
|
||||
match &self {
|
||||
BGPSubmessage::OpenMessage(m) => m.wire_len(ctx),
|
||||
BGPSubmessage::UpdateMessage(m) => m.wire_len(ctx),
|
||||
BGPSubmessage::NotificationMessage(m) => m.wire_len(ctx),
|
||||
BGPSubmessage::KeepaliveMessage(m) => m.wire_len(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// KeepaliveMessage implements the KEEPALIVE message as defined in RFC4271.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct KeepaliveMessage {}
|
||||
|
||||
impl ReadablePacket for KeepaliveMessage {
|
||||
fn from_wire<'a>(
|
||||
_: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
Ok((buf, KeepaliveMessage {}))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for KeepaliveMessage {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for KeepaliveMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "KeepaliveMessage")
|
||||
}
|
||||
}
|
||||
|
||||
/// NotificationMessage implements the NOTIFICATION message type as defined in RFC4271.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct NotificationMessage {
|
||||
pub error_code: u8,
|
||||
pub error_subcode: u8,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ReadablePacket for NotificationMessage {
|
||||
fn from_wire<'a>(
|
||||
_: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
let (buf, ec) = be_u8(buf)?;
|
||||
let (buf, esc) = be_u8(buf)?;
|
||||
let data = &buf;
|
||||
Ok((
|
||||
&[0u8; 0],
|
||||
NotificationMessage {
|
||||
error_code: ec,
|
||||
error_subcode: esc,
|
||||
data: data.to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for NotificationMessage {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf = vec![];
|
||||
buf.push(self.error_code);
|
||||
buf.push(self.error_subcode);
|
||||
buf.extend(self.data.to_owned());
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(2 + self.data.len() as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NotificationMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"NotificationMessage error_code: {}, error_subcode: {}",
|
||||
self.error_code, self.error_subcode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RouteRefreshMessage {
|
||||
pub afi: AddressFamilyIdentifier,
|
||||
pub safi: SubsequentAddressFamilyIdentifier,
|
||||
}
|
||||
|
||||
impl WritablePacket for RouteRefreshMessage {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut res = [0u8; 4];
|
||||
byteorder::NetworkEndian::write_u16(&mut res[..2], self.afi.into());
|
||||
res[3] = self.safi.into();
|
||||
Ok(res.to_vec())
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(4)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadablePacket for RouteRefreshMessage {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
let (buf, (afi, _, safi)) = nom::combinator::complete(nom::sequence::tuple((
|
||||
|i| AddressFamilyIdentifier::from_wire(ctx, i),
|
||||
nom::bytes::complete::take(1u8),
|
||||
|i| SubsequentAddressFamilyIdentifier::from_wire(ctx, i),
|
||||
)))(buf)?;
|
||||
|
||||
IResult::Ok((buf, RouteRefreshMessage { afi, safi }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RouteRefreshMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "RouteRefresh [afi: {}, safi: {}]", self.afi, self.safi)
|
||||
}
|
||||
}
|
||||
|
||||
/// BGPMessage is the top level message which is transmitted over the wire.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct BGPMessage {
|
||||
pub msg_type: BGPMessageType,
|
||||
pub payload: BGPSubmessage,
|
||||
}
|
||||
|
||||
/// Codec is a helper for serializing and deserializing BGP messages.
|
||||
pub struct Codec {
|
||||
pub ctx: ParserContext,
|
||||
}
|
||||
|
||||
impl Encoder<BGPMessage> for Codec {
|
||||
type Error = std::io::Error;
|
||||
fn encode(
|
||||
&mut self,
|
||||
msg: BGPMessage,
|
||||
buf: &mut BytesMut,
|
||||
) -> Result<(), <Self as Encoder<BGPMessage>>::Error> {
|
||||
let result = msg.to_wire(&self.ctx);
|
||||
match result {
|
||||
Ok(bytes) => {
|
||||
// XXX: Copying here because the whole write path needs to be updated
|
||||
// to take a refrence to BytesMut and write to that directly.
|
||||
let tmp: BytesMut = bytes.as_slice().into();
|
||||
buf.put(tmp);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for Codec {
|
||||
type Item = BGPMessage;
|
||||
type Error = std::io::Error;
|
||||
fn decode(
|
||||
&mut self,
|
||||
buf: &mut BytesMut,
|
||||
) -> Result<std::option::Option<<Self as Decoder>::Item>, <Self as Decoder>::Error> {
|
||||
// We first check to see if the frame contains the full BGP message before invoking
|
||||
// the parser on it.
|
||||
// Expected contents: 16x 0xff, u16 of length.
|
||||
// The length contains the header length, so we just check that the buf len matches.
|
||||
if buf.len() < 19 {
|
||||
// Minimum size is 19 for header + length + type.
|
||||
return Ok(None);
|
||||
}
|
||||
// Read the length
|
||||
let len: u16 = byteorder::BigEndian::read_u16(&buf[16..18]);
|
||||
if buf.len() < len.into() {
|
||||
// Not enough data to read this frame.
|
||||
return Ok(None);
|
||||
} else if buf.len() == len as usize {
|
||||
// Exactly one message here, parse and clear buf.
|
||||
let parse_result = BGPMessage::from_wire(&self.ctx, buf.as_ref());
|
||||
match parse_result {
|
||||
Ok(msg) => {
|
||||
let result = msg.1;
|
||||
buf.clear();
|
||||
Ok(Some(result))
|
||||
}
|
||||
Err(e) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Failed to parse message: {:?}", e),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
// More than one message here, parse and advance buf.
|
||||
let parse_result = BGPMessage::from_wire(&self.ctx, buf.as_ref());
|
||||
match parse_result {
|
||||
Ok(msg) => {
|
||||
let result = msg.1;
|
||||
buf.advance(len as usize);
|
||||
Ok(Some(result))
|
||||
}
|
||||
Err(e) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Failed to parse message: {:?}", e),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for BGPMessage {
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
// 16 bytes of 0xff according to Section 4.1 of RFC4271.
|
||||
buf.append(&mut vec![0xff; 16]);
|
||||
// Length.
|
||||
{
|
||||
let mut tmp: [u8; 2] = [0u8; 2];
|
||||
NetworkEndian::write_u16(&mut tmp, self.wire_len(ctx)?);
|
||||
buf.extend_from_slice(&mut tmp);
|
||||
}
|
||||
// Type
|
||||
buf.push(self.msg_type.into());
|
||||
let mut result: Vec<u8> = self.payload.to_wire(ctx)?;
|
||||
buf.append(&mut result);
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(16 + 2 + 1 + self.payload.wire_len(ctx)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadablePacket for BGPMessage {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
let (buf, _) = nom::combinator::complete(nom::bytes::complete::tag(&[0xff; 16]))(buf)?;
|
||||
let (buf, len) = nom::combinator::complete(be_u16)(buf)?;
|
||||
let (buf, typ) = nom::combinator::complete(be_u8)(buf)?;
|
||||
let payload_len = len - 19;
|
||||
let (buf, payload_bytes) = nom::bytes::complete::take(payload_len)(buf)?;
|
||||
let (_, payload) = match typ.into() {
|
||||
BGPMessageTypeValues::OPEN_MESSAGE => {
|
||||
let (b, omsg) = OpenMessage::from_wire(ctx, payload_bytes)?;
|
||||
(b, BGPSubmessage::OpenMessage(omsg))
|
||||
}
|
||||
BGPMessageTypeValues::UPDATE_MESSAGE => {
|
||||
let (b, umsg) = UpdateMessage::from_wire(ctx, payload_bytes)?;
|
||||
(b, BGPSubmessage::UpdateMessage(umsg))
|
||||
}
|
||||
BGPMessageTypeValues::NOTIFICATION_MESSAGE => {
|
||||
let (b, nmsg) = NotificationMessage::from_wire(ctx, payload_bytes)?;
|
||||
(b, BGPSubmessage::NotificationMessage(nmsg))
|
||||
}
|
||||
BGPMessageTypeValues::KEEPALIVE_MESSAGE => {
|
||||
let (b, kmsg) = KeepaliveMessage::from_wire(ctx, payload_bytes)?;
|
||||
(b, BGPSubmessage::KeepaliveMessage(kmsg))
|
||||
}
|
||||
_ => {
|
||||
return Err(Failure(BGPParserError::CustomText(
|
||||
"Unknown BGP message type".to_string(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok((
|
||||
buf,
|
||||
BGPMessage {
|
||||
msg_type: BGPMessageType(typ),
|
||||
payload,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BGPMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.payload {
|
||||
BGPSubmessage::OpenMessage(m) => fmt::Display::fmt(&m, f),
|
||||
BGPSubmessage::UpdateMessage(m) => fmt::Display::fmt(&m, f),
|
||||
BGPSubmessage::KeepaliveMessage(m) => fmt::Display::fmt(&m, f),
|
||||
BGPSubmessage::NotificationMessage(m) => fmt::Display::fmt(&m, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct OpenMessage {
|
||||
pub version: u8,
|
||||
pub asn: u16,
|
||||
pub hold_time: u16,
|
||||
pub identifier: Ipv4Addr,
|
||||
pub options: Vec<OpenOption>,
|
||||
}
|
||||
|
||||
impl ReadablePacket for OpenMessage {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], OpenMessage, BGPParserError<&'a [u8]>> {
|
||||
let (buf, (version, asn, hold_time, identifier)) =
|
||||
nom::combinator::complete(nom::sequence::tuple((be_u8, be_u16, be_u16, be_u32)))(buf)?;
|
||||
// oplen, [ [OpenOption] ... ]
|
||||
// OpenOption = [T, L, V]
|
||||
let (buf, opts): (_, Vec<OpenOption>) = nom::multi::length_value(
|
||||
be_u8,
|
||||
nom::multi::many0(|b| OpenOption::from_wire(ctx, b)),
|
||||
)(buf)?;
|
||||
Ok((
|
||||
buf,
|
||||
OpenMessage {
|
||||
version,
|
||||
asn,
|
||||
hold_time,
|
||||
identifier: Ipv4Addr::from(identifier),
|
||||
options: opts,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for OpenMessage {
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf: Vec<u8> = vec![0; 10];
|
||||
buf[0] = self.version;
|
||||
NetworkEndian::write_u16(&mut buf.as_mut_slice()[1..3], self.asn);
|
||||
NetworkEndian::write_u16(&mut buf.as_mut_slice()[3..5], self.hold_time);
|
||||
buf[5..9].clone_from_slice(&self.identifier.octets());
|
||||
let mut oplen: u8 = 0;
|
||||
for opt in &self.options {
|
||||
buf.append(&mut (*opt).to_wire(ctx)?);
|
||||
oplen += ((*opt).wire_len(ctx)?) as u8;
|
||||
}
|
||||
buf[9] = oplen;
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
|
||||
let mut count: usize = 10;
|
||||
for opt in &self.options {
|
||||
count += (*opt).to_wire(ctx)?.len();
|
||||
}
|
||||
Ok(count
|
||||
.try_into()
|
||||
.map_err(|_| "overflow in wire_len in OpenMessage")?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OpenMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"OpenMessage: [version: {}, asn: {}, hold_time: {}, identifier: {}, options: [",
|
||||
self.version, self.asn, self.hold_time, self.identifier
|
||||
)?;
|
||||
for option in &self.options {
|
||||
fmt::Display::fmt(option, f)?;
|
||||
}
|
||||
write!(f, "]]")
|
||||
}
|
||||
}
|
||||
|
||||
/// UPDATE message and subtypes.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct UpdateMessage {
|
||||
pub withdrawn_nlri: Vec<NLRI>,
|
||||
pub path_attributes: Vec<PathAttribute>,
|
||||
pub announced_nlri: Vec<NLRI>,
|
||||
}
|
||||
|
||||
impl ReadablePacket for UpdateMessage {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
let (buf, wd_nlris): (_, Vec<NLRI>) = nom::multi::length_value(
|
||||
be_u16,
|
||||
nom::multi::many0(|i| NLRI::from_wire(&ctx.clone(), i)),
|
||||
)(buf)?;
|
||||
let (buf, pattrs): (_, Vec<PathAttribute>) = nom::multi::length_value(
|
||||
be_u16,
|
||||
nom::multi::many0(|i| PathAttribute::from_wire(ctx, i)),
|
||||
)(buf)?;
|
||||
let (buf, ann_nlri): (_, Vec<NLRI>) =
|
||||
nom::multi::many0(|i| NLRI::from_wire(&ctx.clone(), i))(buf)?;
|
||||
Ok((
|
||||
buf,
|
||||
UpdateMessage {
|
||||
withdrawn_nlri: wd_nlris,
|
||||
path_attributes: pattrs,
|
||||
announced_nlri: ann_nlri,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for UpdateMessage {
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let mut tmp: &mut [u8] = &mut [0u8; 2];
|
||||
let mut wd_len: u16 = 0;
|
||||
for wd in &self.withdrawn_nlri {
|
||||
wd_len += wd.wire_len(ctx)?;
|
||||
}
|
||||
NetworkEndian::write_u16(&mut tmp, wd_len);
|
||||
buf.append(&mut tmp.to_vec());
|
||||
for wd in &self.withdrawn_nlri {
|
||||
buf.extend(wd.to_wire(ctx)?);
|
||||
}
|
||||
let mut pattr_len: u16 = 0;
|
||||
for pattr in &self.path_attributes {
|
||||
pattr_len += pattr.wire_len(ctx)?;
|
||||
}
|
||||
NetworkEndian::write_u16(&mut tmp, pattr_len);
|
||||
buf.extend(tmp.to_vec());
|
||||
for pattr in &self.path_attributes {
|
||||
buf.extend(pattr.to_wire(ctx)?);
|
||||
}
|
||||
for ann in &self.announced_nlri {
|
||||
buf.extend(ann.to_wire(ctx)?);
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
|
||||
let mut ctr: u16 = 0;
|
||||
ctr += 2;
|
||||
for wd in &self.withdrawn_nlri {
|
||||
ctr += wd.wire_len(ctx)?;
|
||||
}
|
||||
ctr += 2;
|
||||
for pa in &self.path_attributes {
|
||||
ctr += pa.wire_len(ctx)?;
|
||||
}
|
||||
for ann in &self.announced_nlri {
|
||||
ctr += ann.wire_len(ctx)?;
|
||||
}
|
||||
Ok(ctr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UpdateMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "UpdateMessage [ withdrawn: ")?;
|
||||
for withdrawn_nlri in &self.withdrawn_nlri {
|
||||
fmt::Display::fmt(withdrawn_nlri, f)?;
|
||||
}
|
||||
for announced_nlri in &self.announced_nlri {
|
||||
fmt::Display::fmt(announced_nlri, f)?;
|
||||
}
|
||||
for path_attr in &self.path_attributes {
|
||||
fmt::Display::fmt(path_attr, f)?;
|
||||
}
|
||||
write!(f, " ]")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BGPMessage;
|
||||
use super::Codec;
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier::Ipv6;
|
||||
use crate::bgp_packet::messages::AddressFamilyIdentifier::Ipv4;
|
||||
use crate::bgp_packet::traits::ParserContext;
|
||||
use crate::bgp_packet::traits::ReadablePacket;
|
||||
use crate::bgp_packet::traits::WritablePacket;
|
||||
|
||||
use bytes::BufMut;
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
#[test]
|
||||
fn test_open_msg() {
|
||||
let open_msg_bytes: &[u8] = &[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x39, 0x01, 0x04, 0x00, 0x2a, 0x00, 0xb4, 0xd4, 0x19, 0x16, 0x26,
|
||||
0x1c, 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02,
|
||||
0x02, 0x02, 0x00, 0x02, 0x02, 0x46, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x00,
|
||||
0x2a,
|
||||
];
|
||||
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv4);
|
||||
let (buf, result) = BGPMessage::from_wire(ctx, open_msg_bytes).unwrap();
|
||||
assert_eq!(buf.len(), 0);
|
||||
|
||||
let want_str = "OpenMessage: [version: 4, asn: 42, hold_time: 180, identifier: 212.25.22.38, options: [OpenOption: Capabilities: Capabilities: [MultiprotocolCapbility: [ Ipv4 Unicast ]]OpenOption: Capabilities: Capabilities: [UnknownCapability type: 128]OpenOption: Capabilities: Capabilities: [RouteRefreshCapability]OpenOption: Capabilities: Capabilities: [UnknownCapability type: 70]OpenOption: Capabilities: Capabilities: [FourByteASN: asn: 42]]]";
|
||||
assert_eq!(format!("{}", result), want_str);
|
||||
|
||||
let wire: Vec<u8> = result.to_wire(ctx).unwrap();
|
||||
assert_eq!(wire, open_msg_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_msg_ipv6() {
|
||||
let open_msg_bytes: &[u8] = &[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x35, 0x01, 0x04, 0x22, 0x36, 0x00, 0xb4, 0xd4, 0x19, 0x1b, 0x2d,
|
||||
0x18, 0x02, 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02,
|
||||
0x02, 0x80, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x22, 0x36,
|
||||
];
|
||||
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv4);
|
||||
let (buf, result) = BGPMessage::from_wire(ctx, open_msg_bytes).unwrap();
|
||||
assert_eq!(buf.len(), 0);
|
||||
|
||||
let want_str = "OpenMessage: [version: 4, asn: 8758, hold_time: 180, identifier: 212.25.27.45, options: [OpenOption: Capabilities: Capabilities: [MultiprotocolCapbility: [ Ipv6 Unicast ]]OpenOption: Capabilities: Capabilities: [RouteRefreshCapability]OpenOption: Capabilities: Capabilities: [UnknownCapability type: 128]OpenOption: Capabilities: Capabilities: [FourByteASN: asn: 8758]]]";
|
||||
assert_eq!(format!("{}", result), want_str);
|
||||
|
||||
let wire: Vec<u8> = result.to_wire(ctx).unwrap();
|
||||
assert_eq!(wire, open_msg_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_msg_simple() {
|
||||
let update_msg_bytes: &[u8] = &[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x79, 0x02, 0x00, 0x00, 0x00, 0x5e, 0x40, 0x01, 0x01, 0x02, 0x40,
|
||||
0x02, 0x16, 0x02, 0x05, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00,
|
||||
0x73, 0xfb, 0x00, 0x00, 0x05, 0x13, 0x00, 0x00, 0x12, 0x83, 0x40, 0x03, 0x04, 0xb9,
|
||||
0x5f, 0xdb, 0x24, 0xc0, 0x08, 0x1c, 0x05, 0x13, 0x88, 0xb8, 0x73, 0xfb, 0x0f, 0xa0,
|
||||
0x73, 0xfb, 0x0f, 0xb5, 0x9a, 0x74, 0x0f, 0xa0, 0x9a, 0x74, 0x0f, 0xaa, 0xdf, 0x1e,
|
||||
0x07, 0xd0, 0xdf, 0x1e, 0x07, 0xda, 0xc0, 0x20, 0x18, 0x00, 0x00, 0xdf, 0x1e, 0x00,
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x0a, 0x18, 0xcb, 0x01, 0x4e,
|
||||
];
|
||||
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv4);
|
||||
let (buf, result) = BGPMessage::from_wire(ctx, update_msg_bytes).unwrap();
|
||||
assert_eq!(buf.len(), 0);
|
||||
|
||||
let want_str = "UpdateMessage [ withdrawn: 203.1.78.0/24Origin: UnknownAS Path: { Segment [ Type: AS_SEGMENT 39540 57118 29691 1299 4739 ]] }NextHop: 185.95.219.36Communities: [ 1299:35000, 29691:4000, 29691:4021, 39540:4000, 39540:4010, 57118:2000, 57118:2010, ] LargeCommunities: [ 57118:20:0, 57118:20:10, ] ]";
|
||||
assert_eq!(format!("{}", result), want_str);
|
||||
|
||||
let reencoded = result.to_wire(&ctx).unwrap();
|
||||
assert_eq!(&reencoded, update_msg_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insufficient_decode() {
|
||||
let update_msg_bytes: &[u8] = &[0xff, 0xff, 0xff, 0xff, 0xff];
|
||||
let codec = &mut Codec {
|
||||
ctx: ParserContext {
|
||||
four_octet_asn: Some(true),
|
||||
nlri_mode: Some(Ipv6),
|
||||
},
|
||||
};
|
||||
let mut buf = bytes::BytesMut::from(update_msg_bytes);
|
||||
let result = codec.decode(&mut buf);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().is_none());
|
||||
assert_eq!(buf.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exact_decode_encode() {
|
||||
let update_msg_bytes: &[u8] = &[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x79, 0x02, 0x00, 0x00, 0x00, 0x5e, 0x40, 0x01, 0x01, 0x02, 0x40,
|
||||
0x02, 0x16, 0x02, 0x05, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00,
|
||||
0x73, 0xfb, 0x00, 0x00, 0x05, 0x13, 0x00, 0x00, 0x12, 0x83, 0x40, 0x03, 0x04, 0xb9,
|
||||
0x5f, 0xdb, 0x24, 0xc0, 0x08, 0x1c, 0x05, 0x13, 0x88, 0xb8, 0x73, 0xfb, 0x0f, 0xa0,
|
||||
0x73, 0xfb, 0x0f, 0xb5, 0x9a, 0x74, 0x0f, 0xa0, 0x9a, 0x74, 0x0f, 0xaa, 0xdf, 0x1e,
|
||||
0x07, 0xd0, 0xdf, 0x1e, 0x07, 0xda, 0xc0, 0x20, 0x18, 0x00, 0x00, 0xdf, 0x1e, 0x00,
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x0a, 0x18, 0xcb, 0x01, 0x4e,
|
||||
];
|
||||
let codec = &mut Codec {
|
||||
ctx: ParserContext {
|
||||
four_octet_asn: Some(true),
|
||||
nlri_mode: Some(Ipv6),
|
||||
},
|
||||
};
|
||||
let mut buf = bytes::BytesMut::from(update_msg_bytes);
|
||||
let result = codec.decode(&mut buf).unwrap();
|
||||
assert!(result.is_some());
|
||||
assert_eq!(buf.len(), 0);
|
||||
codec.encode(result.unwrap(), &mut buf).unwrap();
|
||||
print!("Output bytes: ");
|
||||
for b in &buf {
|
||||
print!("0x{:02x}, ", b);
|
||||
}
|
||||
assert_eq!(buf.as_ref(), update_msg_bytes.as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_msg_codec_decode() {
|
||||
let update_msg_bytes: &[u8] = &[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x79, 0x02, 0x00, 0x00, 0x00, 0x5e, 0x40, 0x01, 0x01, 0x02, 0x40,
|
||||
0x02, 0x16, 0x02, 0x05, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00,
|
||||
0x73, 0xfb, 0x00, 0x00, 0x05, 0x13, 0x00, 0x00, 0x12, 0x83, 0x40, 0x03, 0x04, 0xb9,
|
||||
0x5f, 0xdb, 0x24, 0xc0, 0x08, 0x1c, 0x05, 0x13, 0x88, 0xb8, 0x73, 0xfb, 0x0f, 0xa0,
|
||||
0x73, 0xfb, 0x0f, 0xb5, 0x9a, 0x74, 0x0f, 0xa0, 0x9a, 0x74, 0x0f, 0xaa, 0xdf, 0x1e,
|
||||
0x07, 0xd0, 0xdf, 0x1e, 0x07, 0xda, 0xc0, 0x20, 0x18, 0x00, 0x00, 0xdf, 0x1e, 0x00,
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x0a, 0x18, 0xcb, 0x01, 0x4e,
|
||||
// Add part of a second message which is incomplete
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00,
|
||||
];
|
||||
let codec = &mut Codec {
|
||||
ctx: ParserContext {
|
||||
four_octet_asn: Some(true),
|
||||
nlri_mode: Some(Ipv6),
|
||||
},
|
||||
};
|
||||
let mut buf = bytes::BytesMut::from(update_msg_bytes);
|
||||
let result = codec.decode(&mut buf);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().is_some());
|
||||
assert_eq!(buf.len(), 17);
|
||||
// Add the rest of the message into buf.
|
||||
buf.put_slice(&[
|
||||
0x79, 0x02, 0x00, 0x00, 0x00, 0x5e, 0x40, 0x01, 0x01, 0x02, 0x40, 0x02, 0x16, 0x02,
|
||||
0x05, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x73, 0xfb, 0x00,
|
||||
0x00, 0x05, 0x13, 0x00, 0x00, 0x12, 0x83, 0x40, 0x03, 0x04, 0xb9, 0x5f, 0xdb, 0x24,
|
||||
0xc0, 0x08, 0x1c, 0x05, 0x13, 0x88, 0xb8, 0x73, 0xfb, 0x0f, 0xa0, 0x73, 0xfb, 0x0f,
|
||||
0xb5, 0x9a, 0x74, 0x0f, 0xa0, 0x9a, 0x74, 0x0f, 0xaa, 0xdf, 0x1e, 0x07, 0xd0, 0xdf,
|
||||
0x1e, 0x07, 0xda, 0xc0, 0x20, 0x18, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00, 0x14,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
|
||||
0x00, 0x0a, 0x18, 0xcb, 0x01, 0x4e,
|
||||
]);
|
||||
let result2 = codec.decode(&mut buf);
|
||||
assert!(result2.is_ok());
|
||||
assert!(result2.unwrap().is_some());
|
||||
assert_eq!(buf.len(), 0);
|
||||
}
|
||||
}
|
||||
26
bgpd/src/bgp_packet/mod.rs
Normal file
26
bgpd/src/bgp_packet/mod.rs
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Implements parsers / serializers for the Border Gateway Protocol wire format.
|
||||
//! RFC4271
|
||||
|
||||
// Meta
|
||||
pub mod constants;
|
||||
pub mod traits;
|
||||
|
||||
// Parsers
|
||||
pub mod capabilities;
|
||||
pub mod messages;
|
||||
pub mod nlri;
|
||||
pub mod path_attributes;
|
||||
353
bgpd/src/bgp_packet/nlri.rs
Normal file
353
bgpd/src/bgp_packet/nlri.rs
Normal file
@ -0,0 +1,353 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use crate::bgp_packet::traits::BGPParserError;
|
||||
use crate::bgp_packet::traits::ParserContext;
|
||||
use crate::bgp_packet::traits::ReadablePacket;
|
||||
use crate::bgp_packet::traits::WritablePacket;
|
||||
use nom::bytes::complete::take;
|
||||
use nom::number::complete::be_u8;
|
||||
use nom::Err::Failure;
|
||||
use nom::IResult;
|
||||
use serde::Serialize;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::io::ErrorKind;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::str::FromStr;
|
||||
|
||||
// NLRI here is the Neighbor Link Reachability Information from RFC 4271.
|
||||
// Other NLRIs such as MP Reach NLRI are implemented as path attributes.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Hash)]
|
||||
pub struct NLRI {
|
||||
pub afi: AddressFamilyIdentifier,
|
||||
pub prefixlen: u8,
|
||||
pub prefix: Vec<u8>,
|
||||
}
|
||||
|
||||
impl NLRI {
|
||||
pub fn from_bytes(
|
||||
afi: AddressFamilyIdentifier,
|
||||
prefix: Vec<u8>,
|
||||
prefixlen: u8,
|
||||
) -> Result<Self, String> {
|
||||
// Check that the vector has enough bytes to represent the prefix.
|
||||
if prefix.len() < ((prefixlen + 7) / 8).into() {
|
||||
return Err(format!(
|
||||
"Prefix: {:?}/{} does not have enough bytes in prefix for given prefixlen",
|
||||
prefix, prefixlen
|
||||
));
|
||||
}
|
||||
Ok(NLRI {
|
||||
afi,
|
||||
prefixlen,
|
||||
prefix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadablePacket for NLRI {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
buf: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
|
||||
// plen is the length in bits of the address.
|
||||
let (buf, prefixlen) = be_u8(buf)?;
|
||||
let octet_len = (prefixlen + 7) / 8;
|
||||
let (buf, prefix) = take(octet_len)(buf)?;
|
||||
|
||||
match ctx.nlri_mode {
|
||||
None => {
|
||||
return Err(Failure(BGPParserError::CustomText(
|
||||
"nlri_mode not set in the context for NLRI::from_wire".to_string(),
|
||||
)));
|
||||
}
|
||||
Some(afi) => Ok((
|
||||
buf,
|
||||
NLRI {
|
||||
afi,
|
||||
prefixlen,
|
||||
prefix: prefix.to_vec(),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NLRI> for Ipv6Addr {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: NLRI) -> Result<Self, Self::Error> {
|
||||
match value.afi {
|
||||
AddressFamilyIdentifier::Ipv6 => {
|
||||
let mut v: [u8; 16] = [0u8; 16];
|
||||
if value.prefix.len() > v.len() {
|
||||
return Err("prefix length greater than IPv6 address length".to_string());
|
||||
}
|
||||
for (pos, e) in value.prefix.iter().enumerate() {
|
||||
v[pos] = *e;
|
||||
}
|
||||
let ip6: Ipv6Addr = v.into();
|
||||
Ok(ip6)
|
||||
}
|
||||
_ => Err("Unsupported AFI type".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NLRI> for Ipv4Addr {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: NLRI) -> Result<Self, Self::Error> {
|
||||
match value.afi {
|
||||
AddressFamilyIdentifier::Ipv4 => {
|
||||
let mut v: [u8; 4] = [0u8; 4];
|
||||
if value.prefix.len() > v.len() {
|
||||
return Err("prefix length greater than IPv4 address length".to_string());
|
||||
}
|
||||
for (pos, e) in value.prefix.iter().enumerate() {
|
||||
v[pos] = *e;
|
||||
}
|
||||
let ip4 = Ipv4Addr::new(v[0], v[1], v[2], v[3]);
|
||||
Ok(ip4)
|
||||
}
|
||||
_ => Err("Unsupported AFI type".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<IpAddr> for NLRI {
|
||||
type Error = std::io::Error;
|
||||
fn try_into(self) -> Result<IpAddr, Self::Error> {
|
||||
match self.afi {
|
||||
AddressFamilyIdentifier::Ipv4 => {
|
||||
let mut v: [u8; 4] = [0u8; 4];
|
||||
if self.prefix.len() > v.len() {
|
||||
return Err(std::io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"prefix length greater than IPv4 address length",
|
||||
));
|
||||
}
|
||||
for (pos, e) in self.prefix.iter().enumerate() {
|
||||
v[pos] = *e;
|
||||
}
|
||||
let ip4 = Ipv4Addr::new(v[0], v[1], v[2], v[3]);
|
||||
Ok(IpAddr::V4(ip4))
|
||||
}
|
||||
AddressFamilyIdentifier::Ipv6 => {
|
||||
let mut v: [u8; 16] = [0u8; 16];
|
||||
if self.prefix.len() > v.len() {
|
||||
return Err(std::io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"prefix length greater than IPv6 address length",
|
||||
));
|
||||
}
|
||||
for (pos, e) in self.prefix.iter().enumerate() {
|
||||
v[pos] = *e;
|
||||
}
|
||||
let ip6: Ipv6Addr = v.into();
|
||||
Ok(IpAddr::V6(ip6))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for NLRI {
|
||||
type Error = String;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
let parts: Vec<&str> = value.split("/").collect();
|
||||
if parts.len() != 2 {
|
||||
return Err(format!("Expected ip_addr/prefixlen but got: {}", value));
|
||||
}
|
||||
|
||||
let prefixlen: u8 = u8::from_str(parts[1]).map_err(|_| "failed to parse prefixlen")?;
|
||||
let mut octets: Vec<u8>;
|
||||
let afi: AddressFamilyIdentifier;
|
||||
|
||||
if parts[0].contains(":") {
|
||||
afi = AddressFamilyIdentifier::Ipv6;
|
||||
let addr: Ipv6Addr = Ipv6Addr::from_str(parts[0]).map_err(|e| e.to_string())?;
|
||||
octets = addr.octets().to_vec();
|
||||
} else if parts[0].contains(".") {
|
||||
afi = AddressFamilyIdentifier::Ipv4;
|
||||
let addr: Ipv4Addr = Ipv4Addr::from_str(parts[0]).map_err(|e| e.to_string())?;
|
||||
octets = addr.octets().to_vec();
|
||||
} else {
|
||||
return Err(format!("Could not detect IP address type: {}", parts[0]));
|
||||
}
|
||||
|
||||
// Truncate octets to prefixlen
|
||||
if prefixlen % 8 == 0 {
|
||||
// Cleanly truncate.
|
||||
octets.truncate((prefixlen / 8).into());
|
||||
} else {
|
||||
let num_bytes = (prefixlen / 8) + 1;
|
||||
let mask = u8::MAX << (8 - (prefixlen % 8));
|
||||
octets.truncate(num_bytes.into());
|
||||
if octets.len() > 0 {
|
||||
let last_pos = octets.len() - 1;
|
||||
octets[last_pos] &= mask;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(NLRI {
|
||||
afi,
|
||||
prefixlen,
|
||||
prefix: octets,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WritablePacket for NLRI {
|
||||
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
buf.push(self.prefixlen);
|
||||
buf.extend(self.prefix.as_slice());
|
||||
Ok(buf)
|
||||
}
|
||||
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
|
||||
Ok(1 + self.prefix.len() as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NLRI {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.afi {
|
||||
AddressFamilyIdentifier::Ipv4 => {
|
||||
let bytes = &mut self.prefix.clone();
|
||||
if bytes.len() < 4 {
|
||||
bytes.extend(std::iter::repeat(0).take(4 - bytes.len()));
|
||||
}
|
||||
let four_bytes: [u8; 4] = bytes.as_slice().try_into().map_err(|_| fmt::Error {})?;
|
||||
let ipv4_addr = Ipv4Addr::from(four_bytes);
|
||||
write!(f, "{}/{}", ipv4_addr, self.prefixlen)
|
||||
}
|
||||
AddressFamilyIdentifier::Ipv6 => {
|
||||
let bytes = &mut self.prefix.clone();
|
||||
if bytes.len() < 16 {
|
||||
bytes.extend(std::iter::repeat(0).take(16 - bytes.len()));
|
||||
}
|
||||
let sixteen_bytes: [u8; 16] =
|
||||
bytes.as_slice().try_into().map_err(|_| fmt::Error {})?;
|
||||
let ipv6_addr = Ipv6Addr::from(sixteen_bytes);
|
||||
write!(f, "{}/{}", ipv6_addr, self.prefixlen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use super::NLRI;
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier::{Ipv4, Ipv6};
|
||||
use crate::bgp_packet::traits::ParserContext;
|
||||
use crate::bgp_packet::traits::ReadablePacket;
|
||||
use crate::bgp_packet::traits::WritablePacket;
|
||||
|
||||
#[test]
|
||||
fn test_basic_nlri_v6() {
|
||||
let nlri_bytes: &[u8] = &[0x20, 0x20, 0x01, 0xdb, 0x8];
|
||||
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
|
||||
let nlri_res: (&[u8], NLRI) = NLRI::from_wire(ctx, nlri_bytes).unwrap();
|
||||
assert_eq!(nlri_res.1.afi, Ipv6);
|
||||
assert_eq!(nlri_res.1.prefixlen, 32);
|
||||
assert_eq!(nlri_res.1.prefix, vec![0x20, 0x01, 0xdb, 0x8]);
|
||||
assert_eq!(nlri_res.0.len(), 0);
|
||||
|
||||
let wire: Vec<u8> = nlri_res.1.to_wire(ctx).unwrap();
|
||||
assert_eq!(wire.as_slice(), nlri_bytes);
|
||||
assert_eq!(nlri_res.1.wire_len(ctx).unwrap() as usize, wire.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_nlri_v4() {
|
||||
let nlri_bytes: &[u8] = &[0x18, 192, 168, 1];
|
||||
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv4);
|
||||
let nlri_res: (&[u8], NLRI) = NLRI::from_wire(ctx, nlri_bytes).unwrap();
|
||||
assert_eq!(nlri_res.1.afi, Ipv4);
|
||||
assert_eq!(nlri_res.1.prefixlen, 24);
|
||||
assert_eq!(nlri_res.1.prefix, vec![192, 168, 1]);
|
||||
assert_eq!(nlri_res.0.len(), 0);
|
||||
|
||||
let wire: Vec<u8> = nlri_res.1.to_wire(ctx).unwrap();
|
||||
assert_eq!(wire.as_slice(), nlri_bytes);
|
||||
assert_eq!(nlri_res.1.wire_len(ctx).unwrap() as usize, wire.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_roundtrip() {
|
||||
let cases: Vec<(String, Vec<u8>, u8, String)> = vec![
|
||||
(
|
||||
"2001:db8::/32".into(),
|
||||
vec![0x20, 0x01, 0xd, 0xb8],
|
||||
32,
|
||||
"2001:db8::/32".into(),
|
||||
),
|
||||
(
|
||||
"2001:db8::1/16".into(),
|
||||
vec![0x20, 0x01],
|
||||
16,
|
||||
"2001::/16".into(),
|
||||
),
|
||||
(
|
||||
"2001:db8::/64".into(),
|
||||
vec![0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0],
|
||||
64,
|
||||
"2001:db8::/64".into(),
|
||||
),
|
||||
(
|
||||
"2001:db8::/24".into(),
|
||||
vec![0x20, 0x01, 0xd],
|
||||
24,
|
||||
"2001:d00::/24".into(),
|
||||
),
|
||||
("2001:db8::/0".into(), vec![], 0, "::/0".into()),
|
||||
("::/0".into(), vec![], 0, "::/0".into()),
|
||||
("10.0.0.0/8".into(), vec![10], 8, "10.0.0.0/8".into()),
|
||||
];
|
||||
|
||||
for (i, case) in cases.iter().enumerate() {
|
||||
let parsed_nlri = NLRI::try_from(case.0.clone()).unwrap();
|
||||
assert_eq!(parsed_nlri.prefix, case.1, "Check prefix match ({})", i);
|
||||
assert_eq!(
|
||||
parsed_nlri.prefixlen, case.2,
|
||||
"Check prefixlen match ({})",
|
||||
i
|
||||
);
|
||||
assert_eq!(
|
||||
case.3,
|
||||
format!("{}", parsed_nlri),
|
||||
"Check std::fmt::Display match ({})",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_to_string_invalids() {
|
||||
// let invalid_v4 = NLRI {
|
||||
// afi: AddressFamilyIdentifier::Ipv4,
|
||||
// prefix: vec![1, 2, 3, 4, 5],
|
||||
// prefixlen: 16,
|
||||
// };
|
||||
// assert_eq!(
|
||||
// "a formatting trait implementation returned an error: Error",
|
||||
// format!("{}", invalid_v4)
|
||||
// );
|
||||
// }
|
||||
}
|
||||
1226
bgpd/src/bgp_packet/path_attributes.rs
Normal file
1226
bgpd/src/bgp_packet/path_attributes.rs
Normal file
File diff suppressed because it is too large
Load Diff
81
bgpd/src/bgp_packet/traits.rs
Normal file
81
bgpd/src/bgp_packet/traits.rs
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Implements high level abstractions for use in the BGP parser.
|
||||
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use nom::error::ErrorKind;
|
||||
use nom::error::ParseError;
|
||||
use nom::IResult;
|
||||
|
||||
// ParserContext contains information pertinent to configurations which affect
|
||||
// how message parsing is to be handled.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ParserContext {
|
||||
// Whether the peer is RFC6793 compliant.
|
||||
pub four_octet_asn: Option<bool>,
|
||||
// nlri_mode specifies if a parsed NLRI prefix should be a IPv4 or IPv6 address.
|
||||
pub nlri_mode: Option<AddressFamilyIdentifier>,
|
||||
}
|
||||
|
||||
impl ParserContext {
|
||||
pub fn new() -> ParserContext {
|
||||
ParserContext {
|
||||
four_octet_asn: None,
|
||||
nlri_mode: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn four_octet_asn(mut self, v: bool) -> Self {
|
||||
self.four_octet_asn = Some(v);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nlri_mode(mut self, v: AddressFamilyIdentifier) -> Self {
|
||||
self.nlri_mode = Some(v);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Custom error type for the parser.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BGPParserError<I> {
|
||||
CustomText(String),
|
||||
Nom(I, ErrorKind),
|
||||
}
|
||||
|
||||
impl<I> ParseError<I> for BGPParserError<I> {
|
||||
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
|
||||
BGPParserError::Nom(input, kind)
|
||||
}
|
||||
fn append(_: I, _: ErrorKind, other: Self) -> Self {
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WritablePacket {
|
||||
/// to_wire serializes the packet to the wire format bytes.
|
||||
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str>;
|
||||
/// wire_len is the length of the message in bytes as would be on the wire.
|
||||
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str>;
|
||||
}
|
||||
|
||||
pub trait ReadablePacket {
|
||||
fn from_wire<'a>(
|
||||
ctx: &ParserContext,
|
||||
i: &'a [u8],
|
||||
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
3
bgpd/src/lib.rs
Normal file
3
bgpd/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod bgp_packet;
|
||||
pub mod route_client;
|
||||
pub mod server;
|
||||
93
bgpd/src/main.rs
Normal file
93
bgpd/src/main.rs
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bgpd::server::config::ServerConfig;
|
||||
use bgpd::server::bgp_server::Server;
|
||||
use clap::{App, Arg};
|
||||
use core::sync::atomic::AtomicBool;
|
||||
use libc::SIGUSR1;
|
||||
use signal_hook::consts::signal::*;
|
||||
use signal_hook::consts::TERM_SIGNALS;
|
||||
use signal_hook::flag;
|
||||
use signal_hook::iterator::exfiltrator::WithOrigin;
|
||||
use signal_hook::iterator::SignalsInfo;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::process::exit;
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let subscriber = tracing_subscriber::fmt().with_env_filter("bgpd=info");
|
||||
|
||||
match subscriber.try_init() {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to initialize logger: {:?}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let argv_matches = App::new("bgpd")
|
||||
.author("Rayhaan Jaufeerally <rayhaan@rayhaan.ch>")
|
||||
.version("0.1")
|
||||
.about("net-control-plane BGP daemon")
|
||||
.arg(Arg::with_name("config").takes_value(true))
|
||||
.get_matches();
|
||||
|
||||
info!("Starting BGP Daemon!");
|
||||
|
||||
let config_file = File::open(argv_matches.value_of("config").unwrap_or("config.json")).unwrap();
|
||||
let reader = BufReader::new(config_file);
|
||||
let server_config: ServerConfig = serde_json::from_reader(reader).unwrap();
|
||||
|
||||
info!("Parsed server config");
|
||||
|
||||
let mut bgp_server = Server::new(server_config);
|
||||
bgp_server.start(true).await.unwrap();
|
||||
|
||||
// The following signal handling code is from:
|
||||
// https://docs.rs/signal-hook/0.3.10/signal_hook/
|
||||
// Comments removed for brevity.
|
||||
let term_now = Arc::new(AtomicBool::new(false));
|
||||
for sig in TERM_SIGNALS {
|
||||
flag::register_conditional_shutdown(*sig, 1, Arc::clone(&term_now))?;
|
||||
flag::register(*sig, Arc::clone(&term_now))?;
|
||||
}
|
||||
|
||||
let mut sigs = vec![SIGHUP, SIGUSR1];
|
||||
sigs.extend(TERM_SIGNALS);
|
||||
let mut signals = SignalsInfo::<WithOrigin>::new(&sigs)?;
|
||||
|
||||
for info in &mut signals {
|
||||
match info.signal {
|
||||
// TODO: Implement something on receiving SIGHUP / SIGUSR1.
|
||||
SIGHUP => {
|
||||
println!("Caught SIGHUP, not doing anything");
|
||||
}
|
||||
SIGUSR1 => {
|
||||
println!("Caught SIGUSR1, not doing anything");
|
||||
}
|
||||
_term_sig => {
|
||||
eprintln!("Shutting down app");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bgp_server.shutdown().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
178
bgpd/src/route_client/fib_state.rs
Normal file
178
bgpd/src/route_client/fib_state.rs
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use crate::bgp_packet::nlri::NLRI;
|
||||
use crate::route_client::southbound_interface::SouthboundInterface;
|
||||
use futures::lock::Mutex;
|
||||
use ip_network_table_deps_treebitmap::address::Address;
|
||||
use ip_network_table_deps_treebitmap::IpLookupTable;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt::Formatter;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::sync::Arc;
|
||||
use tracing::{trace, warn};
|
||||
|
||||
/// fib_state implements the logic to maintain forwarding routes in the FIB.
|
||||
/// This for now means the Linux Kernel via Netlink, but in the future can
|
||||
/// be extended to include other targets such as OpenFlow or even program
|
||||
/// a router using BGP.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FibEntry {
|
||||
nexthop: IpAddr,
|
||||
}
|
||||
|
||||
pub struct FibState<A: Address, S: SouthboundInterface> {
|
||||
pub fib: IpLookupTable<A, Arc<Mutex<FibEntry>>>,
|
||||
pub southbound: S,
|
||||
pub af: AddressFamilyIdentifier,
|
||||
pub table: u32,
|
||||
}
|
||||
|
||||
impl<A: Address, S: SouthboundInterface> std::fmt::Debug for FibState<A, S> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "FibState af: {:?}, table: {}", self.af, self.table)
|
||||
}
|
||||
}
|
||||
|
||||
/// to_octets provides an interface for accessing an address as a vector of bytes.
|
||||
/// This is implemented for IPv4Addr and IPv6Addr to be able to use them interchangably
|
||||
/// to send updates to the kernel.
|
||||
pub trait ToOctets {
|
||||
fn octets(&self) -> Vec<u8>;
|
||||
}
|
||||
|
||||
impl ToOctets for Ipv4Addr {
|
||||
fn octets(&self) -> Vec<u8> {
|
||||
self.octets().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOctets for Ipv6Addr {
|
||||
fn octets(&self) -> Vec<u8> {
|
||||
self.octets().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
A: Address
|
||||
+ std::convert::TryFrom<NLRI>
|
||||
+ ToOctets
|
||||
+ std::cmp::PartialEq
|
||||
+ std::fmt::Display
|
||||
+ std::fmt::Debug,
|
||||
S: SouthboundInterface,
|
||||
> FibState<A, S>
|
||||
where
|
||||
String: From<<A as TryFrom<NLRI>>::Error>,
|
||||
{
|
||||
/// route_add requests updating the nexthop to a particular path if it is not already
|
||||
/// the best path.
|
||||
pub async fn route_add(&mut self, nlri: &NLRI, nexthop: IpAddr) -> Result<(), String> {
|
||||
// Lookup the path in the Fib, there are three possible outcomes:
|
||||
// 1. The route is not yet known, we add it to the FibState and inject it into the kernel,
|
||||
// 2. The route is known and has a prior nexthop that needs to be updated
|
||||
// 3. The route is known and has the same nexthop: no-op.
|
||||
let prefix_addr: A = nlri.clone().try_into()?;
|
||||
match self
|
||||
.fib
|
||||
.exact_match(prefix_addr, nlri.prefixlen.into())
|
||||
.as_mut()
|
||||
{
|
||||
Some(entry_wrapped) => {
|
||||
let mut entry = entry_wrapped.lock().await;
|
||||
if entry.nexthop == nexthop {
|
||||
// Nothing to do, route already in kernel.
|
||||
trace!("Skipping route that already exists in kernel");
|
||||
} else {
|
||||
// Remove old route
|
||||
trace!("Remove old route: {:?}", entry);
|
||||
if let Err(e) = self.southbound.route_del(nlri.clone(), entry.nexthop).await {
|
||||
warn!(
|
||||
"Southbound interface returned error when trying to remove route: {} via {}, error: {}",
|
||||
nlri, entry.nexthop, e
|
||||
);
|
||||
return Err("Netlink remove error".to_string());
|
||||
}
|
||||
|
||||
// Add new route
|
||||
trace!(
|
||||
"Add new route: prefix: {:?}, nexthop: {}",
|
||||
nlri.prefix,
|
||||
nexthop
|
||||
);
|
||||
if let Err(e) = self
|
||||
.southbound
|
||||
.route_add(self.af, nlri.clone(), nexthop)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Netlink returned error when trying to add route: {} via {}, error: {}",
|
||||
nlri, nexthop, e
|
||||
);
|
||||
return Err("Netlink add error".to_string());
|
||||
}
|
||||
|
||||
entry.nexthop = nexthop;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Need to insert a new entry for this route
|
||||
let entry = FibEntry {
|
||||
nexthop: nexthop.clone(),
|
||||
};
|
||||
|
||||
if let Err(e) = self
|
||||
.southbound
|
||||
.route_add(self.af, nlri.clone(), nexthop)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Netlink returned error when trying to add route: {} via {}, error: {}",
|
||||
nlri, nexthop, e
|
||||
);
|
||||
return Err("Netlink add error".to_string());
|
||||
}
|
||||
|
||||
let addr: A = nlri.clone().try_into()?;
|
||||
self.fib
|
||||
.insert(addr, nlri.prefixlen.into(), Arc::new(Mutex::new(entry)));
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// route_del removes a route from the FibState and kernel.
|
||||
pub async fn route_del(&mut self, nlri: NLRI) -> Result<(), String> {
|
||||
let prefix_addr: A = nlri.clone().try_into()?;
|
||||
if let Some(entry_wrapped) = self.fib.exact_match(prefix_addr, nlri.prefixlen.into()) {
|
||||
{
|
||||
let entry = entry_wrapped.lock().await;
|
||||
if let Err(e) = self.southbound.route_del(nlri.clone(), entry.nexthop).await {
|
||||
warn!(
|
||||
"Failed to apply route mutation to remove NLRI: {}, error: {}",
|
||||
nlri, e
|
||||
);
|
||||
}
|
||||
}
|
||||
self.fib.remove(prefix_addr, nlri.prefixlen.into());
|
||||
} else {
|
||||
warn!("Failed to find prefix to remove from FIB: {}", nlri);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
273
bgpd/src/route_client/main.rs
Normal file
273
bgpd/src/route_client/main.rs
Normal file
@ -0,0 +1,273 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bgpd::route_client::netlink::NetlinkConnector;
|
||||
use bgpd::route_client::southbound_interface::DummyVerifier;
|
||||
use bgpd::route_client::southbound_interface::SouthboundInterface;
|
||||
use clap::Parser;
|
||||
use log::trace;
|
||||
use std::convert::TryInto;
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use std::net::Ipv6Addr;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use tonic::transport::Uri;
|
||||
|
||||
use bgpd::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use bgpd::bgp_packet::nlri::NLRI;
|
||||
use bgpd::route_client::fib_state::FibState;
|
||||
|
||||
use ip_network_table_deps_treebitmap::IpLookupTable;
|
||||
use tonic::transport::Endpoint;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use crate::proto::route_service_client::RouteServiceClient;
|
||||
|
||||
pub mod proto {
|
||||
tonic::include_proto!("bgpd.grpc");
|
||||
}
|
||||
|
||||
fn vec_to_array<T, const N: usize>(v: Vec<T>) -> Result<[T; N], anyhow::Error> {
|
||||
v.try_into()
|
||||
.map_err(|_| anyhow::Error::msg("Wrong size of Vec".to_string()))
|
||||
}
|
||||
|
||||
/// Temporary hack to select the route to install to the FIB.
|
||||
/// TODO: Implement proper route selection logic.
|
||||
fn select_best_route(ps: &proto::PathSet) -> Option<proto::Path> {
|
||||
let mut selected: Option<proto::Path> = None;
|
||||
for path in &ps.paths {
|
||||
if let Some(current) = selected.as_ref() {
|
||||
if path.local_pref < current.local_pref {
|
||||
selected = Some(path.clone());
|
||||
}
|
||||
} else {
|
||||
selected = Some(path.clone());
|
||||
}
|
||||
}
|
||||
selected
|
||||
}
|
||||
|
||||
async fn run_connector_v4<S: SouthboundInterface>(
|
||||
route_server: String,
|
||||
rt_table: u32,
|
||||
dry_run: bool,
|
||||
southbound: S,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// Create netlink socket.
|
||||
let mut fib_state = FibState::<Ipv4Addr, S> {
|
||||
fib: IpLookupTable::new(),
|
||||
southbound,
|
||||
af: AddressFamilyIdentifier::Ipv4,
|
||||
table: rt_table,
|
||||
};
|
||||
|
||||
let uri = Uri::from_str(route_server.as_str()).unwrap();
|
||||
let endpoint = Endpoint::from(uri).keep_alive_timeout(Duration::from_secs(10));
|
||||
let mut client = RouteServiceClient::connect(endpoint).await?;
|
||||
let request = proto::StreamPathsRequest {
|
||||
address_family: proto::AddressFamily::IPv4.into(),
|
||||
};
|
||||
|
||||
let mut stream = client.stream_paths(request).await?.into_inner();
|
||||
let mut msg_ctr: u64 = 0;
|
||||
while let Some(route) = stream.message().await? {
|
||||
let nlri = NLRI {
|
||||
afi: AddressFamilyIdentifier::Ipv4,
|
||||
prefixlen: route.prefix.as_ref().unwrap().prefix_len as u8,
|
||||
prefix: route.prefix.as_ref().unwrap().ip_prefix.clone(),
|
||||
};
|
||||
|
||||
trace!("IPv4 Update {} for: {} ", msg_ctr, nlri);
|
||||
msg_ctr += 1;
|
||||
|
||||
if !dry_run {
|
||||
if !route.paths.is_empty() {
|
||||
if let Some(best) = select_best_route(&route) {
|
||||
// Hack to convert the nexthop into a v4 addr
|
||||
let nh_bytes: [u8; 4] = vec_to_array(best.nexthop.clone())?;
|
||||
let nh_addr: Ipv4Addr = Ipv4Addr::from(nh_bytes);
|
||||
if let Err(e) = fib_state.route_add(&nlri, IpAddr::V4(nh_addr)).await {
|
||||
return Err(anyhow!("Failed to add route {}: {}", nlri, e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No more paths, delete
|
||||
if let Err(e) = fib_state.route_del(nlri).await {
|
||||
return Err(anyhow!("Failed to delete route: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Number of paths: {}", route.paths.len());
|
||||
for path in &route.paths {
|
||||
// TODO: have a proper error here not unwrap.
|
||||
let nexthop_bytes: [u8; 4] = path.nexthop.clone().try_into().unwrap();
|
||||
let nexthop: Ipv4Addr = nexthop_bytes.into();
|
||||
trace!(
|
||||
"nexthop: {}, peer: {}, local_pref: {}, med: {}, as_path: {:?}",
|
||||
nexthop,
|
||||
path.peer_name,
|
||||
path.local_pref,
|
||||
path.med,
|
||||
path.as_path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn run_connector_v6<S: SouthboundInterface>(
|
||||
route_server: String,
|
||||
rt_table: u32,
|
||||
dry_run: bool,
|
||||
southbound: S,
|
||||
) -> Result<()> {
|
||||
let mut fib_state = FibState::<Ipv6Addr, S> {
|
||||
fib: IpLookupTable::new(),
|
||||
southbound,
|
||||
af: AddressFamilyIdentifier::Ipv6,
|
||||
table: rt_table,
|
||||
};
|
||||
|
||||
let uri = Uri::from_str(route_server.as_str()).unwrap();
|
||||
let endpoint = Endpoint::from(uri).keep_alive_timeout(Duration::from_secs(10));
|
||||
let mut client = RouteServiceClient::connect(endpoint).await?;
|
||||
let request = proto::StreamPathsRequest {
|
||||
address_family: proto::AddressFamily::IPv6.into(),
|
||||
};
|
||||
info!("Request: {:?}", request);
|
||||
|
||||
let mut stream = client.stream_paths(request).await?.into_inner();
|
||||
let mut msg_ctr: u64 = 0;
|
||||
while let Some(route) = stream.message().await? {
|
||||
let nlri = NLRI {
|
||||
afi: AddressFamilyIdentifier::Ipv6,
|
||||
prefixlen: route.prefix.as_ref().unwrap().prefix_len as u8,
|
||||
prefix: route.prefix.as_ref().unwrap().ip_prefix.clone(),
|
||||
};
|
||||
|
||||
trace!("IPv6 Update {} for: {} ", msg_ctr, nlri);
|
||||
msg_ctr += 1;
|
||||
|
||||
if !dry_run {
|
||||
if !route.paths.is_empty() {
|
||||
if let Some(best) = select_best_route(&route) {
|
||||
// Hack to convert the nexthop into a v6 addr
|
||||
let nh_bytes: [u8; 16] = vec_to_array(best.nexthop.clone())?;
|
||||
let nh_addr: Ipv6Addr = Ipv6Addr::from(nh_bytes);
|
||||
if let Err(e) = fib_state.route_add(&nlri, IpAddr::V6(nh_addr)).await {
|
||||
return Err(anyhow!("Failed to add route {}: {}", nlri, e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No more paths, delete
|
||||
if let Err(e) = fib_state.route_del(nlri).await {
|
||||
return Err(anyhow!("Failed to delete route: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Number of paths: {}", route.paths.len());
|
||||
for path in &route.paths {
|
||||
// TODO: have a proper error here not unwrap.
|
||||
let nexthop_bytes: [u8; 16] = path.nexthop.clone().try_into().unwrap();
|
||||
let nexthop: Ipv6Addr = nexthop_bytes.into();
|
||||
trace!(
|
||||
"nexthop: {}, peer: {}, local_pref: {}, med: {}, as_path: {:?}",
|
||||
nexthop,
|
||||
path.peer_name,
|
||||
path.local_pref,
|
||||
path.med,
|
||||
path.as_path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
author = "Rayhaan Jaufeerally <rayhaan@rayhaan.ch>",
|
||||
version = "0.1",
|
||||
about = "Installs routes from a BGP speaker via streaming RPC to the forwarding plane"
|
||||
)]
|
||||
struct Cli {
|
||||
#[clap(long = "route_server")]
|
||||
route_server: String,
|
||||
#[clap(long = "rt_table")]
|
||||
rt_table: Option<u32>,
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
let _init_log = stderrlog::new()
|
||||
.verbosity(2) // Shows info level.
|
||||
.show_module_names(true)
|
||||
.init();
|
||||
info!("Starting route client");
|
||||
|
||||
let rt_table = match args.rt_table {
|
||||
Some(table) => table,
|
||||
None => 201,
|
||||
};
|
||||
|
||||
let v4_joinhandle = {
|
||||
let server_addr = args.route_server.clone();
|
||||
tokio::task::spawn(async move {
|
||||
run_connector_v4::<NetlinkConnector>(
|
||||
server_addr.clone(),
|
||||
rt_table,
|
||||
args.dry_run,
|
||||
NetlinkConnector::new(Some(rt_table)).await.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
};
|
||||
|
||||
let v6_joinhandle = {
|
||||
let server_addr = args.route_server.clone();
|
||||
tokio::task::spawn(async move {
|
||||
run_connector_v6::<NetlinkConnector>(
|
||||
server_addr,
|
||||
rt_table,
|
||||
args.dry_run,
|
||||
NetlinkConnector::new(Some(rt_table)).await.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
_ = v4_joinhandle => {
|
||||
warn!("Unexpected exit of IPv4 connector");
|
||||
},
|
||||
_ = v6_joinhandle => {
|
||||
warn!("Unexpected exit of IPv6 connector");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
17
bgpd/src/route_client/mod.rs
Normal file
17
bgpd/src/route_client/mod.rs
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod fib_state;
|
||||
pub mod netlink;
|
||||
pub mod southbound_interface;
|
||||
173
bgpd/src/route_client/netlink.rs
Normal file
173
bgpd/src/route_client/netlink.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use crate::bgp_packet::{constants::AddressFamilyIdentifier, nlri::NLRI};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use futures::TryStreamExt;
|
||||
use netlink::constants::RTN_UNICAST;
|
||||
use netlink_packet_route::{rtnl::route::nlas::Nla, RouteHeader};
|
||||
use netlink_packet_route::{RouteMessage, RTPROT_STATIC};
|
||||
use rtnetlink::IpVersion;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::{convert::TryInto, io::ErrorKind};
|
||||
|
||||
use super::southbound_interface::SouthboundInterface;
|
||||
|
||||
/// NetlinkConnector implements methods to read/update Linux networking stuff including
|
||||
/// routes and link level info.
|
||||
pub struct NetlinkConnector {
|
||||
handle: rtnetlink::Handle,
|
||||
table: Option<u32>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SouthboundInterface for NetlinkConnector {
|
||||
async fn route_add(
|
||||
&mut self,
|
||||
address_family: AddressFamilyIdentifier,
|
||||
prefix: NLRI,
|
||||
nexthop: IpAddr,
|
||||
) -> Result<()> {
|
||||
let route = self.handle.route();
|
||||
match address_family {
|
||||
AddressFamilyIdentifier::Ipv6 => {
|
||||
let addr: Ipv6Addr = match prefix.clone().try_into()? {
|
||||
IpAddr::V6(addr) => addr,
|
||||
_ => {
|
||||
return Err(anyhow::Error::from(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Got non-IPv6 address from NLRI",
|
||||
)))
|
||||
}
|
||||
};
|
||||
let gw_addr: Ipv6Addr = match nexthop.clone().try_into()? {
|
||||
IpAddr::V6(addr) => addr,
|
||||
_ => {
|
||||
return Err(anyhow::Error::from(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Got non-IPv6 gateway for IPv6 NLRI",
|
||||
)))
|
||||
}
|
||||
};
|
||||
let mut mutation = route
|
||||
.add()
|
||||
.v6()
|
||||
.destination_prefix(addr, prefix.prefixlen)
|
||||
.gateway(gw_addr);
|
||||
if let Some(table_id) = self.table {
|
||||
mutation = mutation.table(table_id.try_into().unwrap());
|
||||
}
|
||||
mutation.execute().await.map_err(|e| anyhow::Error::from(e))
|
||||
}
|
||||
AddressFamilyIdentifier::Ipv4 => {
|
||||
let addr: Ipv4Addr = match prefix.clone().try_into()? {
|
||||
IpAddr::V4(addr) => addr,
|
||||
_ => {
|
||||
return Err(anyhow::Error::from(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Got non-IPv4 address from NLRI",
|
||||
)))
|
||||
}
|
||||
};
|
||||
let gw_addr = match nexthop.clone().try_into()? {
|
||||
IpAddr::V4(addr) => addr,
|
||||
_ => {
|
||||
return Err(anyhow::Error::from(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Got non-IPv4 gateway for IPv4 NLRI",
|
||||
)))
|
||||
}
|
||||
};
|
||||
let mut mutation = route
|
||||
.add()
|
||||
.v4()
|
||||
.destination_prefix(addr, prefix.prefixlen)
|
||||
.gateway(gw_addr);
|
||||
if let Some(table_id) = self.table {
|
||||
mutation = mutation.table(table_id.try_into().unwrap());
|
||||
}
|
||||
mutation.execute().await.map_err(|e| anyhow::Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn route_del(&mut self, prefix: NLRI, nexthop: IpAddr) -> Result<()> {
|
||||
let nh_octets = match nexthop {
|
||||
IpAddr::V6(addr) => addr.octets().to_vec(),
|
||||
IpAddr::V4(addr) => addr.octets().to_vec(),
|
||||
};
|
||||
let rt_handle = self.handle.route();
|
||||
let address_family = match prefix.afi {
|
||||
AddressFamilyIdentifier::Ipv4 => netlink_packet_route::rtnl::constants::AF_INET as u8,
|
||||
AddressFamilyIdentifier::Ipv6 => netlink_packet_route::rtnl::constants::AF_INET6 as u8,
|
||||
};
|
||||
let header = RouteHeader {
|
||||
address_family,
|
||||
destination_prefix_length: prefix.prefixlen,
|
||||
table: self.table.unwrap_or(0) as u8,
|
||||
protocol: RTPROT_STATIC,
|
||||
kind: RTN_UNICAST,
|
||||
..Default::default()
|
||||
};
|
||||
let mut rt_msg = RouteMessage {
|
||||
header,
|
||||
..Default::default()
|
||||
};
|
||||
let prefix_octets = match prefix.afi {
|
||||
AddressFamilyIdentifier::Ipv4 => {
|
||||
let addr: Ipv4Addr = match prefix.clone().try_into()? {
|
||||
IpAddr::V4(addr) => addr,
|
||||
_ => {
|
||||
return Err(anyhow::Error::from(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Got non-IPv4 address from NLRI",
|
||||
)))
|
||||
}
|
||||
};
|
||||
addr.octets().to_vec()
|
||||
}
|
||||
AddressFamilyIdentifier::Ipv6 => {
|
||||
let addr: Ipv6Addr = match prefix.clone().try_into()? {
|
||||
IpAddr::V6(addr) => addr,
|
||||
_ => {
|
||||
return Err(anyhow::Error::from(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"Got non-IPv6 address from NLRI",
|
||||
)))
|
||||
}
|
||||
};
|
||||
addr.octets().to_vec()
|
||||
}
|
||||
};
|
||||
rt_msg.nlas.push(Nla::Destination(prefix_octets));
|
||||
rt_msg.nlas.push(Nla::Gateway(nh_octets));
|
||||
rt_handle
|
||||
.del(rt_msg)
|
||||
.execute()
|
||||
.await
|
||||
.map_err(|e| anyhow::Error::from(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl NetlinkConnector {
|
||||
pub async fn new(table: Option<u32>) -> Result<Self> {
|
||||
let (connection, handle, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
Ok(NetlinkConnector { handle, table })
|
||||
}
|
||||
|
||||
pub async fn dump_routes(
|
||||
&mut self,
|
||||
address_family: AddressFamilyIdentifier,
|
||||
table: Option<u32>,
|
||||
) -> Result<Vec<RouteMessage>, rtnetlink::Error> {
|
||||
let mut req = self.handle.route().get(match address_family {
|
||||
AddressFamilyIdentifier::Ipv4 => IpVersion::V4,
|
||||
AddressFamilyIdentifier::Ipv6 => IpVersion::V6,
|
||||
});
|
||||
if let Some(table_id) = table {
|
||||
req.message_mut()
|
||||
.nlas
|
||||
.push(Nla::Table(table_id.try_into().unwrap()));
|
||||
}
|
||||
req.execute().try_collect().await
|
||||
}
|
||||
}
|
||||
100
bgpd/src/route_client/southbound_interface.rs
Normal file
100
bgpd/src/route_client/southbound_interface.rs
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{collections::HashMap, net::IpAddr};
|
||||
|
||||
use crate::bgp_packet::{constants::AddressFamilyIdentifier, nlri::NLRI};
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use log::info;
|
||||
|
||||
/// SouthboundInterface provides a uniform API to network forwarding elements
|
||||
/// These are devices or targets that perform packet routing and are the end
|
||||
/// consumers of packet routing data.
|
||||
|
||||
#[async_trait]
|
||||
pub trait SouthboundInterface {
|
||||
async fn route_add(
|
||||
&mut self,
|
||||
address_family: AddressFamilyIdentifier,
|
||||
prefix: NLRI,
|
||||
nexthop: IpAddr,
|
||||
) -> Result<()>;
|
||||
async fn route_del(&mut self, prefix: NLRI, nexthop: IpAddr) -> Result<()>;
|
||||
}
|
||||
|
||||
/// DummyVerifier is a SouthboundInterface that checks that routes are not added more than
|
||||
/// once and not removed when there are none.
|
||||
pub struct DummyVerifier {
|
||||
route_state: HashMap<NLRI, IpAddr>,
|
||||
}
|
||||
|
||||
impl std::default::Default for DummyVerifier {
|
||||
fn default() -> DummyVerifier {
|
||||
DummyVerifier {
|
||||
route_state: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SouthboundInterface for DummyVerifier {
|
||||
async fn route_add(
|
||||
&mut self,
|
||||
_: AddressFamilyIdentifier,
|
||||
prefix: NLRI,
|
||||
nexthop: IpAddr,
|
||||
) -> Result<()> {
|
||||
// Check that the route is not already present.
|
||||
match self.route_state.get(&prefix) {
|
||||
Some(value) => {
|
||||
return Err(anyhow!(
|
||||
"Prefix {} with nexthop {} already contained in route_state! when trying to add {} -> {}",
|
||||
prefix, value, prefix, nexthop,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if self.route_state.get(&prefix).is_some() {}
|
||||
// Insert route into in memory state.
|
||||
self.route_state.insert(prefix, nexthop);
|
||||
|
||||
info!("Route add ok in verifier ({})", self.route_state.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn route_del(&mut self, prefix: NLRI, nexthop: IpAddr) -> Result<()> {
|
||||
match self.route_state.remove(&prefix) {
|
||||
Some(entry) => {
|
||||
if entry != nexthop {
|
||||
return Err(anyhow!(
|
||||
"Removed entry's nexthop did not match: {} vs requested {}",
|
||||
entry,
|
||||
nexthop
|
||||
));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(anyhow!(
|
||||
"Requested removal of route {} that was not in route_state",
|
||||
prefix
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
info!("Route del ok in verifier ({})", self.route_state.len());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
527
bgpd/src/server/bgp_server.rs
Normal file
527
bgpd/src/server/bgp_server.rs
Normal file
@ -0,0 +1,527 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use crate::server::config::PeerConfig;
|
||||
use crate::server::config::ServerConfig;
|
||||
use crate::server::peer::PeerCommands;
|
||||
use crate::server::peer::PeerStateMachine;
|
||||
use crate::server::rib_manager::RibManager;
|
||||
use crate::server::rib_manager::RibSnapshot;
|
||||
use crate::server::rib_manager::RouteManagerCommands;
|
||||
use crate::server::route_server;
|
||||
use crate::server::route_server::route_server::route_service_server::RouteServiceServer;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::{info, warn};
|
||||
use warp::Filter;
|
||||
use warp::Reply;
|
||||
|
||||
// socket_listener starts listening on the given address, and passes clients that have
|
||||
// made an inbound connection to the provided stream. It also implements logic for
|
||||
// recreating the listener in the event that it fails.
|
||||
// Notifier is sent the restult of the first attempt to start the listener.
|
||||
async fn socket_listener(
|
||||
c: UnboundedSender<(TcpStream, SocketAddr)>,
|
||||
listen_addr: String,
|
||||
notifier: oneshot::Sender<Result<(), String>>,
|
||||
mut shutdown: broadcast::Receiver<()>,
|
||||
) {
|
||||
info!("Starting to listen on addr: {}", listen_addr);
|
||||
let listener_result = TcpListener::bind(&listen_addr).await;
|
||||
if let Err(e) = listener_result {
|
||||
warn!("Listener for {} failed: {}", listen_addr, e.to_string());
|
||||
match notifier.send(Err(e.to_string())) {
|
||||
Ok(_) => {}
|
||||
Err(e) => warn!("Failed to send notification of channel error: {:?}", e),
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let listener = listener_result.unwrap();
|
||||
match notifier.send(Ok(())) {
|
||||
Ok(_) => {}
|
||||
Err(e) => warn!("Failed to send notification of channel ready: {:?}", e),
|
||||
}
|
||||
info!("Sucessfully spawned listner for: {}", listen_addr);
|
||||
loop {
|
||||
let conn = tokio::select! {
|
||||
res = listener.accept() => res,
|
||||
_ = shutdown.recv() => {
|
||||
info!("Shutting down listener");
|
||||
return;
|
||||
}
|
||||
};
|
||||
info!("Got something: {:?}", conn);
|
||||
match conn {
|
||||
Ok((stream, addr)) => {
|
||||
info!("Accepted socket connection from {}", addr);
|
||||
match c.send((stream, addr)) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Dropped connection from {} due to mpsc::channel failure: {}",
|
||||
addr, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to accept connection: {}, aborting listener", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_http_server(
|
||||
manager4: UnboundedSender<RouteManagerCommands<Ipv4Addr>>,
|
||||
manager6: UnboundedSender<RouteManagerCommands<Ipv6Addr>>,
|
||||
peers: HashMap<String, UnboundedSender<PeerCommands>>,
|
||||
listen_addr: SocketAddr,
|
||||
mut shutdown: broadcast::Receiver<()>,
|
||||
) -> Result<tokio::task::JoinHandle<()>, String> {
|
||||
async fn manager_get_routes_handler<T: serde::ser::Serialize>(
|
||||
channel: UnboundedSender<RouteManagerCommands<T>>,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<RibSnapshot<T>>();
|
||||
if let Err(e) = channel.send(RouteManagerCommands::DumpRib(tx)) {
|
||||
warn!("Failed to send DumpRib request: {}", e);
|
||||
return Err(warp::reject());
|
||||
}
|
||||
|
||||
match rx.await {
|
||||
Ok(result) => Ok(warp::reply::json(&result)),
|
||||
Err(e) => {
|
||||
warn!("Failed to get RIB from manager: {}", e);
|
||||
Err(warp::reject())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn rm_large_community(
|
||||
chan: UnboundedSender<PeerCommands>,
|
||||
ld1: u32,
|
||||
ld2: u32,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<String>();
|
||||
if let Err(e) = chan.send(PeerCommands::RemoveLargeCommunity((ld1, ld2), tx)) {
|
||||
warn!("Failed to send RemoveLargeCommunity request: {}", e);
|
||||
return Err(warp::reject());
|
||||
}
|
||||
|
||||
match rx.await {
|
||||
Ok(result) => Ok(warp::reply::json(&result)),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"RemoveLargeCommunity response from peer state machine: {}",
|
||||
e
|
||||
);
|
||||
Err(warp::reject())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_large_community(
|
||||
chan: UnboundedSender<PeerCommands>,
|
||||
ld1: u32,
|
||||
ld2: u32,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<String>();
|
||||
if let Err(e) = chan.send(PeerCommands::AddLargeCommunity((ld1, ld2), tx)) {
|
||||
warn!("Failed to send AddLargeCommunity request: {}", e);
|
||||
return Err(warp::reject());
|
||||
}
|
||||
|
||||
match rx.await {
|
||||
Ok(result) => Ok(warp::reply::json(&result)),
|
||||
Err(e) => {
|
||||
warn!("AddLargeCommunity response from peer state machine: {}", e);
|
||||
Err(warp::reject())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset_peer_connection causes the PSM to close the connection, flush state, and reconnect to the peer.
|
||||
async fn reset_peer_connection(
|
||||
peer_name: String,
|
||||
peers: HashMap<String, UnboundedSender<PeerCommands>>,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
if let Some(peer_sender) = peers.get(&peer_name) {
|
||||
if let Err(e) = peer_sender.send(PeerCommands::ConnectionClosed()) {
|
||||
Ok(warp::reply::with_status(
|
||||
format!("Something went wrong: {}", e),
|
||||
warp::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
.into_response())
|
||||
} else {
|
||||
Ok(warp::reply::html(
|
||||
"Sent restart request to PeerStateMachine. Something might happen.",
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
} else {
|
||||
Ok(
|
||||
warp::reply::with_status("No such peer found!", warp::http::StatusCode::NOT_FOUND)
|
||||
.into_response(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// peerz is a debugging endpoint for PeerStateMachines on this server.
|
||||
async fn get_peerz(
|
||||
peers: HashMap<String, UnboundedSender<PeerCommands>>,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
let mut result: String = "<!DOCTYPE html><body>".to_string();
|
||||
for (peer_name, sender) in peers {
|
||||
result += &format!("<h2>{}</h2><br/>", peer_name);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
match sender.send(PeerCommands::GetStatus(tx)) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
warn!("Failed to send request to PSM channel: {}", e);
|
||||
return Ok(warp::reply::with_status(
|
||||
"Something went wrong!",
|
||||
warp::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
}
|
||||
match rx.await {
|
||||
Ok(resp) => {
|
||||
result += &format!("Peer state: <b>{:?}</b><br/>", resp.state);
|
||||
result += &format!("<code>{:?}</code>", resp.config);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error on rx from peer channel: {}", e);
|
||||
return Ok(warp::reply::with_status(
|
||||
"Something went wrong!",
|
||||
warp::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
}
|
||||
}
|
||||
result += "</body></html>";
|
||||
Ok(warp::http::Response::builder().body(result).into_response())
|
||||
}
|
||||
|
||||
/*
|
||||
async fn modify_community_fn(
|
||||
add: bool,
|
||||
peers: HashMap<String, UnboundedSender<PeerCommands>>,
|
||||
name: String,
|
||||
ld1: u32,
|
||||
ld2: u32,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
if let Some(chan) = peers.get(&name) {
|
||||
if let Err(e) = func(chan.clone(), ld1, ld2).await {
|
||||
warn!("Failed to add large community: {:?}", e);
|
||||
return Err(warp::reject());
|
||||
}
|
||||
} else {
|
||||
return Err(warp::reject());
|
||||
}
|
||||
Ok(warp::reply::with_status("Ok", warp::http::StatusCode::OK))
|
||||
}
|
||||
|
||||
let add_community_filter = warp::post()
|
||||
.map(move || true)
|
||||
.and(warp::path::param())
|
||||
.and(warp::path!(u32 / u32))
|
||||
.and_then(modify_community_fn);
|
||||
|
||||
*/
|
||||
|
||||
// Start the web server that has access to the rib managers so that it can expose the state.
|
||||
let v4_mgr_filter = warp::any().map(move || manager4.clone());
|
||||
|
||||
let warp_v4_routes = warp::get()
|
||||
.and(warp::path("ipv4"))
|
||||
.and(warp::path("routes"))
|
||||
.and(warp::path::end())
|
||||
.and(v4_mgr_filter)
|
||||
.and_then(manager_get_routes_handler);
|
||||
|
||||
let v6_mgr_filter = warp::any().map(move || manager6.clone());
|
||||
|
||||
let warp_v6_routes = warp::get()
|
||||
.and(warp::path("ipv6"))
|
||||
.and(warp::path("routes"))
|
||||
.and(warp::path::end())
|
||||
.and(v6_mgr_filter)
|
||||
.and_then(manager_get_routes_handler);
|
||||
|
||||
let peers_map_filter = warp::any().map(move || peers.clone());
|
||||
let peerz_route = warp::get()
|
||||
.and(warp::path("peerz"))
|
||||
.and(warp::path::end())
|
||||
.and(peers_map_filter.clone())
|
||||
.and_then(get_peerz);
|
||||
|
||||
let peers_restart_route = warp::post()
|
||||
.and(warp::path("peerz"))
|
||||
.and(warp::path::param())
|
||||
.and(warp::path("restart"))
|
||||
.and(warp::path::end())
|
||||
.and(peers_map_filter)
|
||||
.and_then(reset_peer_connection);
|
||||
|
||||
let routes = warp_v4_routes
|
||||
.or(warp_v6_routes)
|
||||
.or(peerz_route)
|
||||
.or(peers_restart_route);
|
||||
let (_, server) = warp::serve(routes)
|
||||
.try_bind_with_graceful_shutdown(listen_addr, async move {
|
||||
shutdown.recv().await.ok();
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(tokio::task::spawn(server))
|
||||
}
|
||||
|
||||
/// Server encapsulates the behavior of the BGP speaker.
|
||||
pub struct Server {
|
||||
config: ServerConfig,
|
||||
|
||||
// shutdown is a channel that a
|
||||
shutdown: broadcast::Sender<()>,
|
||||
|
||||
// worker_handles contains the JoinHandle of tasks spawned by the server so that
|
||||
// we can wait on them for shutdown.
|
||||
worker_handles: Vec<tokio::task::JoinHandle<()>>,
|
||||
|
||||
mgr_v6: Option<UnboundedSender<RouteManagerCommands<Ipv6Addr>>>,
|
||||
mgr_v4: Option<UnboundedSender<RouteManagerCommands<Ipv4Addr>>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(config: ServerConfig) -> Server {
|
||||
let (shutdown, _) = broadcast::channel(1);
|
||||
Server {
|
||||
config,
|
||||
shutdown,
|
||||
worker_handles: vec![],
|
||||
mgr_v4: None,
|
||||
mgr_v6: None,
|
||||
}
|
||||
}
|
||||
|
||||
// start kicks off the BGP server
|
||||
// wait_startup controls whether this function waits for the listeners to come up healthy
|
||||
// before returning. This is useful in tests and other situations where we want to wait
|
||||
// and then probe the endpoints.
|
||||
pub async fn start(&mut self, wait_startup: bool) -> Result<(), String> {
|
||||
// TODO: the following code spawns a bunch of asynchronous tasks, and it would be
|
||||
// good to have a handle on the status of these tasks so that we can restart them
|
||||
// or alert if they crash.
|
||||
|
||||
// Channel for passing newly established TCP streams to the dispatcher.
|
||||
let (tcp_in_tx, mut tcp_in_rx): (UnboundedSender<(TcpStream, SocketAddr)>, _) =
|
||||
tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
// For every address we are meant to listen on, we spawn a task that will listen on
|
||||
// that address. This is so that if the listening socket breaks somehow, we can
|
||||
// periodically retry to listen again.
|
||||
for listen_addr in self.config.clone().listen_addrs {
|
||||
info!("Starting listener for {}", listen_addr.to_string());
|
||||
let sender = tcp_in_tx.clone();
|
||||
let (ready_tx, ready_rx) = oneshot::channel();
|
||||
let shutdown_channel = self.shutdown.subscribe();
|
||||
let listen_handle = tokio::spawn(async move {
|
||||
socket_listener(sender, listen_addr.to_string(), ready_tx, shutdown_channel).await;
|
||||
});
|
||||
self.worker_handles.push(listen_handle);
|
||||
if wait_startup {
|
||||
let statup_result = ready_rx.await;
|
||||
match statup_result {
|
||||
Ok(_) => {}
|
||||
Err(err) => return Err(format!("Failed to startup listener: {}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start the route manager for IPv6 and IPv4.
|
||||
let (rp6_tx, rp6_rx) = unbounded_channel::<RouteManagerCommands<Ipv6Addr>>();
|
||||
self.mgr_v6 = Some(rp6_tx.clone());
|
||||
let mut rib_manager6: RibManager<Ipv6Addr> =
|
||||
RibManager::<Ipv6Addr>::new(rp6_rx, self.shutdown.subscribe()).unwrap();
|
||||
tokio::spawn(async move {
|
||||
match rib_manager6.run().await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!("RIBManager exited: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let (rp4_tx, rp4_rx) = unbounded_channel::<RouteManagerCommands<Ipv4Addr>>();
|
||||
self.mgr_v4 = Some(rp4_tx.clone());
|
||||
let mut rib_manager4: RibManager<Ipv4Addr> =
|
||||
RibManager::<Ipv4Addr>::new(rp4_rx, self.shutdown.subscribe()).unwrap();
|
||||
tokio::spawn(async move {
|
||||
match rib_manager4.run().await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!("RIBManager exited: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start a PeerStateMachine for every peer that is configured and store its channel so that
|
||||
// we can communicate with it.
|
||||
|
||||
let mut peer_statemachines: HashMap<String, (PeerConfig, UnboundedSender<PeerCommands>)> =
|
||||
HashMap::new();
|
||||
|
||||
for peer_config in &self.config.peers {
|
||||
let (psm_tx, psm_rx) = unbounded_channel::<PeerCommands>();
|
||||
match peer_config.afi {
|
||||
AddressFamilyIdentifier::Ipv6 => {
|
||||
let mut psm = PeerStateMachine::<Ipv6Addr>::new(
|
||||
self.config.clone(),
|
||||
peer_config.clone(),
|
||||
psm_rx,
|
||||
psm_tx.clone(),
|
||||
rp6_tx.clone(),
|
||||
self.shutdown.subscribe(),
|
||||
);
|
||||
self.worker_handles.push(tokio::spawn(async move {
|
||||
psm.run().await;
|
||||
warn!("Should not reach here");
|
||||
}));
|
||||
}
|
||||
AddressFamilyIdentifier::Ipv4 => {
|
||||
let mut psm = PeerStateMachine::<Ipv4Addr>::new(
|
||||
self.config.clone(),
|
||||
peer_config.clone(),
|
||||
psm_rx,
|
||||
psm_tx.clone(),
|
||||
rp4_tx.clone(),
|
||||
self.shutdown.subscribe(),
|
||||
);
|
||||
self.worker_handles.push(tokio::spawn(async move {
|
||||
psm.run().await;
|
||||
warn!("Should not reach here");
|
||||
}));
|
||||
}
|
||||
_ => panic!("Unsupported address family: {}", peer_config.afi),
|
||||
}
|
||||
|
||||
peer_statemachines.insert(peer_config.name.clone(), (peer_config.clone(), psm_tx));
|
||||
}
|
||||
|
||||
let mut peer_chan_map: HashMap<String, UnboundedSender<PeerCommands>> = HashMap::new();
|
||||
for (k, v) in &peer_statemachines {
|
||||
peer_chan_map.insert(k.to_string(), v.1.clone());
|
||||
}
|
||||
|
||||
// Start the HTTP server for debugging access.
|
||||
if let Some(http_addr) = &self.config.http_addr {
|
||||
let addr = http_addr.parse().unwrap();
|
||||
start_http_server(
|
||||
rp4_tx.clone(),
|
||||
rp6_tx.clone(),
|
||||
peer_chan_map.clone(),
|
||||
addr,
|
||||
self.shutdown.subscribe(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Start the gRPC server for streaming the RIB.
|
||||
if let Some(grpc_addr) = &self.config.grpc_addr {
|
||||
let addr = grpc_addr.parse().unwrap();
|
||||
info!("Running gRPC RouteService on {}", addr);
|
||||
let rs = route_server::RouteServer {
|
||||
ip4_manager: rp4_tx.clone(),
|
||||
ip6_manager: rp6_tx.clone(),
|
||||
peer_state_machines: peer_chan_map,
|
||||
};
|
||||
|
||||
let svc = RouteServiceServer::new(rs);
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = tonic::transport::Server::builder()
|
||||
.add_service(svc)
|
||||
.serve(addr)
|
||||
.await
|
||||
{
|
||||
warn!("Failed to run gRPC server: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Event loop for processing inbound connections.
|
||||
let mut shutdown_recv = self.shutdown.subscribe();
|
||||
self.worker_handles.push(tokio::spawn(async move {
|
||||
loop {
|
||||
let next = tokio::select! {
|
||||
cmd = tcp_in_rx.recv() => cmd,
|
||||
_ = shutdown_recv.recv() => {
|
||||
warn!("Peer connection dispatcher shutting down due to shutdown signal.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match next {
|
||||
Some((socket, addr)) => {
|
||||
let mut psm_opt: Option<UnboundedSender<PeerCommands>> = None;
|
||||
for (name, handle) in &peer_statemachines {
|
||||
if handle.0.ip == addr.ip() {
|
||||
info!("Got connection for peer: {}", name);
|
||||
psm_opt = Some(handle.1.clone());
|
||||
}
|
||||
}
|
||||
if let Some(psm) = psm_opt {
|
||||
psm.send(PeerCommands::NewConnection(socket)).unwrap();
|
||||
} else {
|
||||
info!("Dropping unrecognized connection from {}", addr);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
warn!("Failed to read incoming connections, exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn shutdown(&mut self) {
|
||||
match self.shutdown.send(()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!("Failed to send shutdown signal: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for handle in &mut self.worker_handles {
|
||||
match handle.await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!("Failed to shutdown task: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
bgpd/src/server/config.rs
Normal file
73
bgpd/src/server/config.rs
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::constants::{AddressFamilyIdentifier, SubsequentAddressFamilyIdentifier};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
pub identifier: Ipv4Addr,
|
||||
pub asn: u32,
|
||||
pub hold_time: u16,
|
||||
|
||||
// The address to listen on for control plane gRPC connections.
|
||||
// If unset the gRPC server is not started.
|
||||
pub grpc_addr: Option<String>,
|
||||
|
||||
// The address to listen on for the debugging HTTP server.
|
||||
// If unset the HTTP server is not started.
|
||||
pub http_addr: Option<String>,
|
||||
|
||||
// The addresses to listen on for BGP peers.
|
||||
pub listen_addrs: Vec<String>,
|
||||
|
||||
pub peers: Vec<PeerConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PeerConfig {
|
||||
/// A unique name for this peer.
|
||||
pub name: String,
|
||||
|
||||
pub ip: IpAddr,
|
||||
/// Optional port number to communicate with this peer.
|
||||
pub port: Option<u16>,
|
||||
/// Autonomous system number of the peer.
|
||||
pub asn: u32,
|
||||
|
||||
pub afi: AddressFamilyIdentifier,
|
||||
pub safi: SubsequentAddressFamilyIdentifier,
|
||||
|
||||
pub local_pref: u32,
|
||||
|
||||
// Announcements is a hardcoded list of BGP updates to send
|
||||
// to the peer.
|
||||
pub announcements: Vec<PrefixAnnouncement>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PrefixAnnouncement {
|
||||
pub prefix: String,
|
||||
/// Nexthop to be announced for this prefix.
|
||||
pub nexthop: IpAddr,
|
||||
/// Linklocal nexthop to be used for IPv6 announcements.
|
||||
pub llnh: Option<Ipv6Addr>,
|
||||
|
||||
// Path attributes
|
||||
pub local_pref: Option<u32>,
|
||||
pub med: Option<u32>,
|
||||
pub communities: Option<Vec<String>>,
|
||||
pub large_communities: Option<Vec<String>>,
|
||||
}
|
||||
66
bgpd/src/server/data_structures.rs
Normal file
66
bgpd/src/server/data_structures.rs
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::nlri::NLRI;
|
||||
use crate::bgp_packet::path_attributes::PathAttribute;
|
||||
use std::time::SystemTime;
|
||||
|
||||
/// RouteInfo encapsulates information received about a particular BGP route.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RouteInfo<A> {
|
||||
pub prefix: A,
|
||||
pub prefixlen: u8,
|
||||
pub nlri: NLRI,
|
||||
|
||||
/// accepted is true if the route was accepted.
|
||||
pub accepted: bool,
|
||||
|
||||
/// rejection_reason contains the reason why a particular route was dropped.
|
||||
pub rejection_reason: Option<String>,
|
||||
|
||||
/// Time at which this path was learned from the peer.
|
||||
pub learned: SystemTime,
|
||||
/// Time at which this path was last updated by the peer.
|
||||
pub updated: SystemTime,
|
||||
|
||||
/// The current path attributes from the UPDATE message where this path
|
||||
/// was learned.
|
||||
pub path_attributes: Vec<PathAttribute>,
|
||||
}
|
||||
|
||||
/// RouteUpdate is a type which encapsulates a newly learned, modified, or removed set of prefixes.
|
||||
#[derive(Debug)]
|
||||
pub enum RouteUpdate {
|
||||
Announce(RouteAnnounce),
|
||||
Withdraw(RouteWithdraw),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteAnnounce {
|
||||
pub peer: String,
|
||||
pub prefixes: Vec<NLRI>,
|
||||
|
||||
pub local_pref: u32,
|
||||
pub med: u32,
|
||||
pub as_path: Vec<u32>,
|
||||
pub nexthop: Vec<u8>,
|
||||
|
||||
pub path_attributes: Vec<PathAttribute>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteWithdraw {
|
||||
pub peer: String,
|
||||
pub prefixes: Vec<NLRI>,
|
||||
}
|
||||
20
bgpd/src/server/mod.rs
Normal file
20
bgpd/src/server/mod.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod bgp_server;
|
||||
pub mod config;
|
||||
pub mod data_structures;
|
||||
pub mod peer;
|
||||
pub mod rib_manager;
|
||||
pub mod route_server;
|
||||
1337
bgpd/src/server/peer.rs
Normal file
1337
bgpd/src/server/peer.rs
Normal file
File diff suppressed because it is too large
Load Diff
382
bgpd/src/server/rib_manager.rs
Normal file
382
bgpd/src/server/rib_manager.rs
Normal file
@ -0,0 +1,382 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::nlri::NLRI;
|
||||
use crate::server::data_structures::RouteAnnounce;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::bgp_packet::path_attributes::PathAttribute;
|
||||
use crate::server::config::PeerConfig;
|
||||
use crate::server::data_structures::RouteUpdate;
|
||||
use crate::server::peer::PeerCommands;
|
||||
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
use std::cmp::Eq;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use ip_network_table_deps_treebitmap::address::Address;
|
||||
use serde::Serialize;
|
||||
use std::sync::Mutex;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use super::data_structures::RouteWithdraw;
|
||||
|
||||
type PeerInterface = mpsc::UnboundedSender<PeerCommands>;
|
||||
|
||||
/// Path is a structure to contain a specific route via one nexthop.
|
||||
/// Note that currently there is an assumption that there is only
|
||||
/// one route per peer per prefix, but when ADD-PATH support is added
|
||||
/// this will no longer hold true.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Path {
|
||||
pub nexthop: Vec<u8>,
|
||||
pub peer_name: String,
|
||||
pub local_pref: u32,
|
||||
pub med: u32,
|
||||
pub as_path: Vec<u32>,
|
||||
pub path_attributes: Vec<PathAttribute>,
|
||||
}
|
||||
|
||||
impl PartialEq for Path {
|
||||
fn eq(&self, other: &Path) -> bool {
|
||||
// Local pref
|
||||
if self.local_pref > other.local_pref {
|
||||
return true;
|
||||
}
|
||||
// AS path length
|
||||
if self.as_path.len() < other.as_path.len() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Origin
|
||||
|
||||
// MED lower is better
|
||||
if self.med < other.med {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use peer name as discriminator of last resort
|
||||
self.peer_name < other.peer_name
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Path {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct PathSet<A> {
|
||||
pub addr: A,
|
||||
pub prefixlen: u8,
|
||||
pub nlri: NLRI,
|
||||
// paths is stored in a BTreeMap which is sorted and allows us to efficiently
|
||||
// find the best path.
|
||||
pub paths: BTreeMap<String, Path>,
|
||||
}
|
||||
|
||||
/// RibSnapshot contians a version number and the dump of all the routes.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct RibSnapshot<A> {
|
||||
pub epoch: u64,
|
||||
pub routes: Vec<PathSet<A>>,
|
||||
}
|
||||
|
||||
pub enum RouteManagerCommands<A> {
|
||||
Update(RouteUpdate),
|
||||
/// DumpRib returns the view of the RIB at the current epoch.
|
||||
DumpRib(oneshot::Sender<RibSnapshot<A>>),
|
||||
/// StreamRib will send all the routes currently in the RIB then stream updates.
|
||||
StreamRib(
|
||||
mpsc::UnboundedSender<(u64, PathSet<A>)>,
|
||||
oneshot::Sender<broadcast::Receiver<(u64, PathSet<A>)>>,
|
||||
),
|
||||
}
|
||||
|
||||
pub struct RibManager<A: Address> {
|
||||
mgr_rx: mpsc::UnboundedReceiver<RouteManagerCommands<A>>,
|
||||
peers: HashMap<String, (PeerConfig, PeerInterface)>,
|
||||
|
||||
// We need to use a mutex for PathSet because IpLookupTable does not return a mut ptr.
|
||||
rib: ip_network_table_deps_treebitmap::IpLookupTable<A, Mutex<PathSet<A>>>,
|
||||
epoch: u64,
|
||||
|
||||
// Handle for streaming updates to PathSets in the RIB.
|
||||
pathset_streaming_handle: broadcast::Sender<(u64, PathSet<A>)>,
|
||||
|
||||
shutdown: broadcast::Receiver<()>,
|
||||
}
|
||||
|
||||
impl<A: Address> RibManager<A>
|
||||
where
|
||||
NLRI: TryInto<A>,
|
||||
<NLRI as TryInto<A>>::Error: ToString,
|
||||
A: std::fmt::Debug + std::fmt::Display,
|
||||
{
|
||||
pub fn new(
|
||||
chan: mpsc::UnboundedReceiver<RouteManagerCommands<A>>,
|
||||
shutdown: broadcast::Receiver<()>,
|
||||
) -> Result<Self, std::io::Error> {
|
||||
// TODO: Make this a flag that can be configured.
|
||||
let (pathset_tx, _) = broadcast::channel(10_000_000);
|
||||
Ok(RibManager::<A> {
|
||||
mgr_rx: chan,
|
||||
peers: HashMap::new(),
|
||||
rib: ip_network_table_deps_treebitmap::IpLookupTable::new(),
|
||||
epoch: 0,
|
||||
pathset_streaming_handle: pathset_tx,
|
||||
shutdown,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), String> {
|
||||
loop {
|
||||
let next = tokio::select! {
|
||||
cmd = self.mgr_rx.recv() => cmd,
|
||||
_ = self.shutdown.recv() => {
|
||||
warn!("RIB manager shutting down due to shutdown signal.");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
match next {
|
||||
Some(mgr_cmd) => match mgr_cmd {
|
||||
RouteManagerCommands::Update(update) => self.handle_update(update)?,
|
||||
RouteManagerCommands::DumpRib(sender) => {
|
||||
self.dump_rib(sender);
|
||||
}
|
||||
RouteManagerCommands::StreamRib(dump_sender, stream_sender) => {
|
||||
self.stream_rib(dump_sender, stream_sender);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!("All senders of the manager channel have been dropped, manager exiting!");
|
||||
return Err("Manager exited due to channel closure".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump_rib returns an atomic snapshot of the RIB at the current epoch.
|
||||
fn dump_rib(&mut self, sender: tokio::sync::oneshot::Sender<RibSnapshot<A>>) {
|
||||
info!("Starting RIB dump");
|
||||
let mut snapshot = RibSnapshot::<A> {
|
||||
epoch: self.epoch,
|
||||
routes: vec![],
|
||||
};
|
||||
for pathset in self.rib.iter() {
|
||||
snapshot.routes.push(pathset.2.lock().unwrap().clone());
|
||||
}
|
||||
// TODO: handle an error here.
|
||||
if let Err(e) = sender.send(snapshot) {
|
||||
warn!("Failed to send snapshot of RIB: {:?}", e);
|
||||
}
|
||||
info!("Done RIB dump");
|
||||
}
|
||||
|
||||
/// stream_rib sends the current routes in the RIB back via dump_chan then closes it,
|
||||
/// and subsequently returns a broadcast::Receiver for streaming updates.
|
||||
fn stream_rib(
|
||||
&mut self,
|
||||
dump_sender: mpsc::UnboundedSender<(u64, PathSet<A>)>,
|
||||
stream_sender: oneshot::Sender<broadcast::Receiver<(u64, PathSet<A>)>>,
|
||||
) {
|
||||
// Send all the routes currently in the RIB.
|
||||
for pathset in self.rib.iter() {
|
||||
if let Err(e) = dump_sender.send((self.epoch, pathset.2.lock().unwrap().clone())) {
|
||||
warn!("Failed to send dump to client: {}", e);
|
||||
}
|
||||
}
|
||||
drop(dump_sender);
|
||||
// Create a new subscriber and return that to the caller to be notified of updates.
|
||||
let subscriber = self.pathset_streaming_handle.subscribe();
|
||||
if let Err(e) = stream_sender.send(subscriber) {
|
||||
warn!("Failed to send subscriber in stream_rib: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, update: RouteUpdate) -> Result<(), String> {
|
||||
match update {
|
||||
RouteUpdate::Announce(announce) => self.handle_announce(announce),
|
||||
RouteUpdate::Withdraw(withdraw) => self.handle_withdraw(withdraw),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_announce(&mut self, update: RouteAnnounce) -> Result<(), String> {
|
||||
let peer_name = update.peer.clone();
|
||||
let nexthop = update.nexthop;
|
||||
for nlri in update.prefixes {
|
||||
// Increment the epoch on every NLRI processed.
|
||||
self.epoch += 1;
|
||||
let addr: A = nlri.clone().try_into().map_err(|e| e.to_string())?;
|
||||
let prefixlen = nlri.prefixlen;
|
||||
if let Some(path_set_wrapped) = self.rib.exact_match(addr, prefixlen.into()) {
|
||||
let mut path_set = path_set_wrapped.lock().unwrap();
|
||||
// There is already this prefix in the RIB, check if this is a
|
||||
// reannouncement or fresh announcement.
|
||||
match path_set.paths.get_mut(&update.peer) {
|
||||
// Peer already announced this route before.
|
||||
Some(mut existing) => {
|
||||
trace!(
|
||||
"Updating existing path attributes for NLRI: {}/{}",
|
||||
addr,
|
||||
prefixlen
|
||||
);
|
||||
existing.nexthop = nexthop.clone();
|
||||
existing.path_attributes = update.path_attributes.clone();
|
||||
}
|
||||
// First time that this peer is announcing the route.
|
||||
None => {
|
||||
let path = Path {
|
||||
nexthop: nexthop.clone(),
|
||||
peer_name: peer_name.clone(),
|
||||
local_pref: update.local_pref,
|
||||
med: update.med,
|
||||
as_path: update.as_path.clone(),
|
||||
path_attributes: update.path_attributes.clone(),
|
||||
};
|
||||
path_set.paths.insert(update.peer.clone(), path);
|
||||
}
|
||||
}
|
||||
|
||||
// There is no explicit sorting and marking of the best path since
|
||||
// BTreeMap is already sorted.
|
||||
|
||||
// Ignore errors sending due to no active receivers on the channel.
|
||||
let _ = self
|
||||
.pathset_streaming_handle
|
||||
.send((self.epoch, path_set.clone()));
|
||||
} else {
|
||||
// This prefix has never been seen before, so add a new PathSet for it.
|
||||
let mut path_set = PathSet::<A> {
|
||||
addr,
|
||||
prefixlen: nlri.prefixlen,
|
||||
nlri,
|
||||
paths: BTreeMap::new(),
|
||||
};
|
||||
let path = Path {
|
||||
nexthop: nexthop.clone(),
|
||||
peer_name: peer_name.clone(),
|
||||
local_pref: update.local_pref,
|
||||
med: update.med,
|
||||
as_path: update.as_path.clone(),
|
||||
path_attributes: update.path_attributes.clone(),
|
||||
};
|
||||
path_set.paths.insert(peer_name.clone(), path);
|
||||
self.rib
|
||||
.insert(addr, prefixlen.into(), Mutex::new(path_set.clone()));
|
||||
|
||||
// Ignore errors sending due to no active receivers on the channel.
|
||||
let _ = self.pathset_streaming_handle.send((self.epoch, path_set));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_withdraw(&mut self, update: RouteWithdraw) -> Result<(), String> {
|
||||
for nlri in update.prefixes {
|
||||
self.epoch += 1;
|
||||
let addr: A = nlri.clone().try_into().map_err(|e| e.to_string())?;
|
||||
let mut pathset_empty = false;
|
||||
if let Some(path_set_wrapped) = self.rib.exact_match(addr, nlri.prefixlen.into()) {
|
||||
let mut path_set = path_set_wrapped.lock().unwrap();
|
||||
let removed = path_set.paths.remove(&update.peer);
|
||||
if removed.is_none() {
|
||||
warn!(
|
||||
"Got a withdrawal for route {} from {}, which was not in RIB",
|
||||
nlri, update.peer
|
||||
);
|
||||
}
|
||||
// Ignore errors sending due to no active receivers on the channel.
|
||||
let _ = self
|
||||
.pathset_streaming_handle
|
||||
.send((self.epoch, path_set.clone()));
|
||||
if path_set.paths.is_empty() {
|
||||
pathset_empty = true;
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Got a withdrawal for route {} from {}, which was not in RIB",
|
||||
nlri, update.peer
|
||||
);
|
||||
}
|
||||
if pathset_empty {
|
||||
self.rib.remove(addr, nlri.prefixlen.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lookup_path_exact(&self, addr: A, prefixlen: u32) -> Option<PathSet<A>> {
|
||||
self.rib
|
||||
.exact_match(addr, prefixlen)
|
||||
.map(|path| path.lock().unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use crate::bgp_packet::nlri::NLRI;
|
||||
use crate::server::rib_manager::RibManager;
|
||||
use crate::server::rib_manager::RouteAnnounce;
|
||||
use crate::server::rib_manager::RouteManagerCommands;
|
||||
use crate::server::rib_manager::RouteUpdate;
|
||||
|
||||
use std::net::Ipv6Addr;
|
||||
use std::str::FromStr;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[test]
|
||||
fn test_manager_process_single() {
|
||||
let (_, rp_rx) = mpsc::unbounded_channel::<RouteManagerCommands<Ipv6Addr>>();
|
||||
// Nothing spaawned here so no need to send the shutdown signal.
|
||||
let (_shutdown_tx, shutdown_rx) = tokio::sync::broadcast::channel(1);
|
||||
let mut rib_manager: RibManager<Ipv6Addr> =
|
||||
RibManager::<Ipv6Addr>::new(rp_rx, shutdown_rx).unwrap();
|
||||
|
||||
let nexthop = Ipv6Addr::new(0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0x1);
|
||||
|
||||
// Send an update to the manager and check that it adds it to the RIB.
|
||||
let announce = RouteAnnounce {
|
||||
peer: "Some peer".to_string(),
|
||||
prefixes: vec![NLRI {
|
||||
afi: AddressFamilyIdentifier::Ipv6,
|
||||
prefixlen: 32,
|
||||
prefix: vec![0x20, 0x01, 0xd, 0xb8],
|
||||
}],
|
||||
as_path: vec![65536],
|
||||
local_pref: 0,
|
||||
med: 0,
|
||||
nexthop: nexthop.octets().to_vec(),
|
||||
path_attributes: vec![],
|
||||
};
|
||||
|
||||
// Manually drive the manager instead of calling run to not deal with async in tests.
|
||||
assert_eq!(
|
||||
rib_manager.handle_update(RouteUpdate::Announce(announce)),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let addr = Ipv6Addr::from_str("2001:db8::").unwrap();
|
||||
let prefixlen: u32 = 32;
|
||||
|
||||
let lookup_result = rib_manager.lookup_path_exact(addr, prefixlen).unwrap();
|
||||
assert_eq!(lookup_result.paths.len(), 1);
|
||||
let path_result = lookup_result.paths.get("Some peer").unwrap();
|
||||
assert_eq!(path_result.nexthop, nexthop.octets().to_vec());
|
||||
}
|
||||
}
|
||||
309
bgpd/src/server/route_server.rs
Normal file
309
bgpd/src/server/route_server.rs
Normal file
@ -0,0 +1,309 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use crate::server::peer::PeerCommands;
|
||||
use crate::server::rib_manager;
|
||||
use crate::server::rib_manager::RibSnapshot;
|
||||
use crate::server::rib_manager::RouteManagerCommands;
|
||||
use crate::server::route_server::route_server::route_service_server::RouteService;
|
||||
use crate::server::route_server::route_server::AddressFamily;
|
||||
use crate::server::route_server::route_server::DumpPathsRequest;
|
||||
use crate::server::route_server::route_server::DumpPathsResponse;
|
||||
use crate::server::route_server::route_server::Path;
|
||||
use crate::server::route_server::route_server::PathSet;
|
||||
use crate::server::route_server::route_server::Prefix;
|
||||
use crate::server::route_server::route_server::StreamPathsRequest;
|
||||
use log::warn;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::Ipv6Addr;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tonic::Response;
|
||||
use tonic::Status;
|
||||
|
||||
pub mod route_server {
|
||||
tonic::include_proto!("bgpd.grpc");
|
||||
}
|
||||
|
||||
pub struct RouteServer {
|
||||
pub ip4_manager: UnboundedSender<RouteManagerCommands<Ipv4Addr>>,
|
||||
pub ip6_manager: UnboundedSender<RouteManagerCommands<Ipv6Addr>>,
|
||||
|
||||
pub peer_state_machines: HashMap<String, UnboundedSender<PeerCommands>>,
|
||||
}
|
||||
|
||||
impl RouteServer {
|
||||
async fn get_streaming_receiver<A>(
|
||||
&self,
|
||||
manager: UnboundedSender<RouteManagerCommands<A>>,
|
||||
// dump_tx is used to receive the current state before streaming starts.
|
||||
dump_tx: UnboundedSender<(u64, rib_manager::PathSet<A>)>,
|
||||
) -> Result<broadcast::Receiver<(u64, rib_manager::PathSet<A>)>, Status> {
|
||||
let (stream_tx, stream_rx) =
|
||||
oneshot::channel::<broadcast::Receiver<(u64, rib_manager::PathSet<A>)>>();
|
||||
if let Err(e) = manager.send(RouteManagerCommands::StreamRib(dump_tx, stream_tx)) {
|
||||
warn!("Failed to send StreamRib command to route manager: {}", e);
|
||||
return Err(tonic::Status::internal(
|
||||
"failed to communicate with route manager".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
stream_rx
|
||||
.await
|
||||
.map_err(|e| tonic::Status::internal(e.to_string()))
|
||||
}
|
||||
|
||||
/// Converts a rib_manager::PathSet into the proto format PathSet using the
|
||||
/// appropriate address family.
|
||||
fn transform_pathset<A>(
|
||||
mgr_ps: (u64, rib_manager::PathSet<A>),
|
||||
address_family: i32,
|
||||
) -> PathSet {
|
||||
let mut proto_pathset = PathSet {
|
||||
epoch: mgr_ps.0,
|
||||
prefix: Some(Prefix {
|
||||
ip_prefix: mgr_ps.1.nlri.prefix,
|
||||
prefix_len: mgr_ps.1.nlri.prefixlen.into(),
|
||||
address_family,
|
||||
}),
|
||||
paths: vec![],
|
||||
};
|
||||
for (_, path) in mgr_ps.1.paths {
|
||||
let proto_path = Path {
|
||||
as_path: path.as_path,
|
||||
local_pref: path.local_pref,
|
||||
med: path.med,
|
||||
nexthop: path.nexthop,
|
||||
peer_name: path.peer_name,
|
||||
};
|
||||
proto_pathset.paths.push(proto_path);
|
||||
}
|
||||
proto_pathset
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl RouteService for RouteServer {
|
||||
async fn dump_paths(
|
||||
&self,
|
||||
request: tonic::Request<DumpPathsRequest>,
|
||||
) -> Result<Response<DumpPathsResponse>, Status> {
|
||||
let mut response = DumpPathsResponse {
|
||||
epoch: 0,
|
||||
path_sets: vec![],
|
||||
};
|
||||
|
||||
let afi = AddressFamilyIdentifier::try_from(request.get_ref().address_family as u16)
|
||||
.map_err(|e| tonic::Status::internal(e.to_string()))?;
|
||||
match afi {
|
||||
AddressFamilyIdentifier::Ipv4 => {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<RibSnapshot<Ipv4Addr>>();
|
||||
if let Err(e) = self.ip4_manager.send(RouteManagerCommands::DumpRib(tx)) {
|
||||
warn!("Failed to send DumpRib command to route manager: {}", e);
|
||||
return Err(tonic::Status::internal(
|
||||
"failed to communicate with route manager",
|
||||
));
|
||||
}
|
||||
match rx.await {
|
||||
Ok(result) => {
|
||||
response.epoch = result.epoch;
|
||||
for pathset in result.routes {
|
||||
let mut proto_pathset = PathSet {
|
||||
epoch: result.epoch,
|
||||
prefix: Some(Prefix {
|
||||
ip_prefix: pathset.nlri.prefix,
|
||||
prefix_len: pathset.nlri.prefixlen.into(),
|
||||
address_family: AddressFamily::IPv4.into(),
|
||||
}),
|
||||
paths: vec![],
|
||||
};
|
||||
for (_, path) in pathset.paths {
|
||||
let proto_path = Path {
|
||||
as_path: path.as_path,
|
||||
local_pref: path.local_pref,
|
||||
med: path.med,
|
||||
nexthop: path.nexthop,
|
||||
peer_name: path.peer_name,
|
||||
};
|
||||
proto_pathset.paths.push(proto_path);
|
||||
}
|
||||
response.path_sets.push(proto_pathset);
|
||||
}
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to get response from route manager: {}", e);
|
||||
return Err(tonic::Status::internal(
|
||||
"failed to get response from route manager",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
AddressFamilyIdentifier::Ipv6 => {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<RibSnapshot<Ipv6Addr>>();
|
||||
if let Err(e) = self.ip6_manager.send(RouteManagerCommands::DumpRib(tx)) {
|
||||
warn!("Failed to send DumpRib command to route manager: {}", e);
|
||||
return Err(tonic::Status::internal(
|
||||
"failed to communicate with route manager",
|
||||
));
|
||||
}
|
||||
match rx.await {
|
||||
Ok(result) => {
|
||||
response.epoch = result.epoch;
|
||||
for pathset in result.routes {
|
||||
let mut proto_pathset = PathSet {
|
||||
epoch: result.epoch,
|
||||
prefix: Some(Prefix {
|
||||
ip_prefix: pathset.nlri.prefix,
|
||||
prefix_len: pathset.nlri.prefixlen.into(),
|
||||
address_family: AddressFamily::IPv6.into(),
|
||||
}),
|
||||
paths: vec![],
|
||||
};
|
||||
for (_, path) in pathset.paths {
|
||||
let proto_path = Path {
|
||||
as_path: path.as_path,
|
||||
local_pref: path.local_pref,
|
||||
med: path.med,
|
||||
nexthop: path.nexthop,
|
||||
peer_name: path.peer_name,
|
||||
};
|
||||
proto_pathset.paths.push(proto_path);
|
||||
}
|
||||
response.path_sets.push(proto_pathset);
|
||||
}
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to get response from route manager: {}", e);
|
||||
return Err(tonic::Status::internal(
|
||||
"failed to get response from route manager",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type StreamPathsStream = ReceiverStream<Result<PathSet, Status>>;
|
||||
|
||||
async fn stream_paths(
|
||||
&self,
|
||||
request: tonic::Request<StreamPathsRequest>,
|
||||
) -> Result<Response<Self::StreamPathsStream>, Status> {
|
||||
match request.get_ref().address_family {
|
||||
1 => {
|
||||
let (dump_tx, mut dump_rx) = mpsc::unbounded_channel();
|
||||
let mut receiver = self
|
||||
.get_streaming_receiver::<Ipv4Addr>(self.ip4_manager.clone(), dump_tx)
|
||||
.await?;
|
||||
|
||||
let (tx, rx) = mpsc::channel(10_000);
|
||||
// Spawn a task for receving values from the manager and send them to the peer.
|
||||
tokio::spawn(async move {
|
||||
// Consume the dump before moving to the streamed paths.
|
||||
while let Some(next) = dump_rx.recv().await {
|
||||
let pathset =
|
||||
RouteServer::transform_pathset(next, AddressFamily::IPv4.into());
|
||||
if let Err(e) = tx.send(Ok(pathset)).await {
|
||||
warn!("Failed to send path to peer: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let next = receiver.recv().await;
|
||||
if let Err(e) = next {
|
||||
warn!("Failed to get next streaming route from manager: {}", e);
|
||||
let _ = tx
|
||||
.send(Err(tonic::Status::internal(format!(
|
||||
"Failed to get next route from manager: {}",
|
||||
e
|
||||
))))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let route = next.unwrap();
|
||||
if let Err(e) = tx
|
||||
.send(Ok(RouteServer::transform_pathset(
|
||||
route,
|
||||
AddressFamily::IPv4.into(),
|
||||
)))
|
||||
.await
|
||||
{
|
||||
warn!("Failed to send streaming route to peer: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Ok(Response::new(ReceiverStream::new(rx)));
|
||||
}
|
||||
2 => {
|
||||
let (dump_tx, mut dump_rx) = mpsc::unbounded_channel();
|
||||
let mut receiver = self
|
||||
.get_streaming_receiver::<Ipv6Addr>(self.ip6_manager.clone(), dump_tx)
|
||||
.await?;
|
||||
|
||||
let (tx, rx) = mpsc::channel(10_000);
|
||||
// Spawn a task for receving values from the manager and send them to the peer.
|
||||
tokio::spawn(async move {
|
||||
// Consume the dump before moving to the streamed paths.
|
||||
while let Some(next) = dump_rx.recv().await {
|
||||
let pathset =
|
||||
RouteServer::transform_pathset(next, AddressFamily::IPv4.into());
|
||||
if let Err(e) = tx.send(Ok(pathset)).await {
|
||||
warn!("Failed to send path to peer: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
loop {
|
||||
let next = receiver.recv().await;
|
||||
if let Err(e) = next {
|
||||
warn!("Failed to get next streaming route from manager: {}", e);
|
||||
let _ = tx
|
||||
.send(Err(tonic::Status::internal(format!(
|
||||
"Failed to get next route from manager: {}",
|
||||
e
|
||||
))))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let route = next.unwrap();
|
||||
if let Err(e) = tx
|
||||
.send(Ok(RouteServer::transform_pathset(
|
||||
route,
|
||||
AddressFamily::IPv6.into(),
|
||||
)))
|
||||
.await
|
||||
{
|
||||
warn!("Failed to send streaming route to peer: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Ok(Response::new(ReceiverStream::new(rx)));
|
||||
}
|
||||
_ => return Err(tonic::Status::internal("Unknown address family")),
|
||||
};
|
||||
}
|
||||
}
|
||||
164
bgpd/src/streamer_cli/main.rs
Normal file
164
bgpd/src/streamer_cli/main.rs
Normal file
@ -0,0 +1,164 @@
|
||||
// Copyright 2021 Rayhaan Jaufeerally.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bgpd::bgp_packet::constants::AddressFamilyIdentifier;
|
||||
use bgpd::bgp_packet::nlri::NLRI;
|
||||
use bgpd::server::route_server::route_server::route_service_client::RouteServiceClient;
|
||||
use bgpd::server::route_server::route_server::DumpPathsRequest;
|
||||
use bgpd::server::route_server::route_server::PathSet;
|
||||
use bgpd::server::route_server::route_server::StreamPathsRequest;
|
||||
use clap::Parser;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
use tokio::task::JoinHandle;
|
||||
use tonic::transport::Endpoint;
|
||||
use tracing::{info, warn};
|
||||
|
||||
extern crate clap;
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[clap(
|
||||
author = "Rayhaan Jaufeerally <rayhaan@rayhaan.ch>",
|
||||
version = "0.1",
|
||||
about = "A program to install routes from BGP into the Linux control plane"
|
||||
)]
|
||||
struct Cli {
|
||||
server_address: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), String> {
|
||||
let subscriber = tracing_subscriber::fmt();
|
||||
|
||||
match subscriber.try_init() {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to initialize logger: {:?}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
info!("Starting client");
|
||||
let grpc_endpoint = cli.server_address;
|
||||
let endpoint = Endpoint::from_shared(grpc_endpoint)
|
||||
.map_err(|e| e.to_string())?
|
||||
.keep_alive_timeout(Duration::from_secs(10));
|
||||
let mut client = RouteServiceClient::connect(endpoint)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
info!("Connected");
|
||||
|
||||
// 1. First subscribe to the route feed and put these into an unbounded channel.
|
||||
let (stream_tx, mut stream_rx) = tokio::sync::mpsc::unbounded_channel::<PathSet>();
|
||||
let request = StreamPathsRequest {
|
||||
address_family: 2_i32,
|
||||
};
|
||||
|
||||
let mut client_copy = client.clone();
|
||||
let _recv_handle: JoinHandle<Result<(), String>> = tokio::spawn(async move {
|
||||
let mut rpc_stream = client_copy
|
||||
.stream_paths(request)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.into_inner();
|
||||
while let Some(route) = rpc_stream.message().await.map_err(|e| e.to_string())? {
|
||||
stream_tx.send(route).map_err(|e| e.to_string())?;
|
||||
}
|
||||
Err("Stream closed".to_string())
|
||||
});
|
||||
|
||||
// 2. Dump the whole RIB
|
||||
let dump_request = DumpPathsRequest {
|
||||
address_family: 2_i32,
|
||||
};
|
||||
let dump_response = client.dump_paths(dump_request).await.unwrap().into_inner();
|
||||
let dump_epoch = dump_response.epoch;
|
||||
|
||||
info!("Dump epoch was: {}", dump_epoch);
|
||||
|
||||
let overrun_slot: Option<PathSet>;
|
||||
loop {
|
||||
let item = stream_rx.recv().await;
|
||||
match &item {
|
||||
Some(pathset) => {
|
||||
if pathset.epoch >= dump_epoch {
|
||||
overrun_slot = Some(pathset.clone());
|
||||
break;
|
||||
} else {
|
||||
info!("Skipping already-dumped epoch: {}", pathset.epoch);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err("Stream unexpectedly closed".to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replay all the pathsets from dump_response
|
||||
for pathset in dump_response.path_sets {
|
||||
info!("Got pathset: {:?}", pathset);
|
||||
// Parse an NLRI from the pathset
|
||||
if let Some(prefix) = &pathset.prefix {
|
||||
let nlri = NLRI::from_bytes(
|
||||
AddressFamilyIdentifier::Ipv6,
|
||||
prefix.ip_prefix.clone(),
|
||||
prefix.prefix_len as u8,
|
||||
)
|
||||
.unwrap();
|
||||
info!("Parsed NLRI: {}", nlri.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Replay the overrun slot
|
||||
if let Some(pathset) = overrun_slot {
|
||||
if let Some(prefix) = &pathset.prefix {
|
||||
let nlri = NLRI::from_bytes(
|
||||
AddressFamilyIdentifier::Ipv6,
|
||||
prefix.ip_prefix.clone(),
|
||||
prefix.prefix_len as u8,
|
||||
)
|
||||
.unwrap();
|
||||
info!("Parsed NLRI: {}", nlri.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let item = stream_rx.recv().await;
|
||||
|
||||
match &item {
|
||||
Some(pathset) => {
|
||||
info!("Got pathset: {:?}", pathset);
|
||||
// Parse an NLRI from the pathset
|
||||
if let Some(prefix) = &pathset.prefix {
|
||||
let nlri = NLRI::from_bytes(
|
||||
AddressFamilyIdentifier::Ipv6,
|
||||
prefix.ip_prefix.clone(),
|
||||
prefix.prefix_len as u8,
|
||||
)
|
||||
.unwrap();
|
||||
info!("Parsed NLRI: {}", nlri.to_string());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
warn!("stream_rx closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("Program exited unexpectedly.".to_owned())
|
||||
}
|
||||
Reference in New Issue
Block a user