Skip to content

Commit b1b6e4e

Browse files
committed
ntp: convert reference_id to buffer and add keyword
Store the NTP reference ID as raw network-order bytes so it can be exposed as a sticky buffer and matched with payload keywords. The reference ID is often a 4 character string, or an IP address and not just an integer identifier. Updates the log reference ID to be a string of colon separated hex digits as this matches what tshark does. Ticket: OISF#8488
1 parent 529dad3 commit b1b6e4e

5 files changed

Lines changed: 80 additions & 7 deletions

File tree

doc/userguide/rules/ntp-keywords.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@ NTP Keywords
33

44
.. role:: example-rule-options
55

6+
ntp.reference_id
7+
****************
8+
9+
``ntp.reference_id`` is a sticky buffer exposing the raw 4-byte NTP reference ID.
10+
This allows direct :ref:`payload keyword <payload-keywords>` matches such as
11+
ASCII clock identifiers like ``RATE`` and binary IPv4 values.
12+
13+
Examples::
14+
15+
ntp.reference_id; content:"RATE";
16+
ntp.reference_id; content:"|0a 00 00 01|";
17+
18+
Signature Example:
19+
20+
.. container:: example-rule
21+
22+
alert ntp any any -> any any (msg:"NTP reference ID RATE"; ntp.reference_id; content:"RATE"; sid:4; rev:1;)
23+
624
ntp.mode
725
********
826

etc/schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5578,8 +5578,8 @@
55785578
"description": "The mode of the NTP message"
55795579
},
55805580
"reference_id": {
5581-
"type": "integer",
5582-
"description": "Identifies specific server or reference clock"
5581+
"type": "string",
5582+
"description": "Identifies specific server or reference clock as a colon-separated 4-byte hex string"
55835583
},
55845584
"stratum": {
55855585
"type": "integer",

rust/src/ntp/detect.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER};
2020
use crate::detect::uint::{
2121
detect_parse_uint_enum, DetectUintData, SCDetectU8Free, SCDetectU8Match, SCDetectU8Parse,
2222
};
23-
use crate::detect::{SIGMATCH_INFO_ENUM_UINT, SIGMATCH_INFO_UINT8};
23+
use crate::detect::{
24+
helper_keyword_register_sticky_buffer, SigTableElmtStickyBuffer, SIGMATCH_INFO_ENUM_UINT,
25+
SIGMATCH_INFO_UINT8,
26+
};
2427
use std::ffi::CStr;
2528
use std::os::raw::{c_int, c_void};
2629
use suricata_sys::sys::{
27-
DetectEngineCtx, DetectEngineThreadCtx, Flow, SCDetectHelperBufferProgressRegister,
30+
DetectEngineCtx, DetectEngineThreadCtx, Flow, SCDetectBufferSetActiveList,
31+
SCDetectHelperBufferProgressMpmRegister, SCDetectHelperBufferProgressRegister,
2832
SCDetectHelperKeywordRegister, SCDetectSignatureSetAppProto, SCSigMatchAppendSMToList,
2933
SCSigTableAppLiteElmt, SigMatchCtx, Signature,
3034
};
@@ -35,6 +39,7 @@ static mut G_NTP_MODE_KW_ID: u16 = 0;
3539
static mut G_NTP_MODE_BUFFER_ID: c_int = 0;
3640
static mut G_NTP_STRATUM_KW_ID: u16 = 0;
3741
static mut G_NTP_STRATUM_BUFFER_ID: c_int = 0;
42+
static mut G_NTP_REFERENCE_ID_BUFFER_ID: c_int = 0;
3843

3944
#[derive(Clone, Debug, PartialEq, EnumStringU8)]
4045
#[repr(u8)]
@@ -167,6 +172,27 @@ unsafe extern "C" fn ntp_detect_stratum_match(
167172
return SCDetectU8Match(tx.stratum, ctx);
168173
}
169174

