Problem with Port Triggering

I have an issue. I’m trying to duplicate the port triggering functionality found in many routers for my Debian-based router image. However, I can’t find any configuration options within the kernel to enable such functionality. I have enabled a few configuration options on my own, but I can’t seem to find anything similar to CONFIG_NETFILTER_XT_TARGET_TRIGGER… (or any options with the word TRIGGER in it)

So I started looking to see what my options are. I found ULOGD2 and the NFLOG target. I know I can write a script to deal with port triggering, based on this program and target. However, I was hoping to avoid this…

I found this: Port Triggering with OpenWRT, which adds the functionality to OpenWRT v0.9 (WhiteRussian). I also found this: https://github.com/dnetlab/port-trigger, which seems to also add it as a OpenWRT repository (maybe)…

I’m not sure how to integrate either of these into our kernel… Any ideas?

you want to execute a script/action when port comes up? maybe udev can handle it?

Well, I found this: https://www.howtoforge.com/port-triggering-using-a-nat-firestarter-firewall-and-specter-in-debian-ubuntu, which uses programs called firestarter (GUI front-end to iptables, not needed) and specter (available in Debian 5, not in Debian 10 or 11…). Specter only works with the ULOG target, not NFLOG, and I’m guessing it would be involved to update the code… (Not in anyway ideal!)

But the idea is to open specific port(s) when a port or a port range is accessed. Using iptables exclusively to do this would be ideal without an additional script… I have the supporting software I need installed to write the script, but I was hoping that I wouldn’t have to…

sounds like some kind of portknocking

I’m familiar with port knocking, and I kinda agree. But the difference is that the the port or port range gets directed to the IP address that makes the call to that port(s), not to a specified IP address. It also should last a specific amount of time after the last time that port was accessed…

I believe the “specific amount of time” can be dealt with in iptables. But I haven’t seen support for a trigger action (aka -j TRIGGER) that a very few and far between articles talk about.

ok, so more similar to upnp?

iptables is dead, you should search a way for nftables

Quite similar to UPnP, except that the port(s) and port range(s) are configured on the router.

I’m trying to resist that particular change :stuck_out_tongue: Seriously, why is iptables dead?

at least on debian (afaik buster too) nftables is used as backend for iptables-command and modern functions (like hw-nat) are only working with nft

Sigh… I guess I get to write another script, then… And see about converting to nftables…

Have you found any way for port triggering?

I currently search for port-knocking in nftables,there are some examples in nftables wiki,but have not completely understood them to integrate in my firewall

Sadly, I haven’t even begun to write the script yet… Been trying to get elements of my router Web UI working… Too many elements to write…

You can convert applied iptables into nftables to have a base. This is the way i had it done. Firat convert,the aggregate (same rules for different interfaces) and then some optimization (restructure in own chains as it is easier in nftables to read/define)

https://wiki.nftables.org/wiki-nftables/index.php/Moving_from_iptables_to_nftables

The only thing that was tricky: delete/add rules by script at the right position (needed this for letsencrypt cert update - opening and closing http-port and for optional logging)

Okay, I finally started on the migration from iptables to nftables. Here is my nftables.conf so far:

# List a particular chain in a nftables table:  nft list chain inet global forward

#############################################################################
# Define WAN (Wide Area Network) and LAN (Local Area Network) interfaces:
# NOTE: WAN interfaces face the world.  LAN are ethernet & wireless.
#############################################################################
define DEV_LAN = { br0 }
define DEV_WAN = { wan }
define DEV_NO_NET = { no_net }

#############################################################################
# Clear out the ruleset because we need to start fresh!
#############################################################################
flush ruleset

