How to setup a firewall "white-list" and how to use ip-set on LEDE?

I've been using the firewall custom rules to block
SIP brute force attacks on a server, 99% of them
originate from France, Russia and Germany.

So far I've been using the following syntax on every
new IP address that I come across:

iptables -I FORWARD -s 0.0.0.0/24 -j DROP
iptables -I INPUT -s 0.0.0.0/24 -j DROP

This has worked really well but I have thought about
blocking the entire ip range from those countries, so
I downloaded the ip ranges and tried it, around 50k
lines, the router crashed and had to hard reset.

Then I considered white-listing the US plus two other
countries that need to access the system but the US alone
will use over 34K lines of rules and that turned out to not be
the solution.

I head about a tool call ip-set and that it can be used to handle
those types of huge lists without hammering the cpu and memory
but I have very basic lede and linux experience and don't even know
how to get started.

I'm using aggregated zone files taken from this website:
http://www.ipdeny.com/ipblocks/

So for France I would use all these IPs:
http://www.ipdeny.com/ipblocks/data/aggregated/fr-aggregated.zone

My previous methodology was to apply this syntax for every IP:
iptables -I FORWARD -s 0.0.0.0/24 -j DROP
iptables -I INPUT -s 0.0.0.0/24 -j DROP

But with ip-set I have no clue how it works...

I would appreciate any hint on how to setup ip-set on LEDE (17.01) to
either black-list the ips I need or white-list the US list plus two other countries,
whatever would be easier.

Thank you.

It isn't recommended to use the main FORWARD or INPUT chains. Instead you can add your own custom rules into forwarding_wan_rule and input_rule. You can try the following entries inside /etc/firewall.user:

ipset -N badgeo hash:net

iptables -A forwarding_wan_rule -m set --match-set badgeo src -j DROP
iptables -A input_wan_rule -m set --match-set badgeo src -j DROP

These rules above will check the "badgeo" IPset table for IP addresses to filter.

You can run the following in your own script or add it inside /etc/rc.local to populate "badgeo" entries with French IP addresses:

(
for ip in $(curl -1ks http://www.ipdeny.com/ipblocks/data/aggregated/fr-aggregated.zone); 
do ipset -q add badgeo $ip; 
done
)&

Please note that you're filtering traffic on the INPUT chain, so there's a risk of completely locking yourself out of the router. While testing, you should have a whitelist rule for your internal private IP address; or don't commit any of these into the configuration that will survive reboot until you have fully tested them.

Hope this helps.

EDIT: the custom input table should have been "input_wan_rule" to filter only WAN traffic going to router. This will reduce risk of accidentally filtering LAN access. I've updated the iptables command above.

Awesome, I just did that but can't confirm it's working, I can see the badgeo in the firewall status but don't know if the ips were downloaded successfully.

Question, if I store the badgeo txt files in side let's say inside /tmp/badgeo, what's the command to replace curl download with the file on disk, is the script bellow correct?

cat /tmp/badgeo/fr-aggregated.zone.txt | while read $ip
do
   do ipset -q add badgeo $ip; 
done

Also, how can I confirm the ips were loaded without having to block myself to test it?

Thank youopera_2018-10-20_14-34-23 WinSCP_2018-10-20_14-36-59

The ipset man page suggests ipset list to verify the contents.

If you actually want to test that it is working, you'll need a host connected to your "WAN" interface either configured for each of the source addresses you want to test, or use a tool like scapy. (You'll want to be disconnected from your ISP when you do this.)

Do you really trust that the contents of fr-aggregated.zone.txt can be blindly executed by root?

What if the site or its DNS were compromised and the contents were

; dd if=/dev/zero of=/dev/mtd0 bs=1K count=128 ; reboot

and your router was mysteriously hard bricked?
(Not so far fetched, unfortunately)

You might want to check it before execution with something perhaps as simple as (you should check and understand this, as it is suggestive and provided without any warranty)

egrep -E '[0-9./]+'

Something more specific to the file format and its contents would be highly recommended.

1 Like

You can simply test if that rule works from a fixed IP machine on your internal network, say 192.168.0.45 by blocking it with:

ipset -q add badgeo 192.168.0.45

To check if your badgeo list is correctly populate, you can simply run:

ipset list badgeo

