snort3: complete rework

- Add many options to config file.
  - Move rules and generated snort.lua to /tmp.
  - Add script for downloading rules.
  - Add preliminary reporting capabilites.

Signed-off-by: Eric Fahlgren <ericfahlgren@gmail.com>
This commit is contained in:
Eric Fahlgren 2023-11-27 08:21:43 -08:00 committed by Rosen Penev
parent 904438be39
commit f21dffc2a3
10 changed files with 888 additions and 15 deletions

View File

@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=snort3
PKG_VERSION:=3.1.75.0
PKG_RELEASE:=1
PKG_RELEASE:=3
PKG_SOURCE:=$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/snort3/snort3/archive/refs/tags/
@ -25,7 +25,7 @@ define Package/snort3
SUBMENU:=Firewall
SECTION:=net
CATEGORY:=Network
DEPENDS:=+libstdcpp +libdaq3 +libdnet +libopenssl +libpcap +libpcre +libpthread +libuuid +zlib +libhwloc +libtirpc @HAS_LUAJIT_ARCH +luajit +libatomic
DEPENDS:=+libstdcpp +libdaq3 +libdnet +libopenssl +libpcap +libpcre +libpthread +libuuid +zlib +libhwloc +libtirpc @HAS_LUAJIT_ARCH +luajit +libatomic +kmod-nft-queue
TITLE:=Lightweight Network Intrusion Detection System
URL:=http://www.snort.org/
MENU:=1
@ -76,6 +76,10 @@ define Package/snort3/install
$(PKG_INSTALL_DIR)/usr/bin/u2{boat,spewfoo} \
$(1)/usr/bin/
$(INSTALL_BIN) \
./files/snort-{mgr,rules} \
$(1)/usr/bin/
$(INSTALL_DIR) $(1)/usr/lib/snort
$(CP) \
$(PKG_INSTALL_DIR)/usr/lib/snort/daq/daq_hext.so \
@ -90,6 +94,19 @@ define Package/snort3/install
$(PKG_INSTALL_DIR)/usr/include/snort/lua/snort_plugin.lua \
$(1)/usr/share/lua/
$(INSTALL_DIR) $(1)/usr/share/snort
$(INSTALL_CONF) \
./files/main.uc \
$(1)/usr/share/snort/
$(INSTALL_DIR) $(1)/usr/share/snort/templates
$(INSTALL_CONF) \
./files/nftables.uc \
$(1)/usr/share/snort/templates/
$(INSTALL_CONF) \
./files/snort.uc \
$(1)/usr/share/snort/templates/
$(INSTALL_DIR) $(1)/etc/snort/{rules,lists,builtin_rules,so_rules}
$(INSTALL_CONF) \

View File

@ -1,3 +1,4 @@
-- Unused when using 'snort-mgr', do not modify without deep understanding.
-- setup HOME_NET below with your IP range/ranges to protect
HOME_NET = [[ 192.168.1.0/24 10.1.0.1/24 ]]
EXTERNAL_NET = "!$HOME_NET"
--HOME_NET = [[ 192.168.1.0/24 10.1.0.0/24 ]]
--EXTERNAL_NET = "!$HOME_NET"

View File

@ -1,3 +1,6 @@
-- This file is no longer used if you are using 'snort-mgr' to create the
-- configuration. It is left as a sample.
--
-- use ths file to customize any functions defined in /etc/snort/snort.lua
-- switch tap to inline in ips and uncomment the below to run snort in inline mode

263
net/snort3/files/main.uc Normal file
View File

