Merge remote branch 'origin/topic/gregor/rpc'

Note, I haven't gone through the script-level code as that will change
soon anyway.
This commit is contained in:
Robin Sommer 2011-06-07 15:39:09 -07:00
parent 8266709e20
commit 5bd8caa7a0
42 changed files with 2161 additions and 1288 deletions

View file

@ -720,54 +720,187 @@ const RPC_status = {
[RPC_UNKNOWN_ERROR] = "unknown"
};
module NFS3;
type nfs3_file_type: enum {
NFS3_REG, NFS3_DIR, NFS3_BLK, NFS3_CHR, NFS3_LNK, NFS3_SOCK, NFS3_FIFO,
};
export {
# Should the read and write events return the file data that has been
# read/written?
const return_data = F &redef;
type nfs3_attrs: record {
ftype: nfs3_file_type;
mode: count;
nlink: count;
uid: count;
gid: count;
size: double;
used: double;
rdev1: count;
rdev2: count;
fsid: double;
fileid: double;
atime: time;
mtime: time;
ctime: time;
};
# If nfs_return_data is true, how much data should be returned at most.
const return_data_max = 512 &redef;
type nfs3_opt_attrs: record {
attrs: nfs3_attrs &optional;
};
# If nfs_return_data is true, whether to *only* return data if the read or write
# offset is 0, i.e., only return data for the beginning of the file.
const return_data_first_only = T &redef;
type nfs3_lookup_args: record {
fh: string; # file handle of directory in which to search
name: string; # name of file to look for in directory
};
# This record summarizes the general results and status of NFSv3 request/reply
# pairs. It's part of every NFSv3 event.
type info_t: record {
rpc_stat: rpc_status; # If this indicates not successful, the reply record in the
# events will be empty and contain uninitialized fields, so
# don't use it.
nfs_stat: status_t;
type nfs3_lookup_reply: record {
fh: string; # file handle of object looked up
file_attr: nfs3_opt_attrs; # optional attributes associated w/ file
dir_attr: nfs3_opt_attrs; # optional attributes associated w/ dir.
};
# The start time, duration, and length in bytes of the request (call). Note that
# the start and end time might not be accurate. For TCP, we record the
# time when a chunk of data is delivered to the analyzer. Depending on the
# Reassembler, this might be well after the first packet of the request
# was received.
req_start: time;
req_dur: interval;
req_len: count;
type nfs3_fsstat: record {
attrs: nfs3_opt_attrs;
tbytes: double;
fbytes: double;
abytes: double;
tfiles: double;
ffiles: double;
afiles: double;
invarsec: interval;
};
# Same for the reply.
rep_start: time;
rep_dur: interval;
rep_len: count;
};
# NFSv3 types. Type names are based on RFC 1813.
type fattr_t: record {
ftype: file_type_t;
mode: count;
nlink: count;
uid: count;
gid: count;
size: count;
used: count;
rdev1: count;
rdev2: count;
fsid: count;
fileid: count;
atime: time;
mtime: time;
ctime: time;
};
type diropargs_t : record {
dirfh: string; # the file handle of the directory
fname: string; # the name of the file we are interested in
};
# Note, we don't need a "post_op_attr" type. We use an "fattr_t &optional"
# instead.
type lookup_reply_t: record {
# If the lookup failed, dir_attr may be set.
# If the lookup succeeded, fh is always set and obj_attr and dir_attr may be set.
fh: string &optional; # file handle of object looked up
obj_attr: fattr_t &optional; # optional attributes associated w/ file
dir_attr: fattr_t &optional; # optional attributes associated w/ dir.
};
type readargs_t: record {
fh: string; # file handle to read from
offset: count; # offset in file
size: count; # number of bytes to read
};
type read_reply_t: record {
# If the lookup fails, attr may be set. If the lookup succeeds, attr may be set
# and all other fields are set.
attr: fattr_t &optional; # attributes
size: count &optional; # number of bytes read
eof: bool &optional; # did the read end at EOF
data: string &optional; # the actual data; not yet implemented.
};
type readlink_reply_t: record {
# If the request fails, attr may be set. If the request succeeds, attr may be
# set and all other fields are set.
attr: fattr_t &optional; # attributes
nfspath: string &optional; # the contents of the symlink; in general a pathname as text
};
type writeargs_t: record {
fh: string; # file handle to write to
offset: count; # offset in file
size: count; # number of bytes to write
stable: stable_how_t; # how and when data is commited
data: string &optional; # the actual data; not implemented yet
};
type wcc_attr_t: record {
size: count;
atime: time;
mtime: time;
};
type write_reply_t: record {
# If the request fails, pre|post attr may be set. If the request succeeds,
# pre|post attr may be set and all other fields are set.
preattr: wcc_attr_t &optional; # pre operation attributes
postattr: fattr_t &optional; # post operation attributes
size: count &optional;
commited: stable_how_t &optional;
verf: count &optional; # write verifier cookue
};
# reply for create, mkdir, symlink
type newobj_reply_t: record {
# If the proc failed, dir_*_attr may be set. If the proc succeeded, fh and
# the attr's may be set. Note: no guarantee that fh is set after
# success.
fh: string &optional; # file handle of object created
obj_attr: fattr_t &optional; # optional attributes associated w/ new object
dir_pre_attr: wcc_attr_t &optional; # optional attributes associated w/ dir
dir_post_attr: fattr_t &optional; # optional attributes associated w/ dir
};
# reply for remove, rmdir
# Corresponds to "wcc_data" in the spec.
type delobj_reply_t: record {
dir_pre_attr: wcc_attr_t &optional; # optional attributes associated w/ dir
dir_post_attr: fattr_t &optional; # optional attributes associated w/ dir
};
# This record is used for both readdir and readdirplus.
type readdirargs_t: record {
isplus: bool; # is this a readdirplus request?
dirfh: string; # the directory filehandle
cookie: count; # cookie / pos in dir; 0 for first call
cookieverf: count; # the cookie verifier
dircount: count; # "count" field for readdir; maxcount otherwise (in bytes)
maxcount: count &optional; # only used for readdirplus. in bytes
};
type direntry_t: record {
# fh and attr are used for readdirplus. However, even for readdirplus they may
# not be filled out.
fileid: count; # e.g., inode number
fname: string; # filename
cookie: count;
attr: fattr_t &optional; # readdirplus: the FH attributes for the entry
fh: string &optional; # readdirplus: the FH for the entry
};
type direntry_vec_t: vector of direntry_t;
# Used for readdir and readdirplus.
type readdir_reply_t: record {
# If error: dir_attr might be set. If success: dir_attr may be set, all others
# must be set.
isplus: bool; # is the reply for a readdirplus request
dir_attr: fattr_t &optional;
cookieverf: count &optional;
entries: direntry_vec_t &optional;
eof: bool; # if true, no more entries in dir.
};
type fsstat_t: record {
attrs: fattr_t &optional;
tbytes: double;
fbytes: double;
abytes: double;
tfiles: double;
ffiles: double;
afiles: double;
invarsec: interval;
};
} # end export
module GLOBAL;
type ntp_msg: record {
id: count;
@ -1249,6 +1382,11 @@ global load_sample_freq = 20 &redef;
# degree the measurement process appears to exhibit loss.
const gap_report_freq = 1.0 sec &redef;
# Whether we want content_gap and drop reports for partial connections
# (a connection is partial if it is missing a full handshake). Note that
# gap reports for partial connections might not be reliable.
const report_gaps_for_partial = F &redef;
# Globals associated with entire-run statistics on gaps (useful
# for final summaries).

View file

@ -1,14 +1,24 @@
# $Id: nfs.bro 4017 2007-02-28 07:11:54Z vern $
@load udp
module NFS;
module NFS3;
export {
global log_file = open_log_file("nfs") &redef;
global names_log_file = open_log_file("nfs-files") &redef;
global readdir_log = open_log_file("nfs-readdir") &redef;
# We want to estimate how long it takes to lookup a chain of FH (directories)
# until we reach a FH that is used in a read or write operation. Whenever we
# get a new FH, we check how long ago we got the FH's parent. If this is less
# than fh_chain_maxtime, we assume that they belong to a lookup chain and set
# the dt value for the FH accordingly.
global fh_chain_maxtime = 100 msec;
}
redef capture_filters += {
redef capture_filters += {
["nfs"] = "port 2049",
# NFS UDP packets are often fragmented.
["nfs-frag"] = "(ip[6:2] & 0x3fff != 0) and udp",
@ -17,84 +27,382 @@ redef capture_filters += {
global nfs_ports = { 2049/tcp, 2049/udp } &redef;
redef dpd_config += { [ANALYZER_NFS] = [$ports = nfs_ports] };
# Information about a filehandle
type fh_info : record {
id: count; # A unique ID (counter) for more readable representation of the FH
pathname: string &default="@"; # the path leading to this FH
basename: string &default=""; # the name of this FHs file or directory
mimetype: string &default="";
t0: time &default=double_to_time(0); # time when we first saw this FH
dt: interval &default=0 sec; # time it took to get this FH (assuming a chain of
# procedures that ultimately yield the FH for the file
# a client is interested in
chainlen: count &default=0;
attr: fattr_t &optional;
};
# Maps opaque file handles to numbers for easier tracking.
global num_fhs = 0;
global fh_map: table[string] of count;
global fh_map: table[addr,string] of fh_info;
function map_fh(fh: string): string
# Maps connids to number for easier post processing
global num_nfs_conns = 0;
global nfs_conns: table[conn_id] of count;
# Get the FH info. Create a new info if it doesn't exists
function get_fh_info(c: connection, fh: string): fh_info
{
if ( fh !in fh_map )
fh_map[fh] = ++num_fhs;
if ( [c$id$resp_h, fh] !in fh_map )
{
# Don't have a mapping for this FH yet. E.g., a root FH
local newfhinfo: fh_info = [ $id=++num_fhs ];
newfhinfo$pathname = fmt("@%d", newfhinfo$id);
newfhinfo$t0 = network_time();
fh_map[c$id$resp_h, fh] = newfhinfo;
}
return fh_map[c$id$resp_h, fh];
}
return cat("FH", fh_map[fh]);
function log_filename(proc: string, info: fh_info)
{
print names_log_file, fmt("%.6f %s path FH%d %s/%s", network_time(), proc,
info$id, info$pathname, info$basename);
##print fmt("%.6f FH%d <%s> <%s>", network_time(), info$id, info$pathname, info$basename);
}
function fmt_attr(a: fattr_t): string
{
local s = fmt("%s %s %d %d %d %d %d %d %d %d %d %.2f %.2f %.2f",
a$ftype, mode2string(a$mode), a$nlink, a$uid, a$gid, a$size, a$used, a$rdev1, a$rdev2,
a$fsid, a$fileid, a$atime, a$mtime, a$ctime);
return s;
}
function log_attributes(c: connection, proc: string, fh: string, attr: fattr_t)
{
local info = get_fh_info(c,fh);
local did_change = F;
# check whether the attributes have changes
if (info?$attr)
{
# We can't compare records for equality :-(. So we use a hack.
# We add the two instance we want to compare to a set. If there
# are two elements in the set, the records are not equal...
local dummy: set[fattr_t];
add dummy[info$attr];
add dummy[attr];
if (|dummy| > 1)
did_change = T;
}
else
did_change=T;
if (did_change)
{
info$attr = attr;
print names_log_file, fmt("%.6f %s attr FH%d %s", network_time(), proc,
info$id, fmt_attr(attr));
}
}
# Update (or add) a filehandle mapping.
# parentfh ... parent (directory)
# name ....... the name for this FH
# fh ......... the new FH
function add_update_fh(c: connection, proc: string, parentfh: string, name: string, fh: string)
{
local info = get_fh_info(c, fh);
# TODO: we could/should check if we already have a pathname and/or basename
# for this FH and if so whether it matches the parent we just got!
if (name == ".")
return;
info$basename = name;
if (parentfh != "")
{
local parentinfo = get_fh_info(c, parentfh);
info$pathname = cat(parentinfo$pathname, "/", parentinfo$basename);
if ( (network_time() - parentinfo$t0) < fh_chain_maxtime
&& info$dt < 0 sec )
{
# The FH is part of lookup chain and it doesn't yet have a dt value
# TODO: this should probably be moved to get_fh_info(). But then get_fh_info()
# would need information about a FH's parent....
# TODO: We are using network_time(), but we really should use request
# and reply time!!!
info$dt = parentinfo$dt + (network_time() - parentinfo$t0);
info$chainlen = parentinfo$chainlen + 1;
}
}
log_filename(proc, info);
}
function set_fh_mimetype(c: connection, fh: string, proc:string, data: string)
{
local info = get_fh_info(c,fh);
local mimetype = identify_data(data, T);
if (info$mimetype != mimetype)
{
info$mimetype = mimetype;
print names_log_file, fmt("%.6f %s type FH%d %s/%s %s", network_time(), proc,
info$id, info$pathname, info$basename, (mimetype!="") ? mimetype : "X/X");
}
}
# Get the total time of the lookup chain for this FH to the
# current network time. Returns a negative interal if no
# lookup chain was found
function get_fh_chaintime_str(c:connection, fh:string): string
{
local info = get_fh_info(c, fh);
if ((network_time() - info$t0) < fh_chain_maxtime)
return fmt("%d %.6f", info$chainlen, info$dt + (network_time() - info$t0));
else
return fmt("%d %.6f", 0, 0.0);
}
# Get a FH ID
function get_fh_id(c:connection, fh: string): string
{
return cat("FH", get_fh_info(c, fh)$id);
}
# Get the basename for the FH
function get_fh_basename(c:connection, fh: string): string
{
return get_fh_info(c, fh)$basename;
}
# Get the fullname for the FH
function get_fh_fullname(c:connection, fh: string): string
{
local info = get_fh_info(c, fh);
return cat(info$pathname, "/", info$basename);
}
function print_attr(attr: fattr_t): string
{
return fmt("%s", attr);
}
function map_conn(cid: conn_id): count
{
if (cid !in nfs_conns)
nfs_conns[cid] = ++num_nfs_conns;
return nfs_conns[cid];
}
function NFS_request(n: connection, req: string, addl: string)
function is_success(info: info_t): bool
{
print log_file, fmt("%.06f %s NFS %s: %s",
network_time(), id_string(n$id), req, addl);
return (info$rpc_stat == RPC_SUCCESS && info$nfs_stat == NFS3ERR_OK);
}
function NFS_attempt(n: connection, req: string, status: count, addl: string)
function is_rpc_success(info: info_t): bool
{
print log_file, fmt("%.06f %s NFS attempt %s (%d): %s",
network_time(), id_string(n$id), req, status, addl);
return (info$rpc_stat == RPC_SUCCESS);
}
function nfs_get_log_prefix(c: connection, info: info_t, proc: string): string
{
local nfs_stat_str = (info$rpc_stat == RPC_SUCCESS) ? fmt("%s", info$nfs_stat) : "X";
return fmt("%.06f %.06f %d %.06f %.06f %d %s %s %d %s %s %s",
info$req_start, info$req_dur, info$req_len,
info$rep_start, info$rep_dur, info$rep_len,
id_string(c$id), get_port_transport_proto(c$id$orig_p),
map_conn(c$id),
proc, info$rpc_stat, nfs_stat_str);
}
event nfs_request_null(n: connection)
event nfs_proc_not_implemented(c: connection, info: info_t, proc: proc_t)
{
NFS_request(n, "null", "");
local prefix = nfs_get_log_prefix(c, info, fmt("%s", proc));
print log_file, fmt("%s Not_implemented", prefix);
}
event nfs_attempt_null(n: connection, status: count)
event nfs_proc_null(c: connection, info: info_t)
{
NFS_attempt(n, "null", status, "");
local prefix = nfs_get_log_prefix(c, info, "null");
print log_file, prefix;
}
event nfs_request_getattr(n: connection, fh: string, attrs: nfs3_attrs)
event nfs_proc_getattr (c: connection, info: info_t, fh: string, attrs: fattr_t)
{
NFS_request(n, "getattr", fmt("%s -> %s", map_fh(fh), attrs));
local prefix = nfs_get_log_prefix(c, info, "getattr");
if (is_success(info))
log_attributes(c, "getattr", fh, attrs);
print log_file, fmt("%s %s", prefix, get_fh_id(c,fh));
}
event nfs_attempt_getattr(n: connection, status: count, fh: string)
event nfs_proc_lookup(c: connection, info: info_t, req: diropargs_t, rep: lookup_reply_t)
{
NFS_attempt(n, "getattr", status, map_fh(fh));
local prefix = nfs_get_log_prefix(c, info, "lookup");
if (! is_success(info) )
{
print log_file, fmt("%s %s + %s", prefix, get_fh_id(c, req$dirfh), req$fname);
# could print dir_attr, if they are set ....
return;
}
if (rep?$dir_attr)
log_attributes(c, "lookup", req$dirfh, rep$dir_attr);
if (is_rpc_success(info) && rep?$obj_attr)
log_attributes(c, "lookup", rep$fh, rep$obj_attr);
add_update_fh(c, "lookup", req$dirfh, req$fname, rep$fh);
print log_file, fmt("%s %s + %s => %s", prefix, get_fh_id(c, req$dirfh), req$fname, get_fh_id(c, rep$fh));
}
function opt_attr_fmt(a: nfs3_opt_attrs): string
event nfs_proc_read(c: connection, info: info_t, req: readargs_t, rep: read_reply_t)
{
return a?$attrs ? fmt("%s", a$attrs) : "<missing>";
local msg = nfs_get_log_prefix(c, info, "read");
msg = fmt("%s %s @%d: %d", msg, get_fh_id(c, req$fh), req$offset, req$size);
if (is_success(info))
{
msg = fmt("%s got %d bytes %s %s", msg, rep$size, (rep$eof) ? "<eof>" : "x",
get_fh_chaintime_str(c, req$fh));
if (rep?$data && req$offset==0 && rep$size>0)
set_fh_mimetype(c, req$fh, "read", rep$data);
if (is_rpc_success(info) && rep?$attr)
log_attributes(c, "read", req$fh, rep$attr);
}
print log_file, msg;
}
event nfs_request_lookup(n: connection, req: nfs3_lookup_args, rep: nfs3_lookup_reply)
event nfs_proc_readlink(c: connection, info: info_t, fh: string, rep: readlink_reply_t)
{
NFS_request(n, "lookup", fmt("%s -> %s (file-attr: %s, dir-attr: %s)",
req, rep$fh,
opt_attr_fmt(rep$file_attr),
opt_attr_fmt(rep$dir_attr)));
local msg = nfs_get_log_prefix(c, info, "readlink");
msg = fmt("%s %s", msg, get_fh_id(c, fh));
if (is_success(info))
{
msg = fmt("%s : %s", msg, rep$nfspath);
if (rep?$attr)
log_attributes(c, "readlink", fh, rep$attr);
}
print log_file, msg;
}
event nfs_attempt_lookup(n: connection, status: count, req: nfs3_lookup_args)
event nfs_proc_write(c: connection, info: info_t, req: writeargs_t, rep: write_reply_t)
{
NFS_attempt(n, "lookup", status, fmt("%s", req));
local msg = nfs_get_log_prefix(c, info, "write");
msg = fmt("%s %s @%d: %d %s", msg, get_fh_id(c, req$fh), req$offset, req$size, req$stable);
if (is_success(info))
{
msg = fmt("%s wrote %d bytes %s %s", msg, rep$size, rep$commited,
get_fh_chaintime_str(c, req$fh));
if (req?$data && req$offset==0 && rep$size>0)
set_fh_mimetype(c, req$fh, "write", req$data);
if (rep?$postattr)
log_attributes(c, "write", req$fh, rep$postattr);
}
print log_file, msg;
}
event nfs_request_fsstat(n: connection, root_fh: string, stat: nfs3_fsstat)
function nfs_newobj(c: connection, info: info_t, proc: string, req: diropargs_t, rep: newobj_reply_t)
{
NFS_request(n, "fsstat", fmt("%s -> attr: %s, tbytes: %s, fbytes: %s, abytes: %s, tfiles: %s, ffiles: %s, afiles: %s, invarsec: %s",
map_fh(root_fh),
opt_attr_fmt(stat$attrs),
stat$tbytes, stat$fbytes, stat$abytes,
stat$tfiles, stat$ffiles, stat$afiles,
stat$invarsec));
local prefix = nfs_get_log_prefix(c, info, proc);
local newfh_str: string;
if (! is_success(info) )
{
print log_file, fmt("%s %s + %s", prefix, get_fh_id(c, req$dirfh), req$fname);
# could print dir_attr, if they are set ....
return;
}
if (is_rpc_success(info) && rep?$dir_post_attr)
log_attributes(c, proc, req$dirfh, rep$dir_post_attr);
# TODO: could print dir_pre_attr
if (is_rpc_success(info) && rep?$obj_attr)
log_attributes(c, proc, rep$fh, rep$obj_attr);
add_update_fh(c, proc, req$dirfh, req$fname, rep$fh);
newfh_str = (rep?$fh) ? get_fh_id(c, rep$fh) : "FH??";
print log_file, fmt("%s %s + %s => %s", prefix, get_fh_id(c, req$dirfh), req$fname, get_fh_id(c, rep$fh));
}
event nfs_attempt_fsstat(n: connection, status: count, root_fh: string)
event nfs_proc_create(c: connection, info: info_t, req: diropargs_t, rep: newobj_reply_t)
{
NFS_attempt(n, "fsstat", status, map_fh(root_fh));
# TODO: create request attributes not implemented in core
nfs_newobj(c, info, "create", req, rep);
}
event nfs_proc_mkdir(c: connection, info: info_t, req: diropargs_t, rep: newobj_reply_t)
{
# TODO: mkidir request attributes not implemented in core
nfs_newobj(c, info, "mkdir", req, rep);
}
function nfs_delobj(c: connection, info: info_t, proc: string, req: diropargs_t, rep: delobj_reply_t)
{
local prefix = nfs_get_log_prefix(c, info, proc);
print log_file, fmt("%s %s - %s", prefix, get_fh_id(c, req$dirfh), req$fname);
if (is_rpc_success(info) && rep?$dir_post_attr)
log_attributes(c, proc, req$dirfh, rep$dir_post_attr);
# TODO: could print dir_pre_attr
}
event nfs_proc_remove(c: connection, info: info_t, req: diropargs_t, rep: delobj_reply_t)
{
nfs_delobj(c, info, "remove", req, rep);
}
event nfs_proc_rmdir(c: connection, info: info_t, req: diropargs_t, rep: delobj_reply_t)
{
nfs_delobj(c, info, "rmdir", req, rep);
}
function fmt_direntry(c: connection, e: direntry_t): string
{
local rv = "";
rv = fmt("%d %s %d", e$fileid, e$fname, e$cookie);
if (e?$fh)
rv = fmt("%s %s", rv, get_fh_id(c, e$fh));
return rv;
}
event nfs_proc_readdir(c: connection, info: info_t, req: readdirargs_t, rep: readdir_reply_t)
{
local isplus = req$isplus;
local proc = (isplus) ? "readdirplus" : "readdir";
local msg = nfs_get_log_prefix(c, info, proc);
msg = fmt("%s %s @%d (%x)", msg, get_fh_id(c, req$dirfh), req$cookie, req$cookieverf);
if (is_success(info))
{
msg = fmt("%s %d entries %d", msg, |rep$entries|, rep$eof);
print readdir_log, msg;
for (i in rep$entries)
{
local curentry = rep$entries[i];
if (curentry?$attr && curentry?$fh)
log_attributes(c, proc, curentry$fh, curentry$attr);
if (curentry?$fh)
add_update_fh(c, proc, req$dirfh, curentry$fname, curentry$fh);
print readdir_log,fmt(" %s", fmt_direntry(c, curentry));
}
if (rep?$dir_attr)
log_attributes(c, proc, req$dirfh, rep$dir_attr);
}
else if (is_rpc_success(info) && rep?$dir_attr)
{
log_attributes(c, proc, req$dirfh, rep$dir_attr);
}
print log_file, msg;
}
event connection_state_remove(c: connection)
{
if ( c$id !in nfs_conns )
return;
delete nfs_conns[c$id];
}

View file

@ -118,8 +118,8 @@ export {
# Indexed by the portmapper request and a boolean that's T if
# the request was answered, F it was attempted but not answered.
# If there's an entry in the set, then the access won't be logged
# (unless the connection is hot for some other reason).
# If there's an entry in the set, then the access won't lead to a
# NOTICE (unless the connection is hot for some other reason).
const RPC_do_not_complain: set[string, bool] = {
["pm_null", [T, F]],
} &redef;
@ -133,24 +133,31 @@ export {
[NFS_world_servers, NFS_services],
[sun-rpc.mcast.net, "ypserv"], # sigh
} &redef;
# Logs all portmapper activity as readable "messages"
# Format: timestamp orig_p resp_h resp_p proto localInit PortmapProcedure success details
const log_file = open_log_file("portmapper") &redef;
# Logs all portmapper mappings that we observe (i.e., getport and
# dump replies. Format:
# timestamp orig_h orig_p resp_h resp_p proto localInit PortmapProcedure RPCprogram version port proto
# the mapping is then: <resp_h> accepts <RPCprogram> with <version>
# calls on <port> <proto>. We learned this mapping via <PortmapProcedure>
const mapping_log_file = open_log_file("portmapper-maps") &redef;
}
redef capture_filters += { ["portmapper"] = "port 111" };
const portmapper_ports = { 111/tcp } &redef;
const portmapper_ports = { 111/tcp, 111/udp } &redef;
redef dpd_config += { [ANALYZER_PORTMAPPER] = [$ports = portmapper_ports] };
const portmapper_binpac_ports = { 111/udp } &redef;
redef dpd_config += { [ANALYZER_RPC_UDP_BINPAC] = [$ports = portmapper_binpac_ports] };
# Indexed by source and destination addresses, plus the portmapper service.
# If the tuple is in the set, then we already logged it and shouldn't do
# so again.
global did_pm_log: set[addr, addr, string];
# If the tuple is in the set, then we already created a NOTICE for it and
# shouldn't do so again.
global did_pm_notice: set[addr, addr, string];
# Indexed by source and portmapper service. If set, we already logged
# and shouldn't do so again.
global suppress_pm_log: set[addr, string];
# Indexed by source and portmapper service. If set, we already created
# a notice and shouldn't do so again.
global suppress_pm_notice: set[addr, string];
function RPC_weird_action_filter(c: connection): Weird::WeirdAction
@ -166,6 +173,7 @@ redef Weird::weird_action_filters += {
RPC_weird_action_filter,
};
function rpc_prog(p: count): string
{
if ( p in rpc_programs )
@ -174,6 +182,56 @@ function rpc_prog(p: count): string
return fmt("unknown-%d", p);
}
function pm_get_conn_string(cid: conn_id) : string
{
return fmt("%s %d %s %d %s %s",
cid$orig_h, cid$orig_p,
cid$resp_h, cid$resp_p,
get_port_transport_proto(cid$resp_p),
is_local_addr(cid$orig_h) ? "L" : "X"
);
}
# Log a pm_request or pm_attempt to the log file
function pm_log(r: connection, proc: string, msg: string, success: bool)
{
print log_file, fmt("%f %s %s %s %s", network_time(),
pm_get_conn_string(r$id),
proc, success, msg);
}
# Log portmapper mappings received from a dump procedure
function pm_log_mapping_dump(r: connection, m: pm_mappings)
{
# TODO: sort by program and version
for ( mp in m )
{
local prog = rpc_prog(m[mp]$program);
local ver = m[mp]$version;
local p = m[mp]$p;
print mapping_log_file, fmt("%f %s pm_dump %s %d %d %s", network_time(),
pm_get_conn_string(r$id),
prog, ver, p, get_port_transport_proto(p));
}
}
# Log portmapper mappings received from a getport procedure
# Unfortunately, pm_request_getport doesn't return pm_mapping,
# but returns the parameters separately ....
function pm_log_mapping_getport(r: connection, pr: pm_port_request, p: port)
{
local prog = rpc_prog(pr$program);
local ver = pr$version;
print mapping_log_file, fmt("%f %s pm_getport %s %d %d %s", network_time(),
pm_get_conn_string(r$id),
prog, ver, p, get_port_transport_proto(p));
}
function pm_check_getport(r: connection, prog: string): bool
{
if ( prog in RPC_okay_services ||
@ -187,25 +245,25 @@ function pm_check_getport(r: connection, prog: string): bool
return T;
}
function pm_activity(r: connection, log_it: bool, proc: string)
function pm_activity(r: connection, do_notice: bool, proc: string)
{
local id = r$id;
if ( log_it &&
[id$orig_h, id$resp_h, proc] !in did_pm_log &&
[id$orig_h, proc] !in suppress_pm_log )
if ( do_notice &&
[id$orig_h, id$resp_h, proc] !in did_pm_notice &&
[id$orig_h, proc] !in suppress_pm_notice )
{
NOTICE([$note=SensitivePortmapperAccess, $conn=r,
$msg=fmt("rpc: %s %s: %s",
id_string(r$id), proc, r$addl)]);
add did_pm_log[id$orig_h, id$resp_h, proc];
add did_pm_notice[id$orig_h, id$resp_h, proc];
}
}
function pm_request(r: connection, proc: string, addl: string, log_it: bool)
function pm_request(r: connection, proc: string, addl: string, do_notice: bool)
{
if ( [proc, T] in RPC_do_not_complain )
log_it = F;
do_notice = F;
if ( ! is_tcp_port(r$id$orig_p) )
{
@ -235,7 +293,8 @@ function pm_request(r: connection, proc: string, addl: string, log_it: bool)
add r$service[proc];
Hot::check_hot(r, Hot::CONN_FINISHED);
pm_activity(r, log_it || r$hot > 0, proc);
pm_activity(r, do_notice || r$hot > 0, proc);
pm_log(r, proc, addl, T);
}
@ -273,13 +332,16 @@ function update_RPC_server_map(server: addr, p: port, prog: string)
event pm_request_getport(r: connection, pr: pm_port_request, p: port)
{
local prog = rpc_prog(pr$program);
local log_it = pm_check_getport(r, prog);
local do_notice = pm_check_getport(r, prog);
update_RPC_server_map(r$id$resp_h, p, prog);
pm_request(r, "pm_getport", fmt("%s -> %s", prog, p), log_it);
pm_request(r, "pm_getport", fmt("%s -> %s", prog, p), do_notice);
pm_log_mapping_getport(r, pr, p);
}
# Note, this function has the side effect of updating the
# RPC_server_map
function pm_mapping_to_text(server: addr, m: pm_mappings): string
{
# Used to suppress multiple entries for multiple versions.
@ -313,30 +375,33 @@ function pm_mapping_to_text(server: addr, m: pm_mappings): string
event pm_request_dump(r: connection, m: pm_mappings)
{
local log_it = [r$id$orig_h, r$id$resp_h] !in RPC_dump_okay;
pm_request(r, "pm_dump", length(m) == 0 ? "(nil)" : "(done)", log_it);
append_addl(r, cat("<", pm_mapping_to_text(r$id$resp_h, m), ">"));
local do_notice = [r$id$orig_h, r$id$resp_h] !in RPC_dump_okay;
# pm_mapping_to_text has the side-effect of updating RPC_server_map
pm_request(r, "pm_dump",
length(m) == 0 ? "(nil)" : pm_mapping_to_text(r$id$resp_h, m),
do_notice);
pm_log_mapping_dump(r, m);
}
event pm_request_callit(r: connection, call: pm_callit_request, p: port)
{
local orig_h = r$id$orig_h;
local prog = rpc_prog(call$program);
local log_it = [orig_h, prog] !in suppress_pm_log;
local do_notice = [orig_h, prog] !in suppress_pm_notice;
pm_request(r, "pm_callit", fmt("%s/%d (%d bytes) -> %s",
prog, call$proc, call$arg_size, p), log_it);
prog, call$proc, call$arg_size, p), do_notice);
if ( prog == "walld" )
add suppress_pm_log[orig_h, prog];
add suppress_pm_notice[orig_h, prog];
}
function pm_attempt(r: connection, proc: string, status: rpc_status,
addl: string, log_it: bool)
addl: string, do_notice: bool)
{
if ( [proc, F] in RPC_do_not_complain )
log_it = F;
do_notice = F;
if ( ! is_tcp_port(r$id$orig_p) )
{
@ -351,6 +416,7 @@ function pm_attempt(r: connection, proc: string, status: rpc_status,
# Current policy is ignore any failed attempts.
pm_activity(r, F, proc);
pm_log(r, proc, addl, F);
}
event pm_attempt_null(r: connection, status: rpc_status)
@ -371,14 +437,14 @@ event pm_attempt_unset(r: connection, status: rpc_status, m: pm_mapping)
event pm_attempt_getport(r: connection, status: rpc_status, pr: pm_port_request)
{
local prog = rpc_prog(pr$program);
local log_it = pm_check_getport(r, prog);
pm_attempt(r, "pm_getport", status, prog, log_it);
local do_notice = pm_check_getport(r, prog);
pm_attempt(r, "pm_getport", status, prog, do_notice);
}
event pm_attempt_dump(r: connection, status: rpc_status)
{
local log_it = [r$id$orig_h, r$id$resp_h] !in RPC_dump_okay;
pm_attempt(r, "pm_dump", status, "", log_it);
local do_notice = [r$id$orig_h, r$id$resp_h] !in RPC_dump_okay;
pm_attempt(r, "pm_dump", status, "", do_notice);
}
event pm_attempt_callit(r: connection, status: rpc_status,
@ -386,14 +452,14 @@ event pm_attempt_callit(r: connection, status: rpc_status,
{
local orig_h = r$id$orig_h;
local prog = rpc_prog(call$program);
local log_it = [orig_h, prog] !in suppress_pm_log;
local do_notice = [orig_h, prog] !in suppress_pm_notice;
pm_attempt(r, "pm_callit", status,
fmt("%s/%d (%d bytes)", prog, call$proc, call$arg_size),
log_it);
do_notice);
if ( prog == "walld" )
add suppress_pm_log[orig_h, prog];
add suppress_pm_notice[orig_h, prog];
}
event pm_bad_port(r: connection, bad_p: count)

147
policy/rpc.bro Normal file
View file

@ -0,0 +1,147 @@
#
# Log RPC request and reply messages. Does not in itself start/activate
# an analyzer. You need to load portmap and/or NFS for that
#
# TODO: maybe automatically load portmap, add a generic RPC analyzer and
# use expect connection, so that we can see RPC request/replies for RPC
# programs for which we don't have an analyzer.
#
module RPC;
export {
global log_file = open_log_file("rpc") &redef;
# whether to match request to replies on the policy layer.
# (will report on rexmit and missing requests or replies)
global track_requests_replies = T &redef;
}
type rpc_call_state: enum {
NONE,
HAVE_CALL,
HAVE_REPLY
};
type rpc_call_info: record {
state: rpc_call_state;
calltime: time;
cid: conn_id;
};
function new_call(cid: conn_id): rpc_call_info
{
local ci: rpc_call_info;
ci$state = NONE;
ci$calltime = network_time();
ci$cid = cid;
return ci;
}
function rpc_expire_xid(t: table[count] of rpc_call_info, xid: count): interval
{
local ci = t[xid];
if (ci$state != HAVE_REPLY)
print log_file, fmt("%.6f %s %s note XID %d never recevied a reply",
ci$calltime, id_string(ci$cid),
get_port_transport_proto(ci$cid$orig_p), xid);
return 0 sec;
}
function new_xid_table(): table[count] of rpc_call_info
{
local inner: table[count] of rpc_call_info &write_expire=rpc_timeout &expire_func=rpc_expire_xid;
return inner;
}
# Match requests to replies.
# The analyzer does this indepently and might differ in timeouts and
# handling of xid reuse.
# FIXME: add timeouts. Note, we do clean up on connection_state_remove
global rpc_calls: table[conn_id] of table[count] of rpc_call_info;
# &write_expire = rpc_timeout &expire_func=expire_rpc_call;
event rpc_dialogue(c: connection, prog: count, ver: count, proc: count, status: rpc_status, start_time: time, call_len: count, reply_len: count)
{
# TODO: We currently do nothing here.
# using the rpc_call and rpc_reply events, is all we need.
}
event rpc_call(c: connection, xid: count, prog: count, ver: count, proc: count, call_len: count)
{
if (track_requests_replies)
{
if (c$id !in rpc_calls)
rpc_calls[c$id] = new_xid_table();
if (xid !in rpc_calls[c$id])
rpc_calls[c$id][xid] = new_call(c$id);
local curstate = rpc_calls[c$id][xid]$state;
if (curstate == HAVE_CALL)
print log_file, fmt("%.6f %s %s note XID %d call retransmitted",
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
xid);
else if (curstate == HAVE_REPLY)
print log_file, fmt("%.6f %s %s note XID %d call received after reply",
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
xid);
rpc_calls[c$id][xid]$state = HAVE_CALL;
}
print log_file, fmt("%.6f %s %s rpc_call %d %d %d %d %d",
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
xid, prog, ver, proc, call_len);
}
event rpc_reply(c: connection, xid: count, status: rpc_status, reply_len: count)
{
if (track_requests_replies)
{
if (c$id !in rpc_calls)
rpc_calls[c$id] = new_xid_table();
if (xid !in rpc_calls[c$id])
{
rpc_calls[c$id][xid] = new_call(c$id);
# XXX: what to do about calltime in rpc_call_info??
}
if (rpc_calls[c$id][xid]$state == NONE)
print log_file, fmt("%.6f %s %s note XID %d reply but call is missing",
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
xid);
else if (rpc_calls[c$id][xid]$state == HAVE_REPLY)
print log_file, fmt("%.6f %s %s note XID %d reply retransmitted",
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
xid);
rpc_calls[c$id][xid]$state = HAVE_REPLY;
}
print log_file, fmt("%.6f %s %s rpc_reply %d %s %d",
network_time(), reverse_id_string(c$id), get_port_transport_proto(c$id$orig_p),
xid, status, reply_len);
}
function finish_calls(cid: conn_id)
{
for (xid in rpc_calls[cid])
rpc_expire_xid(rpc_calls[cid], xid);
}
event connection_state_remove(c: connection)
{
if (c$id !in rpc_calls)
return;
finish_calls(c$id);
delete rpc_calls[c$id];
}
event bro_done()
{
for (cid in rpc_calls)
finish_calls(cid);
}

View file

@ -1,5 +1,7 @@
// $Id: Analyzer.cc,v 1.1.4.28 2006/06/01 17:18:10 sommer Exp $
#include <algorithm>
#include "Analyzer.h"
#include "PIA.h"
#include "Event.h"
@ -132,9 +134,6 @@ const Analyzer::Config Analyzer::analyzer_configs[] = {
{ AnalyzerTag::HTTP_BINPAC, "HTTP_BINPAC",
HTTP_Analyzer_binpac::InstantiateAnalyzer,
HTTP_Analyzer_binpac::Available, 0, false },
{ AnalyzerTag::RPC_UDP_BINPAC, "RPC_UDP_BINPAC",
RPC_UDP_Analyzer_binpac::InstantiateAnalyzer,
RPC_UDP_Analyzer_binpac::Available, 0, false },
{ AnalyzerTag::SSL, "SSL",
SSL_Analyzer_binpac::InstantiateAnalyzer,
SSL_Analyzer_binpac::Available, 0, false },

View file

@ -35,7 +35,7 @@ namespace AnalyzerTag {
// Application-layer analyzers, binpac-generated.
DHCP_BINPAC, DNS_TCP_BINPAC, DNS_UDP_BINPAC,
HTTP_BINPAC, RPC_UDP_BINPAC, SSL, SYSLOG_BINPAC,
HTTP_BINPAC, SSL, SYSLOG_BINPAC,
// Other
File, Backdoor, InterConn, SteppingStone, TCPStats,

View file

@ -8,6 +8,8 @@
#include <sys/types.h>
#include <regex.h>
#include <algorithm>
# define FMT_INT "%" PRId64
# define FMT_UINT "%" PRIu64

View file

@ -7,6 +7,8 @@
#include <algorithm>
#include <ctype.h>
#include <algorithm>
#include "BroString.h"
#include "Var.h"

View file

@ -193,8 +193,6 @@ binpac_target(http.pac
binpac_target(ncp.pac)
binpac_target(netflow.pac
netflow-protocol.pac netflow-analyzer.pac)
binpac_target(rpc.pac
rpc-analyzer.pac portmap-analyzer.pac)
binpac_target(smb.pac
smb-protocol.pac smb-pipe.pac smb-mailslot.pac)
binpac_target(ssl.pac

View file

@ -1,5 +1,7 @@
// $Id: ContentLine.cc,v 1.1.2.8 2006/06/01 01:55:42 sommer Exp $
#include <algorithm>
#include "ContentLine.h"
#include "TCP.h"

View file

@ -29,6 +29,8 @@
#endif
#include <stdlib.h>
#include <algorithm>
#include "DNS_Mgr.h"
#include "Event.h"
#include "Net.h"

View file

@ -2,6 +2,8 @@
//
// See the file "COPYING" in the main distribution directory for copyright.
#include <algorithm>
#include "config.h"
#include "Net.h"

View file

@ -20,6 +20,8 @@
#include <errno.h>
#include <unistd.h>
#include <algorithm>
#include "File.h"
#include "Type.h"
#include "Timer.h"

View file

@ -1,5 +1,7 @@
// $Id: FileAnalyzer.cc,v 1.1.4.2 2006/06/01 17:18:10 sommer Exp $
#include <algorithm>
#include "FileAnalyzer.h"
#ifdef HAVE_LIBMAGIC

View file

@ -6,13 +6,14 @@
#include <ctype.h>
#include <algorithm>
#include "NetVar.h"
#include "HTTP.h"
#include "Gnutella.h"
#include "Event.h"
#include "PIA.h"
GnutellaMsgState::GnutellaMsgState()
{
buffer = "";

View file

@ -2,6 +2,8 @@
//
// See the file "COPYING" in the main distribution directory for copyright.
#include <algorithm>
#include "config.h"
#include "Net.h"

View file

@ -5,6 +5,8 @@
#include <unistd.h>
#include <assert.h>
#include <algorithm>
#include "util.h"
#include "IOSource.h"

View file

@ -2,6 +2,8 @@
//
// See the file "COPYING" in the main distribution directory for copyright.
#include <algorithm>
#include "config.h"
#include "NetVar.h"
@ -9,171 +11,296 @@
#include "NFS.h"
#include "Event.h"
#define NFS_PROC_NULL 0
#define NFS_PROC_GETATTR 1
#define NFS_PROC_SETATTR 2
#define NFS_PROC_LOOKUP 3
#define NFS_PROC_READ 6
#define NFS_PROC_WRITE 7
#define NFS_PROC_FSSTAT 18
int NFS_Interp::RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n)
{
if ( c->Program() != 100003 )
Weird("bad_RPC_program");
Weird(fmt("bad_RPC_program (%d)", c->Program()));
uint32 proc = c->Proc();
// The call arguments, depends on the call type obviously ...
Val *callarg = 0;
switch ( proc ) {
case NFS_PROC_NULL:
case BifEnum::NFS3::PROC_NULL:
break;
case NFS_PROC_GETATTR:
{
Val* v = ExtractFH(buf, n);
if ( ! v )
return 0;
c->AddVal(v);
}
case BifEnum::NFS3::PROC_GETATTR:
callarg = nfs3_fh(buf, n);
break;
case NFS_PROC_LOOKUP:
{
StringVal* fh = ExtractFH(buf, n);
int name_len;
const u_char* name = extract_XDR_opaque(buf, n, name_len);
if ( ! fh || ! name )
return 0;
RecordVal* args = new RecordVal(nfs3_lookup_args);
args->Assign(0, fh);
args->Assign(1, new StringVal(new BroString(name, name_len, 0)));
c->AddVal(args);
}
case BifEnum::NFS3::PROC_LOOKUP:
callarg = nfs3_diropargs(buf, n);
break;
case NFS_PROC_FSSTAT:
{
Val* v = ExtractFH(buf, n);
if ( ! v )
return 0;
c->AddVal(v);
}
case BifEnum::NFS3::PROC_READ:
callarg = nfs3_readargs(buf, n);
break;
case NFS_PROC_READ:
case BifEnum::NFS3::PROC_READLINK:
callarg = nfs3_fh(buf, n);
break;
case BifEnum::NFS3::PROC_WRITE:
callarg = nfs3_writeargs(buf, n);
break;
case BifEnum::NFS3::PROC_CREATE:
callarg = nfs3_diropargs(buf, n);
// TODO: implement create attributes. For now we just skip
// over them.
n = 0;
break;
case BifEnum::NFS3::PROC_MKDIR:
callarg = nfs3_diropargs(buf, n);
// TODO: implement mkdir attributes. For now we just skip
// over them.
n = 0;
break;
case BifEnum::NFS3::PROC_REMOVE:
callarg = nfs3_diropargs(buf, n);
break;
case BifEnum::NFS3::PROC_RMDIR:
callarg = nfs3_diropargs(buf, n);
break;
case BifEnum::NFS3::PROC_READDIR:
callarg = nfs3_readdirargs(false, buf, n);
break;
case BifEnum::NFS3::PROC_READDIRPLUS:
callarg = nfs3_readdirargs(true, buf, n);
break;
default:
Weird(fmt("unknown_NFS_request(%u)", proc));
callarg = 0;
if ( proc < BifEnum::NFS3::PROC_END_OF_PROCS )
{
// We know the procedure but haven't implemented it.
// Otherwise DeliverRPC would complain about
// excess_RPC.
n = 0;
}
else
Weird(fmt("unknown_NFS_request(%u)", proc));
// Return 1 so that replies to unprocessed calls will still
// be processed, and the return status extracted
// be processed, and the return status extracted.
return 1;
}
if ( ! buf )
{
// There was a parse error while trying to extract the call
// arguments. However, we don't know where exactly it
// happened and whether Vals where already allocated (e.g., a
// RecordVal was allocated but we failed to fill it). So we
// Unref() the call arguments, and we are fine.
Unref(callarg);
callarg = 0;
return 0;
}
c->AddVal(callarg); // It's save to AddVal(0).
return 1;
}
int NFS_Interp::RPC_BuildReply(const RPC_CallInfo* c, int success,
const u_char*& buf, int& n,
EventHandlerPtr& event, Val*& reply)
int NFS_Interp::RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status rpc_status,
const u_char*& buf, int& n, double start_time,
double last_time, int reply_len)
{
reply = 0;
uint32 status = 0;
if ( success )
EventHandlerPtr event = 0;
Val *reply = 0;
BifEnum::NFS3::status_t nfs_status = BifEnum::NFS3::NFS3ERR_OK;
bool rpc_success = ( rpc_status == BifEnum::RPC_SUCCESS );
// Reply always starts with the NFS status.
if ( rpc_success )
{
if ( n >= 4 )
status = extract_XDR_uint32(buf, n);
nfs_status = (BifEnum::NFS3::status_t)extract_XDR_uint32(buf, n);
else
status = 0xffffffff;
nfs_status = BifEnum::NFS3::NFS3ERR_UNKNOWN;
}
if ( nfs_reply_status )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(new Val(status, TYPE_COUNT));
val_list* vl = event_common_vl(c, rpc_status, nfs_status,
start_time, last_time, reply_len);
analyzer->ConnectionEvent(nfs_reply_status, vl);
}
if ( ! rpc_success )
{
// We set the buffer to NULL, the function that extract the
// reply from the data stream will then return empty records.
//
buf = NULL;
n = 0;
}
switch ( c->Proc() ) {
case NFS_PROC_NULL:
event = success ? nfs_request_null : nfs_attempt_null;
case BifEnum::NFS3::PROC_NULL:
event = nfs_proc_null;
break;
case NFS_PROC_GETATTR:
if ( success )
{
if ( ! buf || status != 0 )
return 0;
reply = ExtractAttrs(buf, n);
event = nfs_request_getattr;
}
else
event = nfs_attempt_getattr;
case BifEnum::NFS3::PROC_GETATTR:
reply = nfs3_fattr(buf, n);
event = nfs_proc_getattr;
break;
case NFS_PROC_LOOKUP:
if ( success )
{
if ( ! buf || status != 0 )
return 0;
RecordVal* r = new RecordVal(nfs3_lookup_reply);
r->Assign(0, ExtractFH(buf, n));
r->Assign(1, ExtractOptAttrs(buf, n));
r->Assign(2, ExtractOptAttrs(buf, n));
reply = r;
event = nfs_request_lookup;
}
else
{
reply = ExtractOptAttrs(buf, n);
event = nfs_attempt_lookup;
}
case BifEnum::NFS3::PROC_LOOKUP:
reply = nfs3_lookup_reply(buf, n, nfs_status);
event = nfs_proc_lookup;
break;
case NFS_PROC_FSSTAT:
if ( success )
{
if ( ! buf || status != 0 )
return 0;
case BifEnum::NFS3::PROC_READ:
bro_uint_t offset;
offset = c->RequestVal()->AsRecordVal()->Lookup(1)->AsCount();
reply = nfs3_read_reply(buf, n, nfs_status, offset);
event = nfs_proc_read;
break;
RecordVal* r = new RecordVal(nfs3_fsstat);
r->Assign(0, ExtractOptAttrs(buf, n));
r->Assign(1, ExtractLongAsDouble(buf, n)); // tbytes
r->Assign(2, ExtractLongAsDouble(buf, n)); // fbytes
r->Assign(3, ExtractLongAsDouble(buf, n)); // abytes
r->Assign(4, ExtractLongAsDouble(buf, n)); // tfiles
r->Assign(5, ExtractLongAsDouble(buf, n)); // ffiles
r->Assign(6, ExtractLongAsDouble(buf, n)); // afiles
r->Assign(7, ExtractInterval(buf, n)); // invarsec
case BifEnum::NFS3::PROC_READLINK:
reply = nfs3_readlink_reply(buf, n, nfs_status);
event = nfs_proc_readlink;
break;
reply = r;
event = nfs_request_fsstat;
}
else
{
reply = ExtractOptAttrs(buf, n);
event = nfs_attempt_fsstat;
}
case BifEnum::NFS3::PROC_WRITE:
reply = nfs3_write_reply(buf, n, nfs_status);
event = nfs_proc_write;
break;
case BifEnum::NFS3::PROC_CREATE:
reply = nfs3_newobj_reply(buf, n, nfs_status);
event = nfs_proc_create;
break;
case BifEnum::NFS3::PROC_MKDIR:
reply = nfs3_newobj_reply(buf, n, nfs_status);
event = nfs_proc_mkdir;
break;
case BifEnum::NFS3::PROC_REMOVE:
reply = nfs3_delobj_reply(buf, n);
event = nfs_proc_remove;
break;
case BifEnum::NFS3::PROC_RMDIR:
reply = nfs3_delobj_reply(buf, n);
event = nfs_proc_rmdir;
break;
case BifEnum::NFS3::PROC_READDIR:
reply = nfs3_readdir_reply(false, buf, n, nfs_status);
event = nfs_proc_readdir;
break;
case BifEnum::NFS3::PROC_READDIRPLUS:
reply = nfs3_readdir_reply(true, buf, n, nfs_status);
event = nfs_proc_readdir;
break;
default:
return 0;
if ( c->Proc() < BifEnum::NFS3::PROC_END_OF_PROCS )
{
// We know the procedure but haven't implemented it.
// Otherwise DeliverRPC would complain about
// excess_RPC.
n = 0;
reply = new EnumVal(c->Proc(), BifType::Enum::NFS3::proc_t);
event = nfs_proc_not_implemented;
}
else
return 0;
}
if ( rpc_success && ! buf )
{
// There was a parse error. We have to unref the reply. (see
// also comments in RPC_BuildCall.
Unref(reply);
reply = 0;
return 0;
}
// Note: if reply == 0, it won't be added to the val_list for the
// event. While we can check for that on the policy layer it's kinda
// ugly, because it's contrary to the event prototype. But having
// this optional argument to the event is really helpful. Otherwise I
// have to let reply point to a RecordVal where all fields are
// optional and all are set to 0 ...
if ( event )
{
val_list* vl = event_common_vl(c, rpc_status, nfs_status,
start_time, last_time, reply_len);
Val *request = c->TakeRequestVal();
if ( request )
vl->append(request);
if ( reply )
vl->append(reply);
analyzer->ConnectionEvent(event, vl);
}
return 1;
}
StringVal* NFS_Interp::ExtractFH(const u_char*& buf, int& n)
StringVal* NFS_Interp::nfs3_file_data(const u_char*& buf, int& n, uint64_t offset, int size)
{
int data_n;
// extract the data, move buf and n
const u_char *data = extract_XDR_opaque(buf, n, data_n, 1 << 30, true);
// check whether we have to deliver data to the event
if ( ! BifConst::NFS3::return_data )
return 0;
if ( BifConst::NFS3::return_data_first_only && offset != 0 )
return 0;
// Ok, so we want to return some data
data_n = min(data_n, size);
data_n = min(data_n, int(BifConst::NFS3::return_data_max));
if ( data_n > 0 )
return new StringVal(new BroString(data, data_n, 0));
return 0;
}
val_list* NFS_Interp::event_common_vl(RPC_CallInfo *c, BifEnum::rpc_status rpc_status,
BifEnum::NFS3::status_t nfs_status,
double rep_start_time,
double rep_last_time, int reply_len)
{
// Returns a new val_list that already has a conn_val, and nfs3_info.
// These are the first parameters for each nfs_* event ...
val_list *vl = new val_list;
vl->append(analyzer->BuildConnVal());
RecordVal *info = new RecordVal(BifType::Record::NFS3::info_t);
info->Assign(0, new EnumVal(rpc_status, BifType::Enum::rpc_status));
info->Assign(1, new EnumVal(nfs_status, BifType::Enum::NFS3::status_t));
info->Assign(2, new Val(c->StartTime(), TYPE_TIME));
info->Assign(3, new Val(c->LastTime()-c->StartTime(), TYPE_INTERVAL));
info->Assign(4, new Val(c->RPCLen(), TYPE_COUNT));
info->Assign(5, new Val(rep_start_time, TYPE_TIME));
info->Assign(6, new Val(rep_last_time-rep_start_time, TYPE_INTERVAL));
info->Assign(7, new Val(reply_len, TYPE_COUNT));
vl->append(info);
return vl;
}
StringVal* NFS_Interp::nfs3_fh(const u_char*& buf, int& n)
{
int fh_n;
const u_char* fh = extract_XDR_opaque(buf, n, fh_n, 64);
@ -184,20 +311,21 @@ StringVal* NFS_Interp::ExtractFH(const u_char*& buf, int& n)
return new StringVal(new BroString(fh, fh_n, 0));
}
RecordVal* NFS_Interp::ExtractAttrs(const u_char*& buf, int& n)
RecordVal* NFS_Interp::nfs3_fattr(const u_char*& buf, int& n)
{
RecordVal* attrs = new RecordVal(nfs3_attrs);
attrs->Assign(0, ExtractCount(buf, n)); // file type
attrs->Assign(1, ExtractCount(buf, n)); // mode
attrs->Assign(2, ExtractCount(buf, n)); // nlink
attrs->Assign(3, ExtractCount(buf, n)); // uid
attrs->Assign(4, ExtractCount(buf, n)); // gid
attrs->Assign(5, ExtractLongAsDouble(buf, n)); // size
attrs->Assign(6, ExtractLongAsDouble(buf, n)); // used
attrs->Assign(7, ExtractCount(buf, n)); // rdev1
attrs->Assign(8, ExtractCount(buf, n)); // rdev2
attrs->Assign(9, ExtractLongAsDouble(buf, n)); // fsid
attrs->Assign(10, ExtractLongAsDouble(buf, n)); // fileid
RecordVal* attrs = new RecordVal(BifType::Record::NFS3::fattr_t);
attrs->Assign(0, nfs3_ftype(buf, n)); // file type
attrs->Assign(1, ExtractUint32(buf, n)); // mode
attrs->Assign(2, ExtractUint32(buf, n)); // nlink
attrs->Assign(3, ExtractUint32(buf, n)); // uid
attrs->Assign(4, ExtractUint32(buf, n)); // gid
attrs->Assign(5, ExtractUint64(buf, n)); // size
attrs->Assign(6, ExtractUint64(buf, n)); // used
attrs->Assign(7, ExtractUint32(buf, n)); // rdev1
attrs->Assign(8, ExtractUint32(buf, n)); // rdev2
attrs->Assign(9, ExtractUint64(buf, n)); // fsid
attrs->Assign(10, ExtractUint64(buf, n)); // fileid
attrs->Assign(11, ExtractTime(buf, n)); // atime
attrs->Assign(12, ExtractTime(buf, n)); // mtime
attrs->Assign(13, ExtractTime(buf, n)); // ctime
@ -205,27 +333,297 @@ RecordVal* NFS_Interp::ExtractAttrs(const u_char*& buf, int& n)
return attrs;
}
RecordVal* NFS_Interp::ExtractOptAttrs(const u_char*& buf, int& n)
EnumVal* NFS_Interp::nfs3_ftype(const u_char*& buf, int& n)
{
BifEnum::NFS3::file_type_t t = (BifEnum::NFS3::file_type_t)extract_XDR_uint32(buf, n);
return new EnumVal(t, BifType::Enum::NFS3::file_type_t);
}
RecordVal* NFS_Interp::nfs3_wcc_attr(const u_char*& buf, int& n)
{
RecordVal* attrs = new RecordVal(BifType::Record::NFS3::wcc_attr_t);
attrs->Assign(0, ExtractUint64(buf, n)); // size
attrs->Assign(1, ExtractTime(buf, n)); // mtime
attrs->Assign(2, ExtractTime(buf, n)); // ctime
return attrs;
}
StringVal *NFS_Interp::nfs3_filename(const u_char*& buf, int& n)
{
int name_len;
const u_char* name = extract_XDR_opaque(buf, n, name_len);
if ( ! name )
return 0;
return new StringVal(new BroString(name, name_len, 0));
}
RecordVal *NFS_Interp::nfs3_diropargs(const u_char*& buf, int& n)
{
RecordVal *diropargs = new RecordVal(BifType::Record::NFS3::diropargs_t);
diropargs->Assign(0, nfs3_fh(buf, n));
diropargs->Assign(1, nfs3_filename(buf, n));
return diropargs;
}
RecordVal* NFS_Interp::nfs3_post_op_attr(const u_char*& buf, int& n)
{
int have_attrs = extract_XDR_uint32(buf, n);
RecordVal* opt_attrs = new RecordVal(nfs3_opt_attrs);
if ( buf && have_attrs )
opt_attrs->Assign(0, ExtractAttrs(buf, n));
else
opt_attrs->Assign(0, 0);
if ( have_attrs )
return nfs3_fattr(buf, n);
return opt_attrs;
return 0;
}
Val* NFS_Interp::ExtractCount(const u_char*& buf, int& n)
StringVal* NFS_Interp::nfs3_post_op_fh(const u_char*& buf, int& n)
{
int have_fh = extract_XDR_uint32(buf, n);
if ( have_fh )
return nfs3_fh(buf, n);
return 0;
}
RecordVal* NFS_Interp::nfs3_pre_op_attr(const u_char*& buf, int& n)
{
int have_attrs = extract_XDR_uint32(buf, n);
if ( have_attrs )
return nfs3_wcc_attr(buf, n);
return 0;
}
EnumVal *NFS_Interp::nfs3_stable_how(const u_char*& buf, int& n)
{
BifEnum::NFS3::stable_how_t stable = (BifEnum::NFS3::stable_how_t)extract_XDR_uint32(buf, n);
return new EnumVal(stable, BifType::Enum::NFS3::stable_how_t);
}
RecordVal* NFS_Interp::nfs3_lookup_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status)
{
RecordVal *rep = new RecordVal(BifType::Record::NFS3::lookup_reply_t);
if ( status == BifEnum::NFS3::NFS3ERR_OK )
{
rep->Assign(0, nfs3_fh(buf,n));
rep->Assign(1, nfs3_post_op_attr(buf, n));
rep->Assign(2, nfs3_post_op_attr(buf, n));
}
else
{
rep->Assign(0, 0);
rep->Assign(1, 0);
rep->Assign(2, nfs3_post_op_attr(buf, n));
}
return rep;
}
RecordVal *NFS_Interp::nfs3_readargs(const u_char*& buf, int& n)
{
RecordVal *readargs = new RecordVal(BifType::Record::NFS3::readargs_t);
readargs->Assign(0, nfs3_fh(buf, n));
readargs->Assign(1, ExtractUint64(buf, n)); // offset
readargs->Assign(2, ExtractUint32(buf,n)); // size
return readargs;
}
RecordVal* NFS_Interp::nfs3_read_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status,
bro_uint_t offset)
{
RecordVal *rep = new RecordVal(BifType::Record::NFS3::read_reply_t);
if (status == BifEnum::NFS3::NFS3ERR_OK)
{
uint32_t bytes_read;
rep->Assign(0, nfs3_post_op_attr(buf, n));
bytes_read = extract_XDR_uint32(buf, n);
rep->Assign(1, new Val(bytes_read, TYPE_COUNT));
rep->Assign(2, ExtractBool(buf, n));
rep->Assign(3, nfs3_file_data(buf, n, offset, bytes_read));
}
else
{
rep->Assign(0, nfs3_post_op_attr(buf, n));
}
return rep;
}
RecordVal* NFS_Interp::nfs3_readlink_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status)
{
RecordVal *rep = new RecordVal(BifType::Record::NFS3::readlink_reply_t);
if (status == BifEnum::NFS3::NFS3ERR_OK)
{
rep->Assign(0, nfs3_post_op_attr(buf, n));
rep->Assign(1, nfs3_nfspath(buf,n));
}
else
{
rep->Assign(0, nfs3_post_op_attr(buf, n));
}
return rep;
}
RecordVal *NFS_Interp::nfs3_writeargs(const u_char*& buf, int& n)
{
uint32_t bytes;
uint64_t offset;
RecordVal *writeargs = new RecordVal(BifType::Record::NFS3::writeargs_t);
offset = extract_XDR_uint64(buf, n);
bytes = extract_XDR_uint32(buf, n);
writeargs->Assign(0, nfs3_fh(buf, n));
writeargs->Assign(1, new Val(offset, TYPE_COUNT));
writeargs->Assign(2, new Val(bytes, TYPE_COUNT));
writeargs->Assign(3, nfs3_stable_how(buf, n));
writeargs->Assign(4, nfs3_file_data(buf, n, offset, bytes));
return writeargs;
}
RecordVal *NFS_Interp::nfs3_write_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status)
{
RecordVal *rep = new RecordVal(BifType::Record::NFS3::write_reply_t);
if ( status == BifEnum::NFS3::NFS3ERR_OK )
{
rep->Assign(0, nfs3_pre_op_attr(buf, n));
rep->Assign(1, nfs3_post_op_attr(buf, n));
rep->Assign(2, ExtractUint32(buf, n));
rep->Assign(3, nfs3_stable_how(buf, n));
// Writeverf. While the RFC says that this should be a fixed
// length opaque, it specifies the lenght as 8 bytes, so we
// can also just as easily extract a uint64.
rep->Assign(4, ExtractUint64(buf, n));
}
else
{
rep->Assign(0, nfs3_post_op_attr(buf, n));
rep->Assign(1, nfs3_pre_op_attr(buf, n));
}
return rep;
}
RecordVal* NFS_Interp::nfs3_newobj_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status)
{
RecordVal *rep = new RecordVal(BifType::Record::NFS3::newobj_reply_t);
if (status == BifEnum::NFS3::NFS3ERR_OK)
{
int i = 0;
rep->Assign(0, nfs3_post_op_fh(buf,n));
rep->Assign(1, nfs3_post_op_attr(buf, n));
// wcc_data
rep->Assign(2, nfs3_pre_op_attr(buf, n));
rep->Assign(3, nfs3_post_op_attr(buf, n));
}
else
{
rep->Assign(0, 0);
rep->Assign(1, 0);
rep->Assign(2, nfs3_pre_op_attr(buf, n));
rep->Assign(3, nfs3_post_op_attr(buf, n));
}
return rep;
}
RecordVal* NFS_Interp::nfs3_delobj_reply(const u_char*& buf, int& n)
{
RecordVal *rep = new RecordVal(BifType::Record::NFS3::delobj_reply_t);
// wcc_data
rep->Assign(0, nfs3_pre_op_attr(buf, n));
rep->Assign(1, nfs3_post_op_attr(buf, n));
return rep;
}
RecordVal* NFS_Interp::nfs3_readdirargs(bool isplus, const u_char*& buf, int&n)
{
RecordVal *args = new RecordVal(BifType::Record::NFS3::readdirargs_t);
args->Assign(0, new Val(isplus, TYPE_BOOL));
args->Assign(1, nfs3_fh(buf, n));
args->Assign(2, ExtractUint64(buf,n)); // cookie
args->Assign(3, ExtractUint64(buf,n)); // cookieverf
args->Assign(4, ExtractUint32(buf,n)); // dircount
if ( isplus )
args->Assign(5, ExtractUint32(buf,n));
return args;
}
RecordVal* NFS_Interp::nfs3_readdir_reply(bool isplus, const u_char*& buf,
int&n, BifEnum::NFS3::status_t status)
{
RecordVal *rep = new RecordVal(BifType::Record::NFS3::readdir_reply_t);
rep->Assign(0, new Val(isplus, TYPE_BOOL));
if ( status == BifEnum::NFS3::NFS3ERR_OK )
{
unsigned pos;
VectorVal *entries = new VectorVal(BifType::Vector::NFS3::direntry_vec_t);
rep->Assign(1, nfs3_post_op_attr(buf,n)); // dir_attr
rep->Assign(2, ExtractUint64(buf,n)); // cookieverf
pos = 1;
while ( extract_XDR_uint32(buf,n) )
{
RecordVal *entry = new RecordVal(BifType::Record::NFS3::direntry_t);
entry->Assign(0, ExtractUint64(buf,n)); // fileid
entry->Assign(1, nfs3_filename(buf,n)); // fname
entry->Assign(2, ExtractUint64(buf,n)); // cookie
if ( isplus )
{
entry->Assign(3, nfs3_post_op_attr(buf,n));
entry->Assign(4, nfs3_post_op_fh(buf,n));
}
entries->Assign(pos, entry, 0);
pos++;
}
rep->Assign(3, entries);
rep->Assign(4, ExtractBool(buf,n)); // eof
}
else
{
rep->Assign(1, nfs3_post_op_attr(buf,n));
}
return rep;
}
Val* NFS_Interp::ExtractUint32(const u_char*& buf, int& n)
{
return new Val(extract_XDR_uint32(buf, n), TYPE_COUNT);
}
Val* NFS_Interp::ExtractLongAsDouble(const u_char*& buf, int& n)
Val* NFS_Interp::ExtractUint64(const u_char*& buf, int& n)
{
return new Val(extract_XDR_uint64_as_double(buf, n), TYPE_DOUBLE);
return new Val(extract_XDR_uint64(buf, n), TYPE_COUNT);
}
Val* NFS_Interp::ExtractTime(const u_char*& buf, int& n)
@ -238,37 +636,14 @@ Val* NFS_Interp::ExtractInterval(const u_char*& buf, int& n)
return new IntervalVal(double(extract_XDR_uint32(buf, n)), 1.0);
}
void NFS_Interp::Event(EventHandlerPtr f, Val* request, int status, Val* reply)
Val* NFS_Interp::ExtractBool(const u_char*& buf, int& n)
{
if ( ! f )
{
Unref(request);
Unref(reply);
return;
}
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
if ( status == RPC_SUCCESS )
{
if ( request )
vl->append(request);
if ( reply )
vl->append(reply);
}
else
{
vl->append(new Val(status, TYPE_COUNT));
if ( request )
vl->append(request);
}
analyzer->ConnectionEvent(f, vl);
return new Val(extract_XDR_uint32(buf, n), TYPE_BOOL);
}
NFS_Analyzer::NFS_Analyzer(Connection* conn)
: RPC_Analyzer(AnalyzerTag::NFS, conn, new NFS_Interp(this))
: RPC_Analyzer(AnalyzerTag::NFS, conn, new NFS_Interp(this))
{
orig_rpc = resp_rpc = 0;
}

View file

@ -6,6 +6,8 @@
#define nfs_h
#include "RPC.h"
#include "XDR.h"
#include "Event.h"
class NFS_Interp : public RPC_Interpreter {
public:
@ -13,19 +15,61 @@ public:
protected:
int RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n);
int RPC_BuildReply(const RPC_CallInfo* c, int success,
const u_char*& buf, int& n,
EventHandlerPtr& event, Val*& reply);
int RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status rpc_status,
const u_char*& buf, int& n, double start_time,
double last_time, int reply_len);
// Returns a new val_list that already has a conn_val, rpc_status and
// nfs_status. These are the first parameters for each nfs_* event
// ...
val_list* event_common_vl(RPC_CallInfo *c, BifEnum::rpc_status rpc_status,
BifEnum::NFS3::status_t nfs_status,
double rep_start_time, double rep_last_time,
int reply_len);
// These methods parse the appropriate NFSv3 "type" out of buf. If
// there are any errors (i.e., buffer to short, etc), buf will be set
// to 0. However, the methods might still return an allocated Val * !
// So, you might want to Unref() the Val if buf is 0. Method names
// are based on the type names of RFC 1813.
StringVal* nfs3_fh(const u_char*& buf, int& n);
RecordVal* nfs3_fattr(const u_char*& buf, int& n);
EnumVal* nfs3_ftype(const u_char*& buf, int& n);
RecordVal* nfs3_wcc_attr(const u_char*& buf, int& n);
RecordVal* nfs3_diropargs(const u_char*&buf, int &n);
StringVal* nfs3_filename(const u_char*& buf, int& n);
StringVal* nfs3_nfspath(const u_char*& buf, int& n)
{
return nfs3_filename(buf,n);
}
RecordVal* nfs3_post_op_attr(const u_char*&buf, int &n); // Return 0 or an fattr
RecordVal* nfs3_pre_op_attr(const u_char*&buf, int &n); // Return 0 or an wcc_attr
RecordVal* nfs3_lookup_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status);
RecordVal* nfs3_readargs(const u_char*& buf, int& n);
RecordVal* nfs3_read_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status, bro_uint_t offset);
RecordVal* nfs3_readlink_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status);
RecordVal* nfs3_writeargs(const u_char*& buf, int& n);
EnumVal* nfs3_stable_how(const u_char*& buf, int& n);
RecordVal* nfs3_write_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status);
RecordVal* nfs3_newobj_reply(const u_char*& buf, int&n, BifEnum::NFS3::status_t status);
RecordVal* nfs3_delobj_reply(const u_char*& buf, int& n);
StringVal* nfs3_post_op_fh(const u_char*& buf, int& n);
RecordVal* nfs3_readdirargs(bool isplus, const u_char*& buf, int&n);
RecordVal* nfs3_readdir_reply(bool isplus, const u_char*& buf, int&n, BifEnum::NFS3::status_t status);
// Consumes the file data in the RPC message. Depending on NFS::return_data* consts
// in bro.init returns NULL or the data as string val:
// * offset is the offset of the read/write call
// * size is the amount of bytes read (or requested to be written),
StringVal* nfs3_file_data(const u_char*& buf, int& n, uint64_t offset, int size);
StringVal* ExtractFH(const u_char*& buf, int& n);
RecordVal* ExtractAttrs(const u_char*& buf, int& n);
RecordVal* ExtractOptAttrs(const u_char*& buf, int& n);
Val* ExtractCount(const u_char*& buf, int& n);
Val* ExtractLongAsDouble(const u_char*& buf, int& n);
Val* ExtractUint32(const u_char*& buf, int& n);
Val* ExtractUint64(const u_char*& buf, int& n);
Val* ExtractTime(const u_char*& buf, int& n);
Val* ExtractInterval(const u_char*& buf, int& n);
void Event(EventHandlerPtr f, Val* request, int status, Val* reply);
Val* ExtractBool(const u_char*& buf, int& n);
};
class NFS_Analyzer : public RPC_Analyzer {
@ -36,7 +80,16 @@ public:
static Analyzer* InstantiateAnalyzer(Connection* conn)
{ return new NFS_Analyzer(conn); }
static bool Available() { return nfs_request_getattr || rpc_call; }
static bool Available()
{
return ( nfs_proc_null || nfs_proc_not_implemented || nfs_proc_getattr ||
nfs_proc_lookup || nfs_proc_read || nfs_proc_readlink ||
nfs_proc_write || nfs_proc_create || nfs_proc_mkdir ||
nfs_proc_remove || nfs_proc_rmdir || nfs_proc_readdir ||
nfs_reply_status ||
rpc_dialogue || rpc_call || rpc_reply );
}
};
#endif

