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
114 changes: 58 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,29 @@ BGPSee is a multi-threaded BGP client for the CLI. Its goal is to allow you to q
- **Graceful shutdown** - Sends proper CEASE notifications when disconnecting
- **Lightweight** - No heavy dependencies, just libjansson for JSON support

## What It Doesn't Do

BGPSee is a passive observation tool, not a routing daemon. Compared to an RFC-compliant implementation (FRR, BIRD, OpenBGPd):

- **No route installation** - Does not install received routes into the kernel RIB/FIB
- **No route advertisement** - Does not originate or advertise any routes to peers
- **No best path selection** - Does not run the BGP decision process over received paths
- **No route policy** - No import/export filters, route-maps, or prefix-lists
- **No route reflection or confederations** - No RR client/non-client or sub-AS handling
- **No graceful restart** - Does not preserve forwarding state across restarts
- **No BFD integration** - No fast failure detection via BFD
- **No route redistribution** - Does not exchange routes with other protocols (OSPF, IS-IS, static)

In short, BGPSee establishes a session, receives UPDATEs, and outputs them as JSON. It never influences forwarding.

# Version

Current version is **0.0.8**
Current version is **0.0.9**

Major changes from **0.0.8** to **0.0.9**:
- VPNv4/MPLS-VPN address family parsing (RFC 4364)
- ADD-PATH capability infrastructure (RFC 7911)
- Configurable BGP hold time (`--hold-time` option, default 600s)

Major changes from **0.0.7** to **0.0.8**:
- 4-byte ASN support (RFC 6793)
Expand Down Expand Up @@ -50,85 +70,63 @@ Usage: bgpsee [options...] <peer> [<peer> ...]

# Example

Here's an example of *bgpsee* peering with an external router. You only see BGP messages recieved from the peer, not the messages sent by *bgpsee* iteslef.
Here's an example of an UPDATE recieved from the global routing table. You can see the AS path, next hop, aggregator, and community path attributes, and the NLRI associated with these.

```sh
./bgpsee -f json --asn 65001 fw1.i.foletta.xyz,65011,"External Router"
./bgpsee -f json --asn 65001 external.test,65011,"external"
```
```json
{
"recv_time": 1701025221,
"peer_name": "External Router",
"id": 0,
"type": "OPEN",
"length": 69,
"message": {
"version": 4,
"asn": 65011,
"hold_time": 180,
"router_id": "10.50.254.1",
"optional_parameter_length": 40
}
}
{
"recv_time": 1701025222,
"peer_name": "External Router",
"id": 1,
"type": "KEEPALIVE",
"length": 19,
"message": {}
}
{
"recv_time": 1701025844,
"peer_name": "External Router",
"id": 2336,
"time": 1769291475,
"peer_name": "external"
"id": 3722,
"type": "UPDATE",
"length": 97,
"length": 105,
"message": {
"withdrawn_route_length": 0,
"withdrawn_routes": [],
"path_attribute_length": 70,
"path_attribute_length": 66,
"path_attributes": {
"ORIGIN": "IGP",
"AS_PATH": {
"n_as_segments": 2,
"n_total_as": 1,
"n_as_segments": 1,
"n_total_as": 5,
"path_segments": [
{
"type": "AS_SEQUENCE",
"n_as": 5,
"asns": [
45270,
4764,
3356,
1299,
56595
]
},
{
"type": "AS_SET",
"n_as": 1,
"asns": [
23456
65011,
15694,
174,
3491,
10361
]
}
]
},
"NEXT_HOP": "10.50.254.1",
"NEXT_HOP": ""192.0.2.1,
"AGGREGATOR": {
"aggregator_asn": 56595,
"aggregator_ip": "192.124.193.146"
}
"aggregator_asn": 10361,
"aggregator_ip": "1.47.249.10"
},
"COMMUNITY": [
"174:21100",
"174:22010",
"15694:174",
"15694:1011"
]
},
"nlri": [
"5.172.183.0/24"
"69.191.207.0/24",
"69.191.183.0/24",
"69.191.182.0/24",
"69.191.84.0/24"
]
}
}
```

We see a connection to an external router, with the peer router sending an OPEN and an immediate KEEPALIVE signalling it accepts the OPEN message we sent. After 5 seconds the peer starts sending UPDATEs from all of the paths it has. This router has a full BGP table, and shown is one of the paths that contains most of the path attributes, including AGGREGATOR and an AS_PATH with AS segments.