#############################################################################
# Define rules that affect both IPv4 and IPv6:
#############################################################################
table inet global {
	#############################################################################
	chain prerouting_ports {
	}

	#############################################################################
	chain prerouting {
		# Default Post-Routing policy is "ACCEPT":
		type filter hook prerouting priority mangle; policy accept;
		
		# Drop invalid packets:
		ct state invalid drop

		# This blocks all packets that are new (don’t belong to an established connection) and don’t use the SYN flag.
		tcp flags & (fin|syn|rst|ack) != syn ct state new drop
		
		# Block Packets With Bogus TCP Flags:
		tcp flags & (fin|syn) == fin|syn drop
		tcp flags & (syn|rst) == syn|rst drop
		tcp flags & (fin|rst) == fin|rst drop
		tcp flags & (fin|ack) == fin drop
		tcp flags & (ack|urg) == urg drop
		tcp flags & (psh|ack) == psh drop
		tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 drop

		# Block Packets From Private Subnets (Spoofing):
		iifname $DEV_WAN ip saddr { 0.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.2.0/24, 192.168.0.0/16, 224.0.0.0/5, 240.0.0.0/5 } drop
		iifname != "lo" ip saddr 127.0.0.0/8 drop

		# Block fragmented packets:
		ip frag-off & 0x1fff != 0 counter drop

		# Jump to chain "prerouting_ports" for port forwarding rules:
		jump prerouting_ports
	}

	#############################################################################
	chain inbound_wan {
	}

	#############################################################################
	chain inbound_lan {
	}

	#############################################################################
	chain inbound {
		# Default Inbound policy is "DROP":
		type filter hook input priority 100; policy drop;

		# Block Flooding of RST packets, SMURF attack Rejection
		meta l4proto icmp limit rate 2/second burst 2 packets accept

		# Allow traffic from established and related packets, drop invalid
		ct state vmap { established : accept, related : accept, invalid : drop }

		# Redirect incoming port 67 to port 68:
		meta l4proto udp udp sport 67 udp dport 68 accept

		# Allow all loopback traffic:
		iifname lo accept

		# Jump to "inbound_wan" chain for our WAN interfaces:
		iifname $DEV_WAN jump inbound_wan

		# Jump to "inbound_lan" chain for our LAN interfaces:
		iifname $DEV_LAN jump inbound_lan
	}

	#############################################################################
	chain forward_ports {
	}

	#############################################################################
	chain forward {
		# Default Forwarding policy is "DROP":
		type filter hook forward priority 100; policy drop;

		# Allow traffic from established and related packets, drop invalid
		ct state vmap { established : accept, related : accept, invalid : drop }

		# Jump to chain "forward_ports" for port forwarding rules:
		jump forward_ports

		# Drop all connections to WAN interfaces from any interfaces with "no_internet" flag set:
		iifname $DEV_NO_NET oifname $DEV_WAN drop

		# Forward connections from the LAN interfaces to WAN interfaces:
		iifname $DEV_LAN oifname $DEV_WAN accept

		# Forward connections from the LAN interfaces to LAN interfaces:
		iifname $DEV_LAN oifname $DEV_LAN accept
	}

	######################################################
	chain postrouting {
		# Default Post-Routing policy is "ACCEPT":
		type nat hook postrouting priority 100; policy accept;

		# Masquerade everything going out on our WAN interfaces:
		oifname $DEV_WAN masquerade
	}

	#############################################################################
	chain outbound_vpn {
		# Block from accessing anything but the "lo" interface:
		oifname != "lo" drop
	}

	#############################################################################
	chain outbound_wan {
	}

	#############################################################################
	chain outbound {
		# Default Output policy is "ACCEPT":
		type filter hook output priority 100; policy accept;

		# User "vpn" must jump to the "outbound_vpn" chain now:
		meta skuid "vpn" jump outbound_vpn

		# All WAN interfaces must jump to the "outbound_wan" chain now:
		oifname $DEV_WAN jump outbound_wan
	}
}

Um, what do you think? br0 connects lan0, lan1, lan2, and lan3… User “vpn” is intended to be able to connect to the internet ONLY while a VPN is running…

Anyways, I’m going to bed now… I’ll look at any responses in the morning…

I have not done userspecific configuration yet,but if it works as expected you’re on a good way.

If you have no connection:

Imho the vpn-service itself is not yet allowed to send traffic to wan…you block all except with target lo-interface,so also wan for user vpn (is vpn server running as user vpn?). Have not complete understood what you’re trying to archieve

I’ve done a test run on my main computer and the ruleset installs just fine. Haven’t tried installing the ruleset on the router yet…

User “vpn” runs any software that is best run with a VPN running. The default ruleset denies all internet access for user “vpn”. When OpenVPN service is started, a script (specified in the vpn configuration file) is run in order to create a split tunnel VPN. That chain will be updated so that user “vpn” will be able to connect to the internet, but ONLY through the VPN… The way the ruleset is written allows for the possibility of specific IPs to run ONLY through the VPN, as well…

