updated to IPTables::ChainMgr 1.2 and IPTables::Parse 1.1
authorMichael Rash <mbr@cipherdyne.org>
Fri, 9 Mar 2012 02:40:43 +0000 (21:40 -0500)
committerMichael Rash <mbr@cipherdyne.org>
Fri, 9 Mar 2012 02:40:43 +0000 (21:40 -0500)
deps/IPTables-ChainMgr/Changes
deps/IPTables-ChainMgr/MANIFEST
deps/IPTables-ChainMgr/Makefile.PL
deps/IPTables-ChainMgr/README
deps/IPTables-ChainMgr/VERSION
deps/IPTables-ChainMgr/lib/IPTables/ChainMgr.pm
deps/IPTables-Parse/Changes
deps/IPTables-Parse/MANIFEST
deps/IPTables-Parse/README
deps/IPTables-Parse/VERSION
deps/IPTables-Parse/lib/IPTables/Parse.pm

index ef4007a..a6f54d0 100644 (file)
@@ -1,5 +1,58 @@
 Revision history for Perl extension IPTables::ChainMgr.
 
+1.2 Fri Mar 02 21:09:57 2012
+    - Added set_chain_policy() function to allow built-in chain policies to be
+      set to the specified target.  iptables/ip6tables does not allow the
+      target to be set for non built-in chains.  Behind the scenes this
+      function runs the command "iptables -t <table> -P <chain> <target>".
+    - Minor update to print the iptables binary name in 'croak' error
+      conditions.  The binary name is either 'iptables' or 'ip6tables'.
+    - Minor perldoc updates to render links better (two spaces at the beginning
+      of lines).
+
+1.1 Tue Feb 28 21:15:11 2012
+    - Added META.{yml,json} files similarly to fixing this bug filed against
+      IPTables::Parse:
+        https://rt.cpan.org/Ticket/Display.html?id=75366
+    - Minor bug fix to ensure not to look for 'extended' hash keys in returned
+      IPTables::Parse data without an existence check first.
+    - Added the ability to specify 'ip6tables' when instantiating an
+      IPTables::ChainMgr object via 'new'.
+    - Updated license to the Artistic license.
+
+1.0 Mon Feb 27 21:56:44 2012
+    - Added 'ctstate' and 'state' keys to extended hashes for find_ip_rule()
+      and add_ip_rule().  This commit fixes:
+        https://rt.cpan.org/Ticket/Display.html?id=67614
+    - Bug fix for missing IPTables::Parse dependency by applying patch from the
+      CPAN bug tracking system here:
+        https://rt.cpan.org/Ticket/Display.html?id=43302
+
+0.9.9 Sun Feb 26 14:03:24 2012
+    - Updated to handle IPv6 via ip6tables.  All functions in the previous
+      version of this module now work with ip6tables - just instantiate the
+      IPTables::ChainMgr object with something like the following:
+
+        my %opts = (
+            'iptables' => $ip6tables_bin,
+            'iptout'   => '/tmp/ip6tables.out',
+            'ipterr'   => '/tmp/ip6tables.err',
+            'debug'    => 0,
+            'verbose'  => 0
+        );
+
+    - Switched to using NetAddr::IP instead of the old Net::IPv4Addr module.
+      This greatly assisted in the ability to offer IPv6 support via ip6tables.
+    - Bug fix to ensure proper handling of empty 'extended' hashes via the
+      'keys' function.
+    - (Miloslav Trmac) Bug fix for "Use of qw(...) as parentheses is
+      deprecated" warnings as described here:
+            https://bugzilla.redhat.com/show_bug.cgi?id=771781
+    - Added a test suite at: t/basic_tests.pl
+    - Migrated to git for source control:
+            http://www.cipherdyne.org/cgi-bin/gitweb.cgi?p=IPTables-ChainMgr.git;a=summary
+            https://github.com/mrash/IPTables-ChainMgr
+
 0.9 Sat Feb 11 23:11:45 2008
     - Added Net::IPv4Addr prerequisite to Makefile.PL (patch submitted by
       Dominik Gehl).
index 022cf14..38b80b4 100644 (file)
@@ -3,4 +3,5 @@ Makefile.PL
 MANIFEST
 README
 t/IPTables-ChainMgr.t
+t/basic_tests.pl
 lib/IPTables/ChainMgr.pm
index 4396418..1fd78aa 100644 (file)
@@ -5,7 +5,7 @@ use ExtUtils::MakeMaker;
 WriteMakefile(
     NAME              => 'IPTables::ChainMgr',
     VERSION_FROM      => 'lib/IPTables/ChainMgr.pm', # finds $VERSION
-    PREREQ_PM         => {'Net::IPv4Addr' => 0.10}, # e.g., Module::Name => 1.1
+    PREREQ_PM         => {'NetAddr::IP' => 4.0, 'IPTables::Parse' => 0.9}, # e.g., Module::Name => 1.1
     ($] >= 5.005 ?     ## Add these new keywords supported since 5.005
       (ABSTRACT_FROM  => 'lib/IPTables/ChainMgr.pm', # retrieve abstract from module
        AUTHOR         => 'Michael Rash <mbr@cipherdyne.org>') : ()),
index bfb1e83..98730c9 100644 (file)
@@ -1,4 +1,4 @@
-IPTables-ChainMgr version 0.01
+IPTables-ChainMgr version 1.2
 ==============================
 
 The README is used to introduce the module and provide instructions on
@@ -32,7 +32,7 @@ COPYRIGHT AND LICENCE
 
 Put the correct copyright and licence information here.
 
-Copyright (C) 2005-2008 by Michael Rash
+Copyright (C) 2005-2012 by Michael Rash
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself, either Perl version 5.8.5 or,
index 84b6910..a5541dc 100644 (file)
@@ -10,7 +10,7 @@
 #
 # Author: Michael Rash (mbr@cipherdyne.org)
 #
-# Version: 0.9
+# Version: 1.2
 #
 ##############################################################################
 #
@@ -26,14 +26,14 @@ use strict;
 use warnings;
 use vars qw($VERSION);
 
