How to write firewall script to block packet that received from another device

Hello, I want to know how to create firewall script to block specific packets.
I have file.txt on openwrt router directory contains list of ip and port to block, they look like this.

91.234.168.83 56489
165.58.211.41 12313
192.168.3.5 13216
195.56.212.35 13235
ā€¦ and more

could you please provide me recommendation how to create firewall script to run on openwrt?
Thank you

If your list is rather large, I would highly recommend you create an ipset for it and block the whole ipset.

2 Likes

I would ask for further question. What directory could I write ipset and could you please provide me an example? Thank you.

Create your ip list in the following format:

add name_of_ipset 91.234.168.83,56489 -exist
add name_of_ipset 165.58.211.41,12313 -exist
add name_of_ipset 192.168.3.5,13216 -exist
add name_of_ipset 195.56.212.35,13235 -exist
...

Then use ipset restore < path/to/ipset.txt to load the ipset.
But there is one problem...
By default ipset uses tcp for matching (? why is that?)
If you want to match for tcp + udp you have to create 2 entries for each ip(???)

You can also create and load the ipset through uci.
/etc/config/firewall

config ipset
	option name 'name_of_the_ipset'
	option storage 'hash'
	option match 'src_ip src_port' (or 'dest_ip dest_port' or whatever does fit)
	option loadfile '/path/to/ipset.txt'

config rule
	option name 'WAN-FORWARD-IPSET-NAMEOFIPSET-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'wan'
	option dest 'lan'
	option ipset 'name_of_ipset'
	option target 'DROP'

config rule
	option name 'WAN-INPUT-IPSET-NAMEOFIPSET-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'wan'
	option ipset 'name_of_ipset'
	option target 'DROP'

Then, for example, create your ipset.txt in the following format:

91.234.168.83,tcp:56489
165.58.211.41,udp:12313 
192.168.3.5,tcp:13216
195.56.212.35,udp:13235

Make sure that you add an extra empty line at the end or otherwise the last line doesn't get parsed.

//off topic
Can someone explain why the protocol is stored in the ipset?
When the matching of the protocol can be done with iptables?

Why do I have to specify the direction when creating an ipset in openwrt?
When the direction can be specified in the firewall rules? Is it used as default or something?

Why is destination shortened to dest and not dst ? src and dst looks better :smile:

DST is daylight saving time as an acronym.

In this case name_of_ipset can be anything right?

and the below should be saved in ipset.txt?

Yes, it can be anything.
You can also add the ipset create command to the ipset.txt.
For example:

create name_of_ipset hash:ip,port family inet hashsize 1024 maxelem 65536
add name_of_ipset 192.168.3.5,tcp:13216 -exist
add name_of_ipset 195.56.212.35,tcp:13235 -exist
add name_of_ipset 165.58.211.41,tcp:12313 -exist
add name_of_ipset 91.234.168.83,tcp:56489 -exist

Then use ipset restore < ipset.txt to create and fill the ipset.
You can also use ipset save name_of_any_ipset > ipset123.txt to save ipsets to a file.
If you don't specify a name all ipsets are saved to the file.

To config ipset,

For src_ip src_port , it should be type like this to make firewall config work or should I change?

and for ipset.txt, I should follow this format, right?

Well, do you want to block connections that are originating from the IPs listened in the IPSet or do you want to block connections going out to the IPs listened in the IPSet?

For the first case, I would use src and for latter one I would use dest.

