Skip to content

Commit 87890e9

Browse files
authored
feat(datapath): add TTL support for message forwarding (agntcy#1714)
# Description Add a hop-by-hop TTL (time-to-live) field to the SLIMHeader that controls how many times a message can be forwarded between SLIM nodes Fixes: agntcy#1715 ## Type of Change - [ ] Bugfix - [x] New Feature - [ ] Breaking Change - [ ] Refactor - [ ] Documentation - [ ] Other (please describe) ## Checklist - [x] I have read the [contributing guidelines](/agntcy/repo-template/blob/main/CONTRIBUTING.md) - [x] Existing issues have been referenced (where applicable) - [x] I have verified this change is not present in other open pull requests - [x] Functionality is documented - [x] All code style checks pass - [x] New code contribution is covered by automated tests - [x] All new and existing tests pass Signed-off-by: Mauro Sardara <msardara@cisco.com>
1 parent 54ea5b7 commit 87890e9

8 files changed

Lines changed: 86 additions & 22 deletions

File tree

data-plane/core/datapath/benches/name_benchmark.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use criterion::{Criterion, black_box, criterion_group, criterion_main};
55
use slim_datapath::api::{EncodedName, NameId, ProtoName, SlimHeader, StringName};
6+
use slim_datapath::messages::utils::DEFAULT_TTL;
67
use slim_datapath::tables::SubscriptionTable;
78
use slim_datapath::tables::subscription_table::SubscriptionTableImpl;
89
use slim_datapath::tables::{ConnType, MatchFilter};
@@ -38,6 +39,7 @@ fn make_slim_header() -> SlimHeader {
3839
incoming_conn: None,
3940
error: None,
4041
header_mac: None,
42+
ttl: DEFAULT_TTL,
4143
}
4244
}
4345

data-plane/core/datapath/src/api/gen/dataplane.proto.v1.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,17 @@ pub struct SlimHeader {
8484
pub fanout: u32,
8585
#[prost(string, tag = "5")]
8686
pub version: ::prost::alloc::string::String,
87-
#[prost(uint64, optional, tag = "6")]
88-
pub recv_from: ::core::option::Option<u64>,
87+
#[prost(uint32, tag = "6")]
88+
pub ttl: u32,
8989
#[prost(uint64, optional, tag = "7")]
90-
pub forward_to: ::core::option::Option<u64>,
90+
pub recv_from: ::core::option::Option<u64>,
9191
#[prost(uint64, optional, tag = "8")]
92+
pub forward_to: ::core::option::Option<u64>,
93+
#[prost(uint64, optional, tag = "9")]
9294
pub incoming_conn: ::core::option::Option<u64>,
93-
#[prost(bool, optional, tag = "9")]
95+
#[prost(bool, optional, tag = "10")]
9496
pub error: ::core::option::Option<bool>,
95-
#[prost(bytes = "vec", optional, tag = "10")]
97+
#[prost(bytes = "vec", optional, tag = "11")]
9698
pub header_mac: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
9799
}
98100
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]

data-plane/core/datapath/src/errors.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ pub enum DataPathError {
9090

9191
#[error("inter-node ephemeral key generation failed")]
9292
LinkKeyGeneration,
93+
94+
#[error("message TTL expired")]
95+
TtlExpired,
9396
}
9497

9598
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]

data-plane/core/datapath/src/header_mac.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,16 @@ fn preimage_upper_bound(header: &SlimHeader) -> usize {
148148
/// Byte size of error: Option<bool> encoded by [`push_bool_opt`]
149149
const ERROR_SIZE: usize = 2;
150150

151+
/// Byte size of TTL: u32 serialized by [`to_le_bytes`]
152+
const TTL_SIZE: usize = 4;
153+
151154
DOMAIN_V1.len()
152155
+ LINK_UUID_SIZE
153156
+ FANOUT_SIZE
154157
+ RECV_FROM_SIZE
155158
+ FORWARD_TO_SIZE
156159
+ ERROR_SIZE
160+
+ TTL_SIZE
157161
+ encoded_name_upper_bound(&header.source)
158162
+ encoded_name_upper_bound(&header.destination)
159163
}
@@ -268,12 +272,14 @@ fn write_preimage(buf: &mut Vec<u8>, hdr: &SlimHeader, link_uuid: &[u8; 16]) {
268272
push_u64_opt(buf, hdr.recv_from);
269273
push_u64_opt(buf, hdr.forward_to);
270274
push_bool_opt(buf, hdr.error);
275+
buf.extend_from_slice(&hdr.ttl.to_le_bytes());
271276
}
272277