View file

@ -110,12 +110,6 @@ TableType* pm_mappings;
RecordType* pm_port_request;
RecordType* pm_callit_request;
RecordType* nfs3_attrs;
RecordType* nfs3_opt_attrs;
RecordType* nfs3_lookup_args;
RecordType* nfs3_lookup_reply;
RecordType* nfs3_fsstat;
RecordType* ntp_msg;
TableVal* samba_cmds;
@ -450,12 +444,6 @@ void init_net_var()
pm_port_request = internal_type("pm_port_request")->AsRecordType();
pm_callit_request = internal_type("pm_callit_request")->AsRecordType();
nfs3_attrs = internal_type("nfs3_attrs")->AsRecordType();
nfs3_opt_attrs = internal_type("nfs3_opt_attrs")->AsRecordType();
nfs3_lookup_args = internal_type("nfs3_lookup_args")->AsRecordType();
nfs3_lookup_reply = internal_type("nfs3_lookup_reply")->AsRecordType();
nfs3_fsstat = internal_type("nfs3_fsstat")->AsRecordType();
ntp_msg = internal_type("ntp_msg")->AsRecordType();
samba_cmds = internal_val("samba_cmds")->AsTableVal();

View file

@ -114,12 +114,6 @@ extern TableType* pm_mappings;
extern RecordType* pm_port_request;
extern RecordType* pm_callit_request;
extern RecordType* nfs3_attrs;
extern RecordType* nfs3_opt_attrs;
extern RecordType* nfs3_lookup_args;
extern RecordType* nfs3_lookup_reply;
extern RecordType* nfs3_fsstat;
extern RecordType* ntp_msg;
extern TableVal* samba_cmds;

