[server] Added '--pcap-file <file>' option
authorMichael Rash <mbr@cipherdyne.org>
Fri, 9 Nov 2012 02:33:23 +0000 (21:33 -0500)
committerMichael Rash <mbr@cipherdyne.org>
Fri, 9 Nov 2012 02:33:23 +0000 (21:33 -0500)
Added a new '--pcap-file <file>' option to allow pcap files to
be processed directly by fwknopd instead of sniffing an interface.  This
feature is mostly intended for debugging purposes.

12 files changed:
ChangeLog
Makefile.am
doc/fwknopd.man.asciidoc
server/cmd_opts.h
server/config_init.c
server/fwknopd.conf
server/fwknopd_common.h
server/incoming_spa.c
server/pcap_capture.c
test/conf/spa_replay.pcap [new file with mode: 0644]
test/test-fwknop.pl
todo.org

index eaa4ea2..bd44590 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -58,6 +58,9 @@ fwknop-2.0.4 (11//2012):
     - [test suite] For GnuPG tests that require a passphrase associated with
       a gpg key, added a pinentry check to see if the local gpg engine
       requires it.  If so, the gpg test that require a key are excluded since.
+    - [server] Added a new '--pcap-file <file>' option to allow pcap files to
+      be processed directly by fwknopd instead of sniffing an interface.  This
+      feature is mostly intended for debugging purposes.
 
 fwknop-2.0.3 (09/03/2012):
     - [server] Fernando Arnaboldi from IOActive found several DoS/code
index 7b9966a..3b89022 100644 (file)
@@ -160,6 +160,7 @@ EXTRA_DIST = \
     test/conf/tcp_pcap_filter_fwknopd.conf \
     test/conf/icmp_pcap_filter_fwknopd.conf \
     test/conf/tcp_server_fwknopd.conf \
+    test/conf/spa_replay.pcap \
     test/fuzzing/patches/enable_perl_fko_bogus_packets.patch \
     test/fuzzing/patches/encoding_append_b64_modified_byte \
     test/fuzzing/patches/encoding_append_b64_modified_byte_eq.patch \
index 19eb8af..b951dfc 100644 (file)
@@ -102,6 +102,12 @@ COMMAND-LINE OPTIONS
     line.  This overrides the value of the PCAP_FILTER variable taken
     from the 'fwknopd.conf' file.
 
+*--pcap-file*='<pcap-file>'::
+    This option instructs *fwknopd* to read packet data from a pcap file
+    instead of sniffing an interface directly.  This mode is usually used for
+    debugging purposes, and will disable SPA packet age checking unless it is
+    manually enabled in the 'fwknop.conf' file.
+
 *-R, --Restart*::
     Restart the currently running *fwknopd* processes.  This option
     will preserve the command line options that were supplied to the
index 0abe85d..e806d7d 100644 (file)
@@ -42,6 +42,7 @@ static char *config_map[NUMBER_OF_CONFIG_ENTRIES] = {
     "OVERRIDE_CONFIG",
     //"FIREWALL_TYPE",
     "PCAP_INTF",
+    "PCAP_FILE",
     "ENABLE_PCAP_PROMISC",
     "PCAP_FILTER",
     "PCAP_DISPATCH_COUNT",
@@ -115,6 +116,7 @@ enum {
     FW_LIST_ALL,
     FW_FLUSH,
     GPG_HOME_DIR,
+    PCAP_FILE,
     ROTATE_DIGEST_CACHE,
     NOOP /* Just to be a marker for the end */
 };
@@ -143,6 +145,7 @@ static struct option cmd_opts[] =
     {"locale",              1, NULL, 'l' },
     {"rotate-digest-cache", 0, NULL, ROTATE_DIGEST_CACHE },
     {"override-config",     1, NULL, 'O' },
+    {"pcap-file",           1, NULL, PCAP_FILE },
     {"pcap-filter",         1, NULL, 'P'},
     {"pid-file",            1, NULL, 'p'},
     {"restart",             0, NULL, 'R'},
index a728cda..a218066 100644 (file)
@@ -337,7 +337,8 @@ validate_options(fko_srv_options_t *opts)
 
     /* Set remaining require CONF_ vars if they are not already set.  */
 
-    /* PCAP capture interface.
+    /* PCAP capture interface - note that if '-r <pcap file>' is specified
+     * on the command line, then this will override the pcap interface setting.
     */
     if(opts->config[CONF_PCAP_INTF] == NULL)
         set_config_entry(opts, CONF_PCAP_INTF, DEF_INTERFACE);
@@ -365,11 +366,21 @@ validate_options(fko_srv_options_t *opts)
     if(opts->config[CONF_PCAP_FILTER] == NULL)
         set_config_entry(opts, CONF_PCAP_FILTER, DEF_PCAP_FILTER);
 
-    /* Enable SPA packet aging.
+    /* Enable SPA packet aging unless we're getting packet data
+     * directly from a pcap file
     */
     if(opts->config[CONF_ENABLE_SPA_PACKET_AGING] == NULL)
-        set_config_entry(opts, CONF_ENABLE_SPA_PACKET_AGING,
-            DEF_ENABLE_SPA_PACKET_AGING);
+    {
+        if(opts->config[CONF_PCAP_FILE] == NULL)
+        {
+            set_config_entry(opts, CONF_ENABLE_SPA_PACKET_AGING,
+                DEF_ENABLE_SPA_PACKET_AGING);
+        }
+        else
+        {
+            set_config_entry(opts, CONF_ENABLE_SPA_PACKET_AGING, "N");
+        }
+    }
 
     /* SPA packet age.
     */
@@ -798,6 +809,9 @@ config_init(fko_srv_options_t *opts, int argc, char **argv)
             case 'P':
                 set_config_entry(opts, CONF_PCAP_FILTER, optarg);
                 break;
+            case PCAP_FILE:
+                set_config_entry(opts, CONF_PCAP_FILE, optarg);
+                break;
             case ROTATE_DIGEST_CACHE:
                 opts->rotate_digest_cache = 1;
                 break;
index c8afc08..c23c5a5 100644 (file)
@@ -27,7 +27,8 @@
 #
 
 # Define the ethernet interface on which we will sniff packets.
-# Default if not set is eth0.
+# Default if not set is eth0.  The '-i <intf>' command line option overrides
+# the PCAP_INTF setting.
 #
 #PCAP_INTF                   eth0;
 
 #
 #ENABLE_DIGEST_PERSISTENCE   Y;
 
-# Sets the number of packets that are processed when the *pcap_dispatch()*
-# call is made.  The default is zero, since this allows *fwknopd* to process
+# Sets the number of packets that are processed when the pcap_dispatch()
+# call is made.  The default is zero, since this allows fwknopd to process
 # as many packets as possible in the corresponding callback where the SPA
 # handling routine is called for packets that pass a set of prerequisite
-# checks.  However, if *fwknopd* is running on a platform with an old
+# checks.  However, if fwknopd is running on a platform with an old
 # version of libpcap, it may be necessary to change this value to a positive
-# non-zero integer.  More information can be found in the *pcap_dispatch(3)*
+# non-zero integer.  More information can be found in the pcap_dispatch(3)
 # man page.
 #PCAP_DISPATCH_COUNT            0;
 
 #SYSLOG_IDENTITY             fwknopd;
 #SYSLOG_FACILITY             LOG_DAEMON;
 
+# Define this to have fwknopd read pcap data from a file instead of sniffing
+# a live interface.  This is usually only used for debugging purposes, and is
+# equivalent to the '-r <pcap file>' command line option.
+#
+#PCAP_FILE                   /some/path/to/file.pcap;
+
 ##############################################################################
 # NOTE: The following EXTERNAL_CMD functionality is not yet implemented.
 #       This is a possible future feature of fwknopd.
index cd4d42e..10c9450 100644 (file)
@@ -165,6 +165,7 @@ enum {
     CONF_OVERRIDE_CONFIG,
     //CONF_FIREWALL_TYPE,
     CONF_PCAP_INTF,
+    CONF_PCAP_FILE,
     CONF_ENABLE_PCAP_PROMISC,
     CONF_PCAP_FILTER,
     CONF_PCAP_DISPATCH_COUNT,
index 67929c2..606688d 100644 (file)
@@ -254,7 +254,7 @@ incoming_spa(fko_srv_options_t *opts)
     char            *spa_ip_demark, *gpg_id, *raw_digest = NULL;
     time_t          now_ts;
     int             res, status, ts_diff, enc_type, stanza_num=0;
-    int             added_replay_digest = 0;
+    int             added_replay_digest = 0, pkt_data_len=0;
 
     spa_pkt_info_t *spa_pkt = &(opts->spa_pkt);
 
@@ -273,6 +273,7 @@ incoming_spa(fko_srv_options_t *opts)
      * SPA data and/or to be reasonably sure we have a SPA packet (i.e
      * try to eliminate obvious non-spa packets).
     */
+    pkt_data_len = spa_pkt->packet_data_len;
     res = preprocess_spa_data(opts, spadat.pkt_source_ip);
     if(res != FKO_SUCCESS)
     {
@@ -282,6 +283,12 @@ incoming_spa(fko_srv_options_t *opts)
         return;
     }
 
+    if(opts->foreground == 1 && opts->verbose > 2)
+    {
+        printf("[+] candidate SPA packet payload:\n");
+        hex_dump(spa_pkt->packet_data, pkt_data_len);
+    }
+
     if (is_src_match(opts->acc_stanzas, ntohl(spa_pkt->packet_src_ip)))
     {
         if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
index 99d6310..47be860 100644 (file)
@@ -57,6 +57,7 @@ pcap_capture(fko_srv_options_t *opts)
     int                 pending_break = 0;
     int                 promisc = 0;
     int                 set_direction = 1;
+    int                 pcap_file_mode = 0;
     int                 status;
     int                 useconds;
     pid_t               child_pid;
@@ -72,25 +73,39 @@ pcap_capture(fko_srv_options_t *opts)
     if(opts->config[CONF_ENABLE_PCAP_PROMISC][0] == 'Y')
         promisc = 1;
 
-    log_msg(LOG_INFO, "Sniffing interface: %s",
-        opts->config[CONF_PCAP_INTF]);
+    if(opts->config[CONF_PCAP_FILE] != NULL
+            && opts->config[CONF_PCAP_FILE][0] != '\0')
+        pcap_file_mode = 1;
 
-    pcap = pcap_open_live(
-        opts->config[CONF_PCAP_INTF],
-        atoi(opts->config[CONF_MAX_SNIFF_BYTES]),
-        promisc, 100, errstr
-    );
+    if(pcap_file_mode == 1) {
+        log_msg(LOG_INFO, "Reading pcap file: %s",
+            opts->config[CONF_PCAP_FILE]);
 
-    if(pcap == NULL)
-    {
-        log_msg(LOG_ERR, "[*] pcap_open_live error: %s\n", errstr);
-        clean_exit(opts, FW_CLEANUP, EXIT_FAILURE);
-    }
+        pcap = pcap_open_offline(opts->config[CONF_PCAP_FILE], errstr);
 
-    if (pcap == NULL)
+        if(pcap == NULL)
+        {
+            log_msg(LOG_ERR, "[*] pcap_open_offline() error: %s\n",
+                    errstr);
+            clean_exit(opts, FW_CLEANUP, EXIT_FAILURE);
+        }
+    }
+    else
     {
-        log_msg(LOG_ERR, "[*] pcap error: %s", errstr);
-        clean_exit(opts, FW_CLEANUP, EXIT_FAILURE);
+        log_msg(LOG_INFO, "Sniffing interface: %s",
+            opts->config[CONF_PCAP_INTF]);
+
+        pcap = pcap_open_live(
+            opts->config[CONF_PCAP_INTF],
+            atoi(opts->config[CONF_MAX_SNIFF_BYTES]),
+            promisc, 100, errstr
+        );
+
+        if(pcap == NULL)
+        {
+            log_msg(LOG_ERR, "[*] pcap_open_live() error: %s\n", errstr);
+            clean_exit(opts, FW_CLEANUP, EXIT_FAILURE);
+        }
     }
 
     /* Set pcap filters, if any.
@@ -144,7 +159,8 @@ pcap_capture(fko_srv_options_t *opts)
 
     /* We are only interested on seeing packets coming into the interface.
     */
-    if (set_direction && (pcap_setdirection(pcap, PCAP_D_IN) < 0))
+    if (set_direction && (pcap_file_mode == 0)
+            && (pcap_setdirection(pcap, PCAP_D_IN) < 0))
         if(opts->verbose)
             log_msg(LOG_WARNING, "[*] Warning: pcap error on setdirection: %s.",
                 pcap_geterr(pcap));
@@ -153,7 +169,7 @@ pcap_capture(fko_srv_options_t *opts)
      *
      * NOTE: This is simply set to 0 for now until we find a need
      *       to actually use this mode (which when set on a FreeBSD
-     *       system, it silently breaks the packet capture). 
+     *       system, it silently breaks the packet capture).
     */
     if((pcap_setnonblock(pcap, DEF_PCAP_NONBLOCK, errstr)) == -1)
     {
@@ -233,6 +249,9 @@ pcap_capture(fko_srv_options_t *opts)
         */
         if(res > 0)
         {
+            if(opts->foreground == 1 && opts->verbose > 2)
+                log_msg(LOG_INFO, "pcap_dispatch() processed: %d packets", res);
+
             /* Count the set of processed packets (pcap_dispatch() return
              * value) - we use this as a comparison for --packet-limit regardless
              * of SPA packet validity at this point.
diff --git a/test/conf/spa_replay.pcap b/test/conf/spa_replay.pcap
new file mode 100644 (file)
index 0000000..4c963e9
Binary files /dev/null and b/test/conf/spa_replay.pcap differ
index 68b266e..b7dedad 100755 (executable)
@@ -20,6 +20,7 @@ my $cmd_out_tmp    = 'cmd.out';
 my $server_cmd_tmp = 'server_cmd.out';
 my $gpg_client_home_dir = "$conf_dir/client-gpg";
 my $gpg_client_home_dir_no_pw = "$conf_dir/client-gpg-no-pw";
+my $replay_pcap_file = "$conf_dir/spa_replay.pcap";
 
 my %cf = (
     'nat'                     => "$conf_dir/nat_fwknopd.conf",
@@ -129,6 +130,7 @@ my $NO  = 0;
 my $PRINT_LEN = 68;
 my $USE_PREDEF_PKTS = 1;
 my $USE_CLIENT = 2;
+my $USE_PCAP_FILE = 3;
 my $REQUIRED = 1;
 my $OPTIONAL = 0;
 my $NEW_RULE_REQUIRED = 1;
@@ -1189,6 +1191,25 @@ my @tests = (
         'fatal'    => $NO
     },
 
+    ### --pcap-file
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => '--pcap-file processing',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&process_pcap_file_directly,
+        'cmdline'  => '',
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd $default_server_conf_args " .
+            "--pcap-file $replay_pcap_file --foreground --verbose --verbose " .
+            "--verbose",
+        'server_positive_output_matches' => [qr/Replay\sdetected/i,
+            qr/candidate\sSPA/, qr/0x0000\:\s+2b/],
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+
     {
         'category' => 'Rijndael SPA',
         'subcategory' => 'client+server',
@@ -4243,6 +4264,46 @@ sub altered_non_base64_spa_data() {
     return $rv;
 }
 
+sub process_pcap_file_directly() {
+    my $test_hr = shift;
+
+    my $rv = 1;
+    my $server_was_stopped = 0;
+    my $fw_rule_created = 0;
+    my $fw_rule_removed = 0;
+
+    ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
+        = &client_server_interaction($test_hr, [], $USE_PCAP_FILE);
+
+    $rv = 0 unless $server_was_stopped;
+
+    if ($test_hr->{'fw_rule_created'} eq $NEW_RULE_REQUIRED) {
+        $rv = 0 unless $fw_rule_created;
+    } elsif ($test_hr->{'fw_rule_created'} eq $REQUIRE_NO_NEW_RULE) {
+        $rv = 0 if $fw_rule_created;
+    }
+
+    if ($test_hr->{'fw_rule_removed'} eq $NEW_RULE_REMOVED) {
+        $rv = 0 unless $fw_rule_removed;
+    } elsif ($test_hr->{'fw_rule_removed'} eq $REQUIRE_NO_NEW_REMOVED) {
+        $rv = 0 if $fw_rule_removed;
+    }
+
+    if ($test_hr->{'server_positive_output_matches'}) {
+        $rv = 0 unless &file_find_regex(
+            $test_hr->{'server_positive_output_matches'},
+            $MATCH_ALL, $server_test_file);
+    }
+
+    if ($test_hr->{'server_negative_output_matches'}) {
+        $rv = 0 if &file_find_regex(
+            $test_hr->{'server_negative_output_matches'},
+            $MATCH_ANY, $server_test_file);
+    }
+
+    return $rv;
+}
+
 sub fuzzer() {
     my $test_hr = shift;
 
@@ -4552,8 +4613,10 @@ sub client_server_interaction() {
                 $current_test_file);
             $rv = 0;
         }
-    } else {
+    } elsif ($spa_client_flag == $USE_PREDEF_PKTS) {
         &send_packets($pkts_hr);
+    } else {
+        ### pcap file mode, nothing to do
     }
 
     ### check to see if the SPA packet resulted in a new fw access rule
index e171a5e..92525d6 100644 (file)
--- a/todo.org
+++ b/todo.org
@@ -2,6 +2,11 @@
   This is the main todo org mode file for the fwknop project
 ** COMPLETED
    This bucket is for completed tasks.
+*** [server] Add the ability to process pcap files offline
+    :CLOSED: <2012-11-08 Thu>
+    Leverage pcap_open_offline() to process pcap files from disk instead of
+    sniffing the network live.
+    - Added a new '--pcap-file <file>' option for this purpose.
 *** Add --disable-gpg arg to the autoconf configure script
     :CLOSED: <2012-10-31 Wed>
     There needs to be a way to easily disable libgpgme usage even if it is
@@ -48,9 +53,6 @@
     :CLOSED: <2012-10-02 Tue>
     The access.c parsing code currently throws an error if there is not KEY
     variable in an access stanza even if GPG_ALLOW_NO_PW is set.
-** [server] Add the ability to process pcap files offline
-   Leverage pcap_open_offline() to process pcap files from disk instead of
-   sniffing the network live.
 ** [server] Add PF NAT support for OpenBSD systems
    fwknopd already supports various NAT modes on iptables, but it should be
    extended to support NAT on PF firewalls.