Merge branch 'master' into topic/jsiwek/gtp

This commit is contained in:
Jon Siwek 2012-11-29 16:11:27 -06:00
commit cc8f20c104
286 changed files with 186014 additions and 951 deletions

View file

@ -28,6 +28,7 @@
#include "DCE_RPC.h"
#include "Gnutella.h"
#include "Ident.h"
#include "Modbus.h"
#include "NCP.h"
#include "NetbiosSSN.h"
#include "SMB.h"
@ -130,6 +131,9 @@ const Analyzer::Config Analyzer::analyzer_configs[] = {
{ AnalyzerTag::SYSLOG_BINPAC, "SYSLOG_BINPAC",
Syslog_Analyzer_binpac::InstantiateAnalyzer,
Syslog_Analyzer_binpac::Available, 0, false },
{ AnalyzerTag::Modbus, "MODBUS",
ModbusTCP_Analyzer::InstantiateAnalyzer,
ModbusTCP_Analyzer::Available, 0, false },
{ AnalyzerTag::AYIYA, "AYIYA",
AYIYA_Analyzer::InstantiateAnalyzer,

View file

@ -32,6 +32,7 @@ namespace AnalyzerTag {
// Application-layer analyzers, binpac-generated.
DHCP_BINPAC, DNS_TCP_BINPAC, DNS_UDP_BINPAC,
HTTP_BINPAC, SSL, SYSLOG_BINPAC,
Modbus,
// Decapsulation analyzers.
AYIYA,

View file

@ -71,7 +71,9 @@ void Attr::DescribeReST(ODesc* d) const
else if ( expr->Type()->Tag() == TYPE_FUNC )
{
d->Add(":bro:type:`func`");
d->Add(":bro:type:`");
d->Add(expr->Type()->AsFuncType()->FlavorString());
d->Add("`");
}
else
@ -401,13 +403,13 @@ void Attributes::CheckAttr(Attr* a)
case ATTR_GROUP:
if ( type->Tag() != TYPE_FUNC ||
! type->AsFuncType()->IsEvent() )
type->AsFuncType()->Flavor() != FUNC_FLAVOR_EVENT )
Error("&group only applicable to events");
break;
case ATTR_ERROR_HANDLER:
if ( type->Tag() != TYPE_FUNC ||
! type->AsFuncType()->IsEvent() )
type->AsFuncType()->Flavor() != FUNC_FLAVOR_EVENT )
Error("&error_handler only applicable to events");
break;

View file

@ -271,6 +271,7 @@ void BroDoc::WriteInterface(const char* heading, char underline,
WriteBroDocObjList(state_vars, isPublic, "State Variables", sub, isShort);
WriteBroDocObjList(types, isPublic, "Types", sub, isShort);
WriteBroDocObjList(events, isPublic, "Events", sub, isShort);
WriteBroDocObjList(hooks, isPublic, "Hooks", sub, isShort);
WriteBroDocObjList(functions, isPublic, "Functions", sub, isShort);
WriteBroDocObjList(redefs, isPublic, "Redefinitions", sub, isShort);
}

View file

@ -179,6 +179,30 @@ public:
all.push_back(o);
}
/**
* Schedules documentation of a hook declared by the script.
* @param o A pointer to a BroDocObj which contains the internal
* Bro language representation of the script hook and
* also any associated comments about it.
*/
void AddHook(const BroDocObj* o)
{
hooks.push_back(o);
all.push_back(o);
}
/**
* Schedules documentation of a hook handler declared by the script.
* @param o A pointer to a BroDocObj which contains the internal
* Bro language representation of the script hook handler and
* also any associated comments about it.
*/
void AddHookHandler(const BroDocObj* o)
{
hook_handlers.push_back(o);
all.push_back(o);
}
/**
* Schedules documentation of a function declared by the script.
* @param o A pointer to a BroDocObj which contains the internal
@ -241,6 +265,8 @@ protected:
BroDocObjList notices;
BroDocObjList events;
BroDocObjList event_handlers;
BroDocObjList hooks;
BroDocObjList hook_handlers;
BroDocObjMap functions;
BroDocObjList redefs;

View file

@ -34,11 +34,15 @@ public:
typedef IdxVec::iterator IdxVecIt;
typedef IdxVec::const_iterator IdxVecCIt;
BroString(int arg_final_NUL, byte_vec str, int arg_n);
// Constructors creating internal copies of the data passed in.
BroString(const u_char* str, int arg_n, int add_NUL);
BroString(const char* str);
BroString(const string& str);
BroString(const BroString& bs);
// Constructor that takes owernship of the vector passed in.
BroString(int arg_final_NUL, byte_vec str, int arg_n);
BroString();
~BroString() { Reset(); }

View file

@ -218,6 +218,8 @@ binpac_target(ssl.pac
ssl-defs.pac ssl-protocol.pac ssl-analyzer.pac)
binpac_target(syslog.pac
syslog-protocol.pac syslog-analyzer.pac)
binpac_target(modbus.pac
modbus-protocol.pac modbus-analyzer.pac)
########################################################################
## bro target
@ -349,6 +351,7 @@ set(bro_SRCS
Reporter.cc
Login.cc
MIME.cc
Modbus.cc
NCP.cc
NFA.cc
NFS.cc

View file

@ -46,13 +46,15 @@ extern int select(int, fd_set *, fd_set *, fd_set *, struct timeval *);
class DNS_Mgr_Request {
public:
DNS_Mgr_Request(const char* h, int af) { host = copy_string(h); fam = af; }
DNS_Mgr_Request(const char* h, int af, bool is_txt)
{ host = copy_string(h); fam = af; qtype = is_txt ? 16 : 0; }
DNS_Mgr_Request(const IPAddr& a) { addr = a; host = 0; fam = 0; }
~DNS_Mgr_Request() { delete [] host; }
// Returns nil if this was an address request.
const char* ReqHost() const { return host; }
const IPAddr& ReqAddr() const { return addr; }
const bool ReqIsTxt() const { return qtype == 16; }
int MakeRequest(nb_dns_info* nb_dns);
int RequestPending() const { return request_pending; }
@ -61,7 +63,8 @@ public:
protected:
char* host; // if non-nil, this is a host request
int fam; // address family query type for host requests
int fam; // address family query type for host requests
int qtype; // Query type
IPAddr addr;
int request_pending;
};
@ -75,7 +78,7 @@ int DNS_Mgr_Request::MakeRequest(nb_dns_info* nb_dns)
char err[NB_DNS_ERRSIZE];
if ( host )
return nb_dns_host_request2(nb_dns, host, fam, (void*) this, err) >= 0;
return nb_dns_host_request2(nb_dns, host, fam, qtype, (void*) this, err) >= 0;
else
{
const uint32* bytes;
@ -475,8 +478,8 @@ TableVal* DNS_Mgr::LookupHost(const char* name)
// Not found, or priming.
switch ( mode ) {
case DNS_PRIME:
requests.append(new DNS_Mgr_Request(name, AF_INET));
requests.append(new DNS_Mgr_Request(name, AF_INET6));
requests.append(new DNS_Mgr_Request(name, AF_INET, false));
requests.append(new DNS_Mgr_Request(name, AF_INET6, false));
return empty_addr_set();
case DNS_FORCE:
@ -484,8 +487,8 @@ TableVal* DNS_Mgr::LookupHost(const char* name)
return 0;
case DNS_DEFAULT:
requests.append(new DNS_Mgr_Request(name, AF_INET));
requests.append(new DNS_Mgr_Request(name, AF_INET6));
requests.append(new DNS_Mgr_Request(name, AF_INET, false));
requests.append(new DNS_Mgr_Request(name, AF_INET6, false));
Resolve();
return LookupHost(name);
@ -636,6 +639,7 @@ int DNS_Mgr::Save()
Save(f, host_mappings);
Save(f, addr_mappings);
// Save(f, text_mappings); // We don't save the TXT mappings (yet?).
fclose(f);
@ -704,39 +708,48 @@ void DNS_Mgr::AddResult(DNS_Mgr_Request* dr, struct nb_dns_result* r)
new_dm = new DNS_Mapping(dr->ReqHost(), h, ttl);
prev_dm = 0;
HostMap::iterator it = host_mappings.find(dr->ReqHost());
if ( it == host_mappings.end() )
if ( dr->ReqIsTxt() )
{
host_mappings[dr->ReqHost()].first =
new_dm->Type() == AF_INET ? new_dm : 0;
host_mappings[dr->ReqHost()].second =
new_dm->Type() == AF_INET ? 0 : new_dm;
TextMap::iterator it = text_mappings.find(dr->ReqHost());
if ( it == text_mappings.end() )
text_mappings[dr->ReqHost()] = new_dm;
}
else
{
if ( new_dm->Type() == AF_INET )
HostMap::iterator it = host_mappings.find(dr->ReqHost());
if ( it == host_mappings.end() )
{
prev_dm = it->second.first;
it->second.first = new_dm;
host_mappings[dr->ReqHost()].first =
new_dm->Type() == AF_INET ? new_dm : 0;
host_mappings[dr->ReqHost()].second =
new_dm->Type() == AF_INET ? 0 : new_dm;
}
else
{
prev_dm = it->second.second;
it->second.second = new_dm;
if ( new_dm->Type() == AF_INET )
{
prev_dm = it->second.first;
it->second.first = new_dm;
}
else
{
prev_dm = it->second.second;
it->second.second = new_dm;
}
}
}
if ( new_dm->Failed() && prev_dm && prev_dm->Valid() )
{
// Put previous, valid entry back - CompareMappings
// will generate a corresponding warning.
if ( prev_dm->Type() == AF_INET )
host_mappings[dr->ReqHost()].first = prev_dm;
else
host_mappings[dr->ReqHost()].second = prev_dm;
if ( new_dm->Failed() && prev_dm && prev_dm->Valid() )
{
// Put previous, valid entry back - CompareMappings
// will generate a corresponding warning.
if ( prev_dm->Type() == AF_INET )
host_mappings[dr->ReqHost()].first = prev_dm;
else
host_mappings[dr->ReqHost()].second = prev_dm;
++keep_prev;
++keep_prev;
}
}
}
else
@ -928,7 +941,10 @@ TableVal* DNS_Mgr::LookupNameInCache(string name)
{
HostMap::iterator it = dns_mgr->host_mappings.find(name);
if ( it == dns_mgr->host_mappings.end() )
{
it = dns_mgr->host_mappings.begin();
return 0;
}
DNS_Mapping* d4 = it->second.first;
DNS_Mapping* d6 = it->second.second;
@ -951,6 +967,26 @@ TableVal* DNS_Mgr::LookupNameInCache(string name)
return tv6;
}
const char* DNS_Mgr::LookupTextInCache(string name)
{
TextMap::iterator it = dns_mgr->text_mappings.find(name);
if ( it == dns_mgr->text_mappings.end() )
return 0;
DNS_Mapping* d = it->second;
if ( d->Expired() )
{
dns_mgr->text_mappings.erase(it);
delete d;
return 0;
}
// The escapes in the following strings are to avoid having it
// interpreted as a trigraph sequence.
return d->names ? d->names[0] : "<\?\?\?>";
}
void DNS_Mgr::AsyncLookupAddr(const IPAddr& host, LookupCallback* callback)
{
if ( ! did_init )
@ -1020,6 +1056,35 @@ void DNS_Mgr::AsyncLookupName(string name, LookupCallback* callback)
IssueAsyncRequests();
}
void DNS_Mgr::AsyncLookupNameText(string name, LookupCallback* callback)
{
if ( ! did_init )
Init();
// Do we already know the answer?
TableVal* addrs;
AsyncRequest* req = 0;
// Have we already a request waiting for this host?
AsyncRequestTextMap::iterator i = asyncs_texts.find(name);
if ( i != asyncs_texts.end() )
req = i->second;
else
{
// A new one.
req = new AsyncRequest;
req->name = name;
req->is_txt = true;
asyncs_queued.push_back(req);
asyncs_texts.insert(AsyncRequestTextMap::value_type(name, req));
}
req->callbacks.push_back(callback);
IssueAsyncRequests();
}
void DNS_Mgr::IssueAsyncRequests()
{
while ( asyncs_queued.size() && asyncs_pending < MAX_PENDING_REQUESTS )
@ -1036,8 +1101,9 @@ void DNS_Mgr::IssueAsyncRequests()
dr = new DNS_Mgr_Request(req->host);
else
{
dr = new DNS_Mgr_Request(req->name.c_str(), AF_INET);
dr6 = new DNS_Mgr_Request(req->name.c_str(), AF_INET6);
dr = new DNS_Mgr_Request(req->name.c_str(), AF_INET, req->is_txt);
if ( ! req->is_txt )
dr6 = new DNS_Mgr_Request(req->name.c_str(), AF_INET6, req->is_txt);
}
if ( ! dr->MakeRequest(nb_dns) )
@ -1109,6 +1175,38 @@ void DNS_Mgr::CheckAsyncAddrRequest(const IPAddr& addr, bool timeout)
}
void DNS_Mgr::CheckAsyncTextRequest(const char* host, bool timeout)
{
// Note that this code is a mirror of that for CheckAsyncAddrRequest.
AsyncRequestTextMap::iterator i = asyncs_texts.find(host);
if ( i != asyncs_texts.end() )
{
const char* name = LookupTextInCache(host);
if ( name )
{
++successful;
i->second->Resolved(name);
}
else if ( timeout )
{
AsyncRequestTextMap::iterator it = asyncs_texts.begin();
++failed;
i->second->Timeout();
}
else
return;
asyncs_texts.erase(i);
--asyncs_pending;
// Don't delete the request. That will be done once it
// eventually times out.
}
}
void DNS_Mgr::CheckAsyncHostRequest(const char* host, bool timeout)
{
// Note that this code is a mirror of that for CheckAsyncAddrRequest.
@ -1157,8 +1255,12 @@ void DNS_Mgr::Flush()
for ( AddrMap::iterator it2 = addr_mappings.begin(); it2 != addr_mappings.end(); ++it2 )
delete it2->second;
for ( TextMap::iterator it3 = text_mappings.begin(); it3 != text_mappings.end(); ++it3 )
delete it3->second;
host_mappings.clear();
addr_mappings.clear();
text_mappings.clear();
}
void DNS_Mgr::Process()
@ -1177,6 +1279,8 @@ void DNS_Mgr::DoProcess(bool flush)
if ( req->IsAddrReq() )
CheckAsyncAddrRequest(req->host, true);
else if ( req->is_txt )
CheckAsyncTextRequest(req->name.c_str(), true);
else
CheckAsyncHostRequest(req->name.c_str(), true);
@ -1184,7 +1288,7 @@ void DNS_Mgr::DoProcess(bool flush)
delete req;
}
if ( asyncs_addrs.size() == 0 && asyncs_names.size() == 0 )
if ( asyncs_addrs.size() == 0 && asyncs_names.size() == 0 && asyncs_texts.size() == 0 )
return;
if ( AnswerAvailable(0) <= 0 )
@ -1217,6 +1321,8 @@ void DNS_Mgr::DoProcess(bool flush)
if ( ! dr->ReqHost() )
CheckAsyncAddrRequest(dr->ReqAddr(), true);
else if ( dr->ReqIsTxt() )
CheckAsyncTextRequest(dr->ReqHost(), do_host_timeout);
else
CheckAsyncHostRequest(dr->ReqHost(), do_host_timeout);
@ -1271,5 +1377,6 @@ void DNS_Mgr::GetStats(Stats* stats)
stats->pending = asyncs_pending;
stats->cached_hosts = host_mappings.size();
stats->cached_addresses = addr_mappings.size();
stats->cached_texts = text_mappings.size();
}

View file

@ -63,6 +63,7 @@ public:
const char* LookupAddrInCache(const IPAddr& addr);
TableVal* LookupNameInCache(string name);
const char* LookupTextInCache(string name);
// Support for async lookups.
class LookupCallback {
@ -77,6 +78,7 @@ public:
void AsyncLookupAddr(const IPAddr& host, LookupCallback* callback);
void AsyncLookupName(string name, LookupCallback* callback);
void AsyncLookupNameText(string name, LookupCallback* callback);
struct Stats {
unsigned long requests; // These count only async requests.
@ -85,6 +87,7 @@ public:
unsigned long pending;
unsigned long cached_hosts;
unsigned long cached_addresses;
unsigned long cached_texts;
};
void GetStats(Stats* stats);
@ -106,6 +109,7 @@ protected:
typedef map<string, pair<DNS_Mapping*, DNS_Mapping*> > HostMap;
typedef map<IPAddr, DNS_Mapping*> AddrMap;
typedef map<string, DNS_Mapping*> TextMap;
void LoadCache(FILE* f);
void Save(FILE* f, const AddrMap& m);
void Save(FILE* f, const HostMap& m);
@ -122,6 +126,7 @@ protected:
// requested.
void CheckAsyncAddrRequest(const IPAddr& addr, bool timeout);
void CheckAsyncHostRequest(const char* host, bool timeout);
void CheckAsyncTextRequest(const char* host, bool timeout);
// Process outstanding requests.
void DoProcess(bool flush);
@ -138,6 +143,7 @@ protected:
HostMap host_mappings;
AddrMap addr_mappings;
TextMap text_mappings;
DNS_mgr_request_list requests;
@ -165,8 +171,11 @@ protected:
double time;
IPAddr host;
string name;
bool is_txt;
CallbackList callbacks;
AsyncRequest() : time(0.0), is_txt(false) { }
bool IsAddrReq() const { return name.length() == 0; }
void Resolved(const char* name)
@ -210,6 +219,9 @@ protected:
typedef map<string, AsyncRequest*> AsyncRequestNameMap;
AsyncRequestNameMap asyncs_names;
typedef map<string, AsyncRequest*> AsyncRequestTextMap;
AsyncRequestTextMap asyncs_texts;
typedef list<AsyncRequest*> QueuedList;
QueuedList asyncs_queued;

View file

@ -4374,7 +4374,7 @@ bool InExpr::DoUnserialize(UnserialInfo* info)
return true;
}
CallExpr::CallExpr(Expr* arg_func, ListExpr* arg_args)
CallExpr::CallExpr(Expr* arg_func, ListExpr* arg_args, bool in_hook)
: Expr(EXPR_CALL)
{
func = arg_func;
@ -4402,8 +4402,33 @@ CallExpr::CallExpr(Expr* arg_func, ListExpr* arg_args)
if ( ! yield )
{
Error("event called in expression");
SetError();
switch ( func_type->AsFuncType()->Flavor() ) {
case FUNC_FLAVOR_FUNCTION:
Error("function has no yield type");
SetError();
break;
case FUNC_FLAVOR_EVENT:
Error("event called in expression, use event statement instead");
SetError();
break;
case FUNC_FLAVOR_HOOK:
// It's fine to not have a yield if it's known that the call
// is being done from a hook statement.
if ( ! in_hook )
{
Error("hook called in expression, use hook statement instead");
SetError();
}
break;
default:
Error("invalid function flavor");
SetError();
break;
}
}
else
SetType(yield->Ref());

View file

@ -959,7 +959,7 @@ protected:
class CallExpr : public Expr {
public:
CallExpr(Expr* func, ListExpr* args);
CallExpr(Expr* func, ListExpr* args, bool in_hook = false);
~CallExpr();
Expr* Func() const { return func; }

View file

@ -284,8 +284,8 @@ Val* BroFunc::Call(val_list* args, Frame* parent) const
#endif
if ( ! bodies.size() )
{
// Can only happen for events.
assert(IsEvent());
// Can only happen for events and hooks.
assert(Flavor() == FUNC_FLAVOR_EVENT || Flavor() == FUNC_FLAVOR_HOOK);
loop_over_list(*args, i)
Unref((*args)[i]);
return 0 ;
@ -309,7 +309,7 @@ Val* BroFunc::Call(val_list* args, Frame* parent) const
DescribeDebug(&d, args);
g_trace_state.LogTrace("%s called: %s\n",
IsEvent() ? "event" : "function", d.Description());
FType()->FlavorString().c_str(), d.Description());
}
loop_over_list(*args, i)
@ -348,6 +348,12 @@ Val* BroFunc::Call(val_list* args, Frame* parent) const
parent->SetDelayed();
break;
}
if ( flow == FLOW_BREAK && Flavor() == FUNC_FLAVOR_HOOK )
{
// short-circuit execution of remaining hook handler bodies
break;
}
}
// Warn if the function returns something, but we returned from
@ -380,7 +386,7 @@ void BroFunc::AddBody(Stmt* new_body, id_list* new_inits, int new_frame_size,
new_body = AddInits(new_body, new_inits);
if ( ! IsEvent() )
if ( Flavor() == FUNC_FLAVOR_FUNCTION )
{
// For functions, we replace the old body with the new one.
assert(bodies.size() <= 1);

View file

@ -25,7 +25,7 @@ public:
virtual ~Func();
virtual int IsPure() const = 0;
int IsEvent() const { return FType()->IsEvent(); }
function_flavor Flavor() const { return FType()->Flavor(); }
struct Body {
Stmt* stmts;

View file

@ -107,7 +107,8 @@ void ID::SetVal(Val* v, Opcode op, bool arg_weak_ref)
#endif
if ( type && val &&
type->Tag() == TYPE_FUNC && type->AsFuncType()->IsEvent() )
type->Tag() == TYPE_FUNC &&
type->AsFuncType()->Flavor() == FUNC_FLAVOR_EVENT )
{
EventHandler* handler = event_registry->Lookup(name);
if ( ! handler )
@ -657,11 +658,19 @@ void ID::DescribeReSTShort(ODesc* d) const
break;
case TYPE_FUNC:
d->Add(type->AsFuncType()->IsEvent() ? "event" : type_name(t));
d->Add(type->AsFuncType()->FlavorString());
break;
case TYPE_ENUM:
if ( is_type )
d->Add(type_name(t));
else
d->Add(type->AsEnumType()->Name().c_str());
break;
default:
d->Add(type_name(t));
break;
}
}

View file

@ -248,10 +248,10 @@ IPPrefix::IPPrefix(const in6_addr& in6, uint8_t length)
prefix.Mask(this->length);
}
IPPrefix::IPPrefix(const IPAddr& addr, uint8_t length)
IPPrefix::IPPrefix(const IPAddr& addr, uint8_t length, bool len_is_v6_relative)
: prefix(addr)
{
if ( prefix.GetFamily() == IPv4 )
if ( prefix.GetFamily() == IPv4 && ! len_is_v6_relative )
{
if ( length > 32 )
reporter->InternalError("Bad IPAddr(v4) IPPrefix length : %d",

View file

@ -342,6 +342,21 @@ public:
return memcmp(&addr1.in6, &addr2.in6, sizeof(in6_addr)) < 0;
}
friend bool operator<=(const IPAddr& addr1, const IPAddr& addr2)
{
return addr1 < addr2 || addr1 == addr2;
}
friend bool operator>=(const IPAddr& addr1, const IPAddr& addr2)
{
return ! ( addr1 < addr2 );
}
friend bool operator>(const IPAddr& addr1, const IPAddr& addr2)
{
return ! ( addr1 <= addr2 );
}
/** Converts the address into the type used internally by the
* inter-thread communication.
*/
@ -481,8 +496,15 @@ public:
* @param addr The IP address.
*
* @param length The prefix length in the range from 0 to 128
*
* @param len_is_v6_relative Whether \a length is relative to the full
* 128 bits of an IPv6 address. If false and \a addr is an IPv4
* address, then \a length is expected to range from 0 to 32. If true
* \a length is expected to range from 0 to 128 even if \a addr is IPv4,
* meaning that the mask is to apply to the IPv4-mapped-IPv6 representation.
*/
IPPrefix(const IPAddr& addr, uint8_t length);
IPPrefix(const IPAddr& addr, uint8_t length,
bool len_is_v6_relative = false);
/**
* Copy constructor.
@ -583,6 +605,11 @@ public:
return net1.Prefix() == net2.Prefix() && net1.Length() == net2.Length();
}
friend bool operator!=(const IPPrefix& net1, const IPPrefix& net2)
{
return ! (net1 == net2);
}
/**
* Comparison operator IP prefixes. This defines a well-defined order for
* IP prefix. However, the order does not necessarily corresponding to their
@ -600,6 +627,21 @@ public:
return false;
}
friend bool operator<=(const IPPrefix& net1, const IPPrefix& net2)
{
return net1 < net2 || net1 == net2;
}
friend bool operator>=(const IPPrefix& net1, const IPPrefix& net2)
{
return ! (net1 < net2 );
}
friend bool operator>(const IPPrefix& net1, const IPPrefix& net2)
{
return ! ( net1 <= net2 );
}
private:
IPAddr prefix; // We store it as an address with the non-prefix bits masked out via Mask().
uint8_t length; // The bit length of the prefix relative to full IPv6 addr.

41
src/Modbus.cc Normal file
View file

@ -0,0 +1,41 @@
#include "Modbus.h"
#include "TCP_Reassembler.h"
ModbusTCP_Analyzer::ModbusTCP_Analyzer(Connection* c)
: TCP_ApplicationAnalyzer(AnalyzerTag::Modbus, c)
{
interp = new binpac::ModbusTCP::ModbusTCP_Conn(this);
}
ModbusTCP_Analyzer::~ModbusTCP_Analyzer()
{
delete interp;
}
void ModbusTCP_Analyzer::Done()
{
TCP_ApplicationAnalyzer::Done();
interp->FlowEOF(true);
interp->FlowEOF(false);
}
void ModbusTCP_Analyzer::DeliverStream(int len, const u_char* data, bool orig)
{
TCP_ApplicationAnalyzer::DeliverStream(len, data, orig);
interp->NewData(orig, data, data + len);
}
void ModbusTCP_Analyzer::Undelivered(int seq, int len, bool orig)
{
TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);
interp->NewGap(orig, len);
}
void ModbusTCP_Analyzer::EndpointEOF(bool is_orig)
{
TCP_ApplicationAnalyzer::EndpointEOF(is_orig);
interp->FlowEOF(is_orig);
}

58
src/Modbus.h Normal file
View file

@ -0,0 +1,58 @@
#ifndef MODBUS_H
#define MODBUS_H
#include "TCP.h"
#include "modbus_pac.h"
class ModbusTCP_Analyzer : public TCP_ApplicationAnalyzer {
public:
ModbusTCP_Analyzer(Connection* conn);
virtual ~ModbusTCP_Analyzer();
virtual void Done();
virtual void DeliverStream(int len, const u_char* data, bool orig);
virtual void Undelivered(int seq, int len, bool orig);
virtual void EndpointEOF(bool is_orig);
static Analyzer* InstantiateAnalyzer(Connection* conn)
{ return new ModbusTCP_Analyzer(conn); }
// Put event names in this function
static bool Available()
{
return modbus_message
| modbus_exception
| modbus_read_coils_request
| modbus_read_coils_response
| modbus_read_discrete_inputs_request
| modbus_read_discrete_inputs_response
| modbus_read_holding_registers_request
| modbus_read_holding_registers_response
| modbus_read_input_registers_request
| modbus_read_input_registers_response
| modbus_write_single_coil_request
| modbus_write_single_coil_response
| modbus_write_single_register_request
| modbus_write_single_register_response
| modbus_write_multiple_coils_request
| modbus_write_multiple_coils_response
| modbus_write_multiple_registers_request
| modbus_write_multiple_registers_response
| modbus_read_file_record_request
| modbus_read_file_record_response
| modbus_write_file_record_request
| modbus_write_file_record_response
| modbus_mask_write_register_request
| modbus_mask_write_register_response
| modbus_read_write_multiple_registers_request
| modbus_read_write_multiple_registers_response
| modbus_read_fifo_queue_request
| modbus_read_fifo_queue_response;
}
protected:
binpac::ModbusTCP::ModbusTCP_Conn* interp;
};
#endif

View file

@ -219,16 +219,35 @@ void PktSrc::Process()
// Get protocol being carried from the ethernet frame.
protocol = (data[12] << 8) + data[13];
// MPLS carried over the ethernet frame.
if ( protocol == 0x8847 )
have_mpls = true;
// VLAN carried over ethernet frame.
else if ( protocol == 0x8100 )
switch ( protocol )
{
data += get_link_header_size(datalink);
data += 4; // Skip the vlan header
pkt_hdr_size = 0;
// MPLS carried over the ethernet frame.
case 0x8847:
have_mpls = true;
break;
// VLAN carried over the ethernet frame.
case 0x8100:
data += get_link_header_size(datalink);
data += 4; // Skip the vlan header
pkt_hdr_size = 0;
break;
// PPPoE carried over the ethernet frame.
case 0x8864:
data += get_link_header_size(datalink);
protocol = (data[6] << 8) + data[7];
data += 8; // Skip the PPPoE session and PPP header
pkt_hdr_size = 0;
if ( protocol != 0x0021 && protocol != 0x0057 )
{
// Neither IPv4 nor IPv6.
sessions->Weird("non_ip_packet_in_pppoe_encapsulation", &hdr, data);
data = 0;
return;
}
break;
}
break;

View file

@ -1,4 +1,5 @@
#include <algorithm>
#include <functional>
#include "config.h"
@ -41,6 +42,23 @@ RuleHdrTest::RuleHdrTest(Prot arg_prot, uint32 arg_offset, uint32 arg_size,
level = 0;
}
RuleHdrTest::RuleHdrTest(Prot arg_prot, Comp arg_comp, vector<IPPrefix> arg_v)
{
prot = arg_prot;
offset = 0;
size = 0;
comp = arg_comp;
vals = new maskedvalue_list;
prefix_vals = arg_v;
sibling = 0;
child = 0;
pattern_rules = 0;
pure_rules = 0;
ruleset = new IntSet;
id = ++idcounter;
level = 0;
}
Val* RuleMatcher::BuildRuleStateValue(const Rule* rule,
const RuleEndpointState* state) const
{
@ -63,6 +81,8 @@ RuleHdrTest::RuleHdrTest(RuleHdrTest& h)
loop_over_list(*h.vals, i)
vals->append(new MaskedValue(*(*h.vals)[i]));
prefix_vals = h.prefix_vals;
for ( int j = 0; j < Rule::TYPES; ++j )
{
loop_over_list(h.psets[j], k)
@ -114,6 +134,10 @@ bool RuleHdrTest::operator==(const RuleHdrTest& h)
(*vals)[i]->mask != (*h.vals)[i]->mask )
return false;
for ( size_t i = 0; i < prefix_vals.size(); ++i )
if ( ! (prefix_vals[i] == h.prefix_vals[i]) )
return false;
return true;
}
@ -129,6 +153,9 @@ void RuleHdrTest::PrintDebug()
fprintf(stderr, " 0x%08x/0x%08x",
(*vals)[i]->val, (*vals)[i]->mask);
for ( size_t i = 0; i < prefix_vals.size(); ++i )
fprintf(stderr, " %s", prefix_vals[i].AsString().c_str());
fprintf(stderr, "\n");
}
@ -410,29 +437,129 @@ static inline uint32 getval(const u_char* data, int size)
}
// A line which can be inserted into the macros below for debugging
// fprintf(stderr, "%.06f %08x & %08x %s %08x\n", network_time, v, (mvals)[i]->mask, #op, (mvals)[i]->val);
// Evaluate a value list (matches if at least one value matches).
#define DO_MATCH_OR( mvals, v, op ) \
{ \
loop_over_list((mvals), i) \
{ \
if ( ((v) & (mvals)[i]->mask) op (mvals)[i]->val ) \
goto match; \
} \
goto no_match; \
template <typename FuncT>
static inline bool match_or(const maskedvalue_list& mvals, uint32 v, FuncT comp)
{
loop_over_list(mvals, i)
{
if ( comp(v & mvals[i]->mask, mvals[i]->val) )
return true;
}
return false;
}
// Evaluate a prefix list (matches if at least one value matches).
template <typename FuncT>
static inline bool match_or(const vector<IPPrefix>& prefixes, const IPAddr& a,
FuncT comp)
{
for ( size_t i = 0; i < prefixes.size(); ++i )
{
IPAddr masked(a);
masked.Mask(prefixes[i].LengthIPv6());
if ( comp(masked, prefixes[i].Prefix()) )
return true;
}
return false;
}
// Evaluate a value list (doesn't match if any value matches).
#define DO_MATCH_NOT_AND( mvals, v, op ) \
{ \
loop_over_list((mvals), i) \
{ \
if ( ((v) & (mvals)[i]->mask) op (mvals)[i]->val ) \
goto no_match; \
} \
goto match; \
template <typename FuncT>
static inline bool match_not_and(const maskedvalue_list& mvals, uint32 v,
FuncT comp)
{
loop_over_list(mvals, i)
{
if ( comp(v & mvals[i]->mask, mvals[i]->val) )
return false;
}
return true;
}
// Evaluate a prefix list (doesn't match if any value matches).
template <typename FuncT>
static inline bool match_not_and(const vector<IPPrefix>& prefixes,
const IPAddr& a, FuncT comp)
{
for ( size_t i = 0; i < prefixes.size(); ++i )
{
IPAddr masked(a);
masked.Mask(prefixes[i].LengthIPv6());
if ( comp(masked, prefixes[i].Prefix()) )
return false;
}
return true;
}
static inline bool compare(const maskedvalue_list& mvals, uint32 v,
RuleHdrTest::Comp comp)
{
switch ( comp ) {
case RuleHdrTest::EQ:
return match_or(mvals, v, std::equal_to<uint32>());
break;
case RuleHdrTest::NE:
return match_not_and(mvals, v, std::equal_to<uint32>());
break;
case RuleHdrTest::LT:
return match_or(mvals, v, std::less<uint32>());
break;
case RuleHdrTest::GT:
return match_or(mvals, v, std::greater<uint32>());
break;
case RuleHdrTest::LE:
return match_or(mvals, v, std::less_equal<uint32>());
break;
case RuleHdrTest::GE:
return match_or(mvals, v, std::greater_equal<uint32>());
break;
default:
reporter->InternalError("unknown comparison type");
break;
}
return false;
}
static inline bool compare(const vector<IPPrefix>& prefixes, const IPAddr& a,
RuleHdrTest::Comp comp)
{
switch ( comp ) {
case RuleHdrTest::EQ:
return match_or(prefixes, a, std::equal_to<IPAddr>());
break;
case RuleHdrTest::NE:
return match_not_and(prefixes, a, std::equal_to<IPAddr>());
break;
case RuleHdrTest::LT:
return match_or(prefixes, a, std::less<IPAddr>());
break;
case RuleHdrTest::GT:
return match_or(prefixes, a, std::greater<IPAddr>());
break;
case RuleHdrTest::LE:
return match_or(prefixes, a, std::less_equal<IPAddr>());
break;
case RuleHdrTest::GE:
return match_or(prefixes, a, std::greater_equal<IPAddr>());
break;
default:
reporter->InternalError("unknown comparison type");
break;
}
return false;
}
RuleEndpointState* RuleMatcher::InitEndpoint(Analyzer* analyzer,
@ -492,66 +619,54 @@ RuleEndpointState* RuleMatcher::InitEndpoint(Analyzer* analyzer,
if ( ip )
{
// Get start of transport layer.
const u_char* transport = ip->Payload();
// Descend the RuleHdrTest tree further.
for ( RuleHdrTest* h = hdr_test->child; h;
h = h->sibling )
{
const u_char* data;
bool match = false;
// Evaluate the header test.
switch ( h->prot ) {
case RuleHdrTest::NEXT:
match = compare(*h->vals, ip->NextProto(), h->comp);
break;
case RuleHdrTest::IP:
data = (const u_char*) ip->IP4_Hdr();
if ( ! ip->IP4_Hdr() )
continue;
match = compare(*h->vals, getval((const u_char*)ip->IP4_Hdr() + h->offset, h->size), h->comp);
break;
case RuleHdrTest::IPv6:
if ( ! ip->IP6_Hdr() )
continue;
match = compare(*h->vals, getval((const u_char*)ip->IP6_Hdr() + h->offset, h->size), h->comp);
break;
case RuleHdrTest::ICMP:
case RuleHdrTest::ICMPv6:
case RuleHdrTest::TCP:
case RuleHdrTest::UDP:
data = transport;
match = compare(*h->vals, getval(ip->Payload() + h->offset, h->size), h->comp);
break;
case RuleHdrTest::IPSrc:
match = compare(h->prefix_vals, ip->IPHeaderSrcAddr(), h->comp);
break;
case RuleHdrTest::IPDst:
match = compare(h->prefix_vals, ip->IPHeaderDstAddr(), h->comp);
break;
default:
data = 0;
reporter->InternalError("unknown protocol");
break;
}
// ### data can be nil here if it's an
// IPv6 packet and we're doing an IP test.
if ( ! data )
continue;
// Sorry for the hidden gotos :-)
switch ( h->comp ) {
case RuleHdrTest::EQ:
DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), ==);
case RuleHdrTest::NE:
DO_MATCH_NOT_AND(*h->vals, getval(data + h->offset, h->size), ==);
case RuleHdrTest::LT:
DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), <);
case RuleHdrTest::GT:
DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), >);
case RuleHdrTest::LE:
DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), <=);
case RuleHdrTest::GE:
DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), >=);
default:
reporter->InternalError("unknown comparision type");
}
no_match:
continue;
match:
tests.append(h);
if ( match )
tests.append(h);
}
}
}
@ -1028,7 +1143,7 @@ void RuleMatcher::DumpStateStats(BroFile* f, RuleHdrTest* hdr_test)
Rule* r = Rule::rule_table[set->ids[k] - 1];
f->Write(fmt("%s ", r->ID()));
}
f->Write("\n");
}
}
@ -1050,8 +1165,11 @@ static Val* get_bro_val(const char* label)
}
// Converts an atomic Val and appends it to the list
static bool val_to_maskedval(Val* v, maskedvalue_list* append_to)
// Converts an atomic Val and appends it to the list. For subnet types,
// if the prefix_vector param isn't null, appending to that is preferred
// over appending to the masked val list.
static bool val_to_maskedval(Val* v, maskedvalue_list* append_to,
vector<IPPrefix>* prefix_vector)
{
MaskedValue* mval = new MaskedValue;
@ -1071,29 +1189,37 @@ static bool val_to_maskedval(Val* v, maskedvalue_list* append_to)
case TYPE_SUBNET:
{
const uint32* n;
uint32 m[4];
v->AsSubNet().Prefix().GetBytes(&n);
v->AsSubNetVal()->Mask().CopyIPv6(m);
for ( unsigned int i = 0; i < 4; ++i )
m[i] = ntohl(m[i]);
bool is_v4_mask = m[0] == 0xffffffff &&
m[1] == m[0] && m[2] == m[0];
if ( v->AsSubNet().Prefix().GetFamily() == IPv4 &&
is_v4_mask )
if ( prefix_vector )
{
mval->val = ntohl(*n);
mval->mask = m[3];
prefix_vector->push_back(v->AsSubNet());
delete mval;
return true;
}
else
{
rules_error("IPv6 subnets not supported");
mval->val = 0;
mval->mask = 0;
const uint32* n;
uint32 m[4];
v->AsSubNet().Prefix().GetBytes(&n);
v->AsSubNetVal()->Mask().CopyIPv6(m);
for ( unsigned int i = 0; i < 4; ++i )
m[i] = ntohl(m[i]);
bool is_v4_mask = m[0] == 0xffffffff &&
m[1] == m[0] && m[2] == m[0];
if ( v->AsSubNet().Prefix().GetFamily() == IPv4 && is_v4_mask )
{
mval->val = ntohl(*n);
mval->mask = m[3];
}
else
{
rules_error("IPv6 subnets not supported");
mval->val = 0;
mval->mask = 0;
}
}
}
break;
@ -1108,7 +1234,8 @@ static bool val_to_maskedval(Val* v, maskedvalue_list* append_to)
return true;
}
void id_to_maskedvallist(const char* id, maskedvalue_list* append_to)
void id_to_maskedvallist(const char* id, maskedvalue_list* append_to,
vector<IPPrefix>* prefix_vector)
{
Val* v = get_bro_val(id);
if ( ! v )
@ -1118,7 +1245,7 @@ void id_to_maskedvallist(const char* id, maskedvalue_list* append_to)
{
val_list* vals = v->AsTableVal()->ConvertToPureList()->Vals();
loop_over_list(*vals, i )
if ( ! val_to_maskedval((*vals)[i], append_to) )
if ( ! val_to_maskedval((*vals)[i], append_to, prefix_vector) )
{
delete_vals(vals);
return;
@ -1128,7 +1255,7 @@ void id_to_maskedvallist(const char* id, maskedvalue_list* append_to)
}
else
val_to_maskedval(v, append_to);
val_to_maskedval(v, append_to, prefix_vector);
}
char* id_to_str(const char* id)

View file

@ -2,7 +2,9 @@
#define sigs_h
#include <limits.h>
#include <vector>
#include "IPAddr.h"
#include "BroString.h"
#include "List.h"
#include "RE.h"
@ -59,17 +61,19 @@ declare(PList, BroString);
typedef PList(BroString) bstr_list;
// Get values from Bro's script-level variables.
extern void id_to_maskedvallist(const char* id, maskedvalue_list* append_to);
extern void id_to_maskedvallist(const char* id, maskedvalue_list* append_to,
vector<IPPrefix>* prefix_vector = 0);
extern char* id_to_str(const char* id);
extern uint32 id_to_uint(const char* id);
class RuleHdrTest {
public:
enum Comp { LE, GE, LT, GT, EQ, NE };
enum Prot { NOPROT, IP, ICMP, TCP, UDP };
enum Prot { NOPROT, IP, IPv6, ICMP, ICMPv6, TCP, UDP, NEXT, IPSrc, IPDst };
RuleHdrTest(Prot arg_prot, uint32 arg_offset, uint32 arg_size,
Comp arg_comp, maskedvalue_list* arg_vals);
RuleHdrTest(Prot arg_prot, Comp arg_comp, vector<IPPrefix> arg_v);
~RuleHdrTest();
void PrintDebug();
@ -86,6 +90,7 @@ private:
Prot prot;
Comp comp;
maskedvalue_list* vals;
vector<IPPrefix> prefix_vals; // for use with IPSrc/IPDst comparisons
uint32 offset;
uint32 size;

View file

@ -165,6 +165,7 @@ SERIAL_STMT(EVENT_BODY_LIST, 16)
SERIAL_STMT(INIT_STMT, 17)
SERIAL_STMT(NULL_STMT, 18)
SERIAL_STMT(WHEN_STMT, 19)
SERIAL_STMT(HOOK_STMT, 20)
#define SERIAL_TYPE(name, val) SERIAL_CONST(name, val, BRO_TYPE)
SERIAL_TYPE(BRO_TYPE, 1)

View file

@ -389,23 +389,35 @@ bool Serializer::UnserializeCall(UnserialInfo* info)
{
if ( info->print )
fprintf(info->print, "%s [%.06f] %s(%s)\n",
functype->IsEvent() ? "Event" : "Function call",
functype->FlavorString().c_str(),
time, name, types ? d.Description() : "<ignored>");
if ( functype->IsEvent() )
switch ( functype->Flavor() ) {
case FUNC_FLAVOR_EVENT:
{
EventHandler* handler = event_registry->Lookup(name);
assert(handler);
if ( ! info->ignore_callbacks )
GotEvent(name, time, handler, args);
break;
}
else
case FUNC_FLAVOR_FUNCTION:
case FUNC_FLAVOR_HOOK:
if ( ! info->ignore_callbacks )
GotFunctionCall(name, time, id->ID_Val()->AsFunc(), args);
break;
default:
reporter->InternalError("invalid function flavor");
break;
}
if ( info->ignore_callbacks )
delete_vals(args);
}
else
delete_vals(args);

View file

@ -125,7 +125,7 @@ protected:
// This will be increased whenever there is an incompatible change
// in the data format.
static const uint32 DATA_FORMAT_VERSION = 22;
static const uint32 DATA_FORMAT_VERSION = 23;
ChunkedIO* io;

View file

@ -23,7 +23,7 @@ const char* stmt_name(BroStmtTag t)
"print", "event", "expr", "if", "when", "switch",
"for", "next", "break", "return", "add", "delete",
"list", "bodylist",
"<init>",
"<init>", "hook",
"null",
};
@ -933,6 +933,52 @@ bool EventStmt::DoUnserialize(UnserialInfo* info)
return event_expr != 0;
}
HookStmt::HookStmt(CallExpr* arg_e) : ExprStmt(STMT_HOOK, arg_e)
{
call_expr = arg_e;
}
Val* HookStmt::Exec(Frame* f, stmt_flow_type& flow) const
{
RegisterAccess();
Val* ret = call_expr->Eval(f);
Unref(ret);
flow = FLOW_NEXT;
return 0;
}
TraversalCode HookStmt::Traverse(TraversalCallback* cb) const
{
TraversalCode tc = cb->PreStmt(this);
HANDLE_TC_STMT_PRE(tc);
// call expr is stored in base class's "e" field.
tc = e->Traverse(cb);
HANDLE_TC_STMT_PRE(tc);
tc = cb->PostStmt(this);
HANDLE_TC_STMT_POST(tc);
}
IMPLEMENT_SERIAL(HookStmt, SER_HOOK_STMT);
bool HookStmt::DoSerialize(SerialInfo* info) const
{
DO_SERIALIZE(SER_HOOK_STMT, ExprStmt);
return call_expr->Serialize(info);
}
bool HookStmt::DoUnserialize(UnserialInfo* info)
{
DO_UNSERIALIZE(ExprStmt);
call_expr = (CallExpr*) Expr::Unserialize(info, EXPR_CALL);
return call_expr != 0;
}
ForStmt::ForStmt(id_list* arg_loop_vars, Expr* loop_expr)
: ExprStmt(STMT_FOR, loop_expr)
{
@ -1944,6 +1990,7 @@ int same_stmt(const Stmt* s1, const Stmt* s2)
case STMT_RETURN:
case STMT_EXPR:
case STMT_EVENT:
case STMT_HOOK:
{
const ExprStmt* e1 = (const ExprStmt*) s1;
const ExprStmt* e2 = (const ExprStmt*) s2;

View file

@ -286,6 +286,24 @@ protected:
EventExpr* event_expr;
};
class HookStmt : public ExprStmt {
public:
HookStmt(CallExpr* e);
Val* Exec(Frame* f, stmt_flow_type& flow) const;
TraversalCode Traverse(TraversalCallback* cb) const;
protected:
friend class Stmt;
HookStmt() { call_expr = 0; }
DECLARE_SERIAL(HookStmt);
CallExpr* call_expr;
};
class ForStmt : public ExprStmt {
public:
ForStmt(id_list* loop_vars, Expr* loop_expr);

View file

@ -15,7 +15,7 @@ typedef enum {
STMT_RETURN,
STMT_ADD, STMT_DELETE,
STMT_LIST, STMT_EVENT_BODY_LIST,
STMT_INIT,
STMT_INIT, STMT_HOOK,
STMT_NULL
#define NUM_STMTS (int(STMT_NULL) + 1)
} BroStmtTag;

View file

@ -651,12 +651,12 @@ bool SetType::DoUnserialize(UnserialInfo* info)
return true;
}
FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, int arg_is_event)
FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, function_flavor arg_flavor)
: BroType(TYPE_FUNC)
{
args = arg_args;
yield = arg_yield;
is_event = arg_is_event;
flavor = arg_flavor;
arg_types = new TypeList();
@ -664,6 +664,25 @@ FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, int arg_is_event)
arg_types->Append(args->FieldType(i)->Ref());
}
string FuncType::FlavorString() const
{
switch ( flavor ) {
case FUNC_FLAVOR_FUNCTION:
return "function";
case FUNC_FLAVOR_EVENT:
return "event";
case FUNC_FLAVOR_HOOK:
return "hook";
default:
reporter->InternalError("Invalid function flavor");
return "invalid_func_flavor";
}
}
FuncType::~FuncType()
{
Unref(arg_types);
@ -698,7 +717,7 @@ void FuncType::Describe(ODesc* d) const
{
if ( d->IsReadable() )
{
d->Add(is_event ? "event" : "function");
d->Add(FlavorString());
d->Add("(");
args->DescribeFields(d);
d->Add(")");
@ -712,7 +731,7 @@ void FuncType::Describe(ODesc* d) const
else
{
d->Add(int(Tag()));
d->Add(is_event);
d->Add(flavor);
d->Add(yield != 0);
args->DescribeFields(d);
if ( yield )
@ -723,7 +742,7 @@ void FuncType::Describe(ODesc* d) const
void FuncType::DescribeReST(ODesc* d) const
{
d->Add(":bro:type:`");
d->Add(is_event ? "event" : "function");
d->Add(FlavorString());
d->Add("`");
d->Add(" (");
args->DescribeFieldsReST(d, true);
@ -755,9 +774,30 @@ bool FuncType::DoSerialize(SerialInfo* info) const
SERIALIZE_OPTIONAL(yield);
int ser_flavor = 0;
switch ( flavor ) {
case FUNC_FLAVOR_FUNCTION:
ser_flavor = 0;
break;
case FUNC_FLAVOR_EVENT:
ser_flavor = 1;
break;
case FUNC_FLAVOR_HOOK:
ser_flavor = 2;
break;
default:
reporter->InternalError("Invalid function flavor serialization");
break;
}
return args->Serialize(info) &&
arg_types->Serialize(info) &&
SERIALIZE(is_event);
SERIALIZE(ser_flavor);
}
bool FuncType::DoUnserialize(UnserialInfo* info)
@ -774,7 +814,27 @@ bool FuncType::DoUnserialize(UnserialInfo* info)
if ( ! arg_types )
return false;
return UNSERIALIZE(&is_event);
int ser_flavor = 0;
if ( ! UNSERIALIZE(&ser_flavor) )
return false;
switch ( ser_flavor ) {
case 0:
flavor = FUNC_FLAVOR_FUNCTION;
break;
case 1:
flavor = FUNC_FLAVOR_EVENT;
break;
case 2:
flavor = FUNC_FLAVOR_HOOK;
break;
default:
reporter->InternalError("Invalid function flavor unserialization");
break;
}
return true;
}
TypeDecl::TypeDecl(BroType* t, const char* i, attr_list* arg_attrs, bool in_record)
@ -1202,9 +1262,10 @@ bool FileType::DoUnserialize(UnserialInfo* info)
return yield != 0;
}
EnumType::EnumType()
EnumType::EnumType(const string& arg_name)
: BroType(TYPE_ENUM)
{
name = arg_name;
counter = 0;
}
@ -1327,6 +1388,13 @@ const char* EnumType::Lookup(bro_int_t value)
return 0;
}
void EnumType::DescribeReST(ODesc* d) const
{
d->Add(":bro:type:`");
d->Add(name.c_str());
d->Add("`");
}
void CommentedEnumType::DescribeReST(ODesc* d) const
{
// create temporary, reverse name map so that enums can be documented
@ -1595,7 +1663,7 @@ int same_type(const BroType* t1, const BroType* t2, int is_init)
const FuncType* ft1 = (const FuncType*) t1;
const FuncType* ft2 = (const FuncType*) t2;
if ( ft1->IsEvent() != ft2->IsEvent() )
if ( ft1->Flavor() != ft2->Flavor() )
return 0;
if ( t1->YieldType() || t2->YieldType() )
@ -1882,7 +1950,7 @@ BroType* merge_types(const BroType* t1, const BroType* t2)
BroType* yield = t1->YieldType() ?
merge_types(t1->YieldType(), t2->YieldType()) : 0;
return new FuncType(args->AsRecordType(), yield, ft1->IsEvent());
return new FuncType(args->AsRecordType(), yield, ft1->Flavor());
}
case TYPE_RECORD:

View file

@ -35,7 +35,11 @@ typedef enum {
#define NUM_TYPES (int(TYPE_ERROR) + 1)
} TypeTag;
typedef enum { FUNC_FLAVOR_FUNCTION, FUNC_FLAVOR_EVENT } function_flavor;
typedef enum {
FUNC_FLAVOR_FUNCTION,
FUNC_FLAVOR_EVENT,
FUNC_FLAVOR_HOOK
} function_flavor;
typedef enum {
TYPE_INTERNAL_VOID,
@ -350,18 +354,19 @@ protected:
class FuncType : public BroType {
public:
FuncType(RecordType* args, BroType* yield, int is_event);
FuncType(RecordType* args, BroType* yield, function_flavor f);
~FuncType();
RecordType* Args() const { return args; }
BroType* YieldType();
void SetYieldType(BroType* arg_yield) { yield = arg_yield; }
int IsEvent() const { return is_event; }
function_flavor Flavor() const { return flavor; }
string FlavorString() const;
// Used to convert a function type to an event type.
void ClearYieldType()
{ Unref(yield); yield = 0; is_event = 1; }
// Used to convert a function type to an event or hook type.
void ClearYieldType(function_flavor arg_flav)
{ Unref(yield); yield = 0; flavor = arg_flav; }
int MatchesIndex(ListExpr*& index) const;
int CheckArgs(const type_list* args) const;
@ -374,14 +379,13 @@ public:
void DescribeReST(ODesc* d) const;
protected:
FuncType() { args = 0; arg_types = 0; yield = 0; return_value = 0; }
FuncType() { args = 0; arg_types = 0; yield = 0; flavor = FUNC_FLAVOR_FUNCTION; }
DECLARE_SERIAL(FuncType)
RecordType* args;
TypeList* arg_types;
BroType* yield;
int is_event;
ID* return_value;
function_flavor flavor;
};
class TypeType : public BroType {
@ -497,7 +501,7 @@ protected:
class EnumType : public BroType {
public:
EnumType();
EnumType(const string& arg_name);
~EnumType();
// The value of this name is next internal counter value, starting
@ -513,7 +517,12 @@ public:
bro_int_t Lookup(const string& module_name, const char* name);
const char* Lookup(bro_int_t value); // Returns 0 if not found
string Name() const { return name; }
void DescribeReST(ODesc* d) const;
protected:
EnumType() { counter = 0; }
DECLARE_SERIAL(EnumType)
virtual void AddNameInternal(const string& module_name,
@ -529,11 +538,14 @@ protected:
// as a flag to prevent mixing of auto-increment and explicit
// enumerator specifications.
bro_int_t counter;
// The name of the enum type is stored for documentation purposes.
string name;
};
class CommentedEnumType: public EnumType {
public:
CommentedEnumType() {}
CommentedEnumType(const string& arg_name) : EnumType(arg_name) {}
~CommentedEnumType();
void DescribeReST(ODesc* d) const;

View file

@ -171,7 +171,9 @@ static void make_var(ID* id, BroType* t, init_class c, Expr* init,
id->UpdateValAttrs();
if ( t && t->Tag() == TYPE_FUNC && t->AsFuncType()->IsEvent() )
if ( t && t->Tag() == TYPE_FUNC &&
(t->AsFuncType()->Flavor() == FUNC_FLAVOR_EVENT ||
t->AsFuncType()->Flavor() == FUNC_FLAVOR_HOOK) )
{
// For events, add a function value (without any body) here so that
// we can later access the ID even if no implementations have been
@ -258,7 +260,7 @@ void add_type(ID* id, BroType* t, attr_list* attr, int /* is_event */)
case TYPE_FUNC:
tnew = new FuncType(t->AsFuncType()->Args(),
t->AsFuncType()->YieldType(),
t->AsFuncType()->IsEvent());
t->AsFuncType()->Flavor());
break;
default:
SerializationFormat* form = new BinarySerializationFormat();
@ -292,14 +294,14 @@ void add_type(ID* id, BroType* t, attr_list* attr, int /* is_event */)
void begin_func(ID* id, const char* module_name, function_flavor flavor,
int is_redef, FuncType* t)
{
if ( flavor == FUNC_FLAVOR_EVENT )
if ( flavor == FUNC_FLAVOR_EVENT || flavor == FUNC_FLAVOR_HOOK )
{
const BroType* yt = t->YieldType();
if ( yt && yt->Tag() != TYPE_VOID )
id->Error("event cannot yield a value", t);
id->Error("event/hook cannot yield a value", t);
t->ClearYieldType();
t->ClearYieldType(flavor);
}
if ( id->Type() )
@ -313,21 +315,29 @@ void begin_func(ID* id, const char* module_name, function_flavor flavor,
if ( id->HasVal() )
{
int id_is_event = id->ID_Val()->AsFunc()->IsEvent();
function_flavor id_flavor = id->ID_Val()->AsFunc()->Flavor();
if ( id_is_event != (flavor == FUNC_FLAVOR_EVENT) )
id->Error("inconsistency between event and function", t);
if ( id_is_event )
{
if ( id_flavor != flavor )
id->Error("inconsistent function flavor", t);
switch ( id_flavor ) {
case FUNC_FLAVOR_EVENT:
case FUNC_FLAVOR_HOOK:
if ( is_redef )
// Clear out value so it will be replaced.
id->SetVal(0);
}
else
{
break;
case FUNC_FLAVOR_FUNCTION:
if ( ! id->IsRedefinable() )
id->Error("already defined");
}
break;
default:
reporter->InternalError("invalid function flavor");
break;
}
}
else
id->SetType(t);

View file

@ -11,6 +11,7 @@
#include <cmath>
#include <sys/stat.h>
#include <cstdio>
#include <time.h>
#include "digest.h"
#include "Reporter.h"
@ -2615,15 +2616,15 @@ function to_double%(str: string%): double
%{
const char* s = str->CheckString();
char* end_s;
double d = strtod(s, &end_s);
if ( s[0] == '\0' || end_s[0] != '\0' )
{
{
builtin_error("bad conversion to double", @ARG@[0]);
d = 0;
}
}
return new Val(d, TYPE_DOUBLE);
%}
@ -2700,6 +2701,27 @@ function to_port%(s: string%): port
return new PortVal(port, TRANSPORT_UNKNOWN);
%}
## Converts a string of bytes (in network byte order) to a :bro:type:`double`.
##
## s: A string of bytes containing the binary representation of a double value.
##
## Returns: The double value contained in *s*, or 0 if the conversion
## failed.
##
function bytestring_to_double%(s: string%): double
%{
if ( s->Len() != sizeof(double) )
{
builtin_error("bad conversion to double");
return new Val(0.0, TYPE_DOUBLE);
}
// See #908 for a discussion of portability.
double d;
memcpy(&d, s->Bytes(), sizeof(double));
return new Val(ntohd(d), TYPE_DOUBLE);
%}
## Converts a reverse pointer name to an address. For example,
## ``1.0.168.192.in-addr.arpa`` to ``192.168.0.1``.
##
@ -3285,6 +3307,31 @@ function strftime%(fmt: string, d: time%) : string
return new StringVal(buffer);
%}
## Parse a textual representation of a date/time value into a ``time`` type value.
##
## fmt: The format string used to parse the following *d* argument. See ``man strftime``
## for the syntax.
##
## d: The string representing the time.
##
## Returns: The time value calculated from parsing *d* with *fmt*.
function strptime%(fmt: string, d: string%) : time
%{
const time_t timeval = time_t(NULL);
struct tm t = *localtime(&timeval);
if ( strptime(d->CheckString(), fmt->CheckString(), &t) == NULL )
{
reporter->Warning("strptime conversion failed: fmt:%s d:%s", fmt->CheckString(), d->CheckString());
return new Val(0.0, TYPE_TIME);
}
double ret = mktime(&t);
return new Val(ret, TYPE_TIME);
%}
# ===========================================================================
#
# Network Type Processing
@ -3744,6 +3791,35 @@ function lookup_addr%(host: addr%) : string
return 0;
%}
## Issues an asynchronous TEXT DNS lookup and delays the function result.
## This function can therefore only be called inside a ``when`` condition,
## e.g., ``when ( local h = lookup_hostname_txt("www.bro-ids.org") ) { f(h); }``.
##
## host: The hostname to lookup.
##
## Returns: The DNS TXT record associated with *host*.
##
## .. bro:see:: lookup_hostname
function lookup_hostname_txt%(host: string%) : string
%{
// FIXME: Is should be easy to adapt the function to synchronous
// lookups if we're reading a trace.
Trigger* trigger = frame->GetTrigger();
if ( ! trigger)
{
builtin_error("lookup_hostname_txt() can only be called inside a when-condition");
return new StringVal("<error>");
}
frame->SetDelayed();
trigger->Hold();
dns_mgr->AsyncLookupNameText(host->CheckString(),
new LookupHostCallback(trigger, frame->GetCall(), true));
return 0;
%}
## Issues an asynchronous DNS lookup and delays the function result.
## This function can therefore only be called inside a ``when`` condition,
## e.g., ``when ( local h = lookup_hostname("www.bro-ids.org") ) { f(h); }``.