@ -0,0 +1,263 @@
{%
//------------------------------------------------------------------------------
// Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
// SPDX-License-Identifier: GPL-2.0
//
// The tables defined using 'config_item' are the source of record for the
// configuration file, '/etc/config/snort'. If you wish to add new items,
// do that only in the tables and propagate that use into the templates.
//
//------------------------------------------------------------------------------
import { cursor } from 'uci';
let uci = cursor();
function wrn(fmt, ...args) {
if (getenv("QUIET"))
exit(1);
let msg = "ERROR: " + sprintf(fmt, ...args);
if (getenv("TTY"))
warn(`\033[33m${msg}\033[m\n`);
else
warn(`[!] ${msg}\n`);
exit(1);
}
//------------------------------------------------------------------------------
function config_item(type, values, def) {
// If no default value is provided explicity, then values[0] is used as default.
if (! type in [ "enum", "range", "path", "str" ]) {
wrn(`Invalid item type '${type}', must be one of "enum", "range", "path" or "str".`);
return;
}
if (type == "range" && (length(values) != 2 || values[0] > values[1])) {
wrn(`A 'range' type item must have exactly 2 values in ascending order.`);
return;
}
// Maybe check paths for existence???
return {
type: type,
values: values,
default: def ?? values[0],
contains: function(value) {
// Check if the value is contained in the listed values,
// depending on the item type.
switch (this.type) {
case "enum":
return value in this.values;
case "range":
return value >= this.values[0] && value <= this.values[1];
default:
return true;
}
},
allowed: function() {
// Show a pretty version of the possible values, for error messages.
switch (this.type) {
case "enum":
return "one of [" + join(", ", this.values) + "]";
case "range":
return `${this.values[0]} <= x <= ${this.values[1]}`;
case "path":
return "a path string";
case "str":
return "a string";
default:
return "???";
}
},
}
};
const snort_config = {
enabled: config_item("enum", [ 0, 1 ], 0), // Defaults to off, so that user must configure before first start.
manual: config_item("enum", [ 0, 1 ], 1), // Allow user to manually configure, legacy behavior when enabled.
oinkcode: config_item("str", [ "" ]), // User subscription oinkcode. Much more in 'snort-rules' script.
home_net: config_item("str", [ "" ], "192.168.1.0/24"),
external_net: config_item("str", [ "" ], "any"),
config_dir: config_item("path", [ "/etc/snort" ]), // Location of the base snort configuration files.
temp_dir: config_item("path", [ "/var/snort.d" ]), // Location of all transient snort config, including downloaded rules.
log_dir: config_item("path", [ "/var/log" ]), // Location of the generated logs, and oh-by-the-way the snort PID file (why?).
logging: config_item("enum", [ 0, 1 ], 1),
openappid: config_item("enum", [ 0, 1 ], 0),
mode: config_item("enum", [ "ids", "ips" ]),
method: config_item("enum", [ "pcap", "afpacket", "nfq" ]),
action: config_item("enum", [ "alert", "block", "drop", "reject" ]),
interface: config_item("str", [ uci.get("network", "wan", "device") ]),
snaplen: config_item("range", [ 1518, 65535 ]), // int daq.snaplen = 1518: set snap length (same as -s) { 0:65535 }
};
const nfq_config = {
queue_count: config_item("range", [ 1, 16 ], 4), // Count of queues to allocate in nft chain when method=nfq, usually 2-8.
queue_start: config_item("range", [ 1, 32768], 4), // Start of queue numbers in nftables.
queue_maxlen: config_item("range", [ 1024, 65536 ], 1024), // --daq-var queue_maxlen=int
fanout_type: config_item("enum", [ "hash", "lb", "cpu", "rollover", "rnd", "qm"], "hash"), // See below.
thread_count: config_item("range", [ 0, 32 ], 0), // 0 = use cpu count
chain_type: config_item("enum", [ "prerouting", "input", "forward", "output", "postrouting" ], "input"),
chain_priority: config_item("enum", [ "raw", "filter", "300"], "filter"),
include: config_item("path", [ "" ]), // User-defined rules to include inside queue chain.
};
let _snort_config_doc =
"
This is not an exhaustive list of configuration items, just those that
require more explanation than is given in the tables that define them, below.
https://openwrt.org/docs/guide-user/services/snort
snort
manual - When set to 1, use manual configuration for legacy behavior.
When disabled, then use this config.
interface - Default should usually be 'uci get network.wan.device',
something like 'eth0'
home_net - IP range/ranges to protect. May be 'any', but more likely it's
your lan range, default is '192.168.1.0/24'
external_net - IP range external to home. Usually 'any', but if you only
care about true external hosts (trusting all lan devices),
then '!$HOMENET' or some specific range
mode - 'ids' or 'ips', for detection-only or prevention, respectively
oinkcode - https://www.snort.org/oinkcodes
config_dir - Location of the base snort configuration files. Default /etc/snort
temp_dir - Location of all transient snort config, including downloaded rules
Default /var/snort.d
logging - Enable external logging of events thus enabling 'snort-mgr report',
otherwise events only go to system log (i.e., 'logread -e snort:')
log_dir - Location of the generated logs, and oh-by-the-way the snort
PID file (why?). Default /var/log
openappid - Enabled inspection using the 'openappid' package
See 'opkg info openappid'
action - 'alert', 'block', 'reject' or 'drop'
method - 'pcap', 'afpacket' or 'nfq'
snaplen - int daq.snaplen = 1518: set snap length (same as -s) { 0:65535 }
nfq - https://github.com/snort3/libdaq/blob/master/modules/nfq/README.nfq.md
queue_maxlen - nfq's '--daq-var queue_maxlen=int'
queue_count - Count of queues to use when method=nfq, usually 2-8
fanout_type - Sets kernel load balancing algorithm*, one of hash, lb, cpu,
rollover, rnd, qm.
thread_count - int snort.-z: <count> maximum number of packet threads
(same as --max-packet-threads); 0 gets the number of
CPU cores reported by the system; default is 1 { 0:max32 }
chain_type - Chain type when generating nft output
chain_priority - Chain priority when generating nft output
include - Full path to user-defined extra rules to include inside queue chain
* - for details on fanout_type, see these pages:
https://github.com/florincoras/daq/blob/master/README
https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
";
function snort_config_doc(comment) {
if (comment == null) comment = "";
if (comment != "") comment += " ";
for (let line in split(_snort_config_doc, "\n")) {
let msg = rtrim(sprintf("%s%s", comment, line));
print(msg, "\n");
}
}
//------------------------------------------------------------------------------
function load(section, config) {
let self = {
".name": section,
".config": config,
};
// Set the defaults from definitions in table.
for (let item in config) {
self[item] = config[item].default;
}
// Overwrite them with any uci config settings.
let cfg = uci.get_all("snort", section);
for (let item in cfg) {
// If you need to rename, delete or change the meaning of a
// config item, just intercept it and do the work here.
if (exists(config, item)) {
let val = cfg[item];
if (config[item].contains(val))
self[item] = val;
else {
wrn(`In option ${item}='${val}', must be ${config[item].allowed()}`);
// ??? self[item] = config[item][0]; ???
}
}
}
return self;
}
let snort = null;
let nfq = null;
function load_all() {
snort = load("snort", snort_config);
nfq = load("nfq", nfq_config);
}
function dump_config(settings) {
let section = settings[".name"];
let config = settings[".config"];
printf("config %s '%s'\n", section, section);
for (let item in config) {
printf("\toption %-15s %-17s# %s\n", item, `'${settings[item]}'`, config[item].allowed());
}
print("\n");
}
function render_snort() {
include("templates/snort.uc", { snort, nfq });
}
function render_nftables() {
include("templates/nftables.uc", { snort, nfq });
}
function render_config() {
snort_config_doc("#");
dump_config(snort);
dump_config(nfq);
}
function render_help() {
snort_config_doc();
}
//------------------------------------------------------------------------------
load_all();
switch (getenv("TYPE")) {
case "snort":
render_snort();
return;
case "nftables":
render_nftables();
return;
case "config":
render_config();
return;
case "help":
render_help();
return;
default:
print("Invalid table type.\n");
return;
}
//------------------------------------------------------------------------------
-%}

