[8/9] suricata: Introduce IPSBYPASS chain

Message ID 20211018101022.15448-8-michael.tremer@ipfire.org
State Accepted
Commit 3fa8300e706227db9f72b4b1349dde3e66399298
Headers
Series [1/9] suricata: Set most significant bit as repeat marker |

Commit Message

Michael Tremer Oct. 18, 2021, 10:10 a.m. UTC
  NFQUEUE does not let the packet continue where it was processed, but
inserts it back into iptables at the start. That is why we need an
extra IPSBYPASS chain which has the following tasks:

* Make the BYPASS bit permanent for the entire connection
* Clear the REPEAT bit

The latter is more of cosmetic nature so that we can identify packets
that have come from suricata again and those which have bypassed the IPS
straight away.

The IPS_* chain will now only be sent traffic to, when none of the two
relevant bits has been set. Otherwise the packet has already been
processed by suricata in the first pass or suricata has decided to
bypass the connection.

This massively reduces load on the IPS which allows many common
connections (TLS connections with downloads) to bypass the IPS bringing
us back to line speed.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
---
 src/initscripts/system/firewall | 23 ++++++++++++++++++++---
 src/initscripts/system/suricata | 27 +++------------------------
 2 files changed, 23 insertions(+), 27 deletions(-)
  

Comments

