# IPTables::interface - Perl style wrapper interface for IPTables::libiptc package IPTables::interface; use strict; use warnings; use Carp; use Time::HiRes; # Used for timing stuff #use Errno qw(:POSIX); #use Data::Dumper; use IPTables::libiptc; # Logging system use Log::Log4perl qw(get_logger :levels); my $logger = get_logger(__PACKAGE__); # Locking system use DCU::lock; our $lock_base_name="iptables_cmd_lock"; our $lock = DCU::lock::new("$lock_base_name"); # Lock object instans BEGIN { use Exporter (); our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); # Package version $VERSION = 0.01; @ISA = qw(Exporter); @EXPORT = qw( ); } # Package counter variables our $command_timesum=0; our $command_count=0; our %stat_timesum; our %stat_count; # TODO: Remember to implement a Singelton class for each table. # # This is needed because libiptc returns a table handle that all # changes MUST be apply to e.g. as changes will be lost if using # several handles to the same table. # our %table_singleton_objects; # Create a new iptables interface Object. sub new { my $tablename = shift; my $self = {}; my $log = "[Function] init() libiptc handle"; # Extract object if it already exist. my $singleton = $table_singleton_objects{"$tablename"}; if (defined $singleton) { $logger->debug("$log (returning singleton object)"); return $singleton; } # Init / create a new object. # --------------------------- # TODO: Obtain lock here, before handle init... # TODO: Obtain a lock per "table" #$lock->lock("$tablename"); $lock->lock(); timer_start(); my $handle = IPTables::libiptc::init("$tablename"); timer_end("init"); my $init_time = $stat_timesum{"init"}; if (not defined $handle) { my $errmsg = "Cannot init libiptc handle: \"$!\""; $logger->logcroak($errmsg); } else { my $logtxt = sprintf("$log (InitTime:[%.3fs])", $init_time); # # Inform through error logging, if init time is high. if ($init_time > 0.5) { $logger->error($logtxt); } else { $logger->info($logtxt); } } bless $self; $self->{'tablename'} = $tablename; $self->{'handle'} = $handle; $table_singleton_objects{"$tablename"} = $self; return $self; } sub commit() { my $func = "commit"; my $self = shift; my $handle = $self->get_handle(); my $log = "[Function] $func()"; $logger->debug($log); timer_start(); my $success = $handle->commit(); timer_end($func); my $tablename = $self->{'tablename'}; # TODO: Unlock here... # $lock->unlock("$tablename"); $lock->unlock(); # Delete singleton hash/object instans here... delete $table_singleton_objects{"$tablename"}; # TODO: What about destroying this object...? # It would be wise to indicate, "do not use anymore after a commit" # TODO: What about destroying the iptables handle? # (think that is done in the XS/C-code). if (!$success) { my $err="Cannot commit, you might be missing a kernel module"; $err .=" but have the userspace support."; $logger->log($ERROR, $err); } $self->record_info($success, $log, $ERROR); return $success; } sub get_handle() { my $self = shift; my $handle = $self->{'handle'}; return $handle; } sub get_tablename() { my $self = shift; my $name = $self->{'tablename'}; return $name; } sub record_info($$$) { my $self = shift; my $success = shift; my $logmsg = shift; my $loglevel = shift || $WARN; $self->{'success'} = $success; if (!$success) { #my $errno = $!; #my $errno = $!{ENOENT}; #$self->{'errno'} = $errno; my $errstr = "$!"; $self->{'logmsg'} = $logmsg; $self->{'errstr'} = "$errstr"; #my $errmsg = "$logmsg: (errno:$errno) $errstr"; my $errmsg = "$logmsg: $errstr"; $self->{'errmsg'} = $errmsg; $logger->log($loglevel, $errmsg); } } ########################################## # Statistics functions ########################################## our $collect_stats = 1; our $timer_start; sub timer_start() { $timer_start = Time::HiRes::time() if $collect_stats; #return $timer_start; } sub timer_end($) { if ($collect_stats) { my $function = shift; my $time_end = Time::HiRes::time(); my $timediff = $time_end - $timer_start; # Record statistics $command_count++; $command_timesum += $timediff; $stat_count{"$function"}++; $stat_timesum{"$function"} += $timediff; #return $timediff; } } sub report_statistics() { my $self = shift; my $time_total = $self->get_commands_time_total(); my $count = $self->get_commands_count(); my $time_average = $self->get_commands_time_average(); print "\n"; print " Statistics for iptables command calls:\n"; print " --------------------------------------\n"; print " Number of calls : $count\n"; printf(" Total time used : %.8fs\n", $time_total); printf(" Average per call: %.8fs\n", $time_average); print " Statistics per command action:\n"; foreach my $action (keys %stat_count ) { my $count = $stat_count{$action}; my $time = $stat_timesum{$action}; my $average = $time / $count; print " calls:$count \t"; printf("time:%.8fs\t", $time); printf(" average:%.8fs\t", $average); print " Action:\"$action\"\n"; } } sub get_commands_count() { my $self = shift; # return $self->{'run_cnt'}; return $command_count; } sub get_commands_time_total() { my $self = shift; return $command_timesum; } sub get_commands_time_average() { my $self = shift; return ($command_timesum / $command_count) if $collect_stats } # Debugging stuff sub report_if_failed() { my $self = shift; if ($self->{success} == 0) { print "ERRMSG:" . $self->{'errmsg'} . "\n"; } } ########################################## # IPTables: Policy operations ########################################## #void get_policy(self, chain) sub get_policy($) { my $func = "get_policy"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; $logger->debug($log); my ($policy, $pkt_cnt, $byte_cnt); timer_start(); my $success = (($policy, $pkt_cnt, $byte_cnt) = $handle->get_policy("$chain")); timer_end($func); $self->record_info($success, $log, $ERROR); return ($policy, $pkt_cnt, $byte_cnt); } #void set_policy(self, chain, policy, pkt_cnt=0, byte_cnt=0) sub set_policy() { my $func = "set_policy"; my $self = shift; my $chain = shift; my $policy = shift; my $pkt_cnt = shift; my $byte_cnt = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; $logger->debug($log); my ($success, $old_policy, $old_pkt_cnt, $old_byte_cnt); timer_start(); if (not defined $byte_cnt) { ($success, $old_policy) = $handle->set_policy("$chain", $policy); } else { ($success, $old_policy, $old_pkt_cnt, $old_byte_cnt) = $handle->set_policy("$chain", $policy, $pkt_cnt, $byte_cnt); } timer_end($func); $self->record_info($success, $log, $ERROR); return ($success, $old_policy, $old_pkt_cnt, $old_byte_cnt); } ########################################## # IPTables: Chain operations ########################################## # $success = is_chain($chain); sub is_chain($) { my $func = "is_chain"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; $logger->debug($log); timer_start(); my $success = $handle->is_chain("$chain"); timer_end($func); # Change loglevel as failure is to be expected. $self->record_info($success, $log, $INFO); return $success; } # This attempts to create the chain $chain. # # $success = create_chain($chain); sub create_chain($) { my $func = "create_chain"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; $logger->debug($log); timer_start(); my $success = $handle->create_chain("$chain"); timer_end($func); $self->record_info($success, $log); return $success; } # This attempts to delete the chain $chain. # #$success = delete_chain($chain); sub delete_chain($) { my $func = "delete_chain"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; $logger->debug($log); timer_start(); my $success = $handle->delete_chain("$chain"); timer_end($func); # TEST/DEBUG code if (!$success) { my $refs = $handle->get_references("$chain"); $logger->error("DEBUG: Failed delete chain: $chain, chain refs: $refs"); } $self->record_info($success, $log, $ERROR); return $success; } # $success = rename_chain($oldchain, $newchain); sub rename_chain($$) { my $func = "rename_chain"; my $self = shift; my $oldchain = shift; my $newchain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($oldchain, $newchain)"; $logger->debug($log); timer_start(); my $success = $handle->rename_chain($oldchain, $newchain); timer_end($func); $self->record_info($success, $log, $ERROR); return $success; } # $success = builtin($chain) sub builtin($) { my $func = "buildin"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; $logger->debug($log); timer_start(); my $success = $handle->builtin($chain); timer_end($func); $self->record_info($success, $log, $DEBUG); return $success; } # $num_of_refs = get_references($chain) # Returns -1 on failure. sub get_references($) { my $func = "get_references"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; my $success=1; #$logger->debug($log); timer_start(); my $num_of_refs = $handle->get_references($chain); timer_end($func); $log .= ": refs=$num_of_refs"; $logger->debug($log); if ($num_of_refs < 0) { $success=0; } $self->record_info($success, $log, $WARN); return $num_of_refs; } ########################################## # Functions affecting all chains ########################################## sub flush_all_chains() { my $func = "flush_all_chains"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func()"; $logger->debug($log); my $verbose = 1; timer_start(); # Function "for_each_chain" not implemented yet in "libiptc.pm" # my $success = # $handle->for_each_chain('flush_entries', $verbose, 1, &handle); my $success = $self->iptables_do_command("-F"); timer_end($func); $self->record_info($success, $log); return $success; } sub delete_all_chains() { my $func = "delete_all_chains"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func()"; $logger->debug($log); my $verbose = 1; timer_start(); # Function "for_each_chain" not implemented yet in "libiptc.pm" # my $success = # $handle->for_each_chain('delete_chain', $verbose, 1, &handle); my $success = $self->iptables_do_command("-X"); timer_end($func); $self->record_info($success, $log); return $success; } ########################################## # Rules/Entries affecting a full chain ########################################## # Delete all rules in a chain sub flush_chain($) { my $self = shift; my $chain = shift; my $success = $self->flush_entries($chain); return $success; } sub flush_entries($) { my $func = "flush_entries"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; $logger->debug($log); timer_start(); my $success = $handle->flush_entries($chain); timer_end($func); $self->record_info($success, $log); return $success; } # Zero counter (on all rules) in a chain sub zero_entries($) { my $func = "zero_entries"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($chain)"; $logger->debug($log); timer_start(); my $success = $handle->zero_entries($chain); timer_end($func); $self->record_info($success, $log); return $success; } ########################################## # Listing related ########################################## sub list_chains() { my $func = "list_chains"; my $self = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func()"; my $success = 1; $logger->debug($log); timer_start(); my @list_of_chainnames = $handle->list_chains(); timer_end($func); $self->record_info($success, $log); return @list_of_chainnames; } # Given a $chain, list the rules src or dst IPs. # $type = {dst,src} sub list_rules_IPs($$) { my $func = "list_rules_IPs"; my $self = shift; my $type = shift; my $chain = shift; my $handle = $self->get_handle(); my $log = "[Function] $func($type, $chain)"; my $success = 1; $logger->debug($log); timer_start(); my @list_of_IPs = $handle->list_rules_IPs($type, $chain); timer_end($func); $self->record_info($success, $log); return @list_of_IPs; } ########################################## # Calling iptables "do_command" function ########################################## # TODO: Catch output from the command (STDOUT and STDERR). This # requires some filehandle Perl tricks... # sub iptables_do_command() { my $func = "iptables_do_command"; my $self = shift; my @input = @_; my $handle = $self->get_handle(); my $log = "[Function] $func(@input)"; $logger->debug($log); #print "INPUT:\n", Dumper(@input); # We need to transform input into an array, where the individual # command arguments are seperated. This is easy in Perl... my @cmd_array; foreach my $string (@input) { if (defined $string) { my @tmp = split(/\s+/, $string); push @cmd_array, @tmp } } #print "CMD_ARRAY:\n", Dumper(\@cmd_array); timer_start(); my $success = $handle->iptables_do_command(\@cmd_array); timer_end($func); $self->record_info($success, $log); return $success; } ########################################## # Rule operations through "do_command" ########################################## # arguments($func, $action, $chain, $rule, $target) sub __command_rule($$$$$) { my $self = shift; my $func = shift; my $action = shift; my $chain = shift; my $rule = shift || ""; my $target = shift; my $loglevel = shift || $ERROR; # Handle if the "target" is not specified my $target_cmd=""; if (defined $target && $target ne "") { $target_cmd="-j $target"; } else { $target = ""; } my $log = "[Function] $func($chain, $rule, $target)"; $logger->debug($log); timer_start(); my $success = $self->iptables_do_command("$action","$chain", $rule, $target_cmd); timer_end($func); $self->record_info($success, $log, $loglevel); return $success; } sub append_rule($$$) { my $self = shift; my $func = "append_rule"; my $action = "--append"; my ($chain, $rule, $target) = @_; my $success = $self->__command_rule($func, $action, "$chain", $rule, $target); return $success; } sub insert_rule($$$) { my $self = shift; my $func = "insert_rule"; my $action = "--insert"; my ($chain, $rule, $target) = @_; my $success = $self->__command_rule($func, $action, "$chain", $rule, $target); return $success; } sub delete_rule($$$) { my $self = shift; my $func = "delete_rule"; my $action = "--delete"; my ($chain, $rule, $target) = @_; my $success = $self->__command_rule($func, $action, "$chain", $rule, $target, $INFO); return $success; } ########################################## # High level Rule operations ########################################## # Move a rule to the end of the chain. # ------------------------------------ # - Purpose: assure that a rule is the last in a chain. # # - This is a legacy function from IPTables::command.pm # where this action was difficult, with libiptc integration # this is quite easy (because we first commit changes later). # sub move_to_end_rule($$$) { my $self = shift; my $func = "move_to_end_rule"; my ($chain, $rule, $target) = @_; my $log = "[Function] $func($chain, $rule, $target)"; $logger->debug($log); my $success = $self->delete_rule("$chain", $rule, $target); if (!$success) { $logger->info("$log: Could not delete rule, trying to creating it."); } $success = $self->append_rule("$chain", $rule, $target); $self->record_info($success, $log, $ERROR); return $success; } 1; __END__ # Below is documentation for the module. =head1 NAME IPTables::interface - Perl style wrapper interface for IPTables::libiptc =head1 SYNOPSIS use IPTables::interface; $table = IPTables::interface::('filter'); $table->create_chain("badehat"); # Its important to commit/push-back the changes to the kernel $table->commit(); =head1 DESCRIPTION This module is basically a wrapper/shadow interface around IPTables::libiptc. The purpose of the module, is to provide: =over =item 1. Safe access to the table handles, by locking and singleton classes. =item 2. Provide logging functionality (with Log::Log4perl). =item 3. Collect call statistics. =back =head1 METHODS Basically we shadows the functions in IPTables::libiptc, see this module for method documentation. =head2 Chain Operations =over =item B my ($policy) = $table->get_policy("chainname"); my ($policy, $pkt_cnt, $byte_cnt) = $table->get_policy("chainname"); This returns an array containing the default policy, and the number of packets and bytes which have reached the default policy, in the chain C. If C does not exist, or if it is not a built-in chain, an empty array will be returned, $! will be set to a string containing the reason, and $table->{'success'} == 0. =item B my ($success) = $table->set_policy("chainname", "POLICY"); my ($success, $old_policy) = $table->set_policy("chainname", "POLICY"); my ($success, $old_policy, $old_pkt_cnt, $old_byte_cnt) = $table->set_policy("chainname", "POLICY", $pkt_cnt, $byte_cnt); This returns an array containing if the command was successful and the previous default policy. It is also possible to set the counter values (on the buildin chain), this will cause the command to return the previous counter values. The C must be a built-in chain name. =back =head2 Listing Operations =over =item B @array = $table->list_chains(); Lists all chains. =item B @list_of_IPs = $table->list_rules_IPs('type', 'chainname'); This function lists the (rules) source or destination IPs from a given chain. The C is either C or C for source and destination IPs. The netmask is also listed together with the IPs, but seperated by a C character. If chainname does not exist C is returned. =back =head2 Iptables commands (from iptables.h) =over =item B $success = $table->iptables_do_command("-A chain", "-s 10.0.0.42"); $success = $table->iptables_do_command("-I", "chain", "-s 10.0.0.42"); The iptables_do_command calls the C function from C. This means that the input is the same as the iptables command line arguments. The perl function automatically transforms the input into the seperate command line arguments need by the C function. =back =head2 Rules Operations Rule operations are done through the C. The following helper function are implemented. $success = append_rule($chain, $rule, $target); $success = insert_rule($chain, $rule, $target); $success = delete_rule($chain, $rule, $target); =head1 DEPENDENCIES L, L, L. L. =head1 SEE ALSO Documentation of the module IPTables::libiptc. =head1 AUTHOR Jesper Dangaard Brouer, Ehawk@comx.dkE or Ehawk@diku.dkE. =head2 Authors SVN version information $LastChangedDate: 2007-11-15 19:02:14 +0100 (Thu, 15 Nov 2007) $ $Revision: 889 $ $LastChangedBy: jdb $ =head1 COPYRIGHT AND LICENSE Copyright (C) 2006 by Jesper Dangaard Brouer 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.4 or, at your option, any later version of Perl 5 you may have available. =cut