View File

@ -0,0 +1,18 @@
# Do not edit, automatically generated. See /usr/share/snort/templates.
{%
// Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
// SPDX-License-Identifier: GPL-2.0
let queues = `${nfq.queue_start}-${int(nfq.queue_start)+int(nfq.queue_count)-1}`;
let chain_type = nfq.chain_type;
-%}
table inet snort {
chain {{ chain_type }}_{{ snort.mode }} {
type filter hook {{ chain_type }} priority {{ nfq.chain_priority }}
policy accept
{% if (nfq.include) { include(nfq.include, { snort, nfq }); } %}
# tcp flags ack ct direction original ct state established counter accept
counter queue flags bypass to {{ queues }}
}
}

260
net/snort3/files/snort-mgr Normal file
View File

@ -0,0 +1,260 @@
#!/bin/sh
# Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
# SPDX-License-Identifier: GPL-2.0
# shellcheck disable=SC2039 # "local" not defined in POSIX sh
PROG="/usr/bin/snort"
MAIN="/usr/share/snort/main.uc"
CONF_DIR="/var/snort.d"
CONF="${CONF_DIR}/snort_conf.lua"
VERBOSE=
TESTING=
NLINES=0
[ ! -e "$CONF_DIR" ] && mkdir "$CONF_DIR"
[ -e /dev/stdin ] && STDIN=/dev/stdin || STDIN=/proc/self/fd/0
[ -e /dev/stdout ] && STDOUT=/dev/stdout || STDOUT=/proc/self/fd/1
[ -t 2 ] && export TTY=1
die() {
[ -n "$QUIET" ] || echo "$@" >&2
exit 1
}
disable_offload()
{
# From https://forum.openwrt.org/t/snort-3-nfq-with-ips-mode/161172
# https://blog.snort.org/2016/08/running-snort-on-commodity-hardware.html
# Not needed when running the nft daq as defragmentation is done by the kernel.
# What about pcap?
local filter_method=$(uci -q get snort.snort.method)
if [ "$filter_method" = "afpacket" ]; then
local wan=$(uci get snort.snort.interface)
if [ -n "$wan" ] && ethtool -k "$wan" | grep -q -E '(tcp-segmentation-offload|receive-offload): on' ; then
ethtool -K "$wan" gro off lro off tso off 2> /dev/null
log "Disabled gro, lro and tso on '$wan' using ethtool."
fi
fi
}
nft_rm_table() {
for table_type in 'inet' 'netdev'; do
nft list tables | grep -q "${table_type} snort" && nft delete table "${table_type}" snort
done
}
nft_add_table() {
if [ "$(uci -q get snort.snort.method)" = "nfq" ]; then
print nftables | nft $VERBOSE -f $STDIN
[ -n "$VERBOSE" ] && nft list table inet snort
fi
}
setup() {
# Generates all the configuration, then reports the config file for snort.
# Does NOT generate the rules file, you'll need to do 'update-rules' first.
nft_rm_table
print snort > "$CONF"
nft_add_table
echo "$CONF"
}
teardown() {
# Merely cleans up after.
nft_rm_table
[ -e "$CONF" ] && rm "$CONF"
}
update_rules() {
/usr/bin/snort-rules $TESTING
}
print() {
# '$1' is file type to generate, one of:
# config, snort or nftables
TYPE=$1 utpl -S "$MAIN"
}
check() {
local manual=$(uci get snort.snort.manual)
[ "$manual" = 1 ] && return 0
[ -n "$QUIET" ] && OUT=/dev/null || OUT=$STDOUT
local test_conf="${CONF_DIR}/test_conf.lua"
print snort > "${test_conf}" || die "Errors during generation of config."
if $PROG -T -q --warn-all -c "${test_conf}" 2> $OUT ; then
rm "${test_conf}"
return 0
fi
die "Errors in snort config tests."
}
report() {
# Reported IPs have source port stripped, but destination port (if any)
# retained.
#
# json notes
# from alert_fast:
# 08/30-11:39:57.639021 [**] [1:382:11] "PROTOCOL-ICMP PING Windows" [**] [Classification: Misc activity] [Priority: 3] {ICMP} 10.1.1.186 -> 10.1.1.20
#
# same event in alert_json (single line broken for clarity):
# { "timestamp" : "08/30-11:39:57.639021", "pkt_num" : 5366, "proto" : "ICMP", "pkt_gen" : "raw",
# "pkt_len" : 60, "dir" : "C2S", "src_ap" : "10.1.1.186:0", "dst_ap" : "10.1.1.20:0",
# "rule" : "1:382:11", "action" : "allow" }
#
# Second part of "rule", 382, is "sid" in ruleset, suffixing 11 is "rev".
# grep '\bsid:382\b' /etc/snort/rules/snort.rules (again, single line broken for clarity):
# alert icmp $EXTERNAL_NET any -> $HOME_NET any ( msg:"PROTOCOL-ICMP PING Windows";
# itype:8; content:"abcdefghijklmnop",depth 16; metadata:ruleset community;
# classtype:misc-activity; sid:382; rev:11; )
#
# Not sure where the prefixing 1 comes from.
local logging=$(uci get snort.snort.logging)
local log_dir=$(uci get snort.snort.log_dir)
local pattern="$1"
if [ "$logging" = 0 ]; then
die "Logging is not enabled in snort config."
fi
#if [ -z "$pattern" ]; then
# die "Provide a valid IP and try again."
#fi
[ "$NLINES" = 0 ] && output="cat" || output="head -n $NLINES"
# Fix this to use json file.
tmp="/tmp/snort.report.$$"
echo "Intrusions involving ${pattern:-all IPs}"
grep "\b${pattern}\b" "$log_dir/alert_fast.txt" \
| sed 's/.*"\([^"]*\)".* \([^ :]*\)[: ].*-> \(.*\)/\1#\2#\3/' > "$tmp"
n_incidents="$(wc -l < $tmp)"
lines=$(sort "$tmp" | uniq -c | sort -nr \
| awk -F'#' '{printf "%-80s %-12s -> %s\n", $1, $2, $3}')
echo "$lines" | $output
n_lines=$(echo "$lines" | wc -l)
[ "$NLINES" -gt 0 ] && [ "$NLINES" -lt "$n_lines" ] && echo " ... Only showing $NLINES of $n_lines most frequent incidents."
printf "%7d total incidents\n" "$n_incidents"
rm "$tmp"
}
status() {
echo 'tbd'
}
while [ -n "$1" ]; do
case "$1" in
-q)
export QUIET=1
shift
;;
-v)
export VERBOSE=-e
shift
;;
-t)
TESTING=-t
shift
;;
-n)
NLINES="$2"
shift
shift
;;
*)
break
;;
esac
done
case "$1" in
setup)
setup
;;
teardown)
teardown
;;
resetup)
QUIET=1 check || die "The generated snort lua configuration contains errors, not restarting."
teardown
setup
;;
update-rules)
update_rules
;;
check)
check
;;
print)
print "$2"
;;
report)
report "$2"
;;
status)
status
;;
*)
cat <<USAGE
Usage:
-n = show only NLINES of output
-q = quiet
-v = verbose
-t = testing mode
$0 [-v] [-q] setup|teardown|resetup
Normally only used internally by init scripts to manage the generation
of configuration files and any needed firewall rules. None of these
modify the snort rules in any way (see 'update-rules').
setup = generates snort config, sets up firewall.
teardown = removes any firewall rules.
resetup = shorthand for teardown and then setup.
$0 [-n lines] report [pattern]
Report on incidents. Note this is somewhat experimental, so suggested
improvements are quite welcome.
pattern = IP or piece of IP or something in the message to filter.
$0 [-t] update-rules
Download and install the snort ruleset. Testing mode generates a canned
rule that matches IPv4 ping requests. A typical test scenario might look
like:
> snort-mgr -t update-rules
> /etc/init.d/snort start
> ping -c4 8.8.8.8
> logread -e "TEST ALERT"
$0 print config|snort|nftables
Print the rendered file contents.
config = Display contents of /etc/config/snort, but with all values and
descriptions. Missing values shown with defaults.
snort = The snort configuration file, which is a lua script.
nftables = The nftables script used to define the input queues when using
the 'nfq' DAQ.
$0 [-q] check
Test the rendered config using snort's check mode without
applying it to the running system.
$0 status
Print the nfq counter values and blah blah blah
USAGE
;;
esac