View file

@ -71,11 +71,14 @@ int PortmapperInterp::RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n)
return 1;
}
int PortmapperInterp::RPC_BuildReply(const RPC_CallInfo* c, int success,
const u_char*& buf, int& n,
EventHandlerPtr& event, Val*& reply)
int PortmapperInterp::RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status status,
const u_char*& buf, int& n,
double start_time, double last_time,
int reply_len)
{
reply = 0;
EventHandlerPtr event;
Val *reply = 0;
int success = (status == BifEnum::RPC_SUCCESS);
switch ( c->Proc() ) {
case PMAPPROC_NULL:
@ -184,6 +187,7 @@ int PortmapperInterp::RPC_BuildReply(const RPC_CallInfo* c, int success,
return 0;
}
Event(event, c->TakeRequestVal(), status, reply);
return 1;
}
@ -267,7 +271,7 @@ uint32 PortmapperInterp::CheckPort(uint32 port)
return port;
}
void PortmapperInterp::Event(EventHandlerPtr f, Val* request, int status, Val* reply)
void PortmapperInterp::Event(EventHandlerPtr f, Val* request, BifEnum::rpc_status status, Val* reply)
{
if ( ! f )
{
@ -279,7 +283,8 @@ void PortmapperInterp::Event(EventHandlerPtr f, Val* request, int status, Val* r
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
if ( status == RPC_SUCCESS )
if ( status == BifEnum::RPC_SUCCESS )
{
if ( request )
vl->append(request);

View file

@ -13,12 +13,12 @@ public:
protected:
int RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n);
int RPC_BuildReply(const RPC_CallInfo* c, int success,
const u_char*& buf, int& n,
EventHandlerPtr& event, Val*& reply);
int RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status success,
const u_char*& buf, int& n, double start_time,
double last_time, int reply_len);
uint32 CheckPort(uint32 port);
void Event(EventHandlerPtr f, Val* request, int status, Val* reply);
void Event(EventHandlerPtr f, Val* request, BifEnum::rpc_status status, Val* reply);
Val* ExtractMapping(const u_char*& buf, int& len);
Val* ExtractPortRequest(const u_char*& buf, int& len);

View file

@ -2,10 +2,12 @@
//
// See the file "COPYING" in the main distribution directory for copyright.
#include "config.h"
#include <stdlib.h>
#include <algorithm>
#include "config.h"
#include "NetVar.h"
#include "XDR.h"
#include "RPC.h"
@ -15,19 +17,19 @@ namespace { // local namespace
const bool DEBUG_rpc_resync = false;
}
// TODO: Should we add start_time and last_time to the rpc_* events??
// TODO: make this configurable
#define MAX_RPC_LEN 65536
// The following correspond to the different RPC status values defined
// in bro.init.
// #define BRO_RPC_TIMEOUT 6
// #define BRO_RPC_AUTH_ERROR 7
// #define BRO_RPC_UNKNOWN_ERROR 8
RPC_CallInfo::RPC_CallInfo(uint32 arg_xid, const u_char*& buf, int& n)
RPC_CallInfo::RPC_CallInfo(uint32 arg_xid, const u_char*& buf, int& n, double arg_start_time, double arg_last_time, int arg_rpc_len)
{
xid = arg_xid;
start_time = network_time;
start_time = arg_start_time;
last_time = arg_last_time;
rpc_len = arg_rpc_len;
call_n = n;
call_buf = new u_char[call_n];
memcpy((void*) call_buf, (const void*) buf, call_n);
@ -76,10 +78,12 @@ RPC_Interpreter::~RPC_Interpreter()
{
}
int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int rpclen,
int is_orig, double start_time, double last_time)
{
uint32 xid = extract_XDR_uint32(buf, n);
uint32 msg_type = extract_XDR_uint32(buf, n);
int rpc_len = n;
if ( ! buf )
return 0;
@ -97,6 +101,14 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
if ( ! call->CompareRexmit(buf, n) )
Weird("RPC_rexmit_inconsistency");
// TODO: Should we update start_time and last_time or
// not??
call->SetStartTime(start_time);
call->SetLastTime(last_time);
// TODO: Not sure whether the handling if rexmit
// inconsistencies are correct. Maybe we should use
// the info in the new call for further processing.
if ( call->HeaderLen() > n )
{
Weird("RPC_underflow");
@ -109,9 +121,10 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
else
{
call = new RPC_CallInfo(xid, buf, n);
call = new RPC_CallInfo(xid, buf, n, start_time, last_time, rpc_len);
if ( ! buf )
{
Weird("bad_RPC");
delete call;
return 0;
}
@ -119,6 +132,11 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
calls.Insert(&h, call);
}
// We now have a valid RPC_CallInfo (either the previous one
// in case of a rexmit or the current one).
// TODO: What to do in case of a rexmit_inconistency??
Event_RPC_Call(call);
if ( RPC_BuildCall(call, buf, n) )
call->SetValidCall();
else
@ -137,7 +155,7 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
if ( ! buf )
return 0;
uint32 status = BifEnum::RPC_UNKNOWN_ERROR;
BifEnum::rpc_status status = BifEnum::RPC_UNKNOWN_ERROR;
if ( reply_stat == RPC_MSG_ACCEPTED )
{
@ -147,7 +165,7 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
// The first members of BifEnum::RPC_* correspond
// to accept_stat.
if ( accept_stat <= RPC_SYSTEM_ERR )
status = accept_stat;
status = (BifEnum::rpc_status)accept_stat;
if ( ! buf )
return 0;
@ -199,13 +217,14 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
else
Weird("bad_RPC");
// We now have extracted the status we want to use.
Event_RPC_Reply(xid, status, n);
if ( call )
{
int success = status == RPC_SUCCESS;
if ( ! call->IsValidCall() )
{
if ( success )
if ( status == BifEnum::RPC_SUCCESS )
Weird("successful_RPC_reply_to_invalid_request");
// We can't process this further, even if
// it was successful, because the call
@ -214,19 +233,11 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
else
{
EventHandlerPtr event;
Val* reply;
if ( ! RPC_BuildReply(call, success, buf,
n, event, reply) )
if ( ! RPC_BuildReply(call, (BifEnum::rpc_status)status, buf, n, start_time, last_time, rpc_len) )
Weird("bad_RPC");
else
{
Event(event, call->TakeRequestVal(),
status, reply);
}
}
RPC_Event(call, status, n);
Event_RPC_Dialogue(call, status, n);
delete calls.RemoveEntry(&h);
}
@ -264,240 +275,406 @@ void RPC_Interpreter::Timeout()
while ( (c = calls.NextEntry(cookie)) )
{
RPC_Event(c, BifEnum::RPC_TIMEOUT, 0);
Event_RPC_Dialogue(c, BifEnum::RPC_TIMEOUT, 0);
if ( c->IsValidCall() )
{
const u_char* buf;
int n = 0;
EventHandlerPtr event;
Val* reply;
if ( ! RPC_BuildReply(c, 0, buf, n, event, reply) )
if ( ! RPC_BuildReply(c, BifEnum::RPC_TIMEOUT, buf, n, network_time, network_time, 0) )
Weird("bad_RPC");
else
{
Event(event, c->TakeRequestVal(),
BifEnum::RPC_TIMEOUT, reply);
}
}
}
}
void RPC_Interpreter::RPC_Event(RPC_CallInfo* c, int status, int reply_len)
void RPC_Interpreter::Event_RPC_Dialogue(RPC_CallInfo* c, BifEnum::rpc_status status, int reply_len)
{
if ( rpc_call )
if ( rpc_dialogue )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(new Val(c->Program(), TYPE_COUNT));
vl->append(new Val(c->Version(), TYPE_COUNT));
vl->append(new Val(c->Proc(), TYPE_COUNT));
vl->append(new Val(status, TYPE_COUNT));
vl->append(new EnumVal(status, BifType::Enum::rpc_status));
vl->append(new Val(c->StartTime(), TYPE_TIME));
vl->append(new Val(c->CallLen(), TYPE_COUNT));
vl->append(new Val(reply_len, TYPE_COUNT));
analyzer->ConnectionEvent(rpc_dialogue, vl);
}
}
void RPC_Interpreter::Event_RPC_Call(RPC_CallInfo* c)
{
if ( rpc_call )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(new Val(c->XID(), TYPE_COUNT));
vl->append(new Val(c->Program(), TYPE_COUNT));
vl->append(new Val(c->Version(), TYPE_COUNT));
vl->append(new Val(c->Proc(), TYPE_COUNT));
vl->append(new Val(c->CallLen(), TYPE_COUNT));
analyzer->ConnectionEvent(rpc_call, vl);
}
}
void RPC_Interpreter::Event_RPC_Reply(uint32_t xid, BifEnum::rpc_status status, int reply_len)
{
if ( rpc_reply )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(new Val(xid, TYPE_COUNT));
vl->append(new EnumVal(status, BifType::Enum::rpc_status));
vl->append(new Val(reply_len, TYPE_COUNT));
analyzer->ConnectionEvent(rpc_reply, vl);
}
}
void RPC_Interpreter::Weird(const char* msg)
{
analyzer->Weird(msg);
}
void RPC_Reasm_Buffer::Init(int64_t arg_maxsize, int64_t arg_expected) {
if ( buf )
delete [] buf;
expected = arg_expected;
maxsize = arg_maxsize;
fill = processed = 0;
buf = new u_char[maxsize];
};
bool RPC_Reasm_Buffer::ConsumeChunk(const u_char*& data, int& len)
{
// How many bytes do we want to process with this call? Either the
// all of the bytes available or the number of bytes that we are
// still missing.
int64_t to_process = min(int64_t(len), (expected-processed));
if ( fill < maxsize )
{
// We haven't yet filled the buffer. How many bytes to copy
// into the buff. Either all of the bytes we want to process
// or the number of bytes until we reach maxsize.
int64_t to_copy = min( to_process, (maxsize-fill) );
if ( to_copy )
memcpy(buf+fill, data, to_copy);
fill += to_copy;
}
processed += to_process;
len -= to_process;
data += to_process;
return (expected == processed);
}
Contents_RPC::Contents_RPC(Connection* conn, bool orig,
RPC_Interpreter* arg_interp)
: TCP_SupportAnalyzer(AnalyzerTag::Contents_RPC, conn, orig)
: TCP_SupportAnalyzer(AnalyzerTag::Contents_RPC, conn, orig)
{
interp = arg_interp;
resync = false;
msg_buf = 0;
InitBuffer();
state = WAIT_FOR_MESSAGE;
resync_state = RESYNC_INIT;
resync_toskip = 0;
start_time = 0;
last_time = 0;
}
void Contents_RPC::Init()
{
TCP_SupportAnalyzer::Init();
TCP_Analyzer* tcp =
static_cast<TCP_ApplicationAnalyzer*>(Parent())->TCP();
assert(tcp);
resync = (IsOrig() ? tcp->OrigState() : tcp->RespState()) !=
TCP_ENDPOINT_ESTABLISHED;
}
void Contents_RPC::InitBuffer()
{
buf_len = 4;
// For record marker:
delete [] msg_buf;
msg_buf = new u_char[buf_len];
buf_n = 0;
last_frag = 0;
state = RPC_RECORD_MARKER;
}
Contents_RPC::~Contents_RPC()
{
delete [] msg_buf;
}
void Contents_RPC::Undelivered(int seq, int len, bool orig)
{
TCP_SupportAnalyzer::Undelivered(seq, len, orig);
// Re-sync after content gaps.
InitBuffer();
resync = true;
NeedResync();
}
void Contents_RPC::DeliverStream(int len, const u_char* data, bool orig)
bool Contents_RPC::CheckResync(int& len, const u_char*& data, bool orig)
{
TCP_SupportAnalyzer::DeliverStream(len, data, orig);
uint32 frame_len;
bool last_frag;
uint32 xid;
uint32 frame_type;
if ( state == RPC_COMPLETE )
InitBuffer();
bool discard_this_chunk = false;
// This is an attempt to re-synchronize the stream with RPC
// frames after a content gap. We try to look for the beginning
// of an RPC frame, assuming (1) RPC frames begin at packet
// boundaries (though they may span over multiple packets) and
// (2) the first piece is longer than 12 bytes. (If we see a
// piece shorter than 12 bytes, it is likely that it's the
// remaining piece of a previous RPC frame, so the code here
// skips that piece.) It then checks if the frame type and length
// make any sense, and if so, it assumes that is beginning of
// a frame.
if ( resync && state == RPC_RECORD_MARKER && buf_n == 0 )
if ( resync_state == RESYNC_INIT )
{
// First time CheckResync is called. If the TCP endpoint
// is fully established we are in sync (since it's the first chunk
// of data after the SYN if its not established we need to
// resync.
TCP_Analyzer* tcp =
static_cast<TCP_ApplicationAnalyzer*>(Parent())->TCP();
assert(tcp);
if ( (IsOrig() ? tcp->OrigState() : tcp->RespState()) !=
TCP_ENDPOINT_ESTABLISHED )
{
NeedResync();
}
else
resync_state = INSYNC;
}
if ( resync_state == INSYNC )
return true;
// This is an attempt to re-synchronize the stream with RPC frames
// after a content gap. Returns true if we are in sync. Returns
// false otherwise (we are in resync mode)
//
// We try to look for the beginning of a RPC frame, assuming RPC
// frames begin at packet boundaries (though they may span over
// multiple packets) (note that the data* of DeliverStream() usually
// starts at a packet boundrary).
//
// If we see a frame start that makes sense (direction and frame
// lenght seem ok), we try to read (skip over) the next RPC message.
// If this is successfull and we the place we are seems like a valid
// start of a RPC msg (direction and frame length seem ok). We assume
// that we have successfully resync'ed.
// Assuming RPC frames align with packet boundaries ...
while (len > 0)
{
// Assuming RPC frames align with packet boundaries ...
if ( resync_toskip )
{
if ( DEBUG_rpc_resync )
DEBUG_MSG("RPC resync: skipping %d bytes.\n", len);
// We have some bytes to skip over.
if ( resync_toskip < len )
{
len -= resync_toskip;
data += resync_toskip;
resync_toskip = 0;
}
else
{
resync_toskip -= len;
data += len;
len = 0;
return false;
}
}
if ( resync_toskip != 0 )
// Should never happen.
internal_error("RPC resync: skipping over data failed");
// Now lets see whether data points to the beginning of a RPC
// frame. If the resync processs is successful, we should be
// at the beginning of a frame.
if ( len < 12 )
{
// Ignore small fragmeents.
// Ignore small chunks.
if ( len != 1 && DEBUG_rpc_resync )
{
// One-byte fragments are likely caused by
// TCP keep-alive retransmissions.
DEBUG_MSG("%.6f RPC resync: "
"discard small pieces: %d\n",
network_time, len);
"discard small pieces: %d\n",
network_time, len);
Conn()->Weird(
fmt("RPC resync: discard %d bytes\n",
len));
}
return;
NeedResync();
return false;
}
const u_char* xdata = data;
const u_char *xdata = data;
int xlen = len;
uint32 frame_len = extract_XDR_uint32(xdata, xlen);
uint32 xid = extract_XDR_uint32(xdata, xlen);
uint32 frame_type = extract_XDR_uint32(xdata, xlen);
frame_len = extract_XDR_uint32(xdata, xlen);
last_frag = (frame_len & 0x80000000) != 0;
frame_len &= 0x7fffffff;
xid = extract_XDR_uint32(xdata, xlen);
frame_type = extract_XDR_uint32(xdata, xlen);
// Check if the direction makes sense and the length of the
// frame to expect.
if ( (IsOrig() && frame_type != 0) ||
(! IsOrig() && frame_type != 1) ||
frame_len < 16 )
(! IsOrig() && frame_type != 1) ||
frame_len < 16 )
discard_this_chunk = true;
// Make sure the frame isn't too long.
// TODO: Could possible even reduce this number even further.
if ( frame_len > MAX_RPC_LEN )
discard_this_chunk = true;
if ( discard_this_chunk )
{
// Skip this packet.
// Skip this chunk
if ( DEBUG_rpc_resync )
{
DEBUG_MSG("RPC resync: skipping %d bytes\n",
len);
}
return;
DEBUG_MSG("RPC resync: Need to resync. dicarding %d bytes.\n", len);
NeedResync(); // let's try the resync again from the beginning
return false;
}
resync = false;
}
// Looks like we are at the start of a frame and have successfully
// extracted the frame length (marker).
int n;
for ( n = 0; buf_n < buf_len && n < len; ++n )
msg_buf[buf_n++] = data[n];
switch (resync_state) {
case NEED_RESYNC:
case RESYNC_WAIT_FOR_MSG_START:
// Initial phase of resyncing. Skip frames until we get a frame
// with the last_fragment bit set.
resync_toskip = frame_len + 4;
if ( buf_n < buf_len )
// Haven't filled up the message buffer yet, no more to do.
return;
switch ( state ) {
case RPC_RECORD_MARKER:
{ // Have the whole record marker.
int prev_frag_len = buf_len - 4;
const u_char* buf = &msg_buf[prev_frag_len];
int n = 4;
uint32 marker = extract_XDR_uint32(buf, n);
if ( ! buf )
internal_error("inconsistent RPC record marker extraction");
if ( prev_frag_len > 0 && last_frag )
internal_error("last_frag set but more fragments");
last_frag = (marker & 0x80000000) != 0;
marker &= 0x7fffffff;
if ( prev_frag_len > 0 )
// We're adding another fragment.
marker += prev_frag_len;
// Fragment length is now given by marker. Sanity-check.
if ( marker > MAX_RPC_LEN )
{
Conn()->Weird("excessive_RPC_len");
marker = MAX_RPC_LEN;
}
// The new size is either the full record size (if this
// is the last fragment), or that plus 4 more bytes for
// the next fragment header.
int new_size = last_frag ? marker : marker + 4;
u_char* tmp = new u_char[new_size];
int msg_len = (unsigned int) buf_len < marker ? buf_len : marker;
for ( int i = 0; i < msg_len; ++i )
tmp[i] = msg_buf[i];
delete [] msg_buf;
msg_buf = tmp;
buf_len = marker; // we only want to fill to here
buf_n = prev_frag_len; // overwrite this fragment's header
state = RPC_MESSAGE_BUFFER;
}
break;
case RPC_MESSAGE_BUFFER:
{ // Have the whole fragment.
if ( ! last_frag )
{
// We earlier made sure to leave an extra 4 bytes
// at the end of the buffer - use them now for
// the new fragment header.
buf_len += 4;
state = RPC_RECORD_MARKER;
if ( last_frag )
resync_state = RESYNC_WAIT_FOR_FULL_MSG;
else
resync_state = RESYNC_WAIT_FOR_MSG_START;
break;
}
if ( ! interp->DeliverRPC(msg_buf, buf_n, IsOrig()) )
Conn()->Weird("partial_RPC");
case RESYNC_WAIT_FOR_FULL_MSG:
// If the resync was successful so far, we should now be the start
// of a new RPC message. Try to skip over it.
resync_toskip = frame_len + 4;
state = RPC_COMPLETE;
delete [] msg_buf;
msg_buf = 0;
}
break;
if ( last_frag )
resync_state = RESYNC_HAD_FULL_MSG;
break;
case RPC_COMPLETE:
internal_error("RPC state inconsistency");
case RESYNC_HAD_FULL_MSG:
// We have now successfully skipped over a full RPC message.
// If we got that far, we are in sync.
resync_state = INSYNC;
if ( DEBUG_rpc_resync )
DEBUG_MSG("RPC resync: success.\n");
return true;
default:
// Should never happen.
NeedResync();
return false;
} // end switch
} // end while (len>0)
return false;
}
if ( n < len )
// More data to munch on.
DeliverStream(len - n, data + n, orig);
void Contents_RPC::DeliverStream(int len, const u_char* data, bool orig)
{
TCP_SupportAnalyzer::DeliverStream(len, data, orig);
uint32 marker;
bool last_frag;
if ( ! CheckResync(len, data, orig) )
return; // Not in sync yet. Still resyncing.
// Should be in sync now.
while (len > 0)
{
last_time = network_time;
switch (state) {
case WAIT_FOR_MESSAGE:
// A new RPC message is starting. Initialize state.
// We expect and want 4 bytes of the frame markers.
marker_buf.Init(4,4);
// We want at most 64KB of message data and we don't
// know yet how much we expect, so we set expected to
// 0.
msg_buf.Init(MAX_RPC_LEN, 0);
last_frag = 0;
state = WAIT_FOR_MARKER;
start_time = network_time;
// no break. fall through
case WAIT_FOR_MARKER:
{
bool got_marker = marker_buf.ConsumeChunk(data,len);
if ( got_marker )
{
const u_char *dummy_p = marker_buf.GetBuf();
int dummy_len = (int) marker_buf.GetFill();
// have full marker
marker = extract_XDR_uint32(dummy_p, dummy_len);
marker_buf.Init(4,4);
if ( ! dummy_p )
{
internal_error("inconsistent RPC record marker extraction");
}
last_frag = (marker & 0x80000000) != 0;
marker &= 0x7fffffff;
//printf("%.6f %d marker= %u <> last_frag= %d <> expected=%llu <> processed= %llu <> len = %d\n",
// network_time, IsOrig(), marker, last_frag, msg_buf.GetExpected(), msg_buf.GetProcessed(), len);
if ( ! msg_buf.AddToExpected(marker) )
Conn()->Weird(fmt("RPC_message_too_long (%" PRId64 ")" , msg_buf.GetExpected()));
if ( last_frag )
state = WAIT_FOR_LAST_DATA;
else
state = WAIT_FOR_DATA;
}
}
// Else remain in state. Haven't got the full 4 bytes
// for the marker yet.
break;
case WAIT_FOR_DATA:
case WAIT_FOR_LAST_DATA:
{
bool got_all_data = msg_buf.ConsumeChunk(data, len);
if ( got_all_data )
{
// Got all the data we expected. Now let's
// see whether there is another fragment
// coming or whether we just finished the
// last fragment.
if ( state == WAIT_FOR_LAST_DATA )
{
const u_char *dummy_p = msg_buf.GetBuf();
int dummy_len = (int) msg_buf.GetFill();
if ( ! interp->DeliverRPC(dummy_p, dummy_len, (int)msg_buf.GetExpected(), IsOrig(), start_time, last_time) )
Conn()->Weird("partial_RPC");
state = WAIT_FOR_MESSAGE;
}
else
state = WAIT_FOR_MARKER;
}
// Else remain in state. Haven't read all the data
// yet.
}
break;
} // end switch
} // end while
}
RPC_Analyzer::RPC_Analyzer(AnalyzerTag::Tag tag, Connection* conn,
@ -520,15 +697,16 @@ void RPC_Analyzer::DeliverPacket(int len, const u_char* data, bool orig,
int seq, const IP_Hdr* ip, int caplen)
{
TCP_ApplicationAnalyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
len = min(len, caplen);
if ( orig )
{
if ( ! interp->DeliverRPC(data, len, 1) )
if ( ! interp->DeliverRPC(data, len, len, 1, network_time, network_time) )
Weird("bad_RPC");
}
else
{
if ( ! interp->DeliverRPC(data, len, 0) )
if ( ! interp->DeliverRPC(data, len, len, 0, network_time, network_time) )
Weird("bad_RPC");
}
}
@ -537,24 +715,7 @@ void RPC_Analyzer::Done()
{
TCP_ApplicationAnalyzer::Done();
// This code was replicated in NFS.cc and Portmap.cc, so we factor
// it into here. The semantics have slightly changed - it used
// to be we'd always execute interp->Timeout(), but now we only
// do for UDP.
if ( Conn()->ConnTransport() == TRANSPORT_TCP && TCP() )
{
if ( orig_rpc->State() != RPC_COMPLETE &&
(TCP()->OrigState() == TCP_ENDPOINT_CLOSED ||
TCP()->OrigPrevState() == TCP_ENDPOINT_CLOSED) &&
// Sometimes things like tcpwrappers will immediately
// close the connection, without any data having been
// transferred. Don't bother flagging these.
TCP()->Orig()->Size() > 0 )
Weird("partial_RPC_request");
}
else
interp->Timeout();
interp->Timeout();
}
void RPC_Analyzer::ExpireTimer(double /* t */)
@ -562,44 +723,3 @@ void RPC_Analyzer::ExpireTimer(double /* t */)
Event(connection_timeout);
sessions->Remove(Conn());
}
// The binpac version of interpreter.
#include "rpc_pac.h"
RPC_UDP_Analyzer_binpac::RPC_UDP_Analyzer_binpac(Connection* conn)
: Analyzer(AnalyzerTag::RPC_UDP_BINPAC, conn)
{
interp = new binpac::SunRPC::RPC_Conn(this);
ADD_ANALYZER_TIMER(&RPC_UDP_Analyzer_binpac::ExpireTimer,
network_time + rpc_timeout, 1, TIMER_RPC_EXPIRE);
}
RPC_UDP_Analyzer_binpac::~RPC_UDP_Analyzer_binpac()
{
delete interp;
}
void RPC_UDP_Analyzer_binpac::Done()
{
Analyzer::Done();
interp->Timeout();
}
void RPC_UDP_Analyzer_binpac::DeliverPacket(int len, const u_char* data, bool orig, int seq, const IP_Hdr* ip, int caplen)
{
Analyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
try
{
interp->NewData(orig, data, data + len);
}
catch ( binpac::Exception &e )
{
Weird(fmt("bad_RPC: %s", e.msg().c_str()));
}
}
void RPC_UDP_Analyzer_binpac::ExpireTimer(double /* t */)
{
Event(connection_timeout);
sessions->Remove(Conn());
}

