Mikrotik firewall filter: script generating the basis for a filtering policy

Anyone who has ever written a firewall filtering policy knows that this is not a simple matter and involves a lot of errors when there are more than 2 network zones. The script from this article will help you in this essay.

Introduction


By network zone I mean a set of interfaces or IP addresses for which the filter rule applies. In my script, the zone IP addresses are assigned to the interface. That is, if we expect β€œtrusted” IP addresses from the local network, it will be strange if the corresponding connections arrive from the provider.

How does it work


Everything revolves around the description of the zones where we ask how they interact with each other.
In my configuration, if no other rules are set, all traffic will be dropped with the DROP rule, so we will set the ACCEPT and REJECT rules.

Next, the script in a loop goes through all the zone descriptions and creates:

  • Interface Lists IF- <zone name>
  • IP Address Lists <zone name>
  • Defines default and slightly supplemented by me filter, mangle and raw configuration

IP traffic destination zone from interface differs in that its name is not in the section for interfaces. Thus, the script understands where we have the purpose of ip, and where is the interface.

Variables


  • gping - Allow ping in all directions and on forwarding. In other words, it will be possible to ping even what was closed by other rules in the filter. Include as desired;)
  • debug - Put the ACCEPT rule at the very beginning of the root chains, which makes all other rules useless. Very useful at the stage of debugging rules. Do the policy as you want, enable Safe Mode, and then disable these rules. If the router has become unavailable, then after 10 seconds the rules will be enabled again and you will be able to think, what were you wrong about?
  • allout - Allows the outgoing traffic of the router, usually the router does not generate bad traffic, so in a number of configurations this is acceptable.

Description of zones


The description itself is divided into two blocks: interfaces and ip addresses. There are special variables for interfaces:

  • is_wan: adds rules for processing tunnels and DNS
  • do_masq: adds traffic masking rules to this zone
  • is_lan: adds rules for responding to DHCP and DNS
  • mss: mss ,
  • ADDoS: DDoS . β€” ( ) , IP . :
    • gen β€” generic, , .
    • mngr β€” managment, mikrotik (winbox, ssh)
    • β€” , .


There are also two special zones: rt, this is the router itself, and all, as it’s not difficult to guess β€œany direction,” all cannot be specified as a source of traffic.

The main action of the script is to create a series of jump rules with filtering by source and destination of traffic, which are terminated by a rule with ACCEPT or REJECT.

The description of the zone can be reduced, as in the example for ISP and rt (should always be).

If you want to terminate the chain with your rule, then simply add custom (or whatever) in the zone description instead of accept or reject, then the jump chain will not be completed by any rule, and you can create it yourself.

Explanation of the rules from the script


Zones
:local gping 1
:local debug 0
:local zones {
	"if"={
		"ISP"={
			"is_wan"=1;
			"do_masq"=1;
			"ADDoS"={
				mngr={
					dst-limit="4/1m,1,src-address/1m40s";
					timeout="1d";
					comment="Blocking bruteforcing winbox & ssh port";
					excl=[:toarray ("Trusted")];
				};
				gen={
					dst-limit="50,50,src-address/10s";
					timeout="1d";
					comment="Blocking potential DDoS";
					excl=[:toarray ("Trusted")];
				};
			};
		};
		"LAN"={
			"is_lan"=1;
			policy={ 
				"all"="accept";
			};
		};
		"TUN"={
			mss=1400;
		};
		"rt"={};
	};
	"ip"={
		"rt"={
			policy={
				"all"="accept" 
			};
		};
		"TUN"={
			"Staff"={
				"Server"="accept";
			};
			"Manager"={
				"all"="accept";
			};
		};
		"ISP"={
			"Trusted"={
				"rt"="accept";
			};
		};
	};
}


There are 4 zones for interfaces, one of which is mandatory rt:

  • ISP is for the provider, therefore, it is indicated to create additional rules for some protocols.
  • LAN - for a local network, it is given rules for lan and there is a general permitting action in any direction.
  • TUN - for tunnels proper, MSS correction is indicated.

B 3rd ip zones:

  • Staff and Manager on TUN interfaces:
    • Staff - has access only to the ip zone of the Server
    • Manager - can go anywhere

  • Trusted on the ISP interface has access to the router

gen-filter
# may/13/2020 10:00:00 by RouterOS 6.46.6
# RoS filter generator v 0.9.5

