/[pdpsoft]/nl.nikhef.ndpf.tools/foundry-tracl/tr-acl.pl
ViewVC logotype

Contents of /nl.nikhef.ndpf.tools/foundry-tracl/tr-acl.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 21 - (show annotations) (download) (as text)
Mon Aug 11 06:15:56 2008 UTC (14 years ago) by davidg
File MIME type: text/x-prolog
File size: 9548 byte(s)
Foundry ACL translator initial checkin

1 #! /usr/bin/perl -w
2 #
3 # @(#)$Id$
4 #
5 use strict;
6 use Socket;
7 use Getopt::Long qw(:config no_ignore_case bundling);
8
9 use vars qw/ $help $verb /;
10 my $help = undef;
11 my $verb = 0;
12
13 GetOptions( "h|help" => \$help, "verbose|v+" => \$verb );
14
15 if ( $help ) {
16 print STDERR <<EOF;
17 Usage: $0 [-h] [-v] [ruleset]
18
19 Generate a set of named extended ACLs for Foundry Network devices to be
20 applied to the inbound dirction of an interface. The ruleset is expressed
21 in bi-directional semantics: based on the list of connected networks to
22 each interface a full complement of access control entries is generated
23 such that only 'allowed' traffic in permitted to enter the logical routing
24 core and thus from there go unfiltered to one of the connected networks.
25 This script can only process and generate IPv4 ACLs.
26
27 -h Give this help
28 -v Give verbose diagnostics on STDERR
29
30 Syntax (see also the example file):
31
32 interface <name>
33 connects <ipv4-net>
34 [connects <ipv4-net> ...]
35 [excludes <ipv4-net> ...]
36 [prepend <ruledef> ...]
37 [append <ruledef> ...]
38 end
39
40 stanza <name> [<in-line-replacement>]
41 [<next-line-replacement> ...]
42 end
43
44 ruleset
45 [<rule-in-FN-EACL-syntax> ...]
46 end
47
48 end
49
50 The "stanza" lines act as macros and can be invoked by \$macro([args])
51 later in the rule set. Any "\$variable" instances are replaced by their
52 value given between the parentheses. Stanzas can be recursive.
53
54 EOF
55
56 exit(0);
57 }
58
59 sub addr_word($) {
60 my ($mask) = @_;
61 my ($bmask,$i)=(0,0);
62
63 if ( length($mask) <= 2 ) {
64 for ($i=0;$i<$mask;$i++) { $bmask|=1<<(31-$i); }
65 } else {
66 foreach (split /\./,$mask) { $bmask|=(($_)<<(8*(3-($i++)))); }
67 }
68 return $bmask;
69 }
70
71 sub netmatch($$) {
72 my ($a,$b) = @_;
73
74 my ($ipa,$maska) = split(/\//,$a,2);
75 my ($ipb,$maskb) = split(/\//,$b,2);
76
77 my $bmaska = &addr_word($maska);
78 my $bmaskb = &addr_word($maskb);
79 my $shortestmask = $bmaska & $bmaskb;
80
81 my $addra=&addr_word($ipa);
82 my $addrb=&addr_word($ipb);
83
84 $verb>2 and print "\n shortest mask: $shortestmask for $ipa $ipb\n";
85 $verb>2 and print " with addra = $addra and addrb = $addrb\n";
86
87 return 1 unless (($addra & $shortestmask) ^ ($addrb & $shortestmask));
88 return 0;
89 }
90
91 sub netwithin($$) {
92 my ($a,$b) = @_;
93
94 my ($ipa,$maska) = split(/\//,$a,2);
95 my ($ipb,$maskb) = split(/\//,$b,2);
96
97 my $bmaska = &addr_word($maska);
98 my $bmaskb = &addr_word($maskb);
99 my $shortestmask = $bmaska & $bmaskb;
100
101 my $neta=&addr_word($ipa) & $bmaskb & $bmaska;
102 my $netb=&addr_word($ipb) & $bmaskb;
103
104 $verb>3 and printf "neta=%04x, netb=%04x, XOR=%04x, maska=%04x, maskb=%04x\n",$neta,$netb,$netb^$neta,$bmaska,$bmaskb;
105 if ( (($netb^$neta)==0) || ($netb==$neta) ) { # they are equal, so may be subset?
106 # now depends on netmask length
107 my $allones=4294967295;
108 if ( ($bmaskb==$bmaska) || (($bmaskb^$allones) & $bmaska) ) { return 1; }
109 }
110 return 0;
111 }
112
113 sub matchtest($$) {
114 my ($a,$b) = @_;
115 print "Testing if $a matches $b: ";
116 if (&netmatch($a,$b)) { print " YES"; } else { print " NO"; }
117 print "\n";
118 }
119
120 #&matchtest("127.0.0.0/8","127.0.0.0/8");
121 #&matchtest("172.16.0.0/12","172.20.0.0/16");
122 #&matchtest("172.16.0.0/12","172.20.0.0/255.255.0.0");
123 #&matchtest("192.16.199.0/24","172.20.0.0/16");
124 #&matchtest("172.21.0.0/16","172.20.0.0/16");
125
126 my (%interface,@rules,%stanza,@oldlines);
127
128 sub readline() {
129 my ($line);
130
131 # are there old lines to feed?
132 if ( defined $oldlines[0] ) {
133 $line = shift @oldlines;
134 chomp($line); $line =~ s/^\s+//;
135 $_ = $line;
136 return $line;
137 }
138
139 while ( $line = <> ) {
140 chomp($line); $line =~ s/^\s+//;
141 $line =~ /^!/ and next;
142 $line =~/^$/ and next;
143 last;
144 }
145
146 if ( defined $line ) {
147 while ( $line =~ m/\$([-\w\d]+)\(([^\)]*)\)/ ) {
148 my ($name,$args) = ($1,$2);
149 die "Stanza $name undefined\n" unless defined $stanza{$name};
150 my $instance = $stanza{$name};
151 foreach my $argdef ( split /,/,$args ) {
152 my ($key,$value) = split /=/,$argdef;
153 $instance =~ s/\$$key([^\(\w])/$value$1/isg;
154 }
155 $line =~ s/\$([-\w\d]+)\(([^\)]*)\)/$instance/;
156 }
157 push @oldlines,split(/\n/,$line);
158 }
159 return undef if not defined $line;
160
161 $line = shift @oldlines;
162
163 $_ = $line;
164 return $line;
165 }
166
167 sub parse_interface($) {
168 my ($name) = @_;
169 my (@networks,@excludes);
170
171 $interface{$name}{"name"} = $name;
172
173 push @networks,"127.0.0.0/8";
174
175 while(&readline) {
176 /^end/ and do {
177 $interface{$name}{"network"} = \@networks;
178 $interface{$name}{"excludes"} = \@excludes;
179 return 0;
180 };
181 /^prepend/ and do {
182 my ($kw,$rule) = split /\s+/,$_,2;
183 push @{$interface{$name}{"prepend"}}, $rule;
184 };
185 /^append/ and do {
186 my ($kw,$rule) = split /\s+/,$_,2;
187 push @{$interface{$name}{"append"}}, $rule;
188 };
189 /^connects/ and do {
190 my ($kw,$network) = split;
191 push @networks,$network;
192 };
193 /^excludes/ and do {
194 my ($kw,$network) = split;
195 push @excludes,$network;
196 };
197 }
198 die "Syntax error in input aroud line $.: \n".
199 "no end statement found in interface definition $name\n";
200 }
201
202 sub parse_ruleset() {
203 while (&readline) {
204 /^end/ and return 0;
205 /^(permit|deny)/ and do {
206 my ($kw,$rule) = split /\s+/,$_,2;
207 my %ruledef = &parse_rule($_);
208 push @rules,\%ruledef;
209 };
210 }
211 }
212
213 sub parse_stanza($) {
214 my ($def) = @_;
215 my ($name,$value) = split /\s/,$def,2;
216
217 $stanza{$name} = $value;
218 while(&readline) {
219 /^end/ and return 0;
220 $stanza{$name}.=$_."\n";
221 }
222 die "Stanza $name unterminated around line $.\n";
223 }
224
225 sub parse_rule($) {
226 my ($str) = @_;
227 my (%rule);
228 my ($i) = 0;
229
230 my @tok = split /\s+/,$str;
231 $rule{"text"} = $str;
232 $rule{"sense"} = $tok[$i++];
233 $rule{"proto"} = $tok[$i++];
234
235 # source IP address
236 if ( $tok[$i] eq "any" ) {
237 $rule{"src"} = "0.0.0.0/0";
238 } elsif ( $tok[$i] eq "host" ) {
239 $i++;
240 $rule{"src"} = $tok[$i]."/32";
241 } elsif ( $tok[$i] =~ /^\d+\.\d+\.\d+\.\d+$/ ) {
242 die "Syntax error at line $.: $_\n"
243 unless $tok[$i+1] =~ /^\d+\.\d+\.\d+\.\d+$/;
244 $rule{"src"} = $tok[$i]."/".$tok[$i+1];
245 $i++;
246 } elsif ( $tok[$i] =~ /^\d+\.\d+\.\d+\.\d+\/.*/ ) {
247 $rule{"src"} = $tok[$i];
248 } else {
249 die "Syntax error in line $.: $_\n";
250 }
251 $i++;
252
253 # optional port
254 if ($tok[$i] =~ /^(eq|gt|lt|neq)$/ ) {
255 $i+=2;
256 }
257 if ($tok[$i] =~ /^established$/ ) {
258 $i++;
259 }
260 if ($tok[$i] =~ /^range$/ ) {
261 $i+=3;
262 }
263
264 # destination IP address
265 if ( $tok[$i] eq "any" ) {
266 $rule{"dst"} = "0.0.0.0/0";
267 } elsif ( $tok[$i] eq "host" ) {
268 $i++;
269 $rule{"dst"} = $tok[$i]."/32";
270 } elsif ( $tok[$i] =~ /^\d+\.\d+\.\d+\.\d+$/ ) {
271 die "Syntax error at line $.: $_\n"
272 unless $tok[$i+1] =~ /^\d+\.\d+\.\d+\.\d+$/;
273 $rule{"dst"} = $tok[$i]."/".$tok[$i+1];
274 $i++;
275 } elsif ( $tok[$i] =~ /^\d+\.\d+\.\d+\.\d+\/.*/ ) {
276 $rule{"dst"} = $tok[$i];
277 } else {
278 die "Syntax error in line $.: $_\n";
279 }
280 $i++;
281
282 return %rule;
283 }
284
285 sub generate_acls() {
286 print "!\n! generated ACLs for inbound direction\n!\n";
287 foreach my $iface ( sort keys %interface ) {
288 print "no ip access-list extended $iface\n";
289 print "ip access-list extended $iface\n";
290 foreach my $rule (@{$interface{$iface}{"prepend"}} ) {
291 print " $rule\n";
292 }
293
294 $verb and do {
295 print "! this interface connects:";
296 foreach my $net ( @{$interface{$iface}{"network"}} ) {
297 print " $net";
298 }
299 print "\n";
300 print "! except for:";
301 foreach my $net ( @{$interface{$iface}{"excludes"}} ) {
302 print " $net";
303 }
304 print "\n";
305 };
306
307 foreach my $rule ( @rules ) {
308 # is it applicable?
309 my $matched = 0;
310
311 foreach my $net (@{$interface{$iface}{"network"}} ) {
312 $verb and
313 print " ! matching source ".$rule->{"src"}."\n";
314 &netmatch($net,$rule->{"src"}) and $matched++;
315 }
316
317 # permit to self-only is useless, but there's not good algo to keep it out
318 my ($nnetworks,$nselfmatches)=(0,0);
319 foreach my $net (@{$interface{$iface}{"network"}} ) {
320 $net eq "127.0.0.0/8" and next;
321 $nnetworks++;
322 if ( &netwithin($rule->{"dst"},$net) ) {
323 $nselfmatches++;
324 $verb and
325 print " ! from source ".$rule->{"src"}.
326 " to dest ".$rule->{"dst"}.
327 " entirely within $net\n";
328 }
329 }
330 if ( $nnetworks == $nselfmatches ) {
331 $matched = 0;
332 $verb and
333 print " ! killed $nselfmatches: ".
334 $rule->{"text"}."\n";
335 }
336
337 foreach my $net (@{$interface{$iface}{"excludes"}} ) {
338 $verb and print " ! $net matching source ".
339 $rule->{"src"}."\n";
340 if ( &netwithin($rule->{"src"},$net) ) {
341 $matched=0;
342 $verb and print " ! killed ".$rule->{"text"}."\n";
343 $verb and print " ! source ".$rule->{"src"}." entirely within $net\n";
344 }
345 }
346 print " ".$rule->{"text"}."\n" if $matched;
347 $verb and print " ! suppressed: ".$rule->{"text"}."\n" unless $matched;
348 }
349 foreach my $rule (@{$interface{$iface}{"append"}} ) {
350 print " $rule\n";
351 }
352 print "!\n";
353 }
354 }
355
356 while (&readline) {
357 my ($kw,$name) = split /\s+/,$_,2;
358
359 $kw =~ /^stanza/ and &parse_stanza($name);
360 $kw =~ /^interface/ and &parse_interface($name);
361 $kw =~ /^ruleset/ and &parse_ruleset;
362 $kw =~ /^end/ and &generate_acls;
363 };
364
365 0;
366
367

Properties

Name Value
svn:executable *

grid.support@nikhef.nl
ViewVC Help
Powered by ViewVC 1.1.28