150
src/RPC.h
View file

@ -49,7 +49,8 @@ enum {
class RPC_CallInfo {
public:
RPC_CallInfo(uint32 xid, const u_char*& buf, int& n);
RPC_CallInfo(uint32 xid, const u_char*& buf, int& n, double start_time,
double last_time, int rpc_len);
~RPC_CallInfo();
void AddVal(Val* arg_v) { Unref(v); v = arg_v; }
@ -63,8 +64,12 @@ public:
uint32 Proc() const { return proc; }
double StartTime() const { return start_time; }
void SetStartTime(double t) { start_time = t; }
double LastTime() const { return last_time; }
void SetLastTime(double t) { last_time = t; }
int CallLen() const { return call_n; }
int HeaderLen() const { return header_len; }
int RPCLen() const { return rpc_len; }
int HeaderLen() const { return header_len; }
uint32 XID() const { return xid; }
@ -76,6 +81,8 @@ protected:
uint32 cred_flavor, verf_flavor;
u_char* call_buf; // copy of original call buffer
double start_time;
double last_time;
int rpc_len; // size of the full RPC call, incl. xid and msg_type
int call_n; // size of call buf
int header_len; // size of data before the arguments
bool valid_call; // whether call was well-formed
@ -93,19 +100,19 @@ public:
// Delivers the given RPC. Returns true if "len" bytes were
// enough, false otherwise. "is_orig" is true if the data is
// from the originator of the connection.
int DeliverRPC(const u_char* data, int len, int is_orig);
int DeliverRPC(const u_char* data, int len, int caplen, int is_orig, double start_time, double last_time);
void Timeout();
protected:
virtual int RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n) = 0;
virtual int RPC_BuildReply(const RPC_CallInfo* c, int success,
const u_char*& buf, int& n,
EventHandlerPtr& event, Val*& reply) = 0;
virtual int RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status success,
const u_char*& buf, int& n, double start_time, double last_time,
int reply_len) = 0;
virtual void Event(EventHandlerPtr f, Val* request, int status, Val* reply) = 0;
void RPC_Event(RPC_CallInfo* c, int status, int reply_len);
void Event_RPC_Dialogue(RPC_CallInfo* c, BifEnum::rpc_status status, int reply_len);
void Event_RPC_Call(RPC_CallInfo* c);
void Event_RPC_Reply(uint32_t xid, BifEnum::rpc_status status, int reply_len);
void Weird(const char* name);
@ -113,35 +120,108 @@ protected:
Analyzer* analyzer;
};
typedef enum {
RPC_RECORD_MARKER, // building up the stream record marker
RPC_MESSAGE_BUFFER, // building up the message in the buffer
RPC_COMPLETE // message fully built
} TCP_RPC_state;
/* A simple buffer for reassembling the fragments that RPC-over-TCP
* uses. Only needed by RPC_Contents.
* However, RPC messages can be quite large. As a first step, we only
* extract and analyzer the first part of an RPC message and skip
* over the rest.
*
* We specify:
* maxsize: the number of bytes we want to copy into the buffer to analyze.
* expected: the total number of bytes in the RPC message. Can be
* quite large. We will be "skipping over" expected-maxsize bytes.
*
* We can extend "expected" (by calling AddToExpected()), but maxsize is
* fixed.
*
* TODO: grow buffer dynamically
*/
class RPC_Reasm_Buffer {
public:
RPC_Reasm_Buffer() {
maxsize = expected = 0;
fill = processed = 0;
buf = 0;
};
~RPC_Reasm_Buffer() { if (buf) delete [] buf; }
void Init(int64_t arg_maxsize, int64_t arg_expected);
const u_char *GetBuf() { return buf; } // Pointer to the buffer
int64_t GetFill() { return fill; } // Number of bytes in buf
int64_t GetSkipped() { return processed-fill; } // How many bytes did we skipped?
int64_t GetExpected() { return expected; } // How many bytes are we expecting?
int64_t GetProcessed() { return processed; } // How many bytes are we expecting?
// Expand expected by delta bytes. Returns false if the number of
// expected bytes exceeds maxsize (which means that we will truncate
// the message).
bool AddToExpected(int64_t delta)
{ expected += delta; return ! (expected > maxsize); }
// Consume a chunk of input data (pointed to by data, up len in
// size). data and len will be adjusted accordingly. Returns true if
// "expected" bytes have been processed, i.e., returns true when we
// don't expect any more data.
bool ConsumeChunk(const u_char*& data, int& len);
protected:
int64_t fill; // how many bytes we currently have in the buffer
int64_t maxsize; // maximum buffer size we want to allocate
int64_t processed; // number of bytes we have processed so far
int64_t expected; // number of input bytes we expect
u_char *buf;
};
/* Support Analyzer for reassembling RPC-over-TCP messages */
class Contents_RPC : public TCP_SupportAnalyzer {
public:
Contents_RPC(Connection* conn, bool orig, RPC_Interpreter* interp);
virtual ~Contents_RPC();
TCP_RPC_state State() const { return state; }
protected:
typedef enum {
WAIT_FOR_MESSAGE,
WAIT_FOR_MARKER,
WAIT_FOR_DATA,
WAIT_FOR_LAST_DATA,
} state_t;
typedef enum {
NEED_RESYNC,
RESYNC_WAIT_FOR_MSG_START,
RESYNC_WAIT_FOR_FULL_MSG,
RESYNC_HAD_FULL_MSG,
INSYNC,
RESYNC_INIT,
} resync_state_t;
virtual void Init();
virtual bool CheckResync(int& len, const u_char*& data, bool orig);
virtual void DeliverStream(int len, const u_char* data, bool orig);
virtual void Undelivered(int seq, int len, bool orig);
virtual void InitBuffer();
virtual void NeedResync() {
resync_state = NEED_RESYNC;
resync_toskip = 0;
state = WAIT_FOR_MESSAGE;
}
RPC_Interpreter* interp;
u_char* msg_buf;
int buf_n; // number of bytes in msg_buf
int buf_len; // size off msg_buf
int last_frag; // if this buffer corresponds to the last "fragment"
bool resync;
RPC_Reasm_Buffer marker_buf; // reassembles the 32bit RPC-over-TCP marker
RPC_Reasm_Buffer msg_buf; // reassembles RPC messages
state_t state;
TCP_RPC_state state;
double start_time;
double last_time;
resync_state_t resync_state;
int resync_toskip;
};
class RPC_Analyzer : public TCP_ApplicationAnalyzer {
@ -164,28 +244,4 @@ protected:
Contents_RPC* resp_rpc;
};
#include "rpc_pac.h"
class RPC_UDP_Analyzer_binpac : public Analyzer {
public:
RPC_UDP_Analyzer_binpac(Connection* conn);
virtual ~RPC_UDP_Analyzer_binpac();
virtual void Done();
virtual void DeliverPacket(int len, const u_char* data, bool orig,
int seq, const IP_Hdr* ip, int caplen);
static Analyzer* InstantiateAnalyzer(Connection* conn)
{ return new RPC_UDP_Analyzer_binpac(conn); }
static bool Available()
{ return pm_request || rpc_call; }
protected:
friend class AnalyzerTimer;
void ExpireTimer(double t);
binpac::SunRPC::RPC_Conn* interp;
};
#endif

