Thursday, July 19, 2012

Port knocking with iptables (Tomato firmware)

I first learned about port knocking many years ago and found it to be a great way to secure my network. Adding port knocking allows you to "ping" your IP address on a sequence of ports which then open up certain ports (rdp, ssh, ftp, router gui, etc.) to only the IP which correctly sent the knock sequence.

Router: Asus RT-N16 (newegg)
Firmware: Tomato-Toastman (version: tomato-K26USB-1.28.7500.2MIPSR2Toastman-RT-VPN.trx)
Note: TomatoUSB or any mod running kernel 2.6 should work. Iptables in kernel 2.4 requires some tweaks (see below).

Just get it working
  1. In the Tomato interface Port Forwarding -> Basic, disable any port forwards you will want to knock into. Save the settings.
  2. In the Tomato interface Administratin -> Admin access, set
    • Local Access = HTTPS
    • HTTPS Port = 443
    • Allow wireless access = unchecked
    • SSH Daemon
      • Enable at Startup = checked
      • Remote access  = unchecked
      • Remote Forwarding = checked
      • Port = 1234 (must use something other than default 22)
    • Telnet Daemon = Disable and stop the service (telnet is not secure)
    •  Save the settings.
  3. In the Tomato interface, Administratin -> Scripts -> Firewall Tab  paste the two parts of the below script.
  4. Reboot the router.
  5. Use putty (windows) or your favorite ssh client (android) from a computer outside your network to knock the port sequence (in this example 45050, 45025, 45075). 

    ssh -p 45050 <your DDNS/IP>
    ssh -p 45025 <your DDNS/IP>
    ssh -p 45075 <your DDNS/IP>

    Note: You can even use a web browser, RDP client, or anything that can specify a port. I usually wait about 5-10 seconds between each ping.
  6. Then try and get to router interface (assuming it is at using a browser. Pay close attention to use "https" (since we set that in step 2). There is a 25 second timeout between knocks or you must start over.

    https://<your DDNS/IP>
Part 1
These lines are the knock sequence. The highlighted lines are recommended to change. Line 54 are the ports before and after the knock ports to try and stop port scanners. I recommend a knock sequence that goes forward, backward, forward since scanners typically scan forward.

# Load iptables modules
modprobe xt_recent

# Knock chains
iptables -t nat -N knock2 2>/dev/null
iptables -t nat -F knock2
iptables -t nat -A knock2 -m recent --name knock1 --remove
iptables -t nat -A knock2 -m recent --name knock2 --set
iptables -t nat -A knock2 -j LOG --log-level info --log-prefix "IN KNOCK2: "

iptables -t nat -N knock3 2>/dev/null
iptables -t nat -F knock3
iptables -t nat -A knock3 -m recent --name knock2 --remove
iptables -t nat -A knock3 -m recent --name knock3 --set
iptables -t nat -A knock3 -j LOG --log-level info --log-prefix "IN KNOCK3: "

iptables -t nat -N knock_deny 2>/dev/null
iptables -t nat -F knock_deny
iptables -t nat -A knock_deny -m recent --name knock1 --remove
iptables -t nat -A knock_deny -m recent --name knock2 --remove
iptables -t nat -A knock_deny -m recent --name knock3 --remove
iptables -t nat -A knock_deny -j LOG --log-level warn --log-prefix "KNOCK DENIED: "

iptables -t nat -N knock_scanned 2>/dev/null
iptables -t nat -F knock_scanned
iptables -t nat -A knock_scanned -m recent --rcheck --name knock1 \
 --seconds $KNOCK_STEP_TIMEOUT_SEC -j knock_deny
iptables -t nat -A knock_scanned -m recent --rcheck --name knock2 \
 --seconds $KNOCK_STEP_TIMEOUT_SEC -j knock_deny
iptables -t nat -A knock_scanned -m recent --rcheck --name knock3 \
 --seconds $KNOCK_STEP_TIMEOUT_SEC -j knock_deny

# 1st knock: 45050
iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp --dport 45050 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --set --name knock1

# 2nd knock: 45025
iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp --dport 45025 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --rcheck --name knock1 --seconds $KNOCK_STEP_TIMEOUT_SEC -j knock2

# 3rd knock: 45075
iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp --dport 45075 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --rcheck --name knock2 --seconds $KNOCK_STEP_TIMEOUT_SEC -j knock3

# To stop port-scans from randomly finding the sequence
iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp \
 -m multiport --destination-port 45049,45051,45024,45026,45074,45076 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN -j knock_scanned

Part 2
These lines are the ports you want forward after "knock 3". The highlighted lines will need to be changed if you change the port/IP address of the computer that you are forwarding to.

# Port forwards after knock

iptables -A wanin -i $KNOCK_INTERFACE -p tcp --dport 3389 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --rcheck --seconds $KNOCK_STEP_TIMEOUT_SEC --name knock3 \

iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp --dport 3389 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --rcheck --seconds $KNOCK_STEP_TIMEOUT_SEC --name knock3 \
 -j DNAT --to-destination

iptables -A wanin -i $KNOCK_INTERFACE -p tcp --dport 21 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --update --seconds $KNOCK_WEB_ADMIN_TIMEOUT_SEC --name knock3 \

iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp --dport 21 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --update --seconds $KNOCK_WEB_ADMIN_TIMEOUT_SEC --name knock3 \
 -j DNAT --to-destination

# Ports open from WAN to this router after knock

# Router Admin GUI (updates "recent")
iptables -A INPUT -d -p tcp --dport 443 \

iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp --dport 443 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --update --seconds $KNOCK_WEB_ADMIN_TIMEOUT_SEC --name knock3 \
 -j DNAT --to-destination

iptables -A INPUT -d -p tcp --dport 1234 \

iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp --dport 1234 \
 -m state --state NEW --tcp-flags SYN,RST,ACK SYN \
 -m recent --rcheck --seconds $KNOCK_STEP_TIMEOUT_SEC --name knock3 \
 -j DNAT --to-destination>

If you have trouble getting this to work, here are a few things to try...
  • You should see "KNOCK" somewhere in the syslog.
  • In Tomato interface  Administratin -> Logging -> Connection Logging. Make sure Inbound and Outbound drop downs are set to "Disabled". You can turn these on and ping the port sequence to see in the log that the router is accepting/dropping the ports and on what interface. The script is set to "vlan2". The port forwards won't work if these drop downs are enabled.
  • In Tomato interface  Administratin -> Debugging, set Console log level to 8 to get as much information in the log.
  • In Tomato interface  Administratin -> Debugging, dump the iptables and see if you can see the lines for the ports you are knocking.
  • Try the other ports that are forwarded. My FTP, RDP, etc. all worked except ssh until I tired a non-standard port.
Vague note for kernel 2.4: My old router (Asus 500GP v2) used TomatoVPN which used kernel 2.4. This script worked except for the following changes.
  • Line 6 (part 1) needed "modprobe ipt_recent" (you may need to compile your own)
  • Line 54 (part 1) "multiport" was "mport"
  • Line 3 (part 1) KNOCK_INTERFACE="vlan1"
There is no reason to run the old kernel since TomatoUSB 2.6 supports older MIPS1 routers.
Code is "as-is" with no warranty. Happy Hacking!


  1. Did you develop it yourself? It seems nicely done.
    But are you sure it runs? I tested it on both K24 (with mods) and K26, but I don't even get iptables to write to the log.

  2. Hi, I had help with the this and don't claim all the credit. I know this works because I use it on my RT-N16 running Toastman's Tomato (tomato-K26USB-1.28.0500.2MIPSR2Toastman-RT-N-VPN)

    I noted that on K24, there are some minor tweaks I had to change from my old router when I moved to K26. iptables doesn't write to the log. If you give me some more information (hardware, firmware), I can try and help you.

  3. Aha, I think I get it. The port forwards only work through the SSH connection, they are redirected as if on the local network?
    Could you nudge me in the right direction of how to forego the SSH, and just active the port forwards after the succesful knocks?

  4. Do I just change this "iptables -A INPUT -d -p tcp --dport 1234 \ -j ACCEPT " with my local host and port,
    instead of these lines:
    iptables -A wanin -i $KNOCK_INTERFACE -p tcp --dport 21 ...?

  5. The 4th port is the port you want to access.

    In part 2 of the script, let's use RDP. The default port for RDP is 3389, so line 5 & 10 stay the same. Line 13 would be the IP of the computer you want to forward RDP. I sometimes use Windows RDP client to knock on the ports.

    Yes to your second question. You will need to reboot your router if you change the script.

  6. Yes, I did those things.
    I also get knock 2 and knock 3 in the log, but the port forwards do not activate. That's why I thought that perhaps it only works with a SSH tunnel, as you actually forward the port inside SSH?
    If that is the case, the iptable rules regarding the port forwards should be changed when you don't use an SSH tunnel.

    I'm reluctant to use the rule on line 30 but with a host IP instead of router IP, because in my limited knowledge, it seems that the "input accept" is not time limited but permanent?

  7. That is good you are getting the knocks showing up in the log. This means iptables is working. It's just the opening port lines.

    This is does not open a SSH tunnel. You are right, line 30 should be the local IP to your router. All IPs in the script are local. If you are using lines 30-36 to get to the Tomato Admin page, remember to use https://

    What Tomato firmware and router are you using?

  8. 2x WRT54GL,
    - 1 with TomatoRAF 1.28.8525 (k2.4.37)
    - 1 with TomatoRAF 1.28.9006 (k2.6.22)

    Perhaps I'm not expressing myself fluently. I have not tested if I can login on HTTPS or SSH on the router interface, because I only want to do a port forward of 1 single port (port X WAN to port 80 local on a local host).

    So perhaps that part of the script does work, but I do not know, or need it.

    When I put the "inbound connection logging" on, I see that packets to my port forward are dropped before the port knock, and after the Timeout period also.
    During the timeout period, after 3 succesfull knocks, nothing happens. The packets to that port do not show up in the log (no drop or accept, nothing).
    I do not know what this means. Are the packets misrouted? Is one of the rule wrong? What could cause this problem.

  9. I changed to Tomato shibby (K24), and now it works!

  10. I have some questions:
    - I understand the statefullness of TCP, but is it also possible to allow UDP after succesful knock?

    - I see that NEW connections are only accepted during the step-timeout period. Afterwards, you can keep using the established connection, but not new ones (eg http-refresh is a new connection).
    If I just remove the "--state NEW", it will allow all connections, but only for the time-out.
    -> How do I instruct iptables that this source-IP may now make new connections, for say 30 minutes?

  11. Thank you for the script. I tried it on Tomato K26 router. I managed to open the port (443 to my web server on let's say 192. 168.1.10) by knocking. The port, however, closes after a short ? 25 sec timeout. Should there be some sort of iptables -I INPUT -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT command to preserve the established connections?

    Also could you give a hint how to forward a port? I tried the following:
    line 10: iptables -t nat -A PREROUTING -i $KNOCK_INTERFACE -p tcp --dport 40000
    line 13: -j DNAT --to-destination

    but that did not work, could not even see knocks in the syslog.


  12. Usually I knock into my network to use FTP or SSH. If I need to use the tomato interface, I will open a separate tab to the bandwidth monitor which refreshes automatically and will keep the connection alive.

    1. I am trying to get into a different server to view my security video feed. Tomato is on I think I will try to experiment with knockd.

  13. Just tried this on TomatoUSB K26 on an Asus RT-N66U and it worked great. I did have to change --log-level info and --log-level warn to --log-level 1 to get the messages to log on my router. Thanks for posting this!

    1. For anybody playing with this, you can look at the source ip address and time stamp in /proc/net/xt_recent/. There is a knock1, knock2, and knock3 file. After each knock, the source IP address and time stamp will appear in the respective knock file.

  14. I'm glad my instructions helped.