What's new

Firewall rule to allow only a specific domain name

  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

Markfree

Regular Contributor
Behind my AX86U I am monitoring a device via SNMP.
The SNMP manager initiates the communication by using a non-standard port.
So I created a forwarding rule that redirects the non-standard port to the internal device.
Something like this:
Code:
external [diff UDP port] -> internal [snmp UDP 161]
Example:
1698951469674.png


The service is running fine and I'm able to monitor the device.
However, I'm seeing some random attempts to access the device via SNMP, all with different global IPs.

The problem is that the SNMP manager uses a dynamic IP that changes from time to time, so I cannot set a fixed IP in the Source IP field.

I want to add a firewall rule in the router to allow access to the device only from the SNMP manager on some specific ports.
However, I cannot set a static IP in the firewall rule set and could not find an option to include the expected DDNS domain name.

Do you guys have any tips on how to include this rule or should I look for a more robust firewall option?
 
The firewall (iptables) only works with IP addresses. You could specify a domain name as the source address but all that would happen is that it would get translated to a fixed IP address when the firewall is started. So unless your were periodically restarting the firewall it wouldn't pick up changes to your manager's DDNS. I guess you could write a script on the router that monitored the DDNS name and only if it changed restart the firewall (or update the firewall rule).
 
Considering this scenario:
  • WAN interface: eth0
  • Service name: SNMP
  • Protocol: UDP
  • External port: [ext port]
  • Internal port: 161
  • Internal IP: 192.168.8.200
  • Source IP: [source ip]

I tried to add the following rule set:
Bash:
iptables -I INPUT -i eth0 -p udp -m udp --dport [ext port] -s [source ip] -j DNAT --to 192.168.8.200:161
iptables -I FORWARD -i eth0 -o br0 -p udp -m udp --dport 161 -d 192.168.8.200 -j ACCEPT

The first rule (-I INPUT) gives me an error right away in "dmesg".
Code:
x_tables: ip_tables: DNAT target: only valid in nat table, not filter

I'm not good with IPTables. Could you suggest a correct rule set?
 
I was reading the IPTable rules using iptables -S and iptables -L. It seems that these commands do not display the table names.
It wasn't until I ran the iptables-save command that I was able to see those tables. Then I started to actually understand things a bit.

There is also a file that shows the table names that I didn't know about.
Bash:
# cat /proc/net/ip_tables_names
raw
nat
mangle
filter

Since IPTables uses the filter table by default, the previous error now makes more sense.
After reading the nat table (iptables -t nat -L) I could see the actual rules set up in the router UI for port forwarding.

So, with your help @ColinTaylor, and considering the same scenario, I've mimicked the virtual server (VSERVER) rules and added the following.
Bash:
iptables -t nat -I VSERVER -s [source ip] -p udp -m udp --dport [ext port] -j DNAT --to-destination 192.168.8.200:161

Using Dig and Crontab, I think I can now create a simple script that replaces the source IP by checking the dynamic DNS name.
Bash:
dig +short @127.0.0.1 [domain]

By the way, I think /jffs/scripts/nat-start script would be the best place to include my own script, right?

There may be a better solution to all of this, but this one is certainly fun.
Thanks a lot.
 
I think the IPTables Replace command should suffice.

Bash:
# Set up the domain, ip and port
DDNSNAME=$1
EXTPORT=$2
NEWIP=$(dig +short @127.0.0.1 $DDNSNAME)

# Considering the chain rule number is unknown, I need to filter it
LINENUM=$(iptables --line-numbers -n -t nat -L VSERVER | grep 'to:192.168.8.200:161' | awk '{print $1}')

# Now, we check if the new IP is different from the current IP
CURRENTIP=$(iptables --line-numbers -n -t nat -L VSERVER $LINENUM | awk '{print $5}')

if [ "$NEWIP" != "$CURRENTIP" ]; then
    iptables -t nat -R VSERVER $LINENUM -s $NEWIP/32 -p udp -m udp --dport $EXTPORT -j DNAT --to-destination 192.168.8.200:161
fi

I would place this script at /jffs/scripts/iptables_ddns_rule.sh.

My /jffs/scripts/nat-start would include something like this.
Bash:
crontab -l > new_crontab.txt
echo '*/5 * * * * /jffs/scripts/iptables_ddns_rule.sh [ddns_domain] [ext_port]' >> new_crontab.txt
crontab new_crontab.txt

Do you guys think that could be enhanced?
 
Looks promising.

You should really only be putting iptables commands in the nat-start script (which may run multiple times). If you want to setup the crontab it should be done in services-start. It's generally better to use cru instead of crontab with asuswrt.

Bear in mind that dig is not part of the base firmware. Therefore you're dependent on having Entware installed on a mounted USB drive and you need to setup the PATH in the script appropriately.

It might also be worth doing a sanity check on the result returned by dig. e.g. it's not null and not more than one address.
Code:
# dig +short @127.0.0.1 blah
# dig +short @127.0.0.1 bbc.co.uk
151.101.64.81
151.101.128.81
151.101.192.81
151.101.0.81