View file

@ -2,6 +2,8 @@
//
// See the file "COPYING" in the main distribution directory for copyright.
#include <algorithm>
#include "config.h"
#include "Reassem.h"

View file

@ -1,5 +1,7 @@
// $Id: RuleMatcher.cc 6724 2009-06-07 09:23:03Z vern $
#include <algorithm>
#include "config.h"
#include "Analyzer.h"

View file

@ -2,6 +2,8 @@
//
// See the file "COPYING" in the main distribution directory for copyright.
#include <algorithm>
#include "NetVar.h"
#include "PIA.h"
#include "File.h"

View file

@ -1,5 +1,7 @@
// $Id: TCP_Reassembler.cc,v 1.1.2.8 2006/05/31 01:52:02 sommer Exp $
#include <algorithm>
#include "Analyzer.h"
#include "TCP_Reassembler.h"
#include "TCP.h"
@ -218,8 +220,9 @@ void TCP_Reassembler::Undelivered(int up_to_seq)
// handshakes, but Oh Well.
if ( content_gap &&
endpoint->state == TCP_ENDPOINT_ESTABLISHED &&
peer->state == TCP_ENDPOINT_ESTABLISHED )
(BifConst::report_gaps_for_partial ||
(endpoint->state == TCP_ENDPOINT_ESTABLISHED &&
peer->state == TCP_ENDPOINT_ESTABLISHED ) ) )
{
val_list* vl = new val_list;
vl->append(dst_analyzer->BuildConnVal());
@ -487,8 +490,9 @@ void TCP_Reassembler::AckReceived(int seq)
return;
bool test_active = ! skip_deliveries && ! tcp_analyzer->Skipping() &&
endp->state == TCP_ENDPOINT_ESTABLISHED &&
endp->peer->state == TCP_ENDPOINT_ESTABLISHED;
( BifConst::report_gaps_for_partial ||
(endp->state == TCP_ENDPOINT_ESTABLISHED &&
endp->peer->state == TCP_ENDPOINT_ESTABLISHED ) );
int num_missing = TrimToSeq(seq);

View file

@ -2,6 +2,8 @@
//
// See the file "COPYING" in the main distribution directory for copyright.
#include <algorithm>
#include "config.h"
#include "Net.h"

View file

@ -2,6 +2,8 @@
//
// See the file "COPYING" in the main distribution directory for copyright.
#include <algorithm>
#include "config.h"
#include "XDR.h"
@ -26,18 +28,18 @@ uint32 extract_XDR_uint32(const u_char*& buf, int& len)
return bits32;
}
double extract_XDR_uint64_as_double(const u_char*& buf, int& len)
uint64 extract_XDR_uint64(const u_char*& buf, int& len)
{
if ( ! buf || len < 8 )
{
buf = 0;
return 0.0;
return 0;
}
uint32 uhi = extract_XDR_uint32(buf, len);
uint32 ulo = extract_XDR_uint32(buf, len);
uint64 uhi = extract_XDR_uint32(buf, len);
uint64 ulo = extract_XDR_uint32(buf, len);
return double(uhi) * 4294967296.0 + double(ulo);
return (uhi << 32) + ulo;
}
double extract_XDR_time(const u_char*& buf, int& len)
@ -54,12 +56,15 @@ double extract_XDR_time(const u_char*& buf, int& len)
return double(uhi) + double(ulo) / 1e9;
}
const u_char* extract_XDR_opaque(const u_char*& buf, int& len, int& n, int max_len)
const u_char* extract_XDR_opaque(const u_char*& buf, int& len, int& n, int max_len, bool short_buf_ok)
{
n = int(extract_XDR_uint32(buf, len));
if ( ! buf )
return 0;
if ( short_buf_ok )
n = std::min(n, len);
if ( n < 0 || n > len || n > max_len )
{ // ### Should really flag this as a different sort of error.
buf = 0;
@ -75,6 +80,25 @@ const u_char* extract_XDR_opaque(const u_char*& buf, int& len, int& n, int max_l
return opaque;
}
const u_char* extract_XDR_opaque_fixed(const u_char*& buf, int& len, int n)
{
if ( ! buf )
return 0;
if ( n < 0 || n > len)
{
buf = 0;
return 0;
}
int n4 = ((n + 3) >> 2) << 2; // n rounded up to next multiple of 4
len -= n4;
const u_char* opaque = buf;
buf += n4;
return opaque;
}
uint32 skip_XDR_opaque_auth(const u_char*& buf, int& len)
{
uint32 auth_flavor = extract_XDR_uint32(buf, len);

View file

@ -11,10 +11,11 @@
#include "util.h"
extern uint32 extract_XDR_uint32(const u_char*& buf, int& len);
extern double extract_XDR_uint64_as_double(const u_char*& buf, int& len);
extern uint64 extract_XDR_uint64(const u_char*& buf, int& len);
extern double extract_XDR_time(const u_char*& buf, int& len);
extern const u_char* extract_XDR_opaque(const u_char*& buf, int& len,
int& n, int max_len=8192);
int& n, int max_len=8192, bool short_buf_ok=false);
extern const u_char* extract_XDR_opaque_fixed(const u_char*& buf, int& len, int n);
extern uint32 skip_XDR_opaque_auth(const u_char*& buf, int& len);
#endif

View file

@ -3463,3 +3463,89 @@ function x509_err2str%(err_num: count%): string
%{
return new StringVal(X509_verify_cert_error_string(err_num));
%}
function NFS3::mode2string%(mode: count%): string
%{
char str[12];
char *p = str;
/* usr */
if (mode & S_IRUSR)
*p++ = 'r';
else
*p++ = '-';
if (mode & S_IWUSR)
*p++ = 'w';
else
*p++ = '-';
switch (mode & (S_IXUSR | S_ISUID)) {
case 0:
*p++ = '-';
break;
case S_IXUSR:
*p++ = 'x';
break;
case S_ISUID:
*p++ = 'S';
break;
case S_IXUSR | S_ISUID:
*p++ = 's';
break;
}
/* group */
if (mode & S_IRGRP)
*p++ = 'r';
else
*p++ = '-';
if (mode & S_IWGRP)
*p++ = 'w';
else
*p++ = '-';
switch (mode & (S_IXGRP | S_ISGID)) {
case 0:
*p++ = '-';
break;
case S_IXGRP:
*p++ = 'x';
break;
case S_ISGID:
*p++ = 'S';
break;
case S_IXGRP | S_ISGID:
*p++ = 's';
break;
}
/* other */
if (mode & S_IROTH)
*p++ = 'r';
else
*p++ = '-';
if (mode & S_IWOTH)
*p++ = 'w';
else
*p++ = '-';
switch (mode & (S_IXOTH | S_ISVTX)) {
case 0:
*p++ = '-';
break;
case S_IXOTH:
*p++ = 'x';
break;
case S_ISVTX:
*p++ = 'T';
break;
case S_IXOTH | S_ISVTX:
*p++ = 't';
break;
}
*p = '\0';
return new StringVal(str);
%}

View file

@ -1,9 +1,14 @@
# $Id: const.bif 3929 2007-01-14 00:37:59Z vern $
# Documentation and default values for these are located in policy/bro.dif.
# Documentation and default values for these are located in policy/bro.init.
const ignore_keep_alive_rexmit: bool;
const skip_http_data: bool;
const parse_udp_tunnels: bool;
const use_conn_size_analyzer: bool;
const report_gaps_for_partial: bool;
const NFS3::return_data: bool;
const NFS3::return_data_max: count;
const NFS3::return_data_first_only: bool;

View file

@ -157,7 +157,13 @@ event mime_all_data%(c: connection, length: count, data: string%);
event mime_event%(c: connection, event_type: string, detail: string%);
event mime_content_hash%(c: connection, content_len: count, hash_value: string%);
event rpc_call%(c: connection, prog: count, ver: count, proc: count, status: count, start_time: time, call_len: count, reply_len: count%);
# Generated for each RPC request / reply *pair* (if there is no reply, the event
# will be generated on timeout).
event rpc_dialogue%(c: connection, prog: count, ver: count, proc: count, status: rpc_status, start_time: time, call_len: count, reply_len: count%);
# Generated for each (correctly formed) RPC_CALL message received.
event rpc_call%(c: connection, xid: count, prog: count, ver: count, proc: count, call_len: count%);
# Generated for each (correctly formed) RPC_REPLY message received.
event rpc_reply%(c: connection, xid: count, status: rpc_status, reply_len: count%);
event pm_request_null%(r: connection%);
event pm_request_set%(r: connection, m: pm_mapping, success: bool%);
@ -173,15 +179,28 @@ event pm_attempt_dump%(r: connection, status: rpc_status%);
event pm_attempt_callit%(r: connection, status: rpc_status, call: pm_callit_request%);
event pm_bad_port%(r: connection, bad_p: count%);
event nfs_request_null%(n: connection%);
event nfs_request_getattr%(n: connection, fh: string, attrs: nfs3_attrs%);
event nfs_request_lookup%(n: connection, req: nfs3_lookup_args, rep: nfs3_lookup_reply%);
event nfs_request_fsstat%(n: connection, root_fh: string, stat: nfs3_fsstat%);
event nfs_attempt_null%(n: connection, status: count%);
event nfs_attempt_getattr%(n: connection, status: count, fh: string%);
event nfs_attempt_lookup%(n: connection, status: count, req: nfs3_lookup_args%);
event nfs_attempt_fsstat%(n: connection, status: count, root_fh: string%);
event nfs_reply_status%(n: connection, status: count%);
# Events for the NFS analyzer. An event is generated if we have received a
# Call (request) / Response pair (or in case of a time out). info$rpc_stat and
# info$nfs_stat show whether the request was successful. The request record is
# always filled out, however, the reply record(s) might not be set or might only
# be partially set. See the comments for the record types in bro.init to see which
# reply fields are set when.
event nfs_proc_null%(c: connection, info: NFS3::info_t%);
event nfs_proc_not_implemented%(c: connection, info: NFS3::info_t, proc: NFS3::proc_t%);
event nfs_proc_getattr%(c: connection, info: NFS3::info_t, fh: string, attrs: NFS3::fattr_t%);
event nfs_proc_lookup%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::lookup_reply_t%);
event nfs_proc_read%(c: connection, info: NFS3::info_t, req: NFS3::readargs_t, rep: NFS3::read_reply_t%);
event nfs_proc_readlink%(c: connection, info: NFS3::info_t, fh: string, rep: NFS3::readlink_reply_t%);
event nfs_proc_write%(c: connection, info: NFS3::info_t, req: NFS3::writeargs_t, rep: NFS3::write_reply_t%);
event nfs_proc_create%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::newobj_reply_t%);
event nfs_proc_mkdir%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::newobj_reply_t%);
event nfs_proc_remove%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::delobj_reply_t%);
event nfs_proc_rmdir%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::delobj_reply_t%);
event nfs_proc_readdir%(c: connection, info: NFS3::info_t, req: NFS3::readdirargs_t, rep: NFS3::readdir_reply_t%);
# Generated for each NFS reply message we receive, giving just gives the status.
event nfs_reply_status%(n: connection, info: NFS3::info_t%);
event ntp_message%(u: connection, msg: ntp_msg, excess: string%);

