Now I know port knocking isn’t too clever; a VPN would be better. But on my phone I use blockada, which acts as a VPN to block ads. OpenVPN or Tailscale can’t run at the same time as blockada. But I could portknock and allow the phone to access the protected resource and have blockada at the same time. I’m aware of the risks
So the question is “how can I do this?” I have a rocky9 server acting as my router and I have a bunch of nftables rules that I worked out with help from this forum ( Building a home router · Ramblings of a Unix Geek ) .
What I would like to do is have a port knocking sequence that would then allow the router to forward port 12345 to 10.0.0.2:993 (for example). I have maybe 4 of these ports I want to protect this way.
I see a number of “how to use nftables for port knocking” pages ( eg Port knocking example - nftables wiki ) but these all seem to be to allow access to ports on the server (eg the router).
But I haven’t seen how to do this for the “ip nat” chain.
My rules are a bit more complicated than that. In particular the simple rules don’t allow for “reflection” (ie an internal host talking to the WAN IP address). And I use a map of destinations.
Something like
add map nat fport { type inet_service : interval ipv4_addr . inet_service ; flags interval; }
add set ip nat reflect { type inet_service; }
...
add element nat fport { 11111 : 10.0.0.2 . 22 } # ssh
...
add element nat reflect { ..., 11111, ... }
...
# Allow marked traffic (ie reflected traffic) to reach the destination
add rule ip filter FORWARD meta mark 100 counter accept
# Tag internal reflection traffic
add rule ip nat PREROUTING ip saddr 10.0.0.0/8 ip daddr @this_host tcp dport @reflect mark set 100
# DNAT incoming traffic from the internet (or internal sent to br-wan address)
add rule ip nat PREROUTING ip daddr @this_host ip protocol tcp dnat ip addr . port to tcp dport map @fport
# Do NAT on egress traffic to the internet
add rule ip nat POSTROUTING oifname "br-wan" counter masquerade
# Also NAT marked traffic from internal to external IP
add rule ip nat POSTROUTING meta mark 100 counter masquerade
Working out those reflection rules was the hard part.
These rules allow access to ports I always want available (eg http, https, ssh). I’m now wanting a second set of ports that are only exposed when port-knocked. I’m guessing I’m gonna need another map “hidden_ports” (or something) for mapping 12345 to 10.0.0.2:993.
But that’s where I’m not sure how to add that additional condition. Would it be as simple as
add rule ip nat PREROUTING ip daddr @this_host ip saddr @clients_ipv4 ip protocol tcp dnat ip addr . port to tcp dport map @hidden_ports
ie just adding ip saddr @clients_ipv4 to a copy of the existing “fport” rule (modified for “hidden_ports”) ?
Yeah, I had seen that. It’s pretty much the same as before; it’s on the “input” chain, so would be for services running on the device implementing the port knocking. I’m wanting to do this for the “nat” chain.
Here they suggest using the iptables to nft converter.
working version with iptables dnat knocking to nft . Nftables port knocking dnat
Try converting and see what nft rules it will convert to.
Yeah, I’ve seen that as well. And I’ve used the translate before to get an idea of how to do things (and then ignored it :-)). But in this case the answer to the question doesn’t even work.
For example, in the source there was a 3389, and we can see that in the saved output, but the translated version has it commented out because it doesn’t know what to do with it
So because I have two tables; one for standard filters and one for NAT, I had to put the port-knocking rules into the “nat” table INPUT rules.
So, for example
add map nat hidden_ports { type inet_service : interval ipv4_addr . inet_service ; flags interval; }
# For port knocking
add set ip nat knocked { type ipv4_addr; flags timeout; timeout 3600s;}
add set ip nat knockers { type ipv4_addr . inet_service ; flags timeout; timeout 1s; }
add element nat hidden_ports { 12345 : 10.0.0.2 . 993 }
# Handle port knocking
add rule ip nat INPUT tcp dport 123 add @knockers {ip saddr . 234 }
add rule ip nat INPUT tcp dport 234 ip saddr . tcp dport @knockers add @knockers {ip saddr . 345 }
add rule ip nat INPUT tcp dport 345 ip saddr . tcp dport @knockers add @knockers {ip saddr . 456 }
add rule ip nat INPUT tcp dport 456 ip saddr . tcp dport @knockers add @knocked {ip saddr }
add rule ip nat PREROUTING ip daddr @this_host ip saddr @knocked ip protocol tcp dnat ip addr . port to tcp dport map @hidden_ports
Obviously I’d change the port sequence to something different. In this example we get 1 second between each port knock, and if it succeeds then the port is opened for 1 hour. Once I’d worked out that table name issues, the PREROUTING rule was as simple as adding the extra ip saddr requirement. I’ve tested this version on a test machine (not my live router) but I think it should work!
Not sure why I really need two tables; maybe I can just merge them into one, which would make things a little simpler. Hmm.