File Coverage

blib/lib/IPTables/ChainMgr.pm
Criterion Covered Total %
statement 24 320 7.5
branch 0 162 0.0
condition 0 177 0.0
subroutine 8 25 32.0
pod 12 17 70.5
total 44 701 6.2


line stmt bran cond sub pod time code
1             #
2             ##############################################################################
3             #
4             # File: IPTables::ChainMgr.pm
5             #
6             # Purpose: Perl interface to add and delete rules to an iptables chain. The
7             # most common application of this module is to create a custom chain
8             # and then add blocking rules to it. Rule additions are (mostly)
9             # guaranteed to be unique.
10             #
11             # Author: Michael Rash (mbr@cipherdyne.org)
12             #
13             # Version: 1.3
14             #
15             ##############################################################################
16             #
17              
18             package IPTables::ChainMgr;
19              
20 1     1   7995 use 5.006;
  1         4  
  1         45  
21 1     1   660 use POSIX ':sys_wait_h';
  1         6370  
  1         5  
22 1     1   1288 use Carp;
  1         8  
  1         61  
23 1     1   770 use IPTables::Parse;
  1         6127  
  1         43  
24 1     1   735 use NetAddr::IP;
  1         64818  
  1         6  
25 1     1   145 use strict;
  1         2  
  1         41  
26 1     1   6 use warnings;
  1         3  
  1         39  
27 1     1   5 use vars qw($VERSION);
  1         2  
  1         4480  