View file

@ -1,191 +0,0 @@
# $Id:$
%include portmap-protocol.pac
# Add a function to the hook in the RPC connection to build the call Val.
refine casefunc RPC_BuildCallVal += {
RPC_SERVICE_PORTMAP ->
PortmapBuildCallVal(call, call.params.portmap);
};
# ... and a function invocation to handle successful portmap replies.
refine typeattr PortmapResults += &let {
action_reply: bool = $context.connection.ProcessPortmapReply(this);
};
# ... and a function to handle failed portmap calls.
refine casefunc RPC_CallFailed += {
RPC_SERVICE_PORTMAP -> PortmapCallFailed(connection, call, status);
};
# Build portmap call Val.
function PortmapBuildCallVal(call: RPC_Call, params: PortmapParams): BroVal =
case call.proc of {
PMAPPROC_NULL, PMAPPROC_DUMP
-> NULL;
PMAPPROC_SET, PMAPPROC_UNSET
-> PortmapBuildMappingVal(params.mapping);
PMAPPROC_GETPORT
-> PortmapBuildPortRequest(params.mapping);
PMAPPROC_CALLIT
-> PortmapBuildCallItVal(params.callit);
};
function PortmapBuildPortVal(port: uint32, proto: uint32): BroPortVal
%{
// TODO: replace port with CheckPort(port)
return new PortVal(port, proto == IPPROTO_TCP ?
TRANSPORT_TCP : TRANSPORT_UDP);
%}
function PortmapBuildMappingVal(params: PortmapMapping): BroVal
%{
RecordVal* mapping = new RecordVal(pm_mapping);
mapping->Assign(0, new Val(params->prog(), TYPE_COUNT));
mapping->Assign(1, new Val(params->vers(), TYPE_COUNT));
mapping->Assign(2, PortmapBuildPortVal(params->port(),
params->proto()));
return mapping;
%}
function PortmapBuildPortRequest(params: PortmapMapping): BroVal
%{
RecordVal* request = new RecordVal(pm_port_request);
request->Assign(0, new Val(params->prog(), TYPE_COUNT));
request->Assign(1, new Val(params->vers(), TYPE_COUNT));
request->Assign(2, new Val(params->proto() == IPPROTO_TCP, TYPE_BOOL));
return request;
%}
function PortmapBuildCallItVal(params: PortmapCallItParams): BroVal
%{
RecordVal* c = new RecordVal(pm_callit_request);
c->Assign(0, new Val(params->prog(), TYPE_COUNT));
c->Assign(1, new Val(params->vers(), TYPE_COUNT));
c->Assign(2, new Val(params->proc(), TYPE_COUNT));
c->Assign(3, new Val(params->params()->length(), TYPE_COUNT));
return c;
%}
function PortmapBuildDumpVal(params: PortmapDumpResults): BroVal
%{
TableVal* mappings = new TableVal(pm_mappings);
for ( int i = 0; i < params->size(); ++i )
{
// The last element has cont()!=1 and this element doesn't contain a
// mapping.
if ((*params)[i]->cont() != 1)
continue;
Val* m = PortmapBuildMappingVal((*params)[i]->mapping());
Val* index = new Val(i + 1, TYPE_COUNT);
mappings->Assign(index, m);
Unref(index);
}
return mappings;
%}
refine connection RPC_Conn += {
function ProcessPortmapReply(results: PortmapResults): bool
%{
RPC_Call const* call = results->call();
PortmapParams const* params = call->params()->portmap();
switch ( call->proc() ) {
case PMAPPROC_NULL:
BifEvent::generate_pm_request_null(bro_analyzer(), bro_analyzer()->Conn());
break;
case PMAPPROC_SET:
BifEvent::generate_pm_request_set(bro_analyzer(),
bro_analyzer()->Conn(),
call->call_val(), results->set());
break;
case PMAPPROC_UNSET:
BifEvent::generate_pm_request_unset(bro_analyzer(),
bro_analyzer()->Conn(),
call->call_val(), results->unset());
break;
case PMAPPROC_GETPORT:
BifEvent::generate_pm_request_getport(bro_analyzer(),
bro_analyzer()->Conn(),
call->call_val(),
PortmapBuildPortVal(results->getport(),
params->mapping()->proto()));
break;
case PMAPPROC_DUMP:
BifEvent::generate_pm_request_dump(bro_analyzer(),
bro_analyzer()->Conn(),
PortmapBuildDumpVal(results->dump()));
break;
case PMAPPROC_CALLIT:
BifEvent::generate_pm_request_callit(bro_analyzer(),
bro_analyzer()->Conn(),
call->call_val(),
new PortVal(results->callit()->port(),
TRANSPORT_UDP));
break;
default:
return false;
}
return true;
%}
};
function PortmapCallFailed(connection: RPC_Conn,
call: RPC_Call,
status: EnumRPCStatus): bool
%{
// BifEnum::rpc_status st = static_cast<BifEnum::rpc_status>(status);
Val *st = new EnumVal(status, BifType::Enum::rpc_status);
switch ( call->proc() ) {
case PMAPPROC_NULL:
BifEvent::generate_pm_attempt_null(connection->bro_analyzer(),
connection->bro_analyzer()->Conn(), st);
break;
case PMAPPROC_SET:
BifEvent::generate_pm_attempt_set(connection->bro_analyzer(),
connection->bro_analyzer()->Conn(), st, call->call_val());
break;
case PMAPPROC_UNSET:
BifEvent::generate_pm_attempt_unset(connection->bro_analyzer(),
connection->bro_analyzer()->Conn(), st, call->call_val());
break;
case PMAPPROC_GETPORT:
BifEvent::generate_pm_attempt_getport(connection->bro_analyzer(),
connection->bro_analyzer()->Conn(), st, call->call_val());
break;
case PMAPPROC_DUMP:
BifEvent::generate_pm_attempt_dump(connection->bro_analyzer(),
connection->bro_analyzer()->Conn(), st);
break;
case PMAPPROC_CALLIT:
BifEvent::generate_pm_attempt_callit(connection->bro_analyzer(),
connection->bro_analyzer()->Conn(), st, call->call_val());
break;
default:
return false;
}
return true;
%}