I’m also trying for a simple method for port forwarding using maps… Many experiments under way :stuck_out_tongue:

I had to make a few modifications because I got locked out using the ruleset I showed earlier. Here is the working ruleset for my router:

# List a chain in the table:     nft list chain inet global <chain_name>
# List the elements in a map:    nft list map inet global <map_name>
# Add an element to the map:     nft add element inet global port_forward { 80 : 192.168.1.1 . 80 }
# Remove an element to the map:  nft delete element inet global port_forward { 80 : 192.168.1.1 . 80 }
 
#############################################################################
# Define WAN (Wide Area Network) and LAN (Local Area Network) interfaces:
# NOTE: WAN interfaces face the world.  LAN are ethernet & wireless.
#############################################################################
define DEV_LAN = { br0 }
define DEV_WAN = { wan }
define DEV_NO_NET = { no_net }

#############################################################################
# Clear out the ruleset because we need to start fresh!
#############################################################################
flush ruleset

#############################################################################
# Define rules that affect both IPv4 and IPv6:
#############################################################################
table inet global {
	#############################################################################
	map port_forward {
		type inet_service : ipv4_addr . inet_service;
		# elements = { 80 : 192.168.2.1 . 80 }
	}

	#############################################################################
	set wan_addr_restriction {
		typeof ip saddr; flags interval;
		elements = { 0.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.2.0/24, 192.168.0.0/16, 224.0.0.0/5, 240.0.0.0/5 }
	}

	#############################################################################
	chain nat {
		type nat hook prerouting priority dstnat; # =-100
		
		# Port forwarding entries in the "port_forward" map: 
		dnat ip addr . port to tcp dport map @port_forward;
	} 

	#############################################################################
	chain prerouting_private {
	}

	#############################################################################
	chain prerouting {
		# Default Post-Routing policy is "ACCEPT":
		type filter hook prerouting priority mangle; policy accept;
		
		# Drop invalid packets:
		ct state invalid drop

		# This blocks all packets that are new (don’t belong to an established connection) and don’t use the SYN flag.
		tcp flags & (fin|syn|rst|ack) != syn ct state new drop
		
		# Block Packets With Bogus TCP Flags:
		tcp flags & (fin|syn) == fin|syn drop
		tcp flags & (syn|rst) == syn|rst drop
		tcp flags & (fin|rst) == fin|rst drop
		tcp flags & (fin|ack) == fin drop
		tcp flags & (ack|urg) == urg drop
		tcp flags & (psh|ack) == psh drop
		tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 drop

		# WAN Interfaces must jump to "wan_addr_restriction" chain for ip addresses in "wan_addr_restriction" set:  
		iifname $DEV_WAN ip saddr @wan_addr_restriction jump prerouting_private

		# Block Packets From Private Subnets (Spoofing):
		iifname $DEV_WAN ip saddr @wan_addr_restriction drop
		iifname != "lo" ip saddr 127.0.0.0/8 drop

		# Block fragmented packets:
		ip frag-off & 0x1fff != 0 counter drop
	}

	#############################################################################
	chain inbound_wan {
	}

	#############################################################################
	chain inbound {
		# Default Inbound policy is "DROP":
		type filter hook input priority 100; policy drop;

		# Block Flooding of RST packets, SMURF attack Rejection
		meta l4proto icmp limit rate 2/second burst 2 packets accept

		# Allow traffic from established and related packets, drop invalid
		ct state vmap { established : accept, related : accept, invalid : drop }

		# Redirect incoming port 67 to port 68:
		meta l4proto udp udp sport 67 udp dport 68 accept

		# Jump to "inbound_wan" chain for our WAN interfaces:
		iifname $DEV_WAN jump inbound_wan

		# Allow traffic from loopback and LAN interfaces:
		iifname { lo, $DEV_LAN } accept
	}

	#############################################################################
	chain forward_to_wan {
	}

	#############################################################################
	chain forward {
		# Default Forwarding policy is "DROP":
		type filter hook forward priority 100; policy drop;

		# Automatically accept all our DNATed packets:
		ct status dnat accept

		# Allow traffic from established and related packets, drop invalid:
		ct state vmap { established : accept, related : accept, invalid : drop }

		# Drop all connections to WAN interfaces from any interfaces with "no_internet" flag set:
		iifname $DEV_NO_NET oifname $DEV_WAN drop

		# LAN to WAN interfaces must jump to "forward_to_wan" chain now:
		iifname $DEV_LAN oifname $DEV_WAN jump forward_to_wan

		# Forward connections from the LAN interfaces to WAN interfaces:
		iifname $DEV_LAN oifname $DEV_WAN accept
		
		# Forward connections from the LAN interfaces to LAN interfaces:
		iifname $DEV_LAN oifname $DEV_LAN accept
	}

	######################################################
	chain postrouting {
		# Default Post-Routing policy is "ACCEPT":
		type nat hook postrouting priority 100; policy accept;

		# Masquerade everything going out on our WAN interfaces:
		oifname $DEV_WAN masquerade

		# Forward ports specified in "port_forward" map: 
		#snat to ip saddr map @port_forward
	}

	#############################################################################
	chain outbound_vpn {
		# Block from accessing anything but the "lo" interface:
		oifname != "lo" drop
	}

	#############################################################################
	chain outbound_wan {
	}

	#############################################################################
	chain outbound {
		# Default Output policy is "ACCEPT":
		type filter hook output priority 100; policy accept;

		# User "vpn" must jump to the "outbound_vpn" chain now:
		meta skuid "vpn" jump outbound_vpn

		# All WAN interfaces must jump to the "outbound_wan" chain now:
		oifname $DEV_WAN jump outbound_wan
	}
}