Thanks for pointing that out. You're perfectly correct! All external data should be sanitised before passing them to executable command. The following regex should only pass on IPs and CIDRs but without checking for correct octet format:

^[0-9]{1,3}([.\/]([0-9]{1,3}))+$

Hi Jeff, I will store the zone files in the internal memory instead, don't want to rely on fetching external urls.

I tested the script inside the terminal and it works perfectly, when I execute it I can confirm using ipset list badgeo that the all the ip addresses are there however it doesn't seem to work from within /etc/rc.local, I have the same script there but upon rebooting ipset list badgeo doesn't contain any ip.

This is what I came up with, I setup a new folder for the zones files:

for ip in $(cat /usr/badgeo/fr-aggregated.zone.txt) do ipset -q add badgeo $ip; done

yep, used the terminal to add my ip temporarily to the list and it did block me out perfectly, thank you,

You're missing a semi-colon:

for ip in $(cat /usr/badgeo/fr-aggregated.zone.txt); do ipset -q add badgeo $ip; done

Note that I updated my original post, the right custom input chain for blocking external traffic is "input_wan_rule"

I've added the semi-colon and rebooted but still no luck, tried to add a sleep before that code but also no luck after rebooting, copying and pasting the code in the terminal works no problem.

opera_2018-10-22_10-57-35

Thank you, updated the firewall script.

So what could be preventing this code from executing upon booting?

BTW all, this can be done in the UCI:

Got it guys, was a syntax issue, here's the working rc.local script:

(sleep 15;)
(for ip in $(cat /usr/badgeo/fr-aggregated.zone.txt) 
do 
ipset -q add badgeo $ip
done;)
exit 0;

Now, since ipset is pretty fast what would it be like to do it the other way around, a "white-list", let's say "http://www.ipdeny.com/ipblocks/data/aggregated/us-aggregated.zone" containing 17457 ip blocks, how can I allow only the ips on those zones to communicate and block everything else?

To reverse black list to white, you simply do a negative match of that rule:

iptables -A forwarding_wan_rule -m set ! --match-set badgeo src -j DROP

Thank you, that worked.

I realized that the aggregated zone files don't contain all the possible ips for the US for the instance, so I ended up purchasing a commercial list which contains 1,559,373,254 IP address for the US alone.

Since the list contains ranges of addresses I still need to write some code to fill the gaps on the IPs which will unfold the list to a total of 1,559,373,254.

Will see if I can do that tomorrow but I'm already wondering if the router/ipset will handle it without hurting the performance, I'm using a Linksys WRT1900ACv2, it has 512MB of RAM of which 460MB are currently free.

Will get back,
Thanks

55K of ipset entries used up 1.5MB, so you can calculate accordingly:

# ipset list badgeo|head -7
Name: badgeo
Type: hash:net
Revision: 6
Header: family inet hashsize 32768 maxelem 65536
Size in memory: 1550336
References: 1
Number of entries: 55552

Note that ipset default size is 64K entries so you'll need declare bigger "maxelem" size during creation.