View file

@ -1,77 +0,0 @@
# $Id:$
# Extends rpc-protocol.pac.
###################
# Hooks into RPC data structures.
refine casefunc RPC_Service += {
100000 -> RPC_SERVICE_PORTMAP;
};
refine casetype RPC_Params += {
RPC_SERVICE_PORTMAP -> portmap: PortmapParams(call);
};
refine casetype RPC_Results += {
RPC_SERVICE_PORTMAP -> portmap: PortmapResults(call);
};
###################
# Portmap types.
enum PortmapProc {
PMAPPROC_NULL = 0,
PMAPPROC_SET = 1,
PMAPPROC_UNSET = 2,
PMAPPROC_GETPORT = 3,
PMAPPROC_DUMP = 4,
PMAPPROC_CALLIT = 5,
};
type PortmapParams(call: RPC_Call) = case call.proc of {
PMAPPROC_NULL -> null: empty;
PMAPPROC_SET, PMAPPROC_UNSET, PMAPPROC_GETPORT
-> mapping: PortmapMapping;
PMAPPROC_DUMP -> dump: empty;
PMAPPROC_CALLIT -> callit: PortmapCallItParams;
};
type PortmapResults(call: RPC_Call) = case call.proc of {
PMAPPROC_NULL -> null: empty;
PMAPPROC_SET -> set: uint32;
PMAPPROC_UNSET -> unset: uint32;
PMAPPROC_GETPORT -> getport: uint32;
PMAPPROC_DUMP -> dump: PortmapDumpResults;
PMAPPROC_CALLIT -> callit: PortmapCallItResults;
};
type PortmapMapping = record {
prog: uint32;
vers: uint32;
proto: uint32;
port: uint32;
};
type PortmapCallItParams = record {
prog: uint32;
vers: uint32;
proc: uint32;
params: RPC_Opaque; # TODO: parse params
};
type PortmapDumpEntry = record {
cont: uint32;
optmapping: case cont of {
0 -> none: empty;
default -> mapping: PortmapMapping;
};
};
# The final element that has cont!=1 will be included in the array.
type PortmapDumpResults = PortmapDumpEntry[] &until($element.cont != 1);
type PortmapCallItResults = record {
port: uint32;
results: RPC_Opaque; # TODO: parse results
};

