BGPFix Golang Library

A generic-purpose, high-performance Golang library for bridging the gaps in BGP.
For a CLI tool, see the bgpipe BGP reverse proxy and firewall.
BGPFix can "fix" or "extend" BGP sessions in-flight, possibly adding new features or protection layers to proprietary BGP speakers (think big router vendors). The vision is to allow implementing:
- bidirectional BGP session to JSON translation, replacing exabgp for some use-cases,
- transparent BGP proxy, optionally rewriting the messages in-flight,
- streaming MRT files to BGP routers, adding the necessary OPEN negotiation beforehand,
- Flowspec data plane firewalls using Linux Netfilter,
- passive inspection (and storage) of ongoing BGP sessions, like in tcpdump,
- cool new BGP extensions for legacy speakers, eg. RPKI and ASPA validation, Only To Customer (OTC) attribute, or even BGPSec,
- protecting from grave flaws in BGP error handling, and possibly other flaws found using BGP fuzzing projects
- academic research ideas, eg. Pretty Good BGP or protection against distributed prefix de-aggregation attacks.
If you're interested in bgpfix, you might also want to see:
Idea
The overall idea is presented below. You don't need to use the whole library, eg. you may stick to the basic BGP message marshal / unmarshal procedures.

The above explains the concept of a Pipe: it has two directions used to exchange BGP messages between 2 speakers on the left (L) and right (R) hand side of the picture.
Each Msg sent to the In channel of a particular direction will go through a set of callbacks (think "plugins") configured in the pipe Options. Each callback can read, write, modify, synthesize, or drop messages before they reach the Out channel. In addition to BGP messages, callbacks may emit Events - such as the standard events of the Pipe - which event handlers may subscribe to in the pipe Options.
Example
A basic example on how to establish a BGP session with a router, and print all messages as JSON to stdout:
package main
import (
"context"
"flag"
"fmt"
"io"
"net"
"net/netip"
"os"
"github.com/bgpfix/bgpfix/msg"
"github.com/bgpfix/bgpfix/pipe"
"github.com/bgpfix/bgpfix/speaker"
)
var (
opt_active = flag.Bool("active", false, "send OPEN first")
opt_asn = flag.Int("asn", 65055, "local ASN number")
opt_hold = flag.Int("hold", 60, "local hold timer")
opt_id = flag.String("id", "1.1.1.1", "local Id (must be IPv4 address)")
)
func main() {
// parse flags
flag.Parse()
if flag.NArg() == 0 {
fmt.Printf("usage: bgpfix [OPTIONS] <target:port>\n")
os.Exit(1)
}
// create a Pipe, add callback and event handlers
p := pipe.NewPipe(context.Background())
p.OnMsg(print, dir.DIR_LR) // call print() on every message in any direction
p.OnEvent(event) // call event() on any pipe event
// L side: a TCP target, sending to R
conn, err := net.Dial("tcp", flag.Arg(0)) // assumes a ":179" suffix
if err != nil {
panic(err)
}
// R side: a local speaker, sending to L
spk := speaker.NewSpeaker(context.Background())
spk.Options.Passive = !*opt_active
spk.Options.LocalASN = *opt_asn
spk.Options.LocalHoldTime = *opt_hold
spk.Options.LocalId = netip.MustParseAddr(*opt_id)
spk.Attach(p, dir.DIR_L)
// copy from conn -> R
go func() {
io.Copy(p.R, conn)
p.Stop()
}()
// copy from L -> conn
go func() {
io.Copy(conn, p.L)
p.Stop()
}()
// start and wait till all processing is done
p.Start()
p.Wait()
}
func print(m *msg.Msg) bool {
os.Stdout.Write(m.GetJSON())
return true
}
func event(ev *pipe.Event) bool {
switch ev.Type {
case pipe.EVENT_ESTABLISHED:
fmt.Printf("session established, capabilities: %s\n", ev.Pipe.Caps.String())
}
return true
}
JSON
bgpfix has full, bidirectional BGP to JSON translation support.
For example, below we connect to the Flowspec version of the great BGP Blackholing project by @LukaszBromirski. Note that the JSON schema might have changed a bit in the meantime:
pjf@pjf:~/bgp2json$ ./bgp2json -active -asn 65055 85.232.240.180:179 | jq .
[
"2023-08-18T11:33:41.298",
1,
"L",
"OPEN",
-1,
{
"bgp": 4,
"asn": 65055,
"id": "0.0.0.1",
"hold": 90,
"caps": {
"MP": [
"IPV4/UNICAST",
"IPV4/FLOWSPEC",
"IPV6/UNICAST",
"IPV6/FLOWSPEC"
],
"ROUTE_REFRESH": true,
"EXTENDED_MESSAGE": true,
"AS4": 65055
}
}
]
[
"2023-08-18T11:33:41.324",
1,
"R",
"OPEN",
56,
{
"bgp": 4,
"asn": 65055,
"id": "85.232.240.180",
"hold": 7200,
"caps": {
"MP": [
"IPV4/FLOWSPEC"
],
"ROUTE_REFRESH": true,
"EXTENDED_NEXTHOP": [
"IPV4/UNICAST/IPV6",
"IPV4/MULTICAST/IPV6",
"IPV4/MPLS_VPN/IPV6"
],
"AS4": 65055,
"PRE_ROUTE_REFRESH": true
}
}
]
[
"2023-08-18T11:33:41.325",
2,
"L",
"KEEPALIVE",
0,
null
]
[
"2023-08-18T11:33:41.348",
2,
"R",
"KEEPALIVE",
0,
null
]
[
"2023-08-18T11:33:46.352",
3,
"R",
"UPDATE",
316,
{
"attrs": {
"ORIGIN": {
"flags": "T",
"value": "IGP"
},
"ASPATH": {
"flags": "T",
"value": []
},
"LOCALPREF": {
"flags": "T",
"value": 100
},
"ORIGINATOR": {
"flags": "O",
"value": "85.232.240.170"
},
"CLUSTER_LIST": {
"flags": "O",
"value": [
"85.232.240.180"
]
},
"MP_REACH": {
"flags": "OX",
"value": {
"af": "IPV4/FLOWSPEC",
"nexthop": "192.0.2.1",
"rules": [
{
"SRC": "2.59.255.53/32",
"PROTO": [
{
"op": "==",
"val": 6
}
],
"PORT_DST": [
{
"op": "==",
"val": 25
}
]
},
/*** ... cut many lines ... ***/
]
}
},
"EXT_COMMUNITY": {
"flags": "OT",
"value": [
{
"type": "FLOW_RATE_BYTES",
"value": 0
}
]
}
}
}
]
[
"2023-08-18T11:33:46.455",
9,
"R",
"UPDATE",
10,
{
"attrs": {
"MP_UNREACH": {
"flags": "O",
"value": {
"af": "IPV4/FLOWSPEC",
"rules": []
}
}
}
}
]
Supported features (some partial/wip)
RFCs:
Drafts:
Author
Pawel Foremski @pforemski 2023-2024