The Problem Nobody’s Solving
We’re automating production networks at an accelerating pace. Cisco, Fortinet, Juniper — everyone’s shipping programmable operations, intent-based
networking, and closed-loop remediation. But there’s a fundamental question nobody has answered:
How do you prove that what your automation reports about your network is actually true?
BGPsec validates that a route came from where it claims. RPKI validates origin ASes. Neither one validates what a router actually did or what state it’s
actually in. When an orchestration platform reports “BGP peer 10.1.1.1 is in Established state,” there’s no cryptographic proof behind that claim. The
system could be working from cached data. It could be reading stale output. It could be compromised.
This is the observation trust gap — and it gets dangerous fast when automated systems start making routing decisions based on unverified state.
VIRP: Signing at the Source
VIRP (Verified Infrastructure Response Protocol) solves this by moving cryptographic attestation inside the network daemon itself. Not beside it. Not
after the fact. Inside the process space where BGP events fire.
I wrote a VIRP module for FRRouting that hooks directly into bgpd’s finite state machine and RIB processing. When a peer transitions state, when a route
is announced or withdrawn, when a best-path recalculation fires — the observation is HMAC-SHA256 signed in the same execution context, before it ever
leaves the daemon.
The architecture:
┌─────────────────────────────────────────────────┐
│ FRR bgpd process │
│ │
│ BGP FSM ──► peer_status_changed ──┐ │
│ BGP FSM ──► peer_backward_trans ──┤ │
│ RIB ──► bgp_process ─────────┤ bgp_virp │
│ RIB ──► bgp_route_update ────┘ module │
│ │ │
│ HMAC-SHA256 sign │
│ │ │
└──────────────────┼───────────────────────────────┘
│ Unix socket
▼
┌──────────────┐
│ O-Node │
│ chain.db │
└──────────────┘
Four hooks. Four observation types. Each one signed at collection time with a key that no external system touches.
What Gets Signed — and Why It Matters
Every observation carries a trust tier assigned at the source, not at analysis time:Hook Obs Type Tier Data Capturedpeer_status_changed Type 1 GREEN Peer IP/AS, old→new FSM state, router-idpeer_backward_transition Type 2 YELLOW Peer dropped from Established — automatic alert gradebgp_route_update Type 4 GREEN Old/new best-path with full attrs (AS path, NH, MED, LP)bgp_process Type 3 GREEN Prefix announce/withdraw, peer, AFI/SAFI
Notice that peer_backward_transition automatically gets YELLOW tier. When a BGP peer drops from Established, that’s not normal operations —
the observation itself carries the severity classification. Any consumer of VIRP observations doesn’t get to decide whether a peer loss is important. The
daemon already told it.
The trust tiers follow a four-level model:
GREEN — Normal operations. Observation verified, no anomalies.
YELLOW — Elevated concern. Something happened that warrants attention.RED — Active threat or failure. Human approval required before action.
BLACK — Observation channel compromised. Full stop.
The Wire Format
Every observation hits the wire in a compact binary format designed for speed and unambiguous parsing:
Bytes 0-3: Magic (0x56495250 — "VIRP" in ASCII)
Byte 4: Version (1)
Byte 5: Observation type (1-4)
Byte 6: Trust tier (0=GREEN, 1=YELLOW, 2=RED, 3=BLACK)
Byte 7: Reserved
Bytes 8-15: Timestamp (nanosecond epoch)
Bytes 16-19: Sequence number
Bytes 20-23: Payload length
Bytes 24-N: JSON payload (variable)
Bytes N-N+32: HMAC-SHA256 (covers bytes 0 through end of payload)
24-byte fixed header. Variable JSON payload. 32-byte HMAC covering everything from the magic bytes through the last byte of payload. Any modification
— a changed timestamp, a flipped bit in the tier field, a rewritten payload — invalidates the signature.
The magic bytes spelling “VIRP” in hex (0x56495250) aren’t just aesthetic. They let any receiver immediately reject non-VIRP traffic on shared sockets
without parsing further.
Building It: Step by Step
Prerequisites
You need an FRR installation (or source tree), OpenSSL development headers, and a Unix-like environment. The VIRP module compiles as either a
loadable module or linked directly into bgpd.
Step 1: Validate Signing Logic (Standalone)
Before touching FRR, confirm the cryptographic primitives work in isolation:
Compile the standalone test harness — no FRR dependency
gcc -o virp_bgp_test virp_bgp_test.c -lssl -lcrypto -Wall -O2
# Generate a test key
echo -n "your-hmac-key-here" > /tmp/virp-test.key
# Run it
./virp_bgp_test --key /tmp/virp-test.key
Expected output: 5/5 PASS — signing, verification, tamper detection, sequence ordering, tier assignment. If any test fails here, you have an OpenSSL
issue, not a VIRP issue.
Step 2: Add to FRR Build System
Option A: Loadable Module (Recommended)
Add to bgpd/subdir.am:
VIRP observation module
if BGPD
module_LTLIBRARIES += bgpd/bgpd_virp.la
endif
bgpd_bgpd_virp_la_SOURCES = bgpd/bgp_virp.c
bgpd_bgpd_virp_la_LIBADD = -lssl -lcrypto
bgpd_bgpd_virp_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
Then rebuild FRR:
cd /path/to/frr
./bootstrap.sh
./configure --enable-bgp-virp
make -j$(nproc)
sudo make install
Option B: Compile Into bgpdAdd bgpd/bgp_virp.c to bgpd_bgpd_SOURCES and -lssl -lcrypto to bgpd_bgpd_LDADD in bgpd/subdir.am.
Step 3: Key Management
VIRP uses channel-separated key binding — the Observation Key (O-Key) used for signing is completely separate from any key used for intent execution.
A compromised management station cannot forge observations.
Generate HMAC key on the O-Node (your trust anchor)
openssl rand -hex 32 > /etc/virp/bgp.key
chmod 600 /etc/virp/bgp.key
# Distribute to FRR host (same key, shared secret)
scp /etc/virp/bgp.key frr-host:/etc/virp/bgp.key
Step 4: Configure FRR
Inside your BGP configuration:
router bgp 65000
virp hmac-key /etc/virp/bgp.key
virp onode-socket /tmp/virp-onode.sock
virp enable
!
Three lines. That’s the entire VIRP configuration on the router side. The module handles hook registration, signing, serialization, and socket transmission
internally.
Step 5: Verify
frr# show bgp virp status
VIRP Module Status:
State: ACTIVE
HMAC Key: loaded (/etc/virp/bgp.key)
O-Node: connected (/tmp/virp-onode.sock)
Observations: 47 signed, 47 delivered, 0 failed
frr# show bgp virp statistics
Type 1 (peer_status): 12
Type 2 (backward_trans): 2
Type 3 (bgp_process): 18
Type 4 (route_update): 15
Signing errors: 0
Socket errors: 0
Step 6: Watch It Work
On a neighbor router, shut and re-enable a BGP peer. Then watch the signed observations flow:
On the FRR host
journalctl -u frr -f | grep VIRP
# You'll see:
VIRP[1]: type=1 tier=0 peer=10.1.1.2 AS=65001 state=Established
VIRP[2]: type=2 tier=1 peer=10.1.1.2 AS=65001 BACKWARD_TRANSITION
VIRP[3]: type=4 tier=0 prefix=10.0.0.0/24 bestpath_changed=yes
Type 2 with tier=1 — that’s the backward transition. Automatically YELLOW. The daemon classified it before any external system touched it.
The O-Node: Your Trust Anchor
The O-Node is a separate process (ideally on a separate host or container) that receives, verifies, and chains observations. In my production deployment,
the O-Node runs on an isolated LXC container with its own IP, its own SQLite chain database, and no automation code anywhere on it.
Here’s a minimal receiver for testing:
!/usr/bin/env python3
"""Minimal VIRP O-Node receiver for testing."""
import socket, struct, hmac, hashlib, json, os
SOCK_PATH = "/tmp/virp-onode.sock"KEY_PATH = "/etc/virp/bgp.key"
HDR_SIZE = 24
HMAC_LEN = 32
TIERS = {0: "GREEN", 1: "YELLOW", 2: "RED", 3: "BLACK"}
def main():
with open(KEY_PATH) as f:
key = f.read().strip().encode()
if os.path.exists(SOCK_PATH):
os.unlink(SOCK_PATH)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(SOCK_PATH)
sock.listen(5)
print(f"O-Node listening on {SOCK_PATH}")
while True:
conn, _ = sock.accept()
try:
header = conn.recv(HDR_SIZE)
if len(header) < HDR_SIZE:
continue
magic = struct.unpack('!I', header[0:4])[0]
if magic != 0x56495250:
continue
obs_type = header[5]
trust_tier = header[6]
sequence = struct.unpack('I', header[16:20])[0]
payload_len = struct.unpack('I', header[20:24])[0]
remaining = conn.recv(payload_len + HMAC_LEN)
payload = remaining[:payload_len]
received_hmac = remaining[payload_len:payload_len + HMAC_LEN]
computed = hmac.new(key, header + payload, hashlib.sha256).digest()
valid = hmac.compare_digest(computed, received_hmac)
status = "VERIFIED" if valid else "** INVALID **"
print(f"[{status}] seq={sequence} type={obs_type} "
f"tier={TIERS.get(trust_tier, '?')} "
f"payload={payload.decode()}")
except Exception as e:
print(f"Error: {e}")
finally:
conn.close()
if name == "main":
main()
In production, the O-Node writes verified observations to chain.db (SQLite), forming a cryptographic audit trail. Each observation references the
previous one’s hash, creating a tamper-evident chain. If someone deletes or modifies a historical observation, the chain breaks and every subsequent
entry becomes suspect.
Why This Isn’t Just Another Monitoring Tool
This is the part most people miss on first read. VIRP isn’t collecting telemetry. It’s creating evidence.
When an automation platform consumes VIRP observations, it’s not reading SNMP counters or parsing CLI output that it generated itself. It’s receiving
cryptographically signed attestations from inside the daemon — signed with a key no external system holds. The automation cannot fabricate an
observation. It cannot backdate one. It cannot upgrade a YELLOW to a GREEN.
This creates what I call evidence-gated operations: any system consuming observations can only act on what it can prove. If it wants to claim a peer is
down, there must be a signed Type 2 observation in the chain. If there isn’t, the system refuses the conclusion. No evidence, no claim.
I tested this by running autonomous remediation workflows against VIRP-protected infrastructure. The orchestration layer identified 10 different bypass
vectors and attempted to rewrite source code, recompile binaries, and set immutable flags — all without approval. VIRP’s trust tier system caught every
attempt. RED-tier actions couldn’t execute without human approval, and the system couldn’t forge the observations needed to justify GREEN-tier
confidence.How This Differs from BGPsec and RPKIBGPsec / RPKI VIRPPrimary goal Route origin and path validation Device state and observation attestationTrigger UPDATE message received from peer Local event inside the daemonWhat’s signed Routing table entries, ROAs Device-level telemetry, FSM state transitionsAutomation role Passive filtering of invalid routes Active evidence chain for automated decision-makingPerformance Computationally expensive (BGPsec adoption bottleneck) HMAC-SHA256, hardware-accelerated on modern CPUs
VIRP doesn’t replace BGPsec or RPKI. They validate the routing control plane. VIRP validates what the device itself is doing. They’re complementary —
and both are necessary once automated systems start making routing decisions.
The Universal Argument
The wire format and signing mechanism are completely protocol-agnostic. The same 24-byte header, the same HMAC, the same O-Node receiver works
for BGP, DNS, IPsec, or anything else that generates observable events. The only thing that changes per domain is the JSON payload schema and the
hook points.
I’ve already built collectors for Cisco IOS (SSH), FortiGate (SSH via C executor), and Palo Alto PAN-OS. The FRR integration goes further — it’s not
collecting from the outside, it’s signing from the inside. But the observation format is identical across all of them.
This is the core argument in the IETF RFC draft (draft-howard-virp-01, submitted to the RATS working group): VIRP is a domain-agnostic trust primitive for
infrastructure operations. If it works for BGP, it works for DNS. If it works for DNS, it works for IoT telemetry. The protocol doesn’t care what it’s attesting
— it cares that the attestation is cryptographically sound.
What This Proves
With this running on a 35-router GNS3 lab across 13 autonomous systems, the implementation demonstrates five things:
VIRP signs at collection time. The HMAC happens inside bgpd’s process space, at the instant the event fires.
Observation-only. The module registers read-only hooks. It cannot modify routes, peer state, or configuration. Pure attestation.
Protocol-agnostic framing. The observation structure works identically whether the source is BGP, DNS, or IPsec.
Trust tiers at the source. Backward transitions are automatically YELLOW. Normal operations are GREEN. Classification happens in the daemon,
not in the consumer.
Tamper detection. Any modification between bgpd and the O-Node is caught by HMAC verification.
Try It Yourself
The full implementation — bgp_virp.h, bgp_virp.c, standalone test harness, and this integration guide — is available on GitHub:
nhowardtli
/
virp
VIRP Protocol: Cryptographic Trust Primitives and Network Trust Anchor consumable by any system
Licensed under Apache 2.0. The RFC draft (draft-howard-virp-01) is under review with the IETF RATS working group. A DOI-registered version is archived
on Zenodo for citation.
If you’re building automated network operations and you don’t have a trust layer, you’re building on faith. VIRP replaces faith with evidence
Top comments (0)