return(FKO_SUCCESS);
}
+ /* For replay attack detection
+ */
+ static int
+ get_raw_digest(char **digest, char *pkt_data)
+ {
+ fko_ctx_t ctx = NULL;
+ char *tmp_digest = NULL;
+ int res = FKO_SUCCESS;
+
+ /* initialize an FKO context with no decryption key just so
+ * we can get the outer message digest
+ */
+ res = fko_new_with_data(&ctx, (char *)pkt_data, NULL);
+ if(res != FKO_SUCCESS)
+ {
+ log_msg(LOG_WARNING, "Error initializing FKO context from SPA data: %s",
+ fko_errstr(res));
+ fko_destroy(ctx);
+ return(SPA_MSG_FKO_CTX_ERROR);
+ }
+
+ res = fko_set_raw_spa_digest_type(ctx, FKO_DEFAULT_DIGEST);
+ if(res != FKO_SUCCESS)
+ {
+ log_msg(LOG_WARNING, "Error setting digest type for SPA data: %s",
+ fko_errstr(res));
+ fko_destroy(ctx);
+ return(SPA_MSG_DIGEST_ERROR);
+ }
+
+ res = fko_set_raw_spa_digest(ctx);
+ if(res != FKO_SUCCESS)
+ {
+ log_msg(LOG_WARNING, "Error setting digest for SPA data: %s",
+ fko_errstr(res));
+ fko_destroy(ctx);
+ return(SPA_MSG_DIGEST_ERROR);
+ }
+
+ res = fko_get_raw_spa_digest(ctx, &tmp_digest);
+ if(res != FKO_SUCCESS)
+ {
+ log_msg(LOG_WARNING, "Error getting digest from SPA data: %s",
+ fko_errstr(res));
+ fko_destroy(ctx);
+ return(SPA_MSG_DIGEST_ERROR);
+ }
+
+ *digest = strdup(tmp_digest);
+
+ if (digest == NULL)
+ return SPA_MSG_ERROR;
+
+ fko_destroy(ctx);
+ return res;
+ }
+
+
/* Popluate a spa_data struct from an initialized (and populated) FKO context.
*/
static int
return(res);
}
+ /* Check for access.conf stanza SOURCE match based on SPA packet
+ * source IP
+ */
+ static int
+ is_src_match(acc_stanza_t *acc, const uint32_t ip)
+ {
+ while (acc)
+ {
+ if(compare_addr_list(acc->source_list, ip))
+ return 1;
+
+ acc = acc->next;
+ }
+ return 0;
+ }
+
/* Process the SPA packet data
*/
void
*/
fko_ctx_t ctx = NULL;
- char *spa_ip_demark, *gpg_id;
+ char *spa_ip_demark, *gpg_id, *raw_digest = NULL;
time_t now_ts;
- int res, status, ts_diff, enc_type, found_acc_sip=0, stanza_num=0;
+ int res, status, ts_diff, enc_type, stanza_num=0;
+ int added_replay_digest = 0;
spa_pkt_info_t *spa_pkt = &(opts->spa_pkt);
return;
}
+ if (is_src_match(opts->acc_stanzas, ntohl(spa_pkt->packet_src_ip)))
+ {
+ if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
+ /* Check for a replay attack
+ */
+ res = get_raw_digest(&raw_digest, (char *)spa_pkt->packet_data);
+ if(res != FKO_SUCCESS)
+ {
+ if (raw_digest != NULL)
+ free(raw_digest);
+ return;
+ }
+ if (raw_digest == NULL)
+ return;
+
+ if (is_replay(opts, raw_digest) != SPA_MSG_SUCCESS)
+ return;
+ }
+ else
+ {
+ log_msg(LOG_WARNING,
+ "No access data found for source IP: %s", spadat.pkt_source_ip
+ );
+ return;
+ }
+
+ /* Now that we know there is a matching access.conf stanza and the
+ * incoming SPA packet is not a replay, see if we should grant any
+ * access
+ */
while(acc)
{
stanza_num++;
continue;
}
- found_acc_sip = 1;
-
log_msg(LOG_INFO, "(stanza #%d) SPA Packet from IP: %s received with access source match",
stanza_num, spadat.pkt_source_ip);
if(enc_type == FKO_ENCRYPTION_RIJNDAEL)
{
if(acc->key != NULL)
- res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, acc->key);
+ res = fko_new_with_data(&ctx,
+ (char *)spa_pkt->packet_data, acc->key, acc->encryption_mode);
else
{
log_msg(LOG_ERR,
else if(enc_type == FKO_ENCRYPTION_GPG)
{
/* For GPG we create the new context without decrypting on the fly
- * so we can set some GPG parameters first.
+ * so we can set some GPG parameters first.
*/
if(acc->gpg_decrypt_pw != NULL)
{
- res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, NULL);
+ res = fko_new_with_data(&ctx, (char *)spa_pkt->packet_data, NULL,
+ acc->encryption_mode);
if(res != FKO_SUCCESS)
{
log_msg(LOG_WARNING,
continue;
}
- /* Do we have a valid FKO context?
+ /* Do we have a valid FKO context? Did the SPA decrypt properly?
*/
if(res != FKO_SUCCESS)
{
continue;
}
+ /* Add this SPA packet into the replay detection cache
+ */
+ if (! added_replay_digest)
+ {
+ res = add_replay(opts, raw_digest);
+ if (res != SPA_MSG_SUCCESS)
+ {
+ log_msg(LOG_WARNING, "(stanza #%d) Could not add digest to replay cache",
+ stanza_num);
+ if(ctx != NULL)
+ fko_destroy(ctx);
+ acc = acc->next;
+ continue;
+ }
+ added_replay_digest = 1;
+ }
+
/* At this point, we assume the SPA data is valid. Now we need to see
* if it meets our access criteria.
*/
}
}
- /* Check for replays if so configured.
- */
- if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
- {
- res = replay_check(opts, ctx);
- if(res != 0) /* non-zero means we have seen this packet before. */
- {
- if(ctx != NULL)
- fko_destroy(ctx);
- acc = acc->next;
- continue;
- }
- }
-
/* Populate our spa data struct for future reference.
*/
res = get_spa_data_fields(ctx, &spadat);
break;
}
- if(! found_acc_sip)
- {
- log_msg(LOG_WARNING,
- "No access data found for source IP: %s", spadat.pkt_source_ip
- );
- }
+ if (raw_digest != NULL)
+ free(raw_digest);
return;
}
use File::Path;
use IO::Socket;
use Data::Dumper;
+use Cwd;
use Getopt::Long 'GetOptions';
use strict;
my $nat_conf = "$conf_dir/nat_fwknopd.conf";
my $default_conf = "$conf_dir/default_fwknopd.conf";
my $default_access_conf = "$conf_dir/default_access.conf";
+my $ecb_mode_access_conf = "$conf_dir/ecb_mode_access.conf";
+my $ctr_mode_access_conf = "$conf_dir/ctr_mode_access.conf";
+my $cfb_mode_access_conf = "$conf_dir/cfb_mode_access.conf";
+my $ofb_mode_access_conf = "$conf_dir/ofb_mode_access.conf";
my $expired_access_conf = "$conf_dir/expired_stanza_access.conf";
my $future_expired_access_conf = "$conf_dir/future_expired_stanza_access.conf";
my $expired_epoch_access_conf = "$conf_dir/expired_epoch_stanza_access.conf";
my $invalid_expire_access_conf = "$conf_dir/invalid_expire_access.conf";
+my $invalid_source_access_conf = "$conf_dir/invalid_source_access.conf";
my $force_nat_access_conf = "$conf_dir/force_nat_access.conf";
+ my $dual_key_usage_access_conf = "$conf_dir/dual_key_usage_access.conf";
my $gpg_access_conf = "$conf_dir/gpg_access.conf";
my $default_digest_file = "$run_dir/digest.cache";
my $default_pid_file = "$run_dir/fwknopd.pid";
my @tests_to_include = ();
my $test_exclude = '';
my @tests_to_exclude = ();
+ my %valgrind_flagged_fcns = ();
+ my %valgrind_flagged_fcns_unique = ();
my $list_mode = 0;
+ my $diff_dir1 = '';
+ my $diff_dir2 = '';
my $loopback_intf = '';
my $anonymize_results = 0;
my $current_test_file = "$output_dir/init";
my $saved_last_results = 0;
my $diff_mode = 0;
my $enable_recompilation_warnings_check = 0;
+my $enable_profile_coverage_check = 0;
my $sudo_path = '';
+my $gcov_path = '';
my $platform = '';
my $help = 0;
my $YES = 1;
'test-exclude=s' => \$test_exclude,
'exclude=s' => \$test_exclude, ### synonym
'enable-recompile-check' => \$enable_recompilation_warnings_check,
+ 'enable-profile-coverage-check' => \$enable_profile_coverage_check,
'List-mode' => \$list_mode,
'enable-valgrind' => \$use_valgrind,
'valgrind-path=s' => \$valgrindCmd,
+ 'output-dir=s' => \$output_dir,
'diff' => \$diff_mode,
+ 'diff-dir1=s' => \$diff_dir1,
+ 'diff-dir2=s' => \$diff_dir2,
'help' => \$help
);
{
'category' => 'Rijndael SPA',
'subcategory' => 'client+server',
+ 'detail' => 'dual usage access key (tcp/80 http)',
+ 'err_msg' => 'could not complete SPA cycle',
+ 'function' => \&spa_cycle,
+ 'cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopCmd -A tcp/80 -a $fake_ip -D $loopback_ip --get-key " .
+ "$local_key_file --verbose --verbose",
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $dual_key_usage_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ ### check for the first stanza that does not allow tcp/80 - the
+ ### second stanza allows this
+ 'server_positive_output_matches' => [qr/stanza #1\)\sOne\sor\smore\srequested\sprotocol\/ports\swas\sdenied/],
+ 'fw_rule_created' => $NEW_RULE_REQUIRED,
+ 'fw_rule_removed' => $NEW_RULE_REMOVED,
+ 'fatal' => $NO
+ },
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
'detail' => 'packet aging (past) (tcp/22 ssh)',
'err_msg' => 'old SPA packet accepted',
'function' => \&spa_cycle,
{
'category' => 'Rijndael SPA',
'subcategory' => 'client+server',
+ 'detail' => 'invalid SOURCE (tcp/22 ssh)',
+ 'err_msg' => 'SPA packet accepted',
+ 'function' => \&spa_cycle,
+ 'cmdline' => $default_client_args,
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $invalid_source_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_positive_output_matches' => [qr/Fatal\serror\sparsing\sIP\sto\sint/],
+ 'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
+ 'fatal' => $NO
+ },
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
'detail' => 'expired stanza (tcp/22 ssh)',
'err_msg' => 'SPA packet accepted',
'function' => \&spa_cycle,
'server_conf' => $nat_conf,
'fatal' => $NO
},
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
+ 'detail' => 'ECB mode (tcp/22 ssh)',
+ 'err_msg' => 'could not complete SPA cycle',
+ 'function' => \&spa_cycle,
+ 'cmdline' => "$default_client_args -M ecb",
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $ecb_mode_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_negative_output_matches' => [qr/Decryption\sfailed/i],
+ 'fw_rule_created' => $NEW_RULE_REQUIRED,
+ 'fw_rule_removed' => $NEW_RULE_REMOVED,
+ 'fatal' => $NO
+ },
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
+ 'detail' => 'CFB mode (tcp/22 ssh)',
+ 'err_msg' => 'could not complete SPA cycle',
+ 'function' => \&spa_cycle,
+ 'cmdline' => "$default_client_args -M cfb",
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $cfb_mode_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_negative_output_matches' => [qr/Decryption\sfailed/i],
+ 'fw_rule_created' => $NEW_RULE_REQUIRED,
+ 'fw_rule_removed' => $NEW_RULE_REMOVED,
+ 'fatal' => $NO
+ },
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
+ 'detail' => 'CTR mode (tcp/22 ssh)',
+ 'err_msg' => 'could not complete SPA cycle',
+ 'function' => \&spa_cycle,
+ 'cmdline' => "$default_client_args -M ctr",
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $ctr_mode_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_negative_output_matches' => [qr/Decryption\sfailed/i],
+ 'fw_rule_created' => $NEW_RULE_REQUIRED,
+ 'fw_rule_removed' => $NEW_RULE_REMOVED,
+ 'fatal' => $NO
+ },
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
+ 'detail' => 'OFB mode (tcp/22 ssh)',
+ 'err_msg' => 'could not complete SPA cycle',
+ 'function' => \&spa_cycle,
+ 'cmdline' => "$default_client_args -M ofb",
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $ofb_mode_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_negative_output_matches' => [qr/Decryption\sfailed/i],
+ 'fw_rule_created' => $NEW_RULE_REQUIRED,
+ 'fw_rule_removed' => $NEW_RULE_REMOVED,
+ 'fatal' => $NO
+ },
+
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
+ 'detail' => 'mode mismatch (tcp/22 ssh)',
+ 'err_msg' => 'server accepted mismatch enc mode',
+ 'function' => \&spa_cycle,
+ 'cmdline' => "$default_client_args -M ecb",
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $default_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_positive_output_matches' => [qr/Decryption\sfailed/i],
+ 'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
+ 'fatal' => $NO
+ },
{
'category' => 'Rijndael SPA',
'fwknopd_cmdline' => $default_server_gpg_args,
'fatal' => $NO
},
-
{
'category' => 'GnuPG (GPG) SPA',
'subcategory' => 'server',
'function' => \&digest_cache_structure,
'fatal' => $NO
},
+
+ {
+ 'category' => 'profile coverage',
+ 'detail' => 'gcov profile coverage',
+ 'err_msg' => 'profile coverage failed',
+ 'function' => \&profile_coverage,
+ 'fatal' => $NO
+ },
);
+ if ($use_valgrind) {
+ push @tests,
+ {
+ 'category' => 'valgrind output',
+ 'subcategory' => 'flagged functions',
+ 'detail' => '',
+ 'err_msg' => 'could not parse flagged functions',
+ 'function' => \&parse_valgrind_flagged_functions,
+ 'fatal' => $NO
+ };
+ }
+
my %test_keys = (
'category' => $REQUIRED,
'subcategory' => $OPTIONAL,
if (@tests_to_include) {
my $found = 0;
for my $test (@tests_to_include) {
- if ($msg =~ /$test/) {
+ if ($msg =~ /$test/ or ($use_valgrind
+ and $msg =~ /valgrind\soutput/)) {
$found = 1;
last;
}
}
sub diff_test_results() {
+
+ $diff_dir1 = "${output_dir}.last" unless $diff_dir2;
+ $diff_dir2 = $output_dir unless $diff_dir1;
+
die "[*] Need results from a previous run before running --diff"
- unless -d "${output_dir}.last";
- die "[*] Current results set does not exist." unless -d $output_dir;
+ unless -d $diff_dir2;
+ die "[*] Current results set does not exist." unless -d $diff_dir1;
my %current_tests = ();
my %previous_tests = ();
### Only diff results for matching tests (parse the logfile to see which
### test numbers match across the two test cycles).
- &build_results_hash(\%current_tests, $output_dir);
- &build_results_hash(\%previous_tests, "${output_dir}.last");
+ &build_results_hash(\%current_tests, $diff_dir1);
+ &build_results_hash(\%previous_tests, $diff_dir2);
for my $test_msg (sort {$current_tests{$a}{'num'} <=> $current_tests{$b}{'num'}}
keys %current_tests) {
### remove CMD timestamps
my $cmd_search_re = qr/^\S+\s.*?\s\d{4}\sCMD\:/;
- for my $file ("${output_dir}.last/${previous_num}.test",
- "${output_dir}.last/${previous_num}_fwknopd.test",
- "${output_dir}/${current_num}.test",
- "${output_dir}/${current_num}_fwknopd.test",
+ for my $file ("$diff_dir1/${previous_num}.test",
+ "$diff_dir1/${previous_num}_fwknopd.test",
+ "$diff_dir2/${current_num}.test",
+ "$diff_dir2/${current_num}_fwknopd.test",
) {
system qq{perl -p -i -e 's|$valgrind_search_re||' $file} if -e $file;
system qq{perl -p -i -e 's|$cmd_search_re|CMD:|' $file} if -e $file;
}
- if (-e "${output_dir}.last/${previous_num}.test"
- and -e "${output_dir}/${current_num}.test") {
- system "diff -u ${output_dir}.last/${previous_num}.test " .
- "${output_dir}/${current_num}.test";
+ if (-e "$diff_dir1/${previous_num}.test"
+ and -e "$diff_dir2/${current_num}.test") {
+ system "diff -u $diff_dir1/${previous_num}.test " .
+ "$diff_dir2/${current_num}.test";
}
- if (-e "${output_dir}.last/${previous_num}_fwknopd.test"
- and -e "${output_dir}/${current_num}_fwknopd.test") {
- system "diff -u ${output_dir}.last/${previous_num}_fwknopd.test " .
- "${output_dir}/${current_num}_fwknopd.test";
+ if (-e "$diff_dir1/${previous_num}_fwknopd.test"
+ and -e "$diff_dir2/${current_num}_fwknopd.test") {
+ system "diff -u $diff_dir1/${previous_num}_fwknopd.test " .
+ "$diff_dir2/${current_num}_fwknopd.test";
}
return;
### look for compilation warnings - something like:
### warning: ‘test’ is used uninitialized in this function
- return 0 if &file_find_regex([qr/\swarning:\s/, qr/gcc\:.*\sunused/], $current_test_file);
+ return 0 if &file_find_regex([qr/\swarning:\s/, qr/gcc\:.*\sunused/],
+ $current_test_file);
### the new binaries should exist
unless (-e $fwknopCmd and -x $fwknopCmd) {
return 1;
}
+sub profile_coverage() {
+
+ ### check for any *.gcno files - if they don't exist, then fwknop was
+ ### not compiled with profile support
+ unless (glob('../client/*.gcno') and glob('../server/*.gcno')) {
+ &write_test_file("[-] ../client/*.gcno and " .
+ "../server/*.gcno files do not exist.\n", $current_test_file);
+ return 0;
+ }
+
+ my $curr_dir = getcwd() or die $!;
+
+ ### gcov -b ../client/*.gcno
+ for my $dir ('../client', '../server', '../lib/.libs') {
+ next unless -d $dir;
+ chdir $dir or die $!;
+ system "$gcov_path -b -u *.gcno > /dev/null 2>&1";
+ chdir $curr_dir or die $!;
+
+ &run_cmd(qq|grep "called 0 returned" $dir/*.gcov|,
+ $cmd_out_tmp, $current_test_file);
+ }
+
+ return 1;
+}
+
sub binary_exists() {
my $test_hr = shift;
return 0 unless $test_hr->{'binary'};
push @tests_to_exclude, 'recompilation';
}
+ unless ($enable_profile_coverage_check) {
+ push @tests_to_exclude, 'profile coverage';
+ }
+
$sudo_path = &find_command('sudo');
unless ((&find_command('cc') or &find_command('gcc')) and &find_command('make')) {
push @tests_to_exclude, 'recompilation';
}
+ $gcov_path = &find_command('gcov');
+
+ if ($gcov_path) {
+ if ($enable_profile_coverage_check) {
+ for my $extension ('*.gcov', '*.gcda') {
+ ### remove profile output from any previous run
+ system qq{find .. -name $extension | xargs rm 2> /dev/null};
+ }
+ }
+ } else {
+ push @tests_to_exclude, 'profile coverage';
+ }
+
open UNAME, "uname |" or die "[*] Could not execute uname: $!";
while (<UNAME>) {
if (/linux/i) {
return;
}
+ sub parse_valgrind_flagged_functions() {
+ for my $file (glob("$output_dir/*.test")) {
+ my $type = 'server';
+ $type = 'client' if $file =~ /\d\.test/;
+ open F, "< $file" or die $!;
+ while (<F>) {
+ ### ==30969== by 0x4E3983A: fko_set_username (fko_user.c:65)
+ if (/^==.*\sby\s\S+\:\s(\S+)\s(.*)/) {
+ $valgrind_flagged_fcns{$type}{"$1 $2"}++;
+ $valgrind_flagged_fcns_unique{$type}{$1}++;
+ }
+ }
+ close F;
+ }
+
+ open F, ">> $current_test_file" or die $!;
+ for my $type ('client', 'server') {
+ print F "\n[+] fwknop $type functions (unique view):\n";
+ next unless defined $valgrind_flagged_fcns_unique{$type};
+ for my $fcn (sort {$valgrind_flagged_fcns_unique{$type}{$b}
+ <=> $valgrind_flagged_fcns_unique{$type}{$a}}
+ keys %{$valgrind_flagged_fcns_unique{$type}}) {
+ printf F " %5d : %s\n", $valgrind_flagged_fcns_unique{$type}{$fcn}, $fcn;
+ }
+ print F "\n[+] fwknop $type functions (with call line numbers):\n";
+ for my $fcn (sort {$valgrind_flagged_fcns{$type}{$b}
+ <=> $valgrind_flagged_fcns{$type}{$a}} keys %{$valgrind_flagged_fcns{$type}}) {
+ printf F " %5d : %s\n", $valgrind_flagged_fcns{$type}{$fcn}, $fcn;
+ }
+ next unless defined $valgrind_flagged_fcns{$type};
+
+ }
+ close F;
+ return 1;
+ }
+
sub is_fw_rule_active() {
my $test_hr = shift;
}
sub usage() {
- return;
+ print <<_HELP_;
+
+ [+] $0 <options>
+
+ -A --Anonymize-results - Prepare anonymized results at:
+ $tarfile
+ --diff - Compare the results of one test run to
+ another. By default this compares output
+ in ${output_dir}.last to $output_dir
+ --diff-dir1=<path> - Left hand side of diff directory path,
+ default is: ${output_dir}.last
+ --diff-dir2=<path> - Right hand side of diff directory path,
+ default is: $output_dir
+ --include=<regex> - Specify a regex to be used over test
+ names that must match.
+ --exclude=<regex> - Specify a regex to be used over test
+ names that must not match.
+ --enable-recompile - Recompile fwknop sources and look for
+ compilation warnings.
+ --enable-valgrind - Run every test underneath valgrind.
+ --List - List test names.
+ --loopback-intf=<intf> - Specify loopback interface name (default
+ depends on the OS where the test suite
+ is executed).
+ --output-dir=<path> - Path to output directory, default is:
+ $output_dir
+ --fwknop-path=<path> - Path to fwknop binary, default is:
+ $fwknopCmd
+ --fwknopd-path=<path> - Path to fwknopd binary, default is:
+ $fwknopdCmd
+ --libfko-path=<path> - Path to libfko, default is:
+ $libfko_bin
+ --valgrind-path=<path> - Path to valgrind, default is:
+ $valgrindCmd
+ -h --help - Display usage on STDOUT and exit.
+
+ _HELP_
+ exit 0;
}