diff --git a/src/LogMgr.cc b/src/LogMgr.cc index 0873ca991e..d92493f299 100644 --- a/src/LogMgr.cc +++ b/src/LogMgr.cc @@ -63,6 +63,21 @@ struct LogMgr::Stream { ~Stream(); }; +LogVal::~LogVal() + { + if ( type == TYPE_STRING && present ) + delete val.string_val; + + if ( type == TYPE_TABLE && present ) + { + for ( int i = 0; i < val.set_val.size; i++ ) + delete val.set_val.vals[i]; + + delete [] val.set_val.vals; + } + } + + bool LogVal::Read(SerializationFormat* fmt) { int ty; @@ -139,6 +154,23 @@ bool LogVal::Read(SerializationFormat* fmt) return fmt->Read(val.string_val, "string"); } + case TYPE_TABLE: + { + if ( ! fmt->Read(&val.set_val.size, "set_size") ) + return false; + + val.set_val.vals = new LogVal* [val.set_val.size]; + + for ( int i = 0; i < val.set_val.size; ++i ) + { + val.set_val.vals[i] = new LogVal; + if ( ! val.set_val.vals[i]->Read(fmt) ) + return false; + } + + return true; + } + default: internal_error(::fmt("unsupported type %s in LogVal::Write", type_name(type))); } @@ -205,6 +237,20 @@ bool LogVal::Write(SerializationFormat* fmt) const case TYPE_STRING: return fmt->Write(*val.string_val, "string"); + case TYPE_TABLE: + { + if ( ! fmt->Write(val.set_val.size, "set_size") ) + return false; + + for ( int i = 0; i < val.set_val.size; ++i ) + { + if ( ! val.set_val.vals[i]->Write(fmt) ) + return false; + } + + return true; + } + default: internal_error(::fmt("unsupported type %s in LogVal::REad", type_name(type))); } @@ -402,14 +448,18 @@ bool LogMgr::TraverseRecord(Filter* filter, RecordType* rt, TableVal* include, T // Recurse. if ( ! TraverseRecord(filter, t->AsRecordType(), include, exclude, new_path, new_indices) ) return false; + + continue; } - else + else if ( t->Tag() == TYPE_TABLE && t->AsTableType()->IsSet() ) { + // That's ok, handle it with all the other types below. + } + + else { run_time("unsupported field type for log column"); return false; } - - continue; } // If include fields are specified, only include if explicitly listed. @@ -701,6 +751,70 @@ bool LogMgr::Write(EnumVal* id, RecordVal* columns) return true; } +LogVal* LogMgr::ValToLogVal(Val* val) + { + LogVal* lval = new LogVal(val->Type()->Tag()); + + switch ( lval->type ) { + case TYPE_BOOL: + case TYPE_INT: + case TYPE_ENUM: + lval->val.int_val = val->InternalInt(); + break; + + case TYPE_COUNT: + case TYPE_COUNTER: + lval->val.uint_val = val->InternalUnsigned(); + break; + + case TYPE_PORT: + lval->val.uint_val = val->AsPortVal()->Port(); + break; + + case TYPE_SUBNET: + lval->val.subnet_val = *val->AsSubNet(); + break; + + case TYPE_NET: + case TYPE_ADDR: + { + addr_type t = val->AsAddr(); + copy_addr(&t, &lval->val.addr_val); + break; + } + + case TYPE_DOUBLE: + case TYPE_TIME: + case TYPE_INTERVAL: + lval->val.double_val = val->InternalDouble(); + break; + + case TYPE_STRING: + { + const BroString* s = val->AsString(); + lval->val.string_val = new string((const char*) s->Bytes(), s->Len()); + break; + } + + case TYPE_TABLE: + { + ListVal* set = val->AsTableVal()->ConvertToPureList(); + lval->val.set_val.size = set->Length(); + lval->val.set_val.vals = new LogVal* [lval->val.set_val.size]; + + for ( int i = 0; i < lval->val.set_val.size; i++ ) + lval->val.set_val.vals[i] = ValToLogVal(set->Index(i)); + + break; + } + + default: + internal_error("unsupported type for log_write"); + } + + return lval; + } + LogVal** LogMgr::RecordToFilterVals(Filter* filter, RecordVal* columns) { LogVal** vals = new LogVal*[filter->num_fields]; @@ -727,55 +841,8 @@ LogVal** LogMgr::RecordToFilterVals(Filter* filter, RecordVal* columns) } } - if ( ! val ) - continue; - - vals[i] = new LogVal(type); - - switch ( val->Type()->Tag() ) { - case TYPE_BOOL: - case TYPE_INT: - case TYPE_ENUM: - vals[i]->val.int_val = val->InternalInt(); - break; - - case TYPE_COUNT: - case TYPE_COUNTER: - vals[i]->val.uint_val = val->InternalUnsigned(); - break; - - case TYPE_PORT: - vals[i]->val.uint_val = val->AsPortVal()->Port(); - break; - - case TYPE_SUBNET: - vals[i]->val.subnet_val = *val->AsSubNet(); - break; - - case TYPE_NET: - case TYPE_ADDR: - { - addr_type t = val->AsAddr(); - copy_addr(&t, &vals[i]->val.addr_val); - break; - } - - case TYPE_DOUBLE: - case TYPE_TIME: - case TYPE_INTERVAL: - vals[i]->val.double_val = val->InternalDouble(); - break; - - case TYPE_STRING: - { - const BroString* s = val->AsString(); - vals[i]->val.string_val = new string((const char*) s->Bytes(), s->Len()); - break; - } - - default: - internal_error("unsupported type for log_write"); - } + if ( val ) + vals[i] = ValToLogVal(val); } return vals; diff --git a/src/LogMgr.h b/src/LogMgr.h index 8f8d991c00..88e7ef0131 100644 --- a/src/LogMgr.h +++ b/src/LogMgr.h @@ -36,17 +36,20 @@ struct LogVal { // The following union is a subset of BroValUnion, including only the // atomic types. - union { + struct set_t { bro_int_t size; LogVal** vals; }; + + union _val { bro_int_t int_val; bro_uint_t uint_val; addr_type addr_val; subnet_type subnet_val; double double_val; string* string_val; + set_t set_val; } val; LogVal(TypeTag arg_type = TYPE_ERROR, bool arg_present = true) : type(arg_type), present(arg_present) {} - ~LogVal() { if ( type == TYPE_STRING && present ) delete val.string_val; } + ~LogVal(); bool Read(SerializationFormat* fmt); bool Write(SerializationFormat* fmt) const; @@ -97,6 +100,7 @@ private: struct WriterInfo; bool TraverseRecord(Filter* filter, RecordType* rt, TableVal* include, TableVal* exclude, string path, list indices); + LogVal* ValToLogVal(Val* val); LogVal** RecordToFilterVals(Filter* filter, RecordVal* columns); Stream* FindStream(EnumVal* id); void RemoveDisabledWriters(Stream* stream); diff --git a/src/LogWriterAscii.cc b/src/LogWriterAscii.cc index 3ec22b7fa0..48537173b3 100644 --- a/src/LogWriterAscii.cc +++ b/src/LogWriterAscii.cc @@ -16,6 +16,10 @@ LogWriterAscii::LogWriterAscii() separator = new char[separator_len]; memcpy(separator, BifConst::LogAscii::separator->Bytes(), separator_len); + set_separator_len = BifConst::LogAscii::set_separator->Len(); + set_separator = new char[set_separator_len]; + memcpy(set_separator, BifConst::LogAscii::set_separator->Bytes(), set_separator_len); + empty_field_len = BifConst::LogAscii::empty_field->Len(); empty_field = new char[empty_field_len]; memcpy(empty_field, BifConst::LogAscii::empty_field->Bytes(), empty_field_len); @@ -27,6 +31,7 @@ LogWriterAscii::LogWriterAscii() header_prefix_len = BifConst::LogAscii::header_prefix->Len(); header_prefix = new char[header_prefix_len]; memcpy(header_prefix, BifConst::LogAscii::header_prefix->Bytes(), header_prefix_len); + } LogWriterAscii::~LogWriterAscii() @@ -35,6 +40,7 @@ LogWriterAscii::~LogWriterAscii() fclose(file); delete [] separator; + delete [] set_separator; delete [] empty_field; delete [] unset_field; delete [] header_prefix; @@ -89,6 +95,86 @@ void LogWriterAscii::DoFinish() { } +bool LogWriterAscii::DoWriteOne(ODesc* desc, LogVal* val, const LogField* field) + { + if ( ! val->present ) + { + desc->AddN(unset_field, unset_field_len); + return true; + } + + switch ( val->type ) { + + case TYPE_BOOL: + desc->Add(val->val.int_val ? "T" : "F"); + break; + + case TYPE_INT: + case TYPE_ENUM: + desc->Add(val->val.int_val); + break; + + case TYPE_COUNT: + case TYPE_COUNTER: + case TYPE_PORT: + desc->Add(val->val.uint_val); + break; + + case TYPE_SUBNET: + desc->Add(dotted_addr(val->val.subnet_val.net)); + desc->Add("/"); + desc->Add(val->val.subnet_val.width); + break; + + case TYPE_NET: + case TYPE_ADDR: + desc->Add(dotted_addr(val->val.addr_val)); + break; + + case TYPE_DOUBLE: + case TYPE_TIME: + case TYPE_INTERVAL: + desc->Add(val->val.double_val); + break; + + case TYPE_STRING: + { + int size = val->val.string_val->size(); + if ( size ) + desc->AddN(val->val.string_val->data(), val->val.string_val->size()); + else + desc->AddN(empty_field, empty_field_len); + break; + } + + case TYPE_TABLE: + { + if ( ! val->val.set_val.size ) + { + desc->AddN(empty_field, empty_field_len); + break; + } + + for ( int j = 0; j < val->val.set_val.size; j++ ) + { + if ( j > 0 ) + desc->AddN(set_separator, set_separator_len); + + if ( ! DoWriteOne(desc, val->val.set_val.vals[j], field) ) + return false; + } + + break; + } + + default: + Error(Fmt("unsupported field format %d for %s", val->type, field->name.c_str())); + return false; + } + + return true; + } + bool LogWriterAscii::DoWrite(int num_fields, const LogField* const * fields, LogVal** vals) { ODesc desc(DESC_READABLE); @@ -99,63 +185,9 @@ bool LogWriterAscii::DoWrite(int num_fields, const LogField* const * fields, Log if ( i > 0 ) desc.AddRaw(separator, separator_len); - LogVal* val = vals[i]; - const LogField* field = fields[i]; - - if ( ! val->present ) - { - desc.AddN(unset_field, unset_field_len); - continue; - } - - switch ( field->type ) { - case TYPE_BOOL: - desc.Add(val->val.int_val ? "T" : "F"); - break; - - case TYPE_INT: - case TYPE_ENUM: - desc.Add(val->val.int_val); - break; - - case TYPE_COUNT: - case TYPE_COUNTER: - case TYPE_PORT: - desc.Add(val->val.uint_val); - break; - - case TYPE_SUBNET: - desc.Add(dotted_addr(val->val.subnet_val.net)); - desc.Add("/"); - desc.Add(val->val.subnet_val.width); - break; - - case TYPE_NET: - case TYPE_ADDR: - desc.Add(dotted_addr(val->val.addr_val)); - break; - - case TYPE_DOUBLE: - case TYPE_TIME: - case TYPE_INTERVAL: - desc.Add(val->val.double_val); - break; - - case TYPE_STRING: - { - int size = val->val.string_val->size(); - if ( size ) - desc.AddN(val->val.string_val->data(), val->val.string_val->size()); - else - desc.AddN(empty_field, empty_field_len); - break; - } - - default: - Error(Fmt("unsupported field format %d for %s", field->type, field->name.c_str())); + if ( ! DoWriteOne(&desc, vals[i], fields[i]) ) return false; } - } desc.Add("\n"); diff --git a/src/LogWriterAscii.h b/src/LogWriterAscii.h index 384be65f19..fee0d2f1f7 100644 --- a/src/LogWriterAscii.h +++ b/src/LogWriterAscii.h @@ -24,6 +24,7 @@ protected: private: bool IsSpecial(string path) { return path.find("/dev/") == 0; } + bool DoWriteOne(ODesc* desc, LogVal* val, const LogField* field); FILE* file; string fname; @@ -35,6 +36,9 @@ private: char* separator; int separator_len; + char* set_separator; + int set_separator_len; + char* empty_field; int empty_field_len; diff --git a/src/logging.bif b/src/logging.bif index 2eb8eea35b..fe2baae782 100644 --- a/src/logging.bif +++ b/src/logging.bif @@ -69,6 +69,7 @@ const output_to_stdout: bool; const include_header: bool; const header_prefix: string; const separator: string; +const set_separator: string; const empty_field: string; const unset_field: string; diff --git a/testing/btest/logging/remote-types.bro b/testing/btest/logging/remote-types.bro new file mode 100644 index 0000000000..57874c7c82 --- /dev/null +++ b/testing/btest/logging/remote-types.bro @@ -0,0 +1,90 @@ +# +# @TEST-EXEC: btest-bg-run sender bro --pseudo-realtime %INPUT ../sender.bro +# @TEST-EXEC: sleep 1 +# @TEST-EXEC: btest-bg-run receiver bro --pseudo-realtime %INPUT ../receiver.bro +# @TEST-EXEC: sleep 1 +# @TEST-EXEC: btest-bg-wait -k 1 +# @TEST-EXEC: btest-diff receiver/ssh.log +# @TEST-EXEC: cmp receiver/ssh.log sender/ssh.log + +# Remote version testing all types. + +# This is the common part loaded by both sender and receiver. + +redef LogAscii::empty_field = "EMPTY"; + +module SSH; + +export { + # Create a new ID for our log stream + redef enum Log::ID += { SSH }; + + type Log: record { + b: bool; + i: int; + e: Log::ID; + c: count; + p: port; + sn: subnet; + n: net; + a: addr; + d: double; + t: time; + iv: interval; + s: string; + sc: set[count]; + ss: set[string]; + se: set[string]; + }; +} + +global log_ssh: event(rec: Log); + +event bro_init() +{ + Log::create_stream(SSH, [$columns=Log, $ev=log_ssh]); +} + +##### + +@TEST-START-FILE sender.bro + +module SSH; + +@load listen-clear + +event remote_connection_handshake_done(p: event_peer) + { + local empty_set: set[string]; + + Log::write(SSH, [ + $b=T, + $i=-42, + $e=SSH, + $c=21, + $p=123/tcp, + $sn=10.0.0.1/24, + $n=10.0., + $a=1.2.3.4, + $d=3.14, + $t=network_time(), + $iv=100secs, + $s="hurz", + $sc=set(1,2,3,4), + $ss=set("AA", "BB", "CC"), + $se=empty_set + ]); + } +@TEST-END-FILE + +@TEST-START-FILE receiver.bro + +##### + +@load remote + +redef Remote::destinations += { + ["foo"] = [$host = 127.0.0.1, $connect=T, $request_logs=T] +}; + +@TEST-END-FILE diff --git a/testing/btest/logging/types.bro b/testing/btest/logging/types.bro new file mode 100644 index 0000000000..c54a1fa9f1 --- /dev/null +++ b/testing/btest/logging/types.bro @@ -0,0 +1,57 @@ +# +# @TEST-EXEC: bro %INPUT +# @TEST-EXEC: btest-diff ssh.log +# +# Testing all possible types. + +redef LogAscii::empty_field = "EMPTY"; + +module SSH; + +export { + redef enum Log::ID += { SSH }; + + type Log: record { + b: bool; + i: int; + e: Log::ID; + c: count; + p: port; + sn: subnet; + n: net; + a: addr; + d: double; + t: time; + iv: interval; + s: string; + sc: set[count]; + ss: set[string]; + se: set[string]; + }; +} + +event bro_init() +{ + Log::create_stream(SSH, [$columns=Log]); + + local empty_set: set[string]; + + Log::write(SSH, [ + $b=T, + $i=-42, + $e=SSH, + $c=21, + $p=123/tcp, + $sn=10.0.0.1/24, + $n=10.0., + $a=1.2.3.4, + $d=3.14, + $t=network_time(), + $iv=100secs, + $s="hurz", + $sc=set(1,2,3,4), + $ss=set("AA", "BB", "CC"), + $se=empty_set + ]); +} +