X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FFirewallSimulator.pm;h=140c46ebf5d9b9ca07dbabcd399ad8aa0ad79737;hp=b01e45bb67e7823adf860abdb68a2cc4e95a102b;hb=HEAD;hpb=1e9c50707a54ca023e5d2c2a0cf13821ed86f237 diff --git a/src/PVE/FirewallSimulator.pm b/src/PVE/FirewallSimulator.pm index b01e45b..fa5ed0e 100644 --- a/src/PVE/FirewallSimulator.pm +++ b/src/PVE/FirewallSimulator.pm @@ -7,6 +7,12 @@ use PVE::Firewall; use File::Basename; use Net::IP; +use base 'Exporter'; +our @EXPORT_OK = qw( +$bridge_name_pattern +$bridge_interface_pattern +); + # dynamically include PVE::QemuServer and PVE::LXC # to avoid dependency problems my $have_qemu_server; @@ -21,20 +27,21 @@ eval { $have_lxc = 1; }; -my $mark; +my $mark = 0; my $trace; my $debug = 0; my $NUMBER_RE = qr/0x[0-9a-fA-F]+|\d+/; +our $bridge_name_pattern = '[a-zA-Z][a-zA-Z0-9]{0,9}'; +our $bridge_interface_pattern = "($bridge_name_pattern)/(\\S+)"; + sub debug { my $new_value = shift; - $debug = $new_value if defined($new_value); - return $debug; } - + sub reset_trace { $trace = ''; } @@ -120,26 +127,33 @@ sub rule_match { return undef if $cstate eq 'INVALID'; # no match return undef if $cstate eq 'RELATED,ESTABLISHED'; # no match - + next if $cstate =~ m/NEW/; - + die "cstate test '$cstate' not implemented\n"; } if ($rule =~ s/^-m addrtype --src-type (\S+)\s*//) { my $atype = $1; - die "missing source address type (srctype)\n" + die "missing source address type (srctype)\n" if !$pkg->{srctype}; return undef if $atype ne $pkg->{srctype}; } if ($rule =~ s/^-m addrtype --dst-type (\S+)\s*//) { my $atype = $1; - die "missing destination address type (dsttype)\n" + die "missing destination address type (dsttype)\n" if !$pkg->{dsttype}; return undef if $atype ne $pkg->{dsttype}; } + if ($rule =~ s/^-m icmp(v6)? --icmp-type (\S+)\s*//) { + my $icmpv6 = !!$1; + my $icmptype = $2; + die "missing destination address type (dsttype)\n" if !defined($pkg->{dport}); + return undef if $icmptype ne $pkg->{dport}; + } + if ($rule =~ s/^-i (\S+)\s*//) { my $devre = $1; die "missing interface (iface_in)\n" if !$pkg->{iface_in}; @@ -178,7 +192,7 @@ sub rule_match { return undef if !$ip->overlaps(Net::IP->new($pkg->{source})); # no match next; } - + if ($rule =~ s/^-d (\S+)\s*//) { die "missing destination" if !$pkg->{dest}; my $ip = Net::IP->new($1); @@ -227,7 +241,7 @@ sub rule_match { if ($rule =~ s@^-m mark --mark ($NUMBER_RE)(?:/($NUMBER_RE))?\s*@@) { my ($value, $mask) = PVE::Firewall::get_mark_values($1, $2); - return undef if !defined($mark) || ($mark & $mask) != $value; + return undef if ($mark & $mask) != $value; next; } @@ -248,7 +262,7 @@ sub rule_match { } if ($rule =~ s/^-j NFLOG --nflog-prefix \"[^\"]+\"$//) { - return undef; + return undef; } last; @@ -261,7 +275,7 @@ sub ruleset_simulate_chain { my ($ruleset, $ipset_ruleset, $chain, $pkg) = @_; add_trace("ENTER chain $chain\n"); - + my $counter = 0; if ($chain eq 'PVEFW-Drop') { @@ -289,7 +303,7 @@ sub ruleset_simulate_chain { next; } add_trace("MATCH: $rule\n"); - + if ($action eq 'ACCEPT' || $action eq 'DROP' || $action eq 'REJECT') { add_trace("TERMINATE chain $chain: $action\n"); return ($action, $counter); @@ -382,7 +396,7 @@ sub route_packet { $pkg->{iface_out} = $from_info->{fwbr} || die 'internal error'; $pkg->{physdev_in} = $from_info->{tapdev} || die 'internal error'; $pkg->{physdev_out} = $from_info->{fwln} || die 'internal error'; - + } elsif ($route_state eq 'fwbr-in') { $chain = 'PVEFW-FORWARD'; @@ -392,8 +406,8 @@ sub route_packet { $pkg->{physdev_in} = $target->{fwln} || die 'internal error'; $pkg->{physdev_out} = $target->{tapdev} || die 'internal error'; - } elsif ($route_state =~ m/^vmbr\d+$/) { - + } elsif ($route_state =~ m/^$bridge_name_pattern$/) { + die "missing physdev_in - internal error?" if !$physdev_in; $pkg->{physdev_in} = $physdev_in; @@ -443,7 +457,7 @@ sub route_packet { my ($res, $ctr) = ruleset_simulate_chain($ruleset, $ipset_ruleset, $chain, $pkg); $rule_check_counter += $ctr; return ($res, $ipt_invocation_counter, $rule_check_counter) if $res ne 'ACCEPT'; - } + } $route_state = $next_route_state; @@ -494,9 +508,9 @@ sub simulate_firewall { my $from = $test->{from} || die "missing 'from' field"; my $to = $test->{to} || die "missing 'to' field"; my $action = $test->{action} || die "missing 'action'"; - + my $testid = $test->{id}; - + die "from/to needs to be different" if $from eq $to; my $pkg = { @@ -526,11 +540,6 @@ sub simulate_firewall { $from_info->{type} = 'host'; $start_state = 'host'; $pkg->{source} = $host_ip if !defined($pkg->{source}); - } elsif ($from =~ m|^(vmbr\d+)/(\S+)$|) { - $from_info->{type} = 'bport'; - $from_info->{bridge} = $1; - $from_info->{iface} = $2; - $start_state = 'from-bport'; } elsif ($from eq 'outside') { $from_info->{type} = 'bport'; $from_info->{bridge} = 'vmbr0'; @@ -545,15 +554,20 @@ sub simulate_firewall { return 'SKIPPED' if !$have_lxc; my $vmid = $1; $from_info = extract_ct_info($vmdata, $vmid, 0); - $start_state = 'fwbr-out'; + $start_state = 'fwbr-out'; $pkg->{mac_source} = $from_info->{macaddr}; } elsif ($from =~ m/^vm(\d+)(i(\d))?$/) { return 'SKIPPED' if !$have_qemu_server; my $vmid = $1; my $netnum = $3 || 0; $from_info = extract_vm_info($vmdata, $vmid, $netnum); - $start_state = 'fwbr-out'; + $start_state = 'fwbr-out'; $pkg->{mac_source} = $from_info->{macaddr}; + } elsif ($from =~ m|^$bridge_interface_pattern$|) { + $from_info->{type} = 'bport'; + $from_info->{bridge} = $1; + $from_info->{iface} = $2; + $start_state = 'from-bport'; } else { die "unable to parse \"from => '$from'\"\n"; } @@ -564,10 +578,6 @@ sub simulate_firewall { $target->{type} = 'host'; $target->{iface} = 'host'; $pkg->{dest} = $host_ip if !defined($pkg->{dest}); - } elsif ($to =~ m|^(vmbr\d+)/(\S+)$|) { - $target->{type} = 'bport'; - $target->{bridge} = $1; - $target->{iface} = $2; } elsif ($to eq 'outside') { $target->{type} = 'bport'; $target->{bridge} = 'vmbr0'; @@ -586,6 +596,10 @@ sub simulate_firewall { my $vmid = $1; $target = extract_vm_info($vmdata, $vmid, 0); $target->{iface} = $target->{tapdev}; + } elsif ($to =~ m|^$bridge_interface_pattern$|) { + $target->{type} = 'bport'; + $target->{bridge} = $1; + $target->{iface} = $2; } else { die "unable to parse \"to => '$to'\"\n"; } @@ -593,16 +607,16 @@ sub simulate_firewall { $pkg->{source} = '100.100.1.2' if !defined($pkg->{source}); $pkg->{dest} = '100.200.3.4' if !defined($pkg->{dest}); - my ($res, $ic, $rc) = route_packet($ruleset, $ipset_ruleset, $pkg, + my ($res, $ic, $rc) = route_packet($ruleset, $ipset_ruleset, $pkg, $from_info, $target, $start_state); add_trace("IPT statistics: invocation = $ic, checks = $rc\n"); - + return $res if $action eq 'QUERY'; die "test failed ($res != $action)\n" if $action ne $res; - return undef; + return undef; } 1;