summaryrefslogtreecommitdiffstats
path: root/roles/common/files
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2016-05-20 01:19:27 +0200
committerGuilhem Moulin <guilhem@fripost.org>2016-05-22 17:53:52 +0200
commit3fafa03aeb3640a86d9cd8c639d085df6a8d085d (patch)
treeba1bc3707aa20e3a80c08b1dd2726524333b3d21 /roles/common/files
parent1bdc6a1202f9cabea5f907c4213f2a6f902443b6 (diff)
Set up IPSec tunnels between each pair of hosts.
We use a dedicated, non-routable, IPv4 subnet for IPSec. Furthermore the subnet is nullrouted in the absence of xfrm lookup (i.e., when there is no matching IPSec Security Association) to avoid data leaks. Each host is associated with an IP in that subnet (thus only reachble within that subnet, either by the host itself or by its IPSec peers). The peers authenticate each other using RSA public key authentication. Kernel traps are used to ensure that connections are only established when traffic is detected between the peers; after 30m of inactivity (this value needs to be less than the rekeying period) the connection is brought down and a kernel trap is installed.
Diffstat (limited to 'roles/common/files')
-rw-r--r--roles/common/files/etc/logcheck/ignore.d.server/strongswan-local19
-rw-r--r--roles/common/files/etc/strongswan.d/charon.conf303
-rw-r--r--roles/common/files/etc/strongswan.d/charon/socket-default.conf20
-rwxr-xr-xroles/common/files/usr/local/sbin/update-firewall.sh102
4 files changed, 374 insertions, 70 deletions
diff --git a/roles/common/files/etc/logcheck/ignore.d.server/strongswan-local b/roles/common/files/etc/logcheck/ignore.d.server/strongswan-local
new file mode 100644
index 0000000..13218b0
--- /dev/null
+++ b/roles/common/files/etc/logcheck/ignore.d.server/strongswan-local
@@ -0,0 +1,19 @@
+# Ansible Managed
+# Do NOT edit this file directly!
+#
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[JOB\] spawning [[:digit:]]+ worker threads$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] creating acquire job for policy [[:xdigit:].:]{3,39}/[[:digit:]]+(\[\w+(/[[:alnum:]-]+)?\])? === [[:xdigit:].:]{3,39}/[[:digit:]]+(\[\w+(/[[:alnum:]-]+)?\])? with reqid \{[[:digit:]]+\}$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] creating rekey job for ESP CHILD_SA with SPI [[:xdigit:]]{8} and reqid \{[[:digit:]]+\}$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] creating delete job for ESP CHILD_SA with SPI [[:xdigit:]]{8} and reqid \{[[:digit:]]+\}$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[KNL\] querying SAD entry with SPI [[:xdigit:]]{8} failed: No such process \([[:digit:]]+\)$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] initiating IKE_SA [[:alnum:]._-]+\[[[:digit:]]+\] to [[:xdigit:].:]{3,39}$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] [[:xdigit:].:]{3,39} is initiating an IKE_SA$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] establishing CHILD_SA [[:alnum:]._-]+(\{[[:digit:]]+\})?$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] IKE_SA [[:alnum:]._-]+\[[[:digit:]]+\] established between [[:xdigit:].:]{3,39}\[[^]\"]+\]\.\.\.[[:xdigit:].:]{3,39}\[[^]]+\]$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] CHILD_SA [[:alnum:]._-]+\{[[:digit:]]+\} established with SPIs [[:xdigit:]]{8}_i [[:xdigit:]]{8}_o and TS [[:xdigit:].:]{3,39}/[[:digit:]]+ === [[:xdigit:].:]{3,39}/[[:digit:]]+$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] closing CHILD_SA [[:alnum:]._-]+\{[[:digit:]]+\} with SPIs [[:xdigit:]]{8}_i \([[:digit:]]+ bytes\) [[:xdigit:]]{8}_o \([[:digit:]]+ bytes\) and TS [[:xdigit:].:]{3,39}/[[:digit:]]+ === [[:xdigit:].:]{3,39}/[[:digit:]]+$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] reauthenticating IKE_SA [[:alnum:]._-]+\[[[:digit:]]+\]$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[JOB\] deleting IKE_SA after [[:digit:]]+ seconds of CHILD_SA inactivity$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] deleting IKE_SA [[:alnum:]._-]+\[[[:digit:]]+\] between [[:xdigit:].:]{3,39}\[[^]\"]+\]\.\.\.[[:xdigit:].:]{3,39}\[[^]]+\]$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ (charon|ipsec\[[[:digit:]]+\]): [[:digit:]]+\[IKE\] IKE_SA deleted$
+^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ vpn: [+-] .* [[:xdigit:].:]{3,39}/[[:digit:]]+ == [[:xdigit:].:]{3,39} -- [[:xdigit:].:]{3,39} == [[:xdigit:].:]{3,39}/[[:digit:]]+$
diff --git a/roles/common/files/etc/strongswan.d/charon.conf b/roles/common/files/etc/strongswan.d/charon.conf
new file mode 100644
index 0000000..17e917a
--- /dev/null
+++ b/roles/common/files/etc/strongswan.d/charon.conf
@@ -0,0 +1,303 @@
+# Options for the charon IKE daemon.
+charon {
+
+ # Accept unencrypted ID and HASH payloads in IKEv1 Main Mode.
+ # accept_unencrypted_mainmode_messages = no
+
+ # Maximum number of half-open IKE_SAs for a single peer IP.
+ # block_threshold = 5
+
+ # Whether relations in validated certificate chains should be cached in
+ # memory.
+ # cert_cache = yes
+
+ # Send Cisco Unity vendor ID payload (IKEv1 only).
+ # cisco_unity = no
+
+ # Close the IKE_SA if setup of the CHILD_SA along with IKE_AUTH failed.
+ # close_ike_on_child_failure = no
+
+ # Number of half-open IKE_SAs that activate the cookie mechanism.
+ # cookie_threshold = 10
+
+ # Use ANSI X9.42 DH exponent size or optimum size matched to cryptographic
+ # strength.
+ # dh_exponent_ansi_x9_42 = yes
+
+ # DNS server assigned to peer via configuration payload (CP).
+ # dns1 =
+
+ # DNS server assigned to peer via configuration payload (CP).
+ # dns2 =
+
+ # Enable Denial of Service protection using cookies and aggressiveness
+ # checks.
+ # dos_protection = yes
+
+ # Compliance with the errata for RFC 4753.
+ # ecp_x_coordinate_only = yes
+
+ # Free objects during authentication (might conflict with plugins).
+ # flush_auth_cfg = no
+
+ # Maximum size (complete IP datagram size in bytes) of a sent IKE fragment
+ # when using proprietary IKEv1 or standardized IKEv2 fragmentation (0 for
+ # address family specific default values). If specified this limit is
+ # used for both IPv4 and IPv6.
+ # fragment_size = 0
+
+ # Name of the group the daemon changes to after startup.
+ # group =
+
+ # Timeout in seconds for connecting IKE_SAs (also see IKE_SA_INIT DROPPING).
+ # half_open_timeout = 30
+
+ # Enable hash and URL support.
+ # hash_and_url = no
+
+ # Allow IKEv1 Aggressive Mode with pre-shared keys as responder.
+ # i_dont_care_about_security_and_use_aggressive_mode_psk = no
+
+ # A space-separated list of routing tables to be excluded from route
+ # lookups.
+ # ignore_routing_tables =
+
+ # Maximum number of IKE_SAs that can be established at the same time before
+ # new connection attempts are blocked.
+ # ikesa_limit = 0
+
+ # Number of exclusively locked segments in the hash table.
+ # ikesa_table_segments = 1
+
+ # Size of the IKE_SA hash table.
+ # ikesa_table_size = 1
+
+ # Whether to close IKE_SA if the only CHILD_SA closed due to inactivity.
+ inactivity_close_ike = yes
+
+ # Limit new connections based on the current number of half open IKE_SAs,
+ # see IKE_SA_INIT DROPPING in strongswan.conf(5).
+ # init_limit_half_open = 0
+
+ # Limit new connections based on the number of queued jobs.
+ # init_limit_job_load = 0
+
+ # Causes charon daemon to ignore IKE initiation requests.
+ # initiator_only = no
+
+ # Install routes into a separate routing table for established IPsec
+ # tunnels.
+ install_routes = no
+
+ # Install virtual IP addresses.
+ install_virtual_ip = no
+
+ # The name of the interface on which virtual IP addresses should be
+ # installed.
+ # install_virtual_ip_on =
+
+ # Check daemon, libstrongswan and plugin integrity at startup.
+ # integrity_test = no
+
+ # A comma-separated list of network interfaces that should be ignored, if
+ # interfaces_use is specified this option has no effect.
+ # interfaces_ignore =
+
+ # A comma-separated list of network interfaces that should be used by
+ # charon. All other interfaces are ignored.
+ # interfaces_use =
+
+ # NAT keep alive interval.
+ # keep_alive = 20s
+
+ # Plugins to load in the IKE daemon charon.
+ # load =
+
+ # Determine plugins to load via each plugin's load option.
+ # load_modular = no
+
+ # Maximum packet size accepted by charon.
+ # max_packet = 10000
+
+ # Enable multiple authentication exchanges (RFC 4739).
+ # multiple_authentication = yes
+
+ # WINS servers assigned to peer via configuration payload (CP).
+ # nbns1 =
+
+ # WINS servers assigned to peer via configuration payload (CP).
+ # nbns2 =
+
+ # UDP port used locally. If set to 0 a random port will be allocated.
+ # port = 500
+
+ # UDP port used locally in case of NAT-T. If set to 0 a random port will be
+ # allocated. Has to be different from charon.port, otherwise a random port
+ # will be allocated.
+ # port_nat_t = 4500
+
+ # By default public IPv6 addresses are preferred over temporary ones (RFC
+ # 4941), to make connections more stable. Enable this option to reverse
+ # this.
+ # prefer_temporary_addrs = no
+
+ # Process RTM_NEWROUTE and RTM_DELROUTE events.
+ # process_route = yes
+
+ # Delay in ms for receiving packets, to simulate larger RTT.
+ # receive_delay = 0
+
+ # Delay request messages.
+ # receive_delay_request = yes
+
+ # Delay response messages.
+ # receive_delay_response = yes
+
+ # Specific IKEv2 message type to delay, 0 for any.
+ # receive_delay_type = 0
+
+ # Size of the AH/ESP replay window, in packets.
+ # replay_window = 32
+
+ # Base to use for calculating exponential back off, see IKEv2 RETRANSMISSION
+ # in strongswan.conf(5).
+ # retransmit_base = 1.8
+
+ # Timeout in seconds before sending first retransmit.
+ # retransmit_timeout = 4.0
+
+ # Number of times to retransmit a packet before giving up.
+ # retransmit_tries = 5
+
+ # Interval to use when retrying to initiate an IKE_SA (e.g. if DNS
+ # resolution failed), 0 to disable retries.
+ # retry_initiate_interval = 0
+
+ # Initiate CHILD_SA within existing IKE_SAs.
+ # reuse_ikesa = yes
+
+ # Numerical routing table to install routes to.
+ # routing_table =
+
+ # Priority of the routing table.
+ # routing_table_prio =
+
+ # Delay in ms for sending packets, to simulate larger RTT.
+ # send_delay = 0
+
+ # Delay request messages.
+ # send_delay_request = yes
+
+ # Delay response messages.
+ # send_delay_response = yes
+
+ # Specific IKEv2 message type to delay, 0 for any.
+ # send_delay_type = 0
+
+ # Send strongSwan vendor ID payload
+ # send_vendor_id = no
+
+ # Number of worker threads in charon.
+ # threads = 16
+
+ # Name of the user the daemon changes to after startup.
+ # user =
+
+ crypto_test {
+
+ # Benchmark crypto algorithms and order them by efficiency.
+ # bench = no
+
+ # Buffer size used for crypto benchmark.
+ # bench_size = 1024
+
+ # Number of iterations to test each algorithm.
+ # bench_time = 50
+
+ # Test crypto algorithms during registration (requires test vectors
+ # provided by the test-vectors plugin).
+ # on_add = no
+
+ # Test crypto algorithms on each crypto primitive instantiation.
+ # on_create = no
+
+ # Strictly require at least one test vector to enable an algorithm.
+ # required = no
+
+ # Whether to test RNG with TRUE quality; requires a lot of entropy.
+ # rng_true = no
+
+ }
+
+ host_resolver {
+
+ # Maximum number of concurrent resolver threads (they are terminated if
+ # unused).
+ # max_threads = 3
+
+ # Minimum number of resolver threads to keep around.
+ # min_threads = 0
+
+ }
+
+ leak_detective {
+
+ # Includes source file names and line numbers in leak detective output.
+ # detailed = yes
+
+ # Threshold in bytes for leaks to be reported (0 to report all).
+ # usage_threshold = 10240
+
+ # Threshold in number of allocations for leaks to be reported (0 to
+ # report all).
+ # usage_threshold_count = 0
+
+ }
+
+ processor {
+
+ # Section to configure the number of reserved threads per priority class
+ # see JOB PRIORITY MANAGEMENT in strongswan.conf(5).
+ priority_threads {
+
+ }
+
+ }
+
+ # Section containing a list of scripts (name = path) that are executed when
+ # the daemon is started.
+ start-scripts {
+
+ }
+
+ # Section containing a list of scripts (name = path) that are executed when
+ # the daemon is terminated.
+ stop-scripts {
+
+ }
+
+ tls {
+
+ # List of TLS encryption ciphers.
+ # cipher =
+
+ # List of TLS key exchange methods.
+ # key_exchange =
+
+ # List of TLS MAC algorithms.
+ # mac =
+
+ # List of TLS cipher suites.
+ # suites =
+
+ }
+
+ x509 {
+
+ # Discard certificates with unsupported or unknown critical extensions.
+ # enforce_critical = yes
+
+ }
+
+}
+
diff --git a/roles/common/files/etc/strongswan.d/charon/socket-default.conf b/roles/common/files/etc/strongswan.d/charon/socket-default.conf
new file mode 100644
index 0000000..1bc20f1
--- /dev/null
+++ b/roles/common/files/etc/strongswan.d/charon/socket-default.conf
@@ -0,0 +1,20 @@
+socket-default {
+
+ # Firewall mark to set on outbound packets.
+ # fwmark =
+
+ # Whether to load the plugin. Can also be an integer to increase the
+ # priority of this plugin.
+ load = yes
+
+ # Set source address on outbound packets, if possible.
+ # set_source = yes
+
+ # Listen on IPv4, if possible.
+ # use_ipv4 = yes
+
+ # Listen on IPv6, if possible.
+ use_ipv6 = no
+
+}
+
diff --git a/roles/common/files/usr/local/sbin/update-firewall.sh b/roles/common/files/usr/local/sbin/update-firewall.sh
index f25f507..065bae2 100755
--- a/roles/common/files/usr/local/sbin/update-firewall.sh
+++ b/roles/common/files/usr/local/sbin/update-firewall.sh
@@ -40,8 +40,12 @@ check=0
verbose=0
addrfam=
-secmark=0xA99 # must match that in /etc/network/if-up.d/ipsec
-secproto=esp # must match /etc/ipsec.conf; ESP is the default (vs AH/IPComp)
+secproto=esp # must match /etc/ipsec.conf; ESP is the default (vs AH/IPComp)
+if [ -x /usr/sbin/ipsec ] && /usr/sbin/ipsec status >/dev/null; then
+ ipsec=y
+else
+ ipsec=n
+fi
fail2ban_re='^(\[[0-9]+:[0-9]+\]\s+)?-A fail2ban-\S'
IPSec_re=" -m policy --dir (in|out) --pol ipsec --reqid [0-9]+ --proto $secproto -j ACCEPT$"
@@ -152,21 +156,9 @@ run() {
tables[$f]=filter
# The default interface associated with this address.
- local if=$( /bin/ip -$f route show to default scope global \
+ local if=$( /bin/ip -$f -o route show to default scope global \
| sed -nr '/^default via \S+ dev (\S+).*/ {s//\1/p;q}' )
- # The virtual interface reserved for IPSec.
- local ifsec=$( /bin/ip -o -$f link show \
- | sed -nr "/^[0-9]+:\s+(sec[0-9]+)@$if:\s.*/ {s//\1/p;q}" )
-
- # The (host-scoped) IP reserved for IPSec.
- local ipsec=
- if [ "$ifsec" -a $f = 4 ]; then
- tables[$f]='mangle nat filter'
- ipsec=$( /bin/ip -$f address show dev "$ifsec" scope host \
- | sed -nr '/^\s+inet\s(\S+).*/ {s//\1/p;q}' )
- fi
-
# Store the old (current) ruleset
local old=$(mktemp --tmpdir current-rules.v$f.XXXXXX) \
new=$(mktemp --tmpdir new-rules.v$f.XXXXXX)
@@ -203,8 +195,9 @@ run() {
grep -E -- "$fail2ban_re" "$old" || true
fi >> "$new"
- if [ "$ipsec" ]; then
- # (Host-to-host) IPSec tunnels come first. TODO: test IPSec with IPv6.
+ if [ "$f" = 4 -a "$ipsec" = y ]; then
+ # Our IPSec tunnels are IPv4 only.
+ # (Host-to-host) IPSec tunnels come first.
grep -E -- "$IPSec_re" "$old" >> "$new" || true
# Allow any IPsec $secproto protocol packets to be sent and received.
@@ -219,15 +212,24 @@ run() {
# http://baldric.net/loose-iptables-firewall-for-servers/
local ip
- if [ "$f" = 4 ]; then
+ if [ "$f" = 4 -a "$ipsec" = y ]; then
# Private-use networks (RFC 1918) and link local (RFC 3927)
- local MyNetwork=$( /bin/ip -4 address show dev $if scope global \
- | sed -nr 's/^\s+inet\s(\S+).*/\1/p')
+ local MyIPSec="$( /bin/ip -4 -o route show table 220 dev $if | sed 's/\s.*//' )"
+ local MyNetwork="$( /bin/ip -4 -o address show dev $if scope global \
+ | sed -nr "s/^[0-9]+:\s+$if\s+inet\s(\S+).*/\1/p" \
+ | while read ip; do
+ for ips in $MyIPSec; do
+ [ "$ips" = "$(/usr/bin/netmask -nc "$ip" "$ips" | sed 's/^ *//')" ] || echo "$ip"
+ done
+ done
+ )"
[ "$MyNetwork" ] && \
for ip in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16; do
# Don't lock us out if we are behind a NAT ;-)
- [ "$ip" = "$(/usr/bin/netmask -nc $ip $MyNetwork | sed 's/ //g')" ] \
- || iptables -A INPUT -i $if -s "$ip" -j DROP
+ for myip in $MyNetwork; do
+ [ "$ip" = "$(/usr/bin/netmask -nc "$ip" "$myip" | sed 's/^ *//')" ] \
+ || iptables -A INPUT -i $if -s "$ip" -j DROP
+ done
done
# Other martian packets: "This" network, multicast, broadcast (RFCs
@@ -259,14 +261,14 @@ run() {
local localhost=$(inet46 $f '127.0.0.1/8' '::1/128')
iptables -A INPUT -i lo -s "$localhost" -d "$localhost" -j ACCEPT
iptables -A OUTPUT -o lo -s "$localhost" -d "$localhost" -j ACCEPT
-
- if [ "$ipsec" ]; then
- # ACCEPT any, *IPSec* traffic destinating to the non-routable
- # $ipsec. Also ACCEPT all traffic originating from $ipsec, as
- # it is MASQUERADE'd.
- iptables -A INPUT -d "$ipsec" -i $if -m policy --dir in \
- --pol ipsec --proto $secproto -j ACCEPT
- iptables -A OUTPUT -m mark --mark "$secmark" -o $if -j ACCEPT
+ if [ "$f" = 4 -a "$ipsec" = y ]; then
+ # Allow local access to our virtual IP
+ /bin/ip -4 -o route show table 220 dev $if \
+ | sed -nr 's/.*\ssrc\s+([[:digit:].]{7,15})(\s.*)?/\1/p' \
+ | while read ipsec; do
+ iptables -A INPUT -i lo -s "$ipsec" -d "$ipsec" -j ACCEPT
+ iptables -A OUTPUT -o lo -s "$ipsec" -d "$ipsec" -j ACCEPT
+ done
fi
# Prepare fail2ban. We make fail2ban insert its rules in a
@@ -330,46 +332,6 @@ run() {
########################################################################
commit
- if [ "$ipsec" ]; then
- # DNAT the IPSec paquets to $ipsec after decapsulation, and SNAT
- # them before encapsulation. We need to do the NAT'ing before
- # packets enter the IPSec stack because they are signed
- # afterwards, and NAT'ing would mess up the signature.
- ipt-chains mangle PREROUTING:ACCEPT INPUT:ACCEPT \
- FORWARD:DROP \
- OUTPUT:ACCEPT POSTROUTING:ACCEPT
-
- # Packets which destination is $ipsec *must* be associated with
- # an IPSec policy.
- iptables -A INPUT -d "$ipsec" -i $if -m policy --dir in \
- --pol ipsec --proto $secproto -j ACCEPT
- iptables -A INPUT -d "$ipsec" -i $if -j DROP
-
- # Packets originating from our (non-routable) $ipsec are marked;
- # if there is no xfrm lookup (i.e., no matching IPSec
- # association), the packet will retain its mark and be null
- # routed later on. Otherwise, the packet is re-queued unmarked.
- iptables -A OUTPUT -o $if -j MARK --set-mark 0x0
- iptables -A OUTPUT -s "$ipsec" -o $if -m policy --dir out \
- --pol none -j MARK --set-mark $secmark
- commit
-
- ipt-chains nat PREROUTING:ACCEPT INPUT:ACCEPT \
- OUTPUT:ACCEPT POSTROUTING:ACCEPT
-
- # DNAT all marked packets after decapsulation.
- iptables -A PREROUTING \! -d "$ipsec" -i $if -m policy --dir in \
- --pol ipsec --proto $secproto -j DNAT --to "${ipsec%/*}"
-
- # Packets originating from our IPSec are SNAT'ed (MASQUERADE).
- # (And null-routed later on unless there is an xfrm
- # association.)
- iptables -A POSTROUTING -m mark --mark $secmark -o $if -j MASQUERADE
- commit
- fi
-
- ########################################################################
-
local rv1=0 rv2=0 persistent=/etc/iptables/rules.v$f
local oldz=$(mktemp --tmpdir current-rules.v$f.XXXXXX)