View file

@ -1,196 +0,0 @@
# $Id:$
########################################
# Protocol Syntax.
%include rpc-protocol.pac
########################################
# Connections and flows.
# External headers (placed outside the "binpac" namespace).
%extern{
#include <map>
using namespace std;
%}
# Extensible function for building a Bro Val for the RPC call.
# See portmap-connection.pac.
function RPC_BuildCallVal(call: RPC_Call): BroVal =
case RPC_Service(call) of {
default -> NULL;
};
# Adding extra let-fields to type RPC_Call.
refine typeattr RPC_Call += &let {
start_time: double = network_time();
# Build the Bro Val.
call_val: BroVal = RPC_BuildCallVal(this);
action_call: bool = $context.flow.ProcessCall(this);
};
# ... and to RPC_Reply.
refine typeattr RPC_Reply += &let {
status = RPC_Status(this);
action_reply: bool = $context.flow.ProcessReply(this);
};
# RPC status in Bro (this is a repeat of enum rpc_status in const.bif :-().
enum EnumRPCStatus {
RPC_STATUS_SUCCESS = 0,
RPC_STATUS_PROG_UNAVAIL = 1,
RPC_STATUS_PROG_MISMATCH= 2,
RPC_STATUS_PROC_UNAVAIL = 3,
RPC_STATUS_GARBAGE_ARGS = 4,
RPC_STATUS_SYSTEM_ERR = 5,
RPC_STATUS_TIMEOUT = 6,
RPC_STATUS_MISMATCH = 7,
RPC_STATUS_AUTH_ERROR = 8,
RPC_STATUS_UNKNOWN_ERROR = 9,
};
function RPC_Status(reply: RPC_Reply): EnumRPCStatus =
case reply.stat of {
MSG_ACCEPTED -> reply.areply.stat;
MSG_DENIED -> case reply.rreply.stat of {
RPC_MISMATCH -> RPC_STATUS_MISMATCH;
AUTH_ERROR -> RPC_STATUS_AUTH_ERROR;
};
};
# An RPC interpreter.
connection RPC_Conn(bro_analyzer: BroAnalyzer) {
upflow = RPC_Flow(true);
downflow = RPC_Flow(false);
# Returns the call corresponding to the xid. Returns
# NULL if not found.
function FindCall(xid: uint32): RPC_Call
%{
RPC_CallTable::const_iterator it = call_table.find(xid);
return it == call_table.end() ? 0 : it->second;
%}
function NewCall(xid: uint32, call: RPC_Call): bool
%{
if ( call_table.find(xid) != call_table.end() )
{
// Compare retransmission with the original.
RPC_Call* orig_call = call_table[xid];
if ( RPC_CompareRexmit(orig_call, call) )
Weird("RPC_rexmit_inconsistency");
return false;
}
else
{
// Add reference count to msg.
call->msg()->Ref();
call_table[xid] = call;
return true;
}
%}
# Returns true if different.
function RPC_CompareRexmit(orig_call: RPC_Call, new_call: RPC_Call): bool
%{
if ( ${orig_call.msg.length} != ${new_call.msg.length} )
return true;
return memcmp(${orig_call.msg_source_data}.begin(),
${new_call.msg_source_data}.begin(),
${orig_call.msg.length}) != 0;
%}
function FinishCall(call: RPC_Call): void
%{
call_table.erase(call->msg()->xid());
Unref(call->msg());
%}
function Timeout(): void
%{
for ( RPC_CallTable::const_iterator it = call_table.begin();
it != call_table.end(); ++it )
{
RPC_Call* call = it->second;
RPC_CallFailed(this, call, RPC_STATUS_TIMEOUT);
Unref(call->msg());
}
call_table.clear();
%}
function Weird(msg: string): void
%{
bro_analyzer()->Weird(msg.c_str());
%}
%member{
typedef ::std::map<uint32, RPC_Call*> RPC_CallTable;
RPC_CallTable call_table;
%}
};
# A virtual RPC flow.
flow RPC_Flow (is_orig: bool) {
# An RPC flow consists of RPC_Message datagrams.
datagram = RPC_Message withcontext (connection, this);
function ProcessCall(call: RPC_Call): bool
%{
if ( ! is_orig() )
Weird("responder_RPC_call");
return true;
%}
function ProcessReply(reply: RPC_Reply): bool
%{
if ( is_orig() )
Weird("originator_RPC_reply");
RPC_Call* call = reply->call();
if ( ! call )
{
Weird("unpaired_RPC_response");
return false;
}
BifEvent::generate_rpc_call(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
call->prog(),
call->vers(),
call->proc(),
reply->status(),
call->start_time(),
call->msg()->length(),
reply->msg()->length());
if ( ! reply->success() )
RPC_CallFailed(connection(), call, reply->status());
connection()->FinishCall(call);
return true;
%}
function Weird(msg: string): void
%{
connection()->Weird(msg);
%}
# TODO: deal with exceptions
# exception(e: Exception)
# {
# Weird(string("bad RPC: ") + e.msg());
# }
};
# Extensible function for handling failed (rejected/timeout) RPC calls.
function RPC_CallFailed(connection: RPC_Conn,
call: RPC_Call,
status: EnumRPCStatus): bool =
case RPC_Service(call) of {
default -> false;
};

View file

@ -1,164 +0,0 @@
# $Id:$
# RPCv2. RFC 1831: http://www.ietf.org/rfc/rfc1831.txt
# This is an analyzer-independent (almost) description of the RPC protocol.
########################################
# Constants.
enum RPC_MsgType {
RPC_CALL = 0,
RPC_REPLY = 1,
};
enum RPC_ReplyStat {
MSG_ACCEPTED = 0,
MSG_DENIED = 1,
};
enum RPC_AcceptStat {
SUCCESS = 0,
PROG_UNAVAIL = 1,
PROG_MISMATCH = 2,
PROC_UNAVAIL = 3,
GARBAGE_ARGS = 4,
SYSTEM_ERR = 5,
};
enum RPC_RejectStat {
RPC_MISMATCH = 0,
AUTH_ERROR = 1,
};
enum RPC_AuthStat {
AUTH_OK = 0,
# failed at remote end
AUTH_BADCRED = 1, # bad credential (seal broken)
AUTH_REJECTEDCRED = 2, # client must begin new session
AUTH_BADVERF = 3, # bad verifier (seal broken)
AUTH_REJECTEDVERF = 4, # verifier expired or replayed
AUTH_TOOWEAK = 5, # rejected for security reasons
# failed locally
AUTH_INVALIDRESP = 6, # bogus response verifier
AUTH_FAILED = 7, # reason unknown
};
########################################
# To be redef'ed in various protocols.
function RPC_Service(prog: uint32, vers: uint32): EnumRPCService =
case prog of {
default -> RPC_SERVICE_UNKNOWN;
};
# Param "call" might be NULL for RPC_Results, thus "RPC_Service(call)"
# rather than simply "call.service".
%code{
inline EnumRPCService RPC_Service(const RPC_Call* call)
{
// If it's an unpaired response, we ignore it for now and
// complain later in the analyzer.
return call ? call->service() : RPC_SERVICE_UNKNOWN;
}
%}
########################################
# Data structures.
# Export the source data for each RPC_Message for RPC retransmission checking.
# With &exportsourcedata, "sourcedata" are defined as members of
# class RPC_Message.
type RPC_Message = record {
xid: uint32;
msg_type: uint32;
msg_body: case msg_type of {
RPC_CALL -> call: RPC_Call(this);
RPC_REPLY -> reply: RPC_Reply(this);
} &requires(length);
} &let {
length = sourcedata.length(); # length of the RPC_Message
} &byteorder = bigendian, &exportsourcedata, &refcount;
type RPC_Call(msg: RPC_Message) = record {
rpcvers: uint32;
prog: uint32;
vers: uint32;
proc: uint32;
cred: RPC_OpaqueAuth;
verf: RPC_OpaqueAuth;
# Compute 'service' before parsing params.
params: RPC_Params(this) &requires(service);
} &let {
service: EnumRPCService = RPC_Service(prog, vers);
# Copy the source data for retransmission checking.
msg_source_data: bytestring = msg.sourcedata;
# Register the RPC call by the xid.
newcall: bool = context.connection.NewCall(msg.xid, this)
&requires(msg_source_data);
};
type RPC_Reply(msg: RPC_Message) = record {
# Find the corresponding RPC call.
# Further parsing of reply depends on call.{prog, vers, proc}
stat: uint32;
reply: case stat of {
MSG_ACCEPTED -> areply: RPC_AcceptedReply(call);
MSG_DENIED -> rreply: RPC_RejectedReply(call);
} &requires(call);
} &let {
call: RPC_Call = context.connection.FindCall(msg.xid);
success: bool = (stat == MSG_ACCEPTED && areply.stat == SUCCESS);
};
type RPC_AcceptedReply(call: RPC_Call) = record {
verf: RPC_OpaqueAuth;
stat: uint32;
data: case stat of {
SUCCESS -> results: RPC_Results(call);
PROG_MISMATCH -> mismatch: RPC_MismatchInfo;
default -> other: empty;
};
};
type RPC_RejectedReply(call: RPC_Call) = record {
stat: uint32;
data: case stat of {
RPC_MISMATCH -> mismatch: RPC_MismatchInfo;
AUTH_ERROR -> auth_stat: uint32; # RPC_AuthStat
};
};
type RPC_MismatchInfo = record {
hi: uint32;
low: uint32;
};
type RPC_Opaque = record {
length: uint32;
data: uint8[length];
pad: padding align 4; # pad to 4-byte boundary
};
type RPC_OpaqueAuth = record {
flavor: uint32;
opaque: RPC_Opaque;
};
# To be extended by higher level protocol analyzers. See portmap-protocol.pac.
type RPC_Params(call: RPC_Call) = case RPC_Service(call) of {
default -> stub: uint8[] &restofdata;
};
type RPC_Results(call: RPC_Call) = case RPC_Service(call) of {
default -> stub: uint8[] &restofdata;
};

View file

@ -1,19 +0,0 @@
# $Id:$
# RPCv2. RFC 1831: http://www.ietf.org/rfc/rfc1831.txt
%include binpac.pac
%include bro.pac
analyzer SunRPC withcontext {
connection: RPC_Conn;
flow: RPC_Flow;
};
enum EnumRPCService {
RPC_SERVICE_UNKNOWN,
RPC_SERVICE_PORTMAP,
};
%include rpc-analyzer.pac
%include portmap-analyzer.pac

View file

@ -4,6 +4,11 @@
#include <errno.h>
#include <stack>
#include <list>
#include <string>
#include <algorithm>
#include "input.h"
#include "util.h"
#include "Scope.h"
@ -19,10 +24,6 @@
#include "Analyzer.h"
#include "AnalyzerTags.h"
#include <stack>
#include <list>
#include <string>
extern YYLTYPE yylloc; // holds start line and column of token
extern int print_loaded_scripts;
extern int generate_documentation;

View file

@ -51,6 +51,110 @@ enum rpc_status %{
RPC_UNKNOWN_ERROR,
%}
module NFS3;
enum proc_t %{ # NFSv3 procedures
PROC_NULL = 0, # done
PROC_GETATTR = 1, # done
PROC_SETATTR = 2, # not implemented
PROC_LOOKUP = 3, # done
PROC_ACCESS = 4, # not implemented
PROC_READLINK = 5, # done
PROC_READ = 6, # done
PROC_WRITE = 7, # done
PROC_CREATE = 8, # partial
PROC_MKDIR = 9, # partial
PROC_SYMLINK = 10, # not implemented
PROC_MKNOD = 11, # not implemented
PROC_REMOVE = 12, # done
PROC_RMDIR = 13, # done
PROC_RENAME = 14, # not implemented
PROC_LINK = 15, # not implemented
PROC_READDIR = 16, # done
PROC_READDIRPLUS = 17, # done
PROC_FSSTAT = 18, # not implemented
PROC_FSINFO = 19, # not implemented
PROC_PATHCONF = 20, # not implemented
PROC_COMMIT = 21, # not implemented
PROC_END_OF_PROCS = 22, # not implemented
%}
enum status_t %{ # NFSv3 return status
NFS3ERR_OK = 0,
NFS3ERR_PERM = 1,
NFS3ERR_NOENT = 2,
NFS3ERR_IO = 5,
NFS3ERR_NXIO = 6,
NFS3ERR_ACCES = 13,
NFS3ERR_EXIST = 17,
NFS3ERR_XDEV = 18,
NFS3ERR_NODEV = 19,
NFS3ERR_NOTDIR = 20,
NFS3ERR_ISDIR = 21,
NFS3ERR_INVAL = 22,
NFS3ERR_FBIG = 27,
NFS3ERR_NOSPC = 28,
NFS3ERR_ROFS = 30,
NFS3ERR_MLINK = 31,
NFS3ERR_NAMETOOLONG = 63,
NFS3ERR_NOTEMPTY = 66,
NFS3ERR_DQUOT = 69,
NFS3ERR_STALE = 70,
NFS3ERR_REMOTE = 71,
NFS3ERR_BADHANDLE = 10001,
NFS3ERR_NOT_SYNC = 10002,
NFS3ERR_BAD_COOKIE = 10003,
NFS3ERR_NOTSUPP = 10004,
NFS3ERR_TOOSMALL = 10005,
NFS3ERR_SERVERFAULT = 10006,
NFS3ERR_BADTYPE = 10007,
NFS3ERR_JUKEBOX = 10008,
NFS3ERR_UNKNOWN = 0xffffffff,
%}
enum file_type_t %{
FTYPE_REG = 1,
FTYPE_DIR = 2,
FTYPE_BLK = 3,
FTYPE_CHR = 4,
FTYPE_LNK = 5,
FTYPE_SOCK = 6,
FTYPE_FIFO = 7,
%}
enum stable_how_t %{
UNSTABLE = 0,
DATA_SYNC = 1,
FILE_SYNC = 2,
%}
enum createmode_t %{
UNCHECKED = 0,
GUARDED = 1,
EXCLUSIVE = 2,
%}
# Decleare record types that we want to access from the even engine. These are
# defined in bro.init.
type info_t: record;
type fattr_t: record;
type diropargs_t: record;
type lookup_reply_t: record;
type readargs_t: record;
type read_reply_t: record;
type readlink_reply_t: record;
type writeargs_t: record;
type wcc_attr_t: record;
type write_reply_t: record;
type newobj_reply_t: record;
type delobj_reply_t: record;
type readdirargs_t: record;
type direntry_t: record;
type direntry_vec_t: vector;
type readdir_reply_t: record;
type fsstat_t: record;
module Log;
enum Writer %{
@ -61,3 +165,5 @@ enum Writer %{
enum ID %{
Unknown,
%}
module GLOBAL;