View File

@ -0,0 +1,92 @@
#!/bin/sh
# Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
# SPDX-License-Identifier: GPL-2.0
# shellcheck disable=SC2039 # "local" not defined in POSIX sh
alias log='logger -s -t "snort-rules[$$]" -p "info"'
[ "$1" = "-t" ] && testing=true || testing=false
download_rules() {
# Further information:
# https://www.snort.org/products#rule_subscriptions
# https://www.snort.org/oinkcodes
#
# Also, what to do about "subscription" vs Talos_LightSPD rules when subbed?
# Add a "use_rules" list or option or something?
oinkcode=$(uci -q get snort.snort.oinkcode)
local conf_dir=$(uci -q get snort.snort.config_dir || echo "/etc/snort")
local rules_file="$conf_dir/rules/snort.rules"
local data_dir=$(uci -q get snort.snort.temp_dir || echo "/var/snort.d")
local data_tar="$data_dir/rules.tar.gz"
# Make sure everything exists.
[ -d "$data_dir" ] || mkdir -p "$data_dir"
if $testing ; then
log "Generating testing rules..."
new_rules="$data_dir/testing.rules"
rm -f "$new_rules"
echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v4"; icode:0; itype: 8; sid:10000010; rev:001;)' >> "$new_rules"
#echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v6"; icode:0; itype:33; sid:10000011; rev:001;)' >> "$new_rules"
#echo 'alert icmp any any <> any any (msg:"TEST ALERT ICMP v6"; icode:0; itype:34; sid:10000012; rev:001;)' >> "$new_rules"
else
if [ -z "$oinkcode" ]; then
# If you do not have a subscription, then we use the community rules:
log "Downloading community rules..."
url="https://www.snort.org/downloads/community/snort3-community-rules.tar.gz"
else
# If you have a subscription and its corresponding oinkcode, use this:
#
# 'snortver' is the version number of the snort executable in use on your
# router.
#
# Ideally, the 'snort --version' output would work, but OpenWrt builds
# are often between (or, more likely, newer than) those listed on the
# snort.org downloads page.
#
# So instead, we define it manually to be the value just before the
# installed version. Look on https://www.snort.org/advisories/ and
# select the most recent date. On that page, find the closest version
# number preceding your installed version and modify the hard-coded
# value below (for example, installed is 31600 then use 31470):
#snortver=$(snort --version | awk '/Version/ {print gensub("\\.", "", "", $NF)}')
snortver=31470
log "Downloading subscription rules..."
url="https://www.snort.org/rules/snortrules-snapshot-$snortver.tar.gz?oinkcode=$oinkcode"
fi
wget "$url" -O "$data_tar" 2>&1 | log || exit 1
# ??? Does non-community tar contain just the one "*.rules" file, too???
new_rules=$(tar tzf "$data_tar" | grep '\.rules$')
new_rules="$data_dir/$new_rules"
old_rules="$data_dir/old.rules"
if [ -e "$new_rules" ]; then
# Before we overwrite with the new download.
log "Stashing old rules to $old_rules ..."
mv -f "$new_rules" "$old_rules"
fi
log "Unpacking $data_tar ..."
tar xzvf "$data_tar" -C "$data_dir" | log || exit 1
if [ -e "$old_rules" ] && ! cmp -s "$new_rules" "$old_rules" ; then
diff "$new_rules" "$old_rules" 2>&1 | log
fi
fi
rm -f "$rules_file"
ln -s "$new_rules" "$rules_file"
log "Snort rules loaded, restart snort now."
}
download_rules