View file

@ -150,7 +150,7 @@ event new_connection%(c: connection%);
## encapsulation value of *e* after this event is raised. If the desired
## behavior is to track the latest tunnel encapsulation per-connection,
## then a handler of this event should assign *e* to ``c$tunnel`` (which Bro's
## default scripts are doing).
## default scripts are doing).
##
## c: The connection whose tunnel/encapsulation changed.
##
@ -6560,6 +6560,301 @@ event netflow_v5_header%(h: nf_v5_header%);
## .. bro:see:: netflow_v5_record
event netflow_v5_record%(r: nf_v5_record%);
## Generated for any modbus message regardless if the particular function
## is further supported or not.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## is_orig: True if the event is raised for the originator side.
event modbus_message%(c: connection, headers: ModbusHeaders, is_orig: bool%);
## Generated for any modbus exception message.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## code: The exception code.
event modbus_exception%(c: connection, headers: ModbusHeaders, code: count%);
## Generated for a Modbus read coils request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address where of the first coil to be read.
##
## quantity: The number of coils to be read.
event modbus_read_coils_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Generated for a Modbus read coils response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## coils: The coil values returned from the device.
event modbus_read_coils_response%(c: connection, headers: ModbusHeaders, coils: ModbusCoils%);
## Generated for a Modbus read discrete inputs request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first coil to be read.
##
## quantity: The number of coils to be read.
event modbus_read_discrete_inputs_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Generated for a Modbus read discrete inputs response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## coils: The coil values returned from the device.
event modbus_read_discrete_inputs_response%(c: connection, headers: ModbusHeaders, coils: ModbusCoils%);
## Generated for a Modbus read holding registers request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first register to be read.
##
## quantity: The number of registers to be read.
event modbus_read_holding_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Generated for a Modbus read holding registers response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## registers: The register values returned from the device.
event modbus_read_holding_registers_response%(c: connection, headers: ModbusHeaders, registers: ModbusRegisters%);
## Generated for a Modbus read input registers request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first register to be read.
##
## quantity: The number of registers to be read.
event modbus_read_input_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Generated for a Modbus read input registers response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## registers: The register values returned from the device.
event modbus_read_input_registers_response%(c: connection, headers: ModbusHeaders, registers: ModbusRegisters%);
## Generated for a Modbus write single coil request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## address: The memory address of the coil to be written.
##
## value: The value to be written to the coil.
event modbus_write_single_coil_request%(c: connection, headers: ModbusHeaders, address: count, value: bool%);
## Generated for a Modbus write single coil response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## address: The memory address of the coil that was written.
##
## value: The value that was written to the coil.
event modbus_write_single_coil_response%(c: connection, headers: ModbusHeaders, address: count, value: bool%);
## Generated for a Modbus write single register request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## address: The memory address of the register to be written.
##
## value: The value to be written to the register.
event modbus_write_single_register_request%(c: connection, headers: ModbusHeaders, address: count, value: count%);
## Generated for a Modbus write single register response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## address: The memory address of the register that was written.
##
## value: The value that was written to the register.
event modbus_write_single_register_response%(c: connection, headers: ModbusHeaders, address: count, value: count%);
## Generated for a Modbus write multiple coils request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first coil to be written.
##
## value: The values to be written to the coils.
event modbus_write_multiple_coils_request%(c: connection, headers: ModbusHeaders, start_address: count, coils: ModbusCoils%);
## Generated for a Modbus write multiple coils response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first coil that was written.
##
## quantity: The quantity of coils that were written.
event modbus_write_multiple_coils_response%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Generated for a Modbus write multiple registers request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first register to be written.
##
## registers: The values to be written to the registers.
event modbus_write_multiple_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, registers: ModbusRegisters%);
## Generated for a Modbus write multiple registers response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first register that was written.
##
## quantity: The quantity of registers that were written.
event modbus_write_multiple_registers_response%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Generated for a Modbus read file record request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## .. note: This event is incomplete. The information from the data structure is not
## yet passed through to the event.
event modbus_read_file_record_request%(c: connection, headers: ModbusHeaders%);
## Generated for a Modbus read file record response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## .. note: This event is incomplete. The information from the data structure is not
## yet passed through to the event.
event modbus_read_file_record_response%(c: connection, headers: ModbusHeaders%);
## Generated for a Modbus write file record request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## .. note: This event is incomplete. The information from the data structure is not
## yet passed through to the event.
event modbus_write_file_record_request%(c: connection, headers: ModbusHeaders%);
## Generated for a Modbus write file record response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## .. note: This event is incomplete. The information from the data structure is not
## yet passed through to the event.
event modbus_write_file_record_response%(c: connection, headers: ModbusHeaders%);
## Generated for a Modbus mask write register request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## address: The memory address of the register where the masks should be applied.
##
## and_mask: The value of the logical AND mask to apply to the register.
##
## or_mask: The value of the logical OR mask to apply to the register.
event modbus_mask_write_register_request%(c: connection, headers: ModbusHeaders, address: count, and_mask: count, or_mask: count%);
## Generated for a Modbus mask write register request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## address: The memory address of the register where the masks were applied.
##
## and_mask: The value of the logical AND mask applied register.
##
## or_mask: The value of the logical OR mask applied to the register.
event modbus_mask_write_register_response%(c: connection, headers: ModbusHeaders, address: count, and_mask: count, or_mask: count%);
## Generated for a Modbus read/write multiple registers request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## read_start_address: The memory address of the first register to be read.
##
## read_quantity: The number of registers to read.
##
## write_start_address: The memory address of the first register to be written.
##
## write_registers: The values to be written to the registers.
event modbus_read_write_multiple_registers_request%(c: connection, headers: ModbusHeaders, read_start_address: count, read_quantity: count, write_start_address: count, write_registers: ModbusRegisters%);
## Generated for a Modbus read/write multiple registers response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## written_registers: The register values read from the registers specified in the request.
event modbus_read_write_multiple_registers_response%(c: connection, headers: ModbusHeaders, written_registers: ModbusRegisters%);
## Generated for a Modbus read FIFO queue request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The address of the FIFO queue to read.
event modbus_read_fifo_queue_request%(c: connection, headers: ModbusHeaders, start_address: count%);
## Generated for a Modbus read FIFO queue response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## fifos: The register values read from the FIFO queue on the device.
event modbus_read_fifo_queue_response%(c: connection, headers: ModbusHeaders, fifos: ModbusRegisters%);
## Raised for informational messages reported via Bro's reporter framework. Such
## messages may be generated internally by the event engine and also by other
## scripts calling :bro:id:`Reporter::info`.