EDIT: You also need to cater for the "first run" as initially there won't be an existing entry present in the nat table for your script to match the line number to.
 
Last edited:
I think the IPTables Replace command should suffice.

Bash:
# Set up the domain, ip and port
DDNSNAME=$1
EXTPORT=$2
NEWIP=$(dig +short @127.0.0.1 $DDNSNAME)

# Considering the chain rule number is unknown, I need to filter it
LINENUM=$(iptables --line-numbers -n -t nat -L VSERVER | grep 'to:192.168.8.200:161' | awk '{print $1}')

# Now, we check if the new IP is different from the current IP
CURRENTIP=$(iptables --line-numbers -n -t nat -L VSERVER $LINENUM | awk '{print $5}')

if [ "$NEWIP" != "$CURRENTIP" ]; then
    iptables -t nat -R VSERVER $LINENUM -s $NEWIP/32 -p udp -m udp --dport $EXTPORT -j DNAT --to-destination 192.168.8.200:161
fi

I would place this script at /jffs/scripts/iptables_ddns_rule.sh.

My /jffs/scripts/nat-start would include something like this.
Bash:
crontab -l > new_crontab.txt
echo '*/5 * * * * /jffs/scripts/iptables_ddns_rule.sh [ddns_domain] [ext_port]' >> new_crontab.txt
crontab new_crontab.txt

Do you guys think that could be enhanced?
You can just use cru a to add the cron job directly instead of writing to a file to add it.
 
Great guys. It's getting even better.

Bear in mind that dig is not part of the base firmware. Therefore you're dependent on having Entware installed on a mounted USB drive and you need to setup the PATH in the script appropriately.
Dig was so much cleaner... I vote to add it to the base firmware. 🖐️
But you're right. Next time I reset the router, I may forget to install it.
I could try something like this with Nslookup:
Bash:
NEWIP=$(nslookup $DDNSNAME 127.0.0.1 | awk 'NR==5 { print $3 })
I think this way it will always give me the first IP.


Don't know offhand what native awk is capable of, but I'd try to save a call of grep there by ...VSERVER | awk '/to:192.168.8.200:161/{print $1}'
Again, AWK doing its magic.
Bash:
LINENUM=$(iptables --line-numbers -n -t nat -L VSERVER | awk '/to:192.168.8.200:161/{print $1}')


EDIT: You also need to cater for the "first run" as initially there won't be an existing entry present in the nat table for your script to match the line number to.
Another "if" to the mix.
Bash:
if [ -z "$LINENUM" ]; then
    iptables -t nat -I VSERVER -s $NEWIP/32 -p udp -m udp --dport $EXTPORT -j DNAT --to-destination 192.168.8.200:161
fi
This way, when the router reboots, the script will run for the first time and add the rule.
The new IP will be the same as the current IP, so the second "if" will be false.


You should really only be putting iptables commands in the nat-start script (which may run multiple times). If you want to setup the crontab it should be done in services-start. It's generally better to use cru instead of crontab with asuswrt.
I see, but if I were to insert the cru command in /jffs/scripts/nat-start, it will have the same effect, right?
Bash:
cru a DDNS_NAT "*/5 * * * * /jffs/scripts/iptables_ddns_nat.sh [DDNSNAME] [EXTPORT]"


The new version of the script /jffs/scripts/iptables_ddns_nat.sh:
Bash:
# set up domain, ip and port
DDNSNAME=$1
EXTPORT=$2
NEWIP=$(nslookup $DDNSNAME 127.0.0.1 | awk 'NR==5 { print $3 })

# chain rule number filter
LINENUM=$(iptables --line-numbers -n -t nat -L VSERVER | awk '/to:192.168.8.200:161/{print $1}')

if [ -z "$LINENUM" ]; then
    iptables -t nat -I VSERVER -s $NEWIP/32 -p udp -m udp --dport $EXTPORT -j DNAT --to-destination 192.168.8.200:161
fi

# IP check and replace
CURRENTIP=$(iptables --line-numbers -n -t nat -L VSERVER $LINENUM | awk '{print $5}')

if [ "$NEWIP" != "$CURRENTIP" ]; then
    iptables -t nat -R VSERVER $LINENUM -s $NEWIP/32 -p udp -m udp --dport $EXTPORT -j DNAT --to-destination 192.168.8.200:161
fi

Sorry for the long post. :)
 
I see, but if I were to insert the cru command in /jffs/scripts/nat-start, it will have the same effect, right?
Bash:
cru a DDNS_NAT "*/5 * * * * /jffs/scripts/iptables_ddns_nat.sh [DDNSNAME] [EXTPORT]"
Probably, but basically it's the wrong place to be creating a crontab entry. nat-start is not intended for that purpose. Use services-start.

 

Similar threads

Support SNBForums w/ Amazon

If you'd like to support SNBForums, just use this link and buy anything on Amazon. Thanks!

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Top