Assuming the first case:
First create a file containing your IP and port combinations:
I use my attached USB Drive here:
/mnt/sda1/myipset.ipset (file name and extension doesn't matter)
Content:

91.234.168.83,tcp:56489
165.58.211.41,udp:12313 
192.168.3.5,tcp:13216
195.56.212.35,udp:13235

Add an empty like at the bottom otherwise the last line doesn't get properly parsed.
When you don't specify a protocol like tcp,udp, ipset will default to tcp.
For example when you use:
91.234.168.83,56489
gets interpreted as:
91.234.168.83,tcp:56489

To automatically load the IPset at firewall (re)start:
/etc/config/firewall

config ipset
	option name 'MyIPSet'
	option storage 'hash'
	option match 'src_ip src_port'
	option loadfile '/mnt/sda1/myipset.ipset'

Change MyIPSet to any name you want.
(But I think white spaces and some special chars are unsupported)
option loadfile needs to be the path (and filename) from the first step.

To block connections from WAN to your LAN use the following rule:

config rule
	option name 'WAN-FORWARD-LAN-IPSET-MYIPSET-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'wan'
	option dest 'lan'
	option ipset 'MyIPSet'
	option target 'DROP'

Coming back to your question about src and dest.
To match the opposite way.
Blocking connections from your LAN To WAN:
You can either change src_ip src_port to dest_ip dest_port and change the above rule to:

config rule
	option name 'LAN-FORWARD-WAN-IPSET-MYIPSET-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'lan'
	option dest 'wan'
	option ipset 'MyIPSet'
	option target 'DROP'

OR
leave the config ipset section as it is and only change (or add) the firewall rule as follows:

config rule
	option name 'LAN-FORWARD-WAN-IPSET-MYIPSET-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'lan'
	option dest 'wan'
	option ipset 'MyIPSet dest dest'
	option target 'DROP'

If you want to block connections to your router (not your internal network):
Use following rule:

config rule
	option name 'WAN-INPUT-IPSET-MYIPSET-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'wan'
	option ipset 'MyIPSet'
	option target 'DROP'

But by looking at your IP/Port list, the port numbers look like random port numbers to me.
Maybe it makes more sense to only block by IP?
For this purpose you maybe want to have a look at the banip package it has support for a custom blacklist.
How large is your list?

Thank you so much for your explanation. Actually, my iplist is not large but for one ip, it comes from various and random port. So I could go further with Banip?

banip comes with various blocklist sources.
You don't have to use those and only use the custom blacklist.
But it is maybe a bit too much for this purpose.

When your list isn't that large you can also use this approach:

config ipset
	option name 'MyIPSet'
	option storage 'hash'
	option match 'src_ip'
	list entry '91.234.168.83'
	list entry '165.58.211.41'
	list entry '192.168.3.5'
	list entry '195.56.212.35'
	
config rule
	option name 'WAN-FORWARD-LAN-IPSET-MYIPSET-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'wan'
	option dest 'lan'
	option ipset 'MyIPSet'
	option target 'DROP'

config rule
	option name 'WAN-INPUT-IPSET-MYIPSET-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'wan'
	option ipset 'MyIPSet'
	option target 'DROP'

Instead of option dest 'lan' you can also use option dest '*' to block forwarding to all zones.
(If you have multiple ones)

Or without IPSet:

config rule
	option name 'WAN-FORWARD-LAN-BADIPS-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'wan'
	option dest 'lan'
	list src_ip '91.234.168.83' 
	list src_ip '165.58.211.41' 
	list src_ip '192.168.3.5' 
	list src_ip '195.56.212.35'
	option target 'DROP'

config rule
	option name 'WAN-INPUT-BADIPS-DROP'
	option family 'ipv4'
	option proto '*'
	option src 'wan'
	list src_ip '91.234.168.83' 
	list src_ip '165.58.211.41' 
	list src_ip '192.168.3.5' 
	list src_ip '195.56.212.35'
	option target 'DROP'

If I want to load file that contains ip,port and also mixed with only ip, is it possible?
for example:

91.234.168.83,56489
165.58.211.41
195.56.212.35,13235
192.168.3.5

Not directly.
I came up with this solution:
It works, but it isn't nice :sweat_smile:

block.set

-! 91.234.168.83,56489
-! 165.58.211.41
-! 195.56.212.35,13235
-! 192.168.3.5

-! is the same as -exist at the end of a line.
Note: Empty line at the end.

/etc/config/firewall:

config ipset
	option name 'block_ip'
	option family 'ipv4'
	option storage 'hash'
	option match 'dest_ip'
	option loadfile '/mnt/sda1/block.set'

config ipset 
	option name 'block_ip_port'
	option family 'ipv4'
	option storage 'hash'
	option match 'dest_ip dest_port'
	option loadfile '/mnt/sda1/block.set'

config ipset
	option name 'block'
	option family 'ipv4'
	option storage 'list'
	option match 'dest_set'
	list entry 'block_ip'
	list entry 'block_ip_port'
	
config rule
	option name 'ANY-FORWARD-WAN-REJECT-IPSET-BLOCK'
	option family 'ipv4'
	option proto 'any'
	option src '*'
	option dest 'wan'
	option ipset 'block dest dest'
	option target 'REJECT'

config rule
	option name 'WAN-FORWARD-ANY-DROP-IPSET-BLOCK'
	option family 'ipv4'
	option proto 'any'
	option src 'wan'
	option dest '*'
	option ipset 'block src src'
	option target 'DROP'

On firewall restart, ipset will print errors because there is a mismatch between the sets and the entries in the block.set file. But it does work.
By "combining" the two sets (block_ip and block_ip_port) into one list (named block here) it is possible to use one instead of two iptables rules. (in this case two rules and instead of four rules)
I didn't find a way to make ipset supress/ignore errors. Maybe someone else has a better solution?
Of course it is possible to split the block.set list with a script but maybe there is a faster way...
You can also use iptables save after applying the above configuration to get/save the proper set lists.

Off-topic:
I think there is bug in fw3?

 * Deleting ipset block_ip
 * Deleting ipset block_ip_port
 * Deleting ipset block
ipset v7.4: Set cannot be destroyed: it is in use by a kernel component
ipset v7.4: Set cannot be destroyed: it is in use by a kernel component

// edit
Okay it does not work through uci.
Because the iptables rule to filter the traffic ignores the second direction parameter.
For example:
option ipset 'block dest dest'
should generate a rule with --match-set block dst,dst but the actual created rule only contains
--match-set block dst.
But when using manual created iptables rules, it does work.

// edit2
Workaround:
Replace the above rules with:

config rule
	option name 'ANY-FORWARD-WAN-REJECT-IPSET-BLOCK'
	option family 'ipv4'
	option proto 'any'
	option src '*'
	option dest 'wan'
	option extra '-m set --match-set block dst,dst'
	option target 'REJECT'

config rule
	option name 'WAN-FORWARD-ANY-DROP-IPSET-BLOCK'
	option family 'ipv4'
	option proto 'any'
	option src 'wan'
	option dest '*'
	option extra '-m set --match-set block src,src'
	option target 'DROP'

I would ask for further question. How to see the ip of dropped flow?
I use command

root@OpenWrt:~/recv# iptables -L -n -v -x | grep DROP
Chain FORWARD (policy DROP 0 packets, 0 bytes)
Chain zone_lan_dest_DROP (2 references)
       0        0 DROP       all  --  *      br-lan  0.0.0.0/0            0.0.0.0/0            /* !fw3 */
       0        0 zone_lan_dest_DROP  all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set myipset_ip src /* !fw3: WAN-FORWARD-IPSET-NAMEOFIPSET-DROP */
       0        0 zone_lan_dest_DROP  all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set myipset_ipport src,src /* !fw3: WAN-FORWARD-IPSET2-NAMEOFIPSET-DROP */
       7      396 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set myipset_ip src /* !fw3: WAN-INPUT-IPSET-NAMEOFIPSET-DROP */              0        0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set myipset_ipport src,src /* !fw3: WAN-INPUT-IPSET2-NAMEOFIPSET-DROP */
       0        0 DROP       all  --  *      eth0.2  0.0.0.0/0            0.0.0.0/0            ctstate INVALID /* !fw3: Prevent NAT leakage */

I dont know why it shows drop statistics but no dropped ip specified just shows 0.0.0.0.

You can create the ipset with the additional counters option.

All set types support the optional counters option when creating a set. If the option is specified then the set is created with packet and byte counters per element support. The packet and byte counters are initialized to zero when the elements are (re-)added to the set, unless the packet and byte counter values are explicitly specified by the packets and bytes options. An example when an element is added to a set with non-zero counter values:

ipset create foo hash:ip counters
ipset add foo 192.168.1.1 packets 42 bytes 1024

Then you can use:
ipset list name_of_ipset | grep -v 'packets 0'
Which will only show members with a packet counter > 0.

But I have to check how to enable the counters option through uci.
// edit
Can you try:

config ipset
	option name 'block_ip'
	...
	option counters '1'
	...

config ipset 
	option name 'block_ip_port'
	...
	option counters '1'
	...

Then use:
ipset list block_ip | grep -v 'packets 0'
ipset list block_ip_port | grep -v 'packets 0'