-$VERSION = '0.9';
+$VERSION = '1.2';
 
 sub new() {
     my $class = shift;
     my %args  = @_;
 
     my $self = {
-        _iptables  => $args{'iptables'}  || '/sbin/iptables',
+        _iptables  => $args{'iptables'}  || $args{'ip6tables'} || '/sbin/iptables',
         _iptout    => $args{'iptout'}    || '/tmp/ipt.out',
         _ipterr    => $args{'ipterr'}    || '/tmp/ipt.err',
         _ipt_alarm => $args{'ipt_alarm'} || 30,
@@ -43,17 +43,21 @@ sub new() {
         _ipt_exec_sleep => $args{'ipt_exec_sleep'} || 0,
         _sigchld_handler => $args{'sigchld_handler'} || \&REAPER,
     };
-    croak "[*] $self->{'_iptables'} incorrect iptables path.\n"
+    $self->{'_ipt_bin_name'} = 'iptables';
+    $self->{'_ipt_bin_name'} = $1 if $self->{'_iptables'} =~ m|.*/(\S+)|;
+
+    croak "[*] $self->{'_iptables'} incorrect $self->{'_ipt_bin_name'} path.\n"
         unless -e $self->{'_iptables'};
     croak "[*] $self->{'_iptables'} not executable.\n"
         unless -x $self->{'_iptables'};
+
     bless $self, $class;
 }
 
 sub chain_exists() {
     my $self = shift;
     my $table = shift || croak '[*] Must specify a table, e.g. "filter".';
-    my $chain = shift || croak '[*] Must specify a chain to create.';
+    my $chain = shift || croak '[*] Must specify a chain to check.';
     my $iptables = $self->{'_iptables'};
 
     ### see if the chain exists
@@ -108,11 +112,14 @@ sub delete_chain() {
     ### could not flush the chain
     return 0, $out_aref, $err_aref unless $rv;
 
+    my $ip_any_net = '0.0.0.0/0';
+    $ip_any_net = '::/0' if $self->{'_ipt_bin_name'} eq 'ip6tables';
+
     ### find and delete jump rules to this chain (we can't delete
     ### the chain until there are no references to it)
     my ($rulenum, $num_chain_rules)
-        = $self->find_ip_rule('0.0.0.0/0',
-            '0.0.0.0/0', $table, $jump_from_chain, $del_chain, {});
+        = $self->find_ip_rule($ip_any_net, $ip_any_net,
+            $table, $jump_from_chain, $del_chain, {});
 
     if ($rulenum) {
         $self->run_ipt_cmd(
@@ -125,14 +132,26 @@ sub delete_chain() {
     return $self->run_ipt_cmd("$iptables -t $table -X $del_chain");
 }
 
+sub set_chain_policy() {
+    my $self = shift;
+    my $table = shift || croak '[*] Must specify a table, e.g. "filter".';
+    my $chain = shift || croak '[*] Must specify a chain.';
+    my $target  = shift || croak qq|[-] Must specify an | .
+        qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|;
+    my $iptables = $self->{'_iptables'};
+
+    ### set the chain policy: note that $chain must be a built-in chain
+    return $self->run_ipt_cmd("$iptables -t $table -P $chain $target");
+}
+
 sub append_ip_rule() {
     my $self = shift;
     my $src = shift || croak '[-] Must specify a src address/network.';
     my $dst = shift || croak '[-] Must specify a dst address/network.';
     my $table   = shift || croak '[-] Must specify a table, e.g. "filter".';
     my $chain   = shift || croak '[-] Must specify a chain.';
-    my $target  = shift ||
-        croak '[-] Must specify a iptables target, e.g. "DROP"';
+    my $target  = shift || croak qq|[-] Must specify an | .
+        qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|;
 
     ### optionally add port numbers and protocols, etc.
     my $extended_href = shift || {};
@@ -150,10 +169,10 @@ sub append_ip_rule() {
 
     if ($rule_position) {
         my $msg = '';
-        if ($extended_href) {
+        if (keys %$extended_href) {
             $msg = "Table: $table, chain: $chain, $normalized_src -> " .
                 "$normalized_dst ";
-            for my $key qw(protocol s_port d_port mac_source) {
+            for my $key (qw(protocol s_port d_port mac_source)) {
                 $msg .= "$key $extended_href->{$key} "
                     if defined $extended_href->{$key};
             }
@@ -170,7 +189,7 @@ sub append_ip_rule() {
     my $msg     = '';
     my $idx_err = '';
 
-    if ($extended_href) {
+    if (keys %$extended_href) {
         $ipt_cmd = "$iptables -t $table -A $chain ";
         $ipt_cmd .= "-p $extended_href->{'protocol'} "
             if defined $extended_href->{'protocol'};
@@ -186,7 +205,7 @@ sub append_ip_rule() {
 
         $msg = "Table: $table, chain: $chain, added $normalized_src " .
             "-> $normalized_dst ";
-        for my $key qw(protocol s_port d_port mac_source) {
+        for my $key (qw(protocol s_port d_port mac_source)) {
             $msg .= "$key $extended_href->{$key} "
                 if defined $extended_href->{$key};
         }
@@ -222,7 +241,8 @@ sub add_ip_rule() {
     my $table   = shift || croak '[-] Must specify a table, e.g. "filter".';
     my $chain   = shift || croak '[-] Must specify a chain.';
     my $target  = shift ||
-        croak '[-] Must specify a iptables target, e.g. "DROP"';
+        croak qq|[-] Must specify an $self->{'_ipt_bin_name'} | .
+            qq|target, e.g. "DROP"|;
     ### optionally add port numbers and protocols, etc.
     my $extended_href = shift || {};
     my $iptables = $self->{'_iptables'};
@@ -239,10 +259,10 @@ sub add_ip_rule() {
 
     if ($rule_position) {
         my $msg = '';
-        if ($extended_href) {
+        if (keys %$extended_href) {
             $msg = "Table: $table, chain: $chain, $normalized_src -> " .
                 "$normalized_dst ";
-            for my $key qw(protocol s_port d_port mac_source) {
+            for my $key (qw(protocol s_port d_port mac_source)) {
                 $msg .= "$key $extended_href->{$key} "
                     if defined $extended_href->{$key};
             }
@@ -269,7 +289,7 @@ sub add_ip_rule() {
     }
     $rulenum = 1 if $rulenum == 0;
 
-    if ($extended_href) {
+    if (keys %$extended_href) {
         $ipt_cmd = "$iptables -t $table -I $chain $rulenum ";
         $ipt_cmd .= "-p $extended_href->{'protocol'} "
             if defined $extended_href->{'protocol'};
@@ -281,11 +301,15 @@ sub add_ip_rule() {
             if defined $extended_href->{'d_port'};
         $ipt_cmd .= "-m mac --mac-source $extended_href->{'mac_source'} "
             if defined $extended_href->{'mac_source'};
+        $ipt_cmd .= "-m state --state $extended_href->{'state'} "
+            if defined $extended_href->{'state'};
+        $ipt_cmd .= "-m conntrack --ctstate $extended_href->{'ctstate'} "
+            if defined $extended_href->{'ctstate'};
         $ipt_cmd .= "-j $target";
 
         $msg = "Table: $table, chain: $chain, added $normalized_src " .
             "-> $normalized_dst ";
-        for my $key qw(protocol s_port d_port mac_source) {
+        for my $key (qw(protocol s_port d_port mac_source)) {
             $msg .= "$key $extended_href->{$key} "
                 if defined $extended_href->{$key};
         }
@@ -319,8 +343,8 @@ sub delete_ip_rule() {
     my $dst = shift || croak '[-] Must specify a dst address/network.';
     my $table  = shift || croak '[-] Must specify a table, e.g. "filter".';
     my $chain  = shift || croak '[-] Must specify a chain.';
-    my $target = shift ||
-        croak '[-] Must specify a iptables target, e.g. "DROP"';
+    my $target = shift || croak qq|[-] Must specify an | .
+        qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|;
     ### optionally add port numbers and protocols, etc.
     my $extended_href = shift || {};
     my $iptables = $self->{'_iptables'};
@@ -341,8 +365,8 @@ sub delete_ip_rule() {
     }
 
     my $extended_msg = '';
-    if ($extended_href) {
-        for my $key qw(protocol s_port d_port mac_source) {
+    if (keys %$extended_href) {
+        for my $key (qw(protocol s_port d_port mac_source)) {
             $extended_msg .= "$key: $extended_href->{$key} "
                 if defined $extended_href->{$key};
         }
@@ -364,10 +388,11 @@ sub find_ip_rule() {
     my $verbose = $self->{'_verbose'};
     my $src   = shift || croak '[*] Must specify source address.';
     my $dst   = shift || croak '[*] Must specify destination address.';
-    my $table = shift || croak '[*] Must specify iptables table.';
-    my $chain = shift || croak '[*] Must specify iptables chain.';
-    my $target = shift ||
-        croak '[*] Must specify iptables target (this may be a chain).';
+    my $table = shift || croak qq|[*] Must specify $self->{'_ipt_bin_name'} table.|;
+    my $chain = shift || croak qq|[*] Must specify $self->{'_ipt_bin_name'} chain.|;
+    my $target = shift || croak qq|[*] Must specify | .
+        qq|$self->{'_ipt_bin_name'} target (this may be a chain).|;
+
     ### optionally add port numbers and protocols, etc.
     my $extended_href = shift || {};
     my $iptables = $self->{'_iptables'};
@@ -394,24 +419,48 @@ sub find_ip_rule() {
 
     my $chain_aref = $ipt_parse->chain_rules($table, $chain);
 
+    $src = $self->normalize_net($src) if defined $extended_href->{'normalize'}
+        and $extended_href->{'normalize'};
+    $dst = $self->normalize_net($dst) if defined $extended_href->{'normalize'}
+        and $extended_href->{'normalize'};
+
     my $rulenum = 1;
     for my $rule_href (@$chain_aref) {
         if ($rule_href->{'target'} eq $target
                 and $rule_href->{'src'} eq $src
                 and $rule_href->{'dst'} eq $dst) {
-            if ($extended_href) {
+            if (keys %$extended_href) {
                 my $found = 1;
-                for my $key qw(
+                for my $key (qw(
                     protocol
                     s_port
                     d_port
                     to_ip
                     to_port
-                ) {
+                    state
+                    ctstate
+                )) {
                     if (defined $extended_href->{$key}) {
-                        unless ($extended_href->{$key}
-                                eq $rule_href->{$key}) {
-                            $found = 0
+                        if (defined $rule_href->{$key}) {
+                            if ($key eq 'state' or $key eq 'ctstate') {
+                                ### make sure that state ordering as reported
+                                ### by iptables is accounted for vs. what was
+                                ### supplied to the module
+                                unless (&state_compare($extended_href->{$key},
+                                        $rule_href->{$key})) {
+                                    $found = 0;
+                                    last;
+                                }
+                            } else {
+                                unless ($extended_href->{$key}
+                                        eq $rule_href->{$key}) {
+                                    $found = 0;
+                                    last;
+                                }
+                            }
+                        } else {
+                            $found = 0;
+                            last;
                         }
                     }
                 }
@@ -436,21 +485,56 @@ sub find_ip_rule() {
     return 0, $#$chain_aref+1;
 }
 
+sub state_compare() {
+    my ($state_str1, $state_str2) = @_;
+
+    my @states1 = split /,/, $state_str1;
+    my @states2 = split /,/, $state_str2;
+
+    for my $state1 (@states1) {
+        my $found = 0;
+        for my $state2 (@states2) {
+            if ($state1 eq $state2) {
+                $found = 1;
+                last;
+            }
+        }
+        return 0 unless $found;
+    }
+
+    for my $state2 (@states2) {
+        my $found = 0;
+        for my $state1 (@states1) {
+            if ($state2 eq $state1) {
+                $found = 1;
+                last;
+            }
+        }
+        return 0 unless $found;
+    }
+
+    return 1;
+}
+
 sub normalize_net() {
     my $self = shift;
     my $net  = shift || croak '[*] Must specify net.';
 
-    ### regex to match an IP address
-    my $ip_re = '(?:\d{1,3}\.){3}\d{1,3}';
+    my $normalized_net = $net;  ### establish default
 
-    my $normalized_net = '';
-    if ($net =~ m|$ip_re/$ip_re| or $net =~ m|$ip_re/\d+|) {
-        my $n = new NetAddr::IP $net
-            or croak "[*] Could not acquire NetAddr::IP object for $net";
-        $normalized_net = $n->network()->cidr();
-    } else {
-        ### it is a hostname or an individual IP
-        $normalized_net = $net;
+    ### regex to match an IPv4 address
+    my $ipv4_re = qr/(?:\d{1,3}\.){3}\d{1,3}/;
+
+    if ($net =~ m|/| and $net =~ $ipv4_re or $net =~ m|:|) {
+        if ($net =~ m|:|) {  ### an IPv6 address
+            my $n = new6 NetAddr::IP $net
+                or croak "[*] Could not acquire NetAddr::IP object for $net";
+            $normalized_net = lc($n->network()->short()) . '/' . $n->masklen();
+        } else {
+            my $n = new NetAddr::IP $net
+                or croak "[*] Could not acquire NetAddr::IP object for $net";
+            $normalized_net = $n->network()->cidr();
+        }
     }
     return $normalized_net;
 }
@@ -469,9 +553,12 @@ sub add_jump_rule() {
             "not allowed."], [];
     }
 
+    my $ip_any_net = '0.0.0.0/0';
+    $ip_any_net = '::/0' if $self->{'_ipt_bin_name'} eq 'ip6tables';
+
     ### first check to see if the jump rule already exists
     my ($rule_position, $num_chain_rules)
-        = $self->find_ip_rule('0.0.0.0/0', '0.0.0.0/0', $table,
+        = $self->find_ip_rule($ip_any_net, $ip_any_net, $table,
             $from_chain, $to_chain, {});
 
     ### check to see if the insertion index ($rulenum) is too big
@@ -508,7 +595,8 @@ sub REAPER {
 
 sub run_ipt_cmd() {
     my $self  = shift;
-    my $cmd = shift || croak '[*] Must specify an iptables command to run.';
+    my $cmd = shift || croak qq|[*] Must specify an | .
+        qq|$self->{'_ipt_bin_name'} command to run.|;
     my $iptables  = $self->{'_iptables'};
     my $iptout    = $self->{'_iptout'};
     my $ipterr    = $self->{'_ipterr'};
@@ -519,8 +607,10 @@ sub run_ipt_cmd() {
     my $ipt_exec_sleep = $self->{'_ipt_exec_sleep'};
     my $sigchld_handler = $self->{'_sigchld_handler'};
 
-    croak "[*] $cmd does not look like an iptables command."
-        unless $cmd =~ m|^\s*iptables| or $cmd =~ m|^\S+/iptables|;
+
+    croak "[*] $cmd does not look like an $self->{'_ipt_bin_name'} command."
+        unless $cmd =~ m|^\s*iptables| or $cmd =~ m|^\S+/iptables|
+            or $cmd =~ m|^\s*ip6tables| or $cmd =~ m|^\S+/ip6tables|;
 
     my $rv = 1;
     my @stdout = ();
@@ -543,7 +633,7 @@ sub run_ipt_cmd() {
         if ($debug or $verbose) {
             print $fh localtime() . " [+] IPTables::ChainMgr: ",
                 "sleeping for $ipt_exec_sleep seconds before ",
-                "executing iptables command.\n";
+                "executing $self->{'_ipt_bin_name'} command.\n";
         }
         sleep $ipt_exec_sleep;
     }
@@ -571,7 +661,8 @@ sub run_ipt_cmd() {
                 ### iptables should never take longer than 30 seconds to execute,
                 ### unless there is some absolutely enormous policy or the kernel
                 ### is exceedingly busy
-                local $SIG{'ALRM'} = sub {die "[*] iptables command timeout.\n"};
+                local $SIG{'ALRM'} = sub {die "[*] $self->{'_ipt_bin_name'} " .
+                    "command timeout.\n"};
                 alarm $ipt_alarm;
                 waitpid($ipt_pid, 0);
                 alarm 0;
@@ -580,7 +671,7 @@ sub run_ipt_cmd() {
                 kill 9, $ipt_pid unless kill 15, $ipt_pid;
             }
         } else {
-            croak "[*] Could not fork iptables: $!"
+            croak "[*] Could not fork $self->{'_ipt_bin_name'}: $!"
                 unless defined $ipt_pid;
 
             ### exec the iptables command and preserve stdout and stderr
@@ -602,7 +693,8 @@ sub run_ipt_cmd() {
     }
 
     if ($debug or $verbose) {
-        print $fh localtime() . "     iptables command stdout:\n";
+        print $fh localtime() . "     $self->{'_ipt_bin_name'} " .
+            "command stdout:\n";
         for my $line (@stdout) {
             if ($line =~ /\n$/) {
                 print $fh $line;
@@ -610,7 +702,8 @@ sub run_ipt_cmd() {
                 print $fh $line, "\n";
             }
         }
-        print $fh localtime() . "     iptables command stderr:\n";
+        print $fh localtime() . "     $self->{'_ipt_bin_name'} " .
+            "command stderr:\n";
         for my $line (@stderr) {
             if ($line =~ /\n$/) {
                 print $fh $line;
@@ -629,18 +722,20 @@ __END__
 
 =head1 NAME
 
-IPTables::ChainMgr - Perl extension for manipulating iptables policies
+IPTables::ChainMgr - Perl extension for manipulating iptables and ip6tables policies
 
 =head1 SYNOPSIS
 
   use IPTables::ChainMgr;
 
+  my $ipt_bin = '/sbin/iptables'; # can set this to /sbin/ip6tables
+
   my %opts = (
-      'iptables' => '/sbin/iptables',
+      'iptables' => $ipt_bin, # can specify 'ip6tables' hash key instead
       'iptout'   => '/tmp/iptables.out',
       'ipterr'   => '/tmp/iptables.err',
       'debug'    => 0,
-      'verbose'  => 0
+      'verbose'  => 0,
 
       ### advanced options
       'ipt_alarm' => 5,  ### max seconds to wait for iptables execution.
@@ -670,55 +765,94 @@ IPTables::ChainMgr - Perl extension for manipulating iptables policies
       $ipt_obj->delete_chain('filter', 'INPUT', 'CUSTOM');
   }
 
+  # set the policy on the FORWARD table to DROP
+  $ipt_obj->set_chain_policy('filter', 'FORWARD', 'DROP');
+
   # create new iptables chain in the 'filter' table
   $ipt_obj->create_chain('filter', 'CUSTOM');
 
+  # translate a network into the same representation that iptables or
+  # ip6tables uses (e.g. '10.1.2.3/24' is properly represented as '10.1.2.0/24',
+  # and '0000:0000:00AA:0000:0000:AA00:0000:0001/64' = '0:0:aa::/64')
+  $normalized_net = $ipt_obj->normalize_net('10.1.2.3/24');
+
   # add rule to jump packets from the INPUT chain into CUSTOM at the
   # 4th rule position
   $ipt_obj->add_jump_rule('filter', 'INPUT', 4, 'CUSTOM');
 
-  # find rule that allows all traffic from 10.1.2.3 to 192.168.1.2
-  ($rv, $rule_num) = $ipt_obj->find_ip_rule('10.1.2.3', '192.168.1.2',
-      'filter', 'INPUT', 'ACCEPT', {});
+  # find rule that allows all traffic from 10.1.2.0/24 to 192.168.1.2
+  ($rv, $rule_num) = $ipt_obj->find_ip_rule('10.1.2.0/24', '192.168.1.2',
+      'filter', 'INPUT', 'ACCEPT', {'normalize' => 1});
 
-  # find rule that allows all TCP port 80 traffic from 10.1.2.3 to
+  # find rule that allows all TCP port 80 traffic from 10.1.2.0/24 to
   # 192.168.1.1
-  ($rv, $rule_num) = $ipt_obj->find_ip_rule('10.1.2.3', '192.168.1.2',
-      'filter', 'INPUT', 'ACCEPT', {'protocol' => 'tcp', 's_port' => 0,
-      'd_port' => 80});
+  ($rv, $rule_num) = $ipt_obj->find_ip_rule('10.1.2.0/24', '192.168.1.2',
+      'filter', 'INPUT', 'ACCEPT', {'normalize' => 1, 'protocol' => 'tcp',
+      's_port' => 0, 'd_port' => 80});
 
   # add rule at the 5th rule position to allow all traffic from
-  # 10.1.2.3 to 192.168.1.2 via the INPUT chain in the filter table
-  ($rv, $out_ar, $errs_ar) = $ipt_obj->add_ip_rule('10.1.2.3',
+  # 10.1.2.0/24 to 192.168.1.2 via the INPUT chain in the filter table
+  ($rv, $out_ar, $errs_ar) = $ipt_obj->add_ip_rule('10.1.2.0/24',
       '192.168.1.2', 5, 'filter', 'INPUT', 'ACCEPT', {});
 
   # add rule at the 4th rule position to allow all traffic from
-  # 10.1.2.3 to 192.168.1.2 over TCP port 80 via the CUSTOM chain
+  # 10.1.2.0/24 to 192.168.1.2 over TCP port 80 via the CUSTOM chain
   # in the filter table
-  ($rv, $out_ar, $errs_ar) = $ipt_obj->add_ip_rule('10.1.2.3',
+  ($rv, $out_ar, $errs_ar) = $ipt_obj->add_ip_rule('10.1.2.0/24',
       '192.168.1.2', 4, 'filter', 'CUSTOM', 'ACCEPT',
       {'protocol' => 'tcp', 's_port' => 0, 'd_port' => 80});
 
   # append rule at the end of the CUSTOM chain in the filter table to
-  # allow all traffic from 10.1.2.3 to 192.168.1.2 via port 80
-  ($rv, $out_ar, $errs_ar) = $ipt_obj->append_ip_rule('10.1.2.3',
+  # allow all traffic from 10.1.2.0/24 to 192.168.1.2 via port 80
+  ($rv, $out_ar, $errs_ar) = $ipt_obj->append_ip_rule('10.1.2.0/24',
       '192.168.1.2', 'filter', 'CUSTOM', 'ACCEPT',
       {'protocol' => 'tcp', 's_port' => 0, 'd_port' => 80});
 
+  # for each of the examples above, here are ip6tables analogs
+  # (requires instantiating the IPTables::ChainMgr object with
+  # /sbin/ip6tables): find rule that allows all traffic from fe80::200:f8ff:fe21:67cf
+  # to 0:0:aa::/64
+  ($rv, $rule_num) = $ipt_obj->find_ip_rule('fe80::200:f8ff:fe21:67cf', '0:0:aa::/64',
+      'filter', 'INPUT', 'ACCEPT', {'normalize' => 1});
+
+  # find rule that allows all TCP port 80 traffic from fe80::200:f8ff:fe21:67c to 0:0:aa::/64
+  ($rv, $rule_num) = $ipt_obj->find_ip_rule('fe80::200:f8ff:fe21:67cf', '0:0:aa::/64',
+      'filter', 'INPUT', 'ACCEPT', {'normalize' => 1, 'protocol' => 'tcp',
+      's_port' => 0, 'd_port' => 80});
+
+  # add rule at the 5th rule position to allow all traffic from
+  # fe80::200:f8ff:fe21:67c to 0:0:aa::/64 via the INPUT chain in the filter table
+  ($rv, $out_ar, $errs_ar) = $ipt_obj->add_ip_rule('fe80::200:f8ff:fe21:67cf',
+      '0:0:aa::/64', 5, 'filter', 'INPUT', 'ACCEPT', {});
+
+  # add rule at the 4th rule position to allow all traffic from
+  # fe80::200:f8ff:fe21:67c to 0:0:aa::/64 over TCP port 80 via the CUSTOM chain
+  # in the filter table
+  ($rv, $out_ar, $errs_ar) = $ipt_obj->add_ip_rule('fe80::200:f8ff:fe21:67cf',
+      '0:0:aa::/64', 4, 'filter', 'CUSTOM', 'ACCEPT',
+      {'protocol' => 'tcp', 's_port' => 0, 'd_port' => 80});
+
+  # append rule at the end of the CUSTOM chain in the filter table to
+  # allow all traffic from fe80::200:f8ff:fe21:67c to 0:0:aa::/64 via port 80
+  ($rv, $out_ar, $errs_ar) = $ipt_obj->append_ip_rule('fe80::200:f8ff:fe21:67cf',
+      '0:0:aa::/64', 'filter', 'CUSTOM', 'ACCEPT',
+      {'protocol' => 'tcp', 's_port' => 0, 'd_port' => 80});
+
   # run an arbitrary iptables command and collect the output
   ($rv, $out_ar, $errs_ar) = $ipt_obj->run_ipt_cmd(
           '/sbin/iptables -v -n -L');
 
 =head1 DESCRIPTION
 
-The C<IPTables::ChainMgr> package provide an interface to manipulate iptables
-policies on Linux systems through the direct execution of iptables commands.
-Although making a perl extension of libiptc provided by the iptables project is
-possible (and has been done by the IPTables::libiptc module available from CPAN),
-it is also easy enough to just execute iptables commands directly in order to
-both parse and change the configuration of the policy.  Further, this simplifies
-installation since the only external requirement is (in the spirit of scripting)
-to be able to point IPTables::ChainMgr at an installed iptables binary instead
+The C<IPTables::ChainMgr> package provides an interface to manipulate iptables
+and ip6tables policies on Linux systems through the direct execution of
+iptables/ip6tables commands.  Although making a perl extension of libiptc
+provided by the Netfilter project is possible (and has been done by the
+IPTables::libiptc module available from CPAN), it is also easy enough to just
+execute iptables/ip6tables commands directly in order to both parse and change
+the configuration of the policy.  Further, this simplifies installation since
+the only external requirement is (in the spirit of scripting) to be able to
+point IPTables::ChainMgr at an installed iptables or ip6tables binary instead
 of having to compile against a library.
 
 =head1 FUNCTIONS
@@ -732,18 +866,21 @@ functions:
 
 This function tests whether or not a chain (e.g. 'INPUT') exists within the
 specified table (e.g. 'filter').  This is most useful to test whether
-a custom chain has been added to the running iptables policy.  The return values
-are (as with many IPTables::ChainMgr functions) an array of three things: a
-numeric value, and both the stdout and stderr of the iptables command in the
-form of array references.  So, an example invocation of the chain_exists()
-function would be:
+a custom chain has been added to the running iptables/ip6tables policy.  The
+return values are (as with many IPTables::ChainMgr functions) an array of
+three things: a numeric value, and both the stdout and stderr of the iptables
+or ip6tables command in the form of array references.  So, an example
+invocation of the chain_exists() function would be:
 
   ($rv, $out_ar, $errs_ar) = $ipt_obj->chain_exists('filter', 'CUSTOM');
 
 If $rv is 1, then the CUSTOM chain exists in the filter table, and 0 otherwise.
 The $out_ar array reference contains the output of the command "/sbin/iptables -t filter -v -n -L CUSTOM",
 which will contain the rules in the CUSTOM chain (if it exists) or nothing (if not).
-The $errs_ar array reference contains the stderr of the iptables command.
+The $errs_ar array reference contains the stderr of the iptables command.  As
+with all IPTables::ChainMgr functions, if the IPTables::ChainMgr object was
+instantiated with the ip6tables binary path, then the above command would
+become "/sbin/ip6tables -t filter -v -n -L CUSTOM".
 
 =item create_chain($table, $chain)
 
@@ -753,7 +890,8 @@ values are given like so:
   ($rv, $out_ar, $errs_ar) = $ipt_obj->create_chain('filter', 'CUSTOM');
 
 Behind the scenes, the create_chain() function in the example above runs the
-iptables command "/sbin/iptables -t filter -N CUSTOM".
+iptables command "/sbin/iptables -t filter -N CUSTOM", or for ip6tables
+"/sbin/ip6tables -t filter -N CUSTOM".
 
 =item flush_chain($table, $chain)
 
@@ -762,8 +900,18 @@ values are returned:
 
   ($rv, $out_ar, $errs_ar) = $ipt_obj->flush_chain('filter', 'CUSTOM');
 
-The flush_chain() function in the example above executes the iptables command
-"/sbin/iptables -t filter -F CUSTOM"
+The flush_chain() function in the example above executes the command
+"/sbin/iptables -t filter -F CUSTOM" or "/sbin/ip6tables -t filter -F CUSTOM".
+
+=item set_chain_policy($table, $chain, $target)
+
+This function sets the policy of a built-in chain (iptables/ip6tables does not allow
+this for non built-in chains) to the specified target:
+
+  ($rv, $out_ar, $errs_ar) = $ipt_obj->set_chain_policy('filter', 'FORWARD', 'DROP');
+
+In this example, the following command is executed behind the scenes:
+"/sbin/iptables -t filter -P FORWARD DROP" or "/sbin/ip6tables -t filter -P FORWARD DROP".
 
 =item delete_chain($table, $jump_from_chain, $chain)
 
@@ -784,19 +932,34 @@ This function parses the specified chain to see if there is a rule that
 matches the $src, $dst, $target, and (optionally) any %extended_info
 criteria.  The return values are the rule number in the chain (or zero
 if it doesn't exist), and the total number of rules in the chain.  Below
-are two examples; the first is to find an ACCEPT rule for 10.1.2.3 to
+are four examples; the first is to find an ACCEPT rule for 10.1.2.0/24 to
 communicate with 192.168.1.2 in the INPUT chain, and the second is the
-same except that the rule is restricted to TCP port 80:
+same except that the rule is restricted to TCP port 80.  The third and
+forth examples illustrate ip6tables analogs of the first two examples
+with source IP fe80::200:f8ff:fe21:67cf/128 and destination network: 0:0:aa::/64
 
-  ($rulenum, $chain_rules) = $ipt_obj->find_ip_rule('10.1.2.3',
-      '192.168.1.2', 'filter', 'INPUT', 'ACCEPT', {});
+  ($rulenum, $chain_rules) = $ipt_obj->find_ip_rule('10.1.2.0/24',
+      '192.168.1.2', 'filter', 'INPUT', 'ACCEPT', {'normalize' => 1});
   if ($rulenum) {
       print "matched rule $rulenum out of $chain_rules rules\n";
   }
 
-  ($rulenum, $chain_rules) = $ipt_obj->find_ip_rule('10.1.2.3',
+  ($rulenum, $chain_rules) = $ipt_obj->find_ip_rule('10.1.2.0/24',
       '192.168.1.2', 'filter', 'INPUT', 'ACCEPT',
-      {'protocol' => 'tcp', 's_port' => 0, 'd_port' => 80});
+      {'normalize' => 1, 'protocol' => 'tcp', 's_port' => 0, 'd_port' => 80});
+  if ($rulenum) {
+      print "matched rule $rulenum out of $chain_rules rules\n";
+  }
+
+  ($rulenum, $chain_rules) = $ipt_obj->find_ip_rule('fe80::200:f8ff:fe21:67cf/128',
+    '0:0:aa::/64', 'filter', 'INPUT', 'ACCEPT', {'normalize' => 1});
+  if ($rulenum) {
+      print "matched rule $rulenum out of $chain_rules rules\n";
+  }
+
+  ($rulenum, $chain_rules) = $ipt_obj->find_ip_rule('fe80::200:f8ff:fe21:67cf/128',
+      '0:0:aa::/64', 'filter', 'INPUT', 'ACCEPT',
+      {'normalize' => 1, 'protocol' => 'tcp', 's_port' => 0, 'd_port' => 80});
   if ($rulenum) {
       print "matched rule $rulenum out of $chain_rules rules\n";
   }
@@ -829,6 +992,15 @@ jumped to the CUSTOM chain from the INPUT chain at rule 4:
 
   ($rv, $out_ar, $errs_ar) = $ipt_obj->add_jump_rule('filter', 'INPUT', 4, 'CUSTOM');
 
+=item normalize_net($net)
+
+This function translates an IP/network into the same representation that iptables
+or ip6tables uses upon listing a policy.  The first example shows an IPv4 network
+and how iptables lists it, and the second is an IPv6 network:
+
+  print $ipt_obj->normalize_net('10.1.2.3/24'), "\n" # prints '10.1.2.0/24'
+  print $ipt_obj->normalize_net('0000:0000:00AA:0000:0000:AA00:0000:0001/64'), "\n" # prints '0:0:aa::/64'
+
 =item run_ipt_cmd($cmd)
 
 This function is a generic work horse function for executing iptables commands,
@@ -852,23 +1024,27 @@ and stderr.  Here is an example to list all rules in the user-defined chain
 =head1 SEE ALSO
 
 The IPTables::ChainMgr extension is closely associated with the IPTables::Parse
-extension, and both are heavily used by the psad, fwsnort, and fwknop projects
-to manipulate iptables policies based on various criteria (see the psad(8),
-fwsnort(8), and fwknop(8) man pages).  As always, the iptables(8) man page
-provides the best information on command line execution and theory behind
-iptables.
+extension, and both are heavily used by the psad and fwsnort projects to
+manipulate iptables policies based on various criteria (see the psad(8) and
+fwsnort(8) man pages).  As always, the iptables(8) man page provides the best
+information on command line execution and theory behind iptables.
 
 Although there is no mailing that is devoted specifically to the IPTables::ChainMgr
 extension, questions about the extension will be answered on the following
 lists:
 
   The psad mailing list: http://lists.sourceforge.net/lists/listinfo/psad-discuss
-  The fwknop mailing list: http://lists.sourceforge.net/lists/listinfo/fwknop-discuss
   The fwsnort mailing list: http://lists.sourceforge.net/lists/listinfo/fwsnort-discuss
 
-The latest version of the IPTables::ChainMgr extension can be found at:
+The latest version of the IPTables::ChainMgr extension can be found on CPAN and
+also here:
+
+  http://www.cipherdyne.org/modules/
+
+Source control is provided by git:
 
-http://www.cipherdyne.org/modules/
+  http://www.cipherdyne.org/git/IPTables-ChainMgr.git
+  http://www.cipherdyne.org/cgi-bin/gitweb.cgi?p=IPTables-ChainMgr.git;a=summary
 
 =head1 CREDITS
 
@@ -881,16 +1057,19 @@ Thanks to the following people:
 =head1 AUTHOR
 
 The IPTables::ChainMgr extension was written by Michael Rash F<E<lt>mbr@cipherdyne.orgE<gt>>
-to support the psad, fwknop, and fwsnort projects.  Please send email to
-this address if there are any questions, comments, or bug reports.
+to support the psad and fwsnort projects.  Please send email to this address if
+there are any questions, comments, or bug reports.
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright (C) 2005-2008 by Michael Rash
+Copyright (C) 2005-2012 Michael Rash.  All rights reserved.
 
-This library is free software; you can redistribute it and/or modify
-it under the same terms as Perl itself, either Perl version 5.8.5 or,
-at your option, any later version of Perl 5 you may have available.
+This module is free software.  You can redistribute it and/or
+modify it under the terms of the Artistic License 2.0.  More information
+can be found here: http://www.perl.com/perl/misc/Artistic.html
 
+This program is distributed "as is" in the hope that it will be useful,
+but without any warranty; without even the implied warranty of
+merchantability or fitness for a particular purpose.
 
 =cut
index 12bf5c4..5278d4b 100644 (file)
@@ -1,5 +1,37 @@
 Revision history for Perl extension IPTables::Parse.
 
+1.1 Fri Mar 02 22:31:12 2012
+    - Minor update to print the iptables binary name in 'croak' error
+      conditions.  The binary name is either 'iptables' or 'ip6tables'.
+    - Minor perldoc updates to render links better (two spaces at the beginning
+      of lines).
+
+1.0 Tue Feb 28 21:45:19 2012
+    - Added META.{yml,json} files to fix this bug:
+        https://rt.cpan.org/Ticket/Display.html?id=75366
+    - Added the ability to specify 'ip6tables' when instantiating an
+      IPTables::ChainMgr object via 'new'.
+    - Updated license to the Artistic license.
+
+0.9 Sun Feb 26 21:01:45 2012
+    - Applied slightly modified patch from SSIMON to properly pick up usage of
+      state tracking in rule extended information as shown in this bug:
+            https://rt.cpan.org/Ticket/Display.html?id=67372#txn-925687
+      Rule 'extended' hash now includes the 'state' or 'ctstate' key
+      depending on which iptables state tracking module is being used (if
+      any).
+
+0.8 Sun Feb 26 14:03:24 2012
+    - Major update to support ip6tables policies.
+    - Added test suite script t/basic_tests.pl to exercise major functions for
+      both iptables and ip6tables.
+    - Bugfix for default_log() and default_drop() functions to ensure that
+      a proper return value is given in addition to the return of a results
+      hash.
+    - Migrated to git for source control:
+            http://www.cipherdyne.org/cgi-bin/gitweb.cgi?p=IPTables-Parse.git;a=summary
+            https://github.com/mrash/IPTables-Parse
+
 0.7 Fri Oct 17 11:55:01 2008
     - Completely re-worked the manner in which iptables commands are executed
       so that they are sent through a single function with various options
index 2a609bb..9e99b0f 100644 (file)
@@ -3,4 +3,5 @@ Makefile.PL
 MANIFEST
 README
 t/IPTables-Parse.t
+t/basic_tests.pl
 lib/IPTables/Parse.pm
index 502b5cd..d293376 100644 (file)
@@ -1,4 +1,4 @@
-IPTables-Parse version 0.01
+IPTables-Parse version 1.1
 ===========================
 
 The README is used to introduce the module and provide instructions on
@@ -30,7 +30,7 @@ COPYRIGHT AND LICENCE
 
 This module is distributed under the same license as perl itself.
 
-Copyright (C) 2005 by Michael Rash
+Copyright (C) 2005-2012 by Michael Rash
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself, either Perl version 5.8.5 or,
index 2e7c7ee..ab5ca42 100644 (file)
@@ -3,11 +3,11 @@
 #
 # File: IPTables::Parse.pm
 #
-# Purpose: Perl interface to parse iptables rulesets.
+# Purpose: Perl interface to parse iptables and ip6tables rulesets.
 #
 # Author: Michael Rash (mbr@cipherdyne.org)
 #
-# Version: 0.7
+# Version: 1.1
 #
 ##################################################################
 #
@@ -21,14 +21,14 @@ use strict;
 use warnings;
 use vars qw($VERSION);
 
-$VERSION = '0.7';
+$VERSION = '1.1';
 
 sub new() {
     my $class = shift;
     my %args  = @_;
 
     my $self = {
-        _iptables => $args{'iptables'} || '/sbin/iptables',
+        _iptables => $args{'iptables'} || $args{'ip6tables'} || '/sbin/iptables',
         _iptout    => $args{'iptout'}    || '/tmp/ipt.out',
         _ipterr    => $args{'ipterr'}    || '/tmp/ipt.err',
         _ipt_alarm => $args{'ipt_alarm'} || 30,
@@ -42,6 +42,10 @@ sub new() {
         unless -e $self->{'_iptables'};
     croak "[*] $self->{'_iptables'} not executable.\n"
         unless -x $self->{'_iptables'};
+
+    $self->{'_ipt_bin_name'} = 'iptables';
+    $self->{'_ipt_bin_name'} = $1 if $self->{'_iptables'} =~ m|.*/(\S+)|;
+
     bless $self, $class;
 }
 
@@ -91,6 +95,8 @@ sub chain_rules() {
 
     my $found_chain  = 0;
     my @ipt_lines = ();
+
+    ### only used for IPv4 + NAT
     my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|;
 
     ### array of hash refs
@@ -154,17 +160,32 @@ sub chain_rules() {
             'to_port'  => '',
             'extended' => '',
             'state'    => '',
+            'ctstate'  => '',
             'raw'      => $line
         );
 
         if ($ipt_verbose) {
+
+            ### iptables:
             ### 0     0 ACCEPT  tcp  --  eth1 * 192.168.10.3  0.0.0.0/0  tcp dpt:80
             ### 0     0 ACCEPT  tcp  --  eth1 * 192.168.10.15 0.0.0.0/0  tcp dpt:22
             ### 33 2348 ACCEPT  tcp  --  eth1 * 192.168.10.2  0.0.0.0/0  tcp dpt:22
             ### 0     0 ACCEPT  tcp  --  eth1 * 192.168.10.2  0.0.0.0/0  tcp dpt:80
             ### 0     0 DNAT    tcp  --  *    * 123.123.123.123 0.0.0.0/0 tcp dpt:55000 to:192.168.12.12:80
-            if ($line =~ m|^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+\-\-\s+
-                                (\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)|x) {
+
+            ### ip6tables:
+            ### 0     0 ACCEPT  tcp   *   *   ::/0     fe80::aa:0:1/128    tcp dpt:12345
+            ### 0     0 LOG     all   *   *   ::/0     ::/0                LOG flags 0 level 4
+
+            my $match_re = qr/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+\-\-\s+
+                                (\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)/x;
+
+            if ($self->{'_ipt_bin_name'} eq 'ip6tables') {
+                $match_re = qr/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+
+                                (\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)/x;
+            }
+
+            if ($line =~ $match_re) {
                 $rule{'packets'}  = $1;
                 $rule{'bytes'}    = $2;
                 $rule{'target'}   = $3;
@@ -199,41 +220,17 @@ sub chain_rules() {
                             $rule{'to_ip'}   = $1;
                             $rule{'to_port'} = $2;
                         }
-
-                        for my $state_hr (@global_accept_state) {
-                            next unless $state_hr->{'src'} eq '0.0.0.0/0';
-                            next unless $state_hr->{'dst'} eq '0.0.0.0/0';
-                            next unless $state_hr->{'proto'} eq 'all' or
-                                $state_hr->{'proto'} = $rule{'proto'};
-                            next unless $state_hr->{'intf_in'} eq '*' or
-                                $state_hr->{'intf_in'} eq $rule{'intf_in'};
-                            next unless $state_hr->{'intf_out'} eq '*' or
-                                $state_hr->{'intf_out'} eq $rule{'intf_out'};
-                            ### if we make it here, then the state rule
-                            ### applies to this rule
-                            $rule{'state'} = $state_hr->{'state'};
-                        }
                     }
-                    if ($rule{'target'} eq 'ACCEPT'
-                            and $rule{'extended'} =~ m|^state\s+(\S+)|) {
-                        my $state_str = $1;
-                        if ($state_str =~ /ESTABLISHED/
-                                or $state_str =~ /RELATED/) {
-
-                            push @global_accept_state, {
-                                'state'    => $state_str,
-                                'src'      => $rule{'src'},
-                                'dst'      => $rule{'dst'},
-                                'intf_in'  => $rule{'intf_in'},
-                                'intf_out' => $rule{'intf_out'},
-                                'proto'    => $rule{'protocol'}
-                            };
-                            my %state_hash = ();
-                        }
+                    if ($rule{'extended'} =~ /\bctstate\s+(\S+)/) {
+                        $rule{'ctstate'} = $1;
+                    } elsif ($rule{'extended'} =~ /\bstate\s+(\S+)/) {
+                        $rule{'state'} = $1;
                     }
                 }
             }
         } else {
+
+            ### iptables:
             ### ACCEPT tcp  -- 164.109.8.0/24  0.0.0.0/0  tcp dpt:22 flags:0x16/0x02
             ### ACCEPT tcp  -- 216.109.125.67  0.0.0.0/0  tcp dpts:7000:7500
             ### ACCEPT udp  -- 0.0.0.0/0       0.0.0.0/0  udp dpts:7000:7500
@@ -244,9 +241,19 @@ sub chain_rules() {
 
             ### LOG  all  --  0.0.0.0/0  0.0.0.0/0  LOG flags 0 level 4 prefix `DROP '
             ### LOG  all  --  127.0.0.2  0.0.0.0/0  LOG flags 0 level 4
-            ### ### DNAT tcp  --  123.123.123.123  0.0.0.0/0  tcp dpt:55000 to:192.168.12.12:80
+            ### DNAT tcp  --  123.123.123.123  0.0.0.0/0  tcp dpt:55000 to:192.168.12.12:80
+
+            ### ip6tables:
+            ### ACCEPT     tcp   ::/0     fe80::aa:0:1/128    tcp dpt:12345
+            ### LOG        all   ::/0     ::/0                LOG flags 0 level 4
 
-            if ($line =~ m|^\s*(\S+)\s+(\S+)\s+\-\-\s+(\S+)\s+(\S+)\s*(.*)|) {
+            my $match_re = qr/^\s*(\S+)\s+(\S+)\s+\-\-\s+(\S+)\s+(\S+)\s*(.*)/;
+
+            if ($self->{'_ipt_bin_name'} eq 'ip6tables') {
+                $match_re = qr/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)/;
+            }
+
+            if ($line =~ $match_re) {
                 $rule{'target'}   = $1;
                 my $proto = $2;
                 $proto = 'all' if $proto eq '0';
@@ -277,6 +284,12 @@ sub chain_rules() {
                         $rule{'to_ip'}   = $1;
                         $rule{'to_port'} = $2;
                     }
+
+                    if ($rule{'extended'} =~ /\bctstate\s+(\S+)/) {
+                        $rule{'ctstate'} = $1;
+                    } elsif ($rule{'extended'} =~ /\bstate\s+(\S+)/) {
+                        $rule{'state'} = $1;
+                    }
                 }
             }
         }
@@ -306,15 +319,16 @@ sub default_drop() {
         @ipt_lines = @$out_ar;
     }
 
-    return '[-] Could not get iptables output!', 0
+    return "[-] Could not get $self->{'_ipt_bin_name'} output!", 0
         unless @ipt_lines;
 
     my %protocols = ();
     my $found_chain = 0;
+    my $found_default_drop = 0;
     my $rule_ctr = 1;
     my $prefix;
     my $policy = 'ACCEPT';
-    my $any_ip_re = '(?:0\.){3}0/0';
+    my $any_ip_re = qr/(?:0\.){3}0\x2f0|\x3a{2}\x2f0/;
 
     LINE: for my $line (@ipt_lines) {
         chomp $line;
@@ -331,8 +345,20 @@ sub default_drop() {
         next LINE unless $found_chain;
 
         ### include ULOG target as well
-        if ($line =~ m|^\s*U?LOG\s+(\w+)\s+\-\-\s+.*
-            $any_ip_re\s+$any_ip_re\s+(.*)|x) {
+        my $log_re = qr/^\s*U?LOG\s+(\w+)\s+\-\-\s+.*
+                $any_ip_re\s+$any_ip_re\s+(.*)/x;
+        my $drop_re = qr/^DROP\s+(\w+)\s+\-\-\s+.*
+            $any_ip_re\s+$any_ip_re\s*$/x;
+
+        if ($self->{'_ipt_bin_name'} eq 'ip6tables') {
+            $log_re = qr/^\s*U?LOG\s+(\w+)\s+
+                    $any_ip_re\s+$any_ip_re\s+(.*)/x;
+            $drop_re = qr/^DROP\s+(\w+)\s+
+                $any_ip_re\s+$any_ip_re\s*$/x;
+        }
+
+        ### might as well pick up any default logging rules as well
+        if ($line =~ $log_re) {
             my $proto  = $1;
             my $p_tmp  = $2;
             my $prefix = 'NONE';
@@ -348,21 +374,28 @@ sub default_drop() {
             ### $proto may equal "all" here
             $protocols{$proto}{'LOG'}{'prefix'} = $prefix;
             $protocols{$proto}{'LOG'}{'rulenum'} = $rule_ctr;
-        } elsif ($policy eq 'ACCEPT' and $line =~ m|^DROP\s+(\w+)\s+\-\-\s+.*
-            $any_ip_re\s+$any_ip_re\s*$|x) {
+        } elsif ($policy eq 'ACCEPT' and $line =~ $drop_re) {
             my $proto = $1;
             $proto = 'all' if $proto eq '0';
             ### DROP    all  --  0.0.0.0/0     0.0.0.0/0
             $protocols{$1}{'DROP'} = $rule_ctr;
+            $found_default_drop = 1;
         }
         $rule_ctr++;
     }
+
     ### if the policy in the chain is DROP, then we don't
     ### necessarily need to find a default DROP rule.
     if ($policy eq 'DROP') {
         $protocols{'all'}{'DROP'} = 0;
+        $found_default_drop = 1;
     }
-    return \%protocols;
+
+    return "[-] There are no default drop rules in the " .
+            "$self->{'_ipt_bin_name'} policy!", 0
+        unless %protocols and $found_default_drop;
+
+    return \%protocols, 1;
 }
 
 sub default_log() {
@@ -372,7 +405,7 @@ sub default_log() {
     my $file  = shift || '';
     my $iptables  = $self->{'_iptables'};
 
-    my $any_ip_re  = '(?:0\.){3}0/0';
+    my $any_ip_re  = qr/(?:0\.){3}0\x2f0|\x3a{2}\x2f0/;
     my @ipt_lines  = ();
     my %log_chains = ();
     my %log_rules  = ();
@@ -403,7 +436,7 @@ sub default_log() {
         }
     }
 
-    return '[-] Could not get iptables output!', 0
+    return "[-] Could not get $self->{'_ipt_bin_name'} output!", 0
         unless @ipt_lines;
 
     ### first get all logging rules and associated chains
@@ -424,17 +457,34 @@ sub default_log() {
         my $proto = '';
         my $found = 0;
         if ($ipt_verbose) {
-            if ($line =~ m|^\s*\d+\s+\d+\s*U?LOG\s+(\w+)\s+\-\-\s+
-                    \S+\s+\S+\s+$any_ip_re
-                    \s+$any_ip_re\s+.*U?LOG|x) {
-                $proto = $1;
-                $found = 1;
+            if ($self->{'_ipt_bin_name'} eq 'ip6tables') {
+                if ($line =~ m|^\s*\d+\s+\d+\s*U?LOG\s+(\w+)\s+
+                        \S+\s+\S+\s+$any_ip_re
+                        \s+$any_ip_re\s+.*U?LOG|x) {
+                    $proto = $1;
+                    $found = 1;
+                }
+            } else {
+                if ($line =~ m|^\s*\d+\s+\d+\s*U?LOG\s+(\w+)\s+\-\-\s+
+                        \S+\s+\S+\s+$any_ip_re
+                        \s+$any_ip_re\s+.*U?LOG|x) {
+                    $proto = $1;
+                    $found = 1;
+                }
             }
         } else {
-            if ($line =~ m|^\s*U?LOG\s+(\w+)\s+\-\-\s+$any_ip_re
-                    \s+$any_ip_re\s+.*U?LOG|x) {
-                $proto = $1;
-                $found = 1;
+            if ($self->{'_ipt_bin_name'} eq 'ip6tables') {
+                if ($line =~ m|^\s*U?LOG\s+(\w+)\s+$any_ip_re
+                        \s+$any_ip_re\s+.*U?LOG|x) {
+                    $proto = $1;
+                    $found = 1;
+                }
+            } else {
+                if ($line =~ m|^\s*U?LOG\s+(\w+)\s+\-\-\s+$any_ip_re
+                        \s+$any_ip_re\s+.*U?LOG|x) {
+                    $proto = $1;
+                    $found = 1;
+                }
             }
         }
 
@@ -446,8 +496,8 @@ sub default_log() {
         }
     }
 
-    return '[-] There are no logging rules in the iptables policy!', 0
-        unless %log_chains;
+    return "[-] There are no default logging rules " .
+        "in the $self->{'_ipt_bin_name'} policy!", 0 unless %log_chains;
 
     my %sub_chains = ();
 
@@ -465,7 +515,7 @@ sub default_log() {
         }
     }
 
-    return \%log_rules;
+    return \%log_rules, 1;
 }
 
 sub sub_chains() {
@@ -484,7 +534,7 @@ sub sub_chains() {
         if ($found and $line =~ /^\s*Chain\s/) {
             last;
         }
-        if ($line =~ m|^\s*(\S+)\s+\S+\s+\-\-|) {
+        if ($line =~ m|^\s*(\S+)\s+\S+\s+|) {
             my $new_chain = $1;
             if ($new_chain ne 'LOG'
                     and $new_chain ne 'DROP'
@@ -494,7 +544,10 @@ sub sub_chains() {
                     and $new_chain ne 'QUEUE'
                     and $new_chain ne 'SNAT'
                     and $new_chain ne 'DNAT'
-                    and $new_chain ne 'MASQUERADE') {
+                    and $new_chain ne 'MASQUERADE'
+                    and $new_chain ne 'pkts'
+                    and $new_chain ne 'Chain'
+                    and $new_chain ne 'target') {
                 $chains_href->{$new_chain} = '';
                 &sub_chains($new_chain, $chains_href, $ipt_lines_aref);
             }
@@ -505,7 +558,8 @@ sub sub_chains() {
 
 sub exec_iptables() {
     my $self  = shift;
-    my $cmd = shift || croak '[*] Must specify an iptables command to run.';
+    my $cmd = shift || croak "[*] Must specify an " .
+        "$self->{'_ipt_bin_name'} command to run.";
     my $iptables  = $self->{'_iptables'};
     my $iptout    = $self->{'_iptout'};
     my $ipterr    = $self->{'_ipterr'};
@@ -516,8 +570,9 @@ sub exec_iptables() {
     my $ipt_exec_sleep = $self->{'_ipt_exec_sleep'};
     my $sigchld_handler = $self->{'_sigchld_handler'};
 
-    croak "[*] $cmd does not look like an iptables command."
-        unless $cmd =~ m|^\s*iptables| or $cmd =~ m|^\S+/iptables|;
+    croak "[*] $cmd does not look like an $self->{'_ipt_bin_name'} command."
+        unless $cmd =~ m|^\s*iptables| or $cmd =~ m|^\S+/iptables|
+            or $cmd =~ m|^\s*ip6tables| or $cmd =~ m|^\S+/ip6tables|;
 
     my $rv = 1;
     my @stdout = ();
@@ -539,7 +594,7 @@ sub exec_iptables() {
         if ($debug or $verbose) {
             print $fh localtime() . " [+] IPTables::Parse: ",
                 "sleeping for $ipt_exec_sleep seconds before ",
-                "executing iptables command.\n";
+                "executing $self->{'_ipt_bin_name'} command.\n";
         }
         sleep $ipt_exec_sleep;
     }
@@ -567,7 +622,8 @@ sub exec_iptables() {
                 ### iptables should never take longer than 30 seconds to execute,
                 ### unless there is some absolutely enormous policy or the kernel
                 ### is exceedingly busy
-                local $SIG{'ALRM'} = sub {die "[*] iptables command timeout.\n"};
+                local $SIG{'ALRM'} = sub {die "[*] $self->{'_ipt_bin_name'} " .
+                    "command timeout.\n"};
                 alarm $ipt_alarm;
                 waitpid($ipt_pid, 0);
                 alarm 0;
@@ -576,7 +632,7 @@ sub exec_iptables() {
                 kill 9, $ipt_pid unless kill 15, $ipt_pid;
             }
         } else {
-            croak "[*] Could not fork iptables: $!"
+            croak "[*] Could not fork $self->{'_ipt_bin_name'}: $!"
                 unless defined $ipt_pid;
 
             ### exec the iptables command and preserve stdout and stderr
@@ -598,7 +654,8 @@ sub exec_iptables() {
     }
 
     if ($debug or $verbose) {
-        print $fh localtime() . "     iptables command stdout:\n";
+        print $fh localtime() . "     $self->{'_ipt_bin_name'} " .
+            "command stdout:\n";
         for my $line (@stdout) {
             if ($line =~ /\n$/) {
                 print $fh $line;
@@ -606,7 +663,8 @@ sub exec_iptables() {
                 print $fh $line, "\n";
             }
         }
-        print $fh localtime() . "     iptables command stderr:\n";
+        print $fh localtime() . "     $self->{'_ipt_bin_name'} " .
+            "command stderr:\n";
         for my $line (@stderr) {
             if ($line =~ /\n$/) {
                 print $fh $line;
@@ -633,14 +691,16 @@ __END__
 
 =head1 NAME
 
-IPTables::Parse - Perl extension for parsing iptables firewall rulesets
+IPTables::Parse - Perl extension for parsing iptables and ip6tables policies
 
 =head1 SYNOPSIS
 
   use IPTables::Parse;
 
+  my $ipt_bin = '/sbin/iptables'; # can set this to /sbin/ip6tables
+
   my %opts = (
-      'iptables' => '/sbin/iptables',
+      'iptables' => $ipt_bin,
       'iptout'   => '/tmp/iptables.out',
       'ipterr'   => '/tmp/iptables.err',
       'debug'    => 0,
@@ -660,14 +720,14 @@ IPTables::Parse - Perl extension for parsing iptables firewall rulesets
       if (defined $ipt_hr->{'all'}) {
           print "The INPUT chain has a default DROP rule for all protocols.\n";
       } else {
-          for my $proto qw/tcp udp icmp/ {
+          for my $proto (qw/tcp udp icmp/) {
               if (defined $ipt_hr->{$proto}) {
                   print "The INPUT chain drops $proto by default.\n";
               }
           }
       }
   } else {
-      print "[-] Could not parse iptables policy\n";
+      print "[-] Could not parse $ipt_obj->{'_ipt_bin_name'} policy\n";
   }
 
   ($ipt_hr, $rv) = $ipt_obj->default_log($table, $chain);
@@ -675,23 +735,24 @@ IPTables::Parse - Perl extension for parsing iptables firewall rulesets
       if (defined $ipt_hr->{'all'}) {
           print "The INPUT chain has a default LOG rule for all protocols.\n";
       } else {
-          for my $proto qw/tcp udp icmp/ {
+          for my $proto (qw/tcp udp icmp/) {
               if (defined $ipt_hr->{$proto}) {
                   print "The INPUT chain logs $proto by default.\n";
               }
           }
       }
   } else {
-      print "[-] Could not parse iptables policy\n";
+      print "[-] Could not parse $ipt_obj->{'_ipt_bin_name'} policy\n";
   }
 
 =head1 DESCRIPTION
 
-The C<IPTables::Parse> package provides an interface to parse iptables
-rules on Linux systems through the direct execution of iptables commands, or
-from parsing a file that contains an iptables policy listing.  You can get the
-current policy applied to a table/chain, look for a specific user-defined chain,
-check for a default DROP policy, or determing whether or not logging rules exist.
+The C<IPTables::Parse> package provides an interface to parse iptables or
+ip6tables rules on Linux systems through the direct execution of
+iptables/ip6tables commands, or from parsing a file that contains an
+iptables/ip6tables policy listing.  You can get the current policy applied to a
+table/chain, look for a specific user-defined chain, check for a default DROP
+policy, or determing whether or not logging rules exist.
 
 =head1 FUNCTIONS
 
@@ -715,27 +776,27 @@ the following keys (that contain values depending on the rule): C<src>, C<dst>,
 C<protocol>, C<s_port>, C<d_port>, C<target>, C<packets>, C<bytes>, C<intf_in>,
 C<intf_out>, C<to_ip>, C<to_port>, C<state>, C<raw>, and C<extended>.  The C<extended>
 element contains the rule output past the protocol information, and the C<raw>
-element contains the complete rule itself as reported by iptables.
+element contains the complete rule itself as reported by iptables or ip6tables.
 
 =item default_drop($table, $chain)
 
-This function parses the running iptables policy in order to determine if
-the specified chain contains a default DROP rule.  Two values are returned,
-a hash reference whose keys are the protocols that are dropped by default
-if a global ACCEPT rule has not accepted matching packets first, along with
-a return value that tells the caller if parsing the iptables policy was
-successful.  Note that if all protocols are dropped by default, then the
-hash key 'all' will be defined.
+This function parses the running iptables or ip6tables policy in order to
+determine if the specified chain contains a default DROP rule.  Two values
+are returned, a hash reference whose keys are the protocols that are dropped by
+default if a global ACCEPT rule has not accepted matching packets first, along
+with a return value that tells the caller if parsing the iptables or ip6tables
+policy was successful.  Note that if all protocols are dropped by default, then
+the hash key 'all' will be defined.
 
   ($ipt_hr, $rv) = $ipt_obj->default_drop('filter', 'INPUT');
 
 =item default_log($table, $chain)
 
-This function parses the running iptables policy in order to determine if
+This function parses the running iptables or ip6tables policy in order to determine if
 the specified chain contains a default LOG rule.  Two values are returned,
 a hash reference whose keys are the protocols that are logged by default
 if a global ACCEPT rule has not accepted matching packets first, along with
-a return value that tells the caller if parsing the iptables policy was
+a return value that tells the caller if parsing the iptables or ip6tables policy was
 successful.  Note that if all protocols are logged by default, then the
 hash key 'all' will be defined.  An example invocation is:
 
@@ -750,21 +811,27 @@ Michael Rash, E<lt>mbr@cipherdyne.orgE<gt>
 =head1 SEE ALSO
 
 The IPTables::Parse is used by the IPTables::ChainMgr extension in support of
-the psad, fwsnort, and fwknop projects to parse iptables policies (see the psad(8),
-fwsnort(8), and fwknop(8) man pages).  As always, the iptables(8) provides the
-best information on command line execution and theory behind iptables.
+the psad and fwsnort projects to parse iptables or ip6tables policies (see the psad(8),
+and fwsnort(8) man pages).  As always, the iptables(8) and ip6tables(8) man pages
+provide the best information on command line execution and theory behind iptables
+and ip6tables.
 
 Although there is no mailing that is devoted specifically to the IPTables::Parse
 extension, questions about the extension will be answered on the following
 lists:
 
   The psad mailing list: http://lists.sourceforge.net/lists/listinfo/psad-discuss
-  The fwknop mailing list: http://lists.sourceforge.net/lists/listinfo/fwknop-discuss
   The fwsnort mailing list: http://lists.sourceforge.net/lists/listinfo/fwsnort-discuss
 
-The latest version of the IPTables::Parse extension can be found at:
+The latest version of the IPTables::Parse extension can be found on CPAN and
+also here:
+
+  http://www.cipherdyne.org/modules/
 
-http://www.cipherdyne.org/modules/
+Source control is provided by git:
+
+  http://www.cipherdyne.org/git/IPTables-Parse.git
+  http://www.cipherdyne.org/cgi-bin/gitweb.cgi?p=IPTables-Parse.git;a=summary
 
 =head1 CREDITS
 
@@ -776,15 +843,19 @@ Thanks to the following people:
 =head1 AUTHOR
 
 The IPTables::Parse extension was written by Michael Rash F<E<lt>mbr@cipherdyne.orgE<gt>>
-to support the psad, fwknop, and fwsnort projects.  Please send email to
+to support the psad and fwsnort projects.  Please send email to
 this address if there are any questions, comments, or bug reports.
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright (C) 2005-2008 by Michael Rash
+Copyright (C) 2005-2012 Michael Rash.  All rights reserved.
+
+This module is free software.  You can redistribute it and/or
+modify it under the terms of the Artistic License 2.0.  More information
+can be found here: http://www.perl.com/perl/misc/Artistic.html
 
-This library is free software; you can redistribute it and/or modify
-it under the same terms as Perl itself, either Perl version 5.8.5 or,
-at your option, any later version of Perl 5 you may have available.
+This program is distributed "as is" in the hope that it will be useful,
+but without any warranty; without even the implied warranty of
+merchantability or fitness for a particular purpose.
 
 =cut