File Coverage

blib/lib/IPTables/ChainMgr.pm
Criterion Covered Total %
statement 23 319 7.2
branch 0 162 0.0
condition 0 177 0.0
subroutine 8 25 32.0
pod 12 17 70.5
total 43 700 6.1


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.6
14             #
15             ##############################################################################
16             #
17              
18             package IPTables::ChainMgr;
19              
20 1     1   5359 use 5.006;
  1         4  
21 1     1   554 use POSIX ':sys_wait_h';
  1         7057  
  1         5  
22 1     1   1274 use Carp;
  1         4  
  1         47  
23 1     1   699 use IPTables::Parse;
  1         27702  
  1         45  
24 1     1   713 use NetAddr::IP;
  1         25490  
  1         5  
25 1     1   133 use strict;
  1         2  
  1         22  
26 1     1   3 use warnings;
  1         2  
  1         31  
27 1     1   3 use vars qw($VERSION);
  1         1  
  1         3638  
28              
29             $VERSION = '1.6';
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              
173             ### optionally add port numbers and protocols, etc.
174 0   0       my $extended_hr = shift || {};
175              
176             ### normalize src/dst if necessary; this is because iptables
177             ### always reports the network address for subnets
178 0           my $normalized_src = $self->normalize_net($src);
179 0           my $normalized_dst = $self->normalize_net($dst);
180              
181             ### first check to see if this rule already exists
182 0           my ($rule_position, $num_chain_rules)
183             = $self->find_ip_rule($normalized_src, $normalized_dst, $table,
184             $chain, $target, $extended_hr);
185              
186 0 0         if ($rule_position) {
187 0           my $msg = '';
188 0 0         if (keys %$extended_hr) {
189 0           $msg = "Table: $table, chain: $chain, $normalized_src -> " .
190             "$normalized_dst ";
191 0           for my $key (keys %$extended_hr) {
192             $msg .= "$key $extended_hr->{$key} "
193 0 0         if defined $extended_hr->{$key};
194             }
195 0           $msg .= 'rule already exists.';
196             } else {
197 0           $msg = "Table: $table, chain: $chain, $normalized_src -> " .
198             "$normalized_dst rule already exists.";
199             }
200 0           return 1, [$msg], [];
201             }
202              
203             ### we need to add the rule
204 0           my $ipt_cmd = '';
205 0           my $msg = '';
206 0           my $idx_err = '';
207              
208 0 0         if ($rulenum == 0) {
    0          
209 0           $ipt_cmd = "$self->{'_cmd'} -t $table -I $chain 1 ";
210             } elsif ($rulenum < 0) {
211             ### switch to append mode
212 0           $ipt_cmd = "$self->{'_cmd'} -t $table -A $chain ";
213             } else {
214             ### check to see if the insertion index ($rulenum) is too big
215 0 0         if ($rulenum > $num_chain_rules+1) {
216 0 0         $idx_err = "Rule position $rulenum is past end of $chain " .
217             "chain ($num_chain_rules rules), compensating."
218             if $num_chain_rules > 0;
219 0           $rulenum = $num_chain_rules + 1;
220             }
221 0           $ipt_cmd = "$self->{'_cmd'} -t $table -I $chain $rulenum ";
222             }
223              
224 0 0         if (keys %$extended_hr) {
225              
226 0           my ($ipt_tmp_str, $msg_tmp_str) = $self->build_ipt_matches(
227             $extended_hr, $normalized_src, $normalized_dst);
228              
229 0           $msg = "Table: $table, chain: $chain, added $normalized_src " .
230             "-> $normalized_dst ";
231              
232             ### always add the target at the end
233 0           $ipt_cmd .= "$ipt_tmp_str -j $target";
234              
235 0           $msg .= $msg_tmp_str;
236 0           $msg =~ s/\s*$//;
237              
238             } else {
239 0           $ipt_cmd .= "-s $normalized_src -d $normalized_dst -j $target";
240 0           $msg = "Table: $table, chain: $chain, added $normalized_src " .
241             "-> $normalized_dst";
242             }
243 0           my ($rv, $out_ar, $err_ar) = $self->run_ipt_cmd($ipt_cmd);
244 0 0         if ($rv) {
245 0 0         push @$out_ar, $msg if $msg;
246             }
247 0 0         push @$err_ar, $idx_err if $idx_err;
248 0           return $rv, $out_ar, $err_ar;
249             }
250              
251             sub build_ipt_matches() {
252 0     0 0   my $self = shift;
253 0           my $extended_hr = shift;
254 0   0       my $normalized_src = shift || '';
255 0   0       my $normalized_dst = shift || '';
256              
257 0           my $ipt_matches = '';
258 0           my $msg = '';
259              
260 0 0         if ($IPTables::Parse::VERSION gt 1.1) {
261              
262             ### src and dst
263 0 0         if ($normalized_src ne '') {
264             $ipt_matches .= $self->{'parse_obj'}->
265 0           {'parse_keys'}->{'regular'}->{'src'}->{'ipt_match'} .
266             " $normalized_src ";
267             }
268              
269 0 0         if ($normalized_src ne '') {
270             $ipt_matches .= $self->{'parse_obj'}->
271 0           {'parse_keys'}->{'regular'}->{'dst'}->{'ipt_match'} .
272             " $normalized_dst ";
273             }
274              
275             ### handle 'regular' keys first
276 0           for my $key (keys %$extended_hr) {
277 0 0         if (defined $self->{'parse_obj'}->{'parse_keys'}->{'regular'}->{$key}) {
278             $ipt_matches .= $self->{'parse_obj'}->
279 0           {'parse_keys'}->{'regular'}->{$key}->{'ipt_match'} .
280             " $extended_hr->{$key} ";
281             }
282             }
283              
284             ### special case for port values (handle them now)
285 0           for my $key (qw/sport s_dport dport d_port/) {
286 0 0         next unless defined $extended_hr->{$key};
287 0 0         if ($extended_hr->{$key}) {
288             $ipt_matches .= $self->{'parse_obj'}->
289 0           {'parse_keys'}->{'extended'}->{$key}->{'ipt_match'} .
290             qq| $extended_hr->{$key} |;
291             }
292             }
293              
294             ### now handle 'match' keys
295 0           for my $key (keys %$extended_hr) {
296 0           my $parse_hr = $self->{'parse_obj'}->{'parse_keys'}->{'extended'};
297 0 0         if (defined $parse_hr->{$key}) {
298 0 0 0       next if $key =~ /s_?port$/ or $key =~ /d_?port$/;
299 0 0 0       if (defined $parse_hr->{$key}->{'use_quotes'}
300             and $parse_hr->{$key}->{'use_quotes'}) {
301 0           $ipt_matches .= "$parse_hr->{$key}->{'ipt_match'} " .
302             qq|"$extended_hr->{$key}" |;
303             } else {
304 0           $ipt_matches .= "$parse_hr->{$key}->{'ipt_match'} " .
305             "$extended_hr->{$key} ";
306             }
307             }
308             }
309              
310             } else {
311             $ipt_matches .= "-p $extended_hr->{'protocol'} "
312 0 0         if defined $extended_hr->{'protocol'};
313 0           $ipt_matches .= "-s $normalized_src ";
314             $ipt_matches .= "--sport $extended_hr->{'s_port'} "
315 0 0         if defined $extended_hr->{'s_port'};
316 0           $ipt_matches .= "-d $normalized_dst ";
317             $ipt_matches .= "--dport $extended_hr->{'d_port'} "
318 0 0         if defined $extended_hr->{'d_port'};
319             $ipt_matches .= "-m mac --mac-source $extended_hr->{'mac_source'} "
320 0 0         if defined $extended_hr->{'mac_source'};
321             $ipt_matches .= "-m state --state $extended_hr->{'state'} "
322 0 0         if defined $extended_hr->{'state'};
323             $ipt_matches .= "-m conntrack --ctstate $extended_hr->{'ctstate'} "
324 0 0         if defined $extended_hr->{'ctstate'};
325              
326 0           for my $key (keys %$extended_hr) {
327             $msg .= "$key $extended_hr->{$key} "
328 0 0         if defined $extended_hr->{$key};
329             }
330              
331             ### for NAT
332 0 0 0       if (defined $extended_hr->{'to_ip'} and
333             defined $extended_hr->{'to_port'}) {
334 0           $ipt_matches .= " --to $extended_hr->{'to_ip'}:" .
335             "$extended_hr->{'to_port'}";
336 0           $msg .= "$extended_hr->{'to_ip'}:$extended_hr->{'to_port'}";
337             }
338             }
339              
340 0           $ipt_matches =~ s/\s*$//;
341              
342 0           return ($ipt_matches, $msg);
343             }
344              
345             sub delete_ip_rule() {
346 0     0 1   my $self = shift;
347 0   0       my $src = shift || croak '[-] Must specify a src address/network.';
348 0   0       my $dst = shift || croak '[-] Must specify a dst address/network.';
349 0   0       my $table = shift || croak '[-] Must specify a table, e.g. "filter".';
350 0   0       my $chain = shift || croak '[-] Must specify a chain.';
351 0   0       my $target = shift || croak qq|[-] Must specify | .
352             qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|;
353             ### optionally add port numbers and protocols, etc.
354 0   0       my $extended_hr = shift || {};
355              
356             ### normalize src/dst if necessary; this is because iptables
357             ### always reports network address for subnets
358 0           my $normalized_src = $self->normalize_net($src);
359 0           my $normalized_dst = $self->normalize_net($dst);
360              
361             ### first check to see if this rule already exists
362 0           my ($rulenum, $num_chain_rules)
363             = $self->find_ip_rule($normalized_src,
364             $normalized_dst, $table, $chain, $target, $extended_hr);
365              
366 0 0         if ($rulenum) {
367             ### we need to delete the rule
368 0           return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -D $chain $rulenum");
369             }
370              
371 0           my $extended_msg = '';
372 0 0         if (keys %$extended_hr) {
373 0           for my $key (keys %$extended_hr) {
374             $extended_msg .= "$key: $extended_hr->{$key} "
375 0 0         if defined $extended_hr->{$key};
376             }
377             ### for NAT
378 0 0 0       if (defined $extended_hr->{'to_ip'} and
379             defined $extended_hr->{'to_port'}) {
380 0           $extended_msg .= "$extended_hr->{'to_ip'}:" .
381             "$extended_hr->{'to_port'}";
382             }
383             }
384 0           $extended_msg =~ s/\s*$//;
385 0           return 0, [], ["Table: $table, chain: $chain, rule $normalized_src " .
386             "-> $normalized_dst $extended_msg does not exist."];
387             }
388              
389             sub find_ip_rule() {
390 0     0 1   my $self = shift;
391 0           my $debug = $self->{'_debug'};
392 0           my $verbose = $self->{'_verbose'};
393 0   0       my $src = shift || croak '[*] Must specify source address.';
394 0   0       my $dst = shift || croak '[*] Must specify destination address.';
395 0   0       my $table = shift || croak qq|[*] Must specify $self->{'_ipt_bin_name'} table.|;
396 0   0       my $chain = shift || croak qq|[*] Must specify $self->{'_ipt_bin_name'} chain.|;
397 0   0       my $target = shift || croak qq|[*] Must specify | .
398             qq|$self->{'_ipt_bin_name'} target (this may be a chain).|;
399              
400             ### optionally add port numbers and protocols, etc.
401 0   0       my $extended_hr = shift || {};
402              
403 0           my $fh = *STDERR;
404 0 0         $fh = *STDOUT if $verbose;
405              
406 0 0 0       if ($debug or $verbose) {
407 0           print $fh localtime() . " [+] IPTables::Parse::VERSION ",
408             "$IPTables::Parse::VERSION\n"
409             }
410              
411             ### default if IPTables::Parse version < 1.2
412 0           my @parse_keys = (qw(protocol s_port d_port to_ip
413             to_port mac_source state ctstate));
414              
415 0 0         if ($IPTables::Parse::VERSION gt '1.1') {
416              
417 0           @parse_keys = ();
418              
419             ### get the keys list from the IPTables::Parse module
420 0           for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'regular'}}) {
  0            
421 0           push @parse_keys, $key;
422             }
423 0           for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'extended'}}) {
  0            
424 0           push @parse_keys, $key;
425             }
426              
427             ### make sure that an unsupported search criteria is not required
428 0 0         if (keys %$extended_hr) {
429 0           for my $key (keys %$extended_hr) {
430 0 0         next if $key eq 'normalize';
431 0           my $found = 0;
432 0           for my $supported_key (@parse_keys) {
433 0 0         if ($key eq $supported_key) {
434 0           $found = 1;
435 0           last;
436             }
437             }
438 0 0         unless ($found) {
439 0           croak "[*] Extended hash search key '$key' not " .
440             "supported by IPTables::Parse";
441             }
442             }
443             }
444             }
445              
446 0           my $chain_ar = $self->{'parse_obj'}->chain_rules($table, $chain);
447              
448             $src = $self->normalize_net($src) if defined $extended_hr->{'normalize'}
449 0 0 0       and $extended_hr->{'normalize'};
450             $dst = $self->normalize_net($dst) if defined $extended_hr->{'normalize'}
451 0 0 0       and $extended_hr->{'normalize'};
452              
453 0           my $rulenum = 1;
454 0           for my $rule_hr (@$chain_ar) {
455 0 0 0       if ($rule_hr->{'target'} eq $target
      0        
456             and $rule_hr->{'src'} eq $src
457             and $rule_hr->{'dst'} eq $dst) {
458 0 0         if (keys %$extended_hr) {
459 0           my $found = 1;
460 0           for my $key (@parse_keys) {
461 0 0         if (defined $extended_hr->{$key}) {
462 0 0         if (defined $rule_hr->{$key}) {
463 0 0 0       if ($key eq 'state' or $key eq 'ctstate') {
    0          
464             ### make sure that state ordering as reported
465             ### by iptables is accounted for vs. what was
466             ### supplied to the module
467 0 0         unless (&state_compare($extended_hr->{$key},
468             $rule_hr->{$key})) {
469 0           $found = 0;
470 0           last;
471             }
472             } elsif ($key eq 'mac_source') {
473             ### make sure case does not matter
474 0 0         unless (lc($extended_hr->{$key})
475             eq lc($rule_hr->{$key})) {
476 0           $found = 0;
477 0           last;
478             }
479             } else {
480 0 0         unless ($extended_hr->{$key}
481             eq $rule_hr->{$key}) {
482 0           $found = 0;
483 0           last;
484             }
485             }
486             } else {
487 0           $found = 0;
488 0           last;
489             }
490             }
491             }
492 0 0         return $rulenum, $#$chain_ar+1 if $found;
493             } else {
494 0 0         if ($rule_hr->{'protocol'} eq 'all') {
495 0 0 0       if ($target eq 'LOG' or $target eq 'ULOG') {
    0          
496             ### built-in LOG and ULOG target rules always
497             ### have extended information
498 0           return $rulenum, $#$chain_ar+1;
499             } elsif (not $rule_hr->{'extended'}) {
500             ### don't want any additional criteria (such as
501             ### port numbers) in the rule. Note that we are
502             ### also not checking interfaces
503 0           return $rulenum, $#$chain_ar+1;
504             }
505             }
506             }
507             }
508 0           $rulenum++;
509             }
510 0           return 0, $#$chain_ar+1;
511             }
512              
513             sub print_parse_capabilities() {
514 0     0 0   my $self = shift;
515              
516 0 0         if ($IPTables::Parse::VERSION gt 1.1) {
517              
518 0           print "[+] IPTables::Parse regular options:\n";
519 0           for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'regular'}}) {
  0            
520 0           my $p_hr = $self->{'parse_obj'}->{'parse_keys'}->{'regular'}->{$key};
521 0           print " $key\n";
522 0 0 0       if (defined $p_hr->{'regex'} and $p_hr->{'regex'}) {
523 0           print " regex: $p_hr->{'regex'}", "\n";
524             }
525 0 0 0       if (defined $p_hr->{'ipt_match'} and $p_hr->{'ipt_match'}) {
526 0           print " ipt_match: $p_hr->{'ipt_match'} ", "\n";
527             }
528             }
529              
530 0           print "\n[+] IPTables::Parse extended options:\n";
531 0           for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'extended'}}) {
  0            
532 0           my $p_hr = $self->{'parse_obj'}->{'parse_keys'}->{'extended'}->{$key};
533 0           print " $key\n";
534 0 0 0       if (defined $p_hr->{'regex'} and $p_hr->{'regex'}) {
535 0           print " regex: $p_hr->{'regex'}", "\n";
536             }
537 0 0 0       if (defined $p_hr->{'ipt_match'} and $p_hr->{'ipt_match'}) {
538 0           print " ipt_match: $p_hr->{'ipt_match'} ", "\n";
539             }
540             }
541              
542             } else {
543 0           print "[+] IPTables::Parse capabilities:\n";
544 0           for my $key (qw(protocol s_port d_port to_ip
545             to_port mac_source state ctstate)) {
546 0           print " $key\n";
547             }
548             }
549 0           return;
550             }
551              
552             sub state_compare() {
553 0     0 0   my ($state_str1, $state_str2) = @_;
554              
555 0           my @states1 = split /,/, $state_str1;
556 0           my @states2 = split /,/, $state_str2;
557              
558 0           for my $state1 (@states1) {
559 0           my $found = 0;
560 0           for my $state2 (@states2) {
561 0 0         if ($state1 eq $state2) {
562 0           $found = 1;
563 0           last;
564             }
565             }
566 0 0         return 0 unless $found;
567             }
568              
569 0           for my $state2 (@states2) {
570 0           my $found = 0;
571 0           for my $state1 (@states1) {
572 0 0         if ($state2 eq $state1) {
573 0           $found = 1;
574 0           last;
575             }
576             }
577 0 0         return 0 unless $found;
578             }
579              
580 0           return 1;
581             }
582              
583             sub normalize_net() {
584 0     0 1   my $self = shift;
585 0   0       my $net = shift || croak '[*] Must specify net.';
586              
587 0           my $normalized_net = $net; ### establish default
588              
589             ### regex to match an IPv4 address
590 0           my $ipv4_re = qr/(?:\d{1,3}\.){3}\d{1,3}/;
591              
592 0 0 0       if ($net =~ m|/| and $net =~ $ipv4_re or $net =~ m|:|) {
      0        
593 0 0         if ($net =~ m|:|) { ### an IPv6 address
594 0 0         my $n = NetAddr::IP->new6($net)
595             or croak "[*] Could not acquire NetAddr::IP object for $net";
596 0           $normalized_net = lc($n->network()->short()) . '/' . $n->masklen();
597 0           $normalized_net =~ s|/128$||;
598             } else {
599 0 0         my $n = NetAddr::IP->new($net)
600             or croak "[*] Could not acquire NetAddr::IP object for $net";
601 0           $normalized_net = $n->network()->cidr();
602 0           $normalized_net =~ s|/32$||;
603             }
604             }
605 0           return $normalized_net;
606             }
607              
608             sub add_jump_rule() {
609 0     0 1   my $self = shift;
610 0   0       my $table = shift || croak '[-] Must specify a table, e.g. "filter".';
611 0   0       my $from_chain = shift || croak '[-] Must specify chain to jump from.';
612 0   0       my $rulenum = shift || croak '[-] Must specify jump rule chain position';
613 0   0       my $to_chain = shift || croak '[-] Must specify chain to jump to.';
614 0           my $idx_err = '';
615              
616 0 0         if ($from_chain eq $to_chain) {
617 0           return 0, ["Identical from_chain and to_chain ($from_chain) " .
618             "not allowed."], [];
619             }
620              
621 0           my $ip_any_net = '0.0.0.0/0';
622 0 0         $ip_any_net = '::/0' if $self->{'_ipv6'};
623              
624             ### first check to see if the jump rule already exists
625 0           my ($rule_position, $num_chain_rules)
626             = $self->find_ip_rule($ip_any_net, $ip_any_net, $table,
627             $from_chain, $to_chain, {});
628              
629             ### check to see if the insertion index ($rulenum) is too big
630 0 0         $rulenum = 1 if $rulenum <= 0;
631 0 0         if ($rulenum > $num_chain_rules+1) {
632 0 0         $idx_err = "Rule position $rulenum is past end of $from_chain " .
633             "chain ($num_chain_rules rules), compensating."
634             if $num_chain_rules > 0;
635 0           $rulenum = $num_chain_rules + 1;
636             }
637 0 0         $rulenum = 1 if $rulenum == 0;
638              
639 0 0         if ($rule_position) {
640             ### the rule already exists
641 0           return 1,
642             ["Table: $table, chain: $to_chain, jump rule already exists."], [];
643             }
644              
645             ### we need to add the rule
646 0           my ($rv, $out_ar, $err_ar) = $self->run_ipt_cmd(
647             "$self->{'_cmd'} -t $table -I $from_chain $rulenum -j $to_chain");
648 0 0         push @$err_ar, $idx_err if $idx_err;
649 0           return $rv, $out_ar, $err_ar;
650             }
651              
652             sub REAPER {
653 0     0 0   my $stiff;
654 0           while(($stiff = waitpid(-1,WNOHANG))>0){
655             # do something with $stiff if you want
656             }
657 0           local $SIG{'CHLD'} = \&REAPER;
658 0           return;
659             }
660              
661             sub run_ipt_cmd() {
662 0     0 1   my $self = shift;
663 0   0       my $cmd = shift || croak qq|[*] Must specify | .
664             qq|$self->{'_ipt_bin_name'} command to run.|;
665              
666             ### iptables execution is provided by IPTables::Parse which is
667             ### a dependency of IPTables::ChainMgr
668 0           return $self->{'parse_obj'}->exec_iptables($cmd);
669             }
670              
671             1;
672              
673             __END__