use File::Basename;
use Net::IP;
-my $mark;
+# dynamically include PVE::QemuServer and PVE::LXC
+# to avoid dependency problems
+my $have_qemu_server;
+eval {
+ require PVE::QemuServer;
+ $have_qemu_server = 1;
+};
+
+my $have_lxc;
+eval {
+ require PVE::LXC;
+ $have_lxc = 1;
+};
+
+my $mark = 0;
my $trace;
my $debug = 0;
+my $NUMBER_RE = qr/0x[0-9a-fA-F]+|\d+/;
+
sub debug {
my $new_value = shift;
}
}
+$SIG{'__WARN__'} = sub {
+ my $err = $@;
+ my $t = $_[0];
+ chomp $t;
+ add_trace("$t\n");
+ $@ = $err;
+};
+
sub nf_dev_match {
my ($devre, $dev) = @_;
}
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";
}
- } 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";
+ }
+ }
+ return 0;
+ } else {
+ die "unknown ipset type '$first' - not implemented\n";
}
return 0;
sub rule_match {
my ($ipset_ruleset, $chain, $rule, $pkg) = @_;
- $rule =~ s/^-A $chain // || die "got strange rule: $rule";
+ $rule =~ s/^-A $chain +// || die "got strange rule: $rule";
while (length($rule)) {
next if $cstate =~ m/NEW/;
- die "please implement cstate test '$cstate'";
+ die "cstate test '$cstate' not implemented\n";
}
if ($rule =~ s/^-m addrtype --src-type (\S+)\s*//) {
my $atype = $1;
- die "missing srctype" if !$pkg->{srctype};
+ 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 dsttype" if !$pkg->{dsttype};
+ die "missing destination address type (dsttype)\n"
+ if !$pkg->{dsttype};
return undef if $atype ne $pkg->{dsttype};
}
if ($rule =~ s/^-i (\S+)\s*//) {
my $devre = $1;
- die "missing iface_in" if !$pkg->{iface_in};
+ die "missing interface (iface_in)\n" if !$pkg->{iface_in};
return undef if !nf_dev_match($devre, $pkg->{iface_in});
next;
}
if ($rule =~ s/^-o (\S+)\s*//) {
my $devre = $1;
- die "missing iface_out" if !$pkg->{iface_out};
+ die "missing interface (iface_out)\n" if !$pkg->{iface_out};
return undef if !nf_dev_match($devre, $pkg->{iface_out});
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;
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;
}
next;
}
- if ($rule =~ s/^-m mark --mark (\d+)\s*//) {
- return undef if !defined($mark) || $mark != $1;
+ if ($rule =~ s@^-m mark --mark ($NUMBER_RE)(?:/($NUMBER_RE))?\s*@@) {
+ my ($value, $mask) = PVE::Firewall::get_mark_values($1, $2);
+ return undef if ($mark & $mask) != $value;
next;
}
# final actions
- if ($rule =~ s/^-j MARK --set-mark (\d+)\s*$//) {
- $mark = $1;
+ if ($rule =~ s@^-j MARK --set-mark ($NUMBER_RE)(?:/($NUMBER_RE))?\s*$@@) {
+ my ($value, $mask) = PVE::Firewall::get_mark_values($1, $2);
+ $mark = ($mark & ~$mask) | $value;
return undef;
}
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;
$pkg->{iface_out} = $target->{bridge} || die 'internal error';
$chain = 'PVEFW-OUTPUT';
$next_route_state = $target->{iface} || die 'internal error';
- } elsif ($target->{type} eq 'ct') {
- $pkg->{iface_in} = 'lo';
- $pkg->{iface_out} = 'venet0';
- $chain = 'PVEFW-OUTPUT';
- $next_route_state = 'venet-in';
- } elsif ($target->{type} eq 'vm') {
+ } elsif ($target->{type} eq 'vm' || $target->{type} eq 'ct') {
$pkg->{iface_in} = 'lo';
$pkg->{iface_out} = $target->{bridge} || die 'internal error';
$chain = 'PVEFW-OUTPUT';
die "implement me";
}
- } elsif ($route_state eq 'venet-out') {
-
- if ($target->{type} eq 'host') {
-
- $chain = 'PVEFW-INPUT';
- $pkg->{iface_in} = 'venet0';
- $pkg->{iface_out} = 'lo';
- $next_route_state = 'host';
-
- } elsif ($target->{type} eq 'bport') {
-
- $chain = 'PVEFW-FORWARD';
- $pkg->{iface_in} = 'venet0';
- $pkg->{iface_out} = $target->{bridge} || die 'internal error';
- $next_route_state = $target->{iface} || die 'internal error';
-
- } elsif ($target->{type} eq 'vm') {
-
- $chain = 'PVEFW-FORWARD';
- $pkg->{iface_in} = 'venet0';
- $pkg->{iface_out} = $target->{bridge} || die 'internal error';
- $next_route_state = 'fwbr-in';
-
- } elsif ($target->{type} eq 'ct') {
-
- $chain = 'PVEFW-FORWARD';
- $pkg->{iface_in} = 'venet0';
- $pkg->{iface_out} = 'venet0';
- $next_route_state = 'venet-in';
-
- } else {
- die "implement me";
- }
-
} elsif ($route_state eq 'fwbr-out') {
$chain = 'PVEFW-FORWARD';
}
$next_route_state = $target->{iface};
- } elsif ($target->{type} eq 'ct') {
-
- $chain = 'PVEFW-FORWARD';
- $pkg->{iface_in} = $route_state;
- $pkg->{iface_out} = 'venet0';
- $next_route_state = 'venet-in';
-
- } elsif ($target->{type} eq 'vm') {
+ } elsif ($target->{type} eq 'vm' || $target->{type} eq 'ct') {
$chain = 'PVEFW-FORWARD';
$pkg->{iface_in} = $route_state;
}
sub extract_ct_info {
- my ($vmdata, $vmid) = @_;
+ my ($vmdata, $vmid, $netnum) = @_;
my $info = { type => 'ct', vmid => $vmid };
- my $conf = $vmdata->{openvz}->{$vmid} || die "no such CT '$vmid'";
- if ($conf->{ip_address}) {
- $info->{ip_address} = $conf->{ip_address}->{value};
- } else {
- die "implement me";
- }
+ my $conf = $vmdata->{lxc}->{$vmid} || die "no such CT '$vmid'";
+ my $net = PVE::LXC::Config->parse_lxc_network($conf->{"net$netnum"});
+ $info->{macaddr} = $net->{hwaddr} || die "unable to get mac address";
+ $info->{bridge} = $net->{bridge} || die "unable to get bridge";
+ $info->{fwbr} = "fwbr${vmid}i$netnum";
+ $info->{tapdev} = "veth${vmid}i$netnum";
+ $info->{fwln} = "fwln${vmid}i$netnum";
+ $info->{fwpr} = "fwpr${vmid}p$netnum";
+ $info->{ip_address} = $net->{ip} || die "unable to get ip address";
+
return $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;
}
$from_info->{iface} = 'tapXYZ';
$start_state = 'from-bport';
} elsif ($from =~ m/^ct(\d+)$/) {
+ return 'SKIPPED' if !$have_lxc;
my $vmid = $1;
- $from_info = extract_ct_info($vmdata, $vmid);
- if ($from_info->{ip_address}) {
- $pkg->{source} = $from_info->{ip_address} if !defined($pkg->{source});
- $start_state = 'venet-out';
- } else {
- die "implement me";
- }
- } elsif ($from =~ m/^vm(\d+)$/) {
+ $from_info = extract_ct_info($vmdata, $vmid, 0);
+ $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;
- $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 {
$target->{bridge} = 'vmbr0';
$target->{iface} = 'tapXYZ';
} elsif ($to =~ m/^ct(\d+)$/) {
+ return 'SKIPPED' if !$have_lxc;
my $vmid = $1;
- $target = extract_ct_info($vmdata, $vmid);
- $target->{iface} = 'venet-in';
-
- if ($target->{ip_address}) {
- $pkg->{dest} = $target->{ip_address};
- } else {
- die "implement me";
- }
+ $target = extract_ct_info($vmdata, $vmid, 0);
+ $target->{iface} = $target->{tapdev};
} elsif ($to =~ m/^vm(\d+)$/) {
+ return 'SKIPPED' if !$have_qemu_server;
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";
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;