Thank you, this worked really well on my TL-WR1043N/ND v3 however when I tried to apply the same settings to my Linksys WRT1900ACv2 (OpenWrt 18.06.1) I had to manually install the ipset packages which my WR1043 already had installed (I'm using a FastPath build) and after everything got installed and rebooted it didn't seem to block outsiders like it did on the WR1043ND.

I had to manually install the following packages on the WRT1900ACv2:

  • kmod-ipt-ipset_4.14.63-1_arm_cortex-a9_vfpv3.ipk
  • kmod-nfnetlink_4.14.63-1_arm_cortex-a9_vfpv3.ipk
  • libmnl_1.0.4-1_arm_cortex-a9_vfpv3.ipk
  • libipset_6.34-1_arm_cortex-a9_vfpv3.ipk
  • ipset_6.34-1_arm_cortex-a9_vfpv3.ipk

Firewall - Custom Rules

ipset -N geolist hash:net maxelem 87000

#WHITE LIST
iptables -A forwarding_wan_rule -m set ! --match-set geolist src -j DROP
iptables -A input_wan_rule -m set ! --match-set geolist src -j DROP

rc.local

#(sleep 15;)
(for ip in $(cat /usr/geo/premium-aggregated.zone.txt) 
do 
ipset -q add geolist $ip
done;)
exit 0;

Model Linksys WRT1900ACv2
Architecture ARMv7 Processor rev 1 (v7l)
Firmware Version OpenWrt 18.06.1 r7258-5eb055306f / LuCI openwrt-18.06 branch (git-18.228.31946-f64b152)
Model TP-Link TL-WR1043N/ND v3
Processor Qualcomm Atheros QCA9558 ver 1 rev 0
Architecture MIPS 74Kc V5.0
Firmware Version LEDE Reboot 17.01-SNAPSHOT r3981-184fe11483 / LuCI lede-17.01 branch ( Trunk )


My WR1043ND has a lot more packages installed from factory, dunno if any additional package
would be missing on the WRT1900ACv2 to get the ipset rules working, "ipset list" on the 1900
does list all the IPs so I assume they are loaded but not being processed.

WR1043ND Firmware: https://github.com/gwlim/Fast-Path-LEDE-OpenWRT
WRT1900ACv2  Firmware: http://downloads.openwrt.org/snapshots/targets/mvebu/cortexa9/

Packages:
http://downloads.openwrt.org/releases/18.06.1/targets/mvebu/cortexa9/packages/
http://downloads.openwrt.org/releases/18.06.1/packages/arm_cortex-a9_vfpv3/base/

If "ipset list geolist" shows the IP addresses and "ipstables -vnL forwarding_wan_rule" shows that rule in the chain, it means you have no module issues and the firewall is running fine.

The issue might be that the source IP address isn't in your geolist or there are other rules / order of rules interfering.

Thank you for the feedback.

/usr/geo$ ipset list geolist
Name: geolist
Type: hash:net
Revision: 6
Header: family inet hashsize 32768 maxelem 87000
Size in memory: 310608
References: 2
Number of entries: 86481
Members:
A HUGE LIST OF IP ADDRESSES.....

/usr/geo$ iptables -vnL forwarding_wan_rule
Chain forwarding_wan_rule (1 references)
pkts bytes target prot opt in out source destination
819 169K DROP all -- * * 0.0.0.0/0

Let's take for the instance this offending IP address from Germany: 85.114.138.129 whose network mask I think should be 85.114.138.129/32, my white list contains the following allowed networks that are just "near" that range:

Line 9829: 185.114.23.202
Line 19814: 185.114.226.0/24
Line 29067: 185.114.92.0/22
Line 38220: 185.114.79.0/24
Line 52636: 185.114.36.0/24
Line 58441: 185.114.152.0/22
Line 65261: 85.114.2.134
Line 77049: 185.114.248.80/28

Those masks were calculate from my white list IP address range table which also don't include the offending German IP:
185.114.23.202 -> 185.114.23.202
185.114.36.0 -> 185.114.36.255
185.114.79.0 -> 185.114.79.255
185.114.92.0 -> 185.114.95.255
185.114.152.0 -> 185.114.155.255
185.114.226.0 -> 185.114.226.255
185.114.248.84 -> 185.114.248.91

Still, that IP keeps hitting the PC (SIPVicious probably) since Friday, can't figure this one out.

You "geolist" rule isn't in the forwarding_wan_rule table. I don't see that match-set suffix in your output.

If it was correctly added to there, it will look like this:

# iptables -vnL forwarding_wan_rule
Chain forwarding_wan_rule (1 references)
 pkts bytes target     prot opt in     out     source               destination
   14   776 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            ! --match-set geolist src

You can simply test if that IP is in your geolist with ipset test:

# ipset test geolist 85.114.138.129
85.114.138.129 is in set geolist.

In that case what do I need to change on the firewall custom rules to fix that?

ipset -N geolist hash:net maxelem 87000

#WHITE LIST
iptables -A forwarding_wan_rule -m set ! --match-set geolist src -j DROP
iptables -A input_wan_rule -m set ! --match-set geolist src -j DROP

You can simply test if that IP is in your geolist with ipset test:

Tested and it reports yes, which it shouldn't right? Cause it's a white list, but I don't see how that IP matches the networks I posted earlier, am I calculating the ip network incorrectly because my white list IP ranges don't include that offending IP but ipset reports it is in set.

/usr/geo$ ipset test geolist 85.114.138.129
85.114.138.129 is in set geolist.

Thank you