Skip to content

Commit 3b9f9cb

Browse files
authored
Handle update messages (#14)
* Ignore update messages unless in established * Compare MED for routes from same AS only * Check update message size * Implement MRAI per peer * Test MRAI * Split file * Setup protoc
1 parent 1106d43 commit 3b9f9cb

File tree

18 files changed

+1049
-84
lines changed

18 files changed

+1049
-84
lines changed

Makefile

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
.PHONY: build clean run test fmt release setup
1+
.PHONY: all build clean run test fmt release setup
2+
3+
all: build
24

35
setup:
4-
command -v protoc >/dev/null 2>&1 || sudo apt-get update && sudo apt-get install -y protobuf-compiler
6+
./script/setup-protoc.sh
57

6-
build: fmt
8+
build: setup
79
cargo build --release
810

911
release:
@@ -12,10 +14,10 @@ release:
1214
clean:
1315
cargo clean
1416

15-
run:
17+
run: setup
1618
cargo run
1719

18-
test:
20+
test: setup
1921
cargo test
2022

2123
fmt:

README.md

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,45 @@
22

33
A BGP implementation written in Rust.
44

5-
## Structure
5+
## Quick Start
66

7-
- `core` - Core BGP protocol implementation
8-
- `daemon` - BGP daemon
9-
- `cli` - Command-line interface
7+
```bash
8+
# Build
9+
make
1010

11-
## Build
11+
# Run daemon
12+
./target/release/bgpggd -c config.yaml
1213

14+
# Use CLI (in another terminal)
15+
./target/release/bgpgg peer add 192.168.1.1:179
16+
./target/release/bgpgg peer list
1317
```
14-
make build
15-
```
1618

17-
## Test
19+
## Configuration
20+
21+
Edit `config.yaml`:
1822

23+
```yaml
24+
asn: 65000 # Your AS number
25+
listen_addr: "127.0.0.1:1790" # BGP listen address
26+
router_id: "1.1.1.1" # Router ID
27+
grpc_listen_addr: "[::1]:50051" # gRPC API address
1928
```
20-
make test
29+
30+
## Development
31+
32+
```bash
33+
make # Build
34+
make test # Run tests
35+
make fmt # Format code
2136
```
2237

38+
## Structure
39+
40+
- `core` - Core BGP protocol implementation
41+
- `daemon` - BGP daemon server
42+
- `cli` - Command-line interface
43+
2344
## License
2445

2546
Apache-2.0

core/src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ pub struct PeerConfig {
6868
/// Default false: collision detection is ignored when peer is in Established state.
6969
#[serde(default)]
7070
pub collision_detect_established_state: bool,
71+
/// MinRouteAdvertisementIntervalTimer - minimum seconds between route advertisements (RFC 4271 9.2.1.1).
72+
/// Default: 30 seconds for eBGP, 5 seconds for iBGP (or disabled for iBGP).
73+
#[serde(default)]
74+
pub min_route_advertisement_interval_secs: Option<u64>,
7175
}
7276

7377
fn default_idle_hold_time() -> Option<u64> {
@@ -110,6 +114,7 @@ impl Default for PeerConfig {
110114
max_prefix: None,
111115
send_notification_without_open: false,
112116
collision_detect_established_state: false,
117+
min_route_advertisement_interval_secs: None,
113118
}
114119
}
115120
}

core/src/grpc/service.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ fn proto_to_peer_config(proto: Option<ProtoSessionConfig>) -> PeerConfig {
8989
collision_detect_established_state: cfg
9090
.collision_detect_established_state
9191
.unwrap_or(defaults.collision_detect_established_state),
92+
min_route_advertisement_interval_secs: cfg.min_route_advertisement_interval_secs,
9293
}
9394
}
9495

core/src/peer/messages.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ impl Peer {
286286
mod tests {
287287
use super::*;
288288
use crate::bgp::msg::Message;
289+
use crate::bgp::msg_update::{AsPathSegment, AsPathSegmentType, Origin, UpdateMessage};
290+
use crate::peer::fsm::BgpState;
291+
use std::net::Ipv4Addr;
289292

290293
#[test]
291294
fn test_admin_shutdown_notification() {
@@ -298,4 +301,97 @@ mod tests {
298301
assert_eq!(bytes[1], 2); // AdministrativeShutdown subcode
299302
assert_eq!(bytes.len(), 2); // No data
300303
}
304+
305+
#[tokio::test]
306+
async fn test_update_rejected_in_non_established_states() {
307+
// RFC 4271 Section 9: "An UPDATE message may be received only in the Established state.
308+
// Receiving an UPDATE message in any other state is an error."
309+
use crate::peer::states::tests::create_test_peer_with_state;
310+
311+
let non_established_states = vec![
312+
BgpState::Connect,
313+
BgpState::Active,
314+
BgpState::OpenSent,
315+
BgpState::OpenConfirm,
316+
];
317+
318+
for state in non_established_states {
319+
let mut peer = create_test_peer_with_state(state).await;
320+
peer.config.send_notification_without_open = true;
321+
322+
let update = UpdateMessage::new(
323+
Origin::IGP,
324+
vec![AsPathSegment {
325+
segment_type: AsPathSegmentType::AsSequence,
326+
segment_len: 1,
327+
asn_list: vec![65001],
328+
}],
329+
Ipv4Addr::new(10, 0, 0, 1),
330+
vec![],
331+
None,
332+
None,
333+
false,
334+
vec![],
335+
);
336+
337+
let result = peer.handle_message(BgpMessage::Update(update)).await;
338+
339+
assert!(
340+
result.is_err(),
341+
"UPDATE should cause error in {:?} state",
342+
state
343+
);
344+
assert_eq!(
345+
peer.state(),
346+
BgpState::Idle,
347+
"Peer should transition to Idle after UPDATE in {:?}",
348+
state
349+
);
350+
assert_eq!(
351+
peer.statistics.notification_sent, 1,
352+
"FSM error NOTIFICATION should be sent in {:?}",
353+
state
354+
);
355+
}
356+
}
357+
358+
#[tokio::test]
359+
async fn test_update_accepted_in_established() {
360+
// RFC 4271 Section 9: UPDATE is processed normally in Established state
361+
use crate::peer::states::tests::create_test_peer_with_state;
362+
363+
let mut peer = create_test_peer_with_state(BgpState::Established).await;
364+
peer.fsm.timers.start_hold_timer();
365+
366+
let update = UpdateMessage::new(
367+
Origin::IGP,
368+
vec![AsPathSegment {
369+
segment_type: AsPathSegmentType::AsSequence,
370+
segment_len: 1,
371+
asn_list: vec![65001],
372+
}],
373+
Ipv4Addr::new(10, 0, 0, 1),
374+
vec![],
375+
None,
376+
None,
377+
false,
378+
vec![],
379+
);
380+
381+
let result = peer.handle_message(BgpMessage::Update(update)).await;
382+
383+
assert!(
384+
result.is_ok(),
385+
"UPDATE should be accepted in Established state"
386+
);
387+
assert_eq!(
388+
peer.state(),
389+
BgpState::Established,
390+
"Peer should remain in Established state"
391+
);
392+
assert_eq!(
393+
peer.statistics.notification_sent, 0,
394+
"No NOTIFICATION should be sent for valid UPDATE"
395+
);
396+
}
301397
}

core/src/peer/mod.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::config::PeerConfig;
1818
use crate::debug;
1919
use crate::rib::rib_in::AdjRibIn;
2020
use crate::server::{ConnectionType, ServerOp};
21+
use std::collections::VecDeque;
2122
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
2223
use std::time::{Duration, Instant};
2324

@@ -156,6 +157,12 @@ pub struct Peer {
156157
manually_stopped: bool,
157158
/// Timestamp when Established state was entered (for stability-based damping reset)
158159
established_at: Option<Instant>,
160+
/// RFC 4271 9.2.1.1: Last time we sent an UPDATE
161+
last_update_sent: Option<Instant>,
162+
/// Queued UPDATE messages waiting for MRAI timer
163+
pending_updates: VecDeque<UpdateMessage>,
164+
/// MinRouteAdvertisementIntervalTimer from config
165+
mrai_interval: Duration,
159166
}
160167

161168
impl Peer {
@@ -193,6 +200,11 @@ impl Peer {
193200
rib_in: AdjRibIn::new(),
194201
session_type: None,
195202
statistics: PeerStatistics::default(),
203+
mrai_interval: Duration::from_secs(
204+
config.min_route_advertisement_interval_secs.unwrap_or(0),
205+
),
206+
last_update_sent: None,
207+
pending_updates: VecDeque::new(),
196208
config,
197209
peer_rx,
198210
server_tx,
@@ -224,8 +236,13 @@ impl Peer {
224236
BgpState::Active => {
225237
self.handle_active_state().await;
226238
}
227-
BgpState::OpenSent | BgpState::OpenConfirm | BgpState::Established => {
228-
if self.handle_open_and_established().await {
239+
BgpState::OpenSent | BgpState::OpenConfirm => {
240+
if self.handle_open_states().await {
241+
return; // Shutdown requested
242+
}
243+
}
244+
BgpState::Established => {
245+
if self.handle_established().await {
229246
return; // Shutdown requested
230247
}
231248
}
@@ -334,6 +351,9 @@ pub mod test_helpers {
334351
conn_type: ConnectionType::Outgoing,
335352
manually_stopped: false,
336353
established_at: None,
354+
mrai_interval: Duration::from_secs(0),
355+
last_update_sent: None,
356+
pending_updates: VecDeque::new(),
337357
}
338358
}
339359
}

core/src/peer/state_active.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,27 @@ impl Peer {
170170
self.fsm.increment_connect_retry_counter();
171171
}
172172

173-
// RFC 4271 8.2.2: Any other events (8, 10-11, 13, 19, 25-28) in Active -> Idle
173+
// RFC 4271 8.2.2: Events 26-27 (KeepAlive, Update) in Active -> FSM Error
174+
(BgpState::Idle, FsmEvent::BgpKeepaliveReceived)
175+
| (BgpState::Idle, FsmEvent::BgpUpdateReceived) => {
176+
let _ = self
177+
.send_notification(NotifcationMessage::new(
178+
BgpError::FiniteStateMachineError,
179+
vec![],
180+
))
181+
.await;
182+
self.fsm.timers.stop_connect_retry();
183+
self.disconnect(true);
184+
self.fsm.increment_connect_retry_counter();
185+
return Err(PeerError::FsmError);
186+
}
187+
188+
// RFC 4271 8.2.2: Any other events (8, 10-11, 13, 19, 28) in Active -> Idle
174189
(BgpState::Idle, FsmEvent::AutomaticStop(_))
175190
| (BgpState::Idle, FsmEvent::HoldTimerExpires)
176191
| (BgpState::Idle, FsmEvent::KeepaliveTimerExpires)
177192
| (BgpState::Idle, FsmEvent::IdleHoldTimerExpires)
178193
| (BgpState::Idle, FsmEvent::BgpOpenReceived(_))
179-
| (BgpState::Idle, FsmEvent::BgpKeepaliveReceived)
180-
| (BgpState::Idle, FsmEvent::BgpUpdateReceived)
181194
| (BgpState::Idle, FsmEvent::BgpUpdateMsgErr(_)) => {
182195
self.fsm.timers.stop_connect_retry();
183196
self.disconnect(true);

0 commit comments

Comments
 (0)