mirror of https://github.com/OISF/suricata
detect: add vlan.id keyword
vlan.id matches on Virtual Local Area Network IDs It is an unsigned 16-bit integer Valid range = [0-4095] Supports prefiltering Ticket: #1065pull/12393/head
parent
637708acb4
commit
b1c2643c87
@ -0,0 +1,85 @@
|
|||||||
|
VLAN Keywords
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. role:: example-rule-action
|
||||||
|
.. role:: example-rule-header
|
||||||
|
.. role:: example-rule-options
|
||||||
|
.. role:: example-rule-emphasis
|
||||||
|
|
||||||
|
vlan.id
|
||||||
|
-------
|
||||||
|
|
||||||
|
Suricata has a ``vlan.id`` keyword that can be used in signatures to identify
|
||||||
|
and filter network packets based on Virtual Local Area Network IDs. By default,
|
||||||
|
it matches all layers if a packet contains multiple VLAN layers. However, if a
|
||||||
|
specific layer is defined, it will only match that layer.
|
||||||
|
|
||||||
|
Syntax::
|
||||||
|
|
||||||
|
vlan.id: [op]id[,layer];
|
||||||
|
|
||||||
|
The id can be matched exactly, or compared using the ``op`` setting::
|
||||||
|
|
||||||
|
vlan.id:300 # exactly 300
|
||||||
|
vlan.id:<300,0 # smaller than 300 at layer 0
|
||||||
|
vlan.id:>=200,1 # greater or equal than 200 at layer 1
|
||||||
|
|
||||||
|
vlan.id uses :ref:`unsigned 16-bit integer <rules-integer-keywords>`.
|
||||||
|
|
||||||
|
The valid range for VLAN id values is ``0 - 4095``.
|
||||||
|
|
||||||
|
This keyword also supports ``all`` and ``any`` as arguments for ``layer``.
|
||||||
|
``all`` matches only if all VLAN layers match and ``any`` matches with any layer.
|
||||||
|
|
||||||
|
.. table:: **Layer values for vlan.id keyword**
|
||||||
|
|
||||||
|
=============== ================================================
|
||||||
|
Value Description
|
||||||
|
=============== ================================================
|
||||||
|
[default] Match with any layer
|
||||||
|
0 - 2 Match specific layer
|
||||||
|
``-3`` - ``-1`` Match specific layer with back to front indexing
|
||||||
|
all Match only if all layers match
|
||||||
|
any Match with any layer
|
||||||
|
=============== ================================================
|
||||||
|
|
||||||
|
This small illustration shows how indexing works for vlan.id::
|
||||||
|
|
||||||
|
[ethernet]
|
||||||
|
[vlan 666 (index 0 and -2)]
|
||||||
|
[vlan 123 (index 1 and -1)]
|
||||||
|
[ipv4]
|
||||||
|
[udp]
|
||||||
|
|
||||||
|
Examples
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
Example of a signature that would alert if any of the VLAN IDs is equal to 300:
|
||||||
|
|
||||||
|
.. container:: example-rule
|
||||||
|
|
||||||
|
alert ip any any -> any any (msg:"Vlan ID is equal to 300"; :example-rule-emphasis:`vlan.id:300;` sid:1;)
|
||||||
|
|
||||||
|
Example of a signature that would alert if the VLAN ID at layer 1 is equal to 300:
|
||||||
|
|
||||||
|
.. container:: example-rule
|
||||||
|
|
||||||
|
alert ip any any -> any any (msg:"Vlan ID is equal to 300 at layer 1"; :example-rule-emphasis:`vlan.id:300,1;` sid:1;)
|
||||||
|
|
||||||
|
Example of a signature that would alert if the VLAN ID at the last layer is equal to 400:
|
||||||
|
|
||||||
|
.. container:: example-rule
|
||||||
|
|
||||||
|
alert ip any any -> any any (msg:"Vlan ID is equal to 400 at the last layer"; :example-rule-emphasis:`vlan.id:400,-1;` sid:1;)
|
||||||
|
|
||||||
|
Example of a signature that would alert only if all the VLAN IDs are greater than 100:
|
||||||
|
|
||||||
|
.. container:: example-rule
|
||||||
|
|
||||||
|
alert ip any any -> any any (msg:"All Vlan IDs are greater than 100"; :example-rule-emphasis:`vlan.id:>100,all;` sid:1;)
|
||||||
|
|
||||||
|
It is also possible to use the vlan.id content as a fast_pattern by using the ``prefilter`` keyword, as shown in the following example.
|
||||||
|
|
||||||
|
.. container:: example-rule
|
||||||
|
|
||||||
|
alert ip any any -> any any (msg:"Vlan ID is equal to 200 at layer 1"; :example-rule-emphasis:`vlan.id:200,1; prefilter;` sid:1;)
|
@ -0,0 +1,200 @@
|
|||||||
|
/* Copyright (C) 2024 Open Information Security Foundation
|
||||||
|
*
|
||||||
|
* You can copy, redistribute or modify this Program under the terms of
|
||||||
|
* the GNU General Public License version 2 as published by the Free
|
||||||
|
* Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* version 2 along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::uint::{detect_parse_uint, DetectUintData};
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub const DETECT_VLAN_ID_ANY: i8 = i8::MIN;
|
||||||
|
pub const DETECT_VLAN_ID_ALL: i8 = i8::MAX;
|
||||||
|
pub static VLAN_MAX_LAYERS: i8 = 3;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
/// This data structure is also used in detect-vlan.c
|
||||||
|
pub struct DetectVlanIdData {
|
||||||
|
/// Vlan id
|
||||||
|
pub du16: DetectUintData<u16>,
|
||||||
|
/// Layer can be DETECT_VLAN_ID_ANY to match with any vlan layer
|
||||||
|
/// DETECT_VLAN_ID_ALL to match if all layers match, or an integer
|
||||||
|
/// within the range -VLAN_MAX_LAYERS to VLAN_MAX_LAYERS-1 for indexing.
|
||||||
|
/// Negative values represent back to front indexing.
|
||||||
|
pub layer: i8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn detect_parse_vlan_id(s: &str) -> Option<DetectVlanIdData> {
|
||||||
|
let parts: Vec<&str> = s.split(',').collect();
|
||||||
|
let du16 = detect_parse_uint(parts[0]).ok()?.1;
|
||||||
|
if parts.len() > 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if du16.arg1 > 0xFFF || du16.arg2 > 0xFFF {
|
||||||
|
// vlan id is encoded on 12 bits
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let layer = if parts.len() == 2 {
|
||||||
|
if parts[1] == "all" {
|
||||||
|
DETECT_VLAN_ID_ALL
|
||||||
|
} else if parts[1] == "any" {
|
||||||
|
DETECT_VLAN_ID_ANY
|
||||||
|
} else {
|
||||||
|
let u8_layer = i8::from_str(parts[1]).ok()?;
|
||||||
|
if !(-VLAN_MAX_LAYERS..=VLAN_MAX_LAYERS - 1).contains(&u8_layer) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
u8_layer
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DETECT_VLAN_ID_ANY
|
||||||
|
};
|
||||||
|
return Some(DetectVlanIdData { du16, layer });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn SCDetectVlanIdParse(
|
||||||
|
ustr: *const std::os::raw::c_char,
|
||||||
|
) -> *mut DetectVlanIdData {
|
||||||
|
let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
|
||||||
|
if let Ok(s) = ft_name.to_str() {
|
||||||
|
if let Some(ctx) = detect_parse_vlan_id(s) {
|
||||||
|
let boxed = Box::new(ctx);
|
||||||
|
return Box::into_raw(boxed) as *mut _;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn SCDetectVlanIdFree(ctx: &mut DetectVlanIdData) {
|
||||||
|
// Just unbox...
|
||||||
|
std::mem::drop(Box::from_raw(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::detect::uint::DetectUintMode;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detect_parse_vlan_id() {
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id("300").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 300,
|
||||||
|
arg2: 0,
|
||||||
|
mode: DetectUintMode::DetectUintModeEqual,
|
||||||
|
},
|
||||||
|
layer: DETECT_VLAN_ID_ANY
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id("300,any").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 300,
|
||||||
|
arg2: 0,
|
||||||
|
mode: DetectUintMode::DetectUintModeEqual,
|
||||||
|
},
|
||||||
|
layer: DETECT_VLAN_ID_ANY
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id("300,all").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 300,
|
||||||
|
arg2: 0,
|
||||||
|
mode: DetectUintMode::DetectUintModeEqual,
|
||||||
|
},
|
||||||
|
layer: DETECT_VLAN_ID_ALL
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id("200,1").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 200,
|
||||||
|
arg2: 0,
|
||||||
|
mode: DetectUintMode::DetectUintModeEqual,
|
||||||
|
},
|
||||||
|
layer: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id("200,-1").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 200,
|
||||||
|
arg2: 0,
|
||||||
|
mode: DetectUintMode::DetectUintModeEqual,
|
||||||
|
},
|
||||||
|
layer: -1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id("!200,2").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 200,
|
||||||
|
arg2: 0,
|
||||||
|
mode: DetectUintMode::DetectUintModeNe,
|
||||||
|
},
|
||||||
|
layer: 2
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id(">200,2").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 200,
|
||||||
|
arg2: 0,
|
||||||
|
mode: DetectUintMode::DetectUintModeGt,
|
||||||
|
},
|
||||||
|
layer: 2
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id("200-300,0").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 200,
|
||||||
|
arg2: 300,
|
||||||
|
mode: DetectUintMode::DetectUintModeRange,
|
||||||
|
},
|
||||||
|
layer: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_parse_vlan_id("0xC8,2").unwrap(),
|
||||||
|
DetectVlanIdData {
|
||||||
|
du16: DetectUintData {
|
||||||
|
arg1: 200,
|
||||||
|
arg2: 0,
|
||||||
|
mode: DetectUintMode::DetectUintModeEqual,
|
||||||
|
},
|
||||||
|
layer: 2
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert!(detect_parse_vlan_id("200abc").is_none());
|
||||||
|
assert!(detect_parse_vlan_id("4096").is_none());
|
||||||
|
assert!(detect_parse_vlan_id("600,abc").is_none());
|
||||||
|
assert!(detect_parse_vlan_id("600,100").is_none());
|
||||||
|
assert!(detect_parse_vlan_id("123,-4").is_none());
|
||||||
|
assert!(detect_parse_vlan_id("1,2,3").is_none());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
/* Copyright (C) 2024 Open Information Security Foundation
|
||||||
|
*
|
||||||
|
* You can copy, redistribute or modify this Program under the terms of
|
||||||
|
* the GNU General Public License version 2 as published by the Free
|
||||||
|
* Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* version 2 along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "detect-vlan.h"
|
||||||
|
#include "detect-engine-uint.h"
|
||||||
|
#include "detect-parse.h"
|
||||||
|
#include "rust.h"
|
||||||
|
|
||||||
|
static int DetectVlanIdMatch(
|
||||||
|
DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx)
|
||||||
|
{
|
||||||
|
const DetectVlanIdData *vdata = (const DetectVlanIdData *)ctx;
|
||||||
|
|
||||||
|
if (p->vlan_idx == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (vdata->layer) {
|
||||||
|
case DETECT_VLAN_ID_ANY:
|
||||||
|
for (int i = 0; i < p->vlan_idx; i++) {
|
||||||
|
if (DetectU16Match(p->vlan_id[i], &vdata->du16)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case DETECT_VLAN_ID_ALL:
|
||||||
|
for (int i = 0; i < p->vlan_idx; i++) {
|
||||||
|
if (!DetectU16Match(p->vlan_id[i], &vdata->du16)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
if (vdata->layer < 0) { // Negative layer values for backward indexing.
|
||||||
|
if (((int16_t)p->vlan_idx) + vdata->layer < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DetectU16Match(p->vlan_id[p->vlan_idx + vdata->layer], &vdata->du16);
|
||||||
|
} else {
|
||||||
|
if (p->vlan_idx < vdata->layer) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DetectU16Match(p->vlan_id[vdata->layer], &vdata->du16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DetectVlanIdFree(DetectEngineCtx *de_ctx, void *ptr)
|
||||||
|
{
|
||||||
|
SCDetectVlanIdFree(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int DetectVlanIdSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
|
||||||
|
{
|
||||||
|
DetectVlanIdData *vdata = SCDetectVlanIdParse(rawstr);
|
||||||
|
if (vdata == NULL) {
|
||||||
|
SCLogError("vlan id invalid %s", rawstr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SigMatchAppendSMToList(
|
||||||
|
de_ctx, s, DETECT_VLAN_ID, (SigMatchCtx *)vdata, DETECT_SM_LIST_MATCH) == NULL) {
|
||||||
|
DetectVlanIdFree(de_ctx, vdata);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->flags |= SIG_FLAG_REQUIRE_PACKET;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrefilterPacketVlanIdMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx)
|
||||||
|
{
|
||||||
|
const PrefilterPacketHeaderCtx *ctx = pectx;
|
||||||
|
|
||||||
|
DetectVlanIdData vdata;
|
||||||
|
vdata.du16.mode = ctx->v1.u8[0];
|
||||||
|
vdata.layer = ctx->v1.u8[1];
|
||||||
|
vdata.du16.arg1 = ctx->v1.u16[2];
|
||||||
|
vdata.du16.arg2 = ctx->v1.u16[3];
|
||||||
|
|
||||||
|
if (p->vlan_idx == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (DetectVlanIdMatch(det_ctx, p, NULL, (const SigMatchCtx *)&vdata)) {
|
||||||
|
PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrefilterPacketVlanIdSet(PrefilterPacketHeaderValue *v, void *smctx)
|
||||||
|
{
|
||||||
|
const DetectVlanIdData *a = smctx;
|
||||||
|
v->u8[0] = a->du16.mode;
|
||||||
|
v->u8[1] = a->layer;
|
||||||
|
v->u16[2] = a->du16.arg1;
|
||||||
|
v->u16[3] = a->du16.arg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool PrefilterPacketVlanIdCompare(PrefilterPacketHeaderValue v, void *smctx)
|
||||||
|
{
|
||||||
|
const DetectVlanIdData *a = smctx;
|
||||||
|
if (v.u8[0] == a->du16.mode && v.u8[1] == a->layer && v.u16[2] == a->du16.arg1 &&
|
||||||
|
v.u16[3] == a->du16.arg2)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int PrefilterSetupVlanId(DetectEngineCtx *de_ctx, SigGroupHead *sgh)
|
||||||
|
{
|
||||||
|
return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_VLAN_ID, SIG_MASK_REQUIRE_REAL_PKT,
|
||||||
|
PrefilterPacketVlanIdSet, PrefilterPacketVlanIdCompare, PrefilterPacketVlanIdMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool PrefilterVlanIdIsPrefilterable(const Signature *s)
|
||||||
|
{
|
||||||
|
return PrefilterIsPrefilterableById(s, DETECT_VLAN_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectVlanIdRegister(void)
|
||||||
|
{
|
||||||
|
sigmatch_table[DETECT_VLAN_ID].name = "vlan.id";
|
||||||
|
sigmatch_table[DETECT_VLAN_ID].desc = "match vlan id";
|
||||||
|
sigmatch_table[DETECT_VLAN_ID].url = "/rules/vlan-keywords.html#vlan-id";
|
||||||
|
sigmatch_table[DETECT_VLAN_ID].Match = DetectVlanIdMatch;
|
||||||
|
sigmatch_table[DETECT_VLAN_ID].Setup = DetectVlanIdSetup;
|
||||||
|
sigmatch_table[DETECT_VLAN_ID].Free = DetectVlanIdFree;
|
||||||
|
sigmatch_table[DETECT_VLAN_ID].SupportsPrefilter = PrefilterVlanIdIsPrefilterable;
|
||||||
|
sigmatch_table[DETECT_VLAN_ID].SetupPrefilter = PrefilterSetupVlanId;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/* Copyright (C) 2024 Open Information Security Foundation
|
||||||
|
*
|
||||||
|
* You can copy, redistribute or modify this Program under the terms of
|
||||||
|
* the GNU General Public License version 2 as published by the Free
|
||||||
|
* Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* version 2 along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SURICATA_DETECT_VLAN_H
|
||||||
|
#define SURICATA_DETECT_VLAN_H
|
||||||
|
|
||||||
|
void DetectVlanIdRegister(void);
|
||||||
|
|
||||||
|
#endif /* SURICATA_DETECT_VLAN_H */
|
Loading…
Reference in New Issue