Stefan Schantl Oct. 19, 2021, 4:04 a.m. UTC | #1
Tested-by: Stefan Schantl <stefan.schantl@ipfire.org>
> NFQUEUE does not let the packet continue where it was processed, but
> inserts it back into iptables at the start. That is why we need an
> extra IPSBYPASS chain which has the following tasks:
> 
> * Make the BYPASS bit permanent for the entire connection
> * Clear the REPEAT bit
> 
> The latter is more of cosmetic nature so that we can identify packets
> that have come from suricata again and those which have bypassed the
> IPS
> straight away.
> 
> The IPS_* chain will now only be sent traffic to, when none of the
> two
> relevant bits has been set. Otherwise the packet has already been
> processed by suricata in the first pass or suricata has decided to
> bypass the connection.
> 
> This massively reduces load on the IPS which allows many common
> connections (TLS connections with downloads) to bypass the IPS
> bringing
> us back to line speed.
> 
> Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
> ---
>  src/initscripts/system/firewall | 23 ++++++++++++++++++++---
>  src/initscripts/system/suricata | 27 +++------------------------
>  2 files changed, 23 insertions(+), 27 deletions(-)
> 
> diff --git a/src/initscripts/system/firewall
> b/src/initscripts/system/firewall
> index ce428393d..530e8f1d6 100644
> --- a/src/initscripts/system/firewall
> +++ b/src/initscripts/system/firewall
> @@ -17,6 +17,11 @@ NAT_MASK="0x0f000000"
>  IPSEC_MARK="0x00800000"
>  IPSEC_MASK="${IPSEC_MARK}"
>  
> +IPS_REPEAT_MARK="0x80000000"
> +IPS_REPEAT_MASK="0x80000000"
> +IPS_BYPASS_MARK="0x40000000"
> +IPS_BYPASS_MASK="0x40000000"
> +
>  function iptables() {
>         /sbin/iptables --wait "$@"
>  }
> @@ -41,6 +46,17 @@ iptables_init() {
>         modprobe nf_log_ipv4
>         sysctl -q -w net.netfilter.nf_log.2=nf_log_ipv4
>  
> +       # IPS Bypass Chain which stores the BYPASS bit in connection
> tracking
> +       iptables -N IPSBYPASS
> +       iptables -A IPSBYPASS -j MARK --set-xmark "0/$((
> IPS_REPEAT_MASK ))"
> +       iptables -A IPSBYPASS -j CONNMARK --save-mark
> +
> +       # Jump into bypass chain when the BYPASS bit is set
> +       for chain in INPUT FORWARD OUTPUT; do
> +               iptables -A "${chain}" -m mark \
> +                       --mark "$(( IPS_REPEAT_MARK | IPS_BYPASS_MARK
> ))/$(( IPS_REPEAT_MASK | IPS_BYPASS_MASK ))" -j IPSBYPASS
> +       done
> +
>         # Empty LOG_DROP and LOG_REJECT chains
>         iptables -N LOG_DROP
>         iptables -A LOG_DROP   -m limit --limit 10/second -j LOG
> @@ -147,9 +163,10 @@ iptables_init() {
>         iptables -N IPS_INPUT
>         iptables -N IPS_FORWARD
>         iptables -N IPS_OUTPUT
> -       iptables -A INPUT -j IPS_INPUT
> -       iptables -A FORWARD -j IPS_FORWARD
> -       iptables -A OUTPUT -j IPS_OUTPUT
> +
> +       for chain in INPUT FORWARD OUTPUT; do
> +               iptables -A "${chain}" -m mark --mark "0x0/$((
> IPS_REPEAT_MASK | IPS_BYPASS_MASK ))" -j "IPS_${chain}"
> +       done
>  
>         # OpenVPN transfer network translation
>         iptables -t nat -N OVPNNAT
> diff --git a/src/initscripts/system/suricata
> b/src/initscripts/system/suricata
> index 72d01b91d..13fcc7f34 100644
> --- a/src/initscripts/system/suricata
> +++ b/src/initscripts/system/suricata
> @@ -34,12 +34,6 @@ network_zones=( red green blue orange ovpn )
>  # Array to store the network zones weather the IPS is enabled for.
>  enabled_ips_zones=()
>  
> -# Mark and Mask options.
> -REPEAT_MARK="0x80000000"
> -REPEAT_MASK="0x80000000"
> -BYPASS_MARK="0x40000000"
> -BYPASS_MASK="0x40000000"
> -
>  # PID file of suricata.
>  PID_FILE="/var/run/suricata.pid"
>  
> @@ -134,34 +128,19 @@ function generate_fw_rules {
>         # Flush the firewall chains.
>         flush_fw_chain
>  
> -       # Skip anything that has the bypass bit set
> -       local chain
> -       for chain in "${IPS_INPUT_CHAIN}" "${IPS_FORWARD_CHAIN}"
> "${IPS_OUTPUT_CHAIN}"; do
> -               iptables -w -A "${chain}" -m mark --mark
> "${BYPASS_MARK}/${BYPASS_MASK}" -j RETURN
> -       done
> -
>         # Check if the array of enabled_ips_zones contains any
> elements.
>         if [[ ${enabled_ips_zones[@]} ]]; then
>                 # Loop through the array and create firewall rules.
>                 for enabled_ips_zone in "${enabled_ips_zones[@]}"; do
>                         # Create rules queue input and output related
> traffic and pass it to the IPS.
> -                       iptables -w -A "$IPS_INPUT_CHAIN" -i
> "$enabled_ips_zone" -m mark ! --mark "${REPEAT_MARK}/${REPEAT_MASK}"
> -j NFQUEUE $NFQ_OPTIONS
> -                       iptables -w -A "$IPS_OUTPUT_CHAIN" -o
> "$enabled_ips_zone" -m mark ! --mark "${REPEAT_MARK}/${REPEAT_MASK}"
> -j NFQUEUE $NFQ_OPTIONS
> +                       iptables -w -A "$IPS_INPUT_CHAIN" -i
> "$enabled_ips_zone" -j NFQUEUE $NFQ_OPTIONS
> +                       iptables -w -A "$IPS_OUTPUT_CHAIN" -o
> "$enabled_ips_zone" -j NFQUEUE $NFQ_OPTIONS
>  
>                         # Create rules which are required to handle
> forwarded traffic.
>                         for enabled_ips_zone_forward in
> "${enabled_ips_zones[@]}"; do
> -                               iptables -w -A "$IPS_FORWARD_CHAIN" -
> i "$enabled_ips_zone" -o "$enabled_ips_zone_forward" -m mark ! --mark
> "${REPEAT_MARK}/${REPEAT_MASK}" -j NFQUEUE $NFQ_OPTIONS
> +                               iptables -w -A "$IPS_FORWARD_CHAIN" -
> i "$enabled_ips_zone" -o "$enabled_ips_zone_forward" -j NFQUEUE
> $NFQ_OPTIONS
>                         done
>                 done
> -
> -               # Add common rules at the end of the chain
> -               for chain in "${IPS_INPUT_CHAIN}"
> "${IPS_FORWARD_CHAIN}" "${IPS_OUTPUT_CHAIN}"; do
> -                       # Clear repeat bit
> -                       iptables -w -A "${chain}" -j MARK --set-xmark
> "0x0/${REPEAT_MASK}"
> -
> -                       # Store bypass bit in CONNMARK
> -                       iptables -w -A "${chain}" -m mark --mark
> "${BYPASS_MARK}/${BYPASS_MASK}" -j CONNMARK --save-mark
> -               done
>         fi
>  }
>
  

Patch

diff --git a/src/initscripts/system/firewall b/src/initscripts/system/firewall
index ce428393d..530e8f1d6 100644
--- a/src/initscripts/system/firewall
+++ b/src/initscripts/system/firewall
@@ -17,6 +17,11 @@  NAT_MASK="0x0f000000"
 IPSEC_MARK="0x00800000"
 IPSEC_MASK="${IPSEC_MARK}"
 
+IPS_REPEAT_MARK="0x80000000"
+IPS_REPEAT_MASK="0x80000000"
+IPS_BYPASS_MARK="0x40000000"
+IPS_BYPASS_MASK="0x40000000"
+
 function iptables() {
 	/sbin/iptables --wait "$@"
 }
