Merge remote-tracking branch 'origin/topic/etyp/redis-analyzer'

* origin/topic/etyp/redis-analyzer:
  spicy-redis: Add NEWS entry
  spicy-redis: Separate error replies from success
  spicy-redis: Cleanup scripts and tests
  spciy-redis: Bring Redis analyzer into Zeek proper
  spicy-redis: Abort parsing if server data comes first
  spicy-redis: Add recursion depth to server data
  spicy-redis: Make client data only accept bulk strings
  spicy-redis: Add dpd signature and clean pcaps
  spicy-redis: Add some commands and touch up parsing
  spicy-redis: Add some script logic for logging
  spicy-redis: Separate client/server
  spicy-redis: Touchup logging and Spicy issues
  spicy-redis: Add synchronization and pipeline support
  spicy-redis: Begin Spicy Redis analyzer
This commit is contained in:
Evan Typanski 2025-05-27 10:18:49 -04:00
commit 9f2fb47f48
71 changed files with 2747 additions and 6 deletions

View file

@ -30,6 +30,8 @@ extend-ignore-re = [
"ot->Yield\\(\\)->InternalType\\(\\)",
"switch \\( ot \\)",
"\\(ZAMOpType ot\\)",
"exat", # Redis expire at
"EXAT",
# News stuff
"SupressWeirds.*deprecated",

41
CHANGES
View file

@ -1,3 +1,44 @@
8.0.0-dev.227 | 2025-05-27 10:18:49 -0400
* spicy-redis: Separate error replies from success (Evan Typanski, Corelight)
* spicy-redis: Cleanup scripts and tests (Evan Typanski, Corelight)
- Recomputes checksums for pcaps to keep clean
- Removes some tests that had big pcaps or weren't necessary
- Cleans up scripting names and minor points
- Comments out Spicy code that causes a build failure now with a TODO to
uncomment it
* spciy-redis: Bring Redis analyzer into Zeek proper (Evan Typanski, Corelight)
* spicy-redis: Abort parsing if server data comes first (Evan Typanski, Corelight)
Redis seems to only want client data first to request server data. The
DPD signature seems to pick up on some cases where server data comes
first, but is otherwise "valid" RESP. See if this helps lower FP rates.
* spicy-redis: Add recursion depth to server data (Evan Typanski, Corelight)
* spicy-redis: Make client data only accept bulk strings (Evan Typanski, Corelight)
* spicy-redis: Add dpd signature and clean pcaps (Evan Typanski, Corelight)
* spicy-redis: Add some commands and touch up parsing (Evan Typanski, Corelight)
* spicy-redis: Add some script logic for logging (Evan Typanski, Corelight)
* spicy-redis: Separate client/server (Evan Typanski, Corelight)
This makes the parser more official and splits the client/server out
from each other.
* spicy-redis: Touchup logging and Spicy issues (Evan Typanski, Corelight)
* spicy-redis: Add synchronization and pipeline support (Evan Typanski, Corelight)
* spicy-redis: Begin Spicy Redis analyzer (Evan Typanski, Corelight)
8.0.0-dev.209 | 2025-05-26 16:08:44 +0200
* btest: Add test for Cluster::hello zero-timestamp (Arne Welzel, Corelight)

11
NEWS
View file

@ -42,6 +42,17 @@ New Functionality
const std::string& topic,
zeek::cluster::detail::Event& event);
- Zeek now includes the Redis protocol analyzer from the evantypanski/spicy-redis
project (https://github.com/evantypanski/spicy-redis). This analyzer is enabled
by default. This analyzer logs Redis commands and their associated replies in
``redis.log``.
To disable the analyzer in case of issues, use the following snippet:
redef Analyzer::disabled_analyzers += {
Analyzer::ANALYZER_REDIS,
};
Changed Functionality
---------------------

View file

@ -1 +1 @@
8.0.0-dev.209
8.0.0-dev.227

View file

@ -72,6 +72,7 @@
@load base/protocols/quic
@load base/protocols/radius
@load base/protocols/rdp
@load base/protocols/redis
@load base/protocols/rfb
@load base/protocols/sip
@load base/protocols/snmp

View file

@ -0,0 +1,6 @@
@if ( have_spicy_analyzers() )
@load ./spicy-events
@load ./main
@load-sigs ./dpd.sig
@endif

View file

@ -0,0 +1,14 @@
signature resp-client {
ip-proto == tcp
payload /^.*\r\n/
tcp-state originator
requires-reverse-signature resp-serialized-server
enable "Redis"
}
signature resp-serialized-server {
ip-proto == tcp
payload /^([-+_,].*\r\n|[:$*#(!=%`~>][+-]?[0-9]+(\.[0-9]*)?\r\n)/
tcp-state responder
enable "Redis"
}

View file

@ -0,0 +1,278 @@
@load base/protocols/conn/removal-hooks
@load base/frameworks/signatures
@load ./spicy-events
module Redis;
export {
## Log stream identifier.
redef enum Log::ID += { LOG };
## The ports to register Redis for.
const ports = {6379/tcp} &redef;
## Record type containing the column fields of the Redis log.
type Info: record {
## Timestamp for when the activity happened.
ts: time &log;
## Unique ID for the connection.
uid: string &log;
## The connection's 4-tuple of endpoint addresses/ports.
id: conn_id &log;
## The Redis command.
cmd: Command &log;
## If the command was successful. Only set if the server responded.
success: bool &log &optional;
## The reply for the command.
reply: ReplyData &log &optional;
};
## A default logging policy hook for the stream.
global log_policy: Log::PolicyHook;
global finalize_redis: Conn::RemovalHook;
## Which numbered commands should not expect a reply due to CLIENT REPLY commands.
## These commands may simply skip one, or they may turn off replies then later
## reenable them. Thus, the end of the interval is optional.
type NoReplyRange: record {
begin: count;
end: count &optional;
};
type State: record {
## Pending commands.
pending: table[count] of Info;
## Current command in the pending queue.
current_command: count &default=0;
## Current reply in the pending queue.
current_reply: count &default=0;
## Ranges where we do not expect a reply due to CLIENT REPLY commands.
## Each range is one or two elements, one meaning it's unbounded, two meaning
## it begins at one and ends at the second.
no_reply_ranges: vector of NoReplyRange;
## We store if this analyzer had a violation to avoid logging if so.
## This should not be super necessary, but worth a shot.
violation: bool &default=F;
};
# Redis specifically mentions 10k commands as a good pipelining threshold, so
# we'll piggyback on that.
option max_pending_commands = 10000;
}
redef record connection += {
redis: Info &optional;
redis_state: State &optional;
};
redef likely_server_ports += {ports};
event zeek_init() &priority=5
{
Log::create_stream(Redis::LOG, [$columns=Info, $path="redis",
$policy=log_policy]);
Analyzer::register_for_ports(Analyzer::ANALYZER_REDIS, ports);
}
event analyzer_violation_info(atype: AllAnalyzers::Tag,
info: AnalyzerViolationInfo)
{
if ( atype == Analyzer::ANALYZER_REDIS && info?$c && info$c?$redis_state )
{
info$c$redis_state$violation = T;
}
}
function new_redis_info(c: connection): Info
{
return Info($ts=network_time(), $uid=c$uid, $id=c$id);
}
function make_new_state(c: connection)
{
local s: State;
c$redis_state = s;
Conn::register_removal_hook(c, finalize_redis);
}
function set_state(c: connection, is_orig: bool)
{
if ( ! c?$redis_state )
make_new_state(c);
local current: count;
if ( is_orig )
current = c$redis_state$current_command;
else
current = c$redis_state$current_reply;
if ( current !in c$redis_state$pending )
c$redis_state$pending[current] = new_redis_info(c);
c$redis = c$redis_state$pending[current];
}
## Returns whether the last "no reply" interval is not still open.
function is_last_interval_closed(c: connection): bool
{
return |c$redis_state$no_reply_ranges| == 0 ||
c$redis_state$no_reply_ranges[-1]?$end;
}
event Redis::command(c: connection, cmd: Command)
{
if ( ! c?$redis_state )
make_new_state(c);
if ( max_pending_commands > 0
&& |c$redis_state$pending| > max_pending_commands )
{
Reporter::conn_weird("Redis_excessive_pipelining", c);
# Delete the current state and restart later. We'll be in a weird state, but
# really we want to abort. I don't quite get how to register this as a
# violation. :)
delete c$redis_state;
return;
}
++c$redis_state$current_command;
# CLIENT commands can skip a number of replies and may be used with
# pipelining. We need special logic in order to track the command/reply
# pairs.
if ( cmd?$known && cmd$known == KnownCommand_CLIENT )
{
# All 3 CLIENT commands we care about have 3 elements
if ( |cmd$raw| == 3 )
{
if ( to_lower(cmd$raw[2]) == "on" )
{
# If the last range is open, close it here. Otherwise, noop
if ( |c$redis_state$no_reply_ranges| > 0 )
{
local range = c$redis_state$no_reply_ranges[-1];
if ( ! range?$end )
{
range$end = c$redis_state$current_command;
}
}
}
if ( to_lower(cmd$raw[2]) == "off" )
{
# Only add a new interval if the last one is closed
if ( is_last_interval_closed(c) )
{
c$redis_state$no_reply_ranges += NoReplyRange(
$begin=c$redis_state$current_command);
}
}
if ( to_lower(cmd$raw[2]) == "skip" )
{
if ( is_last_interval_closed(c) )
# It skips this one and the next one
c$redis_state$no_reply_ranges += NoReplyRange(
$begin=c$redis_state$current_command, $end=c$redis_state$current_command + 2);
}
}
}
set_state(c, T);
c$redis$cmd = cmd;
}
## Gets the next reply number based on a connection. This is necessary since
## some replies may have been skipped.
function reply_num(c: connection): count
{
local resp_num = c$redis_state$current_reply + 1;
for ( i in c$redis_state$no_reply_ranges )
{
local range = c$redis_state$no_reply_ranges[i];
if ( ! range?$end && resp_num > range$begin )
{ } # TODO: This is necessary if not using pipelining
if ( range?$end && resp_num >= range$begin && resp_num < range$end )
return range$end;
}
# Default: no disable/enable shenanigans
return resp_num;
}
# Logs up to and including the last seen command from the last reply
function log_from(c: connection, previous_reply_num: count)
{
# Log each of the pending replies to this point - we will not go
# back.
while ( previous_reply_num < c$redis_state$current_reply )
{
if ( previous_reply_num == 0 )
{
++previous_reply_num;
next;
}
if ( previous_reply_num in c$redis_state$pending &&
c$redis_state$pending[previous_reply_num]?$cmd )
{
Log::write(Redis::LOG, c$redis_state$pending[previous_reply_num]);
delete c$redis_state$pending[previous_reply_num];
}
previous_reply_num += 1;
}
# Log this one if we have the command and reply
if ( c$redis?$cmd )
{
Log::write(Redis::LOG, c$redis);
delete c$redis_state$pending[c$redis_state$current_reply];
}
}
event Redis::reply(c: connection, data: ReplyData)
{
if ( ! c?$redis_state )
make_new_state(c);
local previous_reply_num = c$redis_state$current_reply;
c$redis_state$current_reply = reply_num(c);
set_state(c, F);
c$redis$reply = data;
c$redis$success = T;
log_from(c, previous_reply_num);
}
event Redis::error(c: connection, data: ReplyData)
{
if ( ! c?$redis_state )
make_new_state(c);
local previous_reply_num = c$redis_state$current_reply;
c$redis_state$current_reply = reply_num(c);
set_state(c, F);
c$redis$reply = data;
c$redis$success = F;
log_from(c, previous_reply_num);
}
hook finalize_redis(c: connection)
{
if ( c$redis_state$violation )
{
# If there's a violation, don't log the remaining parts, just return.
return;
}
# Flush all pending but incomplete command/reply pairs.
if ( c?$redis_state && c$redis_state$current_reply != 0 )
{
for ( r, info in c$redis_state$pending )
{
# We don't use pending elements at index 0.
if ( r == 0 )
next;
Log::write(Redis::LOG, info);
}
}
}

View file

@ -0,0 +1,103 @@
##! Events and records generated by the Redis analyzer.
module Redis;
export {
## The Redis SET command.
type SetCommand: record {
## The key the SET command is setting.
key: string &log;
## The value the SET command is setting key to.
value: string &log;
## If NX is set -- only set the key if it does not exist.
nx: bool;
## If XX is set -- only set the key if it already exists.
xx: bool;
## If GET is set -- return the old string stored at key.
get: bool;
## EX option -- set the specified expire time, in seconds.
ex: count &optional;
## PX option -- set the specified expire time, in milliseconds.
px: count &optional;
## EXAT option-- set the specified Unix time at which the key will
## expire, in seconds.
exat: count &optional;
## PXAT option -- set the specified Unix time at which the key will
## expire, in milliseconds.
pxat: count &optional;
## If KEEPTTL is set -- retain the time to live associated with the key.
keep_ttl: bool;
};
## The Redis AUTH command.
type AuthCommand: record {
## The username getting authenticated.
username: string &optional;
## The password authenticated with.
password: string;
};
## A generic Redis command from the client.
type Command: record {
## The raw command, exactly as parsed
raw: vector of string;
## The first element of the command. Some commands are two strings, meaning
## this is inaccurate for those cases.
name: string &log;
## The key, if this command is known to have a key
key: string &log &optional;
## The value, if this command is known to have a value
value: string &log &optional;
## The command in an enum if it was known
known: KnownCommand &optional;
};
## A generic Redis reply from the client.
type ReplyData: record {
value: string &log &optional;
};
}
## Generated for Redis SET commands sent to the Redis server.
##
## c: The connection.
##
## command: The SET command sent to the server and its data.
global set_command: event(c: connection, command: SetCommand);
## Generated for Redis GET commands sent to the Redis server.
##
## c: The connection.
##
## command: The GET command sent to the server and its data.
global get_command: event(c: connection, key: string);
## Generated for Redis AUTH commands sent to the Redis server.
##
## c: The connection.
##
## command: The AUTH command sent to the server and its data.
global auth_command: event(c: connection, command: AuthCommand);
## Generated for every command sent by the client to the Redis server.
##
## c: The connection.
##
## cmd: The command sent to the server.
global command: event(c: connection, cmd: Command);
## Generated for every successful response sent by the Redis server to the
## client.
##
## c: The connection.
##
## data: The server data sent to the client.
global reply: event(c: connection, data: ReplyData);
## Generated for every error response sent by the Redis server to the
## client.
##
## c: The connection.
##
## data: The server data sent to the client.
global error: event(c: connection, data: ReplyData);

View file

@ -32,6 +32,7 @@ add_subdirectory(postgresql)
add_subdirectory(quic)
add_subdirectory(radius)
add_subdirectory(rdp)
add_subdirectory(redis)
add_subdirectory(rfb)
add_subdirectory(rpc)
add_subdirectory(sip)

View file

@ -0,0 +1,5 @@
spicy_add_analyzer(
NAME Redis
PACKAGE_NAME spicy-redis
SOURCES resp.spicy resp.evt redis.spicy
MODULES RESP Redis)

View file

@ -0,0 +1,403 @@
# See the file "COPYING" in the main distribution directory for copyright.
#
# Handle any Redis-specific "parsing"
module Redis;
import RESP;
public type KnownCommand = enum {
APPEND,
AUTH,
BITCOUNT,
BITFIELD,
BITFIELD_RO,
BITOP,
BITPOS,
BLMPOP,
BLPOP,
BRPOP,
CLIENT,
COPY,
DECR,
DECRBY,
DEL,
DUMP,
EXISTS,
EXPIRE,
EXPIREAT,
EXPIRETIME,
GET,
GETBIT,
GETDEL,
GETEX,
GETRANGE,
GETSET,
HDEL,
HGET,
HSET,
INCR,
INCRBY,
KEYS,
MGET,
MOVE,
MSET,
PERSIST,
RENAME,
SET,
STRLEN,
TTL,
TYPE,
};
type Command = struct {
raw: vector<bytes>;
name: bytes;
key: optional<bytes>;
value: optional<bytes>;
known: optional<KnownCommand>;
};
# This just assumes all elements in the array is a bulk string and puts them in a vector
public function make_command(command: RESP::ClientData): Command {
if (command?.multibulk)
return bulk_command(command);
else
return inline_command(command);
}
public function bulk_command(command: RESP::ClientData): Command {
local v: vector<bytes>;
for (ele in command.multibulk.elements) {
v.push_back(ele.content);
}
return parse_command(v);
}
public function inline_command(command: RESP::ClientData): Command {
# Only call this if it's inline :)
assert command?.inline;
local tokenized: vector<bytes>;
local it = command.inline.at(0);
# Redis whitespace characters are null, tab, LF, CR, and space
local whitespace = set<uint8>(0, 9, 10, 13, 32);
# Note: this logic is a bit different from Redis. Hopefully it doesn't matter
while (True) {
while (it != end(command.inline) && ((*it) in whitespace))
it++;
# Get a token
local start = it;
if (it != end(command.inline)) {
local double_quotes = False;
local single_quotes = False;
local done = False;
while (!done) {
if (double_quotes) {
if (*it == '\' && it + 1 != end(command.inline) && *(it + 1) == '"') {
# Skip one, then later we skip another
it++;
} else if (*it == '"') {
double_quotes = False;
}
it++;
} else if (single_quotes) {
if (*it == '\' && it + 1 != end(command.inline) && *(it + 1) == ''') {
# Skip one, then later we skip another
it++;
} else if (*it == ''') {
single_quotes = False;
}
it++;
} else {
if (it != end(command.inline)) {
switch (*it) {
case '"': double_quotes = True;
case ''': single_quotes = True;
default: {
if ((*it) in whitespace)
done = True;
}
}
if (!done)
it++;
}
}
if (it == end(command.inline))
done = True;
}
} else
break;
tokenized.push_back(command.inline.sub(start, it));
}
return parse_command(tokenized);
}
# Parses the vector of bytes to get a Command object
function parse_command(raw: vector<bytes>): Command {
assert |raw| >= 1;
local cmd = command_from(raw[0]);
local parsed: Command = [$raw = raw, $name = raw[0], $key = Null, $value = Null, $known = cmd];
if (!cmd)
return parsed;
if (|raw| >= 2) {
switch (*cmd) {
case KnownCommand::KEYS:
parsed.key = raw[1];
case KnownCommand::APPEND,
KnownCommand::BITCOUNT,
KnownCommand::BITFIELD,
KnownCommand::BITFIELD_RO,
KnownCommand::BITPOS,
KnownCommand::BLPOP,
KnownCommand::BRPOP,
KnownCommand::COPY,
KnownCommand::DECR,
KnownCommand::DECRBY,
KnownCommand::DEL,
KnownCommand::DUMP,
KnownCommand::EXISTS,
KnownCommand::EXPIRE,
KnownCommand::EXPIREAT,
KnownCommand::EXPIRETIME,
KnownCommand::GET,
KnownCommand::GETBIT,
KnownCommand::GETDEL,
KnownCommand::GETEX,
KnownCommand::GETRANGE,
KnownCommand::GETSET,
KnownCommand::HDEL,
KnownCommand::HGET,
KnownCommand::HSET,
KnownCommand::INCR,
KnownCommand::INCRBY,
KnownCommand::MGET,
KnownCommand::MOVE,
KnownCommand::MSET,
KnownCommand::PERSIST,
KnownCommand::RENAME,
KnownCommand::SET,
KnownCommand::STRLEN,
KnownCommand::TTL,
KnownCommand::TYPE:
parsed.key = raw[1];
default: ();
}
}
if (|raw| >= 3) {
switch (*cmd) {
case KnownCommand::SET,
KnownCommand::APPEND,
KnownCommand::DECRBY,
KnownCommand::EXPIRE,
KnownCommand::EXPIREAT,
KnownCommand::GETBIT,
KnownCommand::GETSET,
KnownCommand::HDEL,
KnownCommand::HGET,
KnownCommand::INCRBY,
KnownCommand::MOVE,
KnownCommand::MSET,
KnownCommand::RENAME:
parsed.value = raw[2];
# Op first, destination second, then a list of keys. Just log dest
case KnownCommand::BITOP: parsed.key = raw[2];
default: ();
}
}
if (|raw| >= 4) {
switch (*cmd) {
# timeout, numkeys, then key
case KnownCommand::BLMPOP: parsed.key = raw[3];
default: ();
}
}
return parsed;
}
function command_from(cmd_bytes: bytes): optional<KnownCommand> {
local cmd: optional<KnownCommand> = Null;
switch (cmd_bytes.lower()) {
case b"set": cmd = KnownCommand::SET;
case b"append": cmd = KnownCommand::APPEND;
case b"auth": cmd = KnownCommand::AUTH;
case b"bitcount": cmd = KnownCommand::BITCOUNT;
case b"bitfield": cmd = KnownCommand::BITFIELD;
case b"bitfield_ro": cmd = KnownCommand::BITFIELD_RO;
case b"bitop": cmd = KnownCommand::BITOP;
case b"bitpos": cmd = KnownCommand::BITPOS;
case b"blmpop": cmd = KnownCommand::BLMPOP;
case b"blpop": cmd = KnownCommand::BLPOP;
case b"brpop": cmd = KnownCommand::BRPOP;
case b"client": cmd = KnownCommand::CLIENT;
case b"copy": cmd = KnownCommand::COPY;
case b"decr": cmd = KnownCommand::DECR;
case b"decrby": cmd = KnownCommand::DECRBY;
case b"del": cmd = KnownCommand::DEL;
case b"dump": cmd = KnownCommand::DUMP;
case b"exists": cmd = KnownCommand::EXISTS;
case b"expire": cmd = KnownCommand::EXPIRE;
case b"expireat": cmd = KnownCommand::EXPIREAT;
case b"expiretime": cmd = KnownCommand::EXPIRETIME;
case b"expiretime": cmd = KnownCommand::EXPIRETIME;
case b"get": cmd = KnownCommand::GET;
case b"getbit": cmd = KnownCommand::GETBIT;
case b"getdel": cmd = KnownCommand::GETDEL;
case b"getex": cmd = KnownCommand::GETEX;
case b"getrange": cmd = KnownCommand::GETRANGE;
case b"getset": cmd = KnownCommand::GETSET;
case b"hdel": cmd = KnownCommand::HDEL;
case b"hget": cmd = KnownCommand::HGET;
case b"hset": cmd = KnownCommand::HSET;
case b"incr": cmd = KnownCommand::INCR;
case b"incrby": cmd = KnownCommand::INCRBY;
case b"keys": cmd = KnownCommand::KEYS;
case b"mget": cmd = KnownCommand::MGET;
case b"move": cmd = KnownCommand::MOVE;
case b"mset": cmd = KnownCommand::MSET;
case b"persist": cmd = KnownCommand::PERSIST;
case b"rename": cmd = KnownCommand::RENAME;
case b"strlen": cmd = KnownCommand::STRLEN;
case b"ttl": cmd = KnownCommand::TTL;
case b"type": cmd = KnownCommand::TYPE;
default: cmd = Null;
}
return cmd;
}
type Set = struct {
key: bytes;
value: bytes;
nx: bool &default=False;
xx: bool &default=False;
get: bool &default=False;
ex: optional<uint64> &default=Null;
px: optional<uint64> &default=Null;
exat: optional<uint64> &default=Null;
pxat: optional<uint64> &default=Null;
keep_ttl: bool &default=False;
};
public function make_set(command: Command): Set {
assert |command.raw| >= 3 : "Must have at least 3 elements in SET";
assert command.key : "SET must validate a key";
assert command.value : "SET must validate a value";
local parsed: Set = [$key = *command.key, $value = *command.value];
local i = 3;
while (i < |command.raw|) {
switch (command.raw[i].lower()) {
case b"nx": parsed.nx = True;
case b"xx": parsed.xx = True;
case b"get": parsed.get = True;
case b"ex": {
++i;
if (i >= |command.raw|)
break;
parsed.ex = command.raw[i].to_uint();
}
case b"px": {
++i;
if (i >= |command.raw|)
break;
parsed.px = command.raw[i].to_uint();
}
case b"exat": {
++i;
if (i >= |command.raw|)
break;
parsed.exat = command.raw[i].to_uint();
}
case b"pxat": {
++i;
if (i >= |command.raw|)
break;
parsed.pxat = command.raw[i].to_uint();
}
case b"keepttl": parsed.keep_ttl = True;
default: ();
}
++i;
}
return parsed;
}
public function is_set(data: RESP::ClientData): bool {
return data.command.known && *(data.command.known) == KnownCommand::SET && data.command.key && data.command.value;
}
type Get = struct {
key: bytes;
};
public function make_get(command: Command): Get {
assert command.key : "GET must validate a key";
return [$key = *command.key];
}
public function is_get(data: RESP::ClientData): bool {
return data.command.known && *(data.command.known) == KnownCommand::GET && |data.command.raw| >= 2;
}
type Auth = struct {
username: optional<bytes>;
password: bytes;
};
public function make_auth(command: Command): Auth {
assert |command.raw| >= 2 : "AUTH must have arguments";
if (|command.raw| == 2) {
return [$username = Null, $password = command.raw[1]];
}
return [$username = command.raw[1], $password = command.raw[2]];
}
public function is_auth(data: RESP::ClientData): bool {
return data.command.known && *(data.command.known) == KnownCommand::AUTH && |data.command.raw| >= 2;
}
type ReplyData = struct {
value: optional<bytes>;
};
public function is_err(server_data: RESP::ServerData): bool {
return server_data.data?.simple_error || server_data.data?.bulk_error;
}
function bulk_string_content(bulk: RESP::BulkString): bytes {
if (bulk?.content)
return bulk.content;
else
return b"";
}
# Gets the server reply in a simpler form
public function make_server_reply(data: RESP::ServerData): ReplyData {
local res: ReplyData = [$value = Null];
if (data.data?.simple_error)
res.value = data.data.simple_error.content;
else if (data.data?.bulk_error)
res.value = bulk_string_content(data.data.bulk_error);
else if (data.data?.simple_string)
res.value = data.data.simple_string.content;
else if (data.data?.bulk_string)
res.value = bulk_string_content(data.data.bulk_string);
else if (data.data?.verbatim_string)
res.value = bulk_string_content(data.data.verbatim_string);
else if (data.data?.boolean)
res.value = data.data.boolean.val ? b"T" : b"F";
return res;
}

View file

@ -0,0 +1,20 @@
# See the file "COPYING" in the main distribution directory for copyright.
protocol analyzer Redis over TCP:
parse originator with RESP::ClientMessages,
parse responder with RESP::ServerMessages;
import RESP;
import Redis;
export Redis::KnownCommand;
on RESP::ClientData if ( Redis::is_set(self) ) -> event Redis::set_command($conn, Redis::make_set(self.command));
on RESP::ClientData if ( Redis::is_get(self) ) -> event Redis::get_command($conn, Redis::make_get(self.command).key);
on RESP::ClientData if ( Redis::is_auth(self) ) -> event Redis::auth_command($conn, Redis::make_auth(self.command));
# All client data is a command
on RESP::ClientData -> event Redis::command($conn, self.command);
on RESP::ServerData if ( ! Redis::is_err(self) ) -> event Redis::reply($conn, Redis::make_server_reply(self));
on RESP::ServerData if ( Redis::is_err(self) ) -> event Redis::error($conn, Redis::make_server_reply(self));

View file

@ -0,0 +1,211 @@
# See the file "COPYING" in the main distribution directory for copyright.
module RESP;
import Redis;
import spicy;
# Maximum size for parsing of certain fields. By restricting this we avoid
# exhausting main memory.
const MAX_SIZE = 1024 * 1024;
const MAX_RECURSION_DEPTH = 20;
public type ClientMessages = unit {
# The context here refers to whether we saw client data first. It is a one-time switch,
# either we have seen client data or not.
%context = bool;
on %init {
*self.context() = True;
}
: ClientData[];
};
public type ServerMessages = unit {
%context = bool;
on %init {
if (!*self.context()) {
throw "Server responses must come after a client request is seen";
}
}
: (ServerData &synchronize)[];
};
public type ClientData = unit {
on %init() {
self.start = self.input();
}
# Clients can only be an array or inline
ty: uint8 &convert=DataType($$) {
if (self.ty != DataType::ARRAY) {
# This is inline, so we need to reparse `ty`
self.set_input(self.start);
}
}
if (self.ty == DataType::ARRAY) {
multibulk: BulkStringArray;
} else {
inline: RedisBytes &max-size=1024;
};
var start: iterator<stream>;
var command: Redis::Command;
on %done {
self.command = Redis::make_command(self);
}
};
type BulkStringArray = unit {
num_elements: RedisBytes &convert=$$.to_int(10) &requires=self.num_elements <= int64(MAX_SIZE);
# Null array is an array with elements unset. This is different from an empty array
elements: BulkStringWithTy[uint64(self.num_elements)];
};
type BulkStringWithTy = unit {
# Need to consume the type here
: uint8 &requires=$$ == '$';
length: RedisBytes &convert=$$.to_int(10) &requires=self.length <= int64(MAX_SIZE);
# NullBulkString is a BulkString with content unset
content: bytes &size=uint64(self.length) if(self.length >= 0);
# Consume last CLRF
: skip RedisBytes;
};
public type ServerData = unit {
%synchronize-after = b"\x0d\x0a";
var depth: uint8& = new uint8;
data: Data(self.depth);
};
type Data = unit(depth: uint8&) {
%synchronize-after = b"\x0d\x0a";
ty: uint8 &convert=DataType($$);
switch (self.ty) {
DataType::SIMPLE_STRING -> simple_string: SimpleString(False);
DataType::SIMPLE_ERROR -> simple_error: SimpleString(True);
DataType::INTEGER -> integer: Integer;
DataType::BULK_STRING -> bulk_string: BulkString(False);
DataType::ARRAY -> array: Array(depth);
DataType::NULL -> null: Null_;
DataType::BOOLEAN -> boolean: Boolean;
DataType::DOUBLE -> double: Double;
DataType::BIG_NUM -> big_num: BigNum;
DataType::BULK_ERROR -> bulk_error: BulkString(True);
# This can be a different type, but the docs also say:
# "Some client libraries may ignore the difference between this type and the string type"
# It just includes the encoding first in the content
DataType::VERBATIM_STRING -> verbatim_string: BulkString(False);
DataType::MAP -> map_: Map(depth);
DataType::SET -> set_: Set(depth);
# "Push events are encoded similarly to arrays, differing only in their
# first byte" - TODO: can probably make it more obvious, though
DataType::PUSH -> push: Array(depth);
};
on %init {
depth++;
if (*depth > MAX_RECURSION_DEPTH)
throw "exceeded max recursion depth";
}
on %done {
depth--;
}
};
type DataType = enum {
SIMPLE_STRING = '+',
SIMPLE_ERROR = '-',
INTEGER = ':',
BULK_STRING = '$',
ARRAY = '*',
NULL = '_',
BOOLEAN = '#',
DOUBLE = ',',
BIG_NUM = '(',
BULK_ERROR = '!',
VERBATIM_STRING = '=',
MAP = '%',
SET = '~',
PUSH = '>',
};
# Helper unit to extract bytes of some reasonable size so we do not exhaust mem.
type RedisBytes = unit {
data: bytes &until=b"\x0d\x0a" &max-size=MAX_SIZE;
} &convert=self.data;
type SimpleString = unit(is_error: bool) {
content: RedisBytes;
};
type Integer = unit {
int: RedisBytes &convert=$$.to_int(10);
};
type BulkString = unit(is_error: bool) {
length: RedisBytes &convert=$$.to_int(10) &requires=self.length <= int64(MAX_SIZE);
# NullBulkString is a BulkString with content unset
content: bytes &size=uint64(self.length) if(self.length >= 0);
# Consume last CLRF if not a null bulk string
: skip RedisBytes if(self.length >= 0);
};
type Array = unit(depth: uint8&) {
num_elements: RedisBytes &convert=$$.to_int(10) &requires=self.num_elements <= int64(MAX_SIZE);
# Null array is an array with elements unset. This is different from an empty array
elements: Data(depth)[uint64(self.num_elements)];
};
type Null_ = unit {
# Still must consume CLRF
: skip RedisBytes;
};
type Boolean = unit {
val: uint8 &convert=$$ == 't';
: skip RedisBytes;
};
type Double = unit {
val: RedisBytes &convert=$$.to_real();
};
type BigNum = unit {
# Big num can be very big so leave it in bytes.
val: RedisBytes;
};
type Map = unit(depth: uint8&) {
num_elements: RedisBytes &convert=$$.to_uint(10);
# TODO: How can I make this into a map? Alternatively, how can I do this better?
raw_data: Data(depth)[self.num_elements * 2];
# TODO: This is broken. See https://github.com/zeek/spicy/issues/2061
# var key_val_pairs: vector<tuple<Data, Data>>;
# on raw_data {
# while (local i = 0; i < self.num_elements) {
# self.key_val_pairs.push_back(($$[i], $$[i + 1]));
# i += 2;
# }
# }
};
type Set = unit(depth: uint8&) {
num_elements: RedisBytes &convert=$$.to_uint(10) &requires=self.num_elements <= MAX_SIZE;
# TODO: This should be a set but doesn't go in the backed C++ set
elements: Data(depth)[self.num_elements];
};
on ServerData::%done {
spicy::accept_input();
}
on ServerData::%error {
spicy::decline_input("error while parsing RESP server data");
}

View file

@ -46,6 +46,7 @@
1 614
1 631
1 636
1 6379
1 6666
1 6667
1 6668
@ -66,8 +67,8 @@
1 992
1 993
1 995
75 and
74 or
75 port
47 tcp
76 and
75 or
76 port
48 tcp
28 udp

View file

@ -466,6 +466,9 @@ scripts/base/init-default.zeek
scripts/base/protocols/rdp/__load__.zeek
scripts/base/protocols/rdp/consts.zeek
scripts/base/protocols/rdp/main.zeek
scripts/base/protocols/redis/__load__.zeek
scripts/base/protocols/redis/spicy-events.zeek
scripts/base/protocols/redis/main.zeek
scripts/base/protocols/rfb/__load__.zeek
scripts/base/protocols/rfb/main.zeek
scripts/base/protocols/sip/__load__.zeek

View file

@ -46,6 +46,7 @@ print_log_path
quic
radius
rdp
redis
reporter
rfb
signatures

View file

@ -593,6 +593,39 @@ connection {
* ts: time, log=T, optional=F
* uid: string, log=T, optional=F
}
* redis: record Redis::Info, log=F, optional=T
Redis::Info {
* cmd: record Redis::Command, log=T, optional=F
Redis::Command {
* key: string, log=T, optional=T
* known: enum Redis::KnownCommand, log=F, optional=T
* name: string, log=T, optional=F
* raw: vector of string, log=F, optional=F
* value: string, log=T, optional=T
}
* id: record conn_id, log=T, optional=F
conn_id { ... }
* reply: record Redis::ReplyData, log=T, optional=T
Redis::ReplyData {
* value: string, log=T, optional=T
}
* success: bool, log=T, optional=T
* ts: time, log=T, optional=F
* uid: string, log=T, optional=F
}
* redis_state: record Redis::State, log=F, optional=T
Redis::State {
* current_command: count, log=F, optional=T
* current_reply: count, log=F, optional=T
* no_reply_ranges: vector of record Redis::NoReplyRange, log=F, optional=F
Redis::NoReplyRange {
* begin: count, log=F, optional=F
* end: count, log=F, optional=T
}
* pending: table[count] of record Redis::Info, log=F, optional=F
Redis::Info { ... }
* violation: bool, log=F, optional=T
}
* removal_hooks: set[func], log=F, optional=T
* resp: record endpoint, log=F, optional=F
endpoint { ... }

View file

@ -0,0 +1,12 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 53099 127.0.0.1 6379 AUTH - - T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 53099 127.0.0.1 6379 PING - - T OK
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,13 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
AUTH
username: notauser
password: notapassword
Auth failed:, WRONGPASS invalid username-password pair or user is disabled.
AUTH
username: default
password: defaultpassword
Auth succeeded:, OK
AUTH
username: noone
password: password
Auth failed:, WRONGPASS invalid username-password pair or user is disabled.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 61211 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 61212 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 61211 ::1 6379 CLIENT - - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 61211 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 61212 ::1 6379 CLIENT - - - -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h ::1 61212 ::1 6379 PING - - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,18 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 60761 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 60761 ::1 6379 CLIENT - - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 60761 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 60761 ::1 6379 CLIENT - - T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 60761 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 60761 ::1 6379 CLIENT - - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 60761 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 60761 ::1 6379 PING - - T PONG
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,17 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56348 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56348 ::1 6379 CLIENT - - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56348 ::1 6379 PING - - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56348 ::1 6379 CLIENT - - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56348 ::1 6379 CLIENT - - T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56348 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56348 ::1 6379 PING - - T PONG
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path conn
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents ip_proto
#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string] count
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 tcp redis 13.539827 18106 928 OTH T F 0 DdA 316 34538 158 9144 - 6
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
Factorial of 100 is 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
Found 152 SET commands

View file

@ -0,0 +1,168 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 GET :1:factorial_3 - T 6
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 GET :1:factorial_3 - T 6
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 GET :1:factorial_50 - T (empty)
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_1 1 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_2 2 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_3 6 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_4 24 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_5 120 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_6 720 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_7 5040 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_8 40320 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_9 362880 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_10 3628800 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_11 39916800 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_12 479001600 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_13 6227020800 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_14 87178291200 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_15 1307674368000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_16 20922789888000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_17 355687428096000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_18 6402373705728000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_19 121645100408832000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_20 2432902008176640000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_21 51090942171709440000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_22 1124000727777607680000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_23 25852016738884976640000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_24 620448401733239439360000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_25 15511210043330985984000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_26 403291461126605635584000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_27 10888869450418352160768000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_28 304888344611713860501504000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_29 8841761993739701954543616000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_30 265252859812191058636308480000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_31 8222838654177922817725562880000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_32 263130836933693530167218012160000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_33 8683317618811886495518194401280000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_34 295232799039604140847618609643520000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_35 10333147966386144929666651337523200000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_36 371993326789901217467999448150835200000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_37 13763753091226345046315979581580902400000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_38 523022617466601111760007224100074291200000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_39 20397882081197443358640281739902897356800000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_40 815915283247897734345611269596115894272000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_41 33452526613163807108170062053440751665152000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_42 1405006117752879898543142606244511569936384000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_43 60415263063373835637355132068513997507264512000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_44 2658271574788448768043625811014615890319638528000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_45 119622220865480194561963161495657715064383733760000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_46 5502622159812088949850305428800254892961651752960000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_47 258623241511168180642964355153611979969197632389120000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_48 12413915592536072670862289047373375038521486354677760000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_49 608281864034267560872252163321295376887552831379210240000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_50 30414093201713378043612608166064768844377641568960512000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_50 30414093201713378043612608166064768844377641568960512000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 GET :1:factorial_50 - T 30414093201713378043612608166064768844377641568960512000000000000
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 GET :1:factorial_50 - T 30414093201713378043612608166064768844377641568960512000000000000
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 GET :1:factorial_100 - T (empty)
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_1 1 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_2 2 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_3 6 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_4 24 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_5 120 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_6 720 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_7 5040 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_8 40320 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_9 362880 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_10 3628800 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_11 39916800 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_12 479001600 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_13 6227020800 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_14 87178291200 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_15 1307674368000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_16 20922789888000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_17 355687428096000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_18 6402373705728000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_19 121645100408832000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_20 2432902008176640000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_21 51090942171709440000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_22 1124000727777607680000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_23 25852016738884976640000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_24 620448401733239439360000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_25 15511210043330985984000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_26 403291461126605635584000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_27 10888869450418352160768000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_28 304888344611713860501504000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_29 8841761993739701954543616000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_30 265252859812191058636308480000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_31 8222838654177922817725562880000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_32 263130836933693530167218012160000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_33 8683317618811886495518194401280000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_34 295232799039604140847618609643520000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_35 10333147966386144929666651337523200000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_36 371993326789901217467999448150835200000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_37 13763753091226345046315979581580902400000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_38 523022617466601111760007224100074291200000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_39 20397882081197443358640281739902897356800000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_40 815915283247897734345611269596115894272000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_41 33452526613163807108170062053440751665152000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_42 1405006117752879898543142606244511569936384000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_43 60415263063373835637355132068513997507264512000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_44 2658271574788448768043625811014615890319638528000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_45 119622220865480194561963161495657715064383733760000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_46 5502622159812088949850305428800254892961651752960000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_47 258623241511168180642964355153611979969197632389120000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_48 12413915592536072670862289047373375038521486354677760000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_49 608281864034267560872252163321295376887552831379210240000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_50 30414093201713378043612608166064768844377641568960512000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_51 1551118753287382280224243016469303211063259720016986112000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_52 80658175170943878571660636856403766975289505440883277824000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_53 4274883284060025564298013753389399649690343788366813724672000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_54 230843697339241380472092742683027581083278564571807941132288000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_55 12696403353658275925965100847566516959580321051449436762275840000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_56 710998587804863451854045647463724949736497978881168458687447040000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_57 40526919504877216755680601905432322134980384796226602145184481280000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_58 2350561331282878571829474910515074683828862318181142924420699914240000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_59 138683118545689835737939019720389406345902876772687432540821294940160000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_60 8320987112741390144276341183223364380754172606361245952449277696409600000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_61 507580213877224798800856812176625227226004528988036003099405939480985600000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_62 31469973260387937525653122354950764088012280797258232192163168247821107200000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_63 1982608315404440064116146708361898137544773690227268628106279599612729753600000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_64 126886932185884164103433389335161480802865516174545192198801894375214704230400000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_65 8247650592082470666723170306785496252186258551345437492922123134388955774976000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_66 544344939077443064003729240247842752644293064388798874532860126869671081148416000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_67 36471110918188685288249859096605464427167635314049524593701628500267962436943872000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_68 2480035542436830599600990418569171581047399201355367672371710738018221445712183296000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_69 171122452428141311372468338881272839092270544893520369393648040923257279754140647424000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_70 11978571669969891796072783721689098736458938142546425857555362864628009582789845319680000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_71 850478588567862317521167644239926010288584608120796235886430763388588680378079017697280000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_72 61234458376886086861524070385274672740778091784697328983823014963978384987221689274204160000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_73 4470115461512684340891257138125051110076800700282905015819080092370422104067183317016903680000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_74 330788544151938641225953028221253782145683251820934971170611926835411235700971565459250872320000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_75 24809140811395398091946477116594033660926243886570122837795894512655842677572867409443815424000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_76 1885494701666050254987932260861146558230394535379329335672487982961844043495537923117729972224000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_77 145183092028285869634070784086308284983740379224208358846781574688061991349156420080065207861248000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_78 11324281178206297831457521158732046228731749579488251990048962825668835325234200766245086213177344000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_79 894618213078297528685144171539831652069808216779571907213868063227837990693501860533361810841010176000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_80 71569457046263802294811533723186532165584657342365752577109445058227039255480148842668944867280814080000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_81 5797126020747367985879734231578109105412357244731625958745865049716390179693892056256184534249745940480000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_82 475364333701284174842138206989404946643813294067993328617160934076743994734899148613007131808479167119360000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_83 39455239697206586511897471180120610571436503407643446275224357528369751562996629334879591940103770870906880000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_84 3314240134565353266999387579130131288000666286242049487118846032383059131291716864129885722968716753156177920000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_85 281710411438055027694947944226061159480056634330574206405101912752560026159795933451040286452340924018275123200000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_86 24227095383672732381765523203441259715284870552429381750838764496720162249742450276789464634901319465571660595200000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_87 2107757298379527717213600518699389595229783738061356212322972511214654115727593174080683423236414793504734471782400000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_88 185482642257398439114796845645546284380220968949399346684421580986889562184028199319100141244804501828416633516851200000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_89 16507955160908461081216919262453619309839666236496541854913520707833171034378509739399912570787600662729080382999756800000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_90 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_91 135200152767840296255166568759495142147586866476906677791741734597153670771559994765685283954750449427751168336768008192000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_92 12438414054641307255475324325873553077577991715875414356840239582938137710983519518443046123837041347353107486982656753664000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_93 1156772507081641574759205162306240436214753229576413535186142281213246807121467315215203289516844845303838996289387078090752000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_94 108736615665674308027365285256786601004186803580182872307497374434045199869417927630229109214583415458560865651202385340530688000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_95 10329978488239059262599702099394727095397746340117372869212250571234293987594703124871765375385424468563282236864226607350415360000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_96 991677934870949689209571401541893801158183648651267795444376054838492222809091499987689476037000748982075094738965754305639874560000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_97 96192759682482119853328425949563698712343813919172976158104477319333745612481875498805879175589072651261284189679678167647067832320000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_98 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_99 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_100 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.1.4 50044 18.234.186.95 10625 SET :1:factorial_100 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 T OK
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,22 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 - - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 - - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 - - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 - - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 - - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 - - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 - - - T PONG
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path weird
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p name addl notice peer source
#types time string addr port addr port string string bool string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 57156 ::1 6379 Redis_excessive_pipelining - F zeek -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,18 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 63754 ::1 6379 SET key "my value with spaces" T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 63754 ::1 6379 SET key2 'my value with single quotes' T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 63754 ::1 6379 SET key3 'my value with "double" inners' T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 63754 ::1 6379 SET key4 "my value with 'single' inners" T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 63754 ::1 6379 SET key5 "my value with \\"escaped\\" quotes" T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 63754 ::1 6379 SET key6 'my value with \\'escaped\\' quotes' T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 63754 ::1 6379 SET bad1 "unclosed double quotes F ERR Protocol error: unbalanced quotes in request
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 63754 ::1 6379 SET bad2 'unclosed single quotes - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
SET: HI 3
GET: HI

View file

@ -0,0 +1,14 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56731 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56731 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56731 ::1 6379 SET HI 3 T OK
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 56731 ::1 6379 GET HI - T 3
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,13 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 51122 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 51122 ::1 6379 PING - - T PONG
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 ::1 51122 ::1 6379 PING - - T PONG
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,13 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 56162 127.0.0.1 6379 SUBSCRIBE - - T -
XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h 127.0.0.1 56163 127.0.0.1 6379 PUBLISH - - T -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 56162 127.0.0.1 6379 - - - T -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
Key: test Value: hi
Key: one:1 Value: 2
Key: two:2 Value: three

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,14 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path redis
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p cmd.name cmd.key cmd.value success reply.value
#types time string addr port addr port string string string bool string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 49992 127.0.0.1 6379 XADD - - T 1729622832637-0
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 49992 127.0.0.1 6379 XADD - - T 1729622836953-0
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 49992 127.0.0.1 6379 XADD - - T 1729622840530-0
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 49992 127.0.0.1 6379 XRANGE - - T -
#close XXXX-XX-XX-XX-XX-XX

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,11 @@
# @TEST-DOC: Test 2 commands that look like RESP, then server responses don't
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/almost-resp.pcap %INPUT >output
# @TEST-EXEC: btest-diff redis.log
#
# Really, the first 2 ARE Redis. The later ones should not be logged because we
# realized it's not Redis. The output from the server is:
# +OK\r\n+OK\r\nnot RESP\r\nStill not RESP\r\nNope\r\n
@load base/protocols/redis

View file

@ -0,0 +1,28 @@
# @TEST-DOC: Test Zeek with AUTH commands
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/auth.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
@load base/protocols/redis
event Redis::auth_command(c: connection, command: Redis::AuthCommand)
{
print "AUTH";
if ( command?$username )
print fmt("username: %s", command$username);
else
print "username: default";
print fmt("password: %s", command$password);
}
event Redis::reply(c: connection, data: Redis::ReplyData)
{
print "Auth succeeded:", data$value;
}
event Redis::error(c: connection, data: Redis::ReplyData)
{
print "Auth failed:", data$value;
}

View file

@ -0,0 +1,17 @@
# @TEST-DOC: Test Zeek parsing a trace file made with bulk-created SET commands
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/bulk-loading.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
# The bulk-loading functionality just sends the serialized form from some ruby
# code directly to the server, but it's useful to see if that trace might come
# up with something different. See:
# https://redis.io/docs/latest/develop/use/patterns/bulk-loading/
@load base/protocols/redis
event Redis::set_command(c: connection, command: Redis::SetCommand)
{
print fmt("SET: %s %s", command$key, command$value);
}

View file

@ -0,0 +1,7 @@
# @TEST-DOC: Test CLIENT REPLY OFF, but turns on with new connection
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/reply-off-on-2conn.pcap %INPUT >output
# @TEST-EXEC: btest-diff redis.log
@load base/protocols/redis

View file

@ -0,0 +1,7 @@
# @TEST-DOC: Test CLIENT REPLY OFF then ON again and a SKIP
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/reply-off-on.pcap %INPUT >output
# @TEST-EXEC: btest-diff redis.log
@load base/protocols/redis

View file

@ -0,0 +1,7 @@
# @TEST-DOC: Test CLIENT REPLY OFF then ON again and a SKIP
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/client-skip-while-off.pcap %INPUT >output
# @TEST-EXEC: btest-diff redis.log
@load base/protocols/redis

View file

@ -0,0 +1,39 @@
# @TEST-DOC: Test Redis traffic from a django app using Redis (in the cloud) as a cache
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/django-cloud.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
# @TEST-EXEC: btest-diff redis.log
# @TEST-EXEC: btest-diff conn.log
# This test has a bunch of factorial commands, try to test for the correct
# factorial without exploding the baseline
@load base/protocols/conn
@load base/protocols/redis
redef Redis::ports += {
10625/tcp,
};
global largest_num: count = 0;
global largest_result: string = "";
global num_sets: count = 0;
event Redis::set_command(c: connection, command: Redis::SetCommand)
{
local factorial_of = to_count(command$key[13:]);
if ( factorial_of > largest_num )
{
largest_num = factorial_of;
largest_result = command$value[:];
}
num_sets += 1;
}
event zeek_done()
{
print fmt("Factorial of %d is %s", largest_num, largest_result);
print fmt("Found %d SET commands", num_sets);
}

View file

@ -0,0 +1,11 @@
# @TEST-DOC: Test Zeek parsing "pipelined" data responses
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/excessive-pipelining.pcap %INPUT >output
# @TEST-EXEC: btest-diff redis.log
# @TEST-EXEC: btest-diff weird.log
@load base/protocols/redis
# Make sure we get a weird if we go over the pipelining threshold (intentionally limited)
redef Redis::max_pending_commands = 5;

View file

@ -0,0 +1,12 @@
# @TEST-DOC: Test Zeek parsing "pipelined" data responses
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/pipeline-quotes.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
# @TEST-EXEC: btest-diff redis.log
# TODO: Make it so weird.log exists again with `zeek::weird` for inline commands
# btest-diff weird.log
# Tests unserialized data where quotes should make one token
@load base/protocols/redis

View file

@ -0,0 +1,22 @@
# @TEST-DOC: Test Zeek parsing "pipelined" data responses
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/pipeline-with-commands.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
# @TEST-EXEC: btest-diff redis.log
# Sometimes commands aren't serialized, like when pipelining. This still works! So we
# should handle this. This particular example has a few commands, amongst them a SET and
# a GET.
@load base/protocols/redis
event Redis::set_command(c: connection, command: Redis::SetCommand)
{
print fmt("SET: %s %s", command$key, command$value);
}
event Redis::get_command(c: connection, key: string)
{
print fmt("GET: %s", key);
}

View file

@ -0,0 +1,15 @@
# @TEST-DOC: Test Zeek parsing "pipelined" data responses
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/pipelining-example.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
# @TEST-EXEC: btest-diff redis.log
# Testing the example of "pipelining" in REDIS docs:
# https://redis.io/docs/latest/develop/use/pipelining/
# Namely sending three PINGs. This does not get sent as RESP data, but we should
# be able to skip it and get the responses, which are properly encoded.
#
# Also, you can send serialized data this way - that's kinda what the bulk test does.
@load base/protocols/redis

View file

@ -0,0 +1,12 @@
# @TEST-DOC: Test Zeek parsing pubsub commands
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/pubsub.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
# @TEST-EXEC: btest-diff redis.log
# Testing the example of pub sub in REDIS docs:
# https://redis.io/docs/latest/develop/interact/pubsub/
# These are just commands between two different clients, one PUBLISH and one SUBSCRIBE
@load base/protocols/redis

View file

@ -0,0 +1,12 @@
# @TEST-DOC: Test Zeek parsing SET commands
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/set.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
@load base/protocols/redis
event Redis::set_command(c: connection, command: Redis::SetCommand)
{
print fmt("Key: %s Value: %s", command$key, command$value);
}

View file

@ -0,0 +1,22 @@
# @TEST-DOC: Test that Redis does not parse if it starts with the server data
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/start-with-server.pcap %INPUT >output
# @TEST-EXEC: btest-diff output
@load base/protocols/redis
event Redis::command(c: connection, command: Redis::Command)
{
print "BAD", command;
}
event Redis::reply(c: connection, dat: Redis::ReplyData)
{
print "BAD", dat;
}
event Redis::error(c: connection, dat: Redis::ReplyData)
{
print "BAD", dat;
}

View file

@ -0,0 +1,10 @@
# @TEST-DOC: Test Zeek parsing pubsub commands
# @TEST-REQUIRES: have-spicy
#
# @TEST-EXEC: zeek -b -r $TRACES/redis/stream.pcap %INPUT >output
# @TEST-EXEC: btest-diff redis.log
# Streams like with XRANGE return arrays of bulk strings. We shouldn't count the
# response as commands.
@load base/protocols/redis

View file

@ -1 +1 @@
d20f3027e30434d340f1d3b45b5f86c84e5c74e0
f7c740ab3c2781252ab7d0620715091f6b61ae5d