View File

@ -1,3 +1,74 @@
#
# This is not an exhaustive list of configuration items, just those that
# require more explanation than is given in the tables that define them, below.
#
# https://openwrt.org/docs/guide-user/services/snort
#
# snort
# manual - When set to 1, use manual configuration for legacy behavior.
# When disabled, then use this config.
# interface - Default should usually be 'uci get network.wan.device',
# something like 'eth0'
# home_net - IP range/ranges to protect. May be 'any', but more likely it's
# your lan range, default is '192.168.1.0/24'
# external_net - IP range external to home. Usually 'any', but if you only
# care about true external hosts (trusting all lan devices),
# then '!$HOMENET' or some specific range
# mode - 'ids' or 'ips', for detection-only or prevention, respectively
# oinkcode - https://www.snort.org/oinkcodes
# config_dir - Location of the base snort configuration files. Default /etc/snort
# temp_dir - Location of all transient snort config, including downloaded rules
# Default /var/snort.d
# logging - Enable external logging of events thus enabling 'snort-mgr report',
# otherwise events only go to system log (i.e., 'logread -e snort:')
# log_dir - Location of the generated logs, and oh-by-the-way the snort
# PID file (why?). Default /var/log
# openappid - Enabled inspection using the 'openappid' package
# See 'opkg info openappid'
# action - 'alert', 'block', 'reject' or 'drop'
# method - 'pcap', 'afpacket' or 'nfq'
# snaplen - int daq.snaplen = 1518: set snap length (same as -s) { 0:65535 }
#
# nfq - https://github.com/snort3/libdaq/blob/master/modules/nfq/README.nfq.md
# queue_maxlen - nfq's '--daq-var queue_maxlen=int'
# queue_count - Count of queues to use when method=nfq, usually 2-8
# fanout_type - Sets kernel load balancing algorithm*, one of hash, lb, cpu,
# rollover, rnd, qm.
# thread_count - int snort.-z: <count> maximum number of packet threads
# (same as --max-packet-threads); 0 gets the number of
# CPU cores reported by the system; default is 1 { 0:max32 }
# chain_type - Chain type when generating nft output
# chain_priority - Chain priority when generating nft output
# include - Full path to user-defined extra rules to include inside queue chain
#
# * - for details on fanout_type, see these pages:
# https://github.com/florincoras/daq/blob/master/README
# https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
#
config snort 'snort'
option config_dir '/etc/snort/'
option interface 'eth0'
option enabled '0' # one of [0, 1]
option manual '1' # one of [0, 1]
option oinkcode '' # a string
option home_net '192.168.1.0/24' # a string
option external_net 'any' # a string
option config_dir '/etc/snort' # a path string
option temp_dir '/var/snort.d' # a path string
option log_dir '/var/log' # a path string
option logging '1' # one of [0, 1]
option openappid '0' # one of [0, 1]
option mode 'ids' # one of [ids, ips]
option method 'pcap' # one of [pcap, afpacket, nfq]
option action 'alert' # one of [alert, block, drop, reject]
option interface 'eth0' # a string
option snaplen '1518' # 1518 <= x <= 65535
config nfq 'nfq'
option queue_count '4' # 1 <= x <= 16
option queue_start '4' # 1 <= x <= 32768
option queue_maxlen '1024' # 1024 <= x <= 65536
option fanout_type 'hash' # one of [hash, lb, cpu, rollover, rnd, qm]
option thread_count '0' # 0 <= x <= 32
option chain_type 'input' # one of [prerouting, input, forward, output, postrouting]
option chain_priority 'filter' # one of [raw, filter, 300]
option include '' # a path string

