Sign in

    mrtkit

    BGP MRT parser and route change analyzer in MoonBit

    bgp
    mrt
    network
    parser
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    15 hours ago
    Downloads
    4

    #MRTKit

    CI Mooncakes License

    用 MoonBit 解析 BGP MRT 档案,查询路由并分析变化。提供无第三方依赖的协议核心库和流式 CLI,适用于离线路由研究、故障复盘与教学。

    #快速开始

    需要 MoonBit 和 Node.js 22+;读取 bz2 另需 Python 3。无需安装 npm 依赖。

    git clone https://github.com/prowk/mrtkit.git cd mrtkit npm run build node cli/mrtkit.mjs parse fixtures/demo.mrt node cli/mrtkit.mjs stats --threshold 3 fixtures/demo.mrt node cli/mrtkit.mjs timeline --prefix 203.0.113.0/24 fixtures/demo.mrt

    MoonBit 项目可用 moon add prowk/mrtkit@0.1.0 引入核心库。完整安装、样本解读、筛选、导出和真实档案回放见入门教程。

    #功能

    • TABLE_DUMP_V2、BGP4MP/BGP4MP_ET 的 IPv4/IPv6 单播,含四字节 ASN、ADD-PATH 和会话状态。
    • 常用路径、聚合与社区属性;保留原始编码并解释 AS4 有效值,检查长度、标志和宣告必需属性。
    • 前缀、ASN、时间筛选,JSON Lines / CSV 导出,带文件、记录号和解压后偏移。
    • 初始 RIB 与连续更新回放、结构化变化、快照差异、前缀时间线和变化聚合。

    这是离线分析工具。旧 TABLE_DUMP、RIB_GENERIC、多播、VPN/EVPN 和在线 BGP 会话不在范围内;时间线描述采集器观测,不推断业务中断。规则、资源限制和边界见兼容清单。

    #文档与验证

    两个采集器的 18 个完整档案、约 1,966 万条路由事件已通过独立解析器对照。字段、来源、哈希与复现方法见验证记录。

    npm test moon test --target wasm --deny-warn moon test --target wasm-gc --deny-warn moon info moon fmt --check

    发布记录见 CHANGELOG。许可证为 Apache-2.0。

    ParseError

    pub suberror ParseError {
    Invalid(Int, String)
    }

    解析错误包含字节位置,便于定位损坏记录。

    Aggregation

    pub struct Aggregation {
    // private fields
    }

    Aggregation::new

    Aggregation::observe

    fn Aggregation::observe(self : Aggregation, change : Change) -> Unit raise ParseError

    同一输入事件的多个变化类型只占一次前缀事件计数。

    Aggregation::report

    fn Aggregation::report(self : Aggregation, top? : Int) -> AggregationReport raise ParseError

    AggregationReport

    pub struct AggregationReport {
    schema_version : String
    scope : String
    deduplication : String
    ordering : String
    changed_prefixes : Int
    affected_peers : Int
    change_notices : Int64
    change_types : Map[String, Int64]
    top_prefixes : Array[PrefixRanking]
    } derive(ToJson)

    AggregationReport::to_json

    Aggregator

    pub struct Aggregator {
    asn : UInt
    address : String
    } derive(Eq, ToJson)

    Aggregator::equal

    fn Aggregator::equal(Aggregator, Aggregator) -> Bool

    Aggregator::not_equal

    fn Aggregator::not_equal(x : Aggregator, y : Aggregator) -> Bool

    Aggregator::to_json

    fn Aggregator::to_json(Aggregator) -> Json

    AggregatorDiff

    pub struct AggregatorDiff {
    before : Aggregator?
    after : Aggregator?
    }

    AggregatorDiff::to_json

    fn AggregatorDiff::to_json(self : AggregatorDiff) -> Json

    Analyzer

    pub struct Analyzer {
    // private fields
    }

    Analyzer::new

    fn Analyzer::new(window_seconds? : Int, threshold? : Int, capacity? : Int, from_time? : UInt, to_time? : UInt, from_micros? : Int, to_micros? : Int) -> Analyzer raise ParseError

    Analyzer::observe

    fn Analyzer::observe(self : Analyzer, event : RouteEvent) -> Array[Change] raise ParseError

    每个 peer、方向和前缀单独跟踪,固定窗口内仅提醒一次波动。

    Analyzer::observe_state

    fn Analyzer::observe_state(self : Analyzer, state : StateChange) -> Unit

    会话离开 Established 时清除该 peer 的缓存,避免跨会话误报。

    Analyzer::record_batch

    fn Analyzer::record_batch(self : Analyzer, batch : Batch) -> Unit

    Analyzer::route_states

    fn Analyzer::route_states(self : Analyzer) -> Array[RouteEvent]

    返回可审计的结束状态,顺序按状态键排序。

    Analyzer::seed

    fn Analyzer::seed(self : Analyzer, event : RouteEvent) -> Unit raise ParseError

    基线记录仅建立状态,不参与统计或波动计数。

    Analyzer::set_baseline

    fn Analyzer::set_baseline(self : Analyzer) -> Unit

    Analyzer::summary

    fn Analyzer::summary(self : Analyzer) -> Summary

    AsSegment

    pub struct AsSegment {
    kind : Int
    asns : Array[UInt]
    } derive(Eq, ToJson)

    AsSegment::equal

    fn AsSegment::equal(AsSegment, AsSegment) -> Bool

    AsSegment::not_equal

    fn AsSegment::not_equal(x : AsSegment, y : AsSegment) -> Bool

    AsSegment::to_json

    fn AsSegment::to_json(AsSegment) -> Json

    AtomicAggregateDiff

    pub struct AtomicAggregateDiff {
    before : Bool
    after : Bool
    } derive(ToJson)

    AtomicAggregateDiff::to_json

    Attributes

    pub struct Attributes {
    origin : Int?
    path : Array[AsSegment]
    wire_path : Array[AsSegment]
    as4_path : Array[AsSegment]
    next_hop : String?
    med : UInt?
    local_pref : UInt?
    atomic_aggregate : Bool
    aggregator : Aggregator?
    wire_aggregator : Aggregator?
    as4_aggregator : Aggregator?
    raw_attributes : Array[RawAttribute]
    communities : Array[UInt]
    large_communities : Array[(UInt, UInt, UInt)]
    other : Array[RawAttribute]
    } derive(Eq, ToJson)

    Attributes::equal

    fn Attributes::equal(Attributes, Attributes) -> Bool

    Attributes::not_equal

    fn Attributes::not_equal(x : Attributes, y : Attributes) -> Bool

    Attributes::to_json

    fn Attributes::to_json(Attributes) -> Json

    Batch

    pub struct Batch {
    header : Header
    microseconds : Int?
    events : Array[RouteEvent]
    states : Array[StateChange]
    skipped : Bool
    ignored_families : Int
    message_type : Int?
    diagnostics : Array[Diagnostic]
    } derive(ToJson)

    Batch::to_json

    fn Batch::to_json(Batch) -> Json

    BgpRecord

    pub struct BgpRecord {
    message_type : Int
    events : Array[RouteEvent]
    states : Array[StateChange]
    ignored_families : Int
    ignored : Array[(Int, Int)]
    }

    Change

    pub struct Change {
    schema_version : String
    timestamp : UInt
    microseconds : Int?
    prefix : String
    path_id : UInt?
    peer_asn : UInt
    peer_ip : String
    outbound : Bool
    kind : String
    source : Source?
    before : RouteSnapshot?
    after : RouteSnapshot
    diff : RouteDiff
    churn_count : Int?
    } derive(ToJson)

    Change::to_json

    fn Change::to_json(Change) -> Json

    CommunityDiff

    pub struct CommunityDiff {
    before : Array[UInt]
    after : Array[UInt]
    added : Array[UInt]
    removed : Array[UInt]
    } derive(ToJson)

    CommunityDiff::to_json

    Decoder

    pub struct Decoder {
    // private fields
    }

    Decoder::decode

    fn Decoder::decode(self : Decoder, data : Bytes) -> Batch raise ParseError

    Decoder::decode_at

    fn Decoder::decode_at(self : Decoder, data : Bytes, file : String, record : Int64, offset : Int64) -> Batch raise ParseError

    偏移指解压后的 MRT 记录起点,索引为记录内零起始事件序号。

    Decoder::new

    fn Decoder::new() -> Decoder

    Decoder::reset

    fn Decoder::reset(self : Decoder) -> Unit

    新文件必须清除 peer 表,防止沿用前一文件的索引。

    Diagnostic

    pub struct Diagnostic {
    kind : String
    mrt_type : Int
    subtype : Int
    afi : Int?
    safi : Int?
    source : Source?
    } derive(ToJson)

    Diagnostic::to_json

    fn Diagnostic::to_json(Diagnostic) -> Json

    Frame

    pub struct Frame {
    header : Header
    microseconds : Int?
    payload : Bytes
    } derive(Eq)

    Frame::equal

    fn Frame::equal(Frame, Frame) -> Bool

    Frame::not_equal

    fn Frame::not_equal(x : Frame, y : Frame) -> Bool

    pub struct Header {
    timestamp : UInt
    kind : Int
    subtype : Int
    length : Int
    } derive(Eq, ToJson)

    Header::equal

    fn Header::equal(Header, Header) -> Bool

    Header::not_equal

    fn Header::not_equal(x : Header, y : Header) -> Bool

    Header::to_json

    fn Header::to_json(Header) -> Json

    HopDiff

    pub struct HopDiff {
    before : String?
    after : String?
    }

    impl ToJson for HopDiff

    HopDiff::to_json

    fn HopDiff::to_json(self : HopDiff) -> Json

    LargeCommunityDiff

    pub struct LargeCommunityDiff {
    before : Array[(UInt, UInt, UInt)]
    after : Array[(UInt, UInt, UInt)]
    added : Array[(UInt, UInt, UInt)]
    removed : Array[(UInt, UInt, UInt)]
    } derive(ToJson)

    LargeCommunityDiff::to_json

    ObservationGap

    pub struct ObservationGap {
    from_time : UInt
    from_micros : Int
    to_time : UInt
    to_micros : Int
    } derive(ToJson)

    ObservationGap::new

    fn ObservationGap::new(from_time : UInt, to_time : UInt, from_micros? : Int, to_micros? : Int) -> ObservationGap

    ObservationGap::to_json

    OriginDiff

    pub struct OriginDiff {
    before : UInt?
    after : UInt?
    }

    OriginDiff::to_json

    fn OriginDiff::to_json(self : OriginDiff) -> Json

    PathDiff

    pub struct PathDiff {
    before : Array[AsSegment]
    after : Array[AsSegment]
    } derive(ToJson)

    PathDiff::to_json

    fn PathDiff::to_json(PathDiff) -> Json

    Peer

    pub struct Peer {
    bgp_id : UInt
    address : String
    asn : UInt
    } derive(ToJson)

    Peer::to_json

    fn Peer::to_json(Peer) -> Json

    PeerTable

    pub struct PeerTable {
    collector_id : UInt
    view_name : Bytes
    peers : Array[Peer]
    }

    Prefix

    pub struct Prefix {
    afi : Int
    length : Int
    address : Bytes
    } derive(Eq, ToJson)

    Prefix::contains

    fn Prefix::contains(self : Prefix, other : Prefix) -> Bool

    Prefix::equal

    fn Prefix::equal(Prefix, Prefix) -> Bool

    Prefix::not_equal

    fn Prefix::not_equal(x : Prefix, y : Prefix) -> Bool

    Prefix::text

    fn Prefix::text(self : Prefix) -> String

    Prefix::to_json

    fn Prefix::to_json(Prefix) -> Json

    PrefixRanking

    pub struct PrefixRanking {
    prefix : String
    changed_events : Int
    change_notices : Int64
    affected_peers : Int
    change_types : Map[String, Int64]
    } derive(ToJson)

    PrefixRanking::to_json

    Query

    pub struct Query {
    // private fields
    }

    Query::matches

    fn Query::matches(self : Query, event : RouteEvent) -> Bool

    前缀精确匹配;ASN 匹配 peer 或有效 AS 路径,时间包含端点。

    Query::new

    fn Query::new(prefix? : String, prefix_mode? : String, asn? : UInt, path_id? : UInt, from_time? : UInt, to_time? : UInt, from_micros? : Int, to_micros? : Int) -> Query raise ParseError

    RawAttribute

    pub struct RawAttribute {
    flags : Int
    code : Int
    value : Bytes
    } derive(Eq)

    RawAttribute::equal

    RawAttribute::not_equal

    fn RawAttribute::not_equal(x : RawAttribute, y : RawAttribute) -> Bool

    RawAttribute::to_json

    fn RawAttribute::to_json(self : RawAttribute) -> Json

    RouteDiff

    pub struct RouteDiff {
    atomic_aggregate : AtomicAggregateDiff?
    aggregator : AggregatorDiff?
    path : PathDiff?
    origin_as : OriginDiff?
    next_hop : HopDiff?
    communities : CommunityDiff?
    large_communities : LargeCommunityDiff?
    } derive(ToJson)

    RouteDiff::to_json

    fn RouteDiff::to_json(RouteDiff) -> Json

    RouteEvent

    pub struct RouteEvent {
    schema_version : String
    source : Source?
    timestamp : UInt
    microseconds : Int?
    action : String
    prefix : String
    path_id : UInt?
    peer_asn : UInt
    peer_ip : String
    outbound : Bool
    originated : UInt?
    attributes : Attributes
    } derive(ToJson)

    RouteEvent::to_json

    fn RouteEvent::to_json(RouteEvent) -> Json

    RouteSnapshot

    pub struct RouteSnapshot {
    timestamp : UInt
    microseconds : Int?
    active : Bool
    attributes : Attributes
    source : Source?
    } derive(ToJson)

    RouteSnapshot::to_json

    Snapshot

    pub struct Snapshot {
    // private fields
    }

    Snapshot::diff

    fn Snapshot::diff(self : Snapshot, after : Snapshot) -> SnapshotReport raise ParseError

    比较显式同源、同视图的两个快照;缺失不等同于观测到撤回。

    Snapshot::ingest

    fn Snapshot::ingest(self : Snapshot, data : Bytes, file : String, record : Int64, offset : Int64, query? : Query, peer_asn? : UInt) -> Unit raise ParseError

    快照保留 peer 表,避免把整个 peer 未采集误判为单条路由消失。

    Snapshot::new

    fn Snapshot::new(collector : String, capacity? : Int) -> Snapshot raise ParseError

    SnapshotChange

    pub struct SnapshotChange {
    schema_version : String
    collector : String
    kind : String
    peer_asn : UInt
    peer_ip : String
    outbound : Bool
    prefix : String?
    path_id : UInt?
    before : RouteEvent?
    after : RouteEvent?
    diff : RouteDiff?
    before_source : Source?
    after_source : Source?
    } derive(ToJson)

    SnapshotChange::to_json

    SnapshotReport

    pub struct SnapshotReport {
    schema_version : String
    collector : String
    interpretation : String
    before_source : Source?
    after_source : Source?
    before_time : UInt?
    after_time : UInt?
    before_peers : Int
    after_peers : Int
    before_routes : Int
    after_routes : Int
    changes : Array[SnapshotChange]
    } derive(ToJson)

    SnapshotReport::to_json

    Source

    pub struct Source {
    file : String
    record : Int64
    offset : Int64
    event_index : Int
    } derive(Eq, Hash, ToJson)

    Source::equal

    fn Source::equal(Source, Source) -> Bool

    Source::hash

    fn Source::hash(self : Source) -> Int

    Source::hash_combine

    fn Source::hash_combine(Source, Hasher) -> Unit

    Source::not_equal

    fn Source::not_equal(x : Source, y : Source) -> Bool

    Source::to_json

    fn Source::to_json(Source) -> Json

    StateChange

    pub struct StateChange {
    source : Source?
    timestamp : UInt
    microseconds : Int?
    peer_asn : UInt
    peer_ip : String
    old_state : Int
    new_state : Int
    } derive(ToJson)

    StateChange::to_json

    fn StateChange::to_json(StateChange) -> Json

    StreamDecoder

    pub struct StreamDecoder {
    // private fields
    }

    StreamDecoder::feed

    fn StreamDecoder::feed(self : StreamDecoder, chunk : Bytes) -> Array[Batch] raise ParseError

    输入分块上限 1 MiB,失败后需新建解码器。

    StreamDecoder::finish

    fn StreamDecoder::finish(self : StreamDecoder) -> Unit raise ParseError

    StreamDecoder::new

    Summary

    pub struct Summary {
    schema_version : String
    baseline_status : String
    warmup_records : Int64
    records : Int64
    skipped_records : Int64
    ignored_families : Int64
    rib_routes : Int64
    announcements : Int64
    withdrawals : Int64
    path_changes : Int64
    attribute_changes : Int64
    origin_changes : Int64
    duplicate_announcements : Int64
    churn_alerts : Int64
    out_of_order : Int64
    session_resets : Int64
    tracked_routes : Int
    active_routes : Int
    mean_path_length : Double
    max_path_length : Int
    } derive(ToJson)

    Summary::to_json

    fn Summary::to_json(Summary) -> Json

    Timeline

    pub struct Timeline {
    // private fields
    }

    Timeline::new

    fn Timeline::new(prefix : String, analyzer : Analyzer, continuous? : Bool, gaps? : Array[ObservationGap], capacity? : Int, peer_asn? : UInt) -> Timeline raise ParseError

    Timeline::observe

    fn Timeline::observe(self : Timeline, event : RouteEvent) -> Unit raise ParseError

    Timeline::record_batch

    fn Timeline::record_batch(self : Timeline, batch : Batch) -> Unit raise ParseError

    Timeline::report

    fn Timeline::report(self : Timeline) -> TimelineReport raise ParseError

    Timeline::seed

    fn Timeline::seed(self : Timeline, event : RouteEvent) -> Unit raise ParseError

    Timeline::seed_peers

    fn Timeline::seed_peers(self : Timeline, table : PeerTable, timestamp : UInt) -> Unit raise ParseError

    TimelineEntry

    pub struct TimelineEntry {
    timestamp : UInt
    microseconds : Int?
    kind : String
    prefix : String
    path_id : UInt?
    peer_asn : UInt?
    peer_ip : String?
    outbound : Bool?
    accepted : Bool
    source : Source?
    before : RouteSnapshot?
    after : RouteSnapshot?
    changes : Array[Change]
    } derive(ToJson)

    TimelineEntry::to_json

    TimelinePath

    pub struct TimelinePath {
    prefix : String
    path_id : UInt?
    peer_asn : UInt
    peer_ip : String
    outbound : Bool
    visibility : String
    observed_at : UInt64
    source : Source?
    } derive(ToJson)

    TimelinePath::to_json

    TimelinePeer

    pub struct TimelinePeer {
    peer_asn : UInt
    peer_ip : String
    outbound : Bool
    visibility : String
    visible_paths : Int
    not_visible_paths : Int
    unobserved_paths : Int
    baseline_present : Bool
    } derive(ToJson)

    TimelinePeer::to_json

    TimelineReport

    pub struct TimelineReport {
    schema_version : String
    prefix : String
    interpretation : String
    baseline_status : String
    continuous_observation : Bool
    last_input_time : UInt64?
    observed_window_end : UInt64?
    gaps : Array[ObservationGap]
    entries : Array[TimelineEntry]
    intervals : Array[VisibilityInterval]
    peer_intervals : Array[VisibilityInterval]
    paths : Array[TimelinePath]
    peers : Array[TimelinePeer]
    } derive(ToJson)

    TimelineReport::to_json

    VisibilityInterval

    pub struct VisibilityInterval {
    scope : String
    prefix : String
    path_id : UInt?
    peer_asn : UInt
    peer_ip : String
    outbound : Bool
    started_at : UInt64
    measured_from : UInt64
    observed_until : UInt64
    recovery_time : UInt64?
    known_invisible_microseconds : UInt64?
    left_censored : Bool
    right_censored : Bool
    quality : String
    closed_by : String
    start_source : Source?
    end_source : Source?
    } derive(ToJson)

    VisibilityInterval::to_json

    csv_header

    fn csv_header() -> String

    event_csv

    fn event_csv(event : RouteEvent) -> String

    event_json

    fn event_json(event : RouteEvent) -> String

    origin_as

    fn origin_as(path : Array[AsSegment]) -> UInt?

    末段为 AS_SET 时无法唯一确定起源 AS。

    parse_attributes

    fn parse_attributes(data : Bytes, asn_width : Int) -> Attributes raise ParseError

    parse_bgp4mp

    fn parse_bgp4mp(frame : Frame) -> BgpRecord raise ParseError

    parse_peers

    fn parse_peers(data : Bytes) -> PeerTable raise ParseError

    parse_prefix

    fn parse_prefix(data : Bytes, afi : Int) -> Prefix raise ParseError

    parse_prefix_text

    fn parse_prefix_text(text : String) -> Prefix raise ParseError

    库和 CLI 共用的文本解析;没有掩码的地址表示主机前缀。

    parse_rib

    fn parse_rib(frame : Frame, peers : Array[Peer]) -> Array[RouteEvent] raise ParseError

    TABLE_DUMP_V2 的 AS_PATH 始终使用四字节 ASN。

    path_length

    fn path_length(path : Array[AsSegment]) -> Int

    AS_SET 计一个跳数,联盟路径不参与常规路径长度统计。

    path_text

    fn path_text(path : Array[AsSegment]) -> String

    read_frame

    fn read_frame(data : Bytes) -> Frame raise ParseError

    单条记录上限为 16 MiB,未知类型仍保留完整记录。

    reconstruct_path

    fn reconstruct_path(path : Array[AsSegment], as4 : Array[AsSegment]) -> Array[AsSegment]

    按 RFC 6793 保留旧路径前缀,再拼接 AS4_PATH。