Skip to content

Commit 41c8cb8

Browse files
committed
feat: conntrack new message type
Implemented the `New`/`IPCTNL_MSG_CT_NEW` conntrack message type. Added a test for adding new TCP/IPv4 conntrack entries. Signed-off-by: Shivang K Raghuvanshi <[email protected]>
1 parent 92b3d4f commit 41c8cb8

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

src/conntrack/message.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use netlink_packet_core::{
1010
#[derive(Debug, PartialEq, Eq, Clone)]
1111
#[non_exhaustive]
1212
pub enum ConntrackMessage {
13+
New(Vec<ConntrackAttribute>),
1314
Get(Vec<ConntrackAttribute>),
1415
Delete(Vec<ConntrackAttribute>),
1516
Other {
@@ -18,12 +19,14 @@ pub enum ConntrackMessage {
1819
},
1920
}
2021

22+
const IPCTNL_MSG_CT_NEW: u8 = 0;
2123
const IPCTNL_MSG_CT_GET: u8 = 1;
2224
const IPCTNL_MSG_CT_DELETE: u8 = 2;
2325

2426
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2527
#[non_exhaustive]
2628
pub enum ConntrackMessageType {
29+
New,
2730
Get,
2831
Delete,
2932
Other(u8),
@@ -32,6 +35,7 @@ pub enum ConntrackMessageType {
3235
impl From<u8> for ConntrackMessageType {
3336
fn from(value: u8) -> Self {
3437
match value {
38+
IPCTNL_MSG_CT_NEW => Self::New,
3539
IPCTNL_MSG_CT_GET => Self::Get,
3640
IPCTNL_MSG_CT_DELETE => Self::Delete,
3741
v => Self::Other(v),
@@ -42,6 +46,7 @@ impl From<u8> for ConntrackMessageType {
4246
impl From<ConntrackMessageType> for u8 {
4347
fn from(value: ConntrackMessageType) -> Self {
4448
match value {
49+
ConntrackMessageType::New => IPCTNL_MSG_CT_NEW,
4550
ConntrackMessageType::Get => IPCTNL_MSG_CT_GET,
4651
ConntrackMessageType::Delete => IPCTNL_MSG_CT_DELETE,
4752
ConntrackMessageType::Other(v) => v,
@@ -52,6 +57,7 @@ impl From<ConntrackMessageType> for u8 {
5257
impl ConntrackMessage {
5358
pub fn message_type(&self) -> ConntrackMessageType {
5459
match self {
60+
ConntrackMessage::New(_) => ConntrackMessageType::New,
5561
ConntrackMessage::Get(_) => ConntrackMessageType::Get,
5662
ConntrackMessage::Delete(_) => ConntrackMessageType::Delete,
5763
ConntrackMessage::Other { message_type, .. } => {
@@ -64,6 +70,9 @@ impl ConntrackMessage {
6470
impl Emitable for ConntrackMessage {
6571
fn buffer_len(&self) -> usize {
6672
match self {
73+
ConntrackMessage::New(attributes) => {
74+
attributes.as_slice().buffer_len()
75+
}
6776
ConntrackMessage::Get(attributes) => {
6877
attributes.as_slice().buffer_len()
6978
}
@@ -78,6 +87,9 @@ impl Emitable for ConntrackMessage {
7887

7988
fn emit(&self, buffer: &mut [u8]) {
8089
match self {
90+
ConntrackMessage::New(attributes) => {
91+
attributes.as_slice().emit(buffer)
92+
}
8193
ConntrackMessage::Get(attributes) => {
8294
attributes.as_slice().emit(buffer)
8395
}
@@ -99,6 +111,12 @@ impl<'a, T: AsRef<[u8]> + ?Sized>
99111
message_type: u8,
100112
) -> Result<Self, DecodeError> {
101113
Ok(match ConntrackMessageType::from(message_type) {
114+
ConntrackMessageType::New => {
115+
let attributes = buf.parse_all_nlas(|nla_buf| {
116+
ConntrackAttribute::parse(&nla_buf)
117+
})?;
118+
ConntrackMessage::New(attributes)
119+
}
102120
ConntrackMessageType::Get => {
103121
let attributes = buf.parse_all_nlas(|nla_buf| {
104122
ConntrackAttribute::parse(&nla_buf)

src/tests.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,99 @@ fn test_delete_conntrack_udp_ipv6() {
375375
expected
376376
);
377377
}
378+
379+
// wireshark capture of nlmon against command (netlink message header removed):
380+
// conntrack -I -p tcp --src 192.168.1.100 --dst 10.0.0.1 --sport 12345 --dport
381+
// 80 --state SYN_SENT --timeout 60
382+
#[test]
383+
fn test_new_conntrack() {
384+
let raw: Vec<u8> = vec![
385+
0x02, 0x00, 0x00, 0x00, 0x34, 0x00, 0x01, 0x80, 0x14, 0x00, 0x01, 0x80,
386+
0x08, 0x00, 0x01, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0x08, 0x00, 0x02, 0x00,
387+
0x0a, 0x00, 0x00, 0x01, 0x1c, 0x00, 0x02, 0x80, 0x05, 0x00, 0x01, 0x00,
388+
0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x30, 0x39, 0x00, 0x00,
389+
0x06, 0x00, 0x03, 0x00, 0x00, 0x50, 0x00, 0x00, 0x34, 0x00, 0x02, 0x80,
390+
0x14, 0x00, 0x01, 0x80, 0x08, 0x00, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x01,
391+
0x08, 0x00, 0x02, 0x00, 0xc0, 0xa8, 0x01, 0x64, 0x1c, 0x00, 0x02, 0x80,
392+
0x05, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00,
393+
0x00, 0x50, 0x00, 0x00, 0x06, 0x00, 0x03, 0x00, 0x30, 0x39, 0x00, 0x00,
394+
0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x20, 0x00, 0x04, 0x80,
395+
0x1c, 0x00, 0x01, 0x80, 0x05, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
396+
0x06, 0x00, 0x04, 0x00, 0x0a, 0x0a, 0x00, 0x00, 0x06, 0x00, 0x05, 0x00,
397+
0x0a, 0x0a, 0x00, 0x00,
398+
];
399+
400+
let orig_src_addr =
401+
IPTuple::SourceAddress(IpAddr::V4("192.168.1.100".parse().unwrap()));
402+
let orig_dst_addr =
403+
IPTuple::DestinationAddress(IpAddr::V4("10.0.0.1".parse().unwrap()));
404+
405+
let orig_proto_num = ProtoTuple::Protocol(Protocol::Tcp);
406+
let orig_src_port = ProtoTuple::SourcePort(12345);
407+
let orig_dst_port = ProtoTuple::DestinationPort(80);
408+
409+
let orig_ip_tuple = Tuple::Ip(vec![orig_src_addr, orig_dst_addr]);
410+
let orig_proto_tuple =
411+
Tuple::Proto(vec![orig_proto_num, orig_src_port, orig_dst_port]);
412+
413+
let reply_src_addr =
414+
IPTuple::SourceAddress(IpAddr::V4("10.0.0.1".parse().unwrap()));
415+
let reply_dst_addr = IPTuple::DestinationAddress(IpAddr::V4(
416+
"192.168.1.100".parse().unwrap(),
417+
));
418+
419+
let reply_proto_num = ProtoTuple::Protocol(Protocol::Tcp);
420+
let reply_src_port = ProtoTuple::SourcePort(80);
421+
let reply_dst_port = ProtoTuple::DestinationPort(12345);
422+
423+
let reply_ip_tuple = Tuple::Ip(vec![reply_src_addr, reply_dst_addr]);
424+
let reply_proto_tuple =
425+
Tuple::Proto(vec![reply_proto_num, reply_src_port, reply_dst_port]);
426+
427+
let timeout = 60;
428+
429+
let proto_info = ProtoInfo::TCP(vec![
430+
ProtoInfoTCP::State(1),
431+
ProtoInfoTCP::OriginalFlags(TCPFlags {
432+
flags: 10,
433+
mask: 10,
434+
}),
435+
ProtoInfoTCP::ReplyFlags(TCPFlags {
436+
flags: 10,
437+
mask: 10,
438+
}),
439+
]);
440+
441+
let attributes = vec![
442+
ConntrackAttribute::CtaTupleOrig(vec![orig_ip_tuple, orig_proto_tuple]),
443+
ConntrackAttribute::CtaTupleReply(vec![
444+
reply_ip_tuple,
445+
reply_proto_tuple,
446+
]),
447+
ConntrackAttribute::CtaTimeout(timeout),
448+
ConntrackAttribute::CtaProtoInfo(vec![proto_info]),
449+
];
450+
451+
let expected: NetfilterMessage = NetfilterMessage::new(
452+
NetfilterHeader::new(ProtoFamily::ProtoIPv4, 0, 0),
453+
ConntrackMessage::New(attributes),
454+
);
455+
456+
let mut buffer = vec![0; expected.buffer_len()];
457+
expected.emit(&mut buffer);
458+
459+
// Check if the serialization was correct
460+
assert_eq!(buffer, raw);
461+
462+
let message_type = ((u8::from(Subsystem::Conntrack) as u16) << 8)
463+
| (u8::from(ConntrackMessageType::New) as u16);
464+
// Check if the deserialization was correct
465+
assert_eq!(
466+
NetfilterMessage::parse_with_param(
467+
&NetfilterBuffer::new(&raw),
468+
message_type
469+
)
470+
.unwrap(),
471+
expected
472+
);
473+
}

0 commit comments

Comments
 (0)