@@ -41,6 +46,17 @@  iptables_init() {
 	modprobe nf_log_ipv4
 	sysctl -q -w net.netfilter.nf_log.2=nf_log_ipv4
 
+	# IPS Bypass Chain which stores the BYPASS bit in connection tracking
+	iptables -N IPSBYPASS
+	iptables -A IPSBYPASS -j MARK --set-xmark "0/$(( IPS_REPEAT_MASK ))"
+	iptables -A IPSBYPASS -j CONNMARK --save-mark
+
+	# Jump into bypass chain when the BYPASS bit is set
+	for chain in INPUT FORWARD OUTPUT; do
+		iptables -A "${chain}" -m mark \
+			--mark "$(( IPS_REPEAT_MARK | IPS_BYPASS_MARK ))/$(( IPS_REPEAT_MASK | IPS_BYPASS_MASK ))" -j IPSBYPASS
+	done
+
 	# Empty LOG_DROP and LOG_REJECT chains
 	iptables -N LOG_DROP
 	iptables -A LOG_DROP   -m limit --limit 10/second -j LOG
@@ -147,9 +163,10 @@  iptables_init() {
 	iptables -N IPS_INPUT
 	iptables -N IPS_FORWARD
 	iptables -N IPS_OUTPUT
-	iptables -A INPUT -j IPS_INPUT
-	iptables -A FORWARD -j IPS_FORWARD
-	iptables -A OUTPUT -j IPS_OUTPUT
+
+	for chain in INPUT FORWARD OUTPUT; do
+		iptables -A "${chain}" -m mark --mark "0x0/$(( IPS_REPEAT_MASK | IPS_BYPASS_MASK ))" -j "IPS_${chain}"
+	done
 
 	# OpenVPN transfer network translation
 	iptables -t nat -N OVPNNAT
diff --git a/src/initscripts/system/suricata b/src/initscripts/system/suricata
index 72d01b91d..13fcc7f34 100644
--- a/src/initscripts/system/suricata
+++ b/src/initscripts/system/suricata
@@ -34,12 +34,6 @@  network_zones=( red green blue orange ovpn )
 # Array to store the network zones weather the IPS is enabled for.
 enabled_ips_zones=()
 
-# Mark and Mask options.
-REPEAT_MARK="0x80000000"
-REPEAT_MASK="0x80000000"
-BYPASS_MARK="0x40000000"
-BYPASS_MASK="0x40000000"
-
 # PID file of suricata.
 PID_FILE="/var/run/suricata.pid"
 
@@ -134,34 +128,19 @@  function generate_fw_rules {
 	# Flush the firewall chains.
 	flush_fw_chain
 
-	# Skip anything that has the bypass bit set
-	local chain
-	for chain in "${IPS_INPUT_CHAIN}" "${IPS_FORWARD_CHAIN}" "${IPS_OUTPUT_CHAIN}"; do
-		iptables -w -A "${chain}" -m mark --mark "${BYPASS_MARK}/${BYPASS_MASK}" -j RETURN
-	done
-
 	# Check if the array of enabled_ips_zones contains any elements.
 	if [[ ${enabled_ips_zones[@]} ]]; then
 		# Loop through the array and create firewall rules.
 		for enabled_ips_zone in "${enabled_ips_zones[@]}"; do
 			# Create rules queue input and output related traffic and pass it to the IPS.
-			iptables -w -A "$IPS_INPUT_CHAIN" -i "$enabled_ips_zone" -m mark ! --mark "${REPEAT_MARK}/${REPEAT_MASK}" -j NFQUEUE $NFQ_OPTIONS
-			iptables -w -A "$IPS_OUTPUT_CHAIN" -o "$enabled_ips_zone" -m mark ! --mark "${REPEAT_MARK}/${REPEAT_MASK}" -j NFQUEUE $NFQ_OPTIONS
+			iptables -w -A "$IPS_INPUT_CHAIN" -i "$enabled_ips_zone" -j NFQUEUE $NFQ_OPTIONS
+			iptables -w -A "$IPS_OUTPUT_CHAIN" -o "$enabled_ips_zone" -j NFQUEUE $NFQ_OPTIONS
 
 			# Create rules which are required to handle forwarded traffic.
 			for enabled_ips_zone_forward in "${enabled_ips_zones[@]}"; do
-				iptables -w -A "$IPS_FORWARD_CHAIN" -i "$enabled_ips_zone" -o "$enabled_ips_zone_forward" -m mark ! --mark "${REPEAT_MARK}/${REPEAT_MASK}" -j NFQUEUE $NFQ_OPTIONS
+				iptables -w -A "$IPS_FORWARD_CHAIN" -i "$enabled_ips_zone" -o "$enabled_ips_zone_forward" -j NFQUEUE $NFQ_OPTIONS
 			done
 		done
-
-		# Add common rules at the end of the chain
-		for chain in "${IPS_INPUT_CHAIN}" "${IPS_FORWARD_CHAIN}" "${IPS_OUTPUT_CHAIN}"; do
-			# Clear repeat bit
-			iptables -w -A "${chain}" -j MARK --set-xmark "0x0/${REPEAT_MASK}"
-
-			# Store bypass bit in CONNMARK
-			iptables -w -A "${chain}" -m mark --mark "${BYPASS_MARK}/${BYPASS_MASK}" -j CONNMARK --save-mark
-		done
 	fi
 }