175+
unsafe extern "C" fn ntp_detect_reference_id_setup(
176+
de: *mut DetectEngineCtx, s: *mut Signature, _raw: *const std::os::raw::c_char,
177+
) -> c_int {
178+
if SCDetectSignatureSetAppProto(s, ALPROTO_NTP) != 0 {
179+
return -1;
180+
}
181+
if SCDetectBufferSetActiveList(de, s, G_NTP_REFERENCE_ID_BUFFER_ID) < 0 {
182+
return -1;
183+
}
184+
return 0;
185+
}
186+
187+
unsafe extern "C" fn ntp_detect_reference_id_get_data(
188+
tx: *const c_void, _flow_flags: u8, buffer: *mut *const u8, buffer_len: *mut u32,
189+
) -> bool {
190+
let tx = cast_pointer!(tx, NTPTransaction);
191+
*buffer = tx.reference_id.as_ptr();
192+
*buffer_len = tx.reference_id.len() as u32;
193+
true
194+
}
195+
170196
pub(super) unsafe extern "C" fn detect_ntp_register() {
171197
let kw = SCSigTableAppLiteElmt {
172198
name: b"ntp.version\0".as_ptr() as *const libc::c_char,
@@ -218,12 +244,29 @@ pub(super) unsafe extern "C" fn detect_ntp_register() {
218244
STREAM_TOSERVER | STREAM_TOCLIENT,
219245
1,
220246
);
247+
248+
let kw = SigTableElmtStickyBuffer {
249+
name: String::from("ntp.reference_id"),
250+
desc: String::from("sticky buffer to match on the NTP reference ID"),
251+
url: String::from("/rules/ntp-keywords.html#ntp-reference-id"),
252+
setup: ntp_detect_reference_id_setup,
253+
};
254+
let _g_ntp_reference_id_kw_id = helper_keyword_register_sticky_buffer(&kw);
255+
G_NTP_REFERENCE_ID_BUFFER_ID = SCDetectHelperBufferProgressMpmRegister(
256+
b"ntp.reference_id\0".as_ptr() as *const libc::c_char,
257+
b"NTP reference ID\0".as_ptr() as *const libc::c_char,
258+
ALPROTO_NTP,
259+
STREAM_TOSERVER | STREAM_TOCLIENT,
260+
Some(ntp_detect_reference_id_get_data),
261+
1,
262+
);
221263
}
222264

223265
#[cfg(test)]
224266
mod test {
225267
use super::*;
226268
use crate::detect::uint::DetectUintMode;
269+
use crate::direction::Direction;
227270

228271
#[test]
229272
fn test_ntp_parse_known_mode_strings() {
@@ -273,4 +316,10 @@ mod test {
273316
assert!(detect_parse_uint_enum::<u8, NTPMode>("symmetric_private").is_none());
274317
assert!(detect_parse_uint_enum::<u8, NTPMode>("256").is_none());
275318
}
319+
320+
#[test]
321+
fn test_ntp_reference_id_is_network_order_bytes() {
322+
let tx = NTPTransaction::new(Direction::ToServer, 1, 0x5241_5445);
323+
assert_eq!(&tx.reference_id, b"RATE");
324+
}
276325
}

rust/src/ntp/log.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ fn log(jb: &mut JsonBuilder, tx: &NTPTransaction) -> Result<(), JsonError> {
2323
jb.set_uint("version", tx.version)?;
2424
jb.set_uint("mode", tx.mode)?;
2525
jb.set_uint("stratum", tx.stratum)?;
26-
jb.set_uint("reference_id", tx.reference_id)?;
26+
jb.set_string(
27+
"reference_id",
28+
&format!(
29+
"{:02x}:{:02x}:{:02x}:{:02x}",
30+
tx.reference_id[0], tx.reference_id[1], tx.reference_id[2], tx.reference_id[3]
31+
),
32+
)?;
2733
jb.close()?;
2834
Ok(())
2935
}

rust/src/ntp/ntp.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub struct NTPState {
6060
#[derive(Debug, Default)]
6161
pub struct NTPTransaction {
6262
/// The NTP reference ID
63-
pub reference_id: u32,
63+
pub reference_id: [u8; 4],
6464

6565
pub version: u8,
6666
pub mode: u8,
@@ -169,7 +169,7 @@ impl NTPState {
169169
impl NTPTransaction {
170170
pub fn new(direction: Direction, id: u64, reference_id: u32) -> NTPTransaction {
171171
NTPTransaction {
172-
reference_id,
172+
reference_id: reference_id.to_be_bytes(),
173173
id,
174174
tx_data: applayer::AppLayerTxData::for_direction(direction),
175175
..Default::default()

0 commit comments

Comments
 (0)