View File

@ -1,36 +1,58 @@
#!/bin/sh /etc/rc.common
# shellcheck disable=SC2039 # "local" not defined in POSIX sh
START=99
STOP=10
USE_PROCD=1
PROG=/usr/bin/snort
MGR=/usr/bin/snort-mgr
validate_snort_section() {
$MGR -q check || return 1
uci_validate_section snort snort "${1}" \
'enabled:bool:0' \
'manual:bool:1' \
'config_dir:string' \
'interface:string'
}
start_service() {
local config_file interface
# If you wish to use application-managed PID file:
# output.logdir, in the snort lua config, determines the PID file location.
# Add '--create-pidfile' to the 'command', below.
validate_snort_section snort || {
echo "validation failed"
return 1
}
local enabled
local manual
local config_dir
local interface
validate_snort_section snort || {
echo "Validation failed, try 'snort-mgr check'."
return 1
}
[ "$enabled" = 0 ] && return
procd_open_instance
procd_set_param command $PROG -q -i "$interface" -c "${config_dir%/}/snort.lua" --tweaks local
procd_set_param env SNORT_LUA_PATH="$config_dir"
procd_set_param file $CONFIGFILE
if [ "$manual" = 0 ]; then
local config_file=$($MGR setup)
procd_set_param command "$PROG" -q -c "${config_file}"
else
procd_set_param command $PROG -q -i "$interface" -c "${config_dir%/}/snort.lua" --tweaks local
procd_set_param env SNORT_LUA_PATH="$config_dir"
procd_set_param file $CONFIGFILE
fi
procd_set_param respawn
procd_set_param stdout 0
procd_set_param stderr 1
procd_close_instance
}
stop_service()
{
service_stop ${PROG}
service_stop "$PROG"
$MGR teardown
}
service_triggers()