# Architecture

See [ARCHITECTURE.md](ARCHITECTURE.md) for a detailed description of the threading model, key data structures, BGP FSM implementation, and message flow.
Expand Down Expand Up @@ -164,11 +162,15 @@ Run the test suite with:
make test
```

This runs 73 tests covering:
This runs 318 tests covering:
- Byte conversion functions (big-endian network byte order)
- BGP message parsing (OPEN, UPDATE, KEEPALIVE, NOTIFICATION)
- MP_REACH/MP_UNREACH (IPv6, EVPN, VPNv4)
- EVPN route types 1-5 (MAC/IP, Inclusive Multicast, IP Prefix, etc.)
- VPNv4/MPLS-VPN (RFC 4364)
- Capability negotiation encoding/decoding
- NOTIFICATION message generation
- Invalid input handling (security tests)
- Invalid input handling (truncated data, bad lengths)

For development, use the debug build which includes AddressSanitizer and UndefinedBehaviorSanitizer:
```bash
Expand All @@ -187,6 +189,6 @@ Please report bugs and crashes by [opening an issue](https://github.com/gregfole

# Roadmap

Top 3 items to add in future releases:
- VPNv4 Address Family (AFI: 1, SAFI: 1)
- EVPN Address Family (AFI: 25, SAFI: 70)
Top items to add in future releases:
- VPNv6 Address Family (AFI: 2, SAFI: 128)
- ADD-PATH (RFC 7911)
15 changes: 14 additions & 1 deletion src/bgp.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,18 @@ int set_bgp_reconnect(struct bgp_instance *i, unsigned int id, int enabled, int
return 0;
}

int set_bgp_hold_time(struct bgp_instance *i, unsigned int id, uint16_t hold_time) {
struct bgp_peer *peer;

if (!(peer = get_peer_from_instance(i, id))) {
return -1;
}

peer->peer_timers.conf_hold_time = hold_time;

return 0;
}


void free_bgp_peer(struct bgp_instance *i, unsigned int id) {
struct bgp_peer *peer;
Expand Down Expand Up @@ -830,7 +842,8 @@ int fsm_state_connect(struct bgp_peer *peer) {
open_asn = (*peer->local_asn > 65535) ? 23456 : (uint16_t)*peer->local_asn;

log_print(LOG_DEBUG, "Sending OPEN to peer %s\n", peer->name);
queue_and_send_open(peer, *peer->version, open_asn, 30, *peer->local_rid, caps);
queue_and_send_open(peer, *peer->version, open_asn,
peer->peer_timers.conf_hold_time, *peer->local_rid, caps);

start_timer(peer->local_timers, HoldTimer);
peer->fsm_state = OPENSENT;
Expand Down
1 change: 1 addition & 0 deletions src/bgp.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ unsigned int bgp_peer_source(struct bgp_instance *, unsigned int, const char *);

int set_bgp_output(struct bgp_instance *, unsigned int, enum bgp_output);
int set_bgp_reconnect(struct bgp_instance *, unsigned int, int enabled, int max_retries);
int set_bgp_hold_time(struct bgp_instance *, unsigned int, uint16_t hold_time);

void free_bgp_peer(struct bgp_instance *, unsigned int);
void free_all_bgp_peers(struct bgp_instance *);
Expand Down
59 changes: 59 additions & 0 deletions src/bgp_capability.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,62 @@ int bgp_capabilities_has_four_octet_asn(const struct bgp_capabilities *caps, uin

return 0;
}

int bgp_capabilities_get_addpath(const struct bgp_capabilities *caps,
struct bgp_addpath_config *config) {
struct list_head *pos;
struct bgp_capability *cap;
int found = 0;

if (!caps || !config) {
return 0;
}

/* Initialize config to no ADD-PATH support */
memset(config, 0, sizeof(*config));

list_for_each(pos, &caps->caps) {
cap = list_entry(pos, struct bgp_capability, list);
if (cap->code == BGP_CAP_ADD_PATH && cap->value) {
found = 1;
/* ADD-PATH format: AFI(2) + SAFI(1) + Send/Receive(1) per entry */
/* Total length must be multiple of 4 */
int entries = cap->length / 4;
for (int i = 0; i < entries; i++) {
uint16_t afi = uchar_be_to_uint16(cap->value + i * 4);
uint8_t safi = cap->value[i * 4 + 2];
uint8_t sr = cap->value[i * 4 + 3];

/* Map to our config structure */
if (afi == BGP_AFI_IPV4 && safi == BGP_SAFI_UNICAST) {
config->ipv4_unicast = sr;
} else if (afi == BGP_AFI_IPV6 && safi == BGP_SAFI_UNICAST) {
config->ipv6_unicast = sr;
} else if (afi == BGP_AFI_IPV4 && safi == BGP_SAFI_MPLS_VPN) {
config->vpnv4 = sr;
} else if (afi == BGP_AFI_L2VPN && safi == BGP_SAFI_EVPN) {
config->evpn = sr;
}
}
}
}

return found;
}

int bgp_capabilities_add_addpath(struct bgp_capabilities *caps,
uint16_t afi, uint8_t safi, uint8_t sr_flags) {
uint8_t value[4];

if (!caps) {
return -1;
}

/* ADD-PATH format: AFI(2) + SAFI(1) + Send/Receive(1) */
value[0] = (uint8_t)(afi >> 8);
value[1] = (uint8_t)(afi & 0xFF);
value[2] = safi;
value[3] = sr_flags;

return bgp_capabilities_add(caps, BGP_CAP_ADD_PATH, 4, value);
}
43 changes: 43 additions & 0 deletions src/bgp_capability.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ enum bgp_capability_code {
enum bgp_afi {
BGP_AFI_IPV4 = 1,
BGP_AFI_IPV6 = 2,
BGP_AFI_L2VPN = 25,
};

/*
Expand All @@ -54,6 +55,8 @@ enum bgp_safi {
BGP_SAFI_UNICAST = 1,
BGP_SAFI_MULTICAST = 2,
BGP_SAFI_MPLS = 4,
BGP_SAFI_EVPN = 70,
BGP_SAFI_MPLS_VPN = 128, /* MPLS-labeled VPN (RFC 4364) */
};

/*
Expand Down Expand Up @@ -132,3 +135,43 @@ const char *bgp_capability_name(uint8_t code);
* Returns 1 if present (and sets *asn to the 4-byte ASN), 0 if not present
*/
int bgp_capabilities_has_four_octet_asn(const struct bgp_capabilities *caps, uint32_t *asn);

/*
* ADD-PATH (RFC 7911) Support
*
* ADD-PATH capability allows multiple paths to be advertised for the same prefix.
* When negotiated, each NLRI is prefixed with a 4-byte Path Identifier.
*/

/* ADD-PATH Send/Receive flags */
enum bgp_addpath_sr {
BGP_ADDPATH_RECEIVE = 1, /* Peer can receive ADD-PATH */
BGP_ADDPATH_SEND = 2, /* Peer can send ADD-PATH */
BGP_ADDPATH_BOTH = 3, /* Peer can send and receive ADD-PATH */
};

/*
* ADD-PATH configuration per AFI/SAFI
* Stores what we can receive from the peer (peer's send capability)
*/
struct bgp_addpath_config {
uint8_t ipv4_unicast; /* BGP_ADDPATH_* flags for AFI 1, SAFI 1 */
uint8_t ipv6_unicast; /* BGP_ADDPATH_* flags for AFI 2, SAFI 1 */
uint8_t vpnv4; /* BGP_ADDPATH_* flags for AFI 1, SAFI 128 */
uint8_t evpn; /* BGP_ADDPATH_* flags for AFI 25, SAFI 70 */
};

/*
* Extract ADD-PATH configuration from capabilities
* Populates config with the send/receive flags for each AFI/SAFI
* Returns 1 if ADD-PATH capability found, 0 otherwise
*/
int bgp_capabilities_get_addpath(const struct bgp_capabilities *caps,
struct bgp_addpath_config *config);

/*
* Add ADD-PATH capability for an AFI/SAFI
* sr_flags is a combination of BGP_ADDPATH_RECEIVE and BGP_ADDPATH_SEND
*/
int bgp_capabilities_add_addpath(struct bgp_capabilities *caps,
uint16_t afi, uint8_t safi, uint8_t sr_flags);
Loading