+
+ return undef;
+}
+
+# core functions
+my $bridge_firewall_enabled = 0;
+
+sub enable_bridge_firewall {
+
+ return if $bridge_firewall_enabled; # only once
+
+ PVE::ProcFSTools::write_proc_entry("/proc/sys/net/bridge/bridge-nf-call-iptables", "1");
+ PVE::ProcFSTools::write_proc_entry("/proc/sys/net/bridge/bridge-nf-call-ip6tables", "1");
+
+ # make sure syncookies are enabled (which is default on newer 3.X kernels anyways)
+ PVE::ProcFSTools::write_proc_entry("/proc/sys/net/ipv4/tcp_syncookies", "1");
+
+ $bridge_firewall_enabled = 1;
+}
+
+my $rule_format = "%-15s %-30s %-30s %-15s %-15s %-15s\n";
+
+sub iptables_restore_cmdlist {
+ my ($cmdlist) = @_;
+
+ run_command("/sbin/iptables-restore -n", input => $cmdlist, errmsg => "iptables_restore_cmdlist");
+}
+
+sub ip6tables_restore_cmdlist {
+ my ($cmdlist) = @_;
+
+ run_command("/sbin/ip6tables-restore -n", input => $cmdlist, errmsg => "iptables_restore_cmdlist");
+}
+
+sub ipset_restore_cmdlist {
+ my ($cmdlist) = @_;
+
+ run_command("/usr/sbin/ipset restore", input => $cmdlist, errmsg => "ipset_restore_cmdlist");
+}
+
+sub iptables_get_chains {
+ my ($iptablescmd) = @_;
+
+ $iptablescmd = "iptables" if !$iptablescmd;
+
+ my $res = {};
+
+ # check what chains we want to track
+ my $is_pvefw_chain = sub {
+ my $name = shift;
+
+ return 1 if $name =~ m/^PVEFW-\S+$/;
+
+ return 1 if $name =~ m/^tap\d+i\d+-(:?IN|OUT)$/;
+
+ return 1 if $name =~ m/^veth\d+.\d+-(:?IN|OUT)$/; # fixme: dev name is configurable
+
+ return 1 if $name =~ m/^venet0-\d+-(:?IN|OUT)$/;
+
+ return 1 if $name =~ m/^fwbr\d+(v\d+)?-(:?FW|IN|OUT|IPS)$/;
+ return 1 if $name =~ m/^GROUP-(:?[^\s\-]+)-(:?IN|OUT)$/;
+
+ return undef;
+ };
+
+ my $table = '';
+
+ my $hooks = {};
+
+ my $parser = sub {
+ my $line = shift;
+
+ return if $line =~ m/^#/;
+ return if $line =~ m/^\s*$/;
+
+ if ($line =~ m/^\*(\S+)$/) {
+ $table = $1;
+ return;
+ }
+
+ return if $table ne 'filter';
+
+ if ($line =~ m/^:(\S+)\s/) {
+ my $chain = $1;
+ return if !&$is_pvefw_chain($chain);
+ $res->{$chain} = "unknown";
+ } elsif ($line =~ m/^-A\s+(\S+)\s.*--comment\s+\"PVESIG:(\S+)\"/) {
+ my ($chain, $sig) = ($1, $2);
+ return if !&$is_pvefw_chain($chain);
+ $res->{$chain} = $sig;
+ } elsif ($line =~ m/^-A\s+(INPUT|OUTPUT|FORWARD)\s+-j\s+PVEFW-\1$/) {
+ $hooks->{$1} = 1;
+ } else {
+ # simply ignore the rest
+ return;
+ }
+ };
+
+ run_command("/sbin/$iptablescmd-save", outfunc => $parser);
+
+ return wantarray ? ($res, $hooks) : $res;
+}
+
+sub iptables_chain_digest {
+ my ($rules) = @_;
+ my $digest = Digest::SHA->new('sha1');
+ foreach my $rule (@$rules) { # order is important
+ $digest->add($rule);
+ }
+ return $digest->b64digest;
+}
+
+sub ipset_chain_digest {
+ my ($rules) = @_;
+
+ my $digest = Digest::SHA->new('sha1');
+ foreach my $rule (sort @$rules) { # note: sorted
+ $digest->add($rule);
+ }
+ return $digest->b64digest;
+}
+
+sub ipset_get_chains {
+
+ my $res = {};
+ my $chains = {};
+
+ my $parser = sub {
+ my $line = shift;
+
+ return if $line =~ m/^#/;
+ return if $line =~ m/^\s*$/;
+ if ($line =~ m/^(?:\S+)\s(PVEFW-\S+)\s(?:\S+).*/) {
+ my $chain = $1;
+ $line =~ s/\s+$//; # delete trailing white space
+ push @{$chains->{$chain}}, $line;
+ } else {
+ # simply ignore the rest
+ return;
+ }
+ };
+
+ run_command("/usr/sbin/ipset save", outfunc => $parser);
+
+ # compute digest for each chain
+ foreach my $chain (keys %$chains) {
+ $res->{$chain} = ipset_chain_digest($chains->{$chain});
+ }
+
+ return $res;
+}
+
+sub ruleset_generate_cmdstr {
+ my ($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_;
+
+ return if defined($rule->{enable}) && !$rule->{enable};
+ return if $rule->{errors};
+
+ die "unable to emit macro - internal error" if $rule->{macro}; # should not happen
+
+ my $nbdport = defined($rule->{dport}) ? parse_port_name_number_or_range($rule->{dport}) : 0;
+ my $nbsport = defined($rule->{sport}) ? parse_port_name_number_or_range($rule->{sport}) : 0;
+
+ my @cmd = ();
+
+ push @cmd, "-i $rule->{iface_in}" if $rule->{iface_in};
+ push @cmd, "-o $rule->{iface_out}" if $rule->{iface_out};
+
+ my $source = $rule->{source};
+ my $dest = $rule->{dest};
+
+ if ($source) {
+ if ($source =~ m/^\+/) {
+ if ($source =~ m/^\+(${ipset_name_pattern})$/) {
+ my $name = $1;
+ if ($fw_conf && $fw_conf->{ipset}->{$name}) {
+ my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name, $ipversion);
+ push @cmd, "-m set --match-set ${ipset_chain} src";
+ } elsif ($cluster_conf && $cluster_conf->{ipset}->{$name}) {
+ my $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion);
+ push @cmd, "-m set --match-set ${ipset_chain} src";
+ } else {
+ die "no such ipset '$name'\n";
+ }
+ } else {
+ die "invalid security group name '$source'\n";
+ }
+ } elsif ($source =~ m/^${ip_alias_pattern}$/){
+ my $alias = lc($source);
+ my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
+ $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf;
+ die "no such alias '$source'\n" if !$e;
+ push @cmd, "-s $e->{cidr}";
+ } elsif ($source =~ m/\-/){
+ push @cmd, "-m iprange --src-range $source";
+ } else {
+ push @cmd, "-s $source";
+ }
+ }
+
+ if ($dest) {
+ if ($dest =~ m/^\+/) {
+ if ($dest =~ m/^\+(${ipset_name_pattern})$/) {
+ my $name = $1;
+ if ($fw_conf && $fw_conf->{ipset}->{$name}) {
+ my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name, $ipversion);
+ push @cmd, "-m set --match-set ${ipset_chain} dst";
+ } elsif ($cluster_conf && $cluster_conf->{ipset}->{$name}) {
+ my $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion);
+ push @cmd, "-m set --match-set ${ipset_chain} dst";
+ } else {
+ die "no such ipset '$name'\n";
+ }
+ } else {
+ die "invalid security group name '$dest'\n";
+ }
+ } elsif ($dest =~ m/^${ip_alias_pattern}$/){
+ my $alias = lc($dest);
+ my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
+ $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf;
+ die "no such alias '$dest'\n" if !$e;
+ push @cmd, "-d $e->{cidr}";
+ } elsif ($dest =~ m/^(\d+)\.(\d+).(\d+).(\d+)\-(\d+)\.(\d+).(\d+).(\d+)$/){
+ push @cmd, "-m iprange --dst-range $dest";
+ } else {
+ push @cmd, "-d $dest";
+ }
+ }
+
+ if ($rule->{proto}) {
+ push @cmd, "-p $rule->{proto}";
+
+ my $multiport = 0;
+ $multiport++ if $nbdport > 1;
+ $multiport++ if $nbsport > 1;
+
+ push @cmd, "--match multiport" if $multiport;
+
+ die "multiport: option '--sports' cannot be used together with '--dports'\n"
+ if ($multiport == 2) && ($rule->{dport} ne $rule->{sport});
+
+ if ($rule->{dport}) {
+ if ($rule->{proto} && $rule->{proto} eq 'icmp') {
+ # Note: we use dport to store --icmp-type
+ die "unknown icmp-type '$rule->{dport}'\n" if !defined($icmp_type_names->{$rule->{dport}});
+ push @cmd, "-m icmp --icmp-type $rule->{dport}";
+ } elsif ($rule->{proto} && $rule->{proto} eq 'icmpv6') {
+ # Note: we use dport to store --icmpv6-type
+ die "unknown icmpv6-type '$rule->{dport}'\n" if !defined($icmpv6_type_names->{$rule->{dport}});
+ push @cmd, "-m icmpv6 --icmpv6-type $rule->{dport}";
+ } else {
+ if ($nbdport > 1) {
+ if ($multiport == 2) {
+ push @cmd, "--ports $rule->{dport}";
+ } else {
+ push @cmd, "--dports $rule->{dport}";
+ }
+ } else {
+ push @cmd, "--dport $rule->{dport}";
+ }
+ }
+ }
+
+ if ($rule->{sport}) {
+ if ($nbsport > 1) {
+ push @cmd, "--sports $rule->{sport}" if $multiport != 2;
+ } else {
+ push @cmd, "--sport $rule->{sport}";
+ }
+ }
+ } elsif ($rule->{dport} || $rule->{sport}) {
+ die "destination port '$rule->{dport}', but no protocol specified\n" if $rule->{dport};
+ die "source port '$rule->{sport}', but no protocol specified\n" if $rule->{sport};
+ }
+
+ push @cmd, "-m addrtype --dst-type $rule->{dsttype}" if $rule->{dsttype};
+
+ if (my $action = $rule->{action}) {
+ $action = $actions->{$action} if defined($actions->{$action});
+ $goto = 1 if !defined($goto) && $action eq 'PVEFW-SET-ACCEPT-MARK';
+ push @cmd, $goto ? "-g $action" : "-j $action";
+ }
+
+ return scalar(@cmd) ? join(' ', @cmd) : undef;
+}
+
+sub ruleset_generate_rule {
+ my ($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_;
+
+ my $rules;
+
+ if ($rule->{macro}) {
+ $rules = &$apply_macro($rule->{macro}, $rule, 0, $ipversion);
+ } else {
+ $rules = [ $rule ];
+ }
+
+ # update all or nothing
+
+ my @cmds = ();
+ foreach my $tmp (@$rules) {
+ if (my $cmdstr = ruleset_generate_cmdstr($ruleset, $chain, $ipversion, $tmp, $actions, $goto, $cluster_conf, $fw_conf)) {
+ push @cmds, $cmdstr;
+ }
+ }
+
+ foreach my $cmdstr (@cmds) {
+ ruleset_addrule($ruleset, $chain, $cmdstr);
+ }
+}
+
+sub ruleset_generate_rule_insert {
+ my ($ruleset, $chain, $ipversion, $rule, $actions, $goto) = @_;
+
+ die "implement me" if $rule->{macro}; # not implemented, because not needed so far
+
+ if (my $cmdstr = ruleset_generate_cmdstr($ruleset, $chain, $ipversion, $rule, $actions, $goto)) {
+ ruleset_insertrule($ruleset, $chain, $cmdstr);
+ }
+}
+
+sub ruleset_create_chain {
+ my ($ruleset, $chain) = @_;
+
+ die "Invalid chain name '$chain' (28 char max)\n" if length($chain) > 28;
+ die "chain name may not contain collons\n" if $chain =~ m/:/; # because of log format
+
+ die "chain '$chain' already exists\n" if $ruleset->{$chain};
+
+ $ruleset->{$chain} = [];
+}
+
+sub ruleset_chain_exist {
+ my ($ruleset, $chain) = @_;
+
+ return $ruleset->{$chain} ? 1 : undef;
+}
+
+sub ruleset_addrule {
+ my ($ruleset, $chain, $rule) = @_;
+
+ die "no such chain '$chain'\n" if !$ruleset->{$chain};
+
+ push @{$ruleset->{$chain}}, "-A $chain $rule";
+}
+
+sub ruleset_insertrule {
+ my ($ruleset, $chain, $rule) = @_;
+
+ die "no such chain '$chain'\n" if !$ruleset->{$chain};
+
+ unshift @{$ruleset->{$chain}}, "-A $chain $rule";
+}
+
+sub get_log_rule_base {
+ my ($chain, $vmid, $msg, $loglevel) = @_;
+
+ die "internal error - no log level" if !defined($loglevel);
+
+ $vmid = 0 if !defined($vmid);
+
+ # Note: we use special format for prefix to pass further
+ # info to log daemon (VMID, LOGVELEL and CHAIN)
+
+ return "-j NFLOG --nflog-prefix \":$vmid:$loglevel:$chain: $msg\"";
+}
+
+sub ruleset_addlog {
+ my ($ruleset, $chain, $vmid, $msg, $loglevel, $rule) = @_;
+
+ return if !defined($loglevel);
+
+ my $logrule = get_log_rule_base($chain, $vmid, $msg, $loglevel);
+
+ $logrule = "$rule $logrule" if defined($rule);
+
+ ruleset_addrule($ruleset, $chain, $logrule);