I have a script that makes small changes to the ruleset to add my wireless interfaces as necessary, so it’s a dynamic as I can make it at the moment…

Here is my final ruleset for my router. As far as I can tell, it correctly supports port forwarding and accepts specified ports to the server. WAN and LAN Interfaces are specified in their respective sets. Support for a captive portal exists, but hasn’t been thoroughly tested.

The port triggering section (which is based on Hazel’s Zone Port Triggering with Linux nftables) works correctly, as well. However, it only directs the port specified to the IP address that made the initial request. The port is held open to the WAN for a period of 10 minutes from last access from the IP address. Additional ports can be added in the rules per TCP/UDP port, but such is not handled by this ruleset.

Additionally, DoT (DNS-over-TLC) and DoQ (DNS-over-QUIC) are blocked from the local networks, but not from the router itself.

#############################################################################
# Define rules that affect both IPv4 and IPv6:
#############################################################################
#flush table inet firewall
table inet firewall {

################################################[ MAPS & SETS ]#################################################
	# List of WAN interfaces:
	set DEV_WAN { type ifname; elements = { wan } }

	# List of interfaces denied access to WAN interfaces:
	set DEV_WAN_DENY { type ifname; }

	# List of LAN interfaces:
	set DEV_LAN { type ifname; elements = { br0, mt7615_24g, mt7615_5g } }

	# List of interfaces denied access to WAN interfaces from LAN:
	set DEV_LAN_DENY { type ifname; }

	############################[ Captive Portal Info ]#############################
	# List of interfaces that have a Captive Portal on them:
	set DEV_PORTAL { type ifname; }

	# List of MAC addresses that passed Captive Portal:
	set PORTAL_PASS { type ether_addr; }

	##########################[ Accept TCP/UDP Port Sets ]##########################
	set ACCEPT_PORT_TCP { type inet_service; flags interval; }
	set ACCEPT_PORT_UDP { type inet_service; flags interval; }

	############################[ Port Triggering Sets ]############################
	set TRIGGER_LIST_TCP { type inet_service; flags interval; }
	set TRIGGER_LIST_UDP { type inet_service; flags interval; }

	###########################[ Port Forwarding Stuff ]############################
	map FORWARD_PORT_TCP { type inet_service : ipv4_addr . inet_service; }
	map FORWARD_PORT_UDP { type inet_service : ipv4_addr . inet_service; }
	map FORWARD_RANGE_TCP { type inet_service : ipv4_addr; flags interval; }
	map FORWARD_RANGE_UDP { type inet_service : ipv4_addr; flags interval; }

############################################[ DO NOT EDIT THESE ]#############################################
	set PRIVATE_SUBNETS { 
		type ipv4_addr; flags interval;
		elements = { 0.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.2.0/24, 192.168.0.0/16, 224.0.0.0/5, 240.0.0.0/5 } 
	}
	set INSIDE_NETWORK {
		type ipv4_addr; flags interval; 
		elements = { 192.168.2.0/24, 192.168.21.0/24, 192.168.22.0/24 }
	}
	set TRIGGER_OPEN_TCP { type inet_service; flags timeout; }
	map TRIGGER_PORT_TCP { type inet_service : ipv4_addr; }
	set TRIGGER_OPEN_UDP { type inet_service; flags timeout; }
	map TRIGGER_PORT_UDP { type inet_service : ipv4_addr; }

##############################################[ Filter: INPUT ]###############################################
	chain INPUT {
		type filter hook input priority filter + 100; policy drop;

		# Allow traffic from established and related packets, drop invalid
		ct state vmap { established : accept, related : accept, invalid : drop }

		# Redirect incoming port 67 to port 68:
		meta l4proto udp udp sport 67 udp dport 68 accept

		# Accept anything that has been DNAT'ed:
		ct status dnat accept

		# Accept all connections from "lo" interface:
		iifname lo accept

		# Jump to "INPUT_WAN" chain for our WAN interfaces:
		iifname @DEV_WAN jump INPUT_WAN

		# Drop any remaining packets from our WAN interfaces:
		iifname @DEV_WAN drop

		# Jump to "INPUT_LAN" chain for our LAN interfaces:
		iifname @DEV_LAN jump INPUT_LAN

		# Allow traffic from loopback and LAN interfaces:
		iifname @DEV_LAN accept
	}

	#############################################################################
	chain INPUT_WAN {
		# Allow multicast packets inbound from the Internet:
		# NOTE: Commented out by nftables-script.sh if option "allow_multicast" is "N".
#		pkttype multicast accept

		# Control port 113 (IDENT) access from the Internet:
		# NOTE: Commented out by nftables-script.sh if option "drop_ident" is "N".
#		tcp dport 113 accept

		# Accept any ports listed in "ACCEPT_PORT_TCP" and "ACCEPT_PORT_UDP" sets:
		tcp dport @ACCEPT_PORT_TCP accept
		udp dport @ACCEPT_PORT_UDP accept
	}

	#############################################################################
	chain INPUT_LAN {
	}

#############################################[ Filter: FORWARD ]##############################################
	chain FORWARD {
		type filter hook forward priority filter + 100; policy drop;

		# Allow traffic from established and related packets, drop invalid:
		ct state vmap { established : accept, related : accept, invalid : drop }

		# Accept anything that has been DNAT'ed:
		ct status dnat accept

		# All portal interfaces must jump to "FORWARD_PORTAL" chain now:
		iifname @DEV_PORTAL jump FORWARD_PORTAL

		# LAN to WAN communication must jump to "FORWARD_WAN" chain now:
		iifname @DEV_LAN oifname @DEV_WAN jump FORWARD_WAN

		# Reject communication from WAN-restricted interfaces to WAN interfaces:
		iifname @DEV_WAN_DENY oifname @DEV_WAN reject

		# Forward connections from the LAN interfaces to WAN interfaces:
		iifname @DEV_LAN oifname @DEV_WAN accept

		# LAN to LAN communication must jump to "FORWARD_LAN" chain now:
		iifname @DEV_LAN oifname @DEV_LAN jump FORWARD_LAN

		# Reject communication from LAN-restricted interfaces to LAN interfaces:
		iifname @DEV_LAN_DENY oifname @DEV_LAN reject

		# Forward connections from the LAN interfaces to LAN interfaces:
		iifname @DEV_LAN oifname @DEV_LAN accept
	}

	#############################################################################
	chain FORWARD_PORTAL {
		# Accept any packet that has the "Pass" mark:
		# NOTE: "0x50617373" is "Pass" converted to hexadecimal! :p  ==> CMD: "printf Pass | xxd -p" <==
		mark 0x50617373 return

		# Accept any packets directed to the Captive Portal WebUI:
		ip daddr 192.168.2.1 tcp dport 80 accept

		# Accept all DNS and DHCP communication:
		meta l4proto {tcp, udp} @th,16,16 { 53, 67 } accept

		# Reject all other communication:
		reject
	}

	#############################################################################
	chain FORWARD_WAN {
		# Reject all DoT (DNS-over-TLS) packets from LAN interfaces:
		# NOTE: Commented out by nftables-script.sh if option "allow_dot" is "N".
		meta l4proto {tcp, udp} @th,16,16 853 reject

		# Reject all DoQ (DNS-over-QUIC) packets from LAN interfaces:
		# NOTE: Commented out by nftables-script.sh if option "allow_doq" is "N".
		meta l4proto {tcp, udp} @th,16,16 8853 reject
	}

	#############################################################################
	chain FORWARD_LAN {
	}

##############################################[ Filter: OUTPUT ]##############################################
	chain OUTPUT {
		type filter hook output priority filter + 100; policy accept;

		# User "vpn" must jump to the "OUTPUT_VPN" chain now:
		meta skuid "vpn" jump OUTPUT_VPN

		# All WAN interfaces must jump to the "OUTPUT_WAN" chain now:
		oifname @DEV_WAN jump OUTPUT_WAN

		# All LAN interfaces must jump to the "OUTPUT_LAN" chain now:
		oifname @DEV_LAN jump OUTPUT_LAN
	}

	#############################################################################
	chain OUTPUT_VPN {
		# Default rule is to block from accessing anything but the "lo" interface.
		# This rule will be replaced by the OpenVPN configuration script when connecting.
		oifname != "lo" drop
	}

	#############################################################################
	chain OUTPUT_WAN {
		# Reject multicast packets outbound to the Internet.
		# NOTE: Commented out by nftables-script.sh if option "allow_multicast" is "Y".
		pkttype multicast reject
	}

	#############################################################################
	chain OUTPUT_LAN {
	}

##############################################[ Mangle: OUTPUT ]##############################################
	chain MANGLE_OUTPUT {
		type route hook output priority mangle + 100; policy accept;

		# All packets must jump to "OUTPUT_VPN" chain now:
		jump MANGLE_OUTPUT_VPN
	}

	#############################################################################
	chain MANGLE_OUTPUT_VPN {
	}

############################################[ Mangle: PREROUTING ]############################################
	chain MANGLE_PREROUTING {
		type filter hook prerouting priority mangle; policy accept;
		
		# Jump to DDOS protection chain now:
		jump MANGLE_PREROUTING_DDOS

		# Mark any packets from MAC addresses that passed the Captive Portal with "Pass" flag.
		# NOTE: "0x50617373" is "Pass" converted to hexadecimal! :p  ==> CMD: "printf Pass | xxd -p" <==
		iifname @DEV_PORTAL ether saddr @PORTAL_PASS counter meta mark set 0x50617373
	}

	#############################################################################
	chain MANGLE_PREROUTING_DDOS {
		# Drop packets that use bogus TCP flags:
		tcp flags & (fin|syn) == fin|syn counter drop comment "Bogus TCP flags"
		tcp flags & (syn|rst) == syn|rst counter drop comment "Bogus TCP flags"
		tcp flags & (fin|rst) == fin|rst counter drop comment "Bogus TCP flags"
		tcp flags & (fin|ack) == fin counter drop comment "Bogus TCP flags"
		tcp flags & (ack|urg) == urg counter drop comment "Bogus TCP flags"
		tcp flags & (psh|ack) == psh counter drop comment "Bogus TCP flags"

		# Drop Null packets:
		tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop comment "Null TCP flags"

		# Drop new packets that don't use the SYN flag:
		tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop comment "New Packets without SYN flag"

		# Drop XMAS packets:
		tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg counter drop comment "XMAS Packets"

		# Block fragmented packets:
		ip frag-off & 0x1fff != 0 counter drop comment "Fragmented packets"

		# Block Packets from spoofing as the "lo" interface:
		iifname != "lo" ip saddr 127.0.0.0/8 counter drop comment "Spoofed IP address"

		# Block Packets From Private Subnets from WAN interfaces.  WAN interfaces should insert an
		# "return" on their IP address/range into the "prerouting_private" chain in order to
		# keep double-nat configurations working as expected:
		iifname @DEV_WAN ip saddr @PRIVATE_SUBNETS jump MANGLE_PREROUTING_PRIVATE
	}

	#############################################################################
	chain MANGLE_PREROUTING_PRIVATE {
		# WAN interfaces should insert an "return" on their IP address/range into the
		# "MANGLE_PREROUTING_PRIVATE" chain in order to keep double-nat configurations working as expected:
		counter drop comment "Private IP subnet from WAN"
	}

#############################################[ NAT: PREROUTING ]##############################################
	chain NAT_PREROUTING {
		type nat hook prerouting priority dstnat + 100; policy accept;

		# All Captive Portal interfaces must jump to "PREROUTING_PORTAL" chain now:
		iifname @DEV_PORTAL jump NAT_PREROUTING_PORTAL

		# WAN interfaces must jump to "prerouting_wan" chain now:
		iifname @DEV_WAN jump NAT_PREROUTING_WAN

		# LAN interfaces must jump to "prerouting_lan" chain now:
		iifname @DEV_LAN jump NAT_PREROUTING_LAN
	}

	#############################################################################
	chain NAT_PREROUTING_PORTAL {
		# Accept packets directed to the Captive Portal WebUI:
		ip daddr 192.168.2.1 accept

		# Accept any packet that has the "Pass" mark:
		# NOTE: "0x50617373" is "Pass" converted to hexadecimal! :p  ==> CMD: "printf Pass | xxd -p" <==
		mark 0x50617373 accept

		#  Redirect any HTTP requests to the Captive Portal WebUI:
		tcp dport 80 dnat ip to 192.168.2.1
	}

	#############################################################################
	chain NAT_PREROUTING_WAN {
		# Remove outbound port from "trigger_port" set if timeout has expired:
		tcp dport != @TRIGGER_OPEN_TCP delete @TRIGGER_PORT_TCP { tcp dport : 0.0.0.0 }
		udp dport != @TRIGGER_OPEN_UDP delete @TRIGGER_PORT_UDP { udp dport : 0.0.0.0 }

		# Forward each port in "forward_port" map to it's respective IP address/port combo:
		dnat ip addr . port to tcp dport map @FORWARD_PORT_TCP
		dnat ip addr . port to udp dport map @FORWARD_PORT_UDP

		# Forward range of ports in "trigger_port" map to their respective IP addresses:
		dnat ip to tcp dport map @FORWARD_RANGE_TCP
		dnat ip to udp dport map @FORWARD_RANGE_UDP

		# Forward each port in "trigger_port" map to it's respective IP address --ONLY-- if the timeout hasn't expired:
		tcp dport @TRIGGER_OPEN_TCP dnat ip to tcp dport map @TRIGGER_PORT_TCP
		udp dport @TRIGGER_OPEN_UDP dnat ip to udp dport map @TRIGGER_PORT_UDP
	}

	#############################################################################
	chain NAT_PREROUTING_LAN {
	}

#############################################[ NAT: POSTROUTING ]#############################################
	chain NAT_POSTROUTING {
		type nat hook postrouting priority srcnat + 100; policy accept;

		# All LAN interfaces must jump to the "NAT_POSTROUTING_LAN" chain:
		oifname @DEV_LAN jump NAT_POSTROUTING_LAN

		# All WAN interfaces must jump to the "NAT_POSTROUTING_WAN" chain:
		oifname @DEV_WAN jump NAT_POSTROUTING_WAN
	}

	#############################################################################
	chain NAT_POSTROUTING_LAN {
	}

	#############################################################################
	chain NAT_POSTROUTING_WAN {
		# If source IP address is from inside the network AND the destination port is in
		# the port triggering list, jump to the appropriate chain:
		ip saddr @INSIDE_NETWORK tcp dport @TRIGGER_LIST_TCP jump NAT_POSTROUTING_TRIGGER
		ip saddr @INSIDE_NETWORK udp dport @TRIGGER_LIST_UDP jump NAT_POSTROUTING_TRIGGER

		# Masquerade everything going out on our WAN interfaces:
		masquerade
	}

	#############################################################################
	chain NAT_POSTROUTING_TRIGGER {
		# Remove outbound port from "trigger_port" set if timeout has expired:
		tcp dport != @TRIGGER_OPEN_TCP delete @TRIGGER_PORT_TCP { tcp dport : 0.0.0.0 }
		udp dport != @TRIGGER_OPEN_UDP delete @TRIGGER_PORT_UDP { udp dport : 0.0.0.0 }

		# Link the outbound port to the source IP address:
		add @TRIGGER_PORT_TCP { tcp dport : ip saddr }
		add @TRIGGER_PORT_UDP { udp dport : ip saddr }

		# Update the timeout for the protocol/port combination:
		update @TRIGGER_OPEN_TCP { tcp dport timeout 10m }
		update @TRIGGER_OPEN_UDP { udp dport timeout 10m }
	}
}
1 Like