Snort 3.0 Architecture Series Part 3: The command shell
One of the biggest user-facing changes in the Snort 3 architecture is the inclusion of a user shell interface to interact with the system. Up until now everything has been controlled strictly via the command line interface at startup time and via signals sent to the process. There are many reasons for going with a command shell that I detailed in the first part of this series but basically since Snort is designed to be able to run continuously now it was essential to have a way to interact with it.
Upon starting SnortSP you are sent directly to the command shell prompt.
[+] Loaded pcap DAQ
[+] Loaded file DAQ
[+] Loaded afpacket DAQ
[*] DAQ Modules Loaded...
[*] Loading decoder modules
[+] Loaded ethernet
[+] Loaded null
[+] Loaded arp
[+] Loaded ip
[+] Loaded tcp
[+] Loaded udp
[+] Loaded icmp
[+] Loaded icmp6
[+] Loaded gre
[+] Loaded mpls
[+] Loaded 8021q
[+] Loaded ipv6
[+] Loaded ppp
[+] Loaded pppoe
[+] Loaded gtp
[+] Loaded raw
[*] Decoder initialized...
[*] Flow manager initialized...
[*] Data source subsystem loaded
[*] Engine manager initialized
Control thread running - 3082939280 (18555)
[*] Loading command interface
[!] Loading SnortSP command metatable
[!] Loading data source command metatable
[!] Loading engine command metatable
[!] Loading output command metatable
[!] Loading analyzer command metatable
Executing etc/snort.lua
,,_ -*> SnortSP! <*-
o" )~ Version 3.0.0b2 (Build 9) [BETA]
'''' By Martin Roesch & The Snort Team: http://www.snort.org/team.html
(C) Copyright 2008 Sourcefire Inc.
snort>
If this is your first time running Snort you'll probably want to get help. Every subsystem in the Snort 3 architecture has its own help function available as do the engine modules, if you ever get lost working with a module just invoke its object help function. For example:
snort> ssp.help()
[*] SnortSP Commands:
help()
set_log_level( [debug|info|notice|warn|error|critical] )
shutdown()
Available subsystems within SnortSP have their own help() methods:
dsrc - Data Source
eng - Dispatcher/Engine
analyzer - Analytics Modules
output - Output Modules
For example: dsrc.help() will call the Data Source help function
As you can see, the top level module for SnortSP is called "ssp" and you can invoke its help function by calling it with a "ssp.help()" function. If you want to find out the data source subsystem's available functions simply call "dsrc.help()" and so on.
One cool thing: if your system has Readline support available the Lua interpreter will pick it up automatically and you'll have standard shell functionality available within SnortSP such as command history and command line editing.
Under the covers a few things are happening. When a command is invoked in SnortSP there are a series of lookups performed by the code in the src/platform/lua_interface.c file. Lua is really handy for wrapping C function calls, it's one of the initial reasons I went with it for Snort. Let's take a look at some of the simple functionality in the lua_interface.c file for the ssp.* commands.
static int set_log_level_wrap(lua_State *L) {
int level;
if (lua_isnumber(L, 1))
{
level = lua_tointeger(L, 1);
}
else
{
const char *name = (char *) luaL_checkstring(L, 1);
if (name ==NULL) return 0;
if (strcasecmp(name, "debug") == 0) level = S_LOG_DEBUG;
else if (strcasecmp(name, "info") == 0) level = S_LOG_INFO;
else if (strcasecmp(name, "notice") == 0) level = S_LOG_NOTICE;
else if (strcasecmp(name, "warn") == 0) level = S_LOG_WARN;
else if (strcasecmp(name, "error") == 0) level = S_LOG_ERROR;
else if (strcasecmp(name, "critical") == 0) level = S_LOG_CRITICAL;
else return 0;
}
log_set_current_log_level(level);
level = log_get_current_log_level();
S_INFO("Log level set to %s", log_level_to_string(level));
return 0;
}
static int shutdown_wrap(lua_State *L) {
stop_processing = 1;
return 0;
}
static int platform_help_wrap(lua_State *L) {
printf("[*] "PLATFORM_NAME" Commands:\n"
" help()\n"
" set_log_level( [debug|info|notice|warn|error|critical] )\n"
" shutdown()\n"
" Available subsystems within "PLATFORM_NAME" have their own help() methods:\n"
" dsrc - Data Source\n"
" eng - Dispatcher/Engine\n"
" analyzer - Analytics Modules\n"
" output - Output Modules\n"
" For example: dsrc.help() will call the Data Source help function\n");
return 0;
}
static void platform_set_info(lua_State *L) {
lua_pushliteral (L, "_COPYRIGHT");
lua_pushliteral (L, "Copyright (C) 2008 Sourcefire Inc.");
lua_settable (L, -3);
lua_pushliteral (L, "_DESCRIPTION");
lua_pushliteral (L, "Network Intrusion Prevention System command interface");
lua_settable (L, -3);
lua_pushliteral (L, "_VERSION");
lua_pushliteral (L, PLATFORM_NAME" 0.1");
lua_settable (L, -3);
}
static const struct luaL_reg platformlib[] = {
{"help", platform_help_wrap},
{"shutdown", shutdown_wrap},
{"set_log_level", set_log_level_wrap},
{NULL, NULL},
};
static int platform_dir_create_meta (lua_State *L) {
luaL_newmetatable (L, METATABLE);
/* set its __gc field */
lua_pushstring (L, "__gc");
lua_settable (L, -2);
return 1;
}
static int luaopen_platform(lua_State *L) {
platform_dir_create_meta(L);
S_INFO("[!] Loading "PLATFORM_NAME" command metatable");
luaL_openlib(L, PLATFORM_PROMPT, platformlib, 0);
platform_set_info(L);
return 1;
}
This code sets up a "metatable" in Lua which allows us to call functions from its root with dot notation. The call to bind the root name is a little hidden in this case, it's the luaL_openlib() call in the last function above. We ended up using the PLATFORM_PROMPT substitution due to our habit of renaming the whole project periodically in its earlier days....
The array definition at the top of the code listing shows how the keywords are being mapped to the function calls themselves. When you put it all together, if you call "PLATFORM_PROMPT.help()" it maps that back to calling the platform_help_wrap() function. If you're familiar with the internals of Snort 1.x-2x you'll know that this mapping of keywords in the interpreter to function calls is very similar to how Snort's parser worked so it was a very comfortable transition for me. Add in the validation and scripting that you can get from lua and you can do some really cool stuff!
Let's take a look at a sample lua function scripting the SnortSP architecture to demonstrate the power of this arrangement. One of the things I wanted to do with SnortSP in its early days is test out the decoders to make sure there were no huge flaws in any of them that would lead to crash, as you know Snort's decoders are "critical infrastructure" that must be fast and crash free, the rest of the system is counting on them. The function I wrote would allow a user to point Snort at a directory of pcap files and process each one sequentially until every file in the directory had been processed.
In the old days we would have had to start a new instance of Snort (2.x) for each file we wanted to process and take all the time to load and shutdown. In SnortSP you could (in theory) load a detection configuration and then process each file, all without having to restart. The way the function is setup right now it just processes the file and prints the packet dump to the screen. Here's a listing.
require"lfs" # load filesystem functions
function rundir (path, mask) # args are path to pcap files, filename mask
once = 0 # initialize the data source once only
for file in lfs.dir(path) do # grab the path
if string.match(file, mask) ~= nil then # find files that match the mask
print("Processing File "..path..'/'..file)
if(once == 0) then # setup the dsrc params once only
once = 1
dsrc1 = {name="src1",
type="pcap",
intf="file",
filename=path..'/'..file,
flags=1,
snaplen=0,
maxflows=16384,
maxidle=10,
flow_memcap=10000000,
display="max"}
dsrc.new(dsrc1) # instantiate the dsrc once only
eng.new("e1") # instantiate the dispatcher once only
eng.link({engine="e1", source="src1"}) # link dsrc to dispatcher
print("Starting engine")
eng.start("e1") # run it
else
eng.run_file("e1", path..'/'..file) # just fun the file now
end
end
end
if(once == 1) then # process the last file too
eng.run_file("e1", "")
end
end
The first thing happening here is the inclusion of a Lua library called "lfs". That's necessary for the filesystem interface, Lua doesn't ship with one natively. (I know.) After that it's the function definition which takes two arguments, the path to the directory containing the pcap files and the filename mask to use to select the right files from the directory. The first iteration through the config data structures and engine objects are instantiated and configured and the first file is processed, after that it just processes all the rest of the files in the directory.
This is pretty cool! Practically speaking you can script the startup and shutdown of Snort and script the operation of all the different system objects as well. For example, you can make external system calls to lookup runtime parameters like interface configuration or available memory and use that information to tune your runtime configuration parameters of your engine. You can also script testing new software modules or even get into more crazy stuff like automatically turning engine modules on and off at certain times of day or pretty much anything else you can imagine.
This is just a brief tour of the functionality of the command line, there's more that can be done! I'll leave it to interested readers to explore the SnortSP command shell and I'd love to hear about interesting things you do with it.
In the next part of the series I'll start writing about the data source subsystem and its components.
Labels: IDS, IPS, open source, Snort, Sourcefire