273278
#[cfg(test)]
274279
mod tests {
275280
use super::*;
276281
use crate::api::proto::dataplane::v1::{EncodedName, Name, NameId, StringName};
282+
use crate::messages::utils::DEFAULT_TTL;
277283

278284
fn test_key() -> Vec<u8> {
279285
b"01234567890123456789012345678901".to_vec()
@@ -315,6 +321,7 @@ mod tests {
315321
incoming_conn: Some(999),
316322
error: Some(false),
317323
header_mac: None,
324+
ttl: DEFAULT_TTL,
318325
}
319326
}
320327

data-plane/core/datapath/src/link_ecdh.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub fn derive_header_mac_from_ecdh(
5959
#[cfg(test)]
6060
mod tests {
6161
use super::*;
62+
use crate::messages::utils::DEFAULT_TTL;
6263

6364
#[test]
6465
fn ecdh_hkdf_matches_between_initiator_and_responder() {
@@ -79,6 +80,7 @@ mod tests {
7980
incoming_conn: None,
8081
error: None,
8182
header_mac: None,
83+
ttl: DEFAULT_TTL,
8284
};
8385
a.sign_slim_header(&mut h, &lid).unwrap();
8486
b.verify_slim_header(&h, &lid).unwrap();

data-plane/core/datapath/src/message_processing.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,12 @@ impl MessageProcessor {
13361336
// add incoming connection to the SLIM header
13371337
msg.set_incoming_conn(Some(conn_index));
13381338

1339+
// TTL processing: decrement for remote messages (hop-by-hop)
1340+
if !category.is_local() && msg.decrement_ttl() == 0 {
1341+
debug!(%conn_index, "dropping message: TTL expired");
1342+
return Err(DataPathError::TtlExpired);
1343+
}
1344+
13391345
#[cfg(feature = "otel_tracing")]
13401346
otel_tracing::prepare_inbound_msg(
13411347
&mut msg,

data-plane/core/datapath/src/messages/utils.rs

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ pub const FALSE_VAL: &str = "FALSE";
5757
/// Value: Half of u32::MAX to allow a separate ID space for out-of-band messages.
5858
pub const MAX_PUBLISH_ID: u32 = u32::MAX / 2;
5959

60+
/// Default TTL value for messages that do not have an explicit TTL set.
61+
pub const DEFAULT_TTL: u32 = 16;
62+
6063
#[derive(Error, Debug, PartialEq)]
6164
pub enum MessageError {
6265
#[error("SLIM header not found")]
@@ -181,6 +184,7 @@ pub struct SlimHeaderFlags {
181184
pub forward_to: Option<u64>,
182185
pub incoming_conn: Option<u64>,
183186
pub error: Option<bool>,
187+
pub ttl: u32,
184188
}
185189

186190
impl Default for SlimHeaderFlags {
@@ -191,6 +195,7 @@ impl Default for SlimHeaderFlags {
191195
forward_to: None,
192196
incoming_conn: None,
193197
error: None,
198+
ttl: DEFAULT_TTL,
194199
}
195200
}
196201
}
@@ -199,8 +204,8 @@ impl Display for SlimHeaderFlags {
199204
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200205
write!(
201206
f,
202-
"fanout: {}, recv_from: {:?}, forward_to: {:?}, incoming_conn: {:?}, error: {:?}",
203-
self.fanout, self.recv_from, self.forward_to, self.incoming_conn, self.error
207+
"fanout: {}, recv_from: {:?}, forward_to: {:?}, incoming_conn: {:?}, error: {:?}, ttl: {:?}",
208+
self.fanout, self.recv_from, self.forward_to, self.incoming_conn, self.error, self.ttl
204209
)
205210
}
206211
}
@@ -219,6 +224,7 @@ impl SlimHeaderFlags {
219224
forward_to,
220225
incoming_conn,
221226
error,
227+
ttl: DEFAULT_TTL,
222228
}
223229
}
224230

@@ -253,6 +259,10 @@ impl SlimHeaderFlags {
253259
..self
254260
}
255261
}
262+
263+
pub fn with_ttl(self, ttl: u32) -> Self {
264+
Self { ttl, ..self }
265+
}
256266
}
257267

258268
/// SLIM Header
@@ -277,6 +287,7 @@ impl SlimHeader {
277287
incoming_conn: flags.incoming_conn,
278288
error: flags.error,
279289
header_mac: None,
290+
ttl: flags.ttl,
280291
}
281292
}
282293

@@ -365,6 +376,20 @@ impl SlimHeader {
365376
self.error = error;
366377
}
367378

379+
pub fn get_ttl(&self) -> u32 {
380+
self.ttl
381+
}
382+
383+
pub fn set_ttl(&mut self, ttl: u32) {
384+
self.ttl = ttl;
385+
}
386+
387+
/// Decrements TTL by 1. Returns the new value.
388+
pub fn decrement_ttl(&mut self) -> u32 {
389+
self.ttl = self.ttl.saturating_sub(1);
390+
self.ttl
391+
}
392+
368393
// returns (incoming, recv_from, forward_to) for subscription processing
369394
pub(crate) fn get_connections(&self) -> (u64, Option<u64>, Option<u64>) {
370395
// when calling this function, incoming connection is set
@@ -865,6 +890,19 @@ impl ProtoMessage {
865890
self.get_slim_header_mut().set_error_flag(error);
866891
}
867892

893+
pub fn get_ttl(&self) -> u32 {
894+
self.get_slim_header().get_ttl()
895+
}
896+
897+
pub fn set_ttl(&mut self, ttl: u32) {
898+
self.get_slim_header_mut().set_ttl(ttl);
899+
}
900+
901+
/// Decrements TTL by 1. Returns the new value.
902+
pub fn decrement_ttl(&mut self) -> u32 {
903+
self.get_slim_header_mut().decrement_ttl()
904+
}
905+
868906
pub fn set_session_message_type(&mut self, message_type: SessionMessageType) {
869907
self.get_session_header_mut()
870908
.set_session_message_type(message_type);
@@ -1448,36 +1486,37 @@ impl ProtoMessageBuilder {
14481486

14491487
/// Sets the fanout value
14501488
pub fn fanout(mut self, fanout: u32) -> Self {
1451-
let flags = self.flags.take().unwrap_or_default();
1452-
self.flags = Some(flags.with_fanout(fanout));
1489+
self.flags.get_or_insert_default().fanout = fanout;
14531490
self
14541491
}
14551492

14561493
/// Sets the recv_from connection
14571494
pub fn recv_from(mut self, recv_from: u64) -> Self {
1458-
let flags = self.flags.take().unwrap_or_default();
1459-
self.flags = Some(flags.with_recv_from(recv_from));
1495+
self.flags.get_or_insert_default().recv_from = Some(recv_from);
14601496
self
14611497
}
14621498

14631499
/// Sets the forward_to connection
14641500
pub fn forward_to(mut self, forward_to: u64) -> Self {
1465-
let flags = self.flags.take().unwrap_or_default();
1466-
self.flags = Some(flags.with_forward_to(forward_to));
1501+
self.flags.get_or_insert_default().forward_to = Some(forward_to);
14671502
self
14681503
}
14691504

14701505
/// Sets the incoming connection
14711506
pub fn incoming_conn(mut self, incoming_conn: u64) -> Self {
1472-
let flags = self.flags.take().unwrap_or_default();
1473-
self.flags = Some(flags.with_incoming_conn(incoming_conn));
1507+
self.flags.get_or_insert_default().incoming_conn = Some(incoming_conn);
14741508
self
14751509
}
14761510

14771511
/// Sets the error flag
14781512
pub fn error(mut self, error: bool) -> Self {
1479-
let flags = self.flags.take().unwrap_or_default();
1480-
self.flags = Some(flags.with_error(error));
1513+
self.flags.get_or_insert_default().error = Some(error);
1514+
self
1515+
}
1516+
1517+
/// Sets the TTL (time-to-live) value
1518+
pub fn ttl(mut self, ttl: u32) -> Self {
1519+
self.flags.get_or_insert_default().ttl = ttl;
14811520
self
14821521
}
14831522

@@ -1547,6 +1586,7 @@ impl ProtoMessageBuilder {
15471586
forward_to: header.forward_to,
15481587
incoming_conn: header.incoming_conn,
15491588
error: header.error,
1589+
ttl: header.ttl,
15501590
};
15511591
self.flags = Some(flags);
15521592
self
@@ -2011,6 +2051,7 @@ mod tests {
20112051
incoming_conn: None,
20122052
error: None,
20132053
header_mac: None,
2054+
ttl: DEFAULT_TTL,
20142055
};
20152056

20162057
// the operations to retrieve source and destination should fail with panic

proto/data-plane/v1/data_plane.proto

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,12 @@ message SLIMHeader {
6060
string identity = 3;
6161
uint32 fanout = 4;
6262
string version = 5;
63-
optional uint64 recv_from = 6;
64-
optional uint64 forward_to = 7;
65-
optional uint64 incoming_conn = 8;
66-
optional bool error = 9;
67-
optional bytes header_mac = 10;
63+
uint32 ttl = 6;
64+
optional uint64 recv_from = 7;
65+
optional uint64 forward_to = 8;
66+
optional uint64 incoming_conn = 9;
67+
optional bool error = 10;
68+
optional bytes header_mac = 11;
6869
}
6970

7071
message SessionHeader {

0 commit comments

Comments
 (0)