126
net/snort3/files/snort.uc Normal file
View File

@ -0,0 +1,126 @@
{%
// Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
// SPDX-License-Identifier: GPL-2.0
// Create some snort-format-specific items.
let home_net = snort.home_net == 'any' ? "'any'" : snort.home_net;
let external_net = snort.external_net;
let line_mode = snort.mode == "ids" ? "tap" : "inline";
let inputs = null;
let vars = null;
switch (snort.method) {
case "pcap":
case "afpacket":
inputs = `{ '${snort.interface}' }`;
vars = "{}";
break;
case "nfq":
inputs = "{ ";
for (let i = int(nfq.queue_start); i < int(nfq.queue_start)+int(nfq.queue_count); i++) {
inputs += `'${i}', `
}
inputs += "}";
vars = `{ 'device=${snort.interface}', 'queue_maxlen=${nfq.queue_maxlen}', 'fanout_type=${nfq.fanout_type}', 'fail_open', }`;
break;
}
-%}
-- Do not edit, automatically generated. See /usr/share/snort/templates.
-- These must be defined before processing snort.lua
-- The default include '/etc/snort/homenet.lua' must not redefine them.
HOME_NET = [[ {{ home_net }} ]]
EXTERNAL_NET = '{{ external_net }}'
include('{{ snort.config_dir }}/snort.lua')
snort = {
{% if (snort.mode == 'ips'): %}
['-Q'] = true,
{% endif %}
['--daq'] = {{ snort.method }},
--['--daq-dir'] = '/usr/lib/daq/',
{% if (snort.method == 'nfq'): %}
['--max-packet-threads'] = {{ nfq.thread_count }},
{% endif %}
}
ips = {
mode = {{ line_mode }},
variables = default_variables,
action_override = {{ snort.action }},
include = "{{ snort.config_dir }}/" .. RULE_PATH .. '/snort.rules',
}
daq = {
inputs = {{ inputs }},
snaplen = {{ snort.snaplen }},
module_dirs = { '/usr/lib/daq/', },
modules = {
{
name = '{{ snort.method }}',
mode = {{ line_mode }},
variables = {{ vars }},
}
}
}
alert_syslog = {
level = 'info',
}
{% if (int(snort.logging)): %}
-- Note that this is also the location of the PID file, if you use it.
output.logdir = "{{ snort.log_dir }}"
-- Maybe add snort.log_type, 'fast', 'json' and 'full'?
-- Json would be best for reporting, see 'snort-mgr report' code.
-- alert_full = { file = true, }
alert_fast = {
-- bool alert_fast.file = false: output to alert_fast.txt instead of stdout
-- bool alert_fast.packet = false: output packet dump with alert
-- int alert_fast.limit = 0: set maximum size in MB before rollover (0 is unlimited) { 0:maxSZ }
file = true,
packet = false,
}
alert_json = {
-- bool alert_json.file = false: output to alert_json.txt instead of stdout
-- multi alert_json.fields = timestamp pkt_num proto pkt_gen pkt_len dir src_ap dst_ap rule action: selected fields will be output
-- int alert_json.limit = 0: set maximum size in MB before rollover (0 is unlimited) { 0:maxSZ }
-- string alert_json.separator = , : separate fields with this character sequence
file = true,
}
{% endif -%}
normalizer = {
tcp = {
ips = true,
}
}
file_policy = {
enable_type = true,
enable_signature = true,
rules = {
use = {
verdict = 'log',
enable_file_type = true,
enable_file_signature = true,
}
}
}
-- To use openappid with snort, 'opkg install openappid' and enable in config.
{% if (int(snort.openappid)): %}
appid = {
log_stats = true,
app_detector_dir = '/usr/lib/openappid',
app_stats_period = 60,
}
{% endif %}