:local gping 0
:local debug 1
:local allout 1
:local zones {
	"if"={
		"ISP"={
			"is_wan"=1;
			"do_masq"=1;
			"ADDoS"={
				mngr={
					dst-limit="4/1m,1,src-address/1m40s";
					timeout="1d";
					comment="Blocking bruteforcing winbox & ssh port";
					excl=[:toarray ("Trusted")];
				};
				gen={
					dst-limit="50,50,src-address/10s";
					timeout="1d";
					comment="Blocking potential DDoS";
					excl=[:toarray ("Trusted")];
				};
			};
		};
		"LAN"={
			"is_lan"=1;
			policy={ 
				"all"="accept";
			};
		};
		"TUN"={
			mss=1400;
		};
		"rt"={};
	};
	"ip"={
		"rt"={
			policy={
				"all"="accept" 
			};
		};
		"TUN"={
			"Staff"={
				"Server"="accept";
			};
			"Manager"={
				"all"="accept";
			};
		};
		"ISP"={
			"Trusted"={
				"rt"="accept";
			};
		};
	};
}
/ip firewall raw
add action=notrack chain=prerouting ipsec-policy=in,ipsec comment="Notrack ipsec"
add action=notrack chain=prerouting dst-address-type=multicast comment="Notrack multicast"
/ip firewall filter
:if ( ($debug)=0 ) do={
	add action=accept chain=forward comment=DEBUG!!! disabled=yes
	add action=accept chain=input comment=DEBUG!!! disabled=yes
	add action=accept chain=output comment=DEBUG!!! disabled=yes
} else={
	add action=accept chain=forward comment=DEBUG!!!
	add action=accept chain=input comment=DEBUG!!!
	add action=accept chain=output comment=DEBUG!!!
}
add action=accept chain=input comment="defconf: accept to local loopback (for CAPsMAN)" dst-address=127.0.0.1 dst-port=5246,5247 protocol=udp src-address-type=local
add action=accept chain=input comment="defconf: accept established,related,untracked" connection-state=established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=invalid
add action=jump chain=input comment="defconf: new input" connection-state=new jump-target=in-new
add action=jump chain=input comment="defconf: notrack input" jump-target=in-notrack
add action=drop chain=input comment="defconf: drop all not allowed"
add action=accept chain=output comment="defconf: accept established,related,untracked" connection-state=established,related,untracked
add action=jump chain=output comment="defconf: new output" connection-state=new jump-target=out-new
add action=jump chain=output comment="defconf: notrack output" jump-target=out-notrack
:if ( ($allout)=1 ) do={
	add action=accept chain=out-notrack comment="defconf: Allow any trafic from rt"
} else={
	add action=accept chain=out-notrack comment="defconf: Allow any trafic from rt" disabled=yes
}
add action=drop chain=output comment="defconf: drop all not allowed"
add action=accept chain=forward comment="defconf: accept in ipsec policy" ipsec-policy=in,ipsec
add action=accept chain=forward comment="defconf: accept out ipsec policy" ipsec-policy=out,ipsec
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" connection-mark=no-mark connection-state=established,related
add action=accept chain=forward comment="defconf: accept established,related, untracked" connection-state=established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" connection-state=invalid
add action=jump chain=forward comment="defconf: new forward" connection-state=new jump-target=fw-new
add action=jump chain=forward comment="defconf: notrack forward" jump-target=fw-notrack
add action=accept chain=forward comment="defconf: Accept all forward DSTNATed" connection-nat-state=dstnat
add action=drop chain=forward comment="defconf: drop all not allowed for forward"
:if ( ($gping)=1 ) do={
	add action=accept chain=WAN2RT-STD-PROTO comment=ICMP protocol=icmp disabled=yes
} else={
	add action=accept chain=WAN2RT-STD-PROTO comment=ICMP protocol=icmp
}
add action=accept chain=WAN2RT-STD-PROTO comment=GRE ipsec-policy=in,ipsec protocol=gre
add action=accept chain=WAN2RT-STD-PROTO comment=IPSec protocol=ipsec-esp
add action=accept chain=WAN2RT-STD-PROTO comment=IPSec protocol=ipsec-ah
add action=accept chain=WAN2RT-STD-PROTO comment="IPSec encapsulated" dst-port=500,4500 protocol=udp
add action=accept chain=WAN2RT-STD-PROTO comment=L2TP dst-port=1701 ipsec-policy=in,ipsec protocol=udp
add action=accept chain=WAN2RT-STD-PROTO comment=PPtP dst-port=1723 protocol=tcp
add action=accept chain=LAN2RT-STD-PROTO comment=DNS dst-port=53 protocol=tcp
add action=accept chain=LAN2RT-STD-PROTO comment=NTP,DNS,DHCP dst-port=53,123,67-68 protocol=udp
add action=accept chain=LAN2RT-STD-PROTO comment=DHCP dst-port=67-68 protocol=udp
add action=accept chain=RT2WAN-STD-PROTO comment=DNS dst-port=53 protocol=tcp
add action=accept chain=RT2WAN-STD-PROTO comment=NTP,DNS dst-port=53,123 protocol=udp
add action=accept chain=WAN2RT-STD-PROTO comment=IPSec protocol=ipsec-esp
add action=accept chain=RT2WAN-STD-PROTO comment=IPSec protocol=ipsec-ah
add action=accept chain=RT2WAN-STD-PROTO comment="IPSec encapsulated" dst-port=500,4500 protocol=udp
add action=reject chain=RT2WAN-STD-PROTO comment=GRE ipsec-policy=out,none protocol=gre reject-with=icmp-admin-prohibited
add action=reject chain=RT2WAN-STD-PROTO comment=L2TP dst-port=1701 ipsec-policy=out,none protocol=udp reject-with=icmp-admin-prohibited
:if ( ($gping)=1 ) do={
	add action=accept chain=fw-new comment=ICMP protocol=icmp
	add action=accept chain=in-new comment=ICMP protocol=icmp
	add action=accept chain=out-new comment=ICMP protocol=icmp
} else={
	add action=accept chain=fw-new comment=ICMP protocol=icmp disabled=yes
	add action=accept chain=in-new comment=ICMP protocol=icmp disabled=yes
	add action=accept chain=out-new comment=ICMP protocol=icmp disabled=yes
}
/ip firewall filter
:foreach zone,conf in=($zones->"if") do={
	:if ( ($zone)!="rt" ) do={
		:if ( [/interface list print count-only where name=("IF-".$zone)] = 0) do={
			/interface list add name=("IF-".$zone)
		}
		add action=jump chain=fw-new in-interface-list=("IF-".$zone) comment=("Fwd plc from if ".$zone) jump-target=("fw-plc-s:".$zone)
		add action=jump chain=in-new in-interface-list=("IF-".$zone) comment=("In plc for if ".$zone) jump-target=("in-plc-s:".$zone)
		:foreach ddos,val in=($conf->"ADDoS") do={
			:if ( $ddos="mngr" ) do={
				add action=jump chain=("in-plc-s:".$zone) dst-port=22,8291 jump-target=("ADDoS-s:".$zone."-".$ddos) comment=("AntiDDoS for ".$ddos." from ".$zone) protocol=tcp
			}
			:if ( $ddos="gen" ) do={
				add action=jump chain=("in-plc-s:".$zone) jump-target=("ADDoS-s:".$zone."-".$ddos) comment=("AntiDDoS for ".$ddos." from ".$zone)
			}
			:if ( $ddos="mngr" || $ddos="gen" ) do={
				:foreach excl in=(($val)->"excl") do={ 
					add action=return chain=("ADDoS-s:".$zone."-".$ddos) comment=("Execption for ".$excl." from ".$zone) src-address-list=("IP-".$excl)
				}
				add action=return chain=("ADDoS-s:".$zone."-".$ddos) comment=("Limit number incoming connections for ".$ddos." from ".$zone) dst-limit=(($val)->"dst-limit")
				add action=add-src-to-address-list address-list=("IP-DDoS-s:".$zone."-".$ddos) address-list-timeout=(($val)->"timeout") chain=("ADDoS-s:".$zone."-".$ddos) comment=("Add to IP-DDoS-".$ddos." list from ".$zone)
			}
			:if ( $ddos!="mngr" && $ddos!="gen" ) do={
				add action=jump chain=("in-plc-s:".$zone) jump-target=("ADDoS-".$ddos) comment=("AntiDDoS for ".$ddos." from ".$zone) disabled=yes
				:foreach excl in=(($val)->"excl") do={ 
					add action=return chain=("ADDoS-s:".$zone."-".$ddos) comment=("Execption for ".$excl." from ".$zone) src-address-list=("IP-".$excl)
				}
				add action=return chain=("ADDoS-s:".$zone."-".$ddos) comment=("Limit number incoming connections for ".$ddos." from ".$zone) dst-limit=(($val)->"dst-limit")
				add action=add-src-to-address-list address-list=("IP-DDoS-s:".$zone."-".$ddos) address-list-timeout=(($val)->"timeout") chain=("ADDoS-s:".$zone."-".$ddos) comment=("Add to IP-DDoS-".$ddos." list from ".$zone)
			}
			/ip firewall raw add action=drop chain=prerouting comment=("DROP Banned by ".$ddos." from ".$zone) in-interface-list=("IF-".$zone) src-address-list=("IP-DDoS-s:".$zone."-".$ddos)
		}
	} else={
		add action=jump chain=out-new comment=("Out plc for rt") jump-target="out-plc-s:rt"
	}	
}
:foreach zone,conf in=($zones->"if") do={
	:if ( ($zone)!="rt" && ($zone)!="all") do={
		:if ( ($conf->"is_lan")=1 || ($conf->"is_wan")=1 ) do={
			add action=jump chain=in-notrack comment=("In Allow plc for STD LAN PROTO from ".$zone) in-interface-list=("IF-".$zone) jump-target=LAN2RT-STD-PROTO
		}
		:if ( ($conf->"is_wan")=1) do={
			add action=jump chain=in-notrack comment=("In Allow plc for STD WAN PROTO from ".$zone) in-interface-list=("IF-".$zone) jump-target=WAN2RT-STD-PROTO
			add action=jump chain=out-notrack out-interface-list=("IF-".$zone) comment=("Out Allow plc for STD WAN PROTO to ".$zone) jump-target=RT2WAN-STD-PROTO
		}
		:if ( ($conf->"do_masq")=1) do={
			/ip firewall nat add action=masquerade chain=srcnat out-interface-list=("IF-".$zone) comment=("Masquerade traffic going to ".$zone)
		}
		:if ( [:len ($conf->"mss")]!=0 ) do={
			/ip firewall mangle add action=change-mss chain=forward in-interface-list=("IF-".$zone) new-mss=($conf->"mss") passthrough=yes protocol=tcp tcp-flags=syn tcp-mss=(($conf->"mss"+1)."-65535") comment=("Fix mss on tunel ".$zone)
			/ip firewall mangle add action=change-mss chain=forward out-interface-list=("IF-".$zone) new-mss=($conf->"mss") passthrough=yes protocol=tcp tcp-flags=syn tcp-mss=(($conf->"mss"+1)."-65535") comment=("Fix mss on tunel ".$zone)
		}
		:foreach src,val in=(($zones->"ip")->$"zone") do={
			:foreach tgt,policy in=$val do={
				:if ( ($tgt)!="rt" && ($tgt)!="all") do={
					:if ( [:len (($zones->"if")->$"tgt")]=0 ) do={
						add action=jump chain=("fw-plc-s:".$zone) src-address-list=("IP-".$src) dst-address-list=("IP-".$tgt) comment=("Fwd plc from if ".$zone." & ip ".$src." to ".$tgt) jump-target=("fw-plc-s:".$zone."&".$src.">".$tgt)
					} else={
						add action=jump chain=("fw-plc-s:".$zone) src-address-list=("IP-".$src) out-interface-list=("IF-".$tgt) comment=("Fwd plc from if ".$zone." & ip ".$src." to ".$tgt) jump-target=("fw-plc-s:".$zone."&".$src.">".$tgt)
					}
					:if ( ($policy)="accept") do={
						add action=accept chain=("fw-plc-s:".$zone."&".$src.">".$tgt) comment=("Fwd plc from if ".$zone." & ip ".$src." Accept to ".$tgt)
					}
					:if ( ($policy)="reject") do={
						add action=reject chain=("fw-plc-s:".$zone."&".$src.">".$tgt) comment=("Fwd plc from if ".$zone." & ip ".$src." Reject to ".$tgt)
					}
				}
				:if ( ($tgt)="rt" ) do={
					add action=jump chain=("in-plc-s:".$zone) src-address-list=("IP-".$src) comment=("In plc for if ".$zone." & ip ".$src." to rt") jump-target=("in-plc-s:".$zone."&".$src.">rt")
					:if ( ($policy)="accept") do={
						add action=accept chain=("in-plc-s:".$zone."&".$src.">rt") comment=("In plc for if ".$zone." & ip ".$src." Accept to rt")
					}
					:if ( ($policy)="reject") do={
						add action=reject chain=("in-plc-s:".$zone."&".$src.">rt") comment=("In plc for if ".$zone." & ip ".$src." Accept to rt")
					}
				}
				:if ( ($tgt)="all" ) do={
					add action=jump chain=("fw-plc-s:".$zone) src-address-list=("IP-".$src) comment=("Fwd plc from if ".$zone." & ip ".$src." to All") jump-target=("fw-plc-s:".$zone."&".$src.">all")
					add action=jump chain=("in-plc-s:".$zone) src-address-list=("IP-".$src) comment=("In plc for if ".$zone." & ip ".$src." to All") jump-target=("in-plc-s:".$zone."&".$src.">all")
					:if ( ($policy)="accept") do={
						add action=accept chain=("fw-plc-s:".$zone."&".$src.">all") comment=("Fwd plc from if ".$zone." & ip ".$src." Accept to All")
						add action=accept chain=("in-plc-s:".$zone."&".$src.">all") comment=("In plc from if ".$zone." & ip ".$src." Accept to All")
					}
					:if ( ($policy)="reject") do={
						add action=reject chain=("fw-plc-s:".$zone."&".$src.">all") comment=("Fwd plc from if ".$zone." & ip ".$src." Reject to All")
						add action=reject chain=("in-plc-s:".$zone."&".$src.">all") comment=("In plc from if ".$zone." & ip ".$src." Reject to All")
					}
				}
			}
		}
		:foreach tgt,policy in=($conf->"policy") do={
			:if ( ($tgt)!="rt" && ($tgt)!="all") do={
				:if ( [:len (($zones->"if")->$"tgt")]=0 ) do={
					add action=jump chain=("fw-plc-s:".$zone) dst-address-list=("IP-".$tgt) comment=("Fwd plc from if ".$zone." to ".$tgt) jump-target=("fw-plc-s:".$zone.">".$tgt)
				} else={
					add action=jump chain=("fw-plc-s:".$zone) out-interface-list=("IF-".$tgt) comment=("Fwd plc from if ".$zone." to ".$tgt) jump-target=("fw-plc-s:".$zone.">".$tgt)
				}
				:if ( ($policy)="accept") do={
					add action=accept chain=("fw-plc-s:".$zone.">".$tgt) comment=("Fwd plc from if ".$zone." Accept to ".$tgt)
				}
				:if ( ($policy)="reject") do={
					add action=reject chain=("fw-plc-s:".$zone.">".$tgt) comment=("Fwd plc from if ".$zone." Reject to ".$tgt)
				}
			}
			:if ( ($tgt)="rt" ) do={
				:if ( ($conf->"is_lan")!=1 && ($conf->"is_wan")!=1 ) do={
					add action=jump chain=("in-plc-s:".$zone) comment=("In plc for if ".$zone." to rt") jump-target=("in-plc-s:".$zone.">rt")
				}
				:if ( ($policy)="accept") do={
					add action=accept chain=("in-plc-s:".$zone.">rt") comment=("In plc for if ".$zone." Accept to rt")
				}
				:if ( ($policy)="reject") do={
					add action=reject chain=("in-plc-s:".$zone.">rt") comment=("In plc for if ".$zone." Reject to rt")
				}
			}
			:if ( ($tgt)="all" ) do={
				add action=jump chain=("fw-plc-s:".$zone) comment=("Fwd plc from if ".$zone." to All") jump-target=("fw-plc-s:".$zone.">all")
				add action=jump chain=("in-plc-s:".$zone) comment=("In plc for if ".$zone." to All") jump-target=("in-plc-s:".$zone.">all")
				:if ( ($policy)="accept") do={
					add action=accept chain=("fw-plc-s:".$zone.">all") comment=("Fwd plc from if ".$zone." Accept to All")
					add action=accept chain=("in-plc-s:".$zone.">all") comment=("In plc from if ".$zone." Accept to All")
				}
				:if ( ($policy)="reject") do={
					add action=reject chain=("fw-plc-s:".$zone.">all") comment=("Fwd plc from if ".$zone." Reject to All")
					add action=reject chain=("in-plc-s:".$zone.">all") comment=("In plc from if ".$zone." Reject to All")
				}
			}
		}
	}
}


Conclusion


This script helped me greatly simplify the configuration of complex policies, however, there may still be errors in it. Before use, consult a specialist. In case of detection of side effects, write, we will correct.

All Articles