Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ examples/debug-file.rs
# local benchmark results
benchmarks
docs/
*.md
*.md
!examples/**/*.md
!src/wasm/**/*.md

# WASM build artifacts
pkg/
pkg-*/
node_modules/
.wrangler/
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ All notable changes to this project will be documented in this file.

### New features

* **WebAssembly (WASM) support (experimental)**: New `wasm` feature flag compiles the BMP/BGP/MRT parsing core to WebAssembly for use in JavaScript environments. Published as [`@bgpkit/parser`](https://www.npmjs.com/package/@bgpkit/parser) on npm with support for Node.js (CommonJS), bundlers (ES modules), and browsers/workers (ES modules with manual init). This feature is experimental and the API may change in future releases.
- Core parsing functions (all platforms):
- `parseOpenBmpMessage(data)`: parses OpenBMP-wrapped BMP frames (e.g. RouteViews Kafka stream)
- `parseBmpMessage(data, timestamp)`: parses raw BMP frames (without an OpenBMP header)
- `parseBgpUpdate(data)`: parses a single BGP UPDATE message into `BgpElem[]`
- `parseMrtRecords(data)`: streaming generator that yields MRT records one at a time from a decompressed buffer
- `parseMrtRecord(data)` / `resetMrtParser()`: low-level MRT record parsing
- Node.js I/O helpers:
- `streamMrtFrom(pathOrUrl)`: fetch, decompress (gz/bz2), and stream-parse MRT records from a URL or local file
- `openMrt(pathOrUrl)`: fetch and decompress MRT data into a `Buffer`
- Supports gzip (RIPE RIS) and bzip2 (RouteViews, requires optional `seek-bzip` dependency) compression
- Multi-target build script (`src/wasm/build.sh`) produces nodejs, bundler, and web targets in a single npm package
- JS wrapper handles JSON deserialization; TypeScript types included
- Node.js examples in `examples/wasm/`: Kafka OpenBMP stream consumer and MRT file parser
- Browser-based MRT explorer demo: [mrt-explorer.labs.bgpkit.com](https://mrt-explorer.labs.bgpkit.com/)

* **`BgpElem::peer_bgp_id` field**: `BgpElem` now exposes an optional `peer_bgp_id: Option<BgpIdentifier>` containing the peer's BGP Identifier (Router ID) when available. Populated from the PEER_INDEX_TABLE in TableDumpV2/RIB records; `None` for BGP4MP records.

### Performance improvements
Expand Down
17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ name = "bgpkit-parser"
path = "src/bin/main.rs"
required-features = ["cli"]

# cdylib is required by wasm-pack for WASM builds; rlib is the default for
# normal Rust library usage. Both are listed so the crate works for native
# consumers and wasm-pack alike.
[lib]
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]

[dependencies]

##############
Expand All @@ -44,6 +51,7 @@ oneio = { version = "0.20.0", default-features = false, features = ["http", "gz"
regex = { version = "1", optional = true } # used in parser filter
chrono = { version = "0.4.38", optional = true } # parser filter
serde_json = { version = "1.0", optional = true } # RIS Live parsing
wasm-bindgen = { version = "0.2", optional = true }

####################
# CLI dependencies #
Expand Down Expand Up @@ -76,6 +84,15 @@ rislive = [
"serde_json",
"hex",
]

# WebAssembly bindings for browser/Node.js usage.
# Build with: wasm-pack build --target nodejs --no-default-features --features wasm
wasm = [
"parser",
"serde",
"serde_json",
"dep:wasm-bindgen",
]
serde = [
"dep:serde",
"ipnet/serde",
Expand Down
3 changes: 3 additions & 0 deletions examples/wasm/kafka-openbmp-stream/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
pkg/
package-lock.json
59 changes: 59 additions & 0 deletions examples/wasm/kafka-openbmp-stream/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# RouteViews Kafka Stream — Node.js Example

> **Experimental**: The `@bgpkit/parser` npm package is experimental. The API
> surface, output format, and build process may change in future releases.

This example consumes the [RouteViews](http://www.routeviews.org/routeviews/)
real-time Kafka stream of OpenBMP messages and parses them into JSON using
[`@bgpkit/parser`](https://www.npmjs.com/package/@bgpkit/parser).

## Prerequisites

- [Node.js](https://nodejs.org/) >= 18

## Run

```sh
cd examples/wasm/kafka-openbmp-stream
npm install
npm start
```

## Configuration

Edit the constants at the top of `kafka-stream.js`:

| Variable | Default | Description |
|---|---|---|
| `BROKER` | `stream.routeviews.org:9092` | Kafka broker address |
| `TOPIC_PATTERN` | `/^routeviews\.amsix\..+\.bmp_raw$/` | Regex to filter Kafka topics |
| `GROUP_ID` | `bgpkit-parser-nodejs-example` | Kafka consumer group ID |

### Selecting collectors

Topics follow the naming pattern `routeviews.<collector>.<peer_asn>.bmp_raw`:

```js
// All AMS-IX topics
const TOPIC_PATTERN = /^routeviews\.amsix\..+\.bmp_raw$/;

// A specific collector/peer
const TOPIC_PATTERN = /^routeviews\.amsix\.ams\.6777\.bmp_raw$/;

// All collectors (high volume!)
const TOPIC_PATTERN = /^routeviews\..+\.bmp_raw$/;
```

## Output

Each line is a JSON object representing a parsed BMP message. RouteMonitoring
messages include an `elems` array of BGP elements (announcements/withdrawals).

## How it works

1. `@bgpkit/parser` is a WebAssembly build of bgpkit-parser's BMP/BGP parsing
core, published as an npm package.
2. `parseOpenBmpMessage(bytes)` accepts raw Kafka message bytes (an OpenBMP
header + BMP frame) and returns a parsed JavaScript object.
3. [KafkaJS](https://kafka.js.org/) is used to consume messages from the
RouteViews broker.
85 changes: 85 additions & 0 deletions examples/wasm/kafka-openbmp-stream/kafka-stream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict';

/**
* Route-views Kafka stream consumer using @bgpkit/parser.
*
* Prerequisites:
* 1. Install Node.js dependencies:
* npm install
* 2. Run:
* npm start
*
* The RouteViews Kafka stream is publicly accessible at stream.routeviews.org:9092.
* Topics follow the pattern: routeviews.<collector>.<peer>.bmp_raw
* Each message value is an OpenBMP-wrapped BMP frame (binary).
*/

const { Kafka, logLevel } = require('kafkajs');
const { parseOpenBmpMessage } = require('@bgpkit/parser');

// ── Configuration ────────────────────────────────────────────────────────────

const BROKER = 'stream.routeviews.org:9092';

// Regex filter applied to Kafka topic names. Adjust to taste:
// All collectors: /^routeviews\..+\.bmp_raw$/
// Specific collector: /^routeviews\.amsix\.ams\..+\.bmp_raw$/
const TOPIC_PATTERN = /^routeviews\.amsix\..+\.bmp_raw$/;

// Consumer group ID. Change this to start reading from the latest offset
// in a fresh group, or reuse a group to resume from where you left off.
const GROUP_ID = 'bgpkit-parser-nodejs-example';

// ── Main ─────────────────────────────────────────────────────────────────────

async function run() {
const kafka = new Kafka({
clientId: 'bgpkit-parser-example',
brokers: [BROKER],
logLevel: logLevel.NOTHING,
});

const admin = kafka.admin();
await admin.connect();
const allTopics = await admin.listTopics();
await admin.disconnect();

const topics = allTopics.filter((t) => TOPIC_PATTERN.test(t));
if (topics.length === 0) {
console.error(`No topics matching ${TOPIC_PATTERN} found on ${BROKER}`);
process.exit(1);
}
process.stderr.write(`Subscribed to ${topics.length} topics on ${BROKER}\n`);

const consumer = kafka.consumer({
groupId: GROUP_ID,
sessionTimeout: 30000,
});
await consumer.connect();

for (const topic of topics) {
await consumer.subscribe({ topic, fromBeginning: false });
}

await consumer.run({
eachMessage: async ({ message }) => {
if (!message.value) return;

let msg;
try {
msg = parseOpenBmpMessage(message.value);
} catch {
return;
}

if (!msg) return;

console.log(JSON.stringify(msg));
},
});
}

run().catch((err) => {
console.error('Fatal error:', err);
process.exit(1);
});
17 changes: 17 additions & 0 deletions examples/wasm/kafka-openbmp-stream/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "bgpkit-parser-kafka-example",
"version": "1.0.0",
"description": "Route-views Kafka stream consumer using @bgpkit/parser (WebAssembly)",
"main": "kafka-stream.js",
"scripts": {
"start": "node kafka-stream.js",
"start:quiet": "node kafka-stream.js 2>/dev/null"
},
"dependencies": {
"@bgpkit/parser": "^0.15.0",
"kafkajs": "^2.2.4"
},
"engines": {
"node": ">=18"
}
}
5 changes: 5 additions & 0 deletions examples/wasm/parse-mrt-file/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
package-lock.json
*.gz
*.bz2
*.mrt
51 changes: 51 additions & 0 deletions examples/wasm/parse-mrt-file/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Parse MRT File — Node.js Example

> **Experimental**: The `@bgpkit/parser` npm package is experimental. The API
> surface, output format, and build process may change in future releases.

This example parses an MRT file into BGP elements using
[`@bgpkit/parser`](https://www.npmjs.com/package/@bgpkit/parser) and outputs
one JSON object per line. It accepts both URLs and local files.

## Prerequisites

- [Node.js](https://nodejs.org/) >= 18
- For bz2-compressed files (RouteViews): `npm install seek-bzip`

## Run

```sh
cd examples/wasm/parse-mrt-file
npm install
npm install seek-bzip # optional, for .bz2 files

# Parse directly from a URL (fetches and decompresses in memory)
node parse-mrt.js https://data.ris.ripe.net/rrc06/2026.03/updates.20260322.2105.gz

# Or parse a local file
node parse-mrt.js updates.20260322.2105.gz
```

## Output

Each line is a JSON object representing a single BGP element:

```json
{"timestamp":1742677500.0,"type":"ANNOUNCE","peer_ip":"2001:7f8:4::...","peer_asn":6939,...}
{"timestamp":1742677500.0,"type":"WITHDRAW","peer_ip":"80.249.211.155","peer_asn":34549,...}
```

You can pipe the output through `jq` for pretty-printing or filtering:

```sh
URL=https://data.ris.ripe.net/rrc06/2026.03/updates.20260322.2105.gz

# Pretty-print first element
node parse-mrt.js $URL | head -1 | jq .

# Count announcements vs withdrawals
node parse-mrt.js $URL | jq -r '.type' | sort | uniq -c

# Filter by prefix
node parse-mrt.js $URL | jq -r 'select(.prefix == "1.0.0.0/24")'
```
16 changes: 16 additions & 0 deletions examples/wasm/parse-mrt-file/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "bgpkit-parser-mrt-example",
"version": "1.0.0",
"description": "Parse MRT files into BGP elements using @bgpkit/parser (WebAssembly)",
"main": "parse-mrt.js",
"scripts": {
"start": "node parse-mrt.js"
},
"dependencies": {
"@bgpkit/parser": "^0.15.0",
"seek-bzip": "^2.0.0"
},
"engines": {
"node": ">=18"
}
}
45 changes: 45 additions & 0 deletions examples/wasm/parse-mrt-file/parse-mrt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict';

/**
* Parse an MRT file and output BGP elements as JSON, one per line.
*
* Usage:
* node parse-mrt.js <url-or-file>
* node parse-mrt.js https://data.ris.ripe.net/rrc06/2026.03/updates.20260322.2105.gz
* node parse-mrt.js https://archive.routeviews.org/route-views.amsix/bgpdata/2025.01/UPDATES/updates.20250101.0000.bz2
* node parse-mrt.js updates.20260322.2105.gz
*
* Supports .gz (gzip) and .bz2 (bzip2, requires: npm install seek-bzip) compressed
* and uncompressed MRT files. URLs and local paths are both supported.
*/

const { streamMrtFrom } = require('@bgpkit/parser');

const input = process.argv[2];

if (!input) {
console.error('Usage: node parse-mrt.js <url-or-file>');
console.error(' node parse-mrt.js https://data.ris.ripe.net/rrc06/2026.03/updates.20260322.2105.gz');
console.error(' node parse-mrt.js updates.20260322.2105.gz');
process.exit(1);
}

async function main() {
process.stderr.write(`Parsing ${input}...\n`);

let count = 0;

for await (const { elems } of streamMrtFrom(input)) {
for (const elem of elems) {
console.log(JSON.stringify(elem));
count++;
}
}

process.stderr.write(`Output ${count} BGP elements\n`);
}

main().catch((err) => {
console.error('Error:', err.message);
process.exit(1);
});
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,8 @@ pub mod error;
pub mod models;
#[cfg(feature = "parser")]
pub mod parser;
#[cfg(feature = "wasm")]
pub mod wasm;

pub use models::BgpElem;
pub use models::MrtRecord;
Expand Down
Loading
Loading