For some scripting I'm doing, I'm running into the problem that
attributes associated with named types don't get propagated. For
example,
type a: table[count] of count &default = 5;
global c: a;
print c[9];
complains that c[9] isn't set, instead of returning the value 5.
Having dived[*] into this and examined some potential fixes, I've identified
that the problem is that (1) the attribute "&default = 5" in the above
example gets associated with the identifier 'a' rather than with a's type,
and (2) when the parser processes the second line, early in the process 'a'
gets converted to its underlying type, with the attributes lost at that
point since, internally, BroType's do not have attributes.
This is a pain to deal with. If we simply add attributes to BroType's and
for statements like the first line associate the attributes with the type,
then a sequence like:
type a: table[count] of count &default = 5;
type b: a &read_expire = 1 min;
will wind up changing the attributes associated with 'a' even though it
really shouldn't.
I think the right solution for this is to introduce a new BroType called
a NamedType. A NamedType has an identifier, an underlying BroType, and a
set of attributes. When a NamedType is constructed, for its attributes
it combines both the explicit attributes in its declaration (like the
&read_expire for 'b' above) and the implicit (i.e., it inherits/copies the
&default from 'a').
I plan to implement this soon, so please speak up if you have thoughts.
Vern
[*] The dive also exposed some other bugs in attribute processing, which
I'll enter into the tracker shortly.
Hi,
I'm trying to write an analyzer for a protocol which uses Google Protocol Buffers for serialization. The request message MyProto_Req is like:
<4 bytes indicating the length of the rest of the message>
<Protobuf varint indicating the length of the REQUEST_HEADER>
<REQUEST_HEADER data>
<Protobuf varint indicating the length of the REQUEST_PARAMETER>
<REQUEST_PARAMETER data>
<optional data>
( You can find the Protobuf varint encoding here: https://developers.google.com/protocol-buffers/docs/encoding#varints )
Obviously the length of <optional data> must be calculated using previous length fields.
Below is my code:
type PBVarint = record {
val_bytes : uint8[] &until($element < 0x80);
} &let {
val : uint64 = varint_to_int64(val_bytes);
my_len : uint8 = varint_len(val_bytes); # the length of this varint
};
function varint_to_int64(val_bytes: uint8[]) : uint64
%{
uint64 v = 0;
for ( unsigned int i = 0; i < val_bytes->size(); ++i )
{
uint64 byte = ((*val_bytes)[i] & 0x7f);
v |= byte << (8 * i);
}
return v;
%}
function varint_len(val_bytes: uint8[]) : uint8
%{
return val_bytes->size();
%}
type MyProto_Req = record {
length : uint32;
len_reqHeader : PBVarint;
reqHeader : bytestring &length = len_reqHeader.val;
len_reqPara : PBVarint;
reqPara : bytestring &length = len_reqPara.val;
optionalData : bytestring &length = (length - len_reqHeader.val - len_reqHeader.my_len - len_reqPara.val - len_reqPara.my_len);
};
It works. But I wonder if there is a better way to calcuate the length of optionalData (to kill the function varint_len()). I've tried:
optionalData : bytestring &length = (length - len_reqHeader.val - lenHeader.val_bytes->size() - len_reqPara.val - len_reqPara.val_bytes->size())
but failed.
Any hints?
Trying to broaden the scope of:
https://github.com/bro/bro/issues/208
I recently noticed there's a range of behaviors in how various
scripting mistakes are treated. They may (1) abort, as in case of bad
subnet mask or incompatible vector element assignment (2) skip over
evaluating (sub)expression(s), but otherwise continue current function
body, as in case of non-existing table index access or (3) exit the
current function body, as in the classic case of uninitialized record
field access.
1st question: should these be made more consistent? I'd say yes.
2nd question: what is the expected way for these to be handled? I'd
argue that (3) is close to expected behavior, but it's still weird
that it's only the *current function body* (yes, *function*, not
event) that exits early -- hard to reason about what sort of arbitrary
code was depending on that function to be fully evaluated and what
other sort of inconsistent state is caused by exiting early.
I propose, for 2.7, to aim for consistent error handling for scripting
mistakes and that the expected behavior is to unwind all the way to
exiting the current event handler (all its function bodies). That
makes it easier to explain how to write event handlers such that they
won't enter too wild/inconsistent of a state should a scripting error
occur: "always write an event handler such that it makes no
assumptions about order/priority of other events handlers". That's
already close to current suggestions/approaches.
One exception may be within bro_init(), if an error happens at that
time, I'd say it's fine to completely abort -- it's unlikely or hard
to say whether Bro would operate well if it proceeded after an error
that early in initialization.
Thoughts?
- Jon
I bumped into a limitation that was a little frustrating to work around with the config framework.
It is inconvenient that values stored in files read by adding to Config::config_files are not available within the bro_init event. After reviewing how the Config framework works, I understand why this is the case, but it means that if you want to use configuration values on startup, you're not guaranteed to be working with the anticipated value.
I think the introduction of an event that ensures that configuration files have been read by the time that the event fires might be worthwhile. I was wondering if anyone had any thoughts on how to resolve/work-around this issue.
known.dat:
Known::KnownServers 10.230.21.220,10.230.21.221
try-config.bro:
module Known;
redef Config::config_files += {"Known.dat"};
export {
option KnownServers: set[addr] = set();
}
event bro_init() {
print KnownServers;
}
event bro_done() {
print KnownServers;
}
======= output =======
bro -r stream-1.pcap ./try-config.bro
{
}
{
10.230.21.220,
10.230.21.221
}
Thanks,
Stephen