From 4cdf1e39bbee3dd50a9d4b1a38da4ee78ad3bc22 Mon Sep 17 00:00:00 2001 From: Chung Min Kim Date: Fri, 22 Jun 2018 14:27:46 -0700 Subject: [PATCH 01/43] Add code coverage for bro source files after btest test suite Adds --enable-coverage flag to configure Bro with gcov. A new directory named /testing/bro-code-coverage/ contains a new coverage target that as part of `make coverage` in /testing/. This coverage option creates coverage.log of all important directories in /src/ and places all generated .gcov files alongside the corresponding source file. --- configure | 7 ++ testing/Makefile | 2 + testing/bro-core-coverage/Makefile | 9 ++ testing/bro-core-coverage/code_coverage.sh | 134 +++++++++++++++++++++ testing/btest/Makefile | 1 + 5 files changed, 153 insertions(+) create mode 100644 testing/bro-core-coverage/Makefile create mode 100755 testing/bro-core-coverage/code_coverage.sh diff --git a/configure b/configure index fdbca263c6..56d10e393b 100755 --- a/configure +++ b/configure @@ -45,6 +45,7 @@ Usage: $0 [OPTION]... [VAR=VALUE]... Optional Features: --enable-debug compile in debugging mode (like --build-type=Debug) + --enable-coverage compile with code coverage support (implies debugging mode) --enable-mobile-ipv6 analyze mobile IPv6 features defined by RFC 6275 --enable-perftools force use of Google perftools on non-Linux systems (automatically on when perftools is present on Linux) @@ -142,6 +143,8 @@ append_cache_entry INSTALL_BROCTL BOOL true append_cache_entry CPACK_SOURCE_IGNORE_FILES STRING append_cache_entry ENABLE_MOBILE_IPV6 BOOL false append_cache_entry DISABLE_PERFTOOLS BOOL false +append_cache_entry DISABLE_RUBY_BINDINGS BOOL true +append_cache_entry ENABLE_COVERAGE BOOL false # parse arguments while [ $# -ne 0 ]; do @@ -197,6 +200,10 @@ while [ $# -ne 0 ]; do --logdir=*) append_cache_entry BRO_LOG_DIR PATH $optarg ;; + --enable-coverage) + append_cache_entry ENABLE_COVERAGE BOOL true + append_cache_entry ENABLE_DEBUG BOOL true + ;; --enable-debug) append_cache_entry ENABLE_DEBUG BOOL true ;; diff --git a/testing/Makefile b/testing/Makefile index e83ec09396..e34aaca939 100644 --- a/testing/Makefile +++ b/testing/Makefile @@ -8,6 +8,7 @@ brief: make-brief coverage distclean: @rm -f coverage.log $(MAKE) -C btest $@ + $(MAKE) -C bro-core-coverage $@ make-verbose: @for repo in $(DIRS); do (cd $$repo && make -s ); done @@ -22,4 +23,5 @@ coverage: @echo "Complete test suite code coverage:" @./scripts/coverage-calc "brocov.tmp.*" coverage.log `pwd`/../scripts @rm -f brocov.tmp.* + @cd bro-core-coverage && make coverage diff --git a/testing/bro-core-coverage/Makefile b/testing/bro-core-coverage/Makefile new file mode 100644 index 0000000000..e7db4894fd --- /dev/null +++ b/testing/bro-core-coverage/Makefile @@ -0,0 +1,9 @@ +coverage: cleanup + @./code_coverage.sh + +cleanup: + @rm -f coverage.log + @find ../../ -name "*.gcov" -exec rm {} \; + +distclean: cleanup + @find ../../ -name "*.gcno" -exec rm {} \; diff --git a/testing/bro-core-coverage/code_coverage.sh b/testing/bro-core-coverage/code_coverage.sh new file mode 100755 index 0000000000..b1eb8c599a --- /dev/null +++ b/testing/bro-core-coverage/code_coverage.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# +# On a Bro build configured with --enable-coverage, this script +# produces a code coverage report after Bro has been invoked. The +# intended application of this script is after the btest testsuite has +# run. This combination (btests first, coverage computation afterward) +# happens automatically when running "make" in the testing directory. +# +# This depends on gcov, which should come with your gcc. +# +# AUTOMATES CODE COVERAGE TESTING +# 1. Run test suite +# 2. Check for .gcda files existing. +# 3a. Run gcov (-p to preserve path) +# 3b. Prune .gcov files for objects outside of the Bro tree +# 4a. Analyze .gcov files generated and create summary file +# 4b. Send .gcov files to appropriate path +# +CURR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Location of script +BASE="$(realpath "${CURR}/../../")" +TMP="${CURR}/tmp.$$" +mkdir -p $TMP + +# DEFINE CLEANUP PROCESS +function finish { + find "$BASE" -name "*.gcda" -exec rm {} \; > /dev/null 2>&1 + rm -rf $TMP +} +trap finish EXIT + +# DEFINE CRUCIAL FUNCTIONS FOR COVERAGE CHECKING +function check_file_coverage { + GCOVDIR="$1" + + for i in $GCOVDIR/*.gcov; do + # Effective # of lines: starts with a number (# of runs in line) or ##### (line never run) + TOTAL=$(cut -d: -f 1 "$i" | sed 's/ //g' | grep -v "^[[:alpha:]]" | grep -v "-" | wc -l) + + # Count number of lines never run + UNRUN=$(grep "#####" "$i" | wc -l) + + # Lines in code are either run or unrun + RUN=$(($TOTAL - $UNRUN)) + + # Avoid division-by-zero problems: + PERCENTAGE=0.000 + [ $RUN -gt 0 ] && PERCENTAGE=$(bc <<< "scale=3; 100*$RUN/$TOTAL") + + # Find correlation between % of lines run vs. "Runs" + echo -e "$PERCENTAGE\t$RUN\t$TOTAL\t$(grep "0:Runs" "$i" | sed 's/.*://')\t$i" + done +} + +function check_group_coverage { + DATA="$1" # FILE CONTAINING COVERAGE DATA + SRC_FOLDER="$2" # WHERE BRO WAS COMPILED + OUTPUT="$3" + + # Prints all the relevant directories + DIRS=$(for i in $(cut -f 5 "$DATA"); do basename "$i" | sed 's/#[^#]*$//'; done \ + | sort | uniq | sed 's/^.*'"${SRC_FOLDER}"'//' | grep "^#s\+" ) + # "Generalize" folders unless it's from analyzers + DIRS=$(for i in $DIRS; do + if !(echo "$i" | grep "src#analyzer"); then + echo "$i" | cut -d "#" -f 1,2,3 + fi + done | sort | uniq ) + + for i in $DIRS; do + # For elements in #src, we only care about the files direclty in the directory. + if [[ "$i" = "#src" ]]; then + RUN=$(echo $(grep "$i#[^#]\+$" $DATA | grep "$SRC_FOLDER$i\|build$i" | cut -f 2) | tr " " "+" | bc) + TOTAL=$(echo $(grep "$i#[^#]\+$" $DATA | grep "$SRC_FOLDER$i\|build$i" | cut -f 3) | tr " " "+" | bc) + else + RUN=$(echo $(grep "$i" $DATA | cut -f 2) | tr " " "+" | bc) + TOTAL=$(echo $(grep "$i" $DATA | cut -f 3) | tr " " "+" | bc) + fi + + PERCENTAGE=$( echo "scale=3;100*$RUN/$TOTAL" | bc | tr "\n" " " ) + printf "%-50s\t%12s\t%6s %%\n" "$i" "$RUN/$TOTAL" $PERCENTAGE \ + | sed 's|#|/|g' >>$OUTPUT + done +} + +# 1. Run test suite +# SHOULD HAVE ALREADY BEEN RUN BEFORE THIS SCRIPT (BASED ON MAKEFILE TARGETS) + +# 2. Check for .gcno and .gcda file presence +echo -n "Checking for coverage files... " +for pat in gcda gcno; do + if [ -z "$(find "$BASE" -name "*.$pat" 2>/dev/null)" ]; then + echo "no .$pat files, nothing to do" + exit 0 + fi +done +echo "ok" + +# 3a. Run gcov (-p to preserve path) and move into tmp directory +echo -n "Creating coverage files... " +( cd "$TMP" && find "$BASE" -name "*.o" -exec gcov -p {} > /dev/null 2>&1 \; ) +NUM_GCOVS=$(ls "$TMP"/*.gcov | wc -l) +if [ $NUM_GCOVS -eq 0 ]; then + echo "no gcov files produced, aborting" + exit 1 +fi +echo "ok, $NUM_GCOVS coverage files" + +# 3b. Prune gcov files that fall outside of the Bro tree: +# Look for files containing gcov's slash substitution character "#" +# and remove any that don't contain the Bro path root. +echo -n "Pruning out-of-tree coverage files... " +PREFIX=$(echo "$BASE" | sed 's|/|#|g') +for i in "$TMP"/*#*.gcov; do + if ! [[ "$i" = *$PREFIX* ]]; then + rm -f $i + fi +done +NUM_GCOVS=$(ls "$TMP"/*.gcov | wc -l) +echo "ok, $NUM_GCOVS coverage files remain" + +# 4a. Analyze .gcov files generated and create summary file +echo -n "Creating summary file... " +DATA="${TMP}/data.txt" +SUMMARY="$CURR/coverage.log" +check_file_coverage "$TMP" > "$DATA" +check_group_coverage "$DATA" ${BASE##*/} $SUMMARY +echo "ok" + +# 4b. Send .gcov files to appropriate path +echo -n "Sending coverage files to respective directories... " +for i in "$TMP"/*#*.gcov; do + mv $i $(echo $(basename $i) | sed 's/#/\//g') +done +echo "ok" diff --git a/testing/btest/Makefile b/testing/btest/Makefile index c9bcfff5ee..c6f2438ad1 100644 --- a/testing/btest/Makefile +++ b/testing/btest/Makefile @@ -21,6 +21,7 @@ coverage: cleanup: @rm -f $(DIAG) @rm -rf $(SCRIPT_COV)* + @find ../../ -name "*.gcda" -exec rm {} \; distclean: cleanup @rm -rf .btest.failed.dat \ From 6449b0ab9e87cd61bc180fc6aafc9a5fc5ddfe4e Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Sat, 23 Jun 2018 14:46:47 -0700 Subject: [PATCH 02/43] mirroring previous topic/vern/set-ops to get branch up to date, since I'm a n00b --- src/Expr.cc | 77 +++++++++++++++++++++++++++++++++++++++++++++-------- src/Expr.h | 3 +++ src/Val.cc | 48 +++++++++++++++++++++++++++++++-- src/Val.h | 6 +++++ 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/Expr.cc b/src/Expr.cc index 1ab82853c3..617a7637df 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -663,6 +663,9 @@ Val* BinaryExpr::Fold(Val* v1, Val* v2) const if ( it == TYPE_INTERNAL_STRING ) return StringFold(v1, v2); + if ( v1->Type()->IsSet() ) + return SetFold(v1, v2); + if ( it == TYPE_INTERNAL_ADDR ) return AddrFold(v1, v2); @@ -849,6 +852,32 @@ Val* BinaryExpr::StringFold(Val* v1, Val* v2) const return new Val(result, TYPE_BOOL); } +Val* BinaryExpr::SetFold(Val* v1, Val* v2) const + { + TableVal* tv1 = v1->AsTableVal(); + TableVal* tv2 = v2->AsTableVal(); + + if ( tag != EXPR_AND && tag != EXPR_OR && tag != EXPR_SUB ) + BadTag("BinaryExpr::SetFold"); + + // TableVal* result = new TableVal(v1->Type()->AsTableType()); + TableVal* result = v1->Clone()->AsTableVal(); + + if ( tag == EXPR_OR ) + { + if ( ! tv2->AddTo(result, false, false) ) + reporter->InternalError("set union failed to type check"); + } + + else if ( tag == EXPR_SUB ) + { + if ( ! tv2->RemoveFrom(result) ) + reporter->InternalError("set difference failed to type check"); + } + + return result; + } + Val* BinaryExpr::AddrFold(Val* v1, Val* v2) const { IPAddr a1 = v1->AsAddr(); @@ -1421,24 +1450,39 @@ SubExpr::SubExpr(Expr* arg_op1, Expr* arg_op2) if ( IsError() ) return; - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); BroType* base_result_type = 0; if ( bt1 == TYPE_TIME && bt2 == TYPE_INTERVAL ) base_result_type = base_type(bt1); + else if ( bt1 == TYPE_TIME && bt2 == TYPE_TIME ) SetType(base_type(TYPE_INTERVAL)); + else if ( bt1 == TYPE_INTERVAL && bt2 == TYPE_INTERVAL ) base_result_type = base_type(bt1); + + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( same_type(t1, t2) ) + SetType(op1->Type()->Ref()); + else + ExprError("incompatible \"set\" operands"); + } + else if ( BothArithmetic(bt1, bt2) ) PromoteType(max_type(bt1, bt2), is_vector(op1) || is_vector(op2)); + else ExprError("requires arithmetic operands"); @@ -1864,13 +1908,16 @@ BitExpr::BitExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) if ( IsError() ) return; - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( (bt1 == TYPE_COUNT || bt1 == TYPE_COUNTER) && (bt2 == TYPE_COUNT || bt2 == TYPE_COUNTER) ) @@ -1883,8 +1930,16 @@ BitExpr::BitExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) SetType(base_type(TYPE_COUNT)); } + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( same_type(t1, t2) ) + SetType(op1->Type()->Ref()); + else + ExprError("incompatible \"set\" operands"); + } + else - ExprError("requires \"count\" operands"); + ExprError("requires \"count\" or compatible \"set\" operands"); } IMPLEMENT_SERIAL(BitExpr, SER_BIT_EXPR); diff --git a/src/Expr.h b/src/Expr.h index 9fc9aa15ed..a8c890b675 100644 --- a/src/Expr.h +++ b/src/Expr.h @@ -329,6 +329,9 @@ protected: // Same for when the constants are strings. virtual Val* StringFold(Val* v1, Val* v2) const; + // Same for when the constants are sets. + virtual Val* SetFold(Val* v1, Val* v2) const; + // Same for when the constants are addresses or subnets. virtual Val* AddrFold(Val* v1, Val* v2) const; virtual Val* SubNetFold(Val* v1, Val* v2) const; diff --git a/src/Val.cc b/src/Val.cc index 4da4a35d48..540719ef14 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1704,9 +1704,18 @@ int TableVal::RemoveFrom(Val* val) const HashKey* k; while ( tbl->NextEntry(k, c) ) { - Val* index = RecoverIndex(k); + // ### The following code appears to be a complete + // no-op. Commented out 8+ years after it was + // introduced. -VP 22Jun18 + // Val* index = RecoverIndex(k); + // + // Unref(index); - Unref(index); + // Not sure that this is 100% sound, since the HashKey + // comes from one table but is being used in another. + // OTOH, they are both the same type, so as long as + // we don't have hash keys that are keyed per dictionary, + // it should work ... Unref(t->Delete(k)); delete k; } @@ -1714,6 +1723,41 @@ int TableVal::RemoveFrom(Val* val) const return 1; } +TableVal* TableVal::Intersect(const TableVal* tv) const + { + TableVal* result = new TableVal(table_type); + + const PDict(TableEntryVal)* t1 = tv->AsTable(); + const PDict(TableEntryVal)* t2 = AsTable(); + const PDict(TableEntryVal)* t3 = result->AsTable(); + + // Figure out which is smaller. + if ( t1->Length() > t2->Length() ) + { // Swap. + const PDict(TableEntryVal)* t3 = t1; + t1 = t2; + t2 = t3; + } + + IterCookie* c = t1->InitForIteration(); + HashKey* k; + while ( t1->NextEntry(k, c) ) + { +//### // Here we leverage the same assumption about consistent +//### // hashes as in TableVal::RemoveFrom above. +//### if ( t2->Lookup(k) ) +//### { +//### Val* index = RecoverIndex(); +//### result-> +//### +//### Unref(index); +//### Unref(t->Delete(k)); +//### delete k; + } + + return result; + } + int TableVal::ExpandAndInit(Val* index, Val* new_val) { BroType* index_type = index->Type(); diff --git a/src/Val.h b/src/Val.h index 771ed40dd1..ef2a8eefd6 100644 --- a/src/Val.h +++ b/src/Val.h @@ -809,6 +809,12 @@ public: // Returns true if the addition typechecked, false if not. int RemoveFrom(Val* v) const override; + // Returns a new table that is the intersection of this + // table and the given table. Intersection is just done + // on index, not on yield value, so this really only makes + // sense for sets. + TableVal* Intersect(const TableVal* v) const; + // Expands any lists in the index into multiple initializations. // Returns true if the initializations typecheck, false if not. int ExpandAndInit(Val* index, Val* new_val); From 072a25df0f353009fb4655c39eb2efca7a000ab4 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Sun, 24 Jun 2018 10:43:58 -0700 Subject: [PATCH 03/43] set intersection implemented --- src/Expr.cc | 6 ++++++ src/Val.cc | 18 +++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Expr.cc b/src/Expr.cc index 617a7637df..ed14eb6045 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -860,6 +860,9 @@ Val* BinaryExpr::SetFold(Val* v1, Val* v2) const if ( tag != EXPR_AND && tag != EXPR_OR && tag != EXPR_SUB ) BadTag("BinaryExpr::SetFold"); + if ( tag == EXPR_AND ) + return tv1->Intersect(tv2); + // TableVal* result = new TableVal(v1->Type()->AsTableType()); TableVal* result = v1->Clone()->AsTableVal(); @@ -875,6 +878,9 @@ Val* BinaryExpr::SetFold(Val* v1, Val* v2) const reporter->InternalError("set difference failed to type check"); } + else + BadTag("BinaryExpr::SetFold", expr_name(tag)); + return result; } diff --git a/src/Val.cc b/src/Val.cc index 540719ef14..d6bcdae4a6 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1729,7 +1729,7 @@ TableVal* TableVal::Intersect(const TableVal* tv) const const PDict(TableEntryVal)* t1 = tv->AsTable(); const PDict(TableEntryVal)* t2 = AsTable(); - const PDict(TableEntryVal)* t3 = result->AsTable(); + PDict(TableEntryVal)* t3 = result->AsNonConstTable(); // Figure out which is smaller. if ( t1->Length() > t2->Length() ) @@ -1743,16 +1743,12 @@ TableVal* TableVal::Intersect(const TableVal* tv) const HashKey* k; while ( t1->NextEntry(k, c) ) { -//### // Here we leverage the same assumption about consistent -//### // hashes as in TableVal::RemoveFrom above. -//### if ( t2->Lookup(k) ) -//### { -//### Val* index = RecoverIndex(); -//### result-> -//### -//### Unref(index); -//### Unref(t->Delete(k)); -//### delete k; + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( t2->Lookup(k) ) + t3->Insert(k, new TableEntryVal(0)); + + delete k; } return result; From e416d34f1f3cb291fa164e00f531ba52dde47678 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Fri, 6 Jul 2018 13:46:06 -0700 Subject: [PATCH 04/43] bug fix for set intersection --- src/Val.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Val.cc b/src/Val.cc index d6bcdae4a6..48458254f1 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1734,9 +1734,9 @@ TableVal* TableVal::Intersect(const TableVal* tv) const // Figure out which is smaller. if ( t1->Length() > t2->Length() ) { // Swap. - const PDict(TableEntryVal)* t3 = t1; + const PDict(TableEntryVal)* tmp = t1; t1 = t2; - t2 = t3; + t2 = tmp; } IterCookie* c = t1->InitForIteration(); From 2a8ea87c9fe048657edcf257ced11d89a8067afb Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Fri, 6 Jul 2018 16:22:06 -0700 Subject: [PATCH 05/43] implemented set relationals --- src/Expr.cc | 95 +++++++++++++++++++++++++++++++++++++++-------------- src/Val.cc | 68 +++++++++++++++++++++++++++++++++----- src/Val.h | 10 ++++++ 3 files changed, 140 insertions(+), 33 deletions(-) diff --git a/src/Expr.cc b/src/Expr.cc index ed14eb6045..d3b22503cb 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -856,32 +856,55 @@ Val* BinaryExpr::SetFold(Val* v1, Val* v2) const { TableVal* tv1 = v1->AsTableVal(); TableVal* tv2 = v2->AsTableVal(); + TableVal* result; + bool res; - if ( tag != EXPR_AND && tag != EXPR_OR && tag != EXPR_SUB ) - BadTag("BinaryExpr::SetFold"); - - if ( tag == EXPR_AND ) + switch ( tag ) { + case EXPR_AND: return tv1->Intersect(tv2); - // TableVal* result = new TableVal(v1->Type()->AsTableType()); - TableVal* result = v1->Clone()->AsTableVal(); + case EXPR_OR: + // TableVal* result = new TableVal(v1->Type()->AsTableType()); + result = v1->Clone()->AsTableVal(); - if ( tag == EXPR_OR ) - { if ( ! tv2->AddTo(result, false, false) ) reporter->InternalError("set union failed to type check"); - } + return result; + + case EXPR_SUB: + result = v1->Clone()->AsTableVal(); - else if ( tag == EXPR_SUB ) - { if ( ! tv2->RemoveFrom(result) ) reporter->InternalError("set difference failed to type check"); - } + return result; - else + case EXPR_EQ: + res = tv1->EqualTo(tv2); + break; + + case EXPR_NE: + res = ! tv1->EqualTo(tv2); + break; + + case EXPR_LT: + res = tv1->IsSubsetOf(tv2) && tv1->Size() < tv2->Size(); + break; + + case EXPR_LE: + res = tv1->IsSubsetOf(tv2); + break; + + case EXPR_GE: + case EXPR_GT: + // These should't happen due to canonicalization. + reporter->InternalError("confusion over canonicalization in set comparison"); + + default: BadTag("BinaryExpr::SetFold", expr_name(tag)); + return 0; + } - return result; + return new Val(res, TYPE_BOOL); } Val* BinaryExpr::AddrFold(Val* v1, Val* v2) const @@ -1970,13 +1993,16 @@ EqExpr::EqExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) Canonicize(); - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( is_vector(op1) || is_vector(op2) ) SetType(new VectorType(base_type(TYPE_BOOL))); @@ -2006,10 +2032,20 @@ EqExpr::EqExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) break; case TYPE_ENUM: - if ( ! same_type(op1->Type(), op2->Type()) ) + if ( ! same_type(t1, t2) ) ExprError("illegal enum comparison"); break; + case TYPE_TABLE: + if ( t1->IsSet() && t2->IsSet() ) + { + if ( ! same_type(t1, t2) ) + ExprError("incompatible sets in comparison"); + break; + } + + // FALL THROUGH + default: ExprError("illegal comparison"); } @@ -2072,13 +2108,16 @@ RelExpr::RelExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) Canonicize(); - TypeTag bt1 = op1->Type()->Tag(); - if ( IsVector(bt1) ) - bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + const BroType* t1 = op1->Type(); + const BroType* t2 = op2->Type(); - TypeTag bt2 = op2->Type()->Tag(); + TypeTag bt1 = t1->Tag(); + if ( IsVector(bt1) ) + bt1 = t1->AsVectorType()->YieldType()->Tag(); + + TypeTag bt2 = t2->Tag(); if ( IsVector(bt2) ) - bt2 = op2->Type()->AsVectorType()->YieldType()->Tag(); + bt2 = t2->AsVectorType()->YieldType()->Tag(); if ( is_vector(op1) || is_vector(op2) ) SetType(new VectorType(base_type(TYPE_BOOL))); @@ -2088,6 +2127,12 @@ RelExpr::RelExpr(BroExprTag arg_tag, Expr* arg_op1, Expr* arg_op2) if ( BothArithmetic(bt1, bt2) ) PromoteOps(max_type(bt1, bt2)); + else if ( t1->IsSet() && t2->IsSet() ) + { + if ( ! same_type(t1, t2) ) + ExprError("incompatible sets in comparison"); + } + else if ( bt1 != bt2 ) ExprError("operands must be of the same type"); diff --git a/src/Val.cc b/src/Val.cc index 48458254f1..02e88cd3b4 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1727,16 +1727,16 @@ TableVal* TableVal::Intersect(const TableVal* tv) const { TableVal* result = new TableVal(table_type); + const PDict(TableEntryVal)* t0 = AsTable(); const PDict(TableEntryVal)* t1 = tv->AsTable(); - const PDict(TableEntryVal)* t2 = AsTable(); - PDict(TableEntryVal)* t3 = result->AsNonConstTable(); + PDict(TableEntryVal)* t2 = result->AsNonConstTable(); - // Figure out which is smaller. - if ( t1->Length() > t2->Length() ) + // Figure out which is smaller; assign it to t1. + if ( t1->Length() > t0->Length() ) { // Swap. const PDict(TableEntryVal)* tmp = t1; - t1 = t2; - t2 = tmp; + t1 = t0; + t0 = tmp; } IterCookie* c = t1->InitForIteration(); @@ -1745,8 +1745,8 @@ TableVal* TableVal::Intersect(const TableVal* tv) const { // Here we leverage the same assumption about consistent // hashes as in TableVal::RemoveFrom above. - if ( t2->Lookup(k) ) - t3->Insert(k, new TableEntryVal(0)); + if ( t0->Lookup(k) ) + t2->Insert(k, new TableEntryVal(0)); delete k; } @@ -1754,6 +1754,58 @@ TableVal* TableVal::Intersect(const TableVal* tv) const return result; } +bool TableVal::EqualTo(const TableVal* tv) const + { + const PDict(TableEntryVal)* t0 = AsTable(); + const PDict(TableEntryVal)* t1 = tv->AsTable(); + + if ( t0->Length() != t1->Length() ) + return false; + + IterCookie* c = t0->InitForIteration(); + HashKey* k; + while ( t0->NextEntry(k, c) ) + { + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( ! t1->Lookup(k) ) + { + delete k; + return false; + } + + delete k; + } + + return true; + } + +bool TableVal::IsSubsetOf(const TableVal* tv) const + { + const PDict(TableEntryVal)* t0 = AsTable(); + const PDict(TableEntryVal)* t1 = tv->AsTable(); + + if ( t0->Length() > t1->Length() ) + return false; + + IterCookie* c = t0->InitForIteration(); + HashKey* k; + while ( t0->NextEntry(k, c) ) + { + // Here we leverage the same assumption about consistent + // hashes as in TableVal::RemoveFrom above. + if ( ! t1->Lookup(k) ) + { + delete k; + return false; + } + + delete k; + } + + return true; + } + int TableVal::ExpandAndInit(Val* index, Val* new_val) { BroType* index_type = index->Type(); diff --git a/src/Val.h b/src/Val.h index ef2a8eefd6..32ce6a0187 100644 --- a/src/Val.h +++ b/src/Val.h @@ -815,6 +815,16 @@ public: // sense for sets. TableVal* Intersect(const TableVal* v) const; + // Returns true if this set contains the same members as the + // given set. Note that comparisons are done using hash keys, + // so errors can arise for compound sets such as sets-of-sets. + // See https://bro-tracker.atlassian.net/browse/BIT-1949. + bool EqualTo(const TableVal* v) const; + + // Returns true if this set is a subset (not necessarily proper) + // of the given set. + bool IsSubsetOf(const TableVal* v) const; + // Expands any lists in the index into multiple initializations. // Returns true if the initializations typecheck, false if not. int ExpandAndInit(Val* index, Val* new_val); From 73349362a3e5cec0625738b711bf7aca9c6d8ab6 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Mon, 9 Jul 2018 13:05:10 -0700 Subject: [PATCH 06/43] 'W' for zero window implemented; logarithmic 'T'/'C'/'W' history repetitions --- NEWS | 10 ++++ scripts/base/protocols/conn/main.bro | 14 +++-- src/Conn.cc | 39 +++++++++++++ src/Conn.h | 11 ++++ src/analyzer/protocol/tcp/TCP.cc | 67 ++++++++++++++--------- src/analyzer/protocol/tcp/TCP_Endpoint.cc | 29 ++++++++++ src/analyzer/protocol/tcp/TCP_Endpoint.h | 17 +++++- src/analyzer/protocol/tcp/events.bif | 37 +++++++++++++ src/analyzer/protocol/udp/UDP.cc | 23 +++++++- src/analyzer/protocol/udp/UDP.h | 6 ++ src/analyzer/protocol/udp/events.bif | 13 +++++ 11 files changed, 231 insertions(+), 35 deletions(-) diff --git a/NEWS b/NEWS index b6d88aa82f..31f144cddd 100644 --- a/NEWS +++ b/NEWS @@ -255,6 +255,16 @@ New Functionality semi-present in previous versions of Bro, but required constants as its operands; now you can use any pattern-valued expressions. +- The new history character 'W' indicates that the originator ('w' = responder) + advertised a TCP zero window (instructing the peer to not send any data + until receiving a non-zero window). + +- The history characters 'C' (checksum error seen), 'T' (retransmission + seen), and 'W' (zero window advertised) are now repeated in a logarithmic + fashion upon seeing multiple instances of the corresponding behavior. + Thus a connection with 2 C's in its history means that the originator + sent >= 10 packets with checksum errors; 3 C's means >= 100, etc. + Changed Functionality --------------------- diff --git a/scripts/base/protocols/conn/main.bro b/scripts/base/protocols/conn/main.bro index 0e9661dea3..e96b27873c 100644 --- a/scripts/base/protocols/conn/main.bro +++ b/scripts/base/protocols/conn/main.bro @@ -86,8 +86,9 @@ export { ## d packet with payload ("data") ## f packet with FIN bit set ## r packet with RST bit set - ## c packet with a bad checksum + ## c packet with a bad checksum (applies to UDP too) ## t packet with retransmitted payload + ## w packet with a zero window advertisement ## i inconsistent packet (e.g. FIN+RST bits set) ## q multi-flag packet (SYN+FIN or SYN+RST bits set) ## ^ connection direction was flipped by Bro's heuristic @@ -95,12 +96,15 @@ export { ## ## If the event comes from the originator, the letter is in ## upper-case; if it comes from the responder, it's in - ## lower-case. The 'a', 'c', 'd', 'i', 'q', and 't' flags are + ## lower-case. The 'a', 'd', 'i' and 'q' flags are ## recorded a maximum of one time in either direction regardless - ## of how many are actually seen. However, 'f', 'h', 'r', or - ## 's' may be recorded multiple times for either direction and - ## only compressed when sharing a sequence number with the + ## of how many are actually seen. 'f', 'h', 'r' and + ## 's' can be recorded multiple times for either direction + ## if the associated sequence number differs from the ## last-seen packet of the same flag type. + ## 'c', 't' and 'w' are recorded in a logarithmic fashion: + ## the second instance represents that the event was seen + ## (at least) 10 times; the third instance, 100 times; etc. history: string &log &optional; ## Number of packets that the originator sent. ## Only set if :bro:id:`use_conn_size_analyzer` = T. diff --git a/src/Conn.cc b/src/Conn.cc index 1edecde0b9..7f9dd862b9 100644 --- a/src/Conn.cc +++ b/src/Conn.cc @@ -289,6 +289,45 @@ bool Connection::IsReuse(double t, const u_char* pkt) return root_analyzer && root_analyzer->IsReuse(t, pkt); } +bool Connection::ScaledHistoryEntry(char code, + uint32& counter, uint32& scaling_threshold, + uint32 scaling_base) + { + if ( ++counter == scaling_threshold ) + { + AddHistory(code); + + int new_threshold = scaling_threshold * scaling_base; + + if ( new_threshold <= scaling_threshold ) + // This can happen due to wrap-around. In that + // case, reset the counter but leave the threshold + // unchanged. + counter = 0; + + else + scaling_threshold = new_threshold; + + return true; + } + + return false; + } + +void Connection::HistoryThresholdEvent(EventHandlerPtr e, bool is_orig, + uint32 threshold) + { + if ( ! e ) + return; + + val_list* vl = new val_list; + vl->append(BuildConnVal()); + vl->append(new Val(is_orig, TYPE_BOOL)); + vl->append(new Val(threshold, TYPE_COUNT)); + + ConnectionEvent(e, 0, vl); + } + void Connection::DeleteTimer(double /* t */) { if ( is_active ) diff --git a/src/Conn.h b/src/Conn.h index db0cb2fe55..00f084e8de 100644 --- a/src/Conn.h +++ b/src/Conn.h @@ -240,6 +240,17 @@ public: return true; } + // Increments the passed counter and adds it as a history + // code if it has crossed the next scaling threshold. Scaling + // is done in terms of powers of the third argument. + // Returns true if the threshold was crossed, false otherwise. + bool ScaledHistoryEntry(char code, + uint32& counter, uint32& scaling_threshold, + uint32 scaling_base = 10); + + void HistoryThresholdEvent(EventHandlerPtr e, bool is_orig, + uint32 threshold); + void AddHistory(char code) { history += code; } void DeleteTimer(double t); diff --git a/src/analyzer/protocol/tcp/TCP.cc b/src/analyzer/protocol/tcp/TCP.cc index 791cf9f779..b08fc7b07b 100644 --- a/src/analyzer/protocol/tcp/TCP.cc +++ b/src/analyzer/protocol/tcp/TCP.cc @@ -459,7 +459,7 @@ bool TCP_Analyzer::ValidateChecksum(const struct tcphdr* tp, ! endpoint->ValidChecksum(tp, len) ) { Weird("bad_TCP_checksum"); - endpoint->CheckHistory(HIST_CORRUPT_PKT, 'C'); + endpoint->ChecksumError(); return false; } else @@ -579,16 +579,38 @@ static void init_window(TCP_Endpoint* endpoint, TCP_Endpoint* peer, static void update_window(TCP_Endpoint* endpoint, unsigned int window, uint32 base_seq, uint32 ack_seq, TCP_Flags flags) { - // Note, the offered window on an initial SYN is unscaled, even - // if the SYN includes scaling, so we need to do the following - // test *before* updating the scaling information below. (Hmmm, - // how does this work for windows on SYN/ACKs? ###) + // Note, applying scaling here would be incorrect for an initial SYN, + // whose window value is always unscaled. However, we don't + // check the window's value for recision in that case anyway, so + // no-harm-no-foul. int scale = endpoint->window_scale; window = window << scale; + // Zero windows are boring if either (1) they come with a RST packet + // or after a RST packet, or (2) they come after the peer has sent + // a FIN (because there's no relevant window at that point anyway). + // (They're also boring if they come after the peer has sent a RST, + // but *nothing* should be sent in response to a RST, so we ignore + // that case.) + // + // However, they *are* potentially interesting if sent by an + // endpoint that's already sent a FIN, since that FIN meant "I'm + // not going to send any more", but doesn't mean "I won't receive + // any more". + if ( window == 0 && ! flags.RST() && + endpoint->peer->state != TCP_ENDPOINT_CLOSED && + endpoint->state != TCP_ENDPOINT_RESET ) + endpoint->ZeroWindow(); + // Don't analyze window values off of SYNs, they're sometimes - // immediately rescinded. - if ( ! flags.SYN() ) + // immediately rescinded. Also don't do so for FINs or RSTs, + // or if the connection has already been partially closed, since + // such recisions occur frequently in practice, probably as the + // receiver loses buffer memory due to its process going away. + + if ( ! flags.SYN() && ! flags.FIN() && ! flags.RST() && + endpoint->state != TCP_ENDPOINT_CLOSED && + endpoint->state != TCP_ENDPOINT_RESET ) { // ### Decide whether to accept new window based on Active // Mapping policy. @@ -601,29 +623,20 @@ static void update_window(TCP_Endpoint* endpoint, unsigned int window, if ( advance < 0 ) { - // A window recision. We don't report these - // for FINs or RSTs, or if the connection - // has already been partially closed, since - // such recisions occur frequently in practice, - // probably as the receiver loses buffer memory - // due to its process going away. - // - // We also, for window scaling, allow a bit - // of slop ###. This is because sometimes - // there will be an apparent recision due - // to the granularity of the scaling. - if ( ! flags.FIN() && ! flags.RST() && - endpoint->state != TCP_ENDPOINT_CLOSED && - endpoint->state != TCP_ENDPOINT_RESET && - (-advance) >= (1 << scale) ) + // An apparent window recision. Allow a + // bit of slop for window scaling. This is + // because sometimes there will be an + // apparent recision due to the granularity + // of the scaling. + if ( (-advance) >= (1 << scale) ) endpoint->Conn()->Weird("window_recision"); } - - endpoint->window = window; - endpoint->window_ack_seq = ack_seq; - endpoint->window_seq = base_seq; } } + + endpoint->window = window; + endpoint->window_ack_seq = ack_seq; + endpoint->window_seq = base_seq; } static void syn_weirds(TCP_Flags flags, TCP_Endpoint* endpoint, int data_len) @@ -1206,7 +1219,7 @@ static int32 update_last_seq(TCP_Endpoint* endpoint, uint32 last_seq, endpoint->UpdateLastSeq(last_seq); else if ( delta_last < 0 && len > 0 ) - endpoint->CheckHistory(HIST_RXMIT, 'T'); + endpoint->DidRxmit(); return delta_last; } diff --git a/src/analyzer/protocol/tcp/TCP_Endpoint.cc b/src/analyzer/protocol/tcp/TCP_Endpoint.cc index c3175ec9f5..36fdaf1022 100644 --- a/src/analyzer/protocol/tcp/TCP_Endpoint.cc +++ b/src/analyzer/protocol/tcp/TCP_Endpoint.cc @@ -32,6 +32,9 @@ TCP_Endpoint::TCP_Endpoint(TCP_Analyzer* arg_analyzer, int arg_is_orig) tcp_analyzer = arg_analyzer; is_orig = arg_is_orig; + chk_cnt = rxmt_cnt = win0_cnt = 0; + chk_thresh = rxmt_thresh = win0_thresh = 1; + hist_last_SYN = hist_last_FIN = hist_last_RST = 0; src_addr = is_orig ? Conn()->RespAddr() : Conn()->OrigAddr(); @@ -284,3 +287,29 @@ void TCP_Endpoint::AddHistory(char code) Conn()->AddHistory(code); } +void TCP_Endpoint::ChecksumError() + { + uint32 t = chk_thresh; + if ( Conn()->ScaledHistoryEntry(IsOrig() ? 'C' : 'c', + chk_cnt, chk_thresh) ) + Conn()->HistoryThresholdEvent(tcp_multiple_checksum_errors, + IsOrig(), t); + } + +void TCP_Endpoint::DidRxmit() + { + uint32 t = rxmt_thresh; + if ( Conn()->ScaledHistoryEntry(IsOrig() ? 'T' : 't', + rxmt_cnt, rxmt_thresh) ) + Conn()->HistoryThresholdEvent(tcp_multiple_retransmissions, + IsOrig(), t); + } + +void TCP_Endpoint::ZeroWindow() + { + uint32 t = win0_thresh; + if ( Conn()->ScaledHistoryEntry(IsOrig() ? 'W' : 'w', + win0_cnt, win0_thresh) ) + Conn()->HistoryThresholdEvent(tcp_multiple_zero_windows, + IsOrig(), t); + } diff --git a/src/analyzer/protocol/tcp/TCP_Endpoint.h b/src/analyzer/protocol/tcp/TCP_Endpoint.h index 2e8a8a041e..d84b9adc77 100644 --- a/src/analyzer/protocol/tcp/TCP_Endpoint.h +++ b/src/analyzer/protocol/tcp/TCP_Endpoint.h @@ -166,6 +166,15 @@ public: int ValidChecksum(const struct tcphdr* tp, int len) const; + // Called to inform endpoint that it has generated a checksum error. + void ChecksumError(); + + // Called to inform endpoint that it has generated a retransmission. + void DidRxmit(); + + // Called to inform endpoint that it has offered a zero window.. + void ZeroWindow(); + // Returns true if the data was used (and hence should be recorded // in the save file), false otherwise. int DataSent(double t, uint64 seq, int len, int caplen, const u_char* data, @@ -188,6 +197,7 @@ public: #define HIST_MULTI_FLAG_PKT 0x40 #define HIST_CORRUPT_PKT 0x80 #define HIST_RXMIT 0x100 +#define HIST_WIN0 0x200 int CheckHistory(uint32 mask, char code); void AddHistory(char code); @@ -202,7 +212,7 @@ public: double start_time, last_time; IPAddr src_addr; // the other endpoint IPAddr dst_addr; // this endpoint - uint32 window; // current congestion window (*scaled*, not pre-scaling) + uint32 window; // current advertised window (*scaled*, not pre-scaling) int window_scale; // from the TCP option uint32 window_ack_seq; // at which ack_seq number did we record 'window' uint32 window_seq; // at which sending sequence number did we record 'window' @@ -225,6 +235,11 @@ protected: uint32 last_seq, ack_seq; // in host order uint32 seq_wraps, ack_wraps; // Number of times 32-bit TCP sequence space // has wrapped around (overflowed). + + // Performance history accounting. + uint32 chk_cnt, chk_thresh; + uint32 rxmt_cnt, rxmt_thresh; + uint32 win0_cnt, win0_thresh; }; #define ENDIAN_UNKNOWN 0 diff --git a/src/analyzer/protocol/tcp/events.bif b/src/analyzer/protocol/tcp/events.bif index 5cf2710804..d93ebe4819 100644 --- a/src/analyzer/protocol/tcp/events.bif +++ b/src/analyzer/protocol/tcp/events.bif @@ -290,6 +290,43 @@ event tcp_contents%(c: connection, is_orig: bool, seq: count, contents: string%) ## TODO. event tcp_rexmit%(c: connection, is_orig: bool, seq: count, len: count, data_in_flight: count, window: count%); +## Generated if a TCP flow crosses a checksum-error threshold, per +## 'C'/'c' history reporting. +## +## c: The connection record for the TCP connection. +## +## is_orig: True if the event is raised for the originator side. +## +## threshold: the threshold that was crossed +## +## .. bro:see:: udp_multiple_checksum_errors +## tcp_multiple_zero_windows tcp_multiple_retransmissions +event tcp_multiple_checksum_errors%(c: connection, is_orig: bool, threshold: count%); + +## Generated if a TCP flow crosses a zero-window threshold, per +## 'W'/'w' history reporting. +## +## c: The connection record for the TCP connection. +## +## is_orig: True if the event is raised for the originator side. +## +## threshold: the threshold that was crossed +## +## .. bro:see:: tcp_multiple_checksum_errors tcp_multiple_retransmissions +event tcp_multiple_zero_windows%(c: connection, is_orig: bool, threshold: count%); + +## Generated if a TCP flow crosses a retransmission threshold, per +## 'T'/'t' history reporting. +## +## c: The connection record for the TCP connection. +## +## is_orig: True if the event is raised for the originator side. +## +## threshold: the threshold that was crossed +## +## .. bro:see:: tcp_multiple_checksum_errors tcp_multiple_zero_windows +event tcp_multiple_retransmissions%(c: connection, is_orig: bool, threshold: count%); + ## Generated when failing to write contents of a TCP stream to a file. ## ## c: The connection whose contents are being recorded. diff --git a/src/analyzer/protocol/udp/UDP.cc b/src/analyzer/protocol/udp/UDP.cc index ca46b88339..d1b798e65e 100644 --- a/src/analyzer/protocol/udp/UDP.cc +++ b/src/analyzer/protocol/udp/UDP.cc @@ -20,6 +20,9 @@ UDP_Analyzer::UDP_Analyzer(Connection* conn) conn->EnableStatusUpdateTimer(); conn->SetInactivityTimeout(udp_inactivity_timeout); request_len = reply_len = -1; // -1 means "haven't seen any activity" + + req_chk_cnt = rep_chk_cnt = 0; + req_chk_thresh = rep_chk_thresh = 1; } UDP_Analyzer::~UDP_Analyzer() @@ -77,9 +80,19 @@ void UDP_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig, Weird("bad_UDP_checksum"); if ( is_orig ) - Conn()->CheckHistory(HIST_ORIG_CORRUPT_PKT, 'C'); + { + uint32 t = req_chk_thresh; + if ( Conn()->ScaledHistoryEntry('C', + req_chk_cnt, req_chk_thresh) ) + ChecksumEvent(is_orig, t); + } else - Conn()->CheckHistory(HIST_RESP_CORRUPT_PKT, 'c'); + { + uint32 t = rep_chk_thresh; + if ( Conn()->ScaledHistoryEntry('c', + rep_chk_cnt, rep_chk_thresh) ) + ChecksumEvent(is_orig, t); + } return; } @@ -209,6 +222,12 @@ unsigned int UDP_Analyzer::MemoryAllocation() const return Analyzer::MemoryAllocation() + padded_sizeof(*this) - 24; } +void UDP_Analyzer::ChecksumEvent(bool is_orig, uint32 threshold) + { + Conn()->HistoryThresholdEvent(udp_multiple_checksum_errors, + is_orig, threshold); + } + bool UDP_Analyzer::ValidateChecksum(const IP_Hdr* ip, const udphdr* up, int len) { uint32 sum; diff --git a/src/analyzer/protocol/udp/UDP.h b/src/analyzer/protocol/udp/UDP.h index 2c7f3ce150..7e07902a7e 100644 --- a/src/analyzer/protocol/udp/UDP.h +++ b/src/analyzer/protocol/udp/UDP.h @@ -31,6 +31,8 @@ protected: bool IsReuse(double t, const u_char* pkt) override; unsigned int MemoryAllocation() const override; + void ChecksumEvent(bool is_orig, uint32 threshold); + // Returns true if the checksum is valid, false if not static bool ValidateChecksum(const IP_Hdr* ip, const struct udphdr* up, int len); @@ -44,6 +46,10 @@ private: #define HIST_RESP_DATA_PKT 0x2 #define HIST_ORIG_CORRUPT_PKT 0x4 #define HIST_RESP_CORRUPT_PKT 0x8 + + // For tracking checksum history. + uint32 req_chk_cnt, req_chk_thresh; + uint32 rep_chk_cnt, rep_chk_thresh; }; } } // namespace analyzer::* diff --git a/src/analyzer/protocol/udp/events.bif b/src/analyzer/protocol/udp/events.bif index 394181cf5d..afcace330b 100644 --- a/src/analyzer/protocol/udp/events.bif +++ b/src/analyzer/protocol/udp/events.bif @@ -36,3 +36,16 @@ event udp_reply%(u: connection%); ## udp_content_deliver_all_orig udp_content_deliver_all_resp ## udp_content_delivery_ports_orig udp_content_delivery_ports_resp event udp_contents%(u: connection, is_orig: bool, contents: string%); + +## Generated if a UDP flow crosses a checksum-error threshold, per +## 'C'/'c' history reporting. +## +## u: The connection record for the corresponding UDP flow. +## +## is_orig: True if the event is raised for the originator side. +## +## threshold: the threshold that was crossed +## +## .. bro:see:: udp_reply udp_request udp_session_done +## tcp_multiple_checksum_errors +event udp_multiple_checksum_errors%(u: connection, is_orig: bool, threshold: count%); From 187757f3770c229d89e08d58aa9c5294ad8224f3 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Mon, 9 Jul 2018 13:05:50 -0700 Subject: [PATCH 07/43] a different sort of history update --- doc/intro/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/intro/index.rst b/doc/intro/index.rst index cf448a0c84..b58a4dbb5b 100644 --- a/doc/intro/index.rst +++ b/doc/intro/index.rst @@ -169,7 +169,7 @@ History Bro's history goes back much further than many people realize. `Vern Paxson `_ designed and implemented the -initial version almost two decades ago. +initial version more than two decades ago. Vern began work on the code in 1995 as a researcher at the `Lawrence Berkeley National Laboratory (LBNL) `_. Berkeley Lab began operational deployment in 1996, and the USENIX Security From f4728bd60365781f6351e417f19b8ebf29ec5ae2 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Tue, 17 Jul 2018 10:25:45 -0700 Subject: [PATCH 08/43] only generate history threshold events for > 1 instance mention those events in NEWS --- NEWS | 9 +++++++++ src/Conn.cc | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/NEWS b/NEWS index 31f144cddd..239b53af91 100644 --- a/NEWS +++ b/NEWS @@ -265,6 +265,15 @@ New Functionality Thus a connection with 2 C's in its history means that the originator sent >= 10 packets with checksum errors; 3 C's means >= 100, etc. +- The above behaviors occurring multiple times (i.e., starting at + 10 instances, than again for 100 instances, etc.) generate corresponding + events: tcp_multiple_checksum_errors, udp_multiple_checksum_errors, + tcp_multiple_zero_windows, and tcp_multiple_retransmissions. Each + has the same form, e.g. + + event tcp_multiple_retransmissions(c: connection, is_orig: bool, + threshold: count); + Changed Functionality --------------------- diff --git a/src/Conn.cc b/src/Conn.cc index 7f9dd862b9..c90ddd61c2 100644 --- a/src/Conn.cc +++ b/src/Conn.cc @@ -320,6 +320,11 @@ void Connection::HistoryThresholdEvent(EventHandlerPtr e, bool is_orig, if ( ! e ) return; + if ( threshold == 1 ) + // This will be far and away the most common case, + // and at this stage it's not a *multiple* instance. + return; + val_list* vl = new val_list; vl->append(BuildConnVal()); vl->append(new Val(is_orig, TYPE_BOOL)); From 86cd484759d09f7fea89e8f0281e90bcbe24f8df Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Fri, 20 Jul 2018 08:57:37 -0700 Subject: [PATCH 09/43] documentation, test suite update --- NEWS | 9 +++++ doc/script-reference/types.rst | 9 +++++ testing/btest/Baseline/language.set/out | 27 +++++++++++++++ testing/btest/language/set.bro | 45 +++++++++++++++++++++++++ 4 files changed, 90 insertions(+) diff --git a/NEWS b/NEWS index 93a28cb200..f66cdd617e 100644 --- a/NEWS +++ b/NEWS @@ -249,6 +249,15 @@ New Functionality '^' are binary "and", "or" and "xor" operators, and '~' is a unary ones-complement operator. +- Added support for set union, intersection, difference, and comparison + operations. The corresponding operators for the first three are + "s1 | s2", "s1 & s2", and "s1 - s2". Relationals are in terms + of subsets, so "s1 < s2" yields true if s1 is a proper subset of s2 + and "s1 == s2" if the two sets have exactly the same elements. + "s1 <= s2" holds for subsets or equality, and similarly "s1 != s2", + "s1 > s2", and "s1 >= s2" have the expected meanings in terms + of non-equality, proper superset, and superset-or-equal. + Changed Functionality --------------------- diff --git a/doc/script-reference/types.rst b/doc/script-reference/types.rst index 44dcbbdfb8..5459ae8514 100644 --- a/doc/script-reference/types.rst +++ b/doc/script-reference/types.rst @@ -526,6 +526,15 @@ Here is a more detailed description of each type: |s| + You can compute the union, intersection, or difference of two sets + using the ``|``, ``&``, and ``-`` operators. You can compare + sets for equality (they have exactly the same elements) using ``==``. + The ``<`` operator returns ``T`` if the lefthand operand is a proper + subset of the righthand operand. Similarly, ``<=`` returns ``T`` + if the lefthand operator is a subset (not necessarily proper, i.e., + it may be equal to the righthand operand). The operators ``!=``, ``>`` + and ``>=`` provide the expected complementary operations. + See the :bro:keyword:`for` statement for info on how to iterate over the elements in a set. diff --git a/testing/btest/Baseline/language.set/out b/testing/btest/Baseline/language.set/out index fc157cf7d9..0128420cbf 100644 --- a/testing/btest/Baseline/language.set/out +++ b/testing/btest/Baseline/language.set/out @@ -42,3 +42,30 @@ remove element (PASS) !in operator (PASS) remove element (PASS) !in operator (PASS) +union (PASS) +intersection (FAIL) +difference (PASS) +difference (PASS) +union/inter. (PASS) +relational (PASS) +relational (PASS) +subset (FAIL) +subset (FAIL) +subset (PASS) +superset (FAIL) +superset (FAIL) +superset (FAIL) +superset (PASS) +non-ordering (FAIL) +non-ordering (PASS) +superset (PASS) +superset (FAIL) +superset (PASS) +superset (PASS) +superset (PASS) +superset (FAIL) +equality (PASS) +equality (FAIL) +non-equality (PASS) +equality (FAIL) +magnitude (FAIL) diff --git a/testing/btest/language/set.bro b/testing/btest/language/set.bro index d1eef7e6f0..56cd649b49 100644 --- a/testing/btest/language/set.bro +++ b/testing/btest/language/set.bro @@ -136,5 +136,50 @@ event bro_init() delete sg3["curly"]; test_case( "remove element", |sg3| == 3 ); test_case( "!in operator", "curly" !in sg3 ); + + + local a = set(1,5,7,9,8,14); + local b = set(1,7,9,2); + + local a_plus_b = set(1,2,5,7,9,8,14); + local a_also_b = set(1,7,9); + local a_sans_b = set(5,8,14); + local b_sans_a = set(2); + + local a_or_b = a | b; + local a_and_b = a & b; + + test_case( "union", a_or_b == a_plus_b ); + test_case( "intersection", a_and_b == a_plus_b ); + test_case( "difference", a - b == a_sans_b ); + test_case( "difference", b - a == b_sans_a ); + + test_case( "union/inter.", |b & set(1,7,9,2)| == |b | set(1,7,2,9)| ); + test_case( "relational", |b & a_or_b| == |b| && |b| < |a_or_b| ); + test_case( "relational", b < a_or_b && a < a_or_b && a_or_b > a_and_b ); + + test_case( "subset", b < a ); + test_case( "subset", a < b ); + test_case( "subset", b < (a | set(2)) ); + test_case( "superset", b > a ); + test_case( "superset", b > (a | set(2)) ); + test_case( "superset", b | set(8, 14, 5) > (a | set(2)) ); + test_case( "superset", b | set(8, 14, 99, 5) > (a | set(2)) ); + + test_case( "non-ordering", (a <= b) || (a >= b) ); + test_case( "non-ordering", (a <= a_or_b) && (a_or_b >= b) ); + + test_case( "superset", (b | set(14, 5)) > a - set(8) ); + test_case( "superset", (b | set(14)) > a - set(8) ); + test_case( "superset", (b | set(14)) > a - set(8,5) ); + test_case( "superset", b >= a - set(5,8,14) ); + test_case( "superset", b > a - set(5,8,14) ); + test_case( "superset", (b - set(2)) > a - set(5,8,14) ); + test_case( "equality", a == a | set(5) ); + test_case( "equality", a == a | set(5,11) ); + test_case( "non-equality", a != a | set(5,11) ); + test_case( "equality", a == a | set(5,11) ); + + test_case( "magnitude", |a_and_b| == |a_or_b|); } From 4ca4b0504350fa3546726a4732bfb60f450af30f Mon Sep 17 00:00:00 2001 From: Chung Min Kim Date: Tue, 24 Jul 2018 13:19:14 -0700 Subject: [PATCH 10/43] Refactoring, making error messages nicer, & lcov Directory name for bro core coverage changed to "coverage", error messages made nicer. Use `make html` in testing/coverage to create logs in HTML format when lcov exists on the system. --- .gitignore | 1 + testing/Makefile | 4 ++-- .../{bro-core-coverage => coverage}/Makefile | 6 ++++++ testing/coverage/README | 13 ++++++++++++ .../code_coverage.sh | 20 ++++++++++++------- 5 files changed, 35 insertions(+), 9 deletions(-) rename testing/{bro-core-coverage => coverage}/Makefile (52%) create mode 100644 testing/coverage/README rename testing/{bro-core-coverage => coverage}/code_coverage.sh (90%) diff --git a/.gitignore b/.gitignore index d59a62b7e1..fa397f98d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build tmp +*.gcov diff --git a/testing/Makefile b/testing/Makefile index e34aaca939..c5610fc97b 100644 --- a/testing/Makefile +++ b/testing/Makefile @@ -8,7 +8,7 @@ brief: make-brief coverage distclean: @rm -f coverage.log $(MAKE) -C btest $@ - $(MAKE) -C bro-core-coverage $@ + $(MAKE) -C coverage $@ make-verbose: @for repo in $(DIRS); do (cd $$repo && make -s ); done @@ -23,5 +23,5 @@ coverage: @echo "Complete test suite code coverage:" @./scripts/coverage-calc "brocov.tmp.*" coverage.log `pwd`/../scripts @rm -f brocov.tmp.* - @cd bro-core-coverage && make coverage + @cd coverage && make coverage diff --git a/testing/bro-core-coverage/Makefile b/testing/coverage/Makefile similarity index 52% rename from testing/bro-core-coverage/Makefile rename to testing/coverage/Makefile index e7db4894fd..1aa8036bbf 100644 --- a/testing/bro-core-coverage/Makefile +++ b/testing/coverage/Makefile @@ -7,3 +7,9 @@ cleanup: distclean: cleanup @find ../../ -name "*.gcno" -exec rm {} \; + +html: + @(cd ../../; if which lcov; then\ + lcov --capture --directory . --output-file coverage.info \ + && genhtml coverage.info && rm coverage.info; \ + fi) diff --git a/testing/coverage/README b/testing/coverage/README new file mode 100644 index 0000000000..0c19e14d51 --- /dev/null +++ b/testing/coverage/README @@ -0,0 +1,13 @@ +On a Bro build configured with --enable-coverage, this script produces a code coverage report after Bro has been invoked. The intended application of this script is after the btest testsuite has run. This combination (btests first, coverage computation afterward) happens automatically when running "make" in the testing directory. This script puts .gcov files (which are included in .gitignore) alongside the corresponding source files. + +This depends on gcov, which should come with your gcc. If gcov is not installed, the script will abort with an error message. + +TODO: Use `make html` as make target in this directory to output the html files that gcov can create (must have lcov on system). + +The goal of code-coverage.sh script is to automate code coverage testing. See the following steps for the code structure: + 1. Run test suite + 2. Check for .gcda files existing. + 3a. Run gcov (-p to preserve path) + 3b. Prune .gcov files for objects outside of the Bro tree + 4a. Analyze .gcov files generated and create summary file + 4b. Send .gcov files to appropriate path diff --git a/testing/bro-core-coverage/code_coverage.sh b/testing/coverage/code_coverage.sh similarity index 90% rename from testing/bro-core-coverage/code_coverage.sh rename to testing/coverage/code_coverage.sh index b1eb8c599a..ec583b1e5e 100755 --- a/testing/bro-core-coverage/code_coverage.sh +++ b/testing/coverage/code_coverage.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # On a Bro build configured with --enable-coverage, this script # produces a code coverage report after Bro has been invoked. The @@ -17,7 +17,7 @@ # 4b. Send .gcov files to appropriate path # CURR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Location of script -BASE="$(realpath "${CURR}/../../")" +BASE="$(readlink -f "${CURR}/../../")" TMP="${CURR}/tmp.$$" mkdir -p $TMP @@ -96,14 +96,20 @@ done echo "ok" # 3a. Run gcov (-p to preserve path) and move into tmp directory +# ... if system does not have gcov installed, exit with message. echo -n "Creating coverage files... " -( cd "$TMP" && find "$BASE" -name "*.o" -exec gcov -p {} > /dev/null 2>&1 \; ) -NUM_GCOVS=$(ls "$TMP"/*.gcov | wc -l) -if [ $NUM_GCOVS -eq 0 ]; then - echo "no gcov files produced, aborting" +if which gcov; then + ( cd "$TMP" && find "$BASE" -name "*.o" -exec gcov -p {} > /dev/null 2>&1 \; ) + NUM_GCOVS=$(find "$TMP" -name *.gcov | wc -l) + if [ $NUM_GCOVS -eq 0 ]; then + echo "no gcov files produced, aborting" + exit 1 + fi + echo "ok, $NUM_GCOVS coverage files" +else + echo "gcov is not installed on system, aborting" exit 1 fi -echo "ok, $NUM_GCOVS coverage files" # 3b. Prune gcov files that fall outside of the Bro tree: # Look for files containing gcov's slash substitution character "#" From e31563069b6bdab1dceaa0490f1ad6b89556b267 Mon Sep 17 00:00:00 2001 From: Zhongjie Wang Date: Tue, 24 Jul 2018 14:07:12 -0700 Subject: [PATCH 11/43] Added missing tcp-state for signature dpd_rfb_server --- scripts/base/protocols/rfb/dpd.sig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/base/protocols/rfb/dpd.sig b/scripts/base/protocols/rfb/dpd.sig index 40793ad590..c105070b24 100644 --- a/scripts/base/protocols/rfb/dpd.sig +++ b/scripts/base/protocols/rfb/dpd.sig @@ -1,6 +1,7 @@ signature dpd_rfb_server { ip-proto == tcp payload /^RFB/ + tcp-state responder requires-reverse-signature dpd_rfb_client enable "rfb" } @@ -9,4 +10,4 @@ signature dpd_rfb_client { ip-proto == tcp payload /^RFB/ tcp-state originator -} \ No newline at end of file +} From dfe0768fa1e9f23f2cca09bce6ad79525c18e993 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Thu, 26 Jul 2018 12:18:31 -0700 Subject: [PATCH 12/43] v += e implemented --- src/Expr.cc | 37 ++++++++++++++++++++++++++++++++++++- src/Val.h | 2 -- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/Expr.cc b/src/Expr.cc index a86f86b9c4..8856fb3a4b 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -1390,7 +1390,8 @@ bool AddExpr::DoUnserialize(UnserialInfo* info) } AddToExpr::AddToExpr(Expr* arg_op1, Expr* arg_op2) -: BinaryExpr(EXPR_ADD_TO, arg_op1->MakeLvalue(), arg_op2) +: BinaryExpr(EXPR_ADD_TO, + is_vector(arg_op1) ? arg_op1 : arg_op1->MakeLvalue(), arg_op2) { if ( IsError() ) return; @@ -1404,6 +1405,32 @@ AddToExpr::AddToExpr(Expr* arg_op1, Expr* arg_op2) SetType(base_type(bt1)); else if ( BothInterval(bt1, bt2) ) SetType(base_type(bt1)); + + else if ( IsVector(bt1) ) + { + bt1 = op1->Type()->AsVectorType()->YieldType()->Tag(); + + if ( IsArithmetic(bt1) ) + { + if ( IsArithmetic(bt2) ) + { + if ( bt2 != bt1 ) + op2 = new ArithCoerceExpr(op2, bt1); + + SetType(op1->Type()->Ref()); + } + + else + ExprError("appending non-arithmetic to arithmetic vector"); + } + + else if ( bt1 != bt2 ) + ExprError("incompatible vector append"); + + else + SetType(op1->Type()->Ref()); + } + else ExprError("requires two arithmetic or two string operands"); } @@ -1421,6 +1448,14 @@ Val* AddToExpr::Eval(Frame* f) const return 0; } + if ( is_vector(v1) ) + { + VectorVal* vv = v1->AsVectorVal(); + if ( ! vv->Assign(vv->Size(), v2) ) + reporter->Error("type-checking failed in vector append"); + return v1; + } + Val* result = Fold(v1, v2); Unref(v1); Unref(v2); diff --git a/src/Val.h b/src/Val.h index 771ed40dd1..a050c5d4fe 100644 --- a/src/Val.h +++ b/src/Val.h @@ -1015,8 +1015,6 @@ public: // Returns false if the type of the argument was wrong. // The vector will automatically grow to accomodate the index. - // 'assigner" is the expression that is doing the assignment; - // it's just used for pinpointing errors. // // Note: does NOT Ref() the element! Remember to do so unless // the element was just created and thus has refcount 1. From 016a164bb68355332d08701ae971a7eb3830433c Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Thu, 26 Jul 2018 12:29:50 -0700 Subject: [PATCH 13/43] documentation of v += e --- NEWS | 3 +++ doc/script-reference/types.rst | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/NEWS b/NEWS index 3cb0fd79b3..f160ec06b1 100644 --- a/NEWS +++ b/NEWS @@ -277,6 +277,9 @@ New Functionality - Bro now supports PPPoE over QinQ. +- An expression of the form v += e will append the value of the expression + e to the end of the vector v (of course assuming type-compatbility). + Changed Functionality --------------------- diff --git a/doc/script-reference/types.rst b/doc/script-reference/types.rst index f1d81f63ab..c3bacabbf8 100644 --- a/doc/script-reference/types.rst +++ b/doc/script-reference/types.rst @@ -599,6 +599,20 @@ Here is a more detailed description of each type: |v| + A particularly common operation on a vector is to append an element + to its end. You can do so using: + + .. code:: bro + + v += e; + + where if e's type is ``X``, v's type is ``vector of X``. Note that + this expression is equivalent to: + + .. code:: bro + + v[|v|] = e; + Vectors of integral types (``int`` or ``count``) support the pre-increment (``++``) and pre-decrement operators (``--``), which will increment or decrement each element in the vector. From 81c63a0c65aea8c0dd98d3ce9e6c36c793bc4f58 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Thu, 26 Jul 2018 12:37:06 -0700 Subject: [PATCH 14/43] test case for v += e --- testing/btest/language/vector.bro | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testing/btest/language/vector.bro b/testing/btest/language/vector.bro index 76fc8b69e3..85bed8eae2 100644 --- a/testing/btest/language/vector.bro +++ b/testing/btest/language/vector.bro @@ -163,5 +163,10 @@ event bro_init() test_case( "&& operator", v14[0] == F && v14[1] == F && v14[2] == T ); test_case( "|| operator", v15[0] == T && v15[1] == F && v15[2] == T ); + # Test += operator. + local v16 = v6; + v16 += 40; + test_case( "+= operator", all_set(v16 == vector( 10, 20, 30, 40 )) ); + } From 88fd7510c66980836fa37ab619d65ac3daa0d902 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Thu, 26 Jul 2018 12:51:36 -0700 Subject: [PATCH 15/43] reap the fruits of v += e --- .../data_struct_vector_declaration.bro | 8 +-- scripts/base/files/pe/main.bro | 2 +- scripts/base/files/x509/main.bro | 2 +- scripts/base/frameworks/cluster/main.bro | 4 +- scripts/base/frameworks/cluster/pools.bro | 8 +-- scripts/base/frameworks/config/main.bro | 4 +- scripts/base/frameworks/netcontrol/main.bro | 12 ++--- .../netcontrol/plugins/openflow.bro | 14 ++--- .../base/frameworks/openflow/plugins/ryu.bro | 2 +- scripts/base/frameworks/sumstats/main.bro | 6 +-- .../base/frameworks/sumstats/non-cluster.bro | 2 +- .../frameworks/sumstats/plugins/sample.bro | 2 +- scripts/base/protocols/dhcp/main.bro | 2 +- scripts/base/protocols/dns/main.bro | 4 +- scripts/base/protocols/http/entities.bro | 12 ++--- scripts/base/protocols/http/utils.bro | 2 +- scripts/base/protocols/sip/main.bro | 4 +- scripts/base/protocols/smtp/files.bro | 2 +- scripts/base/protocols/smtp/main.bro | 2 +- scripts/base/protocols/ssl/files.bro | 8 +-- scripts/base/utils/addrs.bro | 4 +- scripts/base/utils/email.bro | 2 +- scripts/base/utils/exec.bro | 6 +-- scripts/base/utils/json.bro | 8 +-- .../notice/extend-email/hostnames.bro | 4 +- scripts/policy/misc/load-balancing.bro | 2 +- scripts/policy/protocols/dhcp/msg-orig.bro | 2 +- .../policy/protocols/http/header-names.bro | 4 +- scripts/policy/protocols/ssl/heartbleed.bro | 54 +++++++++---------- scripts/policy/protocols/ssl/notary.bro | 2 +- scripts/policy/protocols/ssl/validate-sct.bro | 4 +- testing/btest/core/leaks/broker/data.bro | 2 +- testing/btest/language/ipv6-literals.bro | 48 ++++++++--------- .../language/record-default-coercion.bro | 2 +- .../netcontrol/delete-internal-state.bro | 8 +-- .../base/frameworks/netcontrol/multiple.bro | 8 +-- .../frameworks/sumstats/sample-cluster.bro | 2 +- 37 files changed, 132 insertions(+), 132 deletions(-) diff --git a/doc/scripting/data_struct_vector_declaration.bro b/doc/scripting/data_struct_vector_declaration.bro index 6d684d09b1..e9b31880f6 100644 --- a/doc/scripting/data_struct_vector_declaration.bro +++ b/doc/scripting/data_struct_vector_declaration.bro @@ -3,10 +3,10 @@ event bro_init() local v1: vector of count; local v2 = vector(1, 2, 3, 4); - v1[|v1|] = 1; - v1[|v1|] = 2; - v1[|v1|] = 3; - v1[|v1|] = 4; + v1 += 1; + v1 += 2; + v1 += 3; + v1 += 4; print fmt("contents of v1: %s", v1); print fmt("length of v1: %d", |v1|); diff --git a/scripts/base/files/pe/main.bro b/scripts/base/files/pe/main.bro index b2723e4138..972e8a31c8 100644 --- a/scripts/base/files/pe/main.bro +++ b/scripts/base/files/pe/main.bro @@ -126,7 +126,7 @@ event pe_section_header(f: fa_file, h: PE::SectionHeader) &priority=5 if ( ! f$pe?$section_names ) f$pe$section_names = vector(); - f$pe$section_names[|f$pe$section_names|] = h$name; + f$pe$section_names += h$name; } event file_state_remove(f: fa_file) &priority=-5 diff --git a/scripts/base/files/x509/main.bro b/scripts/base/files/x509/main.bro index 30f60f1362..b6fdde5494 100644 --- a/scripts/base/files/x509/main.bro +++ b/scripts/base/files/x509/main.bro @@ -66,7 +66,7 @@ event x509_certificate(f: fa_file, cert_ref: opaque of x509, cert: X509::Certifi event x509_extension(f: fa_file, ext: X509::Extension) &priority=5 { if ( f$info?$x509 ) - f$info$x509$extensions[|f$info$x509$extensions|] = ext; + f$info$x509$extensions += ext; } event x509_ext_basic_constraints(f: fa_file, ext: X509::BasicConstraints) &priority=5 diff --git a/scripts/base/frameworks/cluster/main.bro b/scripts/base/frameworks/cluster/main.bro index 8eb4bf90bf..779f0c159a 100644 --- a/scripts/base/frameworks/cluster/main.bro +++ b/scripts/base/frameworks/cluster/main.bro @@ -251,7 +251,7 @@ function nodes_with_type(node_type: NodeType): vector of NamedNode local names: vector of string = vector(); for ( name in Cluster::nodes ) - names[|names|] = name; + names += name; names = sort(names, strcmp); @@ -263,7 +263,7 @@ function nodes_with_type(node_type: NodeType): vector of NamedNode if ( n$node_type != node_type ) next; - rval[|rval|] = NamedNode($name=name, $node=n); + rval += NamedNode($name=name, $node=n); } return rval; diff --git a/scripts/base/frameworks/cluster/pools.bro b/scripts/base/frameworks/cluster/pools.bro index fb45594adc..ac8673b7e8 100644 --- a/scripts/base/frameworks/cluster/pools.bro +++ b/scripts/base/frameworks/cluster/pools.bro @@ -157,7 +157,7 @@ global registered_pools: vector of Pool = vector(); function register_pool(spec: PoolSpec): Pool { local rval = Pool($spec = spec); - registered_pools[|registered_pools|] = rval; + registered_pools += rval; return rval; } @@ -276,7 +276,7 @@ function init_pool_node(pool: Pool, name: string): bool local pn = PoolNode($name=name, $alias=alias, $site_id=site_id, $alive=Cluster::node == name); pool$nodes[name] = pn; - pool$node_list[|pool$node_list|] = pn; + pool$node_list += pn; if ( pn$alive ) ++pool$alive_count; @@ -366,7 +366,7 @@ event bro_init() &priority=-5 if ( |mgr| > 0 ) { local eln = pool_eligibility[Cluster::LOGGER]$eligible_nodes; - eln[|eln|] = mgr[0]; + eln += mgr[0]; } } @@ -423,7 +423,7 @@ event bro_init() &priority=-5 if ( j < e ) next; - nen[|nen|] = pet$eligible_nodes[j]; + nen += pet$eligible_nodes[j]; } pet$eligible_nodes = nen; diff --git a/scripts/base/frameworks/config/main.bro b/scripts/base/frameworks/config/main.bro index b83da42785..6268ee4a8d 100644 --- a/scripts/base/frameworks/config/main.bro +++ b/scripts/base/frameworks/config/main.bro @@ -120,14 +120,14 @@ function format_value(value: any) : string { local it: set[bool] = value; for ( sv in it ) - part[|part|] = cat(sv); + part += cat(sv); return join_string_vec(part, ","); } else if ( /^vector/ in tn ) { local vit: vector of any = value; for ( i in vit ) - part[|part|] = cat(vit[i]); + part += cat(vit[i]); return join_string_vec(part, ","); } else if ( tn == "string" ) diff --git a/scripts/base/frameworks/netcontrol/main.bro b/scripts/base/frameworks/netcontrol/main.bro index 3e9b35fa8c..a9418508af 100644 --- a/scripts/base/frameworks/netcontrol/main.bro +++ b/scripts/base/frameworks/netcontrol/main.bro @@ -555,19 +555,19 @@ function quarantine_host(infected: addr, dns: addr, quarantine: addr, t: interva local orules: vector of string = vector(); local edrop: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected))]; local rdrop: Rule = [$ty=DROP, $target=FORWARD, $entity=edrop, $expire=t, $location=location]; - orules[|orules|] = add_rule(rdrop); + orules += add_rule(rdrop); local todnse: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected), $dst_h=addr_to_subnet(dns), $dst_p=53/udp)]; local todnsr = Rule($ty=MODIFY, $target=FORWARD, $entity=todnse, $expire=t, $location=location, $mod=FlowMod($dst_h=quarantine), $priority=+5); - orules[|orules|] = add_rule(todnsr); + orules += add_rule(todnsr); local fromdnse: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(dns), $src_p=53/udp, $dst_h=addr_to_subnet(infected))]; local fromdnsr = Rule($ty=MODIFY, $target=FORWARD, $entity=fromdnse, $expire=t, $location=location, $mod=FlowMod($src_h=dns), $priority=+5); - orules[|orules|] = add_rule(fromdnsr); + orules += add_rule(fromdnsr); local wle: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected), $dst_h=addr_to_subnet(quarantine), $dst_p=80/tcp)]; local wlr = Rule($ty=WHITELIST, $target=FORWARD, $entity=wle, $expire=t, $location=location, $priority=+5); - orules[|orules|] = add_rule(wlr); + orules += add_rule(wlr); return orules; } @@ -637,7 +637,7 @@ event NetControl::init() &priority=-20 function activate_impl(p: PluginState, priority: int) { p$_priority = priority; - plugins[|plugins|] = p; + plugins += p; sort(plugins, function(p1: PluginState, p2: PluginState) : int { return p2$_priority - p1$_priority; }); plugin_ids[plugin_counter] = p; @@ -734,7 +734,7 @@ function find_rules_subnet(sn: subnet) : vector of Rule for ( rule_id in rules_by_subnets[sn_entry] ) { if ( rule_id in rules ) - ret[|ret|] = rules[rule_id]; + ret += rules[rule_id]; else Reporter::error("find_rules_subnet - internal data structure error, missing rule"); } diff --git a/scripts/base/frameworks/netcontrol/plugins/openflow.bro b/scripts/base/frameworks/netcontrol/plugins/openflow.bro index c528a1ba3e..f1403a70a8 100644 --- a/scripts/base/frameworks/netcontrol/plugins/openflow.bro +++ b/scripts/base/frameworks/netcontrol/plugins/openflow.bro @@ -158,17 +158,17 @@ function entity_to_match(p: PluginState, e: Entity): vector of OpenFlow::ofp_mat if ( e$ty == CONNECTION ) { - v[|v|] = OpenFlow::match_conn(e$conn); # forward and... - v[|v|] = OpenFlow::match_conn(e$conn, T); # reverse + v += OpenFlow::match_conn(e$conn); # forward and... + v += OpenFlow::match_conn(e$conn, T); # reverse return openflow_match_pred(p, e, v); } if ( e$ty == MAC ) { - v[|v|] = OpenFlow::ofp_match( + v += OpenFlow::ofp_match( $dl_src=e$mac ); - v[|v|] = OpenFlow::ofp_match( + v += OpenFlow::ofp_match( $dl_dst=e$mac ); @@ -182,12 +182,12 @@ function entity_to_match(p: PluginState, e: Entity): vector of OpenFlow::ofp_mat if ( is_v6_subnet(e$ip) ) dl_type = OpenFlow::ETH_IPv6; - v[|v|] = OpenFlow::ofp_match( + v += OpenFlow::ofp_match( $dl_type=dl_type, $nw_src=e$ip ); - v[|v|] = OpenFlow::ofp_match( + v += OpenFlow::ofp_match( $dl_type=dl_type, $nw_dst=e$ip ); @@ -231,7 +231,7 @@ function entity_to_match(p: PluginState, e: Entity): vector of OpenFlow::ofp_mat m$tp_dst = port_to_count(f$dst_p); } - v[|v|] = m; + v += m; return openflow_match_pred(p, e, v); } diff --git a/scripts/base/frameworks/openflow/plugins/ryu.bro b/scripts/base/frameworks/openflow/plugins/ryu.bro index f022fe0f03..cc400293a0 100644 --- a/scripts/base/frameworks/openflow/plugins/ryu.bro +++ b/scripts/base/frameworks/openflow/plugins/ryu.bro @@ -88,7 +88,7 @@ function ryu_flow_mod(state: OpenFlow::ControllerState, match: ofp_match, flow_m local flow_actions: vector of ryu_flow_action = vector(); for ( i in flow_mod$actions$out_ports ) - flow_actions[|flow_actions|] = ryu_flow_action($_type="OUTPUT", $_port=flow_mod$actions$out_ports[i]); + flow_actions += ryu_flow_action($_type="OUTPUT", $_port=flow_mod$actions$out_ports[i]); # Generate our ryu_flow_mod record for the ReST API call. local mod: ryu_ofp_flow_mod = ryu_ofp_flow_mod( diff --git a/scripts/base/frameworks/sumstats/main.bro b/scripts/base/frameworks/sumstats/main.bro index edd80ede0f..f704dbcdd2 100644 --- a/scripts/base/frameworks/sumstats/main.bro +++ b/scripts/base/frameworks/sumstats/main.bro @@ -267,7 +267,7 @@ function add_observe_plugin_dependency(calc: Calculation, depends_on: Calculatio { if ( calc !in calc_deps ) calc_deps[calc] = vector(); - calc_deps[calc][|calc_deps[calc]|] = depends_on; + calc_deps[calc] += depends_on; } event bro_init() &priority=100000 @@ -348,7 +348,7 @@ function add_calc_deps(calcs: vector of Calculation, c: Calculation) { if ( calc_deps[c][i] in calc_deps ) add_calc_deps(calcs, calc_deps[c][i]); - calcs[|c|] = calc_deps[c][i]; + calcs += calc_deps[c][i]; #print fmt("add dep for %s [%s] ", c, calc_deps[c][i]); } } @@ -387,7 +387,7 @@ function create(ss: SumStat) skip_calc=T; } if ( ! skip_calc ) - reducer$calc_funcs[|reducer$calc_funcs|] = calc; + reducer$calc_funcs += calc; } if ( reducer$stream !in reducer_store ) diff --git a/scripts/base/frameworks/sumstats/non-cluster.bro b/scripts/base/frameworks/sumstats/non-cluster.bro index 57785a03b2..100e8dad4a 100644 --- a/scripts/base/frameworks/sumstats/non-cluster.bro +++ b/scripts/base/frameworks/sumstats/non-cluster.bro @@ -11,7 +11,7 @@ event SumStats::process_epoch_result(ss: SumStat, now: time, data: ResultTable) for ( key in data ) { ss$epoch_result(now, key, data[key]); - keys_to_delete[|keys_to_delete|] = key; + keys_to_delete += key; if ( --i == 0 ) break; diff --git a/scripts/base/frameworks/sumstats/plugins/sample.bro b/scripts/base/frameworks/sumstats/plugins/sample.bro index 0200e85949..2f96c5eb30 100644 --- a/scripts/base/frameworks/sumstats/plugins/sample.bro +++ b/scripts/base/frameworks/sumstats/plugins/sample.bro @@ -43,7 +43,7 @@ function sample_add_sample(obs:Observation, rv: ResultVal) ++rv$sample_elements; if ( |rv$samples| < rv$num_samples ) - rv$samples[|rv$samples|] = obs; + rv$samples += obs; else { local ra = rand(rv$sample_elements); diff --git a/scripts/base/protocols/dhcp/main.bro b/scripts/base/protocols/dhcp/main.bro index 5e33d269f3..ae102e6085 100644 --- a/scripts/base/protocols/dhcp/main.bro +++ b/scripts/base/protocols/dhcp/main.bro @@ -178,7 +178,7 @@ event DHCP::aggregate_msgs(ts: time, id: conn_id, uid: string, is_orig: bool, ms if ( uid !in log_info$uids ) add log_info$uids[uid]; - log_info$msg_types[|log_info$msg_types|] = DHCP::message_types[msg$m_type]; + log_info$msg_types += DHCP::message_types[msg$m_type]; # Let's watch for messages in any DHCP message type # and split them out based on client and server. diff --git a/scripts/base/protocols/dns/main.bro b/scripts/base/protocols/dns/main.bro index a8946e871e..127a06b5a0 100644 --- a/scripts/base/protocols/dns/main.bro +++ b/scripts/base/protocols/dns/main.bro @@ -324,11 +324,11 @@ hook DNS::do_reply(c: connection, msg: dns_msg, ans: dns_answer, reply: string) { if ( ! c$dns?$answers ) c$dns$answers = vector(); - c$dns$answers[|c$dns$answers|] = reply; + c$dns$answers += reply; if ( ! c$dns?$TTLs ) c$dns$TTLs = vector(); - c$dns$TTLs[|c$dns$TTLs|] = ans$TTL; + c$dns$TTLs += ans$TTL; } } } diff --git a/scripts/base/protocols/http/entities.bro b/scripts/base/protocols/http/entities.bro index bec89b536d..3670d7879a 100644 --- a/scripts/base/protocols/http/entities.bro +++ b/scripts/base/protocols/http/entities.bro @@ -87,14 +87,14 @@ event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priori if ( ! c$http?$orig_fuids ) c$http$orig_fuids = string_vec(f$id); else - c$http$orig_fuids[|c$http$orig_fuids|] = f$id; + c$http$orig_fuids += f$id; if ( f$info?$filename ) { if ( ! c$http?$orig_filenames ) c$http$orig_filenames = string_vec(f$info$filename); else - c$http$orig_filenames[|c$http$orig_filenames|] = f$info$filename; + c$http$orig_filenames += f$info$filename; } } @@ -103,14 +103,14 @@ event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priori if ( ! c$http?$resp_fuids ) c$http$resp_fuids = string_vec(f$id); else - c$http$resp_fuids[|c$http$resp_fuids|] = f$id; + c$http$resp_fuids += f$id; if ( f$info?$filename ) { if ( ! c$http?$resp_filenames ) c$http$resp_filenames = string_vec(f$info$filename); else - c$http$resp_filenames[|c$http$resp_filenames|] = f$info$filename; + c$http$resp_filenames += f$info$filename; } } @@ -130,14 +130,14 @@ event file_sniff(f: fa_file, meta: fa_metadata) &priority=5 if ( ! f$http?$orig_mime_types ) f$http$orig_mime_types = string_vec(meta$mime_type); else - f$http$orig_mime_types[|f$http$orig_mime_types|] = meta$mime_type; + f$http$orig_mime_types += meta$mime_type; } else { if ( ! f$http?$resp_mime_types ) f$http$resp_mime_types = string_vec(meta$mime_type); else - f$http$resp_mime_types[|f$http$resp_mime_types|] = meta$mime_type; + f$http$resp_mime_types += meta$mime_type; } } diff --git a/scripts/base/protocols/http/utils.bro b/scripts/base/protocols/http/utils.bro index 88549f8404..67f13f2640 100644 --- a/scripts/base/protocols/http/utils.bro +++ b/scripts/base/protocols/http/utils.bro @@ -47,7 +47,7 @@ function extract_keys(data: string, kv_splitter: pattern): string_vec { local key_val = split_string1(parts[part_index], /=/); if ( 0 in key_val ) - key_vec[|key_vec|] = key_val[0]; + key_vec += key_val[0]; } return key_vec; } diff --git a/scripts/base/protocols/sip/main.bro b/scripts/base/protocols/sip/main.bro index f629049928..f4dba22876 100644 --- a/scripts/base/protocols/sip/main.bro +++ b/scripts/base/protocols/sip/main.bro @@ -226,7 +226,7 @@ event sip_header(c: connection, is_request: bool, name: string, value: string) & c$sip$user_agent = value; break; case "VIA", "V": - c$sip$request_path[|c$sip$request_path|] = split_string1(value, /;[ ]?branch/)[0]; + c$sip$request_path += split_string1(value, /;[ ]?branch/)[0]; break; } @@ -256,7 +256,7 @@ event sip_header(c: connection, is_request: bool, name: string, value: string) & c$sip$response_to = value; break; case "VIA", "V": - c$sip$response_path[|c$sip$response_path|] = split_string1(value, /;[ ]?branch/)[0]; + c$sip$response_path += split_string1(value, /;[ ]?branch/)[0]; break; } diff --git a/scripts/base/protocols/smtp/files.bro b/scripts/base/protocols/smtp/files.bro index 352c2025a3..a65b90b528 100644 --- a/scripts/base/protocols/smtp/files.bro +++ b/scripts/base/protocols/smtp/files.bro @@ -49,5 +49,5 @@ event bro_init() &priority=5 event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5 { if ( c?$smtp && !c$smtp$tls ) - c$smtp$fuids[|c$smtp$fuids|] = f$id; + c$smtp$fuids += f$id; } diff --git a/scripts/base/protocols/smtp/main.bro b/scripts/base/protocols/smtp/main.bro index cd0e730d8e..18c75a93c0 100644 --- a/scripts/base/protocols/smtp/main.bro +++ b/scripts/base/protocols/smtp/main.bro @@ -295,7 +295,7 @@ event mime_one_header(c: connection, h: mime_header_rec) &priority=3 c$smtp$process_received_from = F; } if ( c$smtp$path[|c$smtp$path|-1] != ip ) - c$smtp$path[|c$smtp$path|] = ip; + c$smtp$path += ip; } event connection_state_remove(c: connection) &priority=-5 diff --git a/scripts/base/protocols/ssl/files.bro b/scripts/base/protocols/ssl/files.bro index 8750645b36..d0d89561e3 100644 --- a/scripts/base/protocols/ssl/files.bro +++ b/scripts/base/protocols/ssl/files.bro @@ -121,13 +121,13 @@ event file_sniff(f: fa_file, meta: fa_metadata) &priority=5 if ( f$is_orig ) { - c$ssl$client_cert_chain[|c$ssl$client_cert_chain|] = f$info; - c$ssl$client_cert_chain_fuids[|c$ssl$client_cert_chain_fuids|] = f$id; + c$ssl$client_cert_chain += f$info; + c$ssl$client_cert_chain_fuids += f$id; } else { - c$ssl$cert_chain[|c$ssl$cert_chain|] = f$info; - c$ssl$cert_chain_fuids[|c$ssl$cert_chain_fuids|] = f$id; + c$ssl$cert_chain += f$info; + c$ssl$cert_chain_fuids += f$id; } } diff --git a/scripts/base/utils/addrs.bro b/scripts/base/utils/addrs.bro index e8fd746e5e..058aabd96e 100644 --- a/scripts/base/utils/addrs.bro +++ b/scripts/base/utils/addrs.bro @@ -100,7 +100,7 @@ function find_ip_addresses(input: string): string_array &deprecated for ( i in parts ) { if ( i % 2 == 1 && is_valid_ip(parts[i]) ) - output[|output|] = parts[i]; + output += parts[i]; } return output; } @@ -118,7 +118,7 @@ function extract_ip_addresses(input: string): string_vec for ( i in parts ) { if ( i % 2 == 1 && is_valid_ip(parts[i]) ) - output[|output|] = parts[i]; + output += parts[i]; } return output; } diff --git a/scripts/base/utils/email.bro b/scripts/base/utils/email.bro index 08e8db8500..4feed351b4 100644 --- a/scripts/base/utils/email.bro +++ b/scripts/base/utils/email.bro @@ -10,7 +10,7 @@ function extract_email_addrs_vec(str: string): string_vec local raw_addrs = find_all(str, /(^|[<,:[:blank:]])[^<,:[:blank:]@]+"@"[^>,;[:blank:]]+([>,;[:blank:]]|$)/); for ( raw_addr in raw_addrs ) - addrs[|addrs|] = gsub(raw_addr, /[<>,:;[:blank:]]/, ""); + addrs += gsub(raw_addr, /[<>,:;[:blank:]]/, ""); return addrs; } diff --git a/scripts/base/utils/exec.bro b/scripts/base/utils/exec.bro index a926775bda..61488a1249 100644 --- a/scripts/base/utils/exec.bro +++ b/scripts/base/utils/exec.bro @@ -69,14 +69,14 @@ event Exec::line(description: Input::EventDescription, tpe: Input::Event, s: str if ( ! result?$stderr ) result$stderr = vector(s); else - result$stderr[|result$stderr|] = s; + result$stderr += s; } else { if ( ! result?$stdout ) result$stdout = vector(s); else - result$stdout[|result$stdout|] = s; + result$stdout += s; } } @@ -93,7 +93,7 @@ event Exec::file_line(description: Input::EventDescription, tpe: Input::Event, s if ( track_file !in result$files ) result$files[track_file] = vector(s); else - result$files[track_file][|result$files[track_file]|] = s; + result$files[track_file] += s; } event Input::end_of_data(orig_name: string, source:string) diff --git a/scripts/base/utils/json.bro b/scripts/base/utils/json.bro index 05dcff1e27..45248e3ea2 100644 --- a/scripts/base/utils/json.bro +++ b/scripts/base/utils/json.bro @@ -66,7 +66,7 @@ function to_json(v: any, only_loggable: bool &default=F, field_escape_pattern: p if ( field_desc?$value && (!only_loggable || field_desc$log) ) { local onepart = cat("\"", field, "\": ", to_json(field_desc$value, only_loggable)); - rec_parts[|rec_parts|] = onepart; + rec_parts += onepart; } } return cat("{", join_string_vec(rec_parts, ", "), "}"); @@ -79,7 +79,7 @@ function to_json(v: any, only_loggable: bool &default=F, field_escape_pattern: p local sa: set[bool] = v; for ( sv in sa ) { - set_parts[|set_parts|] = to_json(sv, only_loggable); + set_parts += to_json(sv, only_loggable); } return cat("[", join_string_vec(set_parts, ", "), "]"); } @@ -91,7 +91,7 @@ function to_json(v: any, only_loggable: bool &default=F, field_escape_pattern: p { local ts = to_json(ti); local if_quotes = (ts[0] == "\"") ? "" : "\""; - tab_parts[|tab_parts|] = cat(if_quotes, ts, if_quotes, ": ", to_json(ta[ti], only_loggable)); + tab_parts += cat(if_quotes, ts, if_quotes, ": ", to_json(ta[ti], only_loggable)); } return cat("{", join_string_vec(tab_parts, ", "), "}"); } @@ -101,7 +101,7 @@ function to_json(v: any, only_loggable: bool &default=F, field_escape_pattern: p local va: vector of any = v; for ( vi in va ) { - vec_parts[|vec_parts|] = to_json(va[vi], only_loggable); + vec_parts += to_json(va[vi], only_loggable); } return cat("[", join_string_vec(vec_parts, ", "), "]"); } diff --git a/scripts/policy/frameworks/notice/extend-email/hostnames.bro b/scripts/policy/frameworks/notice/extend-email/hostnames.bro index d8dac39e43..9ee58d3e0b 100644 --- a/scripts/policy/frameworks/notice/extend-email/hostnames.bro +++ b/scripts/policy/frameworks/notice/extend-email/hostnames.bro @@ -35,7 +35,7 @@ hook notice(n: Notice::Info) &priority=10 when ( local src_name = lookup_addr(n$src) ) { output = string_cat("orig/src hostname: ", src_name, "\n"); - tmp_notice_storage[uid]$email_body_sections[|tmp_notice_storage[uid]$email_body_sections|] = output; + tmp_notice_storage[uid]$email_body_sections += output; delete tmp_notice_storage[uid]$email_delay_tokens["hostnames-src"]; } } @@ -45,7 +45,7 @@ hook notice(n: Notice::Info) &priority=10 when ( local dst_name = lookup_addr(n$dst) ) { output = string_cat("resp/dst hostname: ", dst_name, "\n"); - tmp_notice_storage[uid]$email_body_sections[|tmp_notice_storage[uid]$email_body_sections|] = output; + tmp_notice_storage[uid]$email_body_sections += output; delete tmp_notice_storage[uid]$email_delay_tokens["hostnames-dst"]; } } diff --git a/scripts/policy/misc/load-balancing.bro b/scripts/policy/misc/load-balancing.bro index 928d0e74c0..40bbe238ca 100644 --- a/scripts/policy/misc/load-balancing.bro +++ b/scripts/policy/misc/load-balancing.bro @@ -40,7 +40,7 @@ event bro_init() &priority=5 # Sort nodes list so that every node iterates over it in same order. for ( name in Cluster::nodes ) - sorted_node_names[|sorted_node_names|] = name; + sorted_node_names += name; sort(sorted_node_names, strcmp); diff --git a/scripts/policy/protocols/dhcp/msg-orig.bro b/scripts/policy/protocols/dhcp/msg-orig.bro index beb7c7d78b..d2350192b5 100644 --- a/scripts/policy/protocols/dhcp/msg-orig.bro +++ b/scripts/policy/protocols/dhcp/msg-orig.bro @@ -17,5 +17,5 @@ export { event DHCP::aggregate_msgs(ts: time, id: conn_id, uid: string, is_orig: bool, msg: DHCP::Msg, options: DHCP::Options) &priority=3 { - log_info$msg_orig[|log_info$msg_orig|] = is_orig ? id$orig_h : id$resp_h; + log_info$msg_orig += is_orig ? id$orig_h : id$resp_h; } diff --git a/scripts/policy/protocols/http/header-names.bro b/scripts/policy/protocols/http/header-names.bro index ed3f9380a7..1b256226dd 100644 --- a/scripts/policy/protocols/http/header-names.bro +++ b/scripts/policy/protocols/http/header-names.bro @@ -35,7 +35,7 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr { if ( ! c$http?$client_header_names ) c$http$client_header_names = vector(); - c$http$client_header_names[|c$http$client_header_names|] = name; + c$http$client_header_names += name; } } else @@ -44,7 +44,7 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr { if ( ! c$http?$server_header_names ) c$http$server_header_names = vector(); - c$http$server_header_names[|c$http$server_header_names|] = name; + c$http$server_header_names += name; } } } diff --git a/scripts/policy/protocols/ssl/heartbleed.bro b/scripts/policy/protocols/ssl/heartbleed.bro index 783961bef2..e1073b3ff0 100644 --- a/scripts/policy/protocols/ssl/heartbleed.bro +++ b/scripts/policy/protocols/ssl/heartbleed.bro @@ -50,33 +50,33 @@ event bro_init() # Minimum length a heartbeat packet must have for different cipher suites. # Note - tls 1.1f and 1.0 have different lengths :( # This should be all cipher suites usually supported by vulnerable servers. - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_AES_256_GCM_SHA384$/, $min_length=43]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_AES_128_GCM_SHA256$/, $min_length=43]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA384$/, $min_length=96]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA256$/, $min_length=80]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA$/, $min_length=64]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_128_CBC_SHA256$/, $min_length=80]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_128_CBC_SHA$/, $min_length=64]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=48]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_SEED_CBC_SHA$/, $min_length=64]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_IDEA_CBC_SHA$/, $min_length=48]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_DES_CBC_SHA$/, $min_length=48]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_DES40_CBC_SHA$/, $min_length=48]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_128_SHA$/, $min_length=39]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_128_MD5$/, $min_length=35]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_40_MD5$/, $min_length=35]; - min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC2_CBC_40_MD5$/, $min_length=48]; - min_lengths[|min_lengths|] = [$cipher=/_256_CBC_SHA$/, $min_length=48]; - min_lengths[|min_lengths|] = [$cipher=/_128_CBC_SHA$/, $min_length=48]; - min_lengths[|min_lengths|] = [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=40]; - min_lengths[|min_lengths|] = [$cipher=/_SEED_CBC_SHA$/, $min_length=48]; - min_lengths[|min_lengths|] = [$cipher=/_IDEA_CBC_SHA$/, $min_length=40]; - min_lengths[|min_lengths|] = [$cipher=/_DES_CBC_SHA$/, $min_length=40]; - min_lengths[|min_lengths|] = [$cipher=/_DES40_CBC_SHA$/, $min_length=40]; - min_lengths[|min_lengths|] = [$cipher=/_RC4_128_SHA$/, $min_length=39]; - min_lengths[|min_lengths|] = [$cipher=/_RC4_128_MD5$/, $min_length=35]; - min_lengths[|min_lengths|] = [$cipher=/_RC4_40_MD5$/, $min_length=35]; - min_lengths[|min_lengths|] = [$cipher=/_RC2_CBC_40_MD5$/, $min_length=40]; + min_lengths_tls11 += [$cipher=/_AES_256_GCM_SHA384$/, $min_length=43]; + min_lengths_tls11 += [$cipher=/_AES_128_GCM_SHA256$/, $min_length=43]; + min_lengths_tls11 += [$cipher=/_256_CBC_SHA384$/, $min_length=96]; + min_lengths_tls11 += [$cipher=/_256_CBC_SHA256$/, $min_length=80]; + min_lengths_tls11 += [$cipher=/_256_CBC_SHA$/, $min_length=64]; + min_lengths_tls11 += [$cipher=/_128_CBC_SHA256$/, $min_length=80]; + min_lengths_tls11 += [$cipher=/_128_CBC_SHA$/, $min_length=64]; + min_lengths_tls11 += [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=48]; + min_lengths_tls11 += [$cipher=/_SEED_CBC_SHA$/, $min_length=64]; + min_lengths_tls11 += [$cipher=/_IDEA_CBC_SHA$/, $min_length=48]; + min_lengths_tls11 += [$cipher=/_DES_CBC_SHA$/, $min_length=48]; + min_lengths_tls11 += [$cipher=/_DES40_CBC_SHA$/, $min_length=48]; + min_lengths_tls11 += [$cipher=/_RC4_128_SHA$/, $min_length=39]; + min_lengths_tls11 += [$cipher=/_RC4_128_MD5$/, $min_length=35]; + min_lengths_tls11 += [$cipher=/_RC4_40_MD5$/, $min_length=35]; + min_lengths_tls11 += [$cipher=/_RC2_CBC_40_MD5$/, $min_length=48]; + min_lengths += [$cipher=/_256_CBC_SHA$/, $min_length=48]; + min_lengths += [$cipher=/_128_CBC_SHA$/, $min_length=48]; + min_lengths += [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=40]; + min_lengths += [$cipher=/_SEED_CBC_SHA$/, $min_length=48]; + min_lengths += [$cipher=/_IDEA_CBC_SHA$/, $min_length=40]; + min_lengths += [$cipher=/_DES_CBC_SHA$/, $min_length=40]; + min_lengths += [$cipher=/_DES40_CBC_SHA$/, $min_length=40]; + min_lengths += [$cipher=/_RC4_128_SHA$/, $min_length=39]; + min_lengths += [$cipher=/_RC4_128_MD5$/, $min_length=35]; + min_lengths += [$cipher=/_RC4_40_MD5$/, $min_length=35]; + min_lengths += [$cipher=/_RC2_CBC_40_MD5$/, $min_length=40]; } event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string) diff --git a/scripts/policy/protocols/ssl/notary.bro b/scripts/policy/protocols/ssl/notary.bro index 07f2cdebc4..4406dd9629 100644 --- a/scripts/policy/protocols/ssl/notary.bro +++ b/scripts/policy/protocols/ssl/notary.bro @@ -56,7 +56,7 @@ event ssl_established(c: connection) &priority=3 local waits_already = digest in waitlist; if ( ! waits_already ) waitlist[digest] = vector(); - waitlist[digest][|waitlist[digest]|] = c$ssl; + waitlist[digest] += c$ssl; if ( waits_already ) return; diff --git a/scripts/policy/protocols/ssl/validate-sct.bro b/scripts/policy/protocols/ssl/validate-sct.bro index f4d1646ae8..0ce11b63ff 100644 --- a/scripts/policy/protocols/ssl/validate-sct.bro +++ b/scripts/policy/protocols/ssl/validate-sct.bro @@ -76,7 +76,7 @@ event bro_init() event ssl_extension_signed_certificate_timestamp(c: connection, is_orig: bool, version: count, logid: string, timestamp: count, signature_and_hashalgorithm: SSL::SignatureAndHashAlgorithm, signature: string) &priority=5 { - c$ssl$ct_proofs[|c$ssl$ct_proofs|] = SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_and_hashalgorithm$SignatureAlgorithm, $hash_alg=signature_and_hashalgorithm$HashAlgorithm, $signature=signature, $source=SCT_TLS_EXT); + c$ssl$ct_proofs += SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_and_hashalgorithm$SignatureAlgorithm, $hash_alg=signature_and_hashalgorithm$HashAlgorithm, $signature=signature, $source=SCT_TLS_EXT); } event x509_ocsp_ext_signed_certificate_timestamp(f: fa_file, version: count, logid: string, timestamp: count, hash_algorithm: count, signature_algorithm: count, signature: string) &priority=5 @@ -103,7 +103,7 @@ event x509_ocsp_ext_signed_certificate_timestamp(f: fa_file, version: count, log local c = f$conns[cid]; } - c$ssl$ct_proofs[|c$ssl$ct_proofs|] = SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_algorithm, $hash_alg=hash_algorithm, $signature=signature, $source=src); + c$ssl$ct_proofs += SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_algorithm, $hash_alg=hash_algorithm, $signature=signature, $source=src); } # Priority = 19 will be handled after validation is done diff --git a/testing/btest/core/leaks/broker/data.bro b/testing/btest/core/leaks/broker/data.bro index d79155fa30..590d041ff1 100644 --- a/testing/btest/core/leaks/broker/data.bro +++ b/testing/btest/core/leaks/broker/data.bro @@ -91,7 +91,7 @@ function broker_to_bro_vector_recurse(it: opaque of Broker::VectorIterator, if ( Broker::vector_iterator_last(it) ) return rval; - rval[|rval|] = Broker::vector_iterator_value(it) as string; + rval += Broker::vector_iterator_value(it) as string; Broker::vector_iterator_next(it); return broker_to_bro_vector_recurse(it, rval); } diff --git a/testing/btest/language/ipv6-literals.bro b/testing/btest/language/ipv6-literals.bro index 004d104c6e..bf888b29e1 100644 --- a/testing/btest/language/ipv6-literals.bro +++ b/testing/btest/language/ipv6-literals.bro @@ -3,30 +3,30 @@ local v: vector of addr = vector(); -v[|v|] = [::1]; -v[|v|] = [::ffff]; -v[|v|] = [::ffff:ffff]; -v[|v|] = [::0a0a:ffff]; -v[|v|] = [1::1]; -v[|v|] = [1::a]; -v[|v|] = [1::1:1]; -v[|v|] = [1::1:a]; -v[|v|] = [a::a]; -v[|v|] = [a::1]; -v[|v|] = [a::a:a]; -v[|v|] = [a::a:1]; -v[|v|] = [a:a::a]; -v[|v|] = [aaaa:0::ffff]; -v[|v|] = [::ffff:192.168.1.100]; -v[|v|] = [ffff::192.168.1.100]; -v[|v|] = [::192.168.1.100]; -v[|v|] = [::ffff:0:192.168.1.100]; -v[|v|] = [805B:2D9D:DC28::FC57:212.200.31.255]; -v[|v|] = [0xaaaa::bbbb]; -v[|v|] = [aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222]; -v[|v|] = [aaaa:bbbb:cccc:dddd:eeee:ffff:1:2222]; -v[|v|] = [aaaa:bbbb:cccc:dddd:eeee:ffff:0:2222]; -v[|v|] = [aaaa:bbbb:cccc:dddd:eeee:0:0:2222]; +v += [::1]; +v += [::ffff]; +v += [::ffff:ffff]; +v += [::0a0a:ffff]; +v += [1::1]; +v += [1::a]; +v += [1::1:1]; +v += [1::1:a]; +v += [a::a]; +v += [a::1]; +v += [a::a:a]; +v += [a::a:1]; +v += [a:a::a]; +v += [aaaa:0::ffff]; +v += [::ffff:192.168.1.100]; +v += [ffff::192.168.1.100]; +v += [::192.168.1.100]; +v += [::ffff:0:192.168.1.100]; +v += [805B:2D9D:DC28::FC57:212.200.31.255]; +v += [0xaaaa::bbbb]; +v += [aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222]; +v += [aaaa:bbbb:cccc:dddd:eeee:ffff:1:2222]; +v += [aaaa:bbbb:cccc:dddd:eeee:ffff:0:2222]; +v += [aaaa:bbbb:cccc:dddd:eeee:0:0:2222]; for (i in v) print v[i]; diff --git a/testing/btest/language/record-default-coercion.bro b/testing/btest/language/record-default-coercion.bro index 822b845f65..9d8babf571 100644 --- a/testing/btest/language/record-default-coercion.bro +++ b/testing/btest/language/record-default-coercion.bro @@ -43,6 +43,6 @@ print_bar(bar6); local r: MyRecord = [$c=13]; print r; print |r$v|; -r$v[|r$v|] = "test"; +r$v += "test"; print r; print |r$v|; diff --git a/testing/btest/scripts/base/frameworks/netcontrol/delete-internal-state.bro b/testing/btest/scripts/base/frameworks/netcontrol/delete-internal-state.bro index 9b8c995fac..29cb439a64 100644 --- a/testing/btest/scripts/base/frameworks/netcontrol/delete-internal-state.bro +++ b/testing/btest/scripts/base/frameworks/netcontrol/delete-internal-state.bro @@ -43,10 +43,10 @@ event dump_info() event connection_established(c: connection) { local id = c$id; - rules[|rules|] = NetControl::shunt_flow([$src_h=id$orig_h, $src_p=id$orig_p, $dst_h=id$resp_h, $dst_p=id$resp_p], 0secs); - rules[|rules|] = NetControl::drop_address(id$orig_h, 0secs); - rules[|rules|] = NetControl::whitelist_address(id$orig_h, 0secs); - rules[|rules|] = NetControl::redirect_flow([$src_h=id$orig_h, $src_p=id$orig_p, $dst_h=id$resp_h, $dst_p=id$resp_p], 5, 0secs); + rules += NetControl::shunt_flow([$src_h=id$orig_h, $src_p=id$orig_p, $dst_h=id$resp_h, $dst_p=id$resp_p], 0secs); + rules += NetControl::drop_address(id$orig_h, 0secs); + rules += NetControl::whitelist_address(id$orig_h, 0secs); + rules += NetControl::redirect_flow([$src_h=id$orig_h, $src_p=id$orig_p, $dst_h=id$resp_h, $dst_p=id$resp_p], 5, 0secs); schedule 1sec { remove_all() }; schedule 2sec { dump_info() }; diff --git a/testing/btest/scripts/base/frameworks/netcontrol/multiple.bro b/testing/btest/scripts/base/frameworks/netcontrol/multiple.bro index 56a764f2e9..d56c8e2468 100644 --- a/testing/btest/scripts/base/frameworks/netcontrol/multiple.bro +++ b/testing/btest/scripts/base/frameworks/netcontrol/multiple.bro @@ -27,10 +27,10 @@ event remove_all() event connection_established(c: connection) { local id = c$id; - rules[|rules|] = NetControl::shunt_flow([$src_h=id$orig_h, $src_p=id$orig_p, $dst_h=id$resp_h, $dst_p=id$resp_p], 0secs); - rules[|rules|] = NetControl::drop_address(id$orig_h, 0secs); - rules[|rules|] = NetControl::whitelist_address(id$orig_h, 0secs); - rules[|rules|] = NetControl::redirect_flow([$src_h=id$orig_h, $src_p=id$orig_p, $dst_h=id$resp_h, $dst_p=id$resp_p], 5, 0secs); + rules += NetControl::shunt_flow([$src_h=id$orig_h, $src_p=id$orig_p, $dst_h=id$resp_h, $dst_p=id$resp_p], 0secs); + rules += NetControl::drop_address(id$orig_h, 0secs); + rules += NetControl::whitelist_address(id$orig_h, 0secs); + rules += NetControl::redirect_flow([$src_h=id$orig_h, $src_p=id$orig_p, $dst_h=id$resp_h, $dst_p=id$resp_p], 5, 0secs); schedule 1sec { remove_all() }; } diff --git a/testing/btest/scripts/base/frameworks/sumstats/sample-cluster.bro b/testing/btest/scripts/base/frameworks/sumstats/sample-cluster.bro index 6426b42680..52fce96dba 100644 --- a/testing/btest/scripts/base/frameworks/sumstats/sample-cluster.bro +++ b/testing/btest/scripts/base/frameworks/sumstats/sample-cluster.bro @@ -31,7 +31,7 @@ event bro_init() &priority=5 print fmt("Host: %s Sampled observations: %d", key$host, r$sample_elements); local sample_nums: vector of count = vector(); for ( sample in r$samples ) - sample_nums[|sample_nums|] =r$samples[sample]$num; + sample_nums += r$samples[sample]$num; print fmt(" %s", sort(sample_nums)); }, From 2375c0c4bea697af75f83a6a7732d089cd374e52 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Thu, 26 Jul 2018 14:35:30 -0700 Subject: [PATCH 16/43] forgot to update test suite results for v += e --- testing/btest/Baseline/language.vector/out | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/btest/Baseline/language.vector/out b/testing/btest/Baseline/language.vector/out index 0aa3ab0a8f..0fdcc1fa24 100644 --- a/testing/btest/Baseline/language.vector/out +++ b/testing/btest/Baseline/language.vector/out @@ -57,3 +57,4 @@ access element (PASS) % operator (PASS) && operator (PASS) || operator (PASS) ++= operator (PASS) From f7358a3351f04a1a28f5e34437658426e73664f6 Mon Sep 17 00:00:00 2001 From: Vern Paxson Date: Thu, 26 Jul 2018 14:35:57 -0700 Subject: [PATCH 17/43] d'oh, still have a (deprecated) string_array rather than string_vector --- scripts/base/utils/addrs.bro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/base/utils/addrs.bro b/scripts/base/utils/addrs.bro index 058aabd96e..f89467c14d 100644 --- a/scripts/base/utils/addrs.bro +++ b/scripts/base/utils/addrs.bro @@ -100,7 +100,7 @@ function find_ip_addresses(input: string): string_array &deprecated for ( i in parts ) { if ( i % 2 == 1 && is_valid_ip(parts[i]) ) - output += parts[i]; + output[|output|] += parts[i]; } return output; } From a8e65d908ea49c51c4e95565dfe11711d2a484df Mon Sep 17 00:00:00 2001 From: Chung Min Kim Date: Mon, 30 Jul 2018 09:35:55 -0700 Subject: [PATCH 18/43] Fixing up `make html` target Add types of files that genhtml (the program that generates html files from .gcno/.gcda files, included in lcov) should ignore, such as .yy and .ll files. --- testing/coverage/Makefile | 5 +-- testing/coverage/README | 11 ++---- testing/coverage/code_coverage.sh | 12 ++++-- testing/coverage/lcov_html.sh | 61 +++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 15 deletions(-) create mode 100755 testing/coverage/lcov_html.sh diff --git a/testing/coverage/Makefile b/testing/coverage/Makefile index 1aa8036bbf..7f458a4f9c 100644 --- a/testing/coverage/Makefile +++ b/testing/coverage/Makefile @@ -9,7 +9,4 @@ distclean: cleanup @find ../../ -name "*.gcno" -exec rm {} \; html: - @(cd ../../; if which lcov; then\ - lcov --capture --directory . --output-file coverage.info \ - && genhtml coverage.info && rm coverage.info; \ - fi) + @./lcov_html.sh $(COVERAGE_HTML_DIR) diff --git a/testing/coverage/README b/testing/coverage/README index 0c19e14d51..2cda389452 100644 --- a/testing/coverage/README +++ b/testing/coverage/README @@ -2,12 +2,7 @@ On a Bro build configured with --enable-coverage, this script produces a code co This depends on gcov, which should come with your gcc. If gcov is not installed, the script will abort with an error message. -TODO: Use `make html` as make target in this directory to output the html files that gcov can create (must have lcov on system). +After `make all` in the upper directory, use `make html` as make target in this directory to output the html files that lcov can create. By default, the html files will be contained in a directory named "coverage-html" in the base directory. To set a custom name, use `make html COVERAGE_HTML_DIR=custom-dir-name`. -The goal of code-coverage.sh script is to automate code coverage testing. See the following steps for the code structure: - 1. Run test suite - 2. Check for .gcda files existing. - 3a. Run gcov (-p to preserve path) - 3b. Prune .gcov files for objects outside of the Bro tree - 4a. Analyze .gcov files generated and create summary file - 4b. Send .gcov files to appropriate path +The script code_coverage.sh is triggered by `make coverage` (included in `make` in /testing), and its goal is to automate code coverage testing. +The script lcov_html.sh is triggered by `make html`, and its goal is to create html files from the aforementioned coverage data. diff --git a/testing/coverage/code_coverage.sh b/testing/coverage/code_coverage.sh index ec583b1e5e..758b2fa915 100755 --- a/testing/coverage/code_coverage.sh +++ b/testing/coverage/code_coverage.sh @@ -17,13 +17,12 @@ # 4b. Send .gcov files to appropriate path # CURR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Location of script -BASE="$(readlink -f "${CURR}/../../")" +BASE="$( cd "$CURR" && cd ../../ && pwd )" TMP="${CURR}/tmp.$$" mkdir -p $TMP # DEFINE CLEANUP PROCESS function finish { - find "$BASE" -name "*.gcda" -exec rm {} \; > /dev/null 2>&1 rm -rf $TMP } trap finish EXIT @@ -98,13 +97,20 @@ echo "ok" # 3a. Run gcov (-p to preserve path) and move into tmp directory # ... if system does not have gcov installed, exit with message. echo -n "Creating coverage files... " -if which gcov; then +if which gcov > /dev/null 2>&1; then ( cd "$TMP" && find "$BASE" -name "*.o" -exec gcov -p {} > /dev/null 2>&1 \; ) NUM_GCOVS=$(find "$TMP" -name *.gcov | wc -l) if [ $NUM_GCOVS -eq 0 ]; then echo "no gcov files produced, aborting" exit 1 fi + + # Account for '^' that occurs in macOS due to LLVM + # This character seems to be equivalent to ".." (up 1 dir) + for file in $(ls $TMP/*.gcov | grep '\^'); do + mv $file "$(sed 's/#[^#]*#\^//g' <<< "$file")" + done + echo "ok, $NUM_GCOVS coverage files" else echo "gcov is not installed on system, aborting" diff --git a/testing/coverage/lcov_html.sh b/testing/coverage/lcov_html.sh new file mode 100755 index 0000000000..d1af203437 --- /dev/null +++ b/testing/coverage/lcov_html.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# +# On a Bro build configured with --enable-coverage, this script +# produces a code coverage report in HTML format after Bro has been invoked. The +# intended application of this script is after the btest testsuite has run. + +# This depends on lcov to run. + +function die { + echo "$@" + exit 1 +} +function finish { + rm -rf "$TMP" +} +function verify-run { + if bash -c "$1" > /dev/null 2>&1; then + echo ${2:-"ok"} + else + die ${3:-"error, abort"} + fi +} +trap finish EXIT + +COVERAGE_FILE="./$TMP/coverage.info" +COVERAGE_HTML_DIR="${1:-"coverage-html"}" +REMOVE_TARGETS="*.yy *.ll *.y *.l" + +# 1. Move to base dir, create tmp dir +cd ../../; +TMP=".tmp.$$" +mkdir "$TMP" + +# 2. Check for .gcno and .gcda file presence +echo -n "Checking for coverage files... " +for pat in gcda gcno; do + if [ -z "$(find "$BASE" -name "*.$pat" 2>/dev/null)" ]; then + echo "no .$pat files, nothing to do" + exit 0 + fi +done +echo "ok" + +# 3. If lcov does not exist, abort process. +echo -n "Checking for lcov... " +verify-run "which lcov" \ + "lcov installed on system, continue" \ + "lcov not installed, abort" + +# 4. Create a "tracefile" through lcov, which is necessary to create html files later on. +echo -n "Creating tracefile for html generation... " +verify-run "lcov --no-external --capture --directory . --output-file $COVERAGE_FILE" + +for TARGET in $REMOVE_TARGETS; do + echo -n "Getting rid of $TARGET files from tracefile... " + verify-run "lcov --remove $COVERAGE_FILE $TARGET --output-file $COVERAGE_FILE" +done + +# 5. Create HTML files. +echo -n "Creating HTML files... " +verify-run "genhtml -q -o $COVERAGE_HTML_DIR $COVERAGE_FILE" From c4cb27b12f2033f9e17a6e562c26a22180b7b5b6 Mon Sep 17 00:00:00 2001 From: Chung Min Kim Date: Mon, 30 Jul 2018 12:17:40 -0700 Subject: [PATCH 19/43] Added coverage to .PHONY in Makefile due to testing/coverage --- testing/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/Makefile b/testing/Makefile index c5610fc97b..98c6b239a2 100644 --- a/testing/Makefile +++ b/testing/Makefile @@ -25,3 +25,4 @@ coverage: @rm -f brocov.tmp.* @cd coverage && make coverage +.PHONY: coverage From e11cc8778f1e061fbdb49cdcf75e71f150951b2e Mon Sep 17 00:00:00 2001 From: Chung Min Kim Date: Mon, 30 Jul 2018 13:34:53 -0700 Subject: [PATCH 20/43] Minor edits due to typo and field changes --- testing/coverage/lcov_html.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/coverage/lcov_html.sh b/testing/coverage/lcov_html.sh index d1af203437..f4ff6f2b29 100755 --- a/testing/coverage/lcov_html.sh +++ b/testing/coverage/lcov_html.sh @@ -22,19 +22,19 @@ function verify-run { } trap finish EXIT +TMP=".tmp.$$" COVERAGE_FILE="./$TMP/coverage.info" COVERAGE_HTML_DIR="${1:-"coverage-html"}" -REMOVE_TARGETS="*.yy *.ll *.y *.l" +REMOVE_TARGETS="*.yy *.ll *.y *.l */bro.dir/* *.bif" # 1. Move to base dir, create tmp dir cd ../../; -TMP=".tmp.$$" mkdir "$TMP" # 2. Check for .gcno and .gcda file presence echo -n "Checking for coverage files... " for pat in gcda gcno; do - if [ -z "$(find "$BASE" -name "*.$pat" 2>/dev/null)" ]; then + if [ -z "$(find . -name "*.$pat" 2>/dev/null)" ]; then echo "no .$pat files, nothing to do" exit 0 fi @@ -58,4 +58,4 @@ done # 5. Create HTML files. echo -n "Creating HTML files... " -verify-run "genhtml -q -o $COVERAGE_HTML_DIR $COVERAGE_FILE" +verify-run "genhtml -o $COVERAGE_HTML_DIR $COVERAGE_FILE" From 9edd38026230119042dd71e1ea77840c68f63e0f Mon Sep 17 00:00:00 2001 From: Chung Min Kim Date: Tue, 31 Jul 2018 13:28:21 -0700 Subject: [PATCH 21/43] Renamed verify-run to verify_run --- testing/coverage/lcov_html.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testing/coverage/lcov_html.sh b/testing/coverage/lcov_html.sh index f4ff6f2b29..c729b2145c 100755 --- a/testing/coverage/lcov_html.sh +++ b/testing/coverage/lcov_html.sh @@ -13,7 +13,7 @@ function die { function finish { rm -rf "$TMP" } -function verify-run { +function verify_run { if bash -c "$1" > /dev/null 2>&1; then echo ${2:-"ok"} else @@ -43,19 +43,19 @@ echo "ok" # 3. If lcov does not exist, abort process. echo -n "Checking for lcov... " -verify-run "which lcov" \ +verify_run "which lcov" \ "lcov installed on system, continue" \ "lcov not installed, abort" # 4. Create a "tracefile" through lcov, which is necessary to create html files later on. echo -n "Creating tracefile for html generation... " -verify-run "lcov --no-external --capture --directory . --output-file $COVERAGE_FILE" +verify_run "lcov --no-external --capture --directory . --output-file $COVERAGE_FILE" for TARGET in $REMOVE_TARGETS; do echo -n "Getting rid of $TARGET files from tracefile... " - verify-run "lcov --remove $COVERAGE_FILE $TARGET --output-file $COVERAGE_FILE" + verify_run "lcov --remove $COVERAGE_FILE $TARGET --output-file $COVERAGE_FILE" done # 5. Create HTML files. echo -n "Creating HTML files... " -verify-run "genhtml -o $COVERAGE_HTML_DIR $COVERAGE_FILE" +verify_run "genhtml -o $COVERAGE_HTML_DIR $COVERAGE_FILE" From 8c8b55cd1816a87d9111310d2c7f8174905d3517 Mon Sep 17 00:00:00 2001 From: Daniel Thayer Date: Thu, 2 Aug 2018 15:44:47 -0500 Subject: [PATCH 22/43] Use default version of OpenSSL on all travis docker containers --- testing/scripts/travis-job | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/scripts/travis-job b/testing/scripts/travis-job index c6221d76a2..7e2c276762 100644 --- a/testing/scripts/travis-job +++ b/testing/scripts/travis-job @@ -59,16 +59,16 @@ setup_docker() { distro_cmds="yum -y install cmake make gcc gcc-c++ flex bison libpcap-devel openssl-devel git openssl which" ;; debian_9) - distro_cmds="apt-get update; apt-get -y install cmake make gcc g++ flex bison python libpcap-dev libssl1.0-dev zlib1g-dev git sqlite3 curl bsdmainutils" + distro_cmds="apt-get update; apt-get -y install cmake make gcc g++ flex bison python libpcap-dev libssl-dev zlib1g-dev git sqlite3 curl bsdmainutils" ;; fedora_28) - distro_cmds="yum -y install cmake make gcc gcc-c++ flex bison libpcap-devel compat-openssl10-devel git sqlite findutils which; ln -s /usr/bin/python3 /usr/local/bin/python" + distro_cmds="yum -y install cmake make gcc gcc-c++ flex bison libpcap-devel openssl-devel git sqlite findutils which; ln -s /usr/bin/python3 /usr/local/bin/python" ;; ubuntu_16.04) distro_cmds="apt-get update; apt-get -y install cmake make gcc g++ flex bison python libpcap-dev libssl-dev zlib1g-dev git sqlite3 curl bsdmainutils" ;; ubuntu_18.04) - distro_cmds="apt-get update; apt-get -y install cmake make gcc g++ flex bison python3 libpcap-dev libssl1.0-dev zlib1g-dev git sqlite3 curl bsdmainutils; ln -s /usr/bin/python3 /usr/local/bin/python" + distro_cmds="apt-get update; apt-get -y install cmake make gcc g++ flex bison python3 libpcap-dev libssl-dev zlib1g-dev git sqlite3 curl bsdmainutils; ln -s /usr/bin/python3 /usr/local/bin/python" ;; *) echo "Error: distro ${distro} is not recognized by this script" From 29c179c30d3ed5e2ce2b6368c654bbd69c8d944a Mon Sep 17 00:00:00 2001 From: Daniel Thayer Date: Thu, 2 Aug 2018 16:12:26 -0500 Subject: [PATCH 23/43] Improve a travis output message in pull request builds The output message is now more explicit and doesn't look like an error message. --- testing/scripts/travis-job | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/scripts/travis-job b/testing/scripts/travis-job index 7e2c276762..92d8d6d53d 100644 --- a/testing/scripts/travis-job +++ b/testing/scripts/travis-job @@ -149,7 +149,7 @@ run() { elif [ -n "${TRAVIS_PULL_REQUEST}" ] && [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then # For pull request builds, the private key is not available, so skip # the private tests to avoid failing. - echo "Note: skipping private tests because encrypted env. variables are not defined." + echo "Note: skipping private tests because encrypted env. variables are not available in pull request builds." else echo "Error: cannot get private tests because encrypted env. variables are not defined." exit 1 From 02900c9401408a22438e7d9fec739c31b852fb6f Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 6 Aug 2018 16:15:47 -0500 Subject: [PATCH 24/43] Updating submodule(s). [nomail] --- aux/bifcl | 2 +- aux/binpac | 2 +- aux/bro-aux | 2 +- aux/broccoli | 2 +- aux/broctl | 2 +- aux/broker | 2 +- cmake | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aux/bifcl b/aux/bifcl index f648ad79b2..08142075ee 160000 --- a/aux/bifcl +++ b/aux/bifcl @@ -1 +1 @@ -Subproject commit f648ad79b20baba4f80259d059044ae78d56d7c4 +Subproject commit 08142075ee48e0db5897bd6e7d5a03f1e9eb8084 diff --git a/aux/binpac b/aux/binpac index 7e6b47ee90..08cbe758a5 160000 --- a/aux/binpac +++ b/aux/binpac @@ -1 +1 @@ -Subproject commit 7e6b47ee90c5b6ab80b0e6f93d5cf835fd86ce4e +Subproject commit 08cbe758a5d6d2d404dcd552e246180827d0257b diff --git a/aux/bro-aux b/aux/bro-aux index 1b27ec4c24..b9de0fc0d1 160000 --- a/aux/bro-aux +++ b/aux/bro-aux @@ -1 +1 @@ -Subproject commit 1b27ec4c24bb13443f1a5e8f4249ff4e20e06dd1 +Subproject commit b9de0fc0d1d9c65bfbfa8fca68f5f668e4f54203 diff --git a/aux/broccoli b/aux/broccoli index 80a4aa6892..3e01d4b41c 160000 --- a/aux/broccoli +++ b/aux/broccoli @@ -1 +1 @@ -Subproject commit 80a4aa68927c2f60ece1200268106edc27f50338 +Subproject commit 3e01d4b41c525870cfebdb131a62d54d998d55ff diff --git a/aux/broctl b/aux/broctl index d900149ef6..4f73b40c1a 160000 --- a/aux/broctl +++ b/aux/broctl @@ -1 +1 @@ -Subproject commit d900149ef6e2f744599a9575b67fb7155953bd4a +Subproject commit 4f73b40c1aa31657199f1021ccc974f4e996c79f diff --git a/aux/broker b/aux/broker index 488dc806d0..1236cfc64a 160000 --- a/aux/broker +++ b/aux/broker @@ -1 +1 @@ -Subproject commit 488dc806d0f777dcdee04f3794f04121ded08b8e +Subproject commit 1236cfc64aae9ceb469ea9cf141dd2e3e9422e6b diff --git a/cmake b/cmake index ec66fd8fbd..f5265a1ea8 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit ec66fd8fbd81fd8b25ced0214016f4c89604a15c +Subproject commit f5265a1ea8b5631aaf00164452ba718f8635db22 From 29359ffff22802f075dbc6095ac0ed7ea921469d Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 6 Aug 2018 16:36:10 -0500 Subject: [PATCH 25/43] Updating submodule(s). [nomail] --- aux/broccoli | 2 +- aux/broctl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aux/broccoli b/aux/broccoli index 3e01d4b41c..f061c47c1d 160000 --- a/aux/broccoli +++ b/aux/broccoli @@ -1 +1 @@ -Subproject commit 3e01d4b41c525870cfebdb131a62d54d998d55ff +Subproject commit f061c47c1db058339aa8d273d2d9cc1b27d8518d diff --git a/aux/broctl b/aux/broctl index 4f73b40c1a..b1f127c7d2 160000 --- a/aux/broctl +++ b/aux/broctl @@ -1 +1 @@ -Subproject commit 4f73b40c1aa31657199f1021ccc974f4e996c79f +Subproject commit b1f127c7d25522ddf25f6677f5914e467b5f86d7 From e6042940dce27ff0588d52a7cfd9d43528d56bf5 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 6 Aug 2018 17:04:42 -0500 Subject: [PATCH 26/43] Fix (non)suppression of proxy-bound events in known-*.bro scripts When not using data stores, these scripts were intended to suppress sending duplicate events to proxies by looking up the key in the local cache. --- scripts/policy/protocols/conn/known-hosts.bro | 3 +++ scripts/policy/protocols/conn/known-services.bro | 3 +++ scripts/policy/protocols/ssl/known-certs.bro | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/scripts/policy/protocols/conn/known-hosts.bro b/scripts/policy/protocols/conn/known-hosts.bro index b920912f11..410ed9edfe 100644 --- a/scripts/policy/protocols/conn/known-hosts.bro +++ b/scripts/policy/protocols/conn/known-hosts.bro @@ -138,6 +138,9 @@ event Known::host_found(info: HostsInfo) if ( use_host_store ) return; + if ( info$host in Known::hosts ) + return; + Cluster::publish_hrw(Cluster::proxy_pool, info$host, known_host_add, info); event known_host_add(info); } diff --git a/scripts/policy/protocols/conn/known-services.bro b/scripts/policy/protocols/conn/known-services.bro index d737c9bad0..7a829214c1 100644 --- a/scripts/policy/protocols/conn/known-services.bro +++ b/scripts/policy/protocols/conn/known-services.bro @@ -159,6 +159,9 @@ event service_info_commit(info: ServicesInfo) if ( Known::use_service_store ) return; + if ( [info$host, info$port_num] in Known::services ) + return; + local key = cat(info$host, info$port_num); Cluster::publish_hrw(Cluster::proxy_pool, key, known_service_add, info); event known_service_add(info); diff --git a/scripts/policy/protocols/ssl/known-certs.bro b/scripts/policy/protocols/ssl/known-certs.bro index 25365eb4b4..e45a243dfd 100644 --- a/scripts/policy/protocols/ssl/known-certs.bro +++ b/scripts/policy/protocols/ssl/known-certs.bro @@ -127,6 +127,9 @@ event Known::cert_found(info: CertsInfo, hash: string) if ( Known::use_cert_store ) return; + if ( [info$host, hash] in Known::certs ) + return; + local key = cat(info$host, hash); Cluster::publish_hrw(Cluster::proxy_pool, key, known_cert_add, info, hash); event known_cert_add(info, hash); @@ -140,6 +143,7 @@ event Cluster::node_up(name: string, id: string) if ( Cluster::local_node_type() != Cluster::WORKER ) return; + # Drop local suppression cache on workers to force HRW key repartitioning. Known::certs = table(); } @@ -151,6 +155,7 @@ event Cluster::node_down(name: string, id: string) if ( Cluster::local_node_type() != Cluster::WORKER ) return; + # Drop local suppression cache on workers to force HRW key repartitioning. Known::certs = table(); } From 71266167074442f75f20925425fd25093a2683eb Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 6 Aug 2018 17:07:56 -0500 Subject: [PATCH 27/43] Fix an "uninitialized" compiler warning Though it is actually initialized on all non-aborting code paths. --- CHANGES | 7 +++++++ VERSION | 2 +- src/Expr.cc | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 5e5d425b26..07f2aefa93 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,11 @@ +2.5-815 | 2018-08-06 17:07:56 -0500 + + * Fix an "uninitialized" compiler warning (Jon Siwek, Corelight) + + * Fix (non)suppression of proxy-bound events in known-*.bro scripts + (Jon Siwek, Corelight) + 2.5-811 | 2018-08-03 11:33:57 -0500 * Update scripts to use vector "+=" append operation (Vern Paxson, Corelight) diff --git a/VERSION b/VERSION index 6e9545e5f2..ff24872445 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-811 +2.5-815 diff --git a/src/Expr.cc b/src/Expr.cc index c5f38841ee..f958c63ecf 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -882,7 +882,7 @@ Val* BinaryExpr::SetFold(Val* v1, Val* v2) const TableVal* tv1 = v1->AsTableVal(); TableVal* tv2 = v2->AsTableVal(); TableVal* result; - bool res; + bool res = false; switch ( tag ) { case EXPR_AND: From df2e2672d920bfeb9442724e4031416807a2ed60 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Wed, 8 Aug 2018 09:43:46 -0500 Subject: [PATCH 28/43] Updating submodule(s). [nomail] --- aux/broker | 2 +- src/3rdparty | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aux/broker b/aux/broker index 1236cfc64a..6027177387 160000 --- a/aux/broker +++ b/aux/broker @@ -1 +1 @@ -Subproject commit 1236cfc64aae9ceb469ea9cf141dd2e3e9422e6b +Subproject commit 602717738716918e6f3e2c93cc17f92a0b8d6d36 diff --git a/src/3rdparty b/src/3rdparty index b7c6be774b..02c5b1d6a3 160000 --- a/src/3rdparty +++ b/src/3rdparty @@ -1 +1 @@ -Subproject commit b7c6be774b922be1e15f53571201c3be2bc28b75 +Subproject commit 02c5b1d6a3990ca989377798bc7e89eacf4713aa From 2c9dbdd05533e83316d26e596828a106a45f7918 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Wed, 8 Aug 2018 13:03:22 -0500 Subject: [PATCH 29/43] Fix cluster layout graphic and doc warnings --- CHANGES | 6 ++++++ VERSION | 2 +- aux/broker | 2 +- doc/frameworks/broker/cluster-layout.png | Bin 52806 -> 56499 bytes doc/frameworks/broker/cluster-layout.xml | 2 +- doc/frameworks/geoip.rst | 2 +- scripts/base/init-bare.bro | 2 +- .../output | 8 ++++---- ...g_data_struct_vector_declaration_bro.btest | 8 ++++---- 9 files changed, 19 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 07f2aefa93..cce85749d0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,10 @@ +2.5-819 | 2018-08-08 13:03:22 -0500 + + * Fix cluster layout graphic and doc warnings (Jon Siwek, Corelight) + + * Added missing tcp-state for signature dpd_rfb_server (Zhongjie Wang) + 2.5-815 | 2018-08-06 17:07:56 -0500 * Fix an "uninitialized" compiler warning (Jon Siwek, Corelight) diff --git a/VERSION b/VERSION index ff24872445..cd1b9210ec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-815 +2.5-819 diff --git a/aux/broker b/aux/broker index 6027177387..398d8f9dad 160000 --- a/aux/broker +++ b/aux/broker @@ -1 +1 @@ -Subproject commit 602717738716918e6f3e2c93cc17f92a0b8d6d36 +Subproject commit 398d8f9dad05a6a71b3bbbceb65243b2ab56350e diff --git a/doc/frameworks/broker/cluster-layout.png b/doc/frameworks/broker/cluster-layout.png index 8016c7321bc5a7e4be48d7856079c173b56b4b13..3813bfbfda1540c3bb31aaaaca3691f7a647a170 100644 GIT binary patch literal 56499 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 XHcdB>Cni=N1RS@nBa8~JI=uKFHPWAd literal 52806 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 XIdqGt)pvZ}0e_OBaw6qI`o8}Ugd!g} diff --git a/doc/frameworks/broker/cluster-layout.xml b/doc/frameworks/broker/cluster-layout.xml index d0fe32437f..4269c6723f 100644 --- a/doc/frameworks/broker/cluster-layout.xml +++ b/doc/frameworks/broker/cluster-layout.xml @@ -1,2 +1,2 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/doc/frameworks/geoip.rst b/doc/frameworks/geoip.rst index d826aabff6..cd41c6f54c 100644 --- a/doc/frameworks/geoip.rst +++ b/doc/frameworks/geoip.rst @@ -102,7 +102,7 @@ following order by default: If you see an error message similar to "Bro was not configured for GeoIP support", then you either need to rebuild Bro and make sure it is linked -against libmaxminddb or else set the :bro:see:`mmdb_dir`` value +against libmaxminddb or else set the :bro:see:`mmdb_dir` value correctly. Normally, if libmaxminddb is installed correctly then it should automatically be found when building Bro. If this doesn't happen, then you may need to specify the path to the libmaxminddb diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index b39490ed7e..8febc9dae3 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -872,7 +872,7 @@ type geo_location: record { longitude: double &optional; ##< Longitude. } &log; -## The directory containing MaxMind DB (*.mmdb) files to use for GeoIP support. +## The directory containing MaxMind DB (.mmdb) files to use for GeoIP support. const mmdb_dir: string = "" &redef; ## Computed entropy values. The record captures a number of measures that are diff --git a/testing/btest/Baseline/doc.sphinx.include-doc_scripting_data_struct_vector_declaration_bro/output b/testing/btest/Baseline/doc.sphinx.include-doc_scripting_data_struct_vector_declaration_bro/output index 4f1260e4ed..22790f45fe 100644 --- a/testing/btest/Baseline/doc.sphinx.include-doc_scripting_data_struct_vector_declaration_bro/output +++ b/testing/btest/Baseline/doc.sphinx.include-doc_scripting_data_struct_vector_declaration_bro/output @@ -7,10 +7,10 @@ event bro_init() local v1: vector of count; local v2 = vector(1, 2, 3, 4); - v1[|v1|] = 1; - v1[|v1|] = 2; - v1[|v1|] = 3; - v1[|v1|] = 4; + v1 += 1; + v1 += 2; + v1 += 3; + v1 += 4; print fmt("contents of v1: %s", v1); print fmt("length of v1: %d", |v1|); diff --git a/testing/btest/doc/sphinx/include-doc_scripting_data_struct_vector_declaration_bro.btest b/testing/btest/doc/sphinx/include-doc_scripting_data_struct_vector_declaration_bro.btest index 4f1260e4ed..22790f45fe 100644 --- a/testing/btest/doc/sphinx/include-doc_scripting_data_struct_vector_declaration_bro.btest +++ b/testing/btest/doc/sphinx/include-doc_scripting_data_struct_vector_declaration_bro.btest @@ -7,10 +7,10 @@ event bro_init() local v1: vector of count; local v2 = vector(1, 2, 3, 4); - v1[|v1|] = 1; - v1[|v1|] = 2; - v1[|v1|] = 3; - v1[|v1|] = 4; + v1 += 1; + v1 += 2; + v1 += 3; + v1 += 4; print fmt("contents of v1: %s", v1); print fmt("length of v1: %d", |v1|); From 7b12fd8c4aa66ed3d160812ad1872a798a265a71 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 8 Aug 2018 13:17:26 -0700 Subject: [PATCH 30/43] Updating submodule(s). [nomail] --- aux/bifcl | 2 +- aux/binpac | 2 +- aux/bro-aux | 2 +- aux/broccoli | 2 +- aux/broctl | 2 +- aux/broker | 2 +- cmake | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aux/bifcl b/aux/bifcl index 08142075ee..e99152c00a 160000 --- a/aux/bifcl +++ b/aux/bifcl @@ -1 +1 @@ -Subproject commit 08142075ee48e0db5897bd6e7d5a03f1e9eb8084 +Subproject commit e99152c00aad8f81c684a01bc4d40790a295f85c diff --git a/aux/binpac b/aux/binpac index 08cbe758a5..74cf55ace0 160000 --- a/aux/binpac +++ b/aux/binpac @@ -1 +1 @@ -Subproject commit 08cbe758a5d6d2d404dcd552e246180827d0257b +Subproject commit 74cf55ace0de2bf061bbbf285ccf47cba122955f diff --git a/aux/bro-aux b/aux/bro-aux index b9de0fc0d1..53aae82024 160000 --- a/aux/bro-aux +++ b/aux/bro-aux @@ -1 +1 @@ -Subproject commit b9de0fc0d1d9c65bfbfa8fca68f5f668e4f54203 +Subproject commit 53aae820242c02790089e384a9fe2d3174799ab1 diff --git a/aux/broccoli b/aux/broccoli index f061c47c1d..edf754ea6e 160000 --- a/aux/broccoli +++ b/aux/broccoli @@ -1 +1 @@ -Subproject commit f061c47c1db058339aa8d273d2d9cc1b27d8518d +Subproject commit edf754ea6e89a84ad74eff69a454c5e285c4b81b diff --git a/aux/broctl b/aux/broctl index b1f127c7d2..70a8b2e151 160000 --- a/aux/broctl +++ b/aux/broctl @@ -1 +1 @@ -Subproject commit b1f127c7d25522ddf25f6677f5914e467b5f86d7 +Subproject commit 70a8b2e15105f4c238765a882151718162e46208 diff --git a/aux/broker b/aux/broker index 398d8f9dad..d94e2c0e3c 160000 --- a/aux/broker +++ b/aux/broker @@ -1 +1 @@ -Subproject commit 398d8f9dad05a6a71b3bbbceb65243b2ab56350e +Subproject commit d94e2c0e3cf00b19eb210b151a41fd238c6d9eeb diff --git a/cmake b/cmake index f5265a1ea8..4cc3e344cf 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit f5265a1ea8b5631aaf00164452ba718f8635db22 +Subproject commit 4cc3e344cf2698010a46684d32a2907a943430e3 From ff22230a73f79b120ae2e02c5a2f6593d55f2369 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 8 Aug 2018 13:25:06 -0700 Subject: [PATCH 31/43] Update submodule [nomail] --- aux/broker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aux/broker b/aux/broker index d94e2c0e3c..3ab9b647ee 160000 --- a/aux/broker +++ b/aux/broker @@ -1 +1 @@ -Subproject commit d94e2c0e3cf00b19eb210b151a41fd238c6d9eeb +Subproject commit 3ab9b647eedf6be674067198551d57b2b17e86d1 From 5d3ef4daf4504d60dcfff8d39967317d5bffce84 Mon Sep 17 00:00:00 2001 From: Daniel Thayer Date: Thu, 9 Aug 2018 13:58:43 -0500 Subject: [PATCH 32/43] Improve the travis-job script to work outside of Travis Improved the travis-job test script so that it works outside of Travis. This can be useful to test changes to the docker container config before pushing to master, for testing changes on a branch, or for debugging some problem with the tests running on Travis. This required the following changes: -The script no longer requires the TRAVIS env. variable to be set. -Added an "all" command as a more convenient way to run all steps in a build. -Added a "coverity" command-line option to do a coverity build. -Before building Bro or downloading coverity tools, do a cleanup from any previous build. Also other minor improvements (code comments, reorganization, etc.). --- testing/scripts/travis-job | 200 ++++++++++++++++++++++++++----------- 1 file changed, 140 insertions(+), 60 deletions(-) diff --git a/testing/scripts/travis-job b/testing/scripts/travis-job index 92d8d6d53d..01065900dd 100644 --- a/testing/scripts/travis-job +++ b/testing/scripts/travis-job @@ -2,58 +2,82 @@ # # This script (along with the .travis.yml file) is used by Travis CI to # build Bro and run the tests. +# +# This script can also be used outside of Travis (the "all" build step is +# especially convenient in this case). Note that if you use this script +# outside of Travis then you will need to fetch the private tests manually +# (if you don't, then the private tests will be skipped). + +usage() { + echo "usage: $0 CMD DISTRO" + echo " CMD is a build step:" + echo " install: install prereqs" + echo " build: build bro" + echo " run: run the tests" + echo " all: do all of the above" + echo " DISTRO is a Linux distro, 'travis' to run without docker, or 'coverity' to run a coverity scan" +} if [ $# -ne 2 ]; then - echo "usage: $0 CMD DISTRO" - echo " CMD is a build step (install, build, or run)" - echo " DISTRO is a Linux distro, or 'travis' to run in Travis without docker" + usage exit 1 fi step=$1 distro=$2 -# Build Bro with the coverity tools. -build_coverity() { - # Get the coverity tools - set -e +case $step in + install) ;; + build) ;; + run) ;; + all) ;; + *) echo "Error: unknown build step: $step"; usage; exit 1 ;; +esac - if [ -z "${COV_TOKEN}" ]; then - echo "Error: COV_TOKEN is not defined (should be defined in environment variables section of Travis settings for this repo)" - exit 1 - fi +# Install the coverity tools. +install_coverity() { + rm -rf coverity_tool.tgz coverity-tools cov-analysis* + + echo "Downloading coverity tools..." wget -nv https://scan.coverity.com/download/cxx/linux64 --post-data "token=${COV_TOKEN}&project=Bro" -O coverity_tool.tgz tar xzf coverity_tool.tgz - mv cov-analysis* coverity-tools rm coverity_tool.tgz + mv cov-analysis* coverity-tools +} - # Configure Bro - ./configure --prefix=`pwd`/build/root --enable-debug --disable-perftools - # Build Bro with coverity tools +# Build Bro with the coverity tools. +build_coverity() { + # Cleanup any previous build (this is really only necessary if running this + # outside of Travis). + make distclean > /dev/null + + ./configure --prefix=`pwd`/build/root --enable-debug --disable-perftools --disable-broker-tests --disable-python --disable-broctl + export PATH=`pwd`/coverity-tools/bin:$PATH cd build cov-build --dir cov-int make -j 4 + cd .. } + # Create a tar file and send it to coverity. run_coverity() { - set -e - EMAIL=bro-commits-internal@bro.org - FILE=myproject.bz2 + FILE=myproject.tgz VER=`cat VERSION` DESC=`git rev-parse HEAD` cd build - tar cjf ${FILE} cov-int + echo "Creating tar file and sending to coverity..." + tar czf ${FILE} cov-int curl --form token=${COV_TOKEN} --form email=${EMAIL} --form file=@${FILE} --form "version=${VER}" --form "description=${DESC}" https://scan.coverity.com/builds?project=Bro } -# Setup a docker container. -setup_docker() { +# Create a docker container, and install all packages needed to build Bro. +install_in_docker() { case $distro in centos_7) distro_cmds="yum -y install cmake make gcc gcc-c++ flex bison libpcap-devel openssl-devel git openssl which" @@ -83,20 +107,24 @@ setup_docker() { # Build bro in a docker container. -build_docker() { - docker exec -e TRAVIS brotest sh testing/scripts/travis-job $step travis +build_in_docker() { + docker exec brotest sh testing/scripts/travis-job build travis } # Run Bro tests in a docker container. -run_docker() { +run_in_docker() { prepare_env - docker exec -t -e TRAVIS -e TRAVIS_PULL_REQUEST -e trav_key -e trav_iv brotest sh testing/scripts/travis-job $step travis + docker exec -t -e TRAVIS -e TRAVIS_PULL_REQUEST -e trav_key -e trav_iv brotest sh testing/scripts/travis-job run travis } # Build Bro. build() { + # Cleanup any previous build (this is really only necessary if running this + # outside of Travis). + make distclean > /dev/null + # Skip building broker tests, python bindings, and broctl, as these are # not needed by the bro tests. ./configure --build-type=Release --disable-broker-tests --disable-python --disable-broctl && make -j 2 @@ -107,6 +135,9 @@ build() { # hard-coded multiple times in this script. prepare_env() { if [ -z "$trav_key" ]; then + # This hash value is found by logging into the Travis CI website, + # and looking at the settings in the bro repo (look in the + # "Environment Variables" section). hash=6a6fe747ff7b eval "trav_key=\$encrypted_${hash}_key" eval "trav_iv=\$encrypted_${hash}_iv" @@ -117,27 +148,15 @@ prepare_env() { } -# Run Bro tests. -run() { - echo - echo "Running unit tests ##################################################" - echo - cd testing/btest - # Must specify a value for "-j" option, otherwise Travis uses a huge value. - ../../aux/btest/btest -j 4 -d - ret=$? - - echo - echo "Getting external tests ##############################################" - echo - cd ../external - - set -e - - make init +# Get the private tests. +get_private_tests() { prepare_env - if [ -n "$trav_key" ] && [ -n "$trav_iv" ]; then + if [ "${TRAVIS}" != "true" ]; then + # When not running in the Travis environment, just skip trying to get + # the private tests. + echo "Note: skipping private tests (to run them, do a git clone of the private testing repo in the 'testing/external' directory before running this script)." + elif [ -n "$trav_key" ] && [ -n "$trav_iv" ]; then curl https://www.bro.org/static/travis-ci/travis_key.enc -o travis_key.enc openssl aes-256-cbc -K $trav_key -iv $trav_iv -in travis_key.enc -out travis_key -d chmod 600 travis_key @@ -154,6 +173,34 @@ run() { echo "Error: cannot get private tests because encrypted env. variables are not defined." exit 1 fi +} + + +# Run Bro tests. +run() { + echo + echo "Running unit tests ##################################################" + echo + cd testing/btest + + set +e + # Must specify a value for "-j" option, otherwise Travis uses a huge value. + ../../aux/btest/btest -j 4 -d + ret=$? + set -e + + echo + echo "Getting external tests ##############################################" + echo + cd ../external + + if [ ! -d bro-testing ]; then + make init + fi + + if [ ! -d bro-testing-private ]; then + get_private_tests + fi echo echo "Running external tests ##############################################" @@ -164,9 +211,8 @@ run() { exit $ret } -# Output the contents of diag.log when a test fails. +# Show failed tests (not skipped tests) from diag.log when a test fails. showdiag() { - # Show failed tests only, not skipped tests. f=bro-testing/diag.log grep -qs '... failed$' $f && \ @@ -178,18 +224,22 @@ showdiag() { exit 1 } -if [ "$step" != "install" ] && [ "$step" != "build" ] && [ "$step" != "run" ]; then - echo "Error: unknown build step: $step" +# Remove the docker container. +remove_container() { + echo "Removing the docker container..." + docker rm -f brotest > /dev/null +} + + +if [ ! -f testing/scripts/travis-job ]; then + echo "Error: must change directory to root of bro source tree before running this script." exit 1 fi -if [ "${TRAVIS}" != "true" ]; then - echo "$0: this script is intended for Travis CI" - exit 1 -fi +set -e if [ "${TRAVIS_EVENT_TYPE}" = "cron" ]; then - # Run the coverity scan from a Travis CI cron job. + # This is a Travis CI cron job, so check the job number. # Extract second component of the job number. if [ -z "${TRAVIS_JOB_NUMBER}" ]; then @@ -204,14 +254,35 @@ if [ "${TRAVIS_EVENT_TYPE}" = "cron" ]; then echo "Coverity scan is performed only in the first job of this build" exit 0 fi +fi - # This is split up into two steps because the build outputs thousands of - # lines (which are conveniently collapsed into a single line in the - # "Job log" on the Travis CI web site). - if [ "$step" = "build" ]; then + +if [ "${TRAVIS_EVENT_TYPE}" = "cron" ] || [ "$distro" = "coverity" ]; then + # Run coverity scan when this script is run from a Travis cron job, or + # if the user specifies the "coverity" distro. + + # Check if the project token is available (this is a secret value and + # should not be hard-coded in this script). This value can be found by + # logging into the coverity scan web site and looking in the project + # settings. + if [ -z "${COV_TOKEN}" ]; then + echo "Error: COV_TOKEN is not defined (should be defined in environment variables section of Travis settings for this repo)" + exit 1 + fi + + # The "build" and "run" steps are split up into separate steps because the + # build outputs thousands of lines (which are conveniently collapsed into + # a single line when viewing the "Job log" on the Travis CI web site). + if [ "$step" = "install" ]; then + install_coverity + elif [ "$step" = "build" ]; then build_coverity elif [ "$step" = "run" ]; then run_coverity + elif [ "$step" = "all" ]; then + install_coverity + build_coverity + run_coverity fi elif [ "$distro" = "travis" ]; then # Build bro and run tests. @@ -223,15 +294,24 @@ elif [ "$distro" = "travis" ]; then build elif [ "$step" = "run" ]; then run + elif [ "$step" = "all" ]; then + build + run fi else # Build bro and run tests in a docker container. if [ "$step" = "install" ]; then - setup_docker + install_in_docker elif [ "$step" = "build" ]; then - build_docker + build_in_docker elif [ "$step" = "run" ]; then - run_docker + run_in_docker + elif [ "$step" = "all" ]; then + install_in_docker + build_in_docker + run_in_docker + # If all tests pass, then remove the docker container. + remove_container fi fi From 116079a9ad5e4e9d096be36e97879b69f036b4a2 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Thu, 18 Jan 2018 11:09:12 -0800 Subject: [PATCH 33/43] Make parsing of booleans a little bit more lenient. This makes the input framework (and everything else that uses the Ascii parser) accept 0 and 1 as valid values for booleans. --- src/threading/formatters/Ascii.cc | 4 ++-- .../btest/Baseline/scripts.base.frameworks.input.basic/out | 2 +- testing/btest/scripts/base/frameworks/input/basic.bro | 5 +++-- testing/btest/scripts/base/frameworks/input/reread.bro | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/threading/formatters/Ascii.cc b/src/threading/formatters/Ascii.cc index e96290e793..6491063585 100644 --- a/src/threading/formatters/Ascii.cc +++ b/src/threading/formatters/Ascii.cc @@ -227,9 +227,9 @@ threading::Value* Ascii::ParseValue(const string& s, const string& name, TypeTag } case TYPE_BOOL: - if ( s == "T" ) + if ( s == "T" || s == "1" ) val->val.int_val = 1; - else if ( s == "F" ) + else if ( s == "F" || s == "0" ) val->val.int_val = 0; else { diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.basic/out b/testing/btest/Baseline/scripts.base.frameworks.input.basic/out index 3f288d5c54..5cc19d85a2 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.input.basic/out +++ b/testing/btest/Baseline/scripts.base.frameworks.input.basic/out @@ -1,5 +1,5 @@ { -[-42] = [b=T, e=SSH::LOG, c=21, p=123/unknown, pp=5/icmp, sn=10.0.0.0/24, a=1.2.3.4, d=3.14, t=1315801931.273616, iv=100.0, s=hurz, ns=4242, sc={ +[-42] = [b=T, bt=T, e=SSH::LOG, c=21, p=123/unknown, pp=5/icmp, sn=10.0.0.0/24, a=1.2.3.4, d=3.14, t=1315801931.273616, iv=100.0, s=hurz, ns=4242, sc={ 2, 4, 1, diff --git a/testing/btest/scripts/base/frameworks/input/basic.bro b/testing/btest/scripts/base/frameworks/input/basic.bro index e77a418f0d..356b87d70b 100644 --- a/testing/btest/scripts/base/frameworks/input/basic.bro +++ b/testing/btest/scripts/base/frameworks/input/basic.bro @@ -7,9 +7,9 @@ redef exit_only_after_terminate = T; @TEST-START-FILE input.log #separator \x09 #path ssh -#fields b i e c p pp sn a d t iv s sc ss se vc ve ns +#fields b bt i e c p pp sn a d t iv s sc ss se vc ve ns #types bool int enum count port port subnet addr double time interval string table table table vector vector string -T -42 SSH::LOG 21 123 5/icmp 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY 4242 +T 1 -42 SSH::LOG 21 123 5/icmp 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY 4242 @TEST-END-FILE @load base/protocols/ssh @@ -26,6 +26,7 @@ type Idx: record { type Val: record { b: bool; + bt: bool; e: Log::ID; c: count; p: port; diff --git a/testing/btest/scripts/base/frameworks/input/reread.bro b/testing/btest/scripts/base/frameworks/input/reread.bro index 4199093543..e4bb09df39 100644 --- a/testing/btest/scripts/base/frameworks/input/reread.bro +++ b/testing/btest/scripts/base/frameworks/input/reread.bro @@ -43,7 +43,7 @@ T -42 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz F -43 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} F -44 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} F -45 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} -F -46 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} +0 -46 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} F -47 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} F -48 SSH::LOG 21 123 10.0.0.0/24 1.2.3.4 3.14 1315801931.273616 100.000000 hurz 2,4,1,3 CC,AA,BB EMPTY 10,20,30 EMPTY SSH::foo\x0a{ \x0aif (0 < SSH::i) \x0a\x09return (Foo);\x0aelse\x0a\x09return (Bar);\x0a\x0a} @TEST-END-FILE From 26ea1999ec7b3a7b4ecf2cecdcb8c09dc827b537 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Fri, 10 Aug 2018 11:23:33 -0700 Subject: [PATCH 34/43] Ascii formatter: do not complain about port text. The ascii formatter already was happy to read ports in the form "42/tcp"; however it emitted a warning message for each line. This patch fixes this and adds a bit more testing for the existing behavior. --- src/threading/formatters/Ascii.cc | 14 ++++++ .../bro..stderr | 1 + .../bro.config.log | 34 +++++++------- .../bro..stderr | 2 + .../bro..stdout | 4 ++ .../scripts/base/frameworks/config/basic.bro | 5 +++ .../base/frameworks/input/port-embedded.bro | 44 +++++++++++++++++++ 7 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.frameworks.config.basic/bro..stderr create mode 100644 testing/btest/Baseline/scripts.base.frameworks.input.port-embedded/bro..stderr create mode 100644 testing/btest/Baseline/scripts.base.frameworks.input.port-embedded/bro..stdout create mode 100644 testing/btest/scripts/base/frameworks/input/port-embedded.bro diff --git a/src/threading/formatters/Ascii.cc b/src/threading/formatters/Ascii.cc index 6491063585..76b1ba4b04 100644 --- a/src/threading/formatters/Ascii.cc +++ b/src/threading/formatters/Ascii.cc @@ -261,8 +261,10 @@ threading::Value* Ascii::ParseValue(const string& s, const string& name, TypeTag break; case TYPE_PORT: + { val->val.port_val.proto = TRANSPORT_UNKNOWN; pos = s.find('/'); + string numberpart; if ( pos != std::string::npos && s.length() > pos + 1 ) { auto proto = s.substr(pos+1); @@ -272,10 +274,22 @@ threading::Value* Ascii::ParseValue(const string& s, const string& name, TypeTag val->val.port_val.proto = TRANSPORT_UDP; else if ( strtolower(proto) == "icmp" ) val->val.port_val.proto = TRANSPORT_ICMP; + else if ( strtolower(proto) == "unknown" ) + val->val.port_val.proto = TRANSPORT_UNKNOWN; + else + GetThread()->Warning(GetThread()->Fmt("Port '%s' contained unknown protocol '%s'", s.c_str(), proto.c_str())); + } + + // make the string end at the position of "/"; + if ( pos != std::string::npos && pos > 0 ) + { + numberpart = s.substr(0, pos); + start = numberpart.c_str(); } val->val.port_val.port = strtoull(start, &end, 10); if ( CheckNumberError(start, end) ) goto parse_error; + } break; case TYPE_SUBNET: diff --git a/testing/btest/Baseline/scripts.base.frameworks.config.basic/bro..stderr b/testing/btest/Baseline/scripts.base.frameworks.config.basic/bro..stderr new file mode 100644 index 0000000000..977e8fc37a --- /dev/null +++ b/testing/btest/Baseline/scripts.base.frameworks.config.basic/bro..stderr @@ -0,0 +1 @@ +received termination signal diff --git a/testing/btest/Baseline/scripts.base.frameworks.config.basic/bro.config.log b/testing/btest/Baseline/scripts.base.frameworks.config.basic/bro.config.log index b1e03411e5..0d96d0f111 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.config.basic/bro.config.log +++ b/testing/btest/Baseline/scripts.base.frameworks.config.basic/bro.config.log @@ -3,21 +3,23 @@ #empty_field (empty) #unset_field - #path config -#open 2017-10-11-20-23-11 +#open 2018-08-10-18-16-52 #fields ts id old_value new_value location #types time string string string string -1507753391.587107 testbool T F ../configfile -1507753391.587107 testcount 0 1 ../configfile -1507753391.587107 testcount 1 2 ../configfile -1507753391.587107 testint 0 -1 ../configfile -1507753391.587107 testenum SSH::LOG Conn::LOG ../configfile -1507753391.587107 testport 42/tcp 45/unknown ../configfile -1507753391.587107 testaddr 127.0.0.1 127.0.0.1 ../configfile -1507753391.587107 testaddr 127.0.0.1 2607:f8b0:4005:801::200e ../configfile -1507753391.587107 testinterval 1.0 sec 60.0 ../configfile -1507753391.587107 testtime 0.0 1507321987.0 ../configfile -1507753391.587107 test_set (empty) b,c,a,d,erdbeerschnitzel ../configfile -1507753391.587107 test_vector (empty) 1,2,3,4,5,6 ../configfile -1507753391.587107 test_set b,c,a,d,erdbeerschnitzel (empty) ../configfile -1507753391.587107 test_set (empty) \x2d ../configfile -#close 2017-10-11-20-23-11 +1533925012.140634 testbool T F ../configfile +1533925012.140634 testcount 0 1 ../configfile +1533925012.140634 testcount 1 2 ../configfile +1533925012.140634 testint 0 -1 ../configfile +1533925012.140634 testenum SSH::LOG Conn::LOG ../configfile +1533925012.140634 testport 42/tcp 45/unknown ../configfile +1533925012.140634 testporttcp 40/udp 42/tcp ../configfile +1533925012.140634 testportudp 40/tcp 42/udp ../configfile +1533925012.140634 testaddr 127.0.0.1 127.0.0.1 ../configfile +1533925012.140634 testaddr 127.0.0.1 2607:f8b0:4005:801::200e ../configfile +1533925012.140634 testinterval 1.0 sec 60.0 ../configfile +1533925012.140634 testtime 0.0 1507321987.0 ../configfile +1533925012.140634 test_set (empty) b,c,a,d,erdbeerschnitzel ../configfile +1533925012.140634 test_vector (empty) 1,2,3,4,5,6 ../configfile +1533925012.140634 test_set b,c,a,d,erdbeerschnitzel (empty) ../configfile +1533925012.140634 test_set (empty) \x2d ../configfile +#close 2018-08-10-18-16-52 diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.port-embedded/bro..stderr b/testing/btest/Baseline/scripts.base.frameworks.input.port-embedded/bro..stderr new file mode 100644 index 0000000000..fee70a8699 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.frameworks.input.port-embedded/bro..stderr @@ -0,0 +1,2 @@ +warning: ../input.log/Input::READER_ASCII: Port '50/trash' contained unknown protocol 'trash' +received termination signal diff --git a/testing/btest/Baseline/scripts.base.frameworks.input.port-embedded/bro..stdout b/testing/btest/Baseline/scripts.base.frameworks.input.port-embedded/bro..stdout new file mode 100644 index 0000000000..d1d886b370 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.frameworks.input.port-embedded/bro..stdout @@ -0,0 +1,4 @@ +[i=1.2.3.4], [p=80/tcp] +[i=1.2.3.5], [p=52/udp] +[i=1.2.3.6], [p=30/unknown] +[i=1.2.3.7], [p=50/unknown] diff --git a/testing/btest/scripts/base/frameworks/config/basic.bro b/testing/btest/scripts/base/frameworks/config/basic.bro index 3b72f6572d..f5a02983fd 100644 --- a/testing/btest/scripts/base/frameworks/config/basic.bro +++ b/testing/btest/scripts/base/frameworks/config/basic.bro @@ -1,6 +1,7 @@ # @TEST-EXEC: btest-bg-run bro bro -b %INPUT # @TEST-EXEC: btest-bg-wait 10 # @TEST-EXEC: btest-diff bro/config.log +# @TEST-EXEC: btest-diff bro/.stderr @load base/frameworks/config @load base/protocols/conn @@ -16,6 +17,8 @@ testcount 2 testint -1 testenum Conn::LOG testport 45 +testporttcp 42/tcp +testportudp 42/udp testaddr 127.0.0.1 testaddr 2607:f8b0:4005:801::200e testinterval 60 @@ -35,6 +38,8 @@ export { option testint: int = 0; option testenum = SSH::LOG; option testport = 42/tcp; + option testporttcp = 40/udp; + option testportudp = 40/tcp; option testaddr = 127.0.0.1; option testtime = network_time(); option testinterval = 1sec; diff --git a/testing/btest/scripts/base/frameworks/input/port-embedded.bro b/testing/btest/scripts/base/frameworks/input/port-embedded.bro new file mode 100644 index 0000000000..8aab733069 --- /dev/null +++ b/testing/btest/scripts/base/frameworks/input/port-embedded.bro @@ -0,0 +1,44 @@ +# @TEST-EXEC: btest-bg-run bro bro -b %INPUT +# @TEST-EXEC: btest-bg-wait 10 +# @TEST-EXEC: btest-diff bro/.stdout +# @TEST-EXEC: btest-diff bro/.stderr + +@TEST-START-FILE input.log +#fields i p +1.2.3.4 80/tcp +1.2.3.5 52/udp +1.2.3.6 30/unknown +1.2.3.7 50/trash +@TEST-END-FILE + +redef exit_only_after_terminate = T; + +redef InputAscii::empty_field = "EMPTY"; + +module A; + +type Idx: record { + i: addr; +}; + +type Val: record { + p: port; +}; + +global servers: table[addr] of Val = table(); + +event line(description: Input::TableDescription, tpe: Input::Event, left: Idx, right: Val) + { + print left, right; + } + +event bro_init() + { + Input::add_table([$source="../input.log", $name="input", $idx=Idx, $val=Val, $ev=line, $destination=servers]); + } + +event Input::end_of_data(name: string, source: string) + { + Input::remove("input"); + terminate(); + } From c34fbee0d1a766dd7e864b28dbbf6380360ec990 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Fri, 10 Aug 2018 11:45:37 -0700 Subject: [PATCH 35/43] Make options redef-able by default. --- src/ID.cc | 16 ++++++++++++++++ src/ID.h | 2 +- testing/btest/Baseline/core.option-redef/.stdout | 1 + testing/btest/core/option-redef.bro | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ID.cc b/src/ID.cc index 4216422225..a68abb6264 100644 --- a/src/ID.cc +++ b/src/ID.cc @@ -294,6 +294,22 @@ void ID::RemoveAttr(attr_tag a) } } +void ID::SetOption() + { + if ( is_option ) + return; + + is_option = true; + + // option implied redefinable + if ( ! IsRedefinable() ) + { + attr_list* attr = new attr_list; + attr->append(new Attr(ATTR_REDEF)); + AddAttrs(new Attributes(attr, Type(), false)); + } + } + void ID::EvalFunc(Expr* ef, Expr* ev) { Expr* arg1 = new ConstExpr(val->Ref()); diff --git a/src/ID.h b/src/ID.h index 442a13dfcc..18754584df 100644 --- a/src/ID.h +++ b/src/ID.h @@ -60,7 +60,7 @@ public: void SetConst() { is_const = true; } bool IsConst() const { return is_const; } - void SetOption() { is_option = true; } + void SetOption(); bool IsOption() const { return is_option; } void SetEnumConst() { is_enum_const = true; } diff --git a/testing/btest/Baseline/core.option-redef/.stdout b/testing/btest/Baseline/core.option-redef/.stdout index 1e8b314962..baf1966653 100644 --- a/testing/btest/Baseline/core.option-redef/.stdout +++ b/testing/btest/Baseline/core.option-redef/.stdout @@ -1 +1,2 @@ 6 +7 diff --git a/testing/btest/core/option-redef.bro b/testing/btest/core/option-redef.bro index 05706ab48b..3d67a9a755 100644 --- a/testing/btest/core/option-redef.bro +++ b/testing/btest/core/option-redef.bro @@ -2,11 +2,15 @@ # @TEST-EXEC: btest-diff .stdout # options are allowed to be redef-able. +# And they are even redef-able by default. option testopt = 5 &redef; redef testopt = 6; +option anotheropt = 6; +redef anotheropt = 7; event bro_init() { print testopt; + print anotheropt; } From 7b44a6499433aa1817a5c1b21b46f4e864621dae Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Fri, 10 Aug 2018 14:28:17 -0700 Subject: [PATCH 36/43] Fix test that fails now that options are automatically redefable. --- testing/btest/Baseline/core.option-errors-4/.stderr | 1 - testing/btest/core/option-errors.bro | 5 ----- 2 files changed, 6 deletions(-) delete mode 100644 testing/btest/Baseline/core.option-errors-4/.stderr diff --git a/testing/btest/Baseline/core.option-errors-4/.stderr b/testing/btest/Baseline/core.option-errors-4/.stderr deleted file mode 100644 index b443da2eb9..0000000000 --- a/testing/btest/Baseline/core.option-errors-4/.stderr +++ /dev/null @@ -1 +0,0 @@ -error in /Users/johanna/corelight/bro/testing/btest/.tmp/core.option-errors-4/option-errors.bro, line 2 and /Users/johanna/corelight/bro/testing/btest/.tmp/core.option-errors-4/option-errors.bro, line 3: already defined (testopt) diff --git a/testing/btest/core/option-errors.bro b/testing/btest/core/option-errors.bro index 6a53598650..6a9a8f1db6 100644 --- a/testing/btest/core/option-errors.bro +++ b/testing/btest/core/option-errors.bro @@ -11,8 +11,3 @@ option testbool : bool; option testopt = 5; testopt = 6; - -@TEST-START-NEXT - -option testopt = 5; -redef testopt = 6; From 9f12b56105be226d26c29c92e7ccb2758d1f8436 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Aug 2018 16:58:27 -0500 Subject: [PATCH 37/43] Misc. unit test improvements --- .../Baseline/bifs.hll_large_estimate/out | 2 +- .../logger-1..stdout | 6 ++++ .../manager-1..stdout | 1 + .../proxy-1..stdout | 1 + .../proxy-2..stdout | 1 + .../worker-1..stdout | 1 + .../worker-2..stdout | 1 + testing/btest/bifs/hll_large_estimate.bro | 2 +- .../frameworks/cluster/start-it-up-logger.bro | 32 ++++++++++++------- .../base/frameworks/config/updates.bro | 6 ++-- testing/external/scripts/diff-all | 2 +- 11 files changed, 38 insertions(+), 17 deletions(-) diff --git a/testing/btest/Baseline/bifs.hll_large_estimate/out b/testing/btest/Baseline/bifs.hll_large_estimate/out index 6897673f4e..c0bbe2b31d 100644 --- a/testing/btest/Baseline/bifs.hll_large_estimate/out +++ b/testing/btest/Baseline/bifs.hll_large_estimate/out @@ -1,3 +1,3 @@ Ok error -171249.90868 +167377.950902 Ok error diff --git a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/logger-1..stdout b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/logger-1..stdout index e10770a5cc..15baa652c9 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/logger-1..stdout +++ b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/logger-1..stdout @@ -2,4 +2,10 @@ Connected to a peer Connected to a peer Connected to a peer Connected to a peer +got fully_connected event from, worker-1 Connected to a peer +got fully_connected event from, proxy-1 +got fully_connected event from, proxy-2 +got fully_connected event from, manager-1 +got fully_connected event from, worker-2 +termination condition met: shutting down diff --git a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/manager-1..stdout b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/manager-1..stdout index e10770a5cc..b7b8f3e3b6 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/manager-1..stdout +++ b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/manager-1..stdout @@ -3,3 +3,4 @@ Connected to a peer Connected to a peer Connected to a peer Connected to a peer +sent fully_connected event diff --git a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/proxy-1..stdout b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/proxy-1..stdout index 7c8eb5ee83..328d7c91a3 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/proxy-1..stdout +++ b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/proxy-1..stdout @@ -2,3 +2,4 @@ Connected to a peer Connected to a peer Connected to a peer Connected to a peer +sent fully_connected event diff --git a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/proxy-2..stdout b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/proxy-2..stdout index 7c8eb5ee83..328d7c91a3 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/proxy-2..stdout +++ b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/proxy-2..stdout @@ -2,3 +2,4 @@ Connected to a peer Connected to a peer Connected to a peer Connected to a peer +sent fully_connected event diff --git a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/worker-1..stdout b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/worker-1..stdout index 7c8eb5ee83..328d7c91a3 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/worker-1..stdout +++ b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/worker-1..stdout @@ -2,3 +2,4 @@ Connected to a peer Connected to a peer Connected to a peer Connected to a peer +sent fully_connected event diff --git a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/worker-2..stdout b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/worker-2..stdout index 7c8eb5ee83..328d7c91a3 100644 --- a/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/worker-2..stdout +++ b/testing/btest/Baseline/scripts.base.frameworks.cluster.start-it-up-logger/worker-2..stdout @@ -2,3 +2,4 @@ Connected to a peer Connected to a peer Connected to a peer Connected to a peer +sent fully_connected event diff --git a/testing/btest/bifs/hll_large_estimate.bro b/testing/btest/bifs/hll_large_estimate.bro index 2059e47568..b17b50678d 100644 --- a/testing/btest/bifs/hll_large_estimate.bro +++ b/testing/btest/bifs/hll_large_estimate.bro @@ -8,7 +8,7 @@ event bro_init() { - local cp: opaque of cardinality = hll_cardinality_init(0.1, 0.99); + local cp: opaque of cardinality = hll_cardinality_init(0.1, 1.0); local base: count = 2130706432; # 127.0.0.0 local i: count = 0; while ( ++i < 170000 ) diff --git a/testing/btest/scripts/base/frameworks/cluster/start-it-up-logger.bro b/testing/btest/scripts/base/frameworks/cluster/start-it-up-logger.bro index 6bb9dcbc03..b973705c97 100644 --- a/testing/btest/scripts/base/frameworks/cluster/start-it-up-logger.bro +++ b/testing/btest/scripts/base/frameworks/cluster/start-it-up-logger.bro @@ -1,13 +1,13 @@ # @TEST-SERIALIZE: comm # -# @TEST-EXEC: btest-bg-run logger-1 CLUSTER_NODE=logger-1 BROPATH=$BROPATH:.. bro %INPUT +# @TEST-EXEC: btest-bg-run logger-1 CLUSTER_NODE=logger-1 BROPATH=$BROPATH:.. bro %INPUT # @TEST-EXEC: btest-bg-run manager-1 CLUSTER_NODE=manager-1 BROPATH=$BROPATH:.. bro %INPUT -# @TEST-EXEC: btest-bg-run proxy-1 CLUSTER_NODE=proxy-1 BROPATH=$BROPATH:.. bro %INPUT -# @TEST-EXEC: btest-bg-run proxy-2 CLUSTER_NODE=proxy-2 BROPATH=$BROPATH:.. bro %INPUT -# @TEST-EXEC: btest-bg-run worker-1 CLUSTER_NODE=worker-1 BROPATH=$BROPATH:.. bro %INPUT -# @TEST-EXEC: btest-bg-run worker-2 CLUSTER_NODE=worker-2 BROPATH=$BROPATH:.. bro %INPUT +# @TEST-EXEC: btest-bg-run proxy-1 CLUSTER_NODE=proxy-1 BROPATH=$BROPATH:.. bro %INPUT +# @TEST-EXEC: btest-bg-run proxy-2 CLUSTER_NODE=proxy-2 BROPATH=$BROPATH:.. bro %INPUT +# @TEST-EXEC: btest-bg-run worker-1 CLUSTER_NODE=worker-1 BROPATH=$BROPATH:.. bro %INPUT +# @TEST-EXEC: btest-bg-run worker-2 CLUSTER_NODE=worker-2 BROPATH=$BROPATH:.. bro %INPUT # @TEST-EXEC: btest-bg-wait 30 -# @TEST-EXEC: btest-diff logger-1/.stdout +# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-sort btest-diff logger-1/.stdout # @TEST-EXEC: btest-diff manager-1/.stdout # @TEST-EXEC: btest-diff proxy-1/.stdout # @TEST-EXEC: btest-diff proxy-2/.stdout @@ -30,20 +30,27 @@ redef Cluster::retry_interval = 1sec; redef Broker::default_listen_retry = 1sec; redef Broker::default_connect_retry = 1sec; -global fully_connected: event(); - global peer_count = 0; global fully_connected_nodes = 0; -event fully_connected() +event fully_connected(n: string) { ++fully_connected_nodes; if ( Cluster::node == "logger-1" ) { + print "got fully_connected event from", n; + if ( peer_count == 5 && fully_connected_nodes == 5 ) + { + print "termination condition met: shutting down"; terminate(); + } + } + else + { + print "sent fully_connected event"; } } @@ -60,17 +67,20 @@ event Broker::peer_added(endpoint: Broker::EndpointInfo, msg: string) if ( Cluster::node == "logger-1" ) { if ( peer_count == 5 && fully_connected_nodes == 5 ) + { + print "termination condition met: shutting down"; terminate(); + } } else if ( Cluster::node == "manager-1" ) { if ( peer_count == 5 ) - event fully_connected(); + event fully_connected(Cluster::node); } else { if ( peer_count == 4 ) - event fully_connected(); + event fully_connected(Cluster::node); } } diff --git a/testing/btest/scripts/base/frameworks/config/updates.bro b/testing/btest/scripts/base/frameworks/config/updates.bro index 1e523c752f..a4ee557e27 100644 --- a/testing/btest/scripts/base/frameworks/config/updates.bro +++ b/testing/btest/scripts/base/frameworks/config/updates.bro @@ -1,11 +1,11 @@ # @TEST-EXEC: btest-bg-run bro bro -b %INPUT -# @TEST-EXEC: $SCRIPTS/wait-for-file bro/got1 5 || (btest-bg-wait -k 1 && false) +# @TEST-EXEC: $SCRIPTS/wait-for-file bro/got1 10 || (btest-bg-wait -k 1 && false) # @TEST-EXEC: mv configfile2 configfile # @TEST-EXEC: touch configfile -# @TEST-EXEC: $SCRIPTS/wait-for-file bro/got2 5 || (btest-bg-wait -k 1 && false) +# @TEST-EXEC: $SCRIPTS/wait-for-file bro/got2 10 || (btest-bg-wait -k 1 && false) # @TEST-EXEC: mv configfile3 configfile # @TEST-EXEC: touch configfile -# @TEST-EXEC: $SCRIPTS/wait-for-file bro/got3 5 || (btest-bg-wait -k 1 && false) +# @TEST-EXEC: $SCRIPTS/wait-for-file bro/got3 10 || (btest-bg-wait -k 1 && false) # @TEST-EXEC: mv configfile4 configfile # @TEST-EXEC: touch configfile # @TEST-EXEC: btest-bg-wait 10 diff --git a/testing/external/scripts/diff-all b/testing/external/scripts/diff-all index e84416c088..d51f3b294f 100755 --- a/testing/external/scripts/diff-all +++ b/testing/external/scripts/diff-all @@ -22,7 +22,7 @@ files_cwd=`ls $@` files_baseline=`cd $TEST_BASELINE && ls $@` for i in `echo $files_cwd $files_baseline | sort | uniq`; do - if [[ "$i" != "loaded_scripts.log" && "$i" != "prof.log" && "$i" != "debug.log" && "$i" != "stats.log" ]]; then + if [[ "$i" != "loaded_scripts.log" && "$i" != "prof.log" && "$i" != "debug.log" && "$i" != "stats.log" && "$i" != broker_*.log ]]; then if [[ "$i" == "reporter.log" ]]; then # Do not diff the reporter.log if it only complains about missing From 083947af4111b0437b1061f74ffc95d14b74c167 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Aug 2018 17:08:26 -0500 Subject: [PATCH 38/43] Update default broker threading configuration Now defaults to a max of 4 threads typically indepedent of core count (previously could go up to a hard cap of 8). Also now allow controlling this setting via BRO_BROKER_MAX_THREADS environment variable. --- scripts/base/frameworks/broker/main.bro | 8 +++--- src/broker/Manager.cc | 35 +++++++++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/scripts/base/frameworks/broker/main.bro b/scripts/base/frameworks/broker/main.bro index 0fdf289ee6..fb73f78d3c 100644 --- a/scripts/base/frameworks/broker/main.bro +++ b/scripts/base/frameworks/broker/main.bro @@ -56,9 +56,11 @@ export { ## control mechanisms). const congestion_queue_size = 200 &redef; - ## Max number of threads to use for Broker/CAF functionality. - ## Using zero will cause this to be automatically determined - ## based on number of available CPUs. + ## Max number of threads to use for Broker/CAF functionality. Setting to + ## zero implies using the value of BRO_BROKER_MAX_THREADS environment + ## variable, if set, or else typically defaults to 4 (actually 2 threads + ## when simply reading offline pcaps as there's not expected to be any + ## communication and more threads just adds more overhead). const max_threads = 0 &redef; ## Max number of microseconds for under-utilized Broker/CAF diff --git a/src/broker/Manager.cc b/src/broker/Manager.cc index 9712516b74..1d3696efa0 100644 --- a/src/broker/Manager.cc +++ b/src/broker/Manager.cc @@ -184,22 +184,29 @@ void Manager::InitPostScript() config.set("scheduler.max-threads", max_threads); else { - // On high-core-count systems, spawning one thread per core - // can lead to significant performance problems even if most - // threads are under-utilized. Related: - // https://github.com/actor-framework/actor-framework/issues/699 - if ( reading_pcaps ) - config.set("scheduler.max-threads", 2u); + auto max_threads_env = getenv("BRO_BROKER_MAX_THREADS"); + + if ( max_threads_env ) + config.set("scheduler.max-threads", atoi(max_threads_env)); else { - auto hc = std::thread::hardware_concurrency(); - - if ( hc > 8u ) - hc = 8u; - else if ( hc < 4u) - hc = 4u; - - config.set("scheduler.max-threads", hc); + // On high-core-count systems, letting CAF spawn a thread per core + // can lead to significant performance problems even if most + // threads are under-utilized. Related: + // https://github.com/actor-framework/actor-framework/issues/699 + if ( reading_pcaps ) + config.set("scheduler.max-threads", 2u); + else + // If the goal was to map threads to actors, 4 threads seems + // like a minimal default that could make sense -- the main + // actors that should be doing work are (1) the core, + // (2) the subscriber, (3) data stores (actually made of + // a frontend + proxy actor). Number of data stores may + // actually vary, but lumped togather for simplicity. A (4) + // may be CAF's multiplexing or other internals... + // 4 is also the minimum number that CAF uses by default, + // even for systems with less than 4 cores. + config.set("scheduler.max-threads", 4u); } } From 67524f26d583d375ccdebb1cb7b927241a19e3c5 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Aug 2018 17:12:53 -0500 Subject: [PATCH 39/43] Immediately apply broker subscriptions made during bro_init() Otherwise that's begging for unit test failures due to races --- CHANGES | 10 ++++++++++ VERSION | 2 +- aux/broker | 2 +- scripts/base/frameworks/broker/main.bro | 5 ++++- src/broker/Manager.cc | 5 +++-- src/broker/Manager.h | 4 ++++ src/main.cc | 1 + 7 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index fabb87ba43..325632239d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,14 @@ +2.5-831 | 2018-08-10 17:12:53 -0500 + + * Immediately apply broker subscriptions made during bro_init() + (Jon Siwek, Corelight) + + * Update default broker threading configuration to use 4 threads and allow + tuning via BRO_BROKER_MAX_THREADS env. variable (Jon Siwek, Corelight) + + * Misc. unit test improvements (Jon Siwek, Corelight) + 2.5-826 | 2018-08-08 13:09:27 -0700 * Add support for code coverage statistics for bro source files after running btest diff --git a/VERSION b/VERSION index ca5b6f51d7..e6f8b2c2ac 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-826 +2.5-831 diff --git a/aux/broker b/aux/broker index 3ab9b647ee..e0f9f6504d 160000 --- a/aux/broker +++ b/aux/broker @@ -1 +1 @@ -Subproject commit 3ab9b647eedf6be674067198551d57b2b17e86d1 +Subproject commit e0f9f6504db9285a48e0be490abddf959999a404 diff --git a/scripts/base/frameworks/broker/main.bro b/scripts/base/frameworks/broker/main.bro index fb73f78d3c..645c2b4382 100644 --- a/scripts/base/frameworks/broker/main.bro +++ b/scripts/base/frameworks/broker/main.bro @@ -261,7 +261,8 @@ export { global publish_id: function(topic: string, id: string): bool; ## Register interest in all peer event messages that use a certain topic - ## prefix. + ## prefix. Note that subscriptions may not be altered immediately after + ## calling (except during :bro:see:`bro_init`). ## ## topic_prefix: a prefix to match against remote message topics. ## e.g. an empty prefix matches everything and "a" matches @@ -271,6 +272,8 @@ export { global subscribe: function(topic_prefix: string): bool; ## Unregister interest in all peer event messages that use a topic prefix. + ## Note that subscriptions may not be altered immediately after calling + ## (except during :bro:see:`bro_init`). ## ## topic_prefix: a prefix previously supplied to a successful call to ## :bro:see:`Broker::subscribe`. diff --git a/src/broker/Manager.cc b/src/broker/Manager.cc index 1d3696efa0..ca5ac53c96 100644 --- a/src/broker/Manager.cc +++ b/src/broker/Manager.cc @@ -137,6 +137,7 @@ Manager::Manager(bool arg_reading_pcaps) { bound_port = 0; reading_pcaps = arg_reading_pcaps; + after_bro_init = false; peer_count = 0; log_topic_func = nullptr; vector_of_data_type = nullptr; @@ -847,14 +848,14 @@ RecordVal* Manager::MakeEvent(val_list* args, Frame* frame) bool Manager::Subscribe(const string& topic_prefix) { DBG_LOG(DBG_BROKER, "Subscribing to topic prefix %s", topic_prefix.c_str()); - bstate->subscriber.add_topic(topic_prefix); + bstate->subscriber.add_topic(topic_prefix, ! after_bro_init); return true; } bool Manager::Unsubscribe(const string& topic_prefix) { DBG_LOG(DBG_BROKER, "Unsubscribing from topic prefix %s", topic_prefix.c_str()); - bstate->subscriber.remove_topic(topic_prefix); + bstate->subscriber.remove_topic(topic_prefix, ! after_bro_init); return true; } diff --git a/src/broker/Manager.h b/src/broker/Manager.h index b5faaee345..415dd00a2c 100644 --- a/src/broker/Manager.h +++ b/src/broker/Manager.h @@ -66,6 +66,9 @@ public: */ void InitPostScript(); + void BroInitDone() + { after_bro_init = true; } + /** * Shuts Broker down at termination. */ @@ -404,6 +407,7 @@ private: uint16_t bound_port; bool reading_pcaps; + bool after_bro_init; int peer_count; Func* log_topic_func; diff --git a/src/main.cc b/src/main.cc index 2e9a89ddd1..757b09351f 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1182,6 +1182,7 @@ int main(int argc, char** argv) // Drain the event queue here to support the protocols framework configuring DPM mgr.Drain(); + broker_mgr->BroInitDone(); analyzer_mgr->DumpDebug(); have_pending_timers = ! reading_traces && timer_mgr->Size() > 0; From a2f8d81fb6449d27aae74da6c80d7c139f6733f4 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 13 Aug 2018 10:20:58 -0500 Subject: [PATCH 40/43] Fix validate-certs.bro comments --- scripts/policy/protocols/ssl/validate-certs.bro | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/policy/protocols/ssl/validate-certs.bro b/scripts/policy/protocols/ssl/validate-certs.bro index 451388da24..3f0d18a1c5 100644 --- a/scripts/policy/protocols/ssl/validate-certs.bro +++ b/scripts/policy/protocols/ssl/validate-certs.bro @@ -50,11 +50,11 @@ export { ## and is thus disabled by default. global ssl_store_valid_chain: bool = F &redef; - ## Event from a worker to the manager that it has encountered a new - ## valid intermediate. + ## Event from a manager to workers when encountering a new, valid + ## intermediate. global intermediate_add: event(key: string, value: vector of opaque of x509); - ## Event from the manager to the workers that a new intermediate chain + ## Event from workers to the manager when a new intermediate chain ## is to be added. global new_intermediate: event(key: string, value: vector of opaque of x509); } From 5821c16490e731a68c0efc9c1aaba2d7aec28f48 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Mon, 13 Aug 2018 17:40:06 -0500 Subject: [PATCH 41/43] Fix SumStats::observe key normalization logic The loop over Reducers in SumStats::observe performs a key normalization and inadvertently modifies the key used for subsequent iterations. Reported by Jim Mellander. --- CHANGES | 5 +++++ VERSION | 2 +- scripts/base/frameworks/sumstats/main.bro | 5 ++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 04b025c23b..7013e2a931 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,9 @@ +2.5-840 | 2018-08-13 17:40:06 -0500 + + * Fix SumStats::observe key normalization logic + (reported by Jim Mellander and fixed by Jon Siwek, Corelight) + 2.5-839 | 2018-08-13 10:51:43 -0500 * Make options redef-able by default. (Johanna Amann, Corelight) diff --git a/VERSION b/VERSION index 7960a61cb9..446a694de2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-839 +2.5-840 diff --git a/scripts/base/frameworks/sumstats/main.bro b/scripts/base/frameworks/sumstats/main.bro index f704dbcdd2..69a853fd5a 100644 --- a/scripts/base/frameworks/sumstats/main.bro +++ b/scripts/base/frameworks/sumstats/main.bro @@ -399,7 +399,7 @@ function create(ss: SumStat) schedule ss$epoch { SumStats::finish_epoch(ss) }; } -function observe(id: string, key: Key, obs: Observation) +function observe(id: string, orig_key: Key, obs: Observation) { if ( id !in reducer_store ) return; @@ -407,8 +407,7 @@ function observe(id: string, key: Key, obs: Observation) # Try to add the data to all of the defined reducers. for ( r in reducer_store[id] ) { - if ( r?$normalize_key ) - key = r$normalize_key(copy(key)); + local key = r?$normalize_key ? r$normalize_key(copy(orig_key)) : orig_key; # If this reducer has a predicate, run the predicate # and skip this key if the predicate return false. From 0e6913fba021bd849fc24a651dd87d04133dd518 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Tue, 14 Aug 2018 16:45:09 -0500 Subject: [PATCH 42/43] BIT-1798: fix PPTP GRE tunnel decapsulation --- CHANGES | 4 ++++ VERSION | 2 +- src/Sessions.cc | 4 ++-- .../btest/Baseline/core.tunnels.gre-pptp/conn.log | 10 ++++++++++ .../btest/Baseline/core.tunnels.gre-pptp/dns.log | 10 ++++++++++ .../Baseline/core.tunnels.gre-pptp/tunnel.log | 11 +++++++++++ testing/btest/Traces/tunnels/gre-pptp.pcap | Bin 0 -> 521 bytes testing/btest/core/tunnels/gre-pptp.test | 4 ++++ 8 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 testing/btest/Baseline/core.tunnels.gre-pptp/conn.log create mode 100644 testing/btest/Baseline/core.tunnels.gre-pptp/dns.log create mode 100644 testing/btest/Baseline/core.tunnels.gre-pptp/tunnel.log create mode 100644 testing/btest/Traces/tunnels/gre-pptp.pcap create mode 100644 testing/btest/core/tunnels/gre-pptp.test diff --git a/CHANGES b/CHANGES index 7013e2a931..32b7c8cbc2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,8 @@ +2.5-841 | 2018-08-14 16:45:09 -0500 + + * BIT-1798: fix PPTP GRE tunnel decapsulation (Jon Siwek, Corelight) + 2.5-840 | 2018-08-13 17:40:06 -0500 * Fix SumStats::observe key normalization logic diff --git a/VERSION b/VERSION index 446a694de2..7a93a5255d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-840 +2.5-841 diff --git a/src/Sessions.cc b/src/Sessions.cc index 9dc569daa7..876988361d 100644 --- a/src/Sessions.cc +++ b/src/Sessions.cc @@ -532,7 +532,7 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr // If a carried packet has ethernet, this will help skip it. unsigned int eth_len = 0; unsigned int gre_len = gre_header_len(flags_ver); - unsigned int ppp_len = gre_version == 1 ? 1 : 0; + unsigned int ppp_len = gre_version == 1 ? 4 : 0; if ( gre_version != 0 && gre_version != 1 ) { @@ -598,7 +598,7 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr if ( gre_version == 1 ) { - int ppp_proto = *((uint8*)(data + gre_len)); + uint16 ppp_proto = ntohs(*((uint16*)(data + gre_len + 2))); if ( ppp_proto != 0x0021 && ppp_proto != 0x0057 ) { diff --git a/testing/btest/Baseline/core.tunnels.gre-pptp/conn.log b/testing/btest/Baseline/core.tunnels.gre-pptp/conn.log new file mode 100644 index 0000000000..20c0dc7317 --- /dev/null +++ b/testing/btest/Baseline/core.tunnels.gre-pptp/conn.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open 2018-08-14-21-42-31 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents +#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string] +1417577703.821897 C4J4Th3PJpwUYZZ6gc 172.16.44.3 40768 8.8.8.8 53 udp dns 0.213894 71 146 SF - - 0 Dd 1 99 1 174 ClEkJM2Vm5giqnMf4h +#close 2018-08-14-21-42-31 diff --git a/testing/btest/Baseline/core.tunnels.gre-pptp/dns.log b/testing/btest/Baseline/core.tunnels.gre-pptp/dns.log new file mode 100644 index 0000000000..01875c2ff9 --- /dev/null +++ b/testing/btest/Baseline/core.tunnels.gre-pptp/dns.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path dns +#open 2018-08-14-21-42-31 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto trans_id rtt query qclass qclass_name qtype qtype_name rcode rcode_name AA TC RD RA Z answers TTLs rejected +#types time string addr port addr port enum count interval string count string count string count string bool bool bool bool count vector[string] vector[interval] bool +1417577703.821897 C4J4Th3PJpwUYZZ6gc 172.16.44.3 40768 8.8.8.8 53 udp 42540 - xqt-detect-mode2-97712e88-167a-45b9-93ee-913140e76678 1 C_INTERNET 28 AAAA 3 NXDOMAIN F F T F 0 - - F +#close 2018-08-14-21-42-31 diff --git a/testing/btest/Baseline/core.tunnels.gre-pptp/tunnel.log b/testing/btest/Baseline/core.tunnels.gre-pptp/tunnel.log new file mode 100644 index 0000000000..780ea33f59 --- /dev/null +++ b/testing/btest/Baseline/core.tunnels.gre-pptp/tunnel.log @@ -0,0 +1,11 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path tunnel +#open 2018-08-14-21-42-31 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p tunnel_type action +#types time string addr port addr port enum enum +1417577703.821897 CHhAvVGS1DHFjwGM9 2402:f000:1:8e01::5555 0 2607:fcd0:100:2300::b108:2a6b 0 Tunnel::IP Tunnel::DISCOVER +1417577703.821897 ClEkJM2Vm5giqnMf4h 16.0.0.200 0 192.52.166.154 0 Tunnel::GRE Tunnel::DISCOVER +#close 2018-08-14-21-42-31 diff --git a/testing/btest/Traces/tunnels/gre-pptp.pcap b/testing/btest/Traces/tunnels/gre-pptp.pcap new file mode 100644 index 0000000000000000000000000000000000000000..45216c7f7adc9c2b7fa0f7f2288e88b4e0165135 GIT binary patch literal 521 zcmca|c+)~A1{MYw`2U}Qff2}g-clFR*~PYV3b5Ffu5Ec^f&jvR#3yde$>AIOv!D6JTICalmBREQ7`lZiaO6 z1_rkNjSL)CW&fEO6oKlJfvOm61jEwT2=H{oQ8tGb^n;ROXT3F~Bnwcl+nwTb8>RKA7rs`T68XKAzq?((VnOiV0$S^Rz z0D9_tHvLA z5CnQ?2h>CBnAsQ@j0ES3fjk596oVGd)i?9VnihpUY6e%gM>Wz|O(Iz}&*X LY1P2M7}x*+<->hB literal 0 HcmV?d00001 diff --git a/testing/btest/core/tunnels/gre-pptp.test b/testing/btest/core/tunnels/gre-pptp.test new file mode 100644 index 0000000000..a5fa8c0d19 --- /dev/null +++ b/testing/btest/core/tunnels/gre-pptp.test @@ -0,0 +1,4 @@ +# @TEST-EXEC: bro -r $TRACES/tunnels/gre-pptp.pcap +# @TEST-EXEC: btest-diff conn.log +# @TEST-EXEC: btest-diff tunnel.log +# @TEST-EXEC: btest-diff dns.log From f336c8c710bdeb41eb0aba88967ee90da24848b2 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Wed, 15 Aug 2018 11:00:20 -0500 Subject: [PATCH 43/43] Fix seg fault on trying to type-cast invalid/nil Broker::Data This situation now throws a runtime expression exception instead of crashing on null pointer access. --- CHANGES | 5 +++++ VERSION | 2 +- src/Expr.cc | 9 ++++++++- src/Val.cc | 8 ++++++++ .../Baseline/language.type-cast-error-dynamic/output | 6 ++++-- testing/btest/language/type-cast-error-dynamic.bro | 2 ++ 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 32b7c8cbc2..af9f1b88f5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,9 @@ +2.5-842 | 2018-08-15 11:00:20 -0500 + + * Fix seg fault on trying to type-cast invalid/nil Broker::Data + (Jon Siwek, Corelight) + 2.5-841 | 2018-08-14 16:45:09 -0500 * BIT-1798: fix PPTP GRE tunnel decapsulation (Jon Siwek, Corelight) diff --git a/VERSION b/VERSION index 7a93a5255d..4744b3cd09 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5-841 +2.5-842 diff --git a/src/Expr.cc b/src/Expr.cc index f958c63ecf..07034db1a8 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -16,6 +16,8 @@ #include "Trigger.h" #include "IPAddr.h" +#include "broker/Data.h" + const char* expr_name(BroExprTag t) { static const char* expr_names[int(NUM_EXPRS)] = { @@ -5503,11 +5505,16 @@ Val* CastExpr::Eval(Frame* f) const } ODesc d; - d.Add("cannot cast value of type '"); + d.Add("invalid cast of value with type '"); v->Type()->Describe(&d); d.Add("' to type '"); Type()->Describe(&d); d.Add("'"); + + if ( same_type(v->Type(), bro_broker::DataVal::ScriptDataType()) && + ! v->AsRecordVal()->Lookup(0) ) + d.Add(" (nil $data field)"); + Unref(v); reporter->ExprRuntimeError(this, "%s", d.Description()); return 0; // not reached. diff --git a/src/Val.cc b/src/Val.cc index 1024251418..7879d282b2 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -3632,6 +3632,10 @@ Val* cast_value_to_type(Val* v, BroType* t) if ( same_type(v->Type(), bro_broker::DataVal::ScriptDataType()) ) { auto dv = v->AsRecordVal()->Lookup(0); + + if ( ! dv ) + return 0; + return static_cast(dv)->castTo(t); } @@ -3654,6 +3658,10 @@ bool can_cast_value_to_type(const Val* v, BroType* t) if ( same_type(v->Type(), bro_broker::DataVal::ScriptDataType()) ) { auto dv = v->AsRecordVal()->Lookup(0); + + if ( ! dv ) + return false; + return static_cast(dv)->canCastTo(t); } diff --git a/testing/btest/Baseline/language.type-cast-error-dynamic/output b/testing/btest/Baseline/language.type-cast-error-dynamic/output index 10d92ac199..8ebf0cc90e 100644 --- a/testing/btest/Baseline/language.type-cast-error-dynamic/output +++ b/testing/btest/Baseline/language.type-cast-error-dynamic/output @@ -1,2 +1,4 @@ -expression error in /home/robin/bro/lang-ext/testing/btest/.tmp/language.type-cast-error-dynamic/type-cast-error-dynamic.bro, line 11: cannot cast value of type 'count' to type 'string' [a as string] -expression error in /home/robin/bro/lang-ext/testing/btest/.tmp/language.type-cast-error-dynamic/type-cast-error-dynamic.bro, line 11: cannot cast value of type 'record { a:addr; b:port; }' to type 'string' [a as string] +expression error in /Users/jon/projects/bro/bro/testing/btest/.tmp/language.type-cast-error-dynamic/type-cast-error-dynamic.bro, line 11: invalid cast of value with type 'count' to type 'string' [a as string] +expression error in /Users/jon/projects/bro/bro/testing/btest/.tmp/language.type-cast-error-dynamic/type-cast-error-dynamic.bro, line 11: invalid cast of value with type 'record { a:addr; b:port; }' to type 'string' [a as string] +expression error in /Users/jon/projects/bro/bro/testing/btest/.tmp/language.type-cast-error-dynamic/type-cast-error-dynamic.bro, line 11: invalid cast of value with type 'record { data:opaque of Broker::Data; }' to type 'string' (nil $data field) [a as string] +data is string, F diff --git a/testing/btest/language/type-cast-error-dynamic.bro b/testing/btest/language/type-cast-error-dynamic.bro index 45f1d1fb5f..91fa212ce4 100644 --- a/testing/btest/language/type-cast-error-dynamic.bro +++ b/testing/btest/language/type-cast-error-dynamic.bro @@ -18,6 +18,8 @@ event bro_init() cast_to_string(42); cast_to_string(x); + cast_to_string(Broker::Data()); + print "data is string", Broker::Data() is string; }