1#![allow(dead_code)]
2use anyhow::Result;
3use bgpkit_parser::BgpkitParser;
4use ipnet::{IpNet, Ipv4Net, Ipv6Net};
5use itertools::Itertools;
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8use std::net::IpAddr;
9
10#[derive(Debug, Clone, Serialize)]
11pub struct RibPeerInfo {
12 pub project: String,
13 pub collector: String,
14 pub rib_dump_url: String,
15 pub peers: HashMap<IpAddr, PeerInfo>,
16}
17
18#[derive(Debug, Clone, Serialize)]
19pub struct PeerInfo {
20 pub ip: IpAddr,
21 pub asn: u32,
22 pub num_v4_pfxs: usize,
23 pub num_v6_pfxs: usize,
24 pub num_connected_asns: usize,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Prefix2As {
29 pub project: String,
30 pub collector: String,
31 pub rib_dump_url: String,
32 pub pfx2as: Vec<Prefix2AsCount>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct Prefix2AsCount {
38 pub prefix: String,
39 pub asn: u32,
40 pub count: usize,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct As2Rel {
45 pub project: String,
46 pub collector: String,
47 pub rib_dump_url: String,
48 pub as2rel: Vec<As2RelCount>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct As2RelCount {
54 pub asn1: u32,
55 pub asn2: u32,
56 pub rel: u8,
58 pub paths_count: usize,
60 pub peers_count: usize,
62}
63
64const TIER1: [u32; 17] = [
65 6762, 12956, 2914, 3356, 6453, 1239, 701, 6461, 3257, 1299, 3491, 7018, 3320, 5511, 6830, 174,
66 6939,
67];
68
69const TIER1_V4: [u32; 17] = [
70 6762, 12956, 2914, 3356, 6453, 1239, 701, 6461, 3257, 1299, 3491, 7018, 3320, 5511, 6830, 174,
71 0,
72];
73
74const TIER1_V6: [u32; 17] = [
75 6762, 12956, 2914, 3356, 6453, 1239, 701, 6461, 3257, 1299, 3491, 7018, 3320, 5511, 6830, 174,
76 6939,
77];
78
79fn dedup_path(path: Vec<u32>) -> Vec<u32> {
80 if path.len() <= 1 {
81 return path;
82 }
83
84 let mut new_path = vec![path[0]];
85
86 for (asn1, asn2) in path.into_iter().tuple_windows::<(u32, u32)>() {
87 if asn1 != asn2 {
88 new_path.push(asn2)
89 }
90 }
91 new_path
92}
93
94fn update_as2rel_map(
95 peer_ip: IpAddr,
96 tier1: &[u32],
97 data_map: &mut HashMap<(u32, u32, u8), (usize, HashSet<IpAddr>)>,
98 original_as_path: &[u32],
100) {
101 let mut as_path = original_as_path.to_vec();
102
103 for (asn1, asn2) in as_path.iter().tuple_windows::<(&u32, &u32)>() {
105 let (msg_count, peers) = data_map
106 .entry((*asn1, *asn2, 0))
107 .or_insert((0, HashSet::new()));
108 *msg_count += 1;
109 peers.insert(peer_ip);
110 }
111
112 as_path.reverse();
114 let contains_tier1 = as_path.iter().any(|x| tier1.contains(x));
115 if contains_tier1 {
116 let mut first_tier1: usize = usize::MAX;
117 for (i, asn) in as_path.iter().enumerate() {
118 if tier1.contains(asn) && first_tier1 == usize::MAX {
119 first_tier1 = i;
120 break;
121 }
122 }
123
124 if first_tier1 < as_path.len() - 1 {
126 for i in 0..first_tier1 {
127 let (asn1, asn2) = (as_path.get(i).unwrap(), as_path.get(i + 1).unwrap());
128 let (msg_count, peers) = data_map
129 .entry((*asn2, *asn1, 1))
130 .or_insert((0, HashSet::new()));
131 *msg_count += 1;
132 peers.insert(peer_ip);
133 }
134 }
135 }
136}
137
138fn compile_as2rel_count(
139 data_map: &HashMap<(u32, u32, u8), (usize, HashSet<IpAddr>)>,
140) -> Vec<As2RelCount> {
141 data_map
142 .iter()
143 .map(|((asn1, asn2, rel), (msg_count, peers))| As2RelCount {
144 asn1: *asn1,
145 asn2: *asn2,
146 rel: *rel,
147 paths_count: *msg_count,
148 peers_count: peers.len(),
149 })
150 .collect()
151}
152
153pub fn parse_rib_file(
164 file_url: &str,
165 project: &str,
166 collector: &str,
167) -> Result<(RibPeerInfo, Prefix2As, (As2Rel, As2Rel, As2Rel))> {
168 let mut peer_asn_map: HashMap<IpAddr, u32> = HashMap::new();
170 let mut peer_connection: HashMap<IpAddr, HashSet<u32>> = HashMap::new();
171 let mut peer_v4_pfxs_map: HashMap<IpAddr, HashSet<Ipv4Net>> = HashMap::new();
172 let mut peer_v6_pfxs_map: HashMap<IpAddr, HashSet<Ipv6Net>> = HashMap::new();
173
174 let mut pfx2as_map: HashMap<(String, u32), usize> = HashMap::new();
176
177 let mut as2rel_map: HashMap<(u32, u32, u8), (usize, HashSet<IpAddr>)> = HashMap::new();
179 let mut as2rel_v4_map: HashMap<(u32, u32, u8), (usize, HashSet<IpAddr>)> = HashMap::new();
180 let mut as2rel_v6_map: HashMap<(u32, u32, u8), (usize, HashSet<IpAddr>)> = HashMap::new();
181
182 for elem in BgpkitParser::new(file_url)? {
183 peer_asn_map
184 .entry(elem.peer_ip)
185 .or_insert(elem.peer_asn.to_u32());
186 if let Some(as_path) = elem.as_path.clone() {
187 if let Some(u32_path) = as_path.to_u32_vec_opt(true) {
188 match u32_path.get(1) {
190 None => {}
191 Some(asn) => {
192 peer_connection
193 .entry(elem.peer_ip)
194 .or_default()
195 .insert(*asn);
196 }
197 };
198
199 match u32_path.last() {
201 None => {}
202 Some(asn) => {
203 let prefix = elem.prefix.to_string();
204 let count = pfx2as_map.entry((prefix, *asn)).or_insert(0);
205 *count += 1;
206 }
207 }
208
209 for is_global in [true, false] {
211 let (tier1, data_map) = match is_global {
213 true => (TIER1.to_vec(), &mut as2rel_map),
214 false => match elem.prefix.prefix {
215 IpNet::V4(_) => (TIER1_V4.to_vec(), &mut as2rel_v4_map),
216 IpNet::V6(_) => (TIER1_V6.to_vec(), &mut as2rel_v6_map),
217 },
218 };
219 update_as2rel_map(elem.peer_ip, &tier1, data_map, &u32_path);
221 }
222 }
223 }
224
225 match elem.prefix.prefix {
226 IpNet::V4(net) => {
227 peer_v4_pfxs_map
228 .entry(elem.peer_ip)
229 .or_default()
230 .insert(net);
231 }
232 IpNet::V6(net) => {
233 peer_v6_pfxs_map
234 .entry(elem.peer_ip)
235 .or_default()
236 .insert(net);
237 }
238 }
239 drop(elem);
240 }
241
242 let mut peer_info_map: HashMap<IpAddr, PeerInfo> = HashMap::new();
243 for (ip, asn) in peer_asn_map {
244 let num_v4_pfxs = peer_v4_pfxs_map.entry(ip).or_default().len();
245 let num_v6_pfxs = peer_v6_pfxs_map.entry(ip).or_default().len();
246 let num_connected_asn = peer_connection.entry(ip).or_default().len();
247 let ip_clone = ip;
248 let asn_clone = asn;
249 peer_info_map.insert(
250 ip_clone,
251 PeerInfo {
252 ip: ip_clone,
253 asn: asn_clone,
254 num_v4_pfxs,
255 num_v6_pfxs,
256 num_connected_asns: num_connected_asn,
257 },
258 );
259 }
260
261 let pfx2as = pfx2as_map
262 .into_iter()
263 .map(|((prefix, asn), count)| Prefix2AsCount { prefix, asn, count })
264 .collect();
265
266 let as2rel_global = compile_as2rel_count(&as2rel_map);
267 let as2rel_v4 = compile_as2rel_count(&as2rel_v4_map);
268 let as2rel_v6 = compile_as2rel_count(&as2rel_v6_map);
269
270 Ok((
271 RibPeerInfo {
272 project: project.to_string(),
273 collector: collector.to_string(),
274 rib_dump_url: file_url.to_string(),
275 peers: peer_info_map,
276 },
277 Prefix2As {
278 project: project.to_string(),
279 collector: collector.to_string(),
280 rib_dump_url: file_url.to_string(),
281 pfx2as,
282 },
283 (
284 As2Rel {
285 project: project.to_string(),
286 collector: collector.to_string(),
287 rib_dump_url: file_url.to_string(),
288 as2rel: as2rel_global,
289 },
290 As2Rel {
291 project: project.to_string(),
292 collector: collector.to_string(),
293 rib_dump_url: file_url.to_string(),
294 as2rel: as2rel_v4,
295 },
296 As2Rel {
297 project: project.to_string(),
298 collector: collector.to_string(),
299 rib_dump_url: file_url.to_string(),
300 as2rel: as2rel_v6,
301 },
302 ),
303 ))
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use serde_json::json;
310 use std::fs::File;
311 use tracing::{info, Level};
312
313 #[test]
314 fn test_read_rib() {
315 tracing_subscriber::fmt()
316 .with_max_level(Level::INFO)
318 .init();
319 info!("start");
320 let (peer_stats, pfx2as, as2rel) = parse_rib_file("http://archive.routeviews.org/route-views.soxrs/bgpdata/2022.08/RIBS/rib.20220808.1400.bz2",
321 "route-views", "route-views.sg").unwrap();
322 serde_json::to_writer_pretty(
323 &File::create("peer_info_example.json").unwrap(),
324 &json!(peer_stats),
325 )
326 .unwrap();
327 serde_json::to_writer_pretty(
328 &File::create("pfx2as_example.json").unwrap(),
329 &json!(pfx2as),
330 )
331 .unwrap();
332 serde_json::to_writer_pretty(
333 &File::create("as2rel_example.json").unwrap(),
334 &json!(as2rel),
335 )
336 .unwrap();
337 info!("finished");
338 }
339
340 #[test]
341 fn test_dedup() {
342 let empty: Vec<u32> = vec![];
343 assert_eq!(dedup_path(empty.clone()), empty);
344 assert_eq!(dedup_path(vec![1]), vec![1]);
345 assert_eq!(dedup_path(vec![0, 1, 2, 3, 3, 3, 3]), vec![0, 1, 2, 3]);
346 assert_eq!(
347 dedup_path(vec![0, 1, 2, 3, 3, 3, 3, 4]),
348 vec![0, 1, 2, 3, 4]
349 );
350 assert_eq!(
351 dedup_path(vec![0, 1, 2, 3, 3, 3, 3, 4, 4, 4, 4]),
352 vec![0, 1, 2, 3, 4]
353 );
354 }
355}