View file

@ -393,7 +393,7 @@ bool Manager::CreateEventStream(RecordVal* fval)
bool allow_file_func = false;
if ( ! etype->IsEvent() )
if ( etype->Flavor() != FUNC_FLAVOR_EVENT )
{
reporter->Error("stream event is a function, not an event");
return false;
@ -566,7 +566,7 @@ bool Manager::CreateTableStream(RecordVal* fval)
{
FuncType* etype = event->FType()->AsFuncType();
if ( ! etype->IsEvent() )
if ( etype->Flavor() != FUNC_FLAVOR_EVENT )
{
reporter->Error("stream event is a function, not an event");
return false;

View file

@ -327,7 +327,7 @@ bool Manager::CreateStream(EnumVal* id, RecordVal* sval)
// Make sure the event is prototyped as expected.
FuncType* etype = event->FType()->AsFuncType();
if ( ! etype->IsEvent() )
if ( etype->Flavor() != FUNC_FLAVOR_EVENT )
{
reporter->Error("stream event is a function, not an event");
return false;

View file

@ -19,6 +19,7 @@ Ascii::Ascii(WriterFrontend* frontend) : WriterBackend(frontend)
{
fd = 0;
ascii_done = false;
only_single_header_row = false;
output_to_stdout = BifConst::LogAscii::output_to_stdout;
include_meta = BifConst::LogAscii::include_meta;
@ -80,7 +81,7 @@ void Ascii::CloseFile(double t)
if ( ! fd )
return;
if ( include_meta )
if ( include_meta && ! only_single_header_row )
WriteHeaderField("close", Timestamp(0));
safe_close(fd);
@ -108,29 +109,29 @@ bool Ascii::DoInit(const WriterInfo& info, int num_fields, const Field* const *
return false;
}
for ( WriterInfo::config_map::const_iterator i = info.config.begin(); i != info.config.end(); i++ )
{
if ( strcmp(i->first, "only_single_header_row") == 0 )
{
if ( strcmp(i->second, "T") == 0 )
only_single_header_row = true;
else if ( strcmp(i->second, "F") == 0 )
only_single_header_row = false;
else
{
Error("invalid value for 'only_single_header_row', must be boolean (T/F)");
return false;
}
}
}
if ( include_meta )
{
string names;
string types;
string str = string(meta_prefix, meta_prefix_len)
+ "separator " // Always use space as separator here.
+ get_escaped_string(string(separator, separator_len), false)
+ "\n";
if ( ! safe_write(fd, str.c_str(), str.length()) )
goto write_error;
if ( ! (WriteHeaderField("set_separator", get_escaped_string(
string(set_separator, set_separator_len), false)) &&
WriteHeaderField("empty_field", get_escaped_string(
string(empty_field, empty_field_len), false)) &&
WriteHeaderField("unset_field", get_escaped_string(
string(unset_field, unset_field_len), false)) &&
WriteHeaderField("path", get_escaped_string(path, false)) &&
WriteHeaderField("open", Timestamp(0))) )
goto write_error;
for ( int i = 0; i < num_fields; ++i )
{
if ( i > 0 )
@ -143,11 +144,39 @@ bool Ascii::DoInit(const WriterInfo& info, int num_fields, const Field* const *
types += fields[i]->TypeName().c_str();
}
if ( only_single_header_row )
{
// A single CSV-style line is all we need.
string str = names + "\n";
if ( ! safe_write(fd, str.c_str(), str.length()) )
goto write_error;
return true;
}
string str = string(meta_prefix, meta_prefix_len)
+ "separator " // Always use space as separator here.
+ get_escaped_string(string(separator, separator_len), false)
+ "\n";
if ( ! safe_write(fd, str.c_str(), str.length()) )
goto write_error;
if ( ! (WriteHeaderField("set_separator", get_escaped_string(
string(set_separator, set_separator_len), false)) &&
WriteHeaderField("empty_field", get_escaped_string(
string(empty_field, empty_field_len), false)) &&
WriteHeaderField("unset_field", get_escaped_string(
string(unset_field, unset_field_len), false)) &&
WriteHeaderField("path", get_escaped_string(path, false)) &&
WriteHeaderField("open", Timestamp(0))) )
goto write_error;
if ( ! (WriteHeaderField("fields", names)
&& WriteHeaderField("types", types)) )
goto write_error;
}
return true;
write_error:

View file

@ -45,6 +45,7 @@ private:
// Options set from the script-level.
bool output_to_stdout;
bool include_meta;
bool only_single_header_row;
char* separator;
int separator_len;

610
src/modbus-analyzer.pac Normal file
View file

@ -0,0 +1,610 @@
#
# The development of Bro's Modbus analyzer has been made possible thanks to
# the support of the Ministry of Security and Justice of the Kingdom of the
# Netherlands within the projects of Hermes, Castor and Midas.
#
# Useful references: http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf
# http://www.simplymodbus.ca/faq.htm
#
%header{
VectorVal* bytestring_to_coils(bytestring coils, uint quantity);
RecordVal* HeaderToBro(ModbusTCP_TransportHeader *header);
%}
%code{
VectorVal* bytestring_to_coils(bytestring coils, uint quantity)
{
VectorVal* modbus_coils = new VectorVal(BifType::Vector::ModbusCoils);
return modbus_coils;
}
RecordVal* HeaderToBro(ModbusTCP_TransportHeader *header)
{
RecordVal* modbus_header = new RecordVal(BifType::Record::ModbusHeaders);
modbus_header->Assign(0, new Val(header->tid(), TYPE_COUNT));
modbus_header->Assign(1, new Val(header->pid(), TYPE_COUNT));
modbus_header->Assign(2, new Val(header->uid(), TYPE_COUNT));
modbus_header->Assign(3, new Val(header->fc(), TYPE_COUNT));
return modbus_header;
}
%}
refine flow ModbusTCP_Flow += {
function deliver_message(header: ModbusTCP_TransportHeader): bool
%{
if ( ::modbus_message )
{
BifEvent::generate_modbus_message(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
is_orig());
}
return true;
%}
# EXCEPTION
function deliver_Exception(header: ModbusTCP_TransportHeader, message: Exception): bool
%{
if ( ::modbus_exception )
{
BifEvent::generate_modbus_exception(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.code});
}
return true;
%}
# REQUEST FC=1
function deliver_ReadCoilsRequest(header: ModbusTCP_TransportHeader, message: ReadCoilsRequest): bool
%{
if ( ::modbus_read_coils_request )
{
BifEvent::generate_modbus_read_coils_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address},
${message.quantity});
}
return true;
%}
# RESPONSE FC=1
function deliver_ReadCoilsResponse(header: ModbusTCP_TransportHeader, message: ReadCoilsResponse): bool
%{
if ( ::modbus_read_coils_response )
{
BifEvent::generate_modbus_read_coils_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
bytestring_to_coils(${message.bits}, ${message.bits}.length()*8));
}
return true;
%}
# REQUEST FC=2
function deliver_ReadDiscreteInputsRequest(header: ModbusTCP_TransportHeader, message: ReadDiscreteInputsRequest): bool
%{
if ( ::modbus_read_discrete_inputs_request )
{
BifEvent::generate_modbus_read_discrete_inputs_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address}, ${message.quantity});
}
return true;
%}
# RESPONSE FC=2
function deliver_ReadDiscreteInputsResponse(header: ModbusTCP_TransportHeader, message: ReadDiscreteInputsResponse): bool
%{
if ( ::modbus_read_discrete_inputs_response )
{
BifEvent::generate_modbus_read_discrete_inputs_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
bytestring_to_coils(${message.bits}, ${message.bits}.length()*8));
}
return true;
%}
# REQUEST FC=3
function deliver_ReadHoldingRegistersRequest(header: ModbusTCP_TransportHeader, message: ReadHoldingRegistersRequest): bool
%{
if ( ::modbus_read_holding_registers_request )
{
BifEvent::generate_modbus_read_holding_registers_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address}, ${message.quantity});
}
return true;
%}
# RESPONSE FC=3
function deliver_ReadHoldingRegistersResponse(header: ModbusTCP_TransportHeader, message: ReadHoldingRegistersResponse): bool
%{
if ( ${message.byte_count} % 2 != 0 )
{
connection()->bro_analyzer()->ProtocolViolation(
fmt("invalid value for modbus read holding register response byte count %d", ${message.byte_count}));
return false;
}
if ( ::modbus_read_holding_registers_response )
{
VectorVal* t = new VectorVal(BifType::Vector::ModbusRegisters);
for ( unsigned int i=0; i < ${message.registers}->size(); ++i )
{
Val* r = new Val(${message.registers[i]}, TYPE_COUNT);
t->Assign(i, r, 0, OP_ASSIGN);
}
BifEvent::generate_modbus_read_holding_registers_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
t);
}
return true;
%}
# REQUEST FC=4
function deliver_ReadInputRegistersRequest(header: ModbusTCP_TransportHeader, message: ReadInputRegistersRequest): bool
%{
if ( ::modbus_read_input_registers_request )
{
BifEvent::generate_modbus_read_input_registers_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address}, ${message.quantity});
}
return true;
%}
# RESPONSE FC=4
function deliver_ReadInputRegistersResponse(header: ModbusTCP_TransportHeader, message: ReadInputRegistersResponse): bool
%{
if ( ${message.byte_count} % 2 != 0 )
{
connection()->bro_analyzer()->ProtocolViolation(
fmt("invalid value for modbus read input register response byte count %d", ${message.byte_count}));
return false;
}
if ( ::modbus_read_input_registers_response )
{
VectorVal* t = new VectorVal(BifType::Vector::ModbusRegisters);
for ( unsigned int i=0; i < (${message.registers})->size(); ++i )
{
Val* r = new Val(${message.registers[i]}, TYPE_COUNT);
t->Assign(i, r, 0, OP_ASSIGN);
}
BifEvent::generate_modbus_read_input_registers_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
t);
}
return true;
%}
# REQUEST FC=5
function deliver_WriteSingleCoilRequest(header: ModbusTCP_TransportHeader, message: WriteSingleCoilRequest): bool
%{
if ( ::modbus_write_single_coil_request )
{
int val;
if ( ${message.value} == 0x0000 )
val = 0;
else if ( ${message.value} == 0xFF00 )
val = 1;
else
{
connection()->bro_analyzer()->ProtocolViolation(fmt("invalid value for modbus write single coil request %d",
${message.value}));
return false;
}
BifEvent::generate_modbus_write_single_coil_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.address},
val);
}
return true;
%}
# RESPONSE FC=5
function deliver_WriteSingleCoilResponse(header: ModbusTCP_TransportHeader, message: WriteSingleCoilResponse): bool
%{
if ( ::modbus_write_single_coil_response )
{
int val;
if ( ${message.value} == 0x0000 )
val = 0;
else if ( ${message.value} == 0xFF00 )
val = 1;
else
{
connection()->bro_analyzer()->ProtocolViolation(fmt("invalid value for modbus write single coil response %d",
${message.value}));
return false;
}
BifEvent::generate_modbus_write_single_coil_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.address},
val);
}
return true;
%}
# REQUEST FC=6
function deliver_WriteSingleRegisterRequest(header: ModbusTCP_TransportHeader, message: WriteSingleRegisterRequest): bool
%{
if ( ::modbus_write_single_register_request )
{
BifEvent::generate_modbus_write_single_register_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.address}, ${message.value});
}
return true;
%}
# RESPONSE FC=6
function deliver_WriteSingleRegisterResponse(header: ModbusTCP_TransportHeader, message: WriteSingleRegisterResponse): bool
%{
if ( ::modbus_write_single_register_response )
{
BifEvent::generate_modbus_write_single_register_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.address}, ${message.value});
}
return true;
%}
# REQUEST FC=15
function deliver_WriteMultipleCoilsRequest(header: ModbusTCP_TransportHeader, message: WriteMultipleCoilsRequest): bool
%{
if ( ::modbus_write_multiple_coils_request )
{
BifEvent::generate_modbus_write_multiple_coils_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address},
bytestring_to_coils(${message.coils}, ${message.quantity}));
}
return true;
%}
# RESPONSE FC=15
function deliver_WriteMultipleCoilsResponse(header: ModbusTCP_TransportHeader, message: WriteMultipleCoilsResponse): bool
%{
if ( ::modbus_write_multiple_coils_response )
{
BifEvent::generate_modbus_write_multiple_coils_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address}, ${message.quantity});
}
return true;
%}
# REQUEST FC=16
function deliver_WriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader, message: WriteMultipleRegistersRequest): bool
%{
if ( ${message.byte_count} % 2 != 0 )
{
connection()->bro_analyzer()->ProtocolViolation(
fmt("invalid value for modbus write multiple registers request byte count %d", ${message.byte_count}));
return false;
}
if ( ::modbus_write_multiple_registers_request )
{
VectorVal * t = new VectorVal(BifType::Vector::ModbusRegisters);
for ( unsigned int i = 0; i < (${message.registers}->size()); ++i )
{
Val* r = new Val(${message.registers[i]}, TYPE_COUNT);
t->Assign(i, r, 0, OP_ASSIGN);
}
BifEvent::generate_modbus_write_multiple_registers_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address}, t);
}
return true;
%}
# RESPONSE FC=16
function deliver_WriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader, message: WriteMultipleRegistersResponse): bool
%{
if ( ::modbus_write_multiple_registers_response )
{
BifEvent::generate_modbus_write_multiple_registers_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address}, ${message.quantity});
}
return true;
%}
# REQUEST FC=20
function deliver_ReadFileRecordRequest(header: ModbusTCP_TransportHeader, message: ReadFileRecordRequest): bool
%{
if ( ::modbus_read_file_record_request )
{
//TODO: this need to be a vector of some Reference Request record type
//VectorVal *t = new VectorVal(new VectorType(base_type(TYPE_COUNT)));
//for ( unsigned int i = 0; i < (${message.references}->size()); ++i )
// {
// Val* r = new Val((${message.references[i].ref_type}), TYPE_COUNT);
// t->Assign(i, r, 0, OP_ASSIGN);
//
// Val* k = new Val((${message.references[i].file_num}), TYPE_COUNT);
// t->Assign(i, k, 0, OP_ASSIGN);
//
// Val* l = new Val((${message.references[i].record_num}), TYPE_COUNT);
// t->Assign(i, l, 0, OP_ASSIGN);
// }
BifEvent::generate_modbus_read_file_record_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header));
}
return true;
%}
# RESPONSE FC=20
function deliver_ReadFileRecordResponse(header: ModbusTCP_TransportHeader, message: ReadFileRecordResponse): bool
%{
if ( ::modbus_read_file_record_response )
{
//VectorVal *t = new VectorVal(new VectorType(base_type(TYPE_COUNT)));
//for ( unsigned int i = 0; i < ${message.references}->size(); ++i )
// {
// //TODO: work the reference type in here somewhere
// Val* r = new Val(${message.references[i].record_data}), TYPE_COUNT);
// t->Assign(i, r, 0, OP_ASSIGN);
// }
BifEvent::generate_modbus_read_file_record_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header));
}
return true;
%}
# REQUEST FC=21
function deliver_WriteFileRecordRequest(header: ModbusTCP_TransportHeader, message: WriteFileRecordRequest): bool
%{
if ( ::modbus_write_file_record_request )
{
//VectorVal* t = new VectorVal(new VectorType(base_type(TYPE_COUNT)));
//for ( unsigned int i = 0; i < (${message.references}->size()); ++i )
// {
// Val* r = new Val((${message.references[i].ref_type}), TYPE_COUNT);
// t->Assign(i, r, 0, OP_ASSIGN);
//
// Val* k = new Val((${message.references[i].file_num}), TYPE_COUNT);
// t->Assign(i, k, 0, OP_ASSIGN);
//
// Val* n = new Val((${message.references[i].record_num}), TYPE_COUNT);
// t->Assign(i, n, 0, OP_ASSIGN);
//
// for ( unsigned int j = 0; j < (${message.references[i].register_value}->size()); ++j )
// {
// k = new Val((${message.references[i].register_value[j]}), TYPE_COUNT);
// t->Assign(i, k, 0, OP_ASSIGN);
// }
// }
BifEvent::generate_modbus_write_file_record_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header));
}
return true;
%}
# RESPONSE FC=21
function deliver_WriteFileRecordResponse(header: ModbusTCP_TransportHeader, message: WriteFileRecordResponse): bool
%{
if ( ::modbus_write_file_record_response )
{
//VectorVal* t = new VectorVal(new VectorType(base_type(TYPE_COUNT)));
//for ( unsigned int i = 0; i < (${messages.references}->size()); ++i )
// {
// Val* r = new Val((${message.references[i].ref_type}), TYPE_COUNT);
// t->Assign(i, r, 0, OP_ASSIGN);
//
// Val* f = new Val((${message.references[i].file_num}), TYPE_COUNT);
// t->Assign(i, f, 0, OP_ASSIGN);
//
// Val* rn = new Val((${message.references[i].record_num}), TYPE_COUNT);
// t->Assign(i, rn, 0, OP_ASSIGN);
//
// for ( unsigned int j = 0; j<(${message.references[i].register_value}->size()); ++j )
// {
// Val* k = new Val((${message.references[i].register_value[j]}), TYPE_COUNT);
// t->Assign(i, k, 0, OP_ASSIGN);
// }
BifEvent::generate_modbus_write_file_record_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header));
}
return true;
%}
# REQUEST FC=22
function deliver_MaskWriteRegisterRequest(header: ModbusTCP_TransportHeader, message: MaskWriteRegisterRequest): bool
%{
if ( ::modbus_mask_write_register_request )
{
BifEvent::generate_modbus_mask_write_register_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.address},
${message.and_mask}, ${message.or_mask});
}
return true;
%}
# RESPONSE FC=22
function deliver_MaskWriteRegisterResponse(header: ModbusTCP_TransportHeader, message: MaskWriteRegisterResponse): bool
%{
if ( ::modbus_mask_write_register_response )
{
BifEvent::generate_modbus_mask_write_register_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.address},
${message.and_mask}, ${message.or_mask});
}
return true;
%}
# REQUEST FC=23
function deliver_ReadWriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader, message: ReadWriteMultipleRegistersRequest): bool
%{
if ( ${message.write_byte_count} % 2 != 0 )
{
connection()->bro_analyzer()->ProtocolViolation(
fmt("invalid value for modbus read write multiple registers request write byte count %d", ${message.write_byte_count}));
return false;
}
if ( ::modbus_read_write_multiple_registers_request )
{
VectorVal* t = new VectorVal(BifType::Vector::ModbusRegisters);
for ( unsigned int i = 0; i < ${message.write_register_values}->size(); ++i )
{
Val* r = new Val(${message.write_register_values[i]}, TYPE_COUNT);
t->Assign(i, r, 0, OP_ASSIGN);
}
BifEvent::generate_modbus_read_write_multiple_registers_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.read_start_address},
${message.read_quantity},
${message.write_start_address},
t);
}
return true;
%}
# RESPONSE FC=23
function deliver_ReadWriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader, message: ReadWriteMultipleRegistersResponse): bool
%{
if ( ${message.byte_count} % 2 != 0 )
{
connection()->bro_analyzer()->ProtocolViolation(
fmt("invalid value for modbus read write multiple registers response byte count %d", ${message.byte_count}));
return false;
}
if ( ::modbus_read_write_multiple_registers_response )
{
VectorVal* t = new VectorVal(BifType::Vector::ModbusRegisters);
for ( unsigned int i = 0; i < ${message.registers}->size(); ++i )
{
Val* r = new Val(${message.registers[i]}, TYPE_COUNT);
t->Assign(i, r, 0, OP_ASSIGN);
}
BifEvent::generate_modbus_read_write_multiple_registers_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
t);
}
return true;
%}
# REQUEST FC=24
function deliver_ReadFIFOQueueRequest(header: ModbusTCP_TransportHeader, message: ReadFIFOQueueRequest): bool
%{
if ( ::modbus_read_fifo_queue_request )
{
BifEvent::generate_modbus_read_fifo_queue_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
${message.start_address});
}
return true;
%}
# RESPONSE FC=24
function deliver_ReadFIFOQueueResponse(header: ModbusTCP_TransportHeader, message: ReadFIFOQueueResponse): bool
%{
if ( ${message.byte_count} % 2 != 0 )
{
connection()->bro_analyzer()->ProtocolViolation(
fmt("invalid value for modbus read FIFO queue response byte count %d", ${message.byte_count}));
return false;
}
if ( ::modbus_read_fifo_queue_response )
{
VectorVal* t = new VectorVal(new VectorType(base_type(TYPE_COUNT)));
for ( unsigned int i = 0; i < (${message.register_data})->size(); ++i )
{
Val* r = new Val(${message.register_data[i]}, TYPE_COUNT);
t->Assign(i, r, 0, OP_ASSIGN);
}
BifEvent::generate_modbus_read_fifo_queue_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
HeaderToBro(header),
t);
}
return true;
%}
};

