use bgpkit_parser::models::{
Asn, AsnLength, BgpMessage, BgpOpenMessage, CapabilityValue, OptParam, ParamValue,
};
use std::error::Error;
use std::fs::File;
use std::io::Write;
use std::net::Ipv4Addr;
use std::path::Path;
const SOURCE_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
const DESTINATION_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
const SOURCE_IP: [u8; 4] = [192, 0, 2, 10];
const DESTINATION_IP: [u8; 4] = [198, 51, 100, 20];
const SOURCE_PORT: u16 = 50_000;
const BGP_PORT: u16 = 179;
fn capability_param(capability: &[u8]) -> OptParam {
OptParam {
param_type: 2,
param_value: ParamValue::Raw(capability.to_vec()),
}
}
fn open_with_customer_role() -> BgpOpenMessage {
BgpOpenMessage {
version: 4,
asn: Asn::new_16bit(65007),
hold_time: 180,
bgp_identifier: Ipv4Addr::new(192, 0, 2, 1),
extended_length: false,
opt_params: vec![
capability_param(&[1, 4, 0, 1, 0, 1]), capability_param(&[1, 4, 0, 2, 0, 1]), capability_param(&[65, 4, 0, 0, 253, 239]), capability_param(&[64, 6, 0, 0, 0, 1, 1, 0]), capability_param(&[69, 8, 0, 1, 1, 3, 0, 2, 1, 3]), capability_param(&[9, 1, 3]), ],
}
}
fn internet_checksum(bytes: &[u8]) -> u16 {
let mut sum = 0u32;
for chunk in bytes.chunks(2) {
let word = match chunk {
[high, low] => u16::from_be_bytes([*high, *low]),
[high] => u16::from_be_bytes([*high, 0]),
_ => unreachable!("chunks(2) only returns chunks of one or two octets"),
};
sum += u32::from(word);
}
while sum > u32::from(u16::MAX) {
sum = (sum & u32::from(u16::MAX)) + (sum >> 16);
}
!(sum as u16)
}
fn tcp_checksum(tcp_segment: &[u8]) -> u16 {
let tcp_length =
u16::try_from(tcp_segment.len()).expect("TCP segment length is bounded by IPv4");
let mut pseudo_header = Vec::with_capacity(12 + tcp_segment.len());
pseudo_header.extend_from_slice(&SOURCE_IP);
pseudo_header.extend_from_slice(&DESTINATION_IP);
pseudo_header.extend_from_slice(&[0, 6]);
pseudo_header.extend_from_slice(&tcp_length.to_be_bytes());
pseudo_header.extend_from_slice(tcp_segment);
internet_checksum(&pseudo_header)
}
fn ethernet_ipv4_tcp_frame(bgp_open: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
let tcp_length = 20usize
.checked_add(bgp_open.len())
.ok_or("TCP segment length overflow")?;
let ip_total_length = 20usize
.checked_add(tcp_length)
.ok_or("IPv4 packet length overflow")?;
let ip_total_length = u16::try_from(ip_total_length)?;
let mut frame = Vec::with_capacity(14 + usize::from(ip_total_length));
frame.extend_from_slice(&DESTINATION_MAC);
frame.extend_from_slice(&SOURCE_MAC);
frame.extend_from_slice(&0x0800u16.to_be_bytes());
let ip_start = frame.len();
frame.extend_from_slice(&[
0x45,
0x00, 0x00,
0x00, 0x00,
0x01, 0x40,
0x00, 64,
6, 0x00,
0x00, SOURCE_IP[0],
SOURCE_IP[1],
SOURCE_IP[2],
SOURCE_IP[3],
DESTINATION_IP[0],
DESTINATION_IP[1],
DESTINATION_IP[2],
DESTINATION_IP[3],
]);
frame[ip_start + 2..ip_start + 4].copy_from_slice(&ip_total_length.to_be_bytes());
let ip_checksum = internet_checksum(&frame[ip_start..ip_start + 20]);
frame[ip_start + 10..ip_start + 12].copy_from_slice(&ip_checksum.to_be_bytes());
let tcp_start = frame.len();
frame.extend_from_slice(&SOURCE_PORT.to_be_bytes());
frame.extend_from_slice(&BGP_PORT.to_be_bytes());
frame.extend_from_slice(&1u32.to_be_bytes()); frame.extend_from_slice(&0u32.to_be_bytes()); frame.extend_from_slice(&0x5008u16.to_be_bytes()); frame.extend_from_slice(&u16::MAX.to_be_bytes()); frame.extend_from_slice(&[0x00, 0x00]); frame.extend_from_slice(&[0x00, 0x00]); frame.extend_from_slice(bgp_open);
let checksum = tcp_checksum(&frame[tcp_start..]);
frame[tcp_start + 16..tcp_start + 18].copy_from_slice(&checksum.to_be_bytes());
Ok(frame)
}
fn classic_pcap(frame: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
let captured_length = u32::try_from(frame.len())?;
let mut pcap = Vec::with_capacity(24 + 16 + frame.len());
pcap.extend_from_slice(&0xa1b2c3d4u32.to_le_bytes()); pcap.extend_from_slice(&2u16.to_le_bytes());
pcap.extend_from_slice(&4u16.to_le_bytes());
pcap.extend_from_slice(&0i32.to_le_bytes()); pcap.extend_from_slice(&0u32.to_le_bytes()); pcap.extend_from_slice(&65_535u32.to_le_bytes()); pcap.extend_from_slice(&1u32.to_le_bytes()); pcap.extend_from_slice(&0u32.to_le_bytes()); pcap.extend_from_slice(&0u32.to_le_bytes()); pcap.extend_from_slice(&captured_length.to_le_bytes());
pcap.extend_from_slice(&captured_length.to_le_bytes());
pcap.extend_from_slice(frame);
Ok(pcap)
}
fn write_pcap(path: &Path, pcap: &[u8]) -> Result<(), Box<dyn Error>> {
let mut output = File::create(path)?;
output.write_all(pcap)?;
output.flush()?;
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
let output = std::env::args()
.nth(1)
.unwrap_or_else(|| "bgp-open-role.pcap".to_owned());
let open = open_with_customer_role();
let wire = BgpMessage::Open(open).encode(AsnLength::Bits32)?;
let mut parser_input = wire.clone();
let parsed = bgpkit_parser::parser::bgp::parse_bgp_message(
&mut parser_input,
false,
&AsnLength::Bits32,
)?;
let BgpMessage::Open(parsed_open) = parsed else {
return Err("encoded BGP OPEN parsed as another message type".into());
};
let has_role_capability = matches!(
parsed_open.opt_params.last().map(|parameter| ¶meter.param_value),
Some(ParamValue::Capacities(capabilities))
if matches!(capabilities.first().map(|capability| &capability.value),
Some(CapabilityValue::BgpRole(_)))
);
if !has_role_capability || wire[75..] != [2, 3, 9, 1, 3] {
return Err("BGP OPEN did not round-trip with RFC 9234 Customer role".into());
}
let frame = ethernet_ipv4_tcp_frame(&wire)?;
let pcap = classic_pcap(&frame)?;
write_pcap(Path::new(&output), &pcap)?;
println!(
"wrote {output}: {} bytes, BGP OPEN {} bytes",
pcap.len(),
wire.len()
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creates_checksum_valid_ethernet_ipv4_tcp_pcap_with_customer_role(
) -> Result<(), Box<dyn Error>> {
let wire = BgpMessage::Open(open_with_customer_role()).encode(AsnLength::Bits32)?;
assert_eq!(wire.len(), 80);
assert_eq!(&wire[16..19], &[0, 80, 1]);
assert_eq!(&wire[75..], &[2, 3, 9, 1, 3]);
let frame = ethernet_ipv4_tcp_frame(&wire)?;
assert_eq!(frame.len(), 134);
assert_eq!(&frame[12..14], &[0x08, 0x00]);
assert_eq!(internet_checksum(&frame[14..34]), 0);
assert_eq!(tcp_checksum(&frame[34..]), 0);
assert_eq!(&frame[54..], wire.as_ref());
let pcap = classic_pcap(&frame)?;
assert_eq!(pcap.len(), 174);
assert_eq!(&pcap[0..4], &[0xd4, 0xc3, 0xb2, 0xa1]);
assert_eq!(u32::from_le_bytes(pcap[32..36].try_into()?), 134);
assert_eq!(&pcap[40..], frame.as_slice());
Ok(())
}
}