I can successfully use the API to add a firewall filter rule with no problems. The issue is that I’d like to add the rules in the appropriate place.
For example, I’d like to add a filter rule before the default rule that has the comment: “defconf: drop all from WAN”. The difficulty is that the number of the rule is variable, so I can’t say something like “place-before=14” and expect it to behave correctly.
I’ve tried using something like: /ip firewall filter get number=[find comment=“defconf: drop all from WAN”]
That successfully finds the correct rule, but doesn’t return anything that I can figure out how to use.
(For reference, it returns: .id=*3;.nextid=*4;action=drop;bytes=541304;chain=input;comment=defconf: drop all from WAN;dynamic=false;in-interface=ether1;invalid=false;packets=7671 )
If I do a “/ip firewall filter print” then that particular rule shows up as number 8.
From my reading elsewhere, it appears as if the “id” that is returned is not really associated with the order of the rules, but is rather auto-generated for the display, so I think it’s a red herring for this topic.
The box is running 6.40.1.
Am I trying to overcomplicate this? Any suggestions would be greatly appreciated!
The “add” command has an argument called “place-before”. It takes an ID of an item, before which the new one will be placed.
If you want to move an existing item, you need to use the “move” command. It takes two arguments - “numbers”, which is items that will be moved from their current position into the order specified, and “destination”, which is the item above which the items in “numbers” will be placed. Without “destination”, the targeted items are moved to the bottom.
First: why do you need to insert firewall rules? There are other features in RouterOS that you would normally
use to have firewall rules that adapt to changing conditions: IP address list, interface list.
Maybe you can use those instead? (a single rule that is always in place and a list that is updated)
boen_robot: Thanks for your comment. I already mentioned why “place-before” won’t work in this instance (although I guess I could always just use “place-before=0”, but that’s ugly.) I’ll look at the “move” command, but if I understand you correctly, it will have the same problem, wouldn’t it? (The problem being that I have not been able to programmatically figure out which line number to move before.)
pe1chi: I’m not sure why the ‘why’ is that relevant, but, in short, I’m working on a web interface that can automate some things like, for example, creating nat and firewall rules automatically for a VPN or a webserver without having to train people in all the steps that would normally be required. (This cuts down on people forgetting a step, etc.) Obviously, “place-before=0” would be fine for a VPN since that’s where it needs to go anyway, but for mailservers, webservers, etc., it would not be ideal. tldr; I’m abstracting away some of the work in order to increase productivity and decrease errors.
Since I know from another topic you’re using my API client, I can tell you that you can use the Util methods. They implement numbers under the hood. More specifically, you can use Util::find() with a number to get the item at that index, or give a number to Util::move() to implicitly find() the item at that index, and use it in place of “numbers” or “destination”.
f.e.
$util->setMenu('/ip firewall filter');
$util->add(array('place-before' => $util->find(14), 'chain' => 'forward'));//Place this new rule above the 15th item
or for existing items
$util->setMenu('/ip firewall filter');
$util->move(14, 0);//Move the 15th item above the 1st
If you were doing it in another API client, you’d need to call “print” with no query or “find” to get all IDs in their actual order (for menus where the position is significant, replies are guaranteed to come in their actual order), and get the appropriate ID out of that, and place it into the appropriate argument.
If you’re doing the same from scripting, you can use :pick over a “print as-value”, and then get the “.id” out of that, e.g.
Ok that is a valid purpose, although as you already indicate the sequence does not really matter for NAT rules.
Of course, NAT rules must be specific to the port being forwarded.
Note, however, that you do not need a matching firewall filter rule for each dst-nat rule that you create. This can
be handled with a single rule that matches incoming forward traffic with connection-nat-state=dstnat
It is always good to limit the number of filter rules as they have to be matched one-by-one for all traffic until a
match is found (which of course is the reason to put the established/related rule a high as possible in the chain).
“Note, however, that you do not need a matching firewall filter rule for each dst-nat rule that you create. This can
be handled with a single rule that matches incoming forward traffic with connection-nat-state=dstnat”
That’s actually a really good point, and one that I will keep in mind. Thank you! With that said, that feature seems like it might be a nuisance, at times, too. For example: is there an elegant way to handle the situation of opening up an internal server to only certain static IPs on the Internet without a VPN? That situation comes up from time to time, but I have never had to solve it with a Mikrotik. I could always remove the connection-nat-state=dstnat line, or put in permit lines followed by a 0.0.0.0 deny (before the connection-nat-state line), I guess. (I’m guessing that the latter idea would probably be better, performance-wise.)
Is there a better way? Should I open a different thread?
But in short and in general: You can group rules into a custom chain, and jump to certain chains depending on criteria. That way, you limit evaluation only to rules in the custom chain, within which the prior checks are guaranteed to be true. You can think of them as if you’re having a PHP function.
e.g. the following pseudo PHP code
function input(array $packet): string {
if ($packet['connection-nat-state'] === 'dstnat') {
$result = natted($packet);
if ($result) {
return $result;
}
}
return 'drop';
}
function natted(array $packet): string {
if (in_array($packet['src-address'], AddressList::toArray('allowed')) {
return 'accept';
}
return '';
}
to achieve the same effect, but in more complicated scenarios, particularly when otherwise the same check(s) need to be done by many rules, it pays to jump to custom chains.
As Sob already replied: do all your checking in the dst-nat rule(s) and allow only what you want to allow, then pass all dstnat traffic in the filter chain.
Sorry for the delayed response, but I wanted to thank everyone for the input. It’s always nice to learn new/better/varied ways of doing things, and I appreciate the assistance. Everything is working now.