394
src/modbus-protocol.pac Normal file
View file

@ -0,0 +1,394 @@
#
# The development of Bro's Modbus analyzer has been made possible thanks to
# the support of the Ministry of Security and Justice of the Kingdom of the
# Netherlands within the projects of Hermes, Castor and Midas.
#
# Useful references: http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf
# http://www.simplymodbus.ca/faq.htm
enum function_codes {
# Standard functions
READ_COILS = 0x01,
READ_DISCRETE_INPUTS = 0x02,
READ_HOLDING_REGISTERS = 0x03,
READ_INPUT_REGISTERS = 0x04,
WRITE_SINGLE_COIL = 0x05,
WRITE_SINGLE_REGISTER = 0x06,
# READ_EXCEPTION_STATUS = 0x07,
# DIAGNOSTICS = 0x08,
# GET_COMM_EVENT_COUNTER = 0x0B,
# GET_COMM_EVENT_LOG = 0x0C,
WRITE_MULTIPLE_COILS = 0x0F,
WRITE_MULTIPLE_REGISTERS = 0x10,
# REPORT_SLAVE_ID = 0x11,
READ_FILE_RECORD = 0x14,
WRITE_FILE_RECORD = 0x15,
MASK_WRITE_REGISTER = 0x16,
READ_WRITE_MULTIPLE_REGISTERS = 0x17,
READ_FIFO_QUEUE = 0x18,
ENCAP_INTERFACE_TRANSPORT = 0x2B,
# Machine/vendor/network specific functions
PROGRAM_484 = 0x09,
POLL_484 = 0x0A,
PROGRAM_584_984 = 0x0D,
POLL_584_984 = 0x0E,
PROGRAM_884_U84 = 0x12,
RESET_COMM_LINK_884_U84 = 0x13,
PROGRAM_CONCEPT = 0x28,
FIRMWARE_REPLACEMENT = 0x7D,
PROGRAM_584_984_2 = 0x7E,
REPORT_LOCAL_ADDRESS = 0x7F,
# Exceptions (not really function codes but they are used similarly)
READ_COILS_EXCEPTION = 0x81,
READ_DISCRETE_INPUTS_EXCEPTION = 0x82,
READ_HOLDING_REGISTERS_EXCEPTION = 0x83,
READ_INPUT_REGISTERS_EXCEPTION = 0x84,
WRITE_SINGLE_COIL_EXCEPTION = 0x85,
WRITE_SINGLE_REGISTER_EXCEPTION = 0x86,
READ_EXCEPTION_STATUS_EXCEPTION = 0x87,
WRITE_MULTIPLE_COILS_EXCEPTION = 0x8F,
WRITE_MULTIPLE_REGISTERS_EXCEPTION = 0x90,
READ_FILE_RECORD_EXCEPTION = 0x94,
WRITE_FILE_RECORD_EXCEPTION = 0x95,
MASK_WRITE_REGISTER_EXCEPTION = 0x96,
READ_WRITE_MULTIPLE_REGISTERS_EXCEPTION = 0x97,
READ_FIFO_QUEUE_EXCEPTION = 0x98,
};
# Main Modbus/TCP PDU
type ModbusTCP_PDU(is_orig: bool) = record {
header: ModbusTCP_TransportHeader;
body: case is_orig of {
true -> request: ModbusTCP_Request(header);
false -> response: ModbusTCP_Response(header);
};
} &length=header.len+6, &byteorder=bigendian;
type ModbusTCP_TransportHeader = record {
tid: uint16; # Transaction identifier
pid: uint16; # Protocol identifier
len: uint16; # Length of everyting after this field
uid: uint8; # Unit identifier (previously 'slave address')
fc: uint8; # MODBUS function code (see function_codes enum)
} &byteorder=bigendian, &let {
deliver: bool = $context.flow.deliver_message(this);
};
type ModbusTCP_Request(header: ModbusTCP_TransportHeader) = case header.fc of {
READ_COILS -> readCoils: ReadCoilsRequest(header);
READ_DISCRETE_INPUTS -> readDiscreteInputs: ReadDiscreteInputsRequest(header);
READ_HOLDING_REGISTERS -> readHoldingRegisters: ReadHoldingRegistersRequest(header);
READ_INPUT_REGISTERS -> readInputRegisters: ReadInputRegistersRequest(header);
WRITE_SINGLE_COIL -> writeSingleCoil: WriteSingleCoilRequest(header);
WRITE_SINGLE_REGISTER -> writeSingleRegister: WriteSingleRegisterRequest(header);
#READ_EXCEPTION_STATUS -> readExceptionStatus: ReadExceptionStatusRequest(header);
#DIAGNOSTICS -> diagnostics: DiagnosticsRequest(header);
#GET_COMM_EVENT_COUNTER -> getCommEventCounter: GetCommEventCounterRequest(header);
#GET_COMM_EVENT_LOG -> getCommEventLog: GetCommEventLogRequest(header);
WRITE_MULTIPLE_COILS -> writeMultipleCoils: WriteMultipleCoilsRequest(header);
WRITE_MULTIPLE_REGISTERS -> writeMultRegisters: WriteMultipleRegistersRequest(header);
#REPORT_SLAVE_ID
READ_FILE_RECORD -> readFileRecord: ReadFileRecordRequest(header);
WRITE_FILE_RECORD -> writeFileRecord: WriteFileRecordRequest(header);
MASK_WRITE_REGISTER -> maskWriteRegister: MaskWriteRegisterRequest(header);
READ_WRITE_MULTIPLE_REGISTERS -> readWriteMultipleRegisters: ReadWriteMultipleRegistersRequest(header);
READ_FIFO_QUEUE -> readFIFOQueue: ReadFIFOQueueRequest(header);
#ENCAP_INTERFACE_TRANSPORT
# All the rest
default -> unknown: bytestring &restofdata;
};
# Responses
#
type ModbusTCP_Response(header: ModbusTCP_TransportHeader) = case header.fc of {
READ_COILS -> readCoils: ReadCoilsResponse(header);
READ_DISCRETE_INPUTS -> readDiscreteInputs: ReadDiscreteInputsResponse(header);
READ_HOLDING_REGISTERS -> readHoldingRegisters: ReadHoldingRegistersResponse(header);
READ_INPUT_REGISTERS -> readInputRegisters: ReadInputRegistersResponse(header);
WRITE_SINGLE_COIL -> writeSingleCoil: WriteSingleCoilResponse(header);
WRITE_SINGLE_REGISTER -> writeSingleRegister: WriteSingleRegisterResponse(header);
#READ_EXCEPTION_STATUS -> readExceptionStatus: ReadExceptionStatusResponse(header);
#DIAGNOSTICS -> diagnostics: DiagnosticsResponse(header);
#GET_COMM_EVENT_COUNTER -> getCommEventCounter: GetCommEventCounterResponse(header);
#GET_COMM_EVENT_LOG -> getCommEventLog: GetCommEventLogResponse(header);
WRITE_MULTIPLE_COILS -> writeMultipleCoils: WriteMultipleCoilsResponse(header);
WRITE_MULTIPLE_REGISTERS -> writeMultRegisters: WriteMultipleRegistersResponse(header);
#REPORT_SLAVE_ID
READ_FILE_RECORD -> readFileRecord: ReadFileRecordResponse(header);
WRITE_FILE_RECORD -> writeFileRecord: WriteFileRecordResponse(header);
MASK_WRITE_REGISTER -> maskWriteRegister: MaskWriteRegisterResponse(header);
READ_WRITE_MULTIPLE_REGISTERS -> readWriteMultipleRegisters: ReadWriteMultipleRegistersResponse(header);
READ_FIFO_QUEUE -> readFIFOQueue: ReadFIFOQueueResponse(header);
# Exceptions
READ_HOLDING_REGISTERS_EXCEPTION -> readHoldingRegistersException: Exception(header);
WRITE_MULTIPLE_REGISTERS_EXCEPTION -> writeMultRegistersException: Exception(header);
READ_COILS_EXCEPTION -> readCoilsException: Exception(header);
READ_DISCRETE_INPUTS_EXCEPTION -> readDiscreteInputsException: Exception(header);
READ_INPUT_REGISTERS_EXCEPTION -> readInputRegistersException: Exception(header);
WRITE_SINGLE_COIL_EXCEPTION -> writeCoilException: Exception(header);
WRITE_SINGLE_REGISTER_EXCEPTION -> writeSingleRegisterException: Exception(header);
READ_EXCEPTION_STATUS_EXCEPTION -> readExceptionStatusException: Exception(header);
WRITE_MULTIPLE_COILS_EXCEPTION -> forceMultipleCoilsException: Exception(header);
READ_FILE_RECORD_EXCEPTION -> readGeneralReferenceException: Exception(header);
WRITE_FILE_RECORD_EXCEPTION -> writeGeneralReferenceException: Exception(header);
MASK_WRITE_REGISTER_EXCEPTION -> maskWriteRegisterException: Exception(header);
READ_WRITE_MULTIPLE_REGISTERS_EXCEPTION -> readWriteRegistersException: Exception(header);
READ_FIFO_QUEUE_EXCEPTION -> readFIFOQueueException: Exception(header);
# All the rest
default -> unknown: bytestring &restofdata;
};
type Exception(header: ModbusTCP_TransportHeader) = record {
code: uint8;
} &let {
deliver: bool = $context.flow.deliver_Exception(header, this);
};
# REQUEST FC=1
type ReadCoilsRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
quantity: uint16 &check(quantity <= 2000);
} &let {
deliver: bool = $context.flow.deliver_ReadCoilsRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=1
type ReadCoilsResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8;
bits: bytestring &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_ReadCoilsResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=2
type ReadDiscreteInputsRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
quantity: uint16 &check(quantity <= 2000);
} &let {
deliver: bool = $context.flow.deliver_ReadDiscreteInputsRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=2
type ReadDiscreteInputsResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8;
bits: bytestring &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_ReadDiscreteInputsResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=3
type ReadHoldingRegistersRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
quantity: uint16 &check(1 <= quantity && quantity <= 125);
} &let {
deliver: bool = $context.flow.deliver_ReadHoldingRegistersRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=3
type ReadHoldingRegistersResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8;
registers: uint16[byte_count/2] &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_ReadHoldingRegistersResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=4
type ReadInputRegistersRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
quantity: uint16 &check(1 <= quantity && quantity <= 125);
} &let {
deliver: bool = $context.flow.deliver_ReadInputRegistersRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=4
type ReadInputRegistersResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8;
registers: uint16[byte_count/2] &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_ReadInputRegistersResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=5
type WriteSingleCoilRequest(header: ModbusTCP_TransportHeader) = record {
address: uint16;
value: uint16 &check(value == 0x0000 || value == 0xFF00);
} &let {
deliver: bool = $context.flow.deliver_WriteSingleCoilRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=5
type WriteSingleCoilResponse(header: ModbusTCP_TransportHeader) = record {
address: uint16;
value: uint16 &check(value == 0x0000 || value == 0xFF00);
} &let {
deliver: bool = $context.flow.deliver_WriteSingleCoilResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=6
type WriteSingleRegisterRequest(header: ModbusTCP_TransportHeader) = record {
address: uint16;
value: uint16;
} &let {
deliver: bool = $context.flow.deliver_WriteSingleRegisterRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=6
type WriteSingleRegisterResponse(header: ModbusTCP_TransportHeader) = record {
address: uint16;
value: uint16;
} &let {
deliver: bool = $context.flow.deliver_WriteSingleRegisterResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=15
type WriteMultipleCoilsRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
quantity: uint16 &check(quantity <= 0x07B0);
byte_count: uint8 &check(byte_count == (quantity + 7)/8);
coils: bytestring &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_WriteMultipleCoilsRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=15
type WriteMultipleCoilsResponse(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
quantity: uint16 &check(quantity <= 0x07B0);
} &let {
deliver: bool = $context.flow.deliver_WriteMultipleCoilsResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=16
type WriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
quantity: uint16;
byte_count: uint8;
# We specify registers buffer with quantity and byte_count so that the analyzer
# will choke if something doesn't match right (correct devices should make it right).
registers: uint16[quantity] &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_WriteMultipleRegistersRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=16
type WriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
quantity: uint16;
} &let {
deliver: bool = $context.flow.deliver_WriteMultipleRegistersResponse(header, this);
} &byteorder=bigendian;
# Support data structure for following message type.
type FileRecordRequest = record {
ref_type: uint8 &check(ref_type == 6);
file_num: uint16 &check(file_num > 0);
record_num: uint16 &check(record_num <= 0x270F);
record_len: uint16;
} &byteorder=bigendian;
# REQUEST FC=20
type ReadFileRecordRequest(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8 &check(byte_count >= 0x07 && byte_count <= 0xF5);
references: FileRecordRequest[] &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_ReadFileRecordRequest(header, this);
} &byteorder=bigendian;
# Support data structure for the following message type.
type FileRecordResponse = record {
file_len: uint8 &check(file_len >= 0x07 && file_len <= 0xF5);
ref_type: uint8 &check(ref_type == 6);
record_data: uint16[file_len/2] &length=file_len;
} &byteorder=bigendian;
# RESPONSE FC=20
type ReadFileRecordResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8 &check(byte_count >= 0x07 && byte_count <= 0xF5);
references: FileRecordResponse[] &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_ReadFileRecordResponse(header, this);
} &byteorder=bigendian;
# Support data structure for the two following message types.
type ReferenceWithData = record {
ref_type: uint8;
file_num: uint16;
record_num: uint16;
word_count: uint16;
register_value: uint16[word_count];
} &byteorder=bigendian;
# REQUEST FC=21
type WriteFileRecordRequest(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8;
references: ReferenceWithData[] &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_WriteFileRecordRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=21
type WriteFileRecordResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8;
references: ReferenceWithData[] &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_WriteFileRecordResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=22
type MaskWriteRegisterRequest(header: ModbusTCP_TransportHeader) = record {
address: uint16;
and_mask: uint16;
or_mask: uint16;
} &let {
deliver: bool = $context.flow.deliver_MaskWriteRegisterRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=22
type MaskWriteRegisterResponse(header: ModbusTCP_TransportHeader) = record {
address: uint16;
and_mask: uint16;
or_mask: uint16;
} &let {
deliver: bool = $context.flow.deliver_MaskWriteRegisterResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=23
type ReadWriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record {
read_start_address: uint16;
read_quantity: uint16 &check(read_quantity <= 125);
write_start_address: uint16;
write_quantity: uint16 &check(write_quantity <= 100);
write_byte_count: uint8;
write_register_values: uint16[write_quantity] &length=write_byte_count;
} &let {
deliver: bool = $context.flow.deliver_ReadWriteMultipleRegistersRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=23
type ReadWriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8;
registers: uint16[byte_count/2] &length=byte_count;
} &let {
deliver: bool = $context.flow.deliver_ReadWriteMultipleRegistersResponse(header, this);
} &byteorder=bigendian;
# REQUEST FC=24
type ReadFIFOQueueRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16;
} &let{
deliver: bool = $context.flow.deliver_ReadFIFOQueueRequest(header, this);
} &byteorder=bigendian;
# RESPONSE FC=24
type ReadFIFOQueueResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint16 &check(byte_count <= 62);
fifo_count: uint16 &check(fifo_count <= 31);
register_data: uint16[fifo_count] &length=fifo_count*2;
} &let {
deliver: bool = $context.flow.deliver_ReadFIFOQueueResponse(header, this);
} &byteorder=bigendian;

28
src/modbus.pac Normal file
View file

@ -0,0 +1,28 @@
#
# The development of Bro's Modbus analyzer has been made possible thanks to
# the support of the Ministry of Security and Justice of the Kingdom of the
# Netherlands within the projects of Hermes, Castor and Midas.
#
# Useful references: http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf
# http://www.simplymodbus.ca/faq.htm
%include binpac.pac
%include bro.pac
analyzer ModbusTCP withcontext {
connection: ModbusTCP_Conn;
flow: ModbusTCP_Flow;
};
connection ModbusTCP_Conn(bro_analyzer: BroAnalyzer) {
upflow = ModbusTCP_Flow(true);
downflow = ModbusTCP_Flow(false);
};
%include modbus-protocol.pac
flow ModbusTCP_Flow(is_orig: bool) {
flowunit = ModbusTCP_PDU(is_orig) withcontext (connection, this);
}
%include modbus-analyzer.pac

View file

@ -307,31 +307,32 @@ nb_dns_host_request(register struct nb_dns_info *nd, register const char *name,
register void *cookie, register char *errstr)
{
return (nb_dns_host_request2(nd, name, AF_INET, cookie, errstr));
return (nb_dns_host_request2(nd, name, AF_INET, 0, cookie, errstr));
}
int
nb_dns_host_request2(register struct nb_dns_info *nd, register const char *name,
register int af, register void *cookie, register char *errstr)
register int af, register int qtype, register void *cookie, register char *errstr)
{
register int qtype;
if (qtype != 16) {
switch (af) {
switch (af) {
case AF_INET:
qtype = T_A;
break;
case AF_INET:
qtype = T_A;
break;
#ifdef AF_INET6
case AF_INET6:
qtype = T_AAAA;
break;
case AF_INET6:
qtype = T_AAAA;
break;
#endif
default:
snprintf(errstr, NB_DNS_ERRSIZE,
"nb_dns_host_request2(): uknown address family %d", af);
return (-1);
default:
snprintf(errstr, NB_DNS_ERRSIZE,
"nb_dns_host_request2(): unknown address family %d", af);
return (-1);
}
}
return (_nb_dns_mkquery(nd, name, af, qtype, cookie, errstr));
}
@ -579,6 +580,7 @@ nb_dns_activity(struct nb_dns_info *nd, struct nb_dns_result *nr, char *errstr)
nr->host_errno = NO_RECOVERY;
return (-1);
}
memcpy(bp, rdata, rdlen);
*hap++ = bp;
bp += rdlen;
@ -587,6 +589,20 @@ nb_dns_activity(struct nb_dns_info *nd, struct nb_dns_result *nr, char *errstr)
/* Keep looking for more A records */
break;
case T_TXT:
if (bp + rdlen >= ep) {
snprintf(errstr, NB_DNS_ERRSIZE,
"nb_dns_activity(): overflow 1 for txt");
nr->host_errno = NO_RECOVERY;
return (-1);
}
memcpy(bp, rdata, rdlen);
he->h_name = bp+1; /* First char is a control character. */
nr->hostent = he;
nr->ttl = rttl;
return (1);
case T_PTR:
n = dn_expand((const u_char *)msg,
(const u_char *)msg + msglen, rdata, bp, ep - bp);

View file

@ -22,7 +22,7 @@ void nb_dns_finish(struct nb_dns_info *);
int nb_dns_fd(struct nb_dns_info *);
int nb_dns_host_request(struct nb_dns_info *, const char *, void *, char *);
int nb_dns_host_request2(struct nb_dns_info *, const char *, int,
int nb_dns_host_request2(struct nb_dns_info *, const char *, int, int,
void *, char *);
int nb_dns_addr_request(struct nb_dns_info *, nb_uint32_t, void *, char *);

View file

@ -2,14 +2,14 @@
// See the file "COPYING" in the main distribution directory for copyright.
%}
%expect 87
%expect 88
%token TOK_ADD TOK_ADD_TO TOK_ADDR TOK_ANY
%token TOK_ATENDIF TOK_ATELSE TOK_ATIF TOK_ATIFDEF TOK_ATIFNDEF
%token TOK_BOOL TOK_BREAK TOK_CASE TOK_CONST
%token TOK_CONSTANT TOK_COPY TOK_COUNT TOK_COUNTER TOK_DEFAULT TOK_DELETE
%token TOK_DOUBLE TOK_ELSE TOK_ENUM TOK_EVENT TOK_EXPORT TOK_FILE TOK_FOR
%token TOK_FUNCTION TOK_GLOBAL TOK_ID TOK_IF TOK_INT
%token TOK_FUNCTION TOK_GLOBAL TOK_HOOK TOK_ID TOK_IF TOK_INT
%token TOK_INTERVAL TOK_LIST TOK_LOCAL TOK_MODULE
%token TOK_NEXT TOK_OF TOK_PATTERN TOK_PATTERN_TEXT
%token TOK_PORT TOK_PRINT TOK_RECORD TOK_REDEF
@ -56,6 +56,7 @@
%type <re> pattern
%type <expr> expr init anonymous_function
%type <event_expr> event
%type <call_expr> hook
%type <stmt> stmt stmt_list func_body for_head
%type <type> type opt_type enum_body
%type <func_type> func_hdr func_params
@ -131,16 +132,18 @@ const char* cur_enum_elem_id = 0;
type_decl_list* fake_type_decl_list = 0;
TypeDecl* last_fake_type_decl = 0;
static ID* cur_decl_type_id = 0;
static void parser_new_enum (void)
{
/* Starting a new enum definition. */
assert(cur_enum_type == NULL);
cur_enum_type = new EnumType();
cur_enum_type = new EnumType(cur_decl_type_id->Name());
// For documentation purposes, a separate type object is created
// in order to avoid overlap that can be caused by redefs.
if ( generate_documentation )
cur_enum_type_doc = new CommentedEnumType();
cur_enum_type_doc = new CommentedEnumType(cur_decl_type_id->Name());
}
static void parser_redef_enum (ID *id)
@ -158,7 +161,7 @@ static void parser_redef_enum (ID *id)
}
if ( generate_documentation )
cur_enum_type_doc = new CommentedEnumType();
cur_enum_type_doc = new CommentedEnumType(id->Name());
}
static void add_enum_comment (std::list<std::string>* comments)
@ -456,17 +459,24 @@ expr:
| '[' expr_list ']'
{
// A little crufty: we peek at the type of
// the first expression in the list. If it's
// a record or a field assignment, then this
// is a record constructor. If not, then this
// is a list used for an initializer.
set_location(@1, @3);
Expr* e0 = $2->Exprs()[0];
if ( e0->Tag() == EXPR_FIELD_ASSIGN ||
e0->Type()->Tag() == TYPE_RECORD )
bool is_record_ctor = true;
// If every expression in the list is a field assignment,
// then treat it as a record constructor, else as a list
// used for an initializer.
for ( int i = 0; i < $2->Exprs().length(); ++i )
{
if ( $2->Exprs()[i]->Tag() != EXPR_FIELD_ASSIGN )
{
is_record_ctor = false;
break;
}
}
if ( is_record_ctor )
$$ = new RecordConstructorExpr($2);
else
$$ = $2;
@ -859,7 +869,13 @@ type:
| TOK_EVENT '(' formal_args ')'
{
set_location(@1, @3);
$$ = new FuncType($3, 0, 1);
$$ = new FuncType($3, 0, FUNC_FLAVOR_EVENT);
}
| TOK_HOOK '(' formal_args ')'
{
set_location(@1, @3);
$$ = new FuncType($3, 0, FUNC_FLAVOR_HOOK);
}
| TOK_FILE TOK_OF type
@ -991,12 +1007,27 @@ decl:
ID* id = $2;
if ( id->Type()->Tag() == TYPE_FUNC )
{
if ( id->Type()->AsFuncType()->IsEvent() )
current_reST_doc->AddEvent(
new BroDocObj(id, reST_doc_comments));
else
switch ( id->Type()->AsFuncType()->Flavor() ) {
case FUNC_FLAVOR_FUNCTION:
current_reST_doc->AddFunction(
new BroDocObj(id, reST_doc_comments));
break;
case FUNC_FLAVOR_EVENT:
current_reST_doc->AddEvent(
new BroDocObj(id, reST_doc_comments));
break;
case FUNC_FLAVOR_HOOK:
current_reST_doc->AddHook(
new BroDocObj(id, reST_doc_comments));
break;
default:
reporter->InternalError("invalid function flavor");
break;
}
}
else
@ -1098,9 +1129,10 @@ decl:
}
}
| TOK_TYPE global_id ':' type opt_attr ';'
| TOK_TYPE global_id ':' { cur_decl_type_id = $2; } type opt_attr ';'
{
add_type($2, $4, $5, 0);
cur_decl_type_id = 0;
add_type($2, $5, $6, 0);
if ( generate_documentation )
{
@ -1175,6 +1207,15 @@ func_hdr:
current_reST_doc->AddEventHandler(
new BroDocObj($2, reST_doc_comments));
}
| TOK_HOOK def_global_id func_params
{
begin_func($2, current_module.c_str(),
FUNC_FLAVOR_HOOK, 0, $3);
$$ = $3;
if ( generate_documentation )
current_reST_doc->AddHookHandler(
new BroDocObj($2, reST_doc_comments));
}
| TOK_REDEF TOK_EVENT event_id func_params
{
begin_func($3, current_module.c_str(),
@ -1209,9 +1250,9 @@ begin_func:
func_params:
'(' formal_args ')' ':' type
{ $$ = new FuncType($2, $5, 0); }
{ $$ = new FuncType($2, $5, FUNC_FLAVOR_FUNCTION); }
| '(' formal_args ')'
{ $$ = new FuncType($2, base_type(TYPE_VOID), 0); }
{ $$ = new FuncType($2, base_type(TYPE_VOID), FUNC_FLAVOR_FUNCTION); }
;
opt_type:
@ -1331,6 +1372,14 @@ stmt:
brofiler.AddStmt($$);
}
| TOK_HOOK hook ';' opt_no_test
{
set_location(@1, @4);
$$ = new HookStmt($2);
if ( ! $4 )
brofiler.AddStmt($$);
}
| TOK_IF '(' expr ')' stmt
{
set_location(@1, @4);
@ -1484,6 +1533,14 @@ event:
}
;
hook:
expr '(' opt_expr_list ')'
{
set_location(@1, @4);
$$ = new CallExpr($1, $3, true);
}
;
case_list:
case_list case
{ $1->append($2); }

View file

@ -1,13 +1,30 @@
%{
#include <stdio.h>
#include <netinet/in.h>
#include <vector>
#include "config.h"
#include "RuleMatcher.h"
#include "Reporter.h"
#include "IPAddr.h"
#include "net_util.h"
extern void begin_PS();
extern void end_PS();
Rule* current_rule = 0;
const char* current_rule_file = 0;
static uint8_t mask_to_len(uint32_t mask)
{
if ( mask == 0xffffffff )
return 32;
uint32_t x = ~mask + 1;
uint8_t len;
for ( len = 0; len < 32 && (! (x & (1 << len))); ++len );
return len;
}
%}
%token TOK_COMP
@ -21,6 +38,7 @@ const char* current_rule_file = 0;
%token TOK_IDENT
%token TOK_INT
%token TOK_IP
%token TOK_IP6
%token TOK_IP_OPTIONS
%token TOK_IP_OPTION_SYM
%token TOK_IP_PROTO
@ -49,7 +67,9 @@ const char* current_rule_file = 0;
%type <hdr_test> hdr_expr
%type <range> range rangeopt
%type <vallist> value_list
%type <prefix_val_list> prefix_value_list
%type <mval> TOK_IP value
%type <prefixval> TOK_IP6 prefix_value
%type <prot> TOK_PROT
%type <ptype> TOK_PATTERN_TYPE
@ -57,6 +77,8 @@ const char* current_rule_file = 0;
Rule* rule;
RuleHdrTest* hdr_test;
maskedvalue_list* vallist;
vector<IPPrefix>* prefix_val_list;
IPPrefix* prefixval;
bool bl;
int val;
@ -91,11 +113,11 @@ rule_attr_list:
;
rule_attr:
TOK_DST_IP TOK_COMP value_list
TOK_DST_IP TOK_COMP prefix_value_list
{
current_rule->AddHdrTest(new RuleHdrTest(
RuleHdrTest::IP, 16, 4,
(RuleHdrTest::Comp) $2, $3));
RuleHdrTest::IPDst,
(RuleHdrTest::Comp) $2, *($3)));
}
| TOK_DST_PORT TOK_COMP value_list
@ -123,10 +145,14 @@ rule_attr:
{
int proto = 0;
switch ( $3 ) {
case RuleHdrTest::ICMP: proto = 1; break;
case RuleHdrTest::ICMP: proto = IPPROTO_ICMP; break;
case RuleHdrTest::ICMPv6: proto = IPPROTO_ICMPV6; break;
// signature matching against outer packet headers of IP-in-IP
// tunneling not supported, so do a no-op there
case RuleHdrTest::IP: proto = 0; break;
case RuleHdrTest::TCP: proto = 6; break;
case RuleHdrTest::UDP: proto = 17; break;
case RuleHdrTest::IPv6: proto = 0; break;
case RuleHdrTest::TCP: proto = IPPROTO_TCP; break;
case RuleHdrTest::UDP: proto = IPPROTO_UDP; break;
default:
rules_error("internal_error: unknown protocol");
}
@ -140,16 +166,20 @@ rule_attr:
val->mask = 0xffffffff;
vallist->append(val);
// offset & size params are dummies, actual next proto value in
// header is retrieved dynamically via IP_Hdr::NextProto()
current_rule->AddHdrTest(new RuleHdrTest(
RuleHdrTest::IP, 9, 1,
RuleHdrTest::NEXT, 0, 0,
(RuleHdrTest::Comp) $2, vallist));
}
}
| TOK_IP_PROTO TOK_COMP value_list
{
// offset & size params are dummies, actual next proto value in
// header is retrieved dynamically via IP_Hdr::NextProto()
current_rule->AddHdrTest(new RuleHdrTest(
RuleHdrTest::IP, 9, 1,
RuleHdrTest::NEXT, 0, 0,
(RuleHdrTest::Comp) $2, $3));
}
@ -193,11 +223,11 @@ rule_attr:
| TOK_SAME_IP
{ current_rule->AddCondition(new RuleConditionSameIP()); }
| TOK_SRC_IP TOK_COMP value_list
| TOK_SRC_IP TOK_COMP prefix_value_list
{
current_rule->AddHdrTest(new RuleHdrTest(
RuleHdrTest::IP, 12, 4,
(RuleHdrTest::Comp) $2, $3));
RuleHdrTest::IPSrc,
(RuleHdrTest::Comp) $2, *($3)));
}
| TOK_SRC_PORT TOK_COMP value_list
@ -254,6 +284,38 @@ value_list:
}
;
prefix_value_list:
prefix_value_list ',' prefix_value
{
$$ = $1;
$$->push_back(*($3));
}
| prefix_value_list ',' TOK_IDENT
{
$$ = $1;
id_to_maskedvallist($3, 0, $1);
}
| prefix_value
{
$$ = new vector<IPPrefix>();
$$->push_back(*($1));
}
| TOK_IDENT
{
$$ = new vector<IPPrefix>();
id_to_maskedvallist($1, 0, $$);
}
;
prefix_value:
TOK_IP
{
$$ = new IPPrefix(IPAddr(IPv4, &($1.val), IPAddr::Host),
mask_to_len($1.mask));
}
| TOK_IP6
;
value:
TOK_INT
{ $$.val = $1; $$.mask = 0xffffffff; }

