]> git.proxmox.com Git - pve-firewall.git/blame - test/fwtester.pl
add tests for corosync multicast addrtype rules
[pve-firewall.git] / test / fwtester.pl
CommitLineData
f1bafd37
DM
1#!/usr/bin/perl
2
3use lib '../src';
4use strict;
5use warnings;
6use Data::Dumper;
7use PVE::Firewall;
ec2e28f6
DM
8use Getopt::Long;
9use File::Basename;
680d56ee 10use Net::IP;
f1bafd37
DM
11
12my $mark;
13my $trace;
14
d1486f38
DM
15my $debug = 0;
16
ec2e28f6
DM
17sub print_usage_and_exit {
18 die "usage: $0 [--debug] [testfile [testid]]\n";
19}
20
21if (!GetOptions ('debug' => \$debug)) {
22 print_usage_and_exit();
23}
24
25my $testfilename = shift;
26my $testid = shift;
27
d1486f38
DM
28sub add_trace {
29 my ($text) = @_;
30
31 if ($debug) {
32 print $text;
33 } else {
34 $trace .= $text;
35 }
36}
37
e4e5fcaf
DM
38sub nf_dev_match {
39 my ($devre, $dev) = @_;
40
41 $devre =~ s/\+$/\.\*/;
42 return ($dev =~ m/^${devre}$/) ? 1 : 0;
43}
44
292e0ad9
DM
45sub ipset_match {
46 my ($ipsetname, $ipset, $ipaddr) = @_;
47
48 my $ip = Net::IP->new($ipaddr);
49
50 foreach my $entry (@$ipset) {
51 next if $entry =~ m/^create/; # simply ignore
52 if ($entry =~ m/add \S+ (\S+)$/) {
53 my $test = Net::IP->new($1);
54 if ($test->overlaps($ip)) {
55 add_trace("IPSET $ipsetname match $ipaddr\n");
56 return 1;
57 }
58 } else {
59 die "implement me";
60 }
61 }
62
63 return 0;
64}
65
f1bafd37 66sub rule_match {
292e0ad9 67 my ($ipset_ruleset, $chain, $rule, $pkg) = @_;
f1bafd37
DM
68
69 $rule =~ s/^-A $chain // || die "got strange rule: $rule";
70
680d56ee
DM
71 while (length($rule)) {
72
832cd14c
DM
73 if ($rule =~ s/^-m conntrack --ctstate (\S+)\s*//) {
74 my $cstate = $1;
75
76 return undef if $cstate eq 'INVALID'; # no match
77 return undef if $cstate eq 'RELATED,ESTABLISHED'; # no match
78
79 next if $cstate =~ m/NEW/;
80
81 die "please implement cstate test '$cstate'";
680d56ee
DM
82 }
83
4a9ce6d3
DM
84 if ($rule =~ s/^-m addrtype --src-type (\S+)\s*//) {
85 my $atype = $1;
86 die "missing srctype" if !$pkg->{srctype};
87 return undef if $atype ne $pkg->{srctype};
88 }
89
90 if ($rule =~ s/^-m addrtype --dst-type (\S+)\s*//) {
91 my $atype = $1;
92 die "missing dsttype" if !$pkg->{dsttype};
93 return undef if $atype ne $pkg->{dsttype};
680d56ee
DM
94 }
95
96 if ($rule =~ s/^-i (\S+)\s*//) {
97 my $devre = $1;
98 die "missing iface_in" if !$pkg->{iface_in};
99 return undef if !nf_dev_match($devre, $pkg->{iface_in});
100 next;
101 }
102
103 if ($rule =~ s/^-o (\S+)\s*//) {
104 my $devre = $1;
105 die "missing iface_out" if !$pkg->{iface_out};
106 return undef if !nf_dev_match($devre, $pkg->{iface_out});
107 next;
108 }
109
110 if ($rule =~ s/^-p (tcp|udp)\s*//) {
111 die "missing proto" if !$pkg->{proto};
112 return undef if $pkg->{proto} ne $1; # no match
113 next;
114 }
115
116 if ($rule =~ s/^--dport (\d+):(\d+)\s*//) {
117 die "missing dport" if !$pkg->{dport};
118 return undef if ($pkg->{dport} < $1) || ($pkg->{dport} > $2); # no match
119 next;
120 }
121
122 if ($rule =~ s/^--dport (\d+)\s*//) {
123 die "missing dport" if !$pkg->{dport};
124 return undef if $pkg->{dport} != $1; # no match
125 next;
126 }
127
128 if ($rule =~ s/^-s (\S+)\s*//) {
129 die "missing source" if !$pkg->{source};
130 my $ip = Net::IP->new($1);
131 return undef if !$ip->overlaps(Net::IP->new($pkg->{source})); # no match
132 next;
133 }
f1bafd37 134
680d56ee
DM
135 if ($rule =~ s/^-d (\S+)\s*//) {
136 die "missing destination" if !$pkg->{dest};
137 my $ip = Net::IP->new($1);
138 return undef if !$ip->overlaps(Net::IP->new($pkg->{dest})); # no match
139 next;
140 }
141
292e0ad9
DM
142 if ($rule =~ s/^-m set --match-set (\S+) src\s*//) {
143 die "missing source" if !$pkg->{source};
144 my $ipset = $ipset_ruleset->{$1};
145 die "no such ip set '$1'" if !$ipset;
146 return undef if !ipset_match($1, $ipset, $pkg->{source});
147 next;
148 }
149
150 if ($rule =~ s/^-m set --match-set (\S+) dst\s*//) {
151 die "missing destination" if !$pkg->{dest};
152 my $ipset = $ipset_ruleset->{$1};
153 die "no such ip set '$1'" if !$ipset;
154 return undef if !ipset_match($1, $ipset, $pkg->{dest});
155 next;
156 }
157
680d56ee
DM
158 if ($rule =~ s/^-m mac ! --mac-source (\S+)\s*//) {
159 die "missing source mac" if !$pkg->{mac_source};
160 return undef if $pkg->{mac_source} eq $1; # no match
161 next;
162 }
163
164 if ($rule =~ s/^-m physdev --physdev-is-bridged --physdev-in (\S+)\s*//) {
165 my $devre = $1;
166 return undef if !$pkg->{physdev_in};
167 return undef if !nf_dev_match($devre, $pkg->{physdev_in});
168 next;
169 }
170
171 if ($rule =~ s/^-m physdev --physdev-is-bridged --physdev-out (\S+)\s*//) {
172 my $devre = $1;
173 return undef if !$pkg->{physdev_out};
174 return undef if !nf_dev_match($devre, $pkg->{physdev_out});
175 next;
176 }
177
178 if ($rule =~ s/^-m mark --mark (\d+)\s*//) {
179 return undef if !defined($mark) || $mark != $1;
180 next;
181 }
182
183 # final actions
292e0ad9 184
680d56ee
DM
185 if ($rule =~ s/^-j MARK --set-mark (\d+)\s*$//) {
186 $mark = $1;
187 return undef;
188 }
189
190 if ($rule =~ s/^-j (\S+)\s*$//) {
191 return (0, $1);
192 }
193
194 if ($rule =~ s/^-g (\S+)\s*$//) {
195 return (1, $1);
196 }
197
292e0ad9
DM
198 if ($rule =~ s/^-j NFLOG --nflog-prefix \"[^\"]+\"$//) {
199 return undef;
200 }
201
680d56ee 202 last;
f1bafd37
DM
203 }
204
205 die "unable to parse rule: $rule";
206}
207
208sub ruleset_simulate_chain {
292e0ad9 209 my ($ruleset, $ipset_ruleset, $chain, $pkg) = @_;
f1bafd37 210
d1486f38 211 add_trace("ENTER chain $chain\n");
f1bafd37 212
6a84e461 213 my $counter = 0;
111cce51 214
f1bafd37 215 if ($chain eq 'PVEFW-Drop') {
d1486f38 216 add_trace("LEAVE chain $chain\n");
111cce51 217 return ('DROP', $counter);
f1bafd37
DM
218 }
219 if ($chain eq 'PVEFW-reject') {
d1486f38 220 add_trace("LEAVE chain $chain\n");
111cce51 221 return ('REJECT', $counter);
f1bafd37
DM
222 }
223
224 if ($chain eq 'PVEFW-tcpflags') {
d1486f38 225 add_trace("LEAVE chain $chain\n");
111cce51 226 return (undef, $counter);
f1bafd37
DM
227 }
228
229 my $rules = $ruleset->{$chain} ||
230 die "no such chain '$chain'";
231
232 foreach my $rule (@$rules) {
111cce51 233 $counter++;
292e0ad9 234 my ($goto, $action) = rule_match($ipset_ruleset, $chain, $rule, $pkg);
f1bafd37 235 if (!defined($action)) {
d1486f38 236 add_trace("SKIP: $rule\n");
f1bafd37
DM
237 next;
238 }
d1486f38 239 add_trace("MATCH: $rule\n");
f1bafd37
DM
240
241 if ($action eq 'ACCEPT' || $action eq 'DROP' || $action eq 'REJECT') {
d1486f38 242 add_trace("TERMINATE chain $chain: $action\n");
111cce51 243 return ($action, $counter);
f1bafd37 244 } elsif ($action eq 'RETURN') {
d1486f38 245 add_trace("RETURN FROM chain $chain\n");
f1bafd37
DM
246 last;
247 } else {
248 if ($goto) {
d1486f38 249 add_trace("LEAVE chain $chain - goto $action\n");
292e0ad9 250 return ruleset_simulate_chain($ruleset, $ipset_ruleset, $action, $pkg)
f1bafd37
DM
251 #$chain = $action;
252 #$rules = $ruleset->{$chain} || die "no such chain '$chain'";
253 } else {
292e0ad9 254 my ($act, $ctr) = ruleset_simulate_chain($ruleset, $ipset_ruleset, $action, $pkg);
111cce51
DM
255 $counter += $ctr;
256 return ($act, $counter) if $act;
d1486f38 257 add_trace("CONTINUE chain $chain\n");
f1bafd37
DM
258 }
259 }
260 }
261
d1486f38 262 add_trace("LEAVE chain $chain\n");
f1bafd37 263 if ($chain =~ m/^PVEFW-(INPUT|OUTPUT|FORWARD)$/) {
111cce51 264 return ('ACCEPT', $counter); # default policy
f1bafd37
DM
265 }
266
111cce51 267 return (undef, $counter);
f1bafd37
DM
268}
269
270sub copy_packet {
271 my ($pkg) = @_;
272
273 my $res = {};
274
275 while (my ($k,$v) = each %$pkg) {
276 $res->{$k} = $v;
277 }
278
279 return $res;
280}
281
d1486f38
DM
282# Try to simulate packet traversal inside kernel. This invokes iptable
283# checks several times.
284sub route_packet {
285 my ($ruleset, $ipset_ruleset, $pkg, $from_info, $target, $start_state) = @_;
286
287 my $route_state = $start_state;
288
289 my $physdev_in;
290
111cce51
DM
291 my $ipt_invocation_counter = 0;
292 my $rule_check_counter = 0;
293
d1486f38
DM
294 while ($route_state ne $target->{iface}) {
295
296 my $chain;
297 my $next_route_state;
298 my $next_physdev_in;
299
300 $pkg->{iface_in} = $pkg->{iface_out} = undef;
301 $pkg->{physdev_in} = $pkg->{physdev_out} = undef;
302
47ece390
DM
303 if ($route_state eq 'from-bport') {
304 $next_route_state = $from_info->{bridge} || die 'internal error';
305 $next_physdev_in = $from_info->{iface} || die 'internal error';
31dc73f1 306 } elsif ($route_state eq 'host') {
d1486f38 307
47ece390 308 if ($target->{type} eq 'bport') {
c0c871d8 309 $pkg->{iface_in} = 'lo';
47ece390 310 $pkg->{iface_out} = $target->{bridge} || die 'internal error';
c0c871d8 311 $chain = 'PVEFW-OUTPUT';
47ece390 312 $next_route_state = $target->{iface} || die 'internal error';
31dc73f1 313 } elsif ($target->{type} eq 'ct') {
d1486f38
DM
314 $pkg->{iface_in} = 'lo';
315 $pkg->{iface_out} = 'venet0';
316 $chain = 'PVEFW-OUTPUT';
317 $next_route_state = 'venet-in';
318 } elsif ($target->{type} eq 'vm') {
319 $pkg->{iface_in} = 'lo';
320 $pkg->{iface_out} = $target->{bridge} || die 'internal error';
321 $chain = 'PVEFW-OUTPUT';
322 $next_route_state = 'fwbr-in';
323 } else {
324 die "implement me";
325 }
326
327 } elsif ($route_state eq 'venet-out') {
328
329 if ($target->{type} eq 'host') {
330
331 $chain = 'PVEFW-INPUT';
332 $pkg->{iface_in} = 'venet0';
333 $pkg->{iface_out} = 'lo';
334 $next_route_state = 'host';
335
47ece390 336 } elsif ($target->{type} eq 'bport') {
c0c871d8
DM
337
338 $chain = 'PVEFW-FORWARD';
339 $pkg->{iface_in} = 'venet0';
47ece390
DM
340 $pkg->{iface_out} = $target->{bridge} || die 'internal error';
341 $next_route_state = $target->{iface} || die 'internal error';
c0c871d8 342
d1486f38
DM
343 } elsif ($target->{type} eq 'vm') {
344
345 $chain = 'PVEFW-FORWARD';
346 $pkg->{iface_in} = 'venet0';
347 $pkg->{iface_out} = $target->{bridge} || die 'internal error';
348 $next_route_state = 'fwbr-in';
349
350 } elsif ($target->{type} eq 'ct') {
351
352 $chain = 'PVEFW-FORWARD';
353 $pkg->{iface_in} = 'venet0';
354 $pkg->{iface_out} = 'venet0';
355 $next_route_state = 'venet-in';
356
357 } else {
358 die "implement me";
359 }
360
361 } elsif ($route_state eq 'fwbr-out') {
362
363 $chain = 'PVEFW-FORWARD';
364 $next_route_state = $from_info->{bridge} || die 'internal error';
365 $next_physdev_in = $from_info->{fwpr} || die 'internal error';
366 $pkg->{iface_in} = $from_info->{fwbr} || die 'internal error';
367 $pkg->{iface_out} = $from_info->{fwbr} || die 'internal error';
368 $pkg->{physdev_in} = $from_info->{tapdev} || die 'internal error';
369 $pkg->{physdev_out} = $from_info->{fwln} || die 'internal error';
370
371 } elsif ($route_state eq 'fwbr-in') {
372
373 $chain = 'PVEFW-FORWARD';
374 $next_route_state = $target->{tapdev};
375 $pkg->{iface_in} = $target->{fwbr} || die 'internal error';
376 $pkg->{iface_out} = $target->{fwbr} || die 'internal error';
377 $pkg->{physdev_in} = $target->{fwln} || die 'internal error';
378 $pkg->{physdev_out} = $target->{tapdev} || die 'internal error';
379
380 } elsif ($route_state =~ m/^vmbr\d+$/) {
381
382 die "missing physdev_in - internal error?" if !$physdev_in;
eb5faed9 383 $pkg->{physdev_in} = $physdev_in;
d1486f38
DM
384
385 if ($target->{type} eq 'host') {
386
387 $chain = 'PVEFW-INPUT';
388 $pkg->{iface_in} = $route_state;
389 $pkg->{iface_out} = 'lo';
390 $next_route_state = 'host';
391
47ece390 392 } elsif ($target->{type} eq 'bport') {
31dc73f1
DM
393
394 $chain = 'PVEFW-FORWARD';
395 $pkg->{iface_in} = $route_state;
47ece390 396 $pkg->{iface_out} = $target->{bridge} || die 'internal error';
c0c871d8 397 # conditionally set physdev_out (same behavior as kernel)
47ece390
DM
398 if ($route_state eq $target->{bridge}) {
399 $pkg->{physdev_out} = $target->{iface} || die 'internal error';
c0c871d8 400 }
47ece390 401 $next_route_state = $target->{iface};
c0c871d8 402
d1486f38
DM
403 } elsif ($target->{type} eq 'ct') {
404
405 $chain = 'PVEFW-FORWARD';
406 $pkg->{iface_in} = $route_state;
407 $pkg->{iface_out} = 'venet0';
408 $next_route_state = 'venet-in';
409
410 } elsif ($target->{type} eq 'vm') {
411
412 $chain = 'PVEFW-FORWARD';
31dc73f1
DM
413 $pkg->{iface_in} = $route_state;
414 $pkg->{iface_out} = $target->{bridge};
31dc73f1 415 # conditionally set physdev_out (same behavior as kernel)
d1486f38 416 if ($route_state eq $target->{bridge}) {
d1486f38 417 $pkg->{physdev_out} = $target->{fwpr} || die 'internal error';
d1486f38
DM
418 }
419 $next_route_state = 'fwbr-in';
420
421 } else {
422 die "implement me";
423 }
424
425 } else {
426 die "implement me $route_state";
427 }
428
429 die "internal error" if !defined($next_route_state);
430
431 if ($chain) {
432 add_trace("IPT check at $route_state (chain $chain)\n");
433 add_trace(Dumper($pkg));
111cce51 434 $ipt_invocation_counter++;
292e0ad9 435 my ($res, $ctr) = ruleset_simulate_chain($ruleset, $ipset_ruleset, $chain, $pkg);
111cce51
DM
436 $rule_check_counter += $ctr;
437 return ($res, $ipt_invocation_counter, $rule_check_counter) if $res ne 'ACCEPT';
d1486f38
DM
438 }
439
440 $route_state = $next_route_state;
441
442 $physdev_in = $next_physdev_in;
443 }
444
111cce51 445 return ('ACCEPT', $ipt_invocation_counter, $rule_check_counter);
d1486f38
DM
446}
447
448sub extract_ct_info {
449 my ($vmdata, $vmid) = @_;
450
451 my $info = { type => 'ct', vmid => $vmid };
452
453 my $conf = $vmdata->{openvz}->{$vmid} || die "no such CT '$vmid'";
454 if ($conf->{ip_address}) {
455 $info->{ip_address} = $conf->{ip_address}->{value};
456 } else {
457 die "implement me";
458 }
459 return $info;
460}
461
462sub extract_vm_info {
463 my ($vmdata, $vmid) = @_;
464
465 my $info = { type => 'vm', vmid => $vmid };
466
467 my $conf = $vmdata->{qemu}->{$vmid} || die "no such VM '$vmid'";
468 my $net = PVE::QemuServer::parse_net($conf->{net0});
469 $info->{macaddr} = $net->{macaddr} || die "unable to get mac address";
470 $info->{bridge} = $net->{bridge} || die "unable to get bridge";
471 $info->{fwbr} = "fwbr${vmid}i0";
472 $info->{tapdev} = "tap${vmid}i0";
473 $info->{fwln} = "fwln${vmid}i0";
474 $info->{fwpr} = "fwpr${vmid}p0";
475
476 return $info;
477}
f1bafd37
DM
478
479sub simulate_firewall {
480 my ($ruleset, $ipset_ruleset, $vmdata, $test) = @_;
481
1352eaa1
DM
482 my $from = $test->{from} || die "missing 'from' field";
483 my $to = $test->{to} || die "missing 'to' field";
484 my $action = $test->{action} || die "missing 'action'";
8215a0da 485
1352eaa1 486 my $testid = $test->{id};
8215a0da 487
f1bafd37
DM
488 die "from/to needs to be different" if $from eq $to;
489
490 my $pkg = {
f1bafd37 491 proto => 'tcp',
8215a0da
DM
492 sport => undef,
493 dport => undef,
494 source => undef,
495 dest => undef,
4a9ce6d3
DM
496 srctype => 'UNICAST',
497 dsttype => 'UNICAST',
f1bafd37
DM
498 };
499
500 while (my ($k,$v) = each %$test) {
1352eaa1
DM
501 next if $k eq 'from';
502 next if $k eq 'to';
503 next if $k eq 'action';
504 next if $k eq 'id';
8215a0da 505 die "unknown attribute '$k'\n" if !exists($pkg->{$k});
f1bafd37
DM
506 $pkg->{$k} = $v;
507 }
508
d1486f38
DM
509 my $from_info = {};
510
511 my $start_state;
f1bafd37 512
ee06b009 513 my $host_ip = '172.16.1.2';
832cd14c 514
f1bafd37 515 if ($from eq 'host') {
d1486f38
DM
516 $from_info->{type} = 'host';
517 $start_state = 'host';
832cd14c 518 $pkg->{source} = $host_ip if !defined($pkg->{source});
47ece390
DM
519 } elsif ($from =~ m|^(vmbr\d+)/(\S+)$|) {
520 $from_info->{type} = 'bport';
521 $from_info->{bridge} = $1;
522 $from_info->{iface} = $2;
523 $start_state = 'from-bport';
31dc73f1 524 } elsif ($from eq 'outside') {
47ece390
DM
525 $from_info->{type} = 'bport';
526 $from_info->{bridge} = 'vmbr0';
527 $from_info->{iface} = 'eth0';
528 $start_state = 'from-bport';
c0c871d8 529 } elsif ($from eq 'nfvm') {
47ece390
DM
530 $from_info->{type} = 'bport';
531 $from_info->{bridge} = 'vmbr0';
532 $from_info->{iface} = 'tapXYZ';
533 $start_state = 'from-bport';
f1bafd37
DM
534 } elsif ($from =~ m/^ct(\d+)$/) {
535 my $vmid = $1;
d1486f38
DM
536 $from_info = extract_ct_info($vmdata, $vmid);
537 if ($from_info->{ip_address}) {
1352eaa1 538 $pkg->{source} = $from_info->{ip_address} if !defined($pkg->{source});
d1486f38 539 $start_state = 'venet-out';
f1bafd37
DM
540 } else {
541 die "implement me";
542 }
f1bafd37
DM
543 } elsif ($from =~ m/^vm(\d+)$/) {
544 my $vmid = $1;
d1486f38
DM
545 $from_info = extract_vm_info($vmdata, $vmid);
546 $start_state = 'fwbr-out';
547 $pkg->{mac_source} = $from_info->{macaddr};
f1bafd37 548 } else {
49e9d9a5 549 die "unable to parse \"from => '$from'\"\n";
f1bafd37
DM
550 }
551
d1486f38
DM
552 my $target;
553
f1bafd37 554 if ($to eq 'host') {
d1486f38
DM
555 $target->{type} = 'host';
556 $target->{iface} = 'host';
832cd14c 557 $pkg->{dest} = $host_ip if !defined($pkg->{dest});
47ece390
DM
558 } elsif ($to =~ m|^(vmbr\d+)/(\S+)$|) {
559 $target->{type} = 'bport';
560 $target->{bridge} = $1;
561 $target->{iface} = $2;
31dc73f1 562 } elsif ($to eq 'outside') {
47ece390
DM
563 $target->{type} = 'bport';
564 $target->{bridge} = 'vmbr0';
565 $target->{iface} = 'eth0';
c0c871d8 566 } elsif ($to eq 'nfvm') {
47ece390
DM
567 $target->{type} = 'bport';
568 $target->{bridge} = 'vmbr0';
569 $target->{iface} = 'tapXYZ';
f1bafd37
DM
570 } elsif ($to =~ m/^ct(\d+)$/) {
571 my $vmid = $1;
d1486f38
DM
572 $target = extract_ct_info($vmdata, $vmid);
573 $target->{iface} = 'venet-in';
574
575 if ($target->{ip_address}) {
576 $pkg->{dest} = $target->{ip_address};
f1bafd37
DM
577 } else {
578 die "implement me";
579 }
f1bafd37
DM
580 } elsif ($to =~ m/^vm(\d+)$/) {
581 my $vmid = $1;
d1486f38
DM
582 $target = extract_vm_info($vmdata, $vmid);
583 $target->{iface} = $target->{tapdev};
f1bafd37 584 } else {
49e9d9a5 585 die "unable to parse \"to => '$to'\"\n";
f1bafd37
DM
586 }
587
832cd14c
DM
588 $pkg->{source} = '100.100.1.2' if !defined($pkg->{source});
589 $pkg->{dest} = '100.200.3.4' if !defined($pkg->{dest});
590
111cce51
DM
591 my ($res, $ic, $rc) = route_packet($ruleset, $ipset_ruleset, $pkg,
592 $from_info, $target, $start_state);
f1bafd37 593
111cce51
DM
594 add_trace("IPT statistics: invocation = $ic, checks = $rc\n");
595
d1486f38 596 die "test failed ($res != $action)\n" if $action ne $res;
f1bafd37 597
d1486f38 598 return undef;
f1bafd37
DM
599}
600
601sub run_tests {
ec2e28f6
DM
602 my ($vmdata, $testdir, $testfile, $testid) = @_;
603
604 $testfile = 'tests' if !$testfile;
f1bafd37
DM
605
606 $vmdata->{testdir} = $testdir;
607
ee06b009
DM
608 PVE::Firewall::cluster_network('172.16.1.0/24');
609
f1bafd37
DM
610 my ($ruleset, $ipset_ruleset) =
611 PVE::Firewall::compile(undef, undef, $vmdata);
612
ec2e28f6
DM
613 my $filename = "$testdir/$testfile";
614 my $fh = IO::File->new($filename) ||
615 die "unable to open '$filename' - $!\n";
f1bafd37 616
ec2e28f6 617 my $testcount = 0;
f1bafd37
DM
618 while (defined(my $line = <$fh>)) {
619 next if $line =~ m/^\s*$/;
620 next if $line =~ m/^#.*$/;
621 if ($line =~ m/^\{.*\}\s*$/) {
622 my $test = eval $line;
623 die $@ if $@;
ec2e28f6 624 next if defined($testid) && (!defined($test->{id}) || ($testid ne $test->{id}));
f1bafd37 625 $trace = '';
d1486f38 626 print Dumper($ruleset) if $debug;
ec2e28f6 627 $testcount++;
1352eaa1
DM
628 eval {
629 my @test_zones = qw(host outside nfvm vm100 ct200);
630 if (!defined($test->{from}) && !defined($test->{to})) {
631 die "missing zone speification (from, to)\n";
632 } elsif (!defined($test->{to})) {
633 foreach my $zone (@test_zones) {
634 next if $zone eq $test->{from};
635 $test->{to} = $zone;
636 add_trace("Set Zone: to => '$zone'\n");
637 simulate_firewall($ruleset, $ipset_ruleset, $vmdata, $test);
638 }
639 } elsif (!defined($test->{from})) {
640 foreach my $zone (@test_zones) {
641 next if $zone eq $test->{to};
642 $test->{from} = $zone;
643 add_trace("Set Zone: from => '$zone'\n");
644 simulate_firewall($ruleset, $ipset_ruleset, $vmdata, $test);
645 }
646 } else {
647 simulate_firewall($ruleset, $ipset_ruleset, $vmdata, $test);
648 }
649 };
f1bafd37
DM
650 if (my $err = $@) {
651
d1486f38 652 print Dumper($ruleset) if !$debug;
f1bafd37 653
d1486f38 654 print "$trace\n" if !$debug;
f1bafd37 655
ec2e28f6 656 print "$filename line $.: $line";
f1bafd37
DM
657
658 print "test failed: $err\n";
659
660 exit(-1);
661 }
662 } else {
663 die "parse error";
664 }
665 }
666
ec2e28f6
DM
667 die "no tests found\n" if $testcount <= 0;
668
669 print "PASS: $filename\n";
f1bafd37
DM
670
671 return undef;
672}
673
674my $vmdata = {
675 qemu => {
676 100 => {
db990d66 677 net0 => "e1000=0E:0B:38:B8:B3:21,bridge=vmbr0,firewall=1",
d1486f38
DM
678 },
679 101 => {
db990d66 680 net0 => "e1000=0E:0B:38:B8:B3:22,bridge=vmbr0,firewall=1",
d1486f38
DM
681 },
682 # on bridge vmbr1
683 110 => {
db990d66 684 net0 => "e1000=0E:0B:38:B8:B4:21,bridge=vmbr1,firewall=1",
f1bafd37
DM
685 },
686 },
687 openvz => {
688 200 => {
689 ip_address => { value => '10.0.200.1' },
690 },
d1486f38
DM
691 201 => {
692 ip_address => { value => '10.0.200.2' },
693 },
f1bafd37
DM
694 },
695};
696
ec2e28f6
DM
697if ($testfilename) {
698 my $testfile;
699 my $dir;
700
701 if (-d $testfilename) {
702 $dir = $testfilename;
703 } elsif (-f $testfilename) {
704 $dir = dirname($testfilename);
705 $testfile = basename($testfilename);
706 } else {
707 die "no such file/dir '$testfilename'\n";
708 }
709
710 run_tests($vmdata, $dir, $testfile, $testid);
711
712} else {
713 foreach my $dir (<test-*>) {
714 next if ! -d $dir;
715 run_tests($vmdata, $dir);
716 }
f1bafd37
DM
717}
718
719print "OK - all tests passed\n";
720
721exit(0);