route_verification 0.7.1

Parse RPSL in the IRR to verify observed BGP routes
Documentation
use super::*;

/// Generate sources for ASes with unrecorded AutNum.
/// Copy the whole function after running code from [`parse_bgp_lines`],
/// and run as
/// ```no_run
/// transit_as_rules(&query, &db)
/// ```
fn transit_as_rules(query: &QueryIr, db: &AsRelDb) {
    let mut transit_ases: Vec<u32> = db
        .source2dest
        .iter()
        .filter_map(|((from, to), relationship)| match relationship {
            Relationship::P2C => Some(*from),
            Relationship::P2P => None,
            Relationship::C2P => Some(*to),
        })
        .collect();
    transit_ases.sort_unstable();
    transit_ases.dedup();

    #[derive(Default)]
    struct Appear {
        as_num: u32,
        overall: Vec<u32>,
        peering: Vec<u32>,
        filter: Vec<u32>,
    }

    impl Appear {
        fn record_as_expr(&mut self, as_expr: &AsExpr, query: &QueryIr) {
            match as_expr {
                AsExpr::Single(AsName::Num(num)) => {
                    self.overall.push(*num);
                    self.peering.push(*num);
                }
                AsExpr::Single(AsName::Set(name)) => {
                    if let Some(as_set) = query.as_sets.get(name) {
                        self.overall.extend(&as_set.members);
                        self.peering.extend(&as_set.members);
                    }
                }
                AsExpr::Single(AsName::Any | AsName::Invalid(_)) => {}
                AsExpr::PeeringSet(name) => {
                    if let Some(peering_set) = query.peering_sets.get(name) {
                        for peering in &peering_set.peerings {
                            self.record_as_expr(&peering.remote_as, query);
                        }
                    }
                }
                AsExpr::And { left, right }
                | AsExpr::Or { left, right }
                | AsExpr::Except { left, right } => {
                    self.record_as_expr(left, query);
                    self.record_as_expr(right, query);
                }
                AsExpr::Group(as_expr) => self.record_as_expr(as_expr, query),
            }
        }
        fn record_filter(&mut self, filter: &Filter, query: &QueryIr) {
            match filter {
                Filter::FilterSet(name) => {
                    if let Some(filter_set) = query.filter_sets.get(name) {
                        for filter in &filter_set.filters {
                            self.record_filter(filter, query);
                        }
                    }
                }
                Filter::AsNum(num, _) => {
                    self.overall.push(*num);
                    self.filter.push(*num);
                }
                Filter::AsSet(name, _) => {
                    if let Some(as_set) = query.as_sets.get(name) {
                        self.overall.extend(&as_set.members);
                        self.filter.extend(&as_set.members)
                    }
                }
                Filter::And { left, right } | Filter::Or { left, right } => {
                    self.record_filter(left, query);
                    self.record_filter(right, query);
                }
                Filter::Not(filter) | Filter::Group(filter) => self.record_filter(filter, query),
                Filter::Any
                | Filter::AddrPrefixSet(_)
                | Filter::RouteSet(_, _)
                | Filter::AsPathRE(_)
                | Filter::PeerAS
                | Filter::Community(_)
                | Filter::Unknown(_) => {}
            }
        }
        fn record_entry(&mut self, entry: &Entry, query: &QueryIr) {
            for peering_action in &entry.mp_peerings {
                self.record_as_expr(&peering_action.mp_peering.remote_as, query);
            }
            self.record_filter(&entry.mp_filter, query);
        }
        fn clean_up(&mut self) {
            self.overall.sort_unstable();
            self.overall.dedup();
            self.peering.sort_unstable();
            self.peering.dedup();
            self.filter.sort_unstable();
            self.filter.dedup();
        }
    }

    let mut file = BufWriter::new(File::create("transit_as_stats2.csv").unwrap());
    file.write_all(b"as_num,import_provider,import_peer,import_customer,import_other,import_both_provider,import_both_peer,import_both_customer,import_both_other,import_peering_provider,import_filter_provider,import_peering_peer,import_filter_peer,import_peering_customer,import_filter_customer,import_peering_other,import_filter_other,export_provider,export_peer,export_customer,export_other,export_self,export_peering_provider,export_filter_provider,export_peering_peer,export_filter_peer,export_peering_customer,export_filter_customer,export_peering_other,export_filter_other,export_peering_self,export_filter_self\n");

    macro_rules! write_comma_num {
        ($num:expr) => {
            file.write_all(b",").unwrap();
            file.write_all($num.to_string().as_bytes()).unwrap();
        };
    }

    for &as_num in &transit_ases {
        file.write_all(as_num.to_string().as_bytes()).unwrap();
        let Some(aut_num) = query.aut_nums.get(&as_num) else {
            file.write_all(b",-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n");
            continue;
        };

        {
            let mut import_provider = 0;
            let mut import_peer = 0;
            let mut import_customer = 0;
            let mut import_other = 0;
            let mut import_both_provider = 0;
            let mut import_both_peer = 0;
            let mut import_both_customer = 0;
            let mut import_both_other = 0;
            let mut import_peering_provider = 0;
            let mut import_filter_provider = 0;
            let mut import_peering_peer = 0;
            let mut import_filter_peer = 0;
            let mut import_peering_customer = 0;
            let mut import_filter_customer = 0;
            let mut import_peering_other = 0;
            let mut import_filter_other = 0;

            let mut appear = Appear {
                as_num,
                ..Default::default()
            };
            for entry in aut_num.imports.entries_iter() {
                appear.record_entry(entry, query);
            }
            appear.clean_up();

            for import_as in appear.overall {
                match db.get(as_num, import_as) {
                    Some(Relationship::C2P) => import_provider += 1,
                    Some(Relationship::P2P) => import_peer += 1,
                    Some(Relationship::P2C) => import_customer += 1,
                    None => import_other += 1,
                }
            }

            for &import_as in &appear.peering {
                if appear.filter.contains(&import_as) {
                    match db.get(as_num, import_as) {
                        Some(Relationship::C2P) => import_both_provider += 1,
                        Some(Relationship::P2P) => import_both_peer += 1,
                        Some(Relationship::P2C) => import_both_customer += 1,
                        None => import_peering_other += 1,
                    }
                }
            }

            for import_as in appear.peering {
                match db.get(as_num, import_as) {
                    Some(Relationship::C2P) => import_peering_provider += 1,
                    Some(Relationship::P2P) => import_peering_peer += 1,
                    Some(Relationship::P2C) => import_peering_customer += 1,
                    None => import_peering_other += 1,
                }
            }

            for import_as in appear.filter {
                match db.get(as_num, import_as) {
                    Some(Relationship::C2P) => import_filter_provider += 1,
                    Some(Relationship::P2P) => import_filter_peer += 1,
                    Some(Relationship::P2C) => import_filter_customer += 1,
                    None => import_filter_other += 1,
                }
            }

            write_comma_num!(import_provider);
            write_comma_num!(import_peer);
            write_comma_num!(import_customer);
            write_comma_num!(import_other);
            write_comma_num!(import_both_provider);
            write_comma_num!(import_both_peer);
            write_comma_num!(import_both_customer);
            write_comma_num!(import_both_other);
            write_comma_num!(import_peering_provider);
            write_comma_num!(import_filter_provider);
            write_comma_num!(import_peering_peer);
            write_comma_num!(import_filter_peer);
            write_comma_num!(import_peering_customer);
            write_comma_num!(import_filter_customer);
            write_comma_num!(import_peering_other);
            write_comma_num!(import_filter_other);
        }

        {
            let mut export_provider = 0;
            let mut export_peer = 0;
            let mut export_customer = 0;
            let mut export_other = 0;
            let mut export_self = 0;
            let mut export_peering_provider = 0;
            let mut export_filter_provider = 0;
            let mut export_peering_peer = 0;
            let mut export_filter_peer = 0;
            let mut export_peering_customer = 0;
            let mut export_filter_customer = 0;
            let mut export_peering_other = 0;
            let mut export_filter_other = 0;
            let mut export_peering_self = 0;
            let mut export_filter_self = 0;

            let mut appear = Appear {
                as_num,
                ..Default::default()
            };
            for entry in aut_num.exports.entries_iter() {
                appear.record_entry(entry, query);
            }
            appear.clean_up();

            for export_as in appear.overall {
                if as_num == export_as {
                    export_self += 1;
                }
                match db.get(as_num, export_as) {
                    Some(Relationship::C2P) => export_provider += 1,
                    Some(Relationship::P2P) => export_peer += 1,
                    Some(Relationship::P2C) => export_customer += 1,
                    None => export_other += 1,
                }
            }

            for export_as in appear.peering {
                if as_num == export_as {
                    export_peering_self += 1;
                }
                match db.get(as_num, export_as) {
                    Some(Relationship::C2P) => export_peering_provider += 1,
                    Some(Relationship::P2P) => export_peering_peer += 1,
                    Some(Relationship::P2C) => export_peering_customer += 1,
                    None => export_peering_other += 1,
                }
            }

            for export_as in appear.filter {
                if as_num == export_as {
                    export_filter_self += 1;
                }
                match db.get(as_num, export_as) {
                    Some(Relationship::C2P) => export_filter_provider += 1,
                    Some(Relationship::P2P) => export_filter_peer += 1,
                    Some(Relationship::P2C) => export_filter_customer += 1,
                    None => export_filter_other += 1,
                }
            }

            write_comma_num!(export_provider);
            write_comma_num!(export_peer);
            write_comma_num!(export_customer);
            write_comma_num!(export_other);
            write_comma_num!(export_self);
            write_comma_num!(export_peering_provider);
            write_comma_num!(export_filter_provider);
            write_comma_num!(export_peering_peer);
            write_comma_num!(export_filter_peer);
            write_comma_num!(export_peering_customer);
            write_comma_num!(export_filter_customer);
            write_comma_num!(export_peering_other);
            write_comma_num!(export_filter_other);
            write_comma_num!(export_peering_self);
            write_comma_num!(export_filter_self);
        }
        file.write_all(b"\n").unwrap();
    }

    file.flush().unwrap();
    drop(file);
}