View file

@ -1,12 +1,13 @@
%{
typedef unsigned int uint32;
#include <string.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "RuleMatcher.h"
#include "IPAddr.h"
#include "util.h"
#include "rule-parse.h"
int rules_line_number = 0;
@ -14,11 +15,14 @@ int rules_line_number = 0;
%x PS
OWS [ \t]*
WS [ \t]+
D [0-9]+
H [0-9a-fA-F]+
HEX {H}
STRING \"([^\n\"]|\\\")*\"
ID ([0-9a-zA-Z_-]+::)*[0-9a-zA-Z_-]+
IP6 ("["({HEX}:){7}{HEX}"]")|("["0x{HEX}({HEX}|:)*"::"({HEX}|:)*"]")|("["({HEX}|:)*"::"({HEX}|:)*"]")|("["({HEX}|:)*"::"({HEX}|:)*({D}"."){3}{D}"]")
RE \/(\\\/)?([^/]|[^\\]\\\/)*\/
META \.[^ \t]+{WS}[^\n]+
PID ([0-9a-zA-Z_-]|"::")+
@ -34,6 +38,18 @@ PID ([0-9a-zA-Z_-]|"::")+
\n ++rules_line_number;
}
{IP6} {
rules_lval.prefixval = new IPPrefix(IPAddr(extract_ip(yytext)), 128, true);
return TOK_IP6;
}
{IP6}{OWS}"/"{OWS}{D} {
int len = 0;
string ip = extract_ip_and_len(yytext, &len);
rules_lval.prefixval = new IPPrefix(IPAddr(ip), len, true);
return TOK_IP6;
}
[!\]\[{}&:,] return rules_text[0];
"<=" { rules_lval.val = RuleHdrTest::LE; return TOK_COMP; }
@ -45,7 +61,9 @@ PID ([0-9a-zA-Z_-]|"::")+
"!=" { rules_lval.val = RuleHdrTest::NE; return TOK_COMP; }
ip { rules_lval.val = RuleHdrTest::IP; return TOK_PROT; }
ip6 { rules_lval.val = RuleHdrTest::IPv6; return TOK_PROT; }
icmp { rules_lval.val = RuleHdrTest::ICMP; return TOK_PROT; }
icmp6 { rules_lval.val = RuleHdrTest::ICMPv6; return TOK_PROT; }
tcp { rules_lval.val = RuleHdrTest::TCP; return TOK_PROT; }
udp { rules_lval.val = RuleHdrTest::UDP; return TOK_PROT; }
@ -123,7 +141,7 @@ http { rules_lval.val = Rule::HTTP_REQUEST; return TOK_PATTERN_TYPE; }
ftp { rules_lval.val = Rule::FTP; return TOK_PATTERN_TYPE; }
finger { rules_lval.val = Rule::FINGER; return TOK_PATTERN_TYPE; }
{D}("."{D}){3}"/"{D} {
{D}("."{D}){3}{OWS}"/"{OWS}{D} {
char* s = strchr(yytext, '/');
*s++ = '\0';

View file

@ -148,6 +148,7 @@ D [0-9]+
HEX [0-9a-fA-F]+
IDCOMPONENT [A-Za-z_][A-Za-z_0-9]*
ID {IDCOMPONENT}(::{IDCOMPONENT})*
IP6 ("["({HEX}:){7}{HEX}"]")|("["0x{HEX}({HEX}|:)*"::"({HEX}|:)*"]")|("["({HEX}|:)*"::"({HEX}|:)*"]")|("["({HEX}|:)*"::"({HEX}|:)*({D}"."){3}{D}"]")
FILE [^ \t\n]+
PREFIX [^ \t\n]+
FLOAT (({D}*"."?{D})|({D}"."?{D}*))([eE][-+]?{D})?
@ -229,21 +230,23 @@ ESCSEQ (\\([^\n]|[0-7]+|x[[:xdigit:]]+))
}
/* IPv6 literal constant patterns */
"["({HEX}:){7}{HEX}"]" {
string s(yytext+1);
RET_CONST(new AddrVal(s.erase(s.size()-1)))
{IP6} {
RET_CONST(new AddrVal(extract_ip(yytext)))
}
"["0x{HEX}({HEX}|:)*"::"({HEX}|:)*"]" {
string s(yytext+3);
RET_CONST(new AddrVal(s.erase(s.size()-1)))
{IP6}{OWS}"/"{OWS}{D} {
int len = 0;
string ip = extract_ip_and_len(yytext, &len);
RET_CONST(new SubNetVal(IPPrefix(IPAddr(ip), len, true)))
}
"["({HEX}|:)*"::"({HEX}|:)*"]" {
string s(yytext+1);
RET_CONST(new AddrVal(s.erase(s.size()-1)))
}
"["({HEX}|:)*"::"({HEX}|:)*({D}"."){3}{D}"]" {
string s(yytext+1);
RET_CONST(new AddrVal(s.erase(s.size()-1)))
/* IPv4 literal constant patterns */
({D}"."){3}{D} RET_CONST(new AddrVal(yytext))
({D}"."){3}{D}{OWS}"/"{OWS}{D} {
int len = 0;
string ip = extract_ip_and_len(yytext, &len);
RET_CONST(new SubNetVal(IPPrefix(IPAddr(ip), len)))
}
[!%*/+\-,:;<=>?()\[\]{}~$|] return yytext[0];
@ -284,6 +287,7 @@ for return TOK_FOR;
function return TOK_FUNCTION;
global return TOK_GLOBAL;
"?$" return TOK_HAS_FIELD;
hook return TOK_HOOK;
if return TOK_IF;
in return TOK_IN;
"!"{OWS}in/[^A-Za-z0-9] return TOK_NOT_IN; /* don't confuse w "! infoo"! */
@ -484,8 +488,6 @@ F RET_CONST(new Val(false, TYPE_BOOL))
{FLOAT}{OWS}msec(s?) RET_CONST(new IntervalVal(atof(yytext),Milliseconds))
{FLOAT}{OWS}usec(s?) RET_CONST(new IntervalVal(atof(yytext),Microseconds))
({D}"."){3}{D} RET_CONST(new AddrVal(yytext))
"0x"{HEX}+ RET_CONST(new Val(static_cast<bro_uint_t>(strtoull(yytext, 0, 16)), TYPE_COUNT))
{H}("."{H})+ RET_CONST(dns_mgr->LookupHost(yytext))

View file

@ -156,6 +156,13 @@ type readdir_reply_t: record;
type fsstat_t: record;
module GLOBAL;
type ModbusHeaders: record;
type ModbusCoils: vector;
type ModbusRegisters: vector;
module Log;
enum Writer %{

View file

@ -43,6 +43,40 @@
#include "Net.h"
#include "Reporter.h"
/**
* Return IP address without enclosing brackets and any leading 0x.
*/
std::string extract_ip(const std::string& i)
{
std::string s(skip_whitespace(i.c_str()));
if ( s.size() > 0 && s[0] == '[' )
s.erase(0, 1);
if ( s.size() > 1 && s.substr(0, 2) == "0x" )
s.erase(0, 2);
size_t pos = 0;
if ( (pos = s.find(']')) != std::string::npos )
s = s.substr(0, pos);
return s;
}
/**
* Given a subnet string, return IP address and subnet length separately.
*/
std::string extract_ip_and_len(const std::string& i, int* len)
{
size_t pos = i.find('/');
if ( pos == std::string::npos )
return i;
if ( len )
*len = atoi(i.substr(pos + 1).c_str());
return extract_ip(i.substr(0, pos));
}
/**
* Takes a string, unescapes all characters that are escaped as hex codes
* (\x##) and turns them into the equivalent ascii-codes. Returns a string

View file

@ -91,6 +91,9 @@ void delete_each(T* t)
delete *it;
}
std::string extract_ip(const std::string& i);
std::string extract_ip_and_len(const std::string& i, int* len);
std::string get_unescaped_string(const std::string& str);
std::string get_escaped_string(const std::string& str, bool escape_all);