]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/FirewallSimulator.pm
ipset_match: implement simulation of list type ipsets
[pve-firewall.git] / src / PVE / FirewallSimulator.pm
index 22ddee47c95c1d3463931dd75ea16bd37b41a882..4042ace0377c885740bf19f7edf8cccb0ad78888 100644 (file)
@@ -37,6 +37,14 @@ sub add_trace {
     }
 }
 
+$SIG{'__WARN__'} = sub {
+    my $err = $@;
+    my $t = $_[0];
+    chomp $t;
+    add_trace("$t\n");
+    $@ = $err;
+};
+
 sub nf_dev_match {
     my ($devre, $dev) = @_;
 
@@ -45,21 +53,40 @@ sub nf_dev_match {
 }
 
 sub ipset_match {
-    my ($ipsetname, $ipset, $ipaddr) = @_;
+    my ($ipset_ruleset, $ipsetname, $ipaddr) = @_;
+
+    my $ipset = $ipset_ruleset->{$ipsetname};
+    die "no such ipset '$ipsetname'" if !$ipset;
 
     my $ip = Net::IP->new($ipaddr);
 
-    foreach my $entry (@$ipset) {
-       next if $entry =~ m/^create/; # simply ignore
-       if ($entry =~ m/add \S+ (\S+)$/) {
-           my $test = Net::IP->new($1);
-           if ($test->overlaps($ip)) {
-               add_trace("IPSET $ipsetname match $ipaddr\n");
-               return 1;
+    my $first = $ipset->[0];
+    if ($first =~ m/^create\s+\S+\s+list:/) {
+       foreach my $entry (@$ipset) {
+           next if $entry =~ m/^create/; # simply ignore
+           if ($entry =~ m/add \S+ (\S+)$/) {
+               return 1 if ipset_match($ipset_ruleset, $1, $ipaddr);
+           } else {
+               die "implement me";
+           }
+       }
+       return 0;
+    } elsif ($first =~ m/^create\s+\S+\s+hash:net/) {
+       foreach my $entry (@$ipset) {
+           next if $entry =~ m/^create/; # simply ignore
+           if ($entry =~ m/add \S+ (\S+)$/) {
+               my $test = Net::IP->new($1);
+               if ($test->overlaps($ip)) {
+                   add_trace("IPSET $ipsetname match $ipaddr\n");
+                   return 1;
+               }
+           } else {
+               die "implement me";
            }
-       } else {
-           die "implement me";
        }
+       return 0;
+    } else {
+       die "unknown ipset type '$first' - not implemented\n";
     }
 
     return 0;
@@ -111,7 +138,7 @@ sub rule_match {
            next;
        }
 
-       if ($rule =~ s/^-p (tcp|udp)\s*//) {
+       if ($rule =~ s/^-p (tcp|udp|igmp|icmp)\s*//) {
            die "missing proto" if !$pkg->{proto};
            return undef if $pkg->{proto} ne $1; # no match
            next;
@@ -143,19 +170,22 @@ sub rule_match {
            next;
        }
 
-       if ($rule =~ s/^-m set --match-set (\S+) src\s*//) {
+       if ($rule =~ s/^-m set (!\s+)?--match-set (\S+) src\s*//) {
            die "missing source" if !$pkg->{source};
-           my $ipset = $ipset_ruleset->{$1};
-           die "no such ip set '$1'" if !$ipset;
-           return undef if !ipset_match($1, $ipset, $pkg->{source});
+           my $neg = $1;
+           my $ipset_name = $2;
+           if ($neg) {
+               return undef if ipset_match($ipset_ruleset, $ipset_name, $pkg->{source});
+           } else {
+               return undef if !ipset_match($ipset_ruleset, $ipset_name, $pkg->{source});
+           }
            next;
        }
 
        if ($rule =~ s/^-m set --match-set (\S+) dst\s*//) {
            die "missing destination" if !$pkg->{dest};
-           my $ipset = $ipset_ruleset->{$1};
-           die "no such ip set '$1'" if !$ipset;
-           return undef if !ipset_match($1, $ipset, $pkg->{dest});
+           my $ipset_name = $1;
+           return undef if !ipset_match($ipset_ruleset, $ipset_name, $pkg->{dest});
            next;
        }
 
@@ -288,6 +318,8 @@ sub copy_packet {
 sub route_packet {
     my ($ruleset, $ipset_ruleset, $pkg, $from_info, $target, $start_state) = @_;
 
+    $pkg->{ipversion} = 4; # fixme: allow ipv6
+
     my $route_state = $start_state;
 
     my $physdev_in;
@@ -464,18 +496,18 @@ sub extract_ct_info {
 }
 
 sub extract_vm_info {
-    my ($vmdata, $vmid) = @_;
+    my ($vmdata, $vmid, $netnum) = @_;
 
     my $info = { type => 'vm', vmid => $vmid };
 
     my $conf = $vmdata->{qemu}->{$vmid} || die "no such VM '$vmid'";
-    my $net = PVE::QemuServer::parse_net($conf->{net0});
+    my $net = PVE::QemuServer::parse_net($conf->{"net$netnum"});
     $info->{macaddr} = $net->{macaddr} || die "unable to get mac address";
     $info->{bridge} = $net->{bridge} || die "unable to get bridge";
-    $info->{fwbr} = "fwbr${vmid}i0";
-    $info->{tapdev} = "tap${vmid}i0";
-    $info->{fwln} = "fwln${vmid}i0";
-    $info->{fwpr} = "fwpr${vmid}p0";
+    $info->{fwbr} = "fwbr${vmid}i$netnum";
+    $info->{tapdev} = "tap${vmid}i$netnum";
+    $info->{fwln} = "fwln${vmid}i$netnum";
+    $info->{fwpr} = "fwpr${vmid}p$netnum";
 
     return $info;
 }
@@ -542,9 +574,10 @@ sub simulate_firewall {
        } else {
            die "implement me";
        }
-    } elsif ($from =~ m/^vm(\d+)$/) {
+    } elsif ($from =~ m/^vm(\d+)(i(\d))?$/) {
        my $vmid = $1;
-       $from_info = extract_vm_info($vmdata, $vmid);
+       my $netnum = $3 || 0;
+       $from_info = extract_vm_info($vmdata, $vmid, $netnum);
        $start_state = 'fwbr-out'; 
        $pkg->{mac_source} = $from_info->{macaddr};
     } else {
@@ -581,7 +614,7 @@ sub simulate_firewall {
        }
    } elsif ($to =~ m/^vm(\d+)$/) {
        my $vmid = $1;
-       $target = extract_vm_info($vmdata, $vmid);
+       $target = extract_vm_info($vmdata, $vmid, 0);
        $target->{iface} = $target->{tapdev};
     } else {
        die "unable to parse \"to => '$to'\"\n";