28              
29             $VERSION = '1.3';
30              
31             sub new() {
32 0     0 0   my $class = shift;
33 0           my %args = @_;
34              
35 0           my $self = {};
36              
37 0           $self->{'parse_obj'} = IPTables::Parse->new(%args);
38              
39 0           for my $key ('_cmd',
40             '_ipt_bin_name',
41             '_iptables',
42             '_firewall_cmd',
43             '_fwd_args',
44             '_ipv6',
45             '_iptout',
46             '_ipterr',
47             '_ipt_alarm',
48             '_debug',
49             '_verbose',
50             '_ipt_exec_style',
51             '_ipt_exec_sleep',
52             '_sigchld_handler',
53             ) {
54 0           $self->{$key} = $self->{'parse_obj'}->{$key};
55             }
56              
57 0           bless $self, $class;
58             }
59              
60             sub chain_exists() {
61 0     0 1   my $self = shift;
62 0   0       my $table = shift || croak '[*] Must specify a table, e.g. "filter".';
63 0   0       my $chain = shift || croak '[*] Must specify a chain to check.';
64              
65             ### see if the chain exists
66 0           return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -v -n -L $chain");
67             }
68              
69             sub create_chain() {
70 0     0 1   my $self = shift;
71 0   0       my $table = shift || croak '[*] Must specify a table, e.g. "filter".';
72 0   0       my $chain = shift || croak '[*] Must specify a chain to create.';
73              
74             ### see if the chain exists first
75 0           my ($rv, $out_ar, $err_ar) = $self->chain_exists($table, $chain);
76              
77             ### the chain already exists
78 0 0         return 1, $out_ar, $err_ar if $rv;
79              
80             ### create the chain
81 0           return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -N $chain");
82             }
83              
84             sub flush_chain() {
85 0     0 1   my $self = shift;
86 0   0       my $table = shift || croak '[*] Must specify a table, e.g. "filter".';
87 0   0       my $chain = shift || croak '[*] Must specify a chain.';
88              
89             ### flush the chain
90 0           return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -F $chain");
91             }
92              
93             sub delete_chain() {
94 0     0 1   my $self = shift;
95 0   0       my $table = shift || croak '[*] Must specify a table, e.g. "filter".';
96 0   0       my $jump_from_chain = shift ||
97             croak '[*] Must specify a chain from which ',
98             'packets were jumped to this chain';
99 0   0       my $del_chain = shift || croak '[*] Must specify a chain to delete.';
100              
101             ### see if the chain exists first
102 0           my ($rv, $out_ar, $err_ar) = $self->chain_exists($table, $del_chain);
103              
104             ### return true if the chain doesn't exist (it is not an error condition)
105 0 0         return 1, $out_ar, $err_ar unless $rv;
106              
107             ### flush the chain
108 0           ($rv, $out_ar, $err_ar)
109             = $self->flush_chain($table, $del_chain);
110              
111             ### could not flush the chain
112 0 0         return 0, $out_ar, $err_ar unless $rv;
113              
114 0           my $ip_any_net = '0.0.0.0/0';
115 0 0         $ip_any_net = '::/0' if $self->{'_ipv6'};
116              
117             ### find and delete jump rules to this chain (we can't delete
118             ### the chain until there are no references to it)
119 0           my ($rulenum, $num_chain_rules)
120             = $self->find_ip_rule($ip_any_net, $ip_any_net,
121             $table, $jump_from_chain, $del_chain, {});
122              
123 0 0         if ($rulenum) {
124 0           $self->run_ipt_cmd(
125             "$self->{'_cmd'} -t $table -D $jump_from_chain $rulenum");
126             }
127              
128             ### note that we try to delete the chain now regardless
129             ### of whether their were jump rules above (should probably
130             ### parse for the "0 references" under the -nL output).
131 0           return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -X $del_chain");
132             }
133              
134             sub set_chain_policy() {
135 0     0 1   my $self = shift;
136 0   0       my $table = shift || croak '[*] Must specify a table, e.g. "filter".';
137 0   0       my $chain = shift || croak '[*] Must specify a chain.';
138 0   0       my $target = shift || croak qq|[-] Must specify | .
139             qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|;
140              
141             ### set the chain policy: note that $chain must be a built-in chain
142 0           return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -P $chain $target");
143             }
144              
145             sub append_ip_rule() {
146 0     0 1   my $self = shift;
147 0   0       my $src = shift || croak '[-] Must specify a src address/network.';
148 0   0       my $dst = shift || croak '[-] Must specify a dst address/network.';
149 0   0       my $table = shift || croak '[-] Must specify a table, e.g. "filter".';
150 0   0       my $chain = shift || croak '[-] Must specify a chain.';
151 0   0       my $target = shift || croak qq|[-] Must specify | .
152             qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|;
153              
154             ### optionally add port numbers and protocols, etc.
155 0   0       my $extended_hr = shift || {};
156              
157             ### -1 for append
158 0           return $self->add_ip_rule($src, $dst, -1, $table,
159             $chain, $target, $extended_hr);
160             }
161              
162             sub add_ip_rule() {
163 0     0 1   my $self = shift;
164 0   0       my $src = shift || croak '[-] Must specify a src address/network.';
165 0   0       my $dst = shift || croak '[-] Must specify a dst address/network.';
166 0 0         (my $rulenum = shift) >= -1 || croak '[-] Must specify insert rule number, or -1 for append.';
167 0   0       my $table = shift || croak '[-] Must specify a table, e.g. "filter".';
168 0   0       my $chain = shift || croak '[-] Must specify a chain.';
169 0   0       my $target = shift ||
170             croak qq|[-] Must specify $self->{'_ipt_bin_name'} | .
171             qq|target, e.g. "DROP"|;
172             ### optionally add port numbers and protocols, etc.
173 0   0       my $extended_hr = shift || {};
174              
175             ### normalize src/dst if necessary; this is because iptables
176             ### always reports the network address for subnets
177 0           my $normalized_src = $self->normalize_net($src);
178 0           my $normalized_dst = $self->normalize_net($dst);
179              
180             ### first check to see if this rule already exists
181 0           my ($rule_position, $num_chain_rules)
182             = $self->find_ip_rule($normalized_src, $normalized_dst, $table,
183             $chain, $target, $extended_hr);
184              
185 0 0         if ($rule_position) {
186 0           my $msg = '';
187 0 0         if (keys %$extended_hr) {
188 0           $msg = "Table: $table, chain: $chain, $normalized_src -> " .
189             "$normalized_dst ";
190 0           for my $key (keys %$extended_hr) {
191 0 0         $msg .= "$key $extended_hr->{$key} "
192             if defined $extended_hr->{$key};
193             }
194 0           $msg .= 'rule already exists.';
195             } else {
196 0           $msg = "Table: $table, chain: $chain, $normalized_src -> " .
197             "$normalized_dst rule already exists.";
198             }
199 0           return 1, [$msg], [];
200             }
201              
202             ### we need to add the rule
203 0           my $ipt_cmd = '';
204 0           my $msg = '';
205 0           my $idx_err = '';
206              
207 0 0         if ($rulenum == 0) {
    0          
208 0           $ipt_cmd = "$self->{'_cmd'} -t $table -I $chain 1 ";
209             } elsif ($rulenum < 0) {
210             ### switch to append mode
211 0           $ipt_cmd = "$self->{'_cmd'} -t $table -A $chain ";
212             } else {
213             ### check to see if the insertion index ($rulenum) is too big
214 0 0         if ($rulenum > $num_chain_rules+1) {
215 0 0         $idx_err = "Rule position $rulenum is past end of $chain " .
216             "chain ($num_chain_rules rules), compensating."
217             if $num_chain_rules > 0;
218 0           $rulenum = $num_chain_rules + 1;
219             }
220 0           $ipt_cmd = "$self->{'_cmd'} -t $table -I $chain $rulenum ";
221             }
222              
223 0 0         if (keys %$extended_hr) {
224              
225 0           my ($ipt_tmp_str, $msg_tmp_str) = $self->build_ipt_matches(
226             $extended_hr, $normalized_src, $normalized_dst);
227              
228 0           $msg = "Table: $table, chain: $chain, added $normalized_src " .
229             "-> $normalized_dst ";
230              
231             ### always add the target at the end
232 0           $ipt_cmd .= "$ipt_tmp_str -j $target";
233              
234 0           $msg .= $msg_tmp_str;
235 0           $msg =~ s/\s*$//;
236              
237             } else {
238 0           $ipt_cmd .= "-s $normalized_src -d $normalized_dst -j $target";
239 0           $msg = "Table: $table, chain: $chain, added $normalized_src " .
240             "-> $normalized_dst";
241             }
242 0           my ($rv, $out_ar, $err_ar) = $self->run_ipt_cmd($ipt_cmd);
243 0 0         if ($rv) {
244 0 0         push @$out_ar, $msg if $msg;
245             }
246 0 0         push @$err_ar, $idx_err if $idx_err;
247 0           return $rv, $out_ar, $err_ar;
248             }
249              
250             sub build_ipt_matches() {
251 0     0 0   my $self = shift;
252 0           my $extended_hr = shift;
253 0   0       my $normalized_src = shift || '';
254 0   0       my $normalized_dst = shift || '';
255              
256 0           my $ipt_matches = '';
257 0           my $msg = '';
258              
259 0 0         if ($IPTables::Parse::VERSION > 1.1) {
260              
261             ### src and dst
262 0 0         if ($normalized_src ne '') {
263 0           $ipt_matches .= $self->{'parse_obj'}->
264             {'parse_keys'}->{'regular'}->{'src'}->{'ipt_match'} .
265             " $normalized_src ";
266             }
267              
268 0 0         if ($normalized_src ne '') {
269 0           $ipt_matches .= $self->{'parse_obj'}->
270             {'parse_keys'}->{'regular'}->{'dst'}->{'ipt_match'} .
271             " $normalized_dst ";
272             }
273              
274             ### handle 'regular' keys first
275 0           for my $key (keys %$extended_hr) {
276 0 0         if (defined $self->{'parse_obj'}->{'parse_keys'}->{'regular'}->{$key}) {
277 0           $ipt_matches .= $self->{'parse_obj'}->
278             {'parse_keys'}->{'regular'}->{$key}->{'ipt_match'} .
279             " $extended_hr->{$key} ";
280             }
281             }
282              
283             ### special case for port values (handle them now)
284 0           for my $key (qw/sport s_dport dport d_port/) {
285 0 0         next unless defined $extended_hr->{$key};
286 0 0         if ($extended_hr->{$key}) {
287 0           $ipt_matches .= $self->{'parse_obj'}->
288             {'parse_keys'}->{'extended'}->{$key}->{'ipt_match'} .
289             qq| $extended_hr->{$key} |;
290             }
291             }
292              
293             ### now handle 'match' keys
294 0           for my $key (keys %$extended_hr) {
295 0           my $parse_hr = $self->{'parse_obj'}->{'parse_keys'}->{'extended'};
296 0 0         if (defined $parse_hr->{$key}) {
297 0 0 0       next if $key =~ /s_?port$/ or $key =~ /d_?port$/;
298 0 0 0       if (defined $parse_hr->{$key}->{'use_quotes'}
299             and $parse_hr->{$key}->{'use_quotes'}) {
300 0           $ipt_matches .= "$parse_hr->{$key}->{'ipt_match'} " .
301             qq|"$extended_hr->{$key}" |;
302             } else {
303 0           $ipt_matches .= "$parse_hr->{$key}->{'ipt_match'} " .
304             "$extended_hr->{$key} ";
305             }
306             }
307             }
308              
309             } else {
310 0 0         $ipt_matches .= "-p $extended_hr->{'protocol'} "
311             if defined $extended_hr->{'protocol'};
312 0           $ipt_matches .= "-s $normalized_src ";
313 0 0         $ipt_matches .= "--sport $extended_hr->{'s_port'} "
314             if defined $extended_hr->{'s_port'};
315 0           $ipt_matches .= "-d $normalized_dst ";
316 0 0         $ipt_matches .= "--dport $extended_hr->{'d_port'} "
317             if defined $extended_hr->{'d_port'};
318 0 0         $ipt_matches .= "-m mac --mac-source $extended_hr->{'mac_source'} "
319             if defined $extended_hr->{'mac_source'};
320 0 0         $ipt_matches .= "-m state --state $extended_hr->{'state'} "
321             if defined $extended_hr->{'state'};
322 0 0         $ipt_matches .= "-m conntrack --ctstate $extended_hr->{'ctstate'} "
323             if defined $extended_hr->{'ctstate'};
324              
325 0           for my $key (keys %$extended_hr) {
326 0 0         $msg .= "$key $extended_hr->{$key} "
327             if defined $extended_hr->{$key};
328             }
329              
330             ### for NAT
331 0 0 0       if (defined $extended_hr->{'to_ip'} and
332             defined $extended_hr->{'to_port'}) {
333 0           $ipt_matches .= " --to $extended_hr->{'to_ip'}:" .
334             "$extended_hr->{'to_port'}";
335 0           $msg .= "$extended_hr->{'to_ip'}:$extended_hr->{'to_port'}";
336             }
337             }
338              
339 0           $ipt_matches =~ s/\s*$//;
340              
341 0           return ($ipt_matches, $msg);
342             }
343              
344             sub delete_ip_rule() {
345 0     0 1   my $self = shift;
346 0   0       my $src = shift || croak '[-] Must specify a src address/network.';
347 0   0       my $dst = shift || croak '[-] Must specify a dst address/network.';
348 0   0       my $table = shift || croak '[-] Must specify a table, e.g. "filter".';
349 0   0       my $chain = shift || croak '[-] Must specify a chain.';
350 0   0       my $target = shift || croak qq|[-] Must specify | .
351             qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|;
352             ### optionally add port numbers and protocols, etc.
353 0   0       my $extended_hr = shift || {};
354              
355             ### normalize src/dst if necessary; this is because iptables
356             ### always reports network address for subnets
357 0           my $normalized_src = $self->normalize_net($src);
358 0           my $normalized_dst = $self->normalize_net($dst);
359              
360             ### first check to see if this rule already exists
361 0           my ($rulenum, $num_chain_rules)
362             = $self->find_ip_rule($normalized_src,
363             $normalized_dst, $table, $chain, $target, $extended_hr);
364              
365 0 0         if ($rulenum) {
366             ### we need to delete the rule
367 0           return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -D $chain $rulenum");
368             }
369              
370 0           my $extended_msg = '';
371 0 0         if (keys %$extended_hr) {
372 0           for my $key (keys %$extended_hr) {
373 0 0         $extended_msg .= "$key: $extended_hr->{$key} "
374             if defined $extended_hr->{$key};
375             }
376             ### for NAT
377 0 0 0       if (defined $extended_hr->{'to_ip'} and
378             defined $extended_hr->{'to_port'}) {
379 0           $extended_msg .= "$extended_hr->{'to_ip'}:" .
380             "$extended_hr->{'to_port'}";
381             }
382             }
383 0           $extended_msg =~ s/\s*$//;
384 0           return 0, [], ["Table: $table, chain: $chain, rule $normalized_src " .
385             "-> $normalized_dst $extended_msg does not exist."];
386             }
387              
388             sub find_ip_rule() {
389 0     0 1   my $self = shift;
390 0           my $debug = $self->{'_debug'};
391 0           my $verbose = $self->{'_verbose'};
392 0   0       my $src = shift || croak '[*] Must specify source address.';
393 0   0       my $dst = shift || croak '[*] Must specify destination address.';
394 0   0       my $table = shift || croak qq|[*] Must specify $self->{'_ipt_bin_name'} table.|;
395 0   0       my $chain = shift || croak qq|[*] Must specify $self->{'_ipt_bin_name'} chain.|;
396 0   0       my $target = shift || croak qq|[*] Must specify | .
397             qq|$self->{'_ipt_bin_name'} target (this may be a chain).|;
398              
399             ### optionally add port numbers and protocols, etc.
400 0   0       my $extended_hr = shift || {};
401              
402 0           my $fh = *STDERR;
403 0 0         $fh = *STDOUT if $verbose;
404              
405 0 0 0       if ($debug or $verbose) {
406 0           print $fh localtime() . " [+] IPTables::Parse::VERSION ",
407             "$IPTables::Parse::VERSION\n"
408             }
409              
410             ### default if IPTables::Parse version < 1.2
411 0           my @parse_keys = (qw(protocol s_port d_port to_ip
412             to_port mac_source state ctstate));
413              
414 0 0         if ($IPTables::Parse::VERSION > 1.1) {
415              
416 0           @parse_keys = ();
417              
418             ### get the keys list from the IPTables::Parse module
419 0           for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'regular'}}) {
  0            
420 0           push @parse_keys, $key;
421             }
422 0           for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'extended'}}) {
  0            
423 0           push @parse_keys, $key;
424             }
425              
426             ### make sure that an unsupported search criteria is not required
427 0 0         if (keys %$extended_hr) {
428 0           for my $key (keys %$extended_hr) {
429 0 0         next if $key eq 'normalize';
430 0           my $found = 0;
431 0           for my $supported_key (@parse_keys) {
432 0 0         if ($key eq $supported_key) {
433 0           $found = 1;
434 0           last;
435             }
436             }
437 0 0         unless ($found) {
438 0           croak "[*] Extended hash search key '$key' not " .
439             "supported by IPTables::Parse";
440             }
441             }
442             }
443             }
444              
445 0           my $chain_ar = $self->{'parse_obj'}->chain_rules($table, $chain);
446              
447 0 0 0       $src = $self->normalize_net($src) if defined $extended_hr->{'normalize'}
448             and $extended_hr->{'normalize'};
449 0 0 0       $dst = $self->normalize_net($dst) if defined $extended_hr->{'normalize'}
450             and $extended_hr->{'normalize'};
451              
452 0           my $rulenum = 1;
453 0           for my $rule_hr (@$chain_ar) {
454 0 0 0       if ($rule_hr->{'target'} eq $target
      0        
455             and $rule_hr->{'src'} eq $src
456             and $rule_hr->{'dst'} eq $dst) {
457 0 0         if (keys %$extended_hr) {
458 0           my $found = 1;
459 0           for my $key (@parse_keys) {
460 0 0         if (defined $extended_hr->{$key}) {
461 0 0         if (defined $rule_hr->{$key}) {
462 0 0 0       if ($key eq 'state' or $key eq 'ctstate') {
    0          
463             ### make sure that state ordering as reported
464             ### by iptables is accounted for vs. what was
465             ### supplied to the module
466 0 0         unless (&state_compare($extended_hr->{$key},
467             $rule_hr->{$key})) {
468 0           $found = 0;
469 0           last;
470             }
471             } elsif ($key eq 'mac_source') {
472             ### make sure case does not matter
473 0 0         unless (lc($extended_hr->{$key})
474             eq lc($rule_hr->{$key})) {
475 0           $found = 0;
476 0           last;
477             }
478             } else {
479 0 0         unless ($extended_hr->{$key}
480             eq $rule_hr->{$key}) {
481 0           $found = 0;
482 0           last;
483             }
484             }
485             } else {
486 0           $found = 0;
487 0           last;
488             }
489             }
490             }
491 0 0         return $rulenum, $#$chain_ar+1 if $found;
492             } else {
493 0 0         if ($rule_hr->{'protocol'} eq 'all') {
494 0 0 0       if ($target eq 'LOG' or $target eq 'ULOG') {
    0          
495             ### built-in LOG and ULOG target rules always
496             ### have extended information
497 0           return $rulenum, $#$chain_ar+1;
498             } elsif (not $rule_hr->{'extended'}) {
499             ### don't want any additional criteria (such as
500             ### port numbers) in the rule. Note that we are
501             ### also not checking interfaces
502 0           return $rulenum, $#$chain_ar+1;
503             }
504             }
505             }
506             }
507 0           $rulenum++;
508             }
509 0           return 0, $#$chain_ar+1;
510             }
511              
512             sub print_parse_capabilities() {
513 0     0 0   my $self = shift;
514              
515 0 0         if ($IPTables::Parse::VERSION > 1.1) {
516              
517 0           print "[+] IPTables::Parse regular options:\n";
518 0           for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'regular'}}) {
  0            
519 0           my $p_hr = $self->{'parse_obj'}->{'parse_keys'}->{'regular'}->{$key};
520 0           print " $key\n";
521 0 0 0       if (defined $p_hr->{'regex'} and $p_hr->{'regex'}) {
522 0           print " regex: $p_hr->{'regex'}", "\n";
523             }
524 0 0 0       if (defined $p_hr->{'ipt_match'} and $p_hr->{'ipt_match'}) {
525 0           print " ipt_match: $p_hr->{'ipt_match'} ", "\n";
526             }
527             }
528              
529 0           print "\n[+] IPTables::Parse extended options:\n";
530 0           for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'extended'}}) {
  0            
531 0           my $p_hr = $self->{'parse_obj'}->{'parse_keys'}->{'extended'}->{$key};
532 0           print " $key\n";
533 0 0 0       if (defined $p_hr->{'regex'} and $p_hr->{'regex'}) {
534 0           print " regex: $p_hr->{'regex'}", "\n";
535             }
536 0 0 0       if (defined $p_hr->{'ipt_match'} and $p_hr->{'ipt_match'}) {
537 0           print " ipt_match: $p_hr->{'ipt_match'} ", "\n";
538             }
539             }
540              
541             } else {
542 0           print "[+] IPTables::Parse capabilities:\n";
543 0           for my $key (qw(protocol s_port d_port to_ip
544             to_port mac_source state ctstate)) {
545 0           print " $key\n";
546             }
547             }
548 0           return;
549             }
550              
551             sub state_compare() {
552 0     0 0   my ($state_str1, $state_str2) = @_;
553              
554 0           my @states1 = split /,/, $state_str1;
555 0           my @states2 = split /,/, $state_str2;
556              
557 0           for my $state1 (@states1) {
558 0           my $found = 0;
559 0           for my $state2 (@states2) {
560 0 0         if ($state1 eq $state2) {
561 0           $found = 1;
562 0           last;
563             }
564             }
565 0 0         return 0 unless $found;
566             }
567              
568 0           for my $state2 (@states2) {
569 0           my $found = 0;
570 0           for my $state1 (@states1) {
571 0 0         if ($state2 eq $state1) {
572 0           $found = 1;
573 0           last;
574             }
575             }
576 0 0         return 0 unless $found;
577             }
578              
579 0           return 1;
580             }
581              
582             sub normalize_net() {
583 0     0 1   my $self = shift;
584 0   0       my $net = shift || croak '[*] Must specify net.';
585              
586 0           my $normalized_net = $net; ### establish default
587              
588             ### regex to match an IPv4 address
589 0           my $ipv4_re = qr/(?:\d{1,3}\.){3}\d{1,3}/;
590              
591 0 0 0       if ($net =~ m|/| and $net =~ $ipv4_re or $net =~ m|:|) {
      0        
592 0 0         if ($net =~ m|:|) { ### an IPv6 address
593 0 0         my $n = NetAddr::IP->new6($net)
594             or croak "[*] Could not acquire NetAddr::IP object for $net";
595 0           $normalized_net = lc($n->network()->short()) . '/' . $n->masklen();
596 0           $normalized_net =~ s|/128$||;
597             } else {
598 0 0         my $n = NetAddr::IP->new($net)
599             or croak "[*] Could not acquire NetAddr::IP object for $net";
600 0           $normalized_net = $n->network()->cidr();
601 0           $normalized_net =~ s|/32$||;
602             }
603             }
604 0           return $normalized_net;
605             }
606              
607             sub add_jump_rule() {
608 0     0 1   my $self = shift;
609 0   0       my $table = shift || croak '[-] Must specify a table, e.g. "filter".';
610 0   0       my $from_chain = shift || croak '[-] Must specify chain to jump from.';
611 0   0       my $rulenum = shift || croak '[-] Must specify jump rule chain position';
612 0   0       my $to_chain = shift || croak '[-] Must specify chain to jump to.';
613 0           my $idx_err = '';
614              
615 0 0         if ($from_chain eq $to_chain) {
616 0           return 0, ["Identical from_chain and to_chain ($from_chain) " .
617             "not allowed."], [];
618             }
619              
620 0           my $ip_any_net = '0.0.0.0/0';
621 0 0         $ip_any_net = '::/0' if $self->{'_ipv6'};
622              
623             ### first check to see if the jump rule already exists
624 0           my ($rule_position, $num_chain_rules)
625             = $self->find_ip_rule($ip_any_net, $ip_any_net, $table,
626             $from_chain, $to_chain, {});
627              
628             ### check to see if the insertion index ($rulenum) is too big
629 0 0         $rulenum = 1 if $rulenum <= 0;
630 0 0         if ($rulenum > $num_chain_rules+1) {
631 0 0         $idx_err = "Rule position $rulenum is past end of $from_chain " .
632             "chain ($num_chain_rules rules), compensating."
633             if $num_chain_rules > 0;
634 0           $rulenum = $num_chain_rules + 1;
635             }
636 0 0         $rulenum = 1 if $rulenum == 0;
637              
638 0 0         if ($rule_position) {
639             ### the rule already exists
640 0           return 1,
641             ["Table: $table, chain: $to_chain, jump rule already exists."], [];
642             }
643              
644             ### we need to add the rule
645 0           my ($rv, $out_ar, $err_ar) = $self->run_ipt_cmd(
646             "$self->{'_cmd'} -t $table -I $from_chain $rulenum -j $to_chain");
647 0 0         push @$err_ar, $idx_err if $idx_err;
648 0           return $rv, $out_ar, $err_ar;
649             }
650              
651             sub REAPER {
652 0     0 0   my $stiff;
653 0           while(($stiff = waitpid(-1,WNOHANG))>0){
654             # do something with $stiff if you want
655             }
656 0           local $SIG{'CHLD'} = \&REAPER;
657 0           return;
658             }
659              
660             sub run_ipt_cmd() {
661 0     0 1   my $self = shift;
662 0   0       my $cmd = shift || croak qq|[*] Must specify | .
663             qq|$self->{'_ipt_bin_name'} command to run.|;
664              
665             ### iptables execution is provided by IPTables::Parse which is
666             ### a dependency of IPTables::ChainMgr
667 0           return $self->{'parse_obj'}->exec_iptables($cmd);
668             }
669              
670             1;
671              
672             __END__