ROSv7 How to correctly create custom routing table?

Let's not ask why, because it worked before, and we want to achieve the same effect in ROSv7.

What I’m trying to achieve:

Create custom table where want add only wanted routings, not only default routing (this important!)

  1. So I create routing table with FIB (because another type not worked at all)

  2. I add routes:
    add dst-address=192.168.0.0/24 gateway=%bridgeLocal routing-table=mytable
    add dst-address=0.0.0.0/0 gateway=%vpn routing-table=mytable

  3. Then I create mangle rule:
    add chain=prerouting action=mark-routing new-routing-mark=mytable src-address=192.168.0.11

  4. Now 192.168.0.11 goes to out through %vpn, BUT it’s loose connection to 192.168.0.1 - router IP address because for some reason ROSv7 do not look into 192.168.0.0/24 route in this table.

Yes, I know, I can create rule with match for not local IP’s and subnets(and it’s work), but main question WHY it’s not work now? I really want to have Linux-like routing how it was before.

P.S. Yes, I know how to go around, just want understand why added routing not work. I try it even with gateway=192.168.0.1 / change distance for routing(and default) / pref-source / scope=15 target-scope=10 and etc

Not fully fleshed requirements are always hard to answer.
Do you mean that you want 192.168.0.11 to go out VPN for all traffic, except retain the ability to execut local local traffic??

The first way I would consider ( to avoid the hassle of mangling ) is use routing rules. The first method is specific only to one subnet....... ORDER IS CRITICAL!

/routing rule
add src-address=192.168.0.0/24 dst-address=192.168.0.0/24 action=lookup-only-in-table table=main 
add src-address=192.168.0.11/32 action=lookup-only-in-table table=mytable  { anything else out table }

If you have multiple subnets and 192.168.0.11 needs either:
a. to reach other local subnets
OR
b. other local subnets need to reach it,

THEN this applies:

/routing rule
add min-prefix=0 action=lookup-only-in-table table=main  { process local to local traffic first }
add src-address=192.168.0.11/32 action=lookup-only-in-table table=mytable { anything else out table }

If you need mangling then do it this way.
Lets say you have subnets A, B, C
then
/ip firewall address-list
add address=SubnetA list=connected
add address=SubnetB list=connected
add address=SubnetC list=connected

/ip firewall mangle { order is critical rule has to go first }
add action=accept chain=prerouting src-addess-list=connected \
    dst-address-list=connected
add action=mark-routing chain=prerouting src-address=192.168.0.11 \
    new-routing-mark=mytable  passthrough=no

/ip route
add dst-address=0.0.0.0/0 gateway=wireguard1  routing-table=mytable

Yeah, thanks, with routing rules it’s work, I know… but why it’s not fully(because not work access to 192.168.0.1, but correctly work outbound traffic) work with mangle? It works in ROSv6.

As I except:

192.168.0.11 → 192.168.0.1 → “prerouting mangle” change routing table → route with mytable → forward → postroute → out

In back way it’s must work with main routing table where all routes present, so traffic must flow to normal…..

Seems traffic for some reason do not go to “input” on route decision even route mark was set in preroute, so all traffic which must go to input chain goes to 0.0.0.0/0 destination, in other words it’s goes to “forward” chain, not input. That’s why I don’t understand why it’s works so different with ROSv6 where it’s works very-very same with Linux netfilter flow https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg

Because things change, if you cannot handle it, I suggest one lives in a hut with a fireplace and no technology. :wink:

Yeah I know it sucks to have to learn new tricks but overall the improvements are better.
In version 7, mangling overrides any routing rules if there is overlap.

Other than that, there would be no change to the advice I have provided for vers7 or ver6 except for some slight variations.. as shown below

Version 6 Approach

(one does not create a table)

/ip firewall address-list
add address=SubnetA list=connected
add address=SubnetB list=connected
add address=SubnetC list=connected

/ip firewall mangle
add action=accept chain=prerouting src-address-list=connected dst-address-list=connected
add action=mark-routing chain=prerouting src-address=192.168.0.11  \
     new-routing-mark=mytable passthrough=no

/ip route
add dst-address=0.0.0.0/0  gateway=wireguard1  routing-mark=mytable

+++++++++++++++++++++
in both cases I would probably add the two rules prior to the fastrack rule,in order to keep the fastrack rule in place.

/ip firewall  {forward chain}
add action=accept chain=forward src-address=192.168.0.11
add action=accept chain=forward dst-address=192.168.0.11
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
    connection-state=established,related hw-offload=yes

Alternatively one could mangle as follows ( in both versions):

/ip firewall mangle
add action=accept chain=prerouting src-address-list=connected dst-address-list=connected
add action=mark-connections chain=forward connection-mark=no-mark \
     src-address=192.168.0.11 new-connection-mark=SpecialTraffic passthrough=yes
add action=mark-routing chain=prerouting connection-mark=SpecialTraffic \
    new-routing-mark=mytable passthrough=no

and the forward chain firewall rule would look like

/ip firewall  {forward chain}
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
    connection-state=established,related hw-offload=yes connection-mark=no-mark

But it’s really seems bug, because it’s break official routing diagram:

Here after prerouting and change routing mark for packet next is “Routing decision”, and after this it’s goes to “input” or to “forward”. I add few log rules into input and postrouting chains to detect where does the traffic go. And for local 192.168.0.1 IP it’s goes to outbound interface!

To summarize - it’s broken in ROSv7. Yes, it’s can be go around, but it breaks flexible policy way for routing.

@zidane
Can you check and describe what happens using this diagram as reference (specific for version 7)?:

I am not sure to understand if there is the need to add a note to it or if what you experience is "normal/expected".

I took some time to play with this, and could of course, reproduce your finding. After some more tests I have the following hypothesis that might explain what you could observe:

As we know MikroTik has done a lot of work on VRF in RouterOS 7, even in recent versions, and my guess from what I can see, is that there is now a 1:1 mapping between VRF and routing table with FIB. We already know that when you create a VRF instance, a FIB routing table is dynamically added. We can also see that the main routing table actually has the associated main VRF that contains all interfaces.

But what if the reverse is also true. What if each time you create a FIB routing table, an implicit matching VRF instance is created in the background, it's just that this VRF contains no interfaces at all, and because it has no interfaces, the router also has no IP addresses in in this VRF.

There are several places in RouterOS where beside being able to specify @vrf you could also use @routingTable. Even right now in 7.20.4, tools like /ping and /tool/traceroute have the vrf= parameter, and when you press TAB after writing vrf=, you'll will only see the VRFs offered as completion. However, both those tools allow you to specify vrf=routingTable without an explicitly created VRF instance, and that work without problem.

(Note: in 7.21 those tools now no longer have the vrf= parameter but use the @ syntax)

If you consider that when writing new-routing-mark=xyz in action=mark-routing mangle rule, you are not specifying the routing table, but the VRF (including the implicit, hidden VRF) then what you observed can easily be explained. Let's look at the diagram from @jaclaz thread:

At #4 (mangle prerouting) you set the routing mark that choose the "VRF" mytable (implicit VRF), at step #12 a decision is made for dst-addr=self?. But, as I wrote above, in this implicitly created VRF mytable the router has no interfaces and no IP addresses. So, this check yields "N" and the next step in the flow is #13 (mangle forward), the INPUT chain is completely bypassed. The FORWARD chain will only send the packets to the out interfaces, even when you've explicitly added the dst-address=192.168.0.0/24 route, the gateway is the out interface bridgeLocal.

To give even more weight to the hypothesis, you can do the following experiment on your router:

  • We'll create a VRF. But because when you create VRF instances, you need to associate interfaces to them, first we have to add a dummy interface, let's create a dummy bridge:

    /interface bridge
    add name=dummy protocol-mode=none
    

    We do not assign any IP address at the moment, to simulate the "router has no IP addresses" situation.

  • Add a mytable2 VRF instance containing that dummy bridge:

    /ip vrf
    add interfaces=dummy name=mytable2
    

    As we know, this automatically adds the dynamic mytable2 FIB routing table.

  • Now we add the same default route that you added to mytable above, but to mytable2:

    /ip route
    add dst-address=0.0.0.0/0 gateway=vpn@main routing-table=mytable2
    

    Note: the dst-address=192.168.0.0/24 route doesn't matter for this test, you can add it or not, there is no difference. I just write it here for completeness:

    /ip route
    add dst-address=192.168.0.0/24 gateway=bridgeLocal@main routing-table=mytable2
    
  • Of course, you also need to edit your mangle rule, so that it uses the routing mark mytable2 instead of mytable:

    /ip firewall mangle
    add chain=prerouting action=mark-routing new-routing-mark=mytable2 src-address=192.168.0.11
    

If you do the tests now, you'll see everything behaves exactly like your original configuration. Client 192.168.0.11 uses the vpn gateway to go to the internet. But is unable to access 192.168.0.1.

And the reason it's unable to access 192.168.0.1 is because in the mytable2 VRF, the router doesn't have the IP address 192.168.0.1. At step #12 of the flowchart, the comparison produces false. The packet will be sent to vpn (if you don't have the 2nd route) or to bridgeLocal (if you have the second route).

How can you make it works? Very easy, we just give the router the IP address 192.168.0.1 in the VRF mytable2!

/ip address
add address=192.168.0.1/32 interface=dummy network=192.168.0.0

Note that the netmask or network doesn't matter here, we just need the router to have the IP address 192.168.0.1 on one of the interfaces of the mytable2 VRF, in this case the dummy bridge is the perfect candidate interface.

Once this change is made, at step #12 of the flowchart, the check will produce true and the correct INPUT chain will be used (#6 mangle input is the next step). You'll see that 192.168.0.11 now has no problem accessing the router at 192.168.0.1 anymore.


I think if we accept that "creating FIB table creates implicit VRF with no interfaces" then the behavior you observed can be explained quite well.

As for a quick "fix" if you don't want to create VRF with dummy interfaces. Just add dst-address-type=!local to your original mangle rule. No extra routing rules needed.

If I get this right, the original mangle rule:
add chain=prerouting action=mark-routing new-routing-mark=mytable src-address=192.168.0.11

would apply the mark to ALL packets coming from 192.168.0.11, and then which route to take is demanded to the (added) fib routing table.

But it doesn't work.

The routing rules anav suggested do instead a distinction based on destination, both sets:

/routing rule
add src-address=192.168.0.0/24 dst-address=192.168.0.0/24 action=lookup-only-in-table table=main 
add src-address=192.168.0.11/32 action=lookup-only-in-table table=mytable  { anything else out table }

and

/routing rule
add min-prefix=0 action=lookup-only-in-table table=main  { process local to local traffic first }
add src-address=192.168.0.11/32 action=lookup-only-in-table table=mytable { anything else out table }

distinguish between local destination and outside destinations.

As well the mangle+firewall rules make this distinction early.

The revised mangle rule proposed by CGGXANNX (should be):
add chain=prerouting action=mark-routing new-routing-mark=mytable src-address=192.168.0.11 dst-address-type=!local

has exactly the same effect, i.e. traffic is marked differently depending on destination before it will ever reach a routing table.

Yes, but OP already knows about the routing rules or exceptions that can be made to the mangle rules (as he wrote in the other posts). What he didn't understand and considered a bug is the behavior that the router is not reachable if the non-main routing table is used, even with the 192.168.0.0/24 route added.

My post was an attempt to guess the reason for that behavior.

@CGGXANNX

Interest theory, but seems it’s better to switch back to ROSv6 and wait before all this things will be corrected and stabilized and precisely documented, because it’s very confuse when you use RoutingTable, and routing rule works well and as expected, but mangle rules also get routing mark(it’s complete to RoutingTable names), but it’s works different. It might be better to rename it to VRF-mark, then there won't be any questions.

@jaclaz

About "Here it must look in to selected table": Yes of course it does that. But don't forget the routing tables are only used for forwarding, not for local (router-local) routing. The table is used to determine the next-hop to forward packet to if that packet is not destined for the router. Remember that "bridgeLocal" as interface is something that the router uses (like a door) to send packet to the outside or used to identify the source of incoming packet (enter from this door). It's not used to identify where inside the router the packet should go (it's not identifying the kitchen or the bathroom).

The routing tables choose the front house door or the gate at the back of the garden. While at #12 it's decided whether the packet should stay in the house or leave the house (the "is the packet destined for the router"-check).

It's documented in the documentation, beside the fact the FIB means Forwarding Information Base:

Routing table lookup

FIB uses the following information from the packet to determine its destination:

  • source address
  • destination address
  • source interface
  • routing mark

Possible routing decisions are:

  • receive packet locally
  • discard the packet (either silently or by sending an ICMP message to the sender of the packet)
  • send the packet to a specific IP address on a specific interface

Run routing decision:

  • check that the packet has to be locally delivered (the destination address is the address of the router)
  • process implicit policy routing rules
  • process policy routing rules added by a user
  • process implicit catch-all rule that looks up the destination in the ''main'' routing table
  • the returned result is "network unreachable"

The result of the routing decision can be:

  • IP address of nexthop + interface
  • point-to-point interface
  • local delivery
  • discard
  • ICMP prohibited
  • ICMP host unreachable
  • ICMP network unreachable

As you can see, if the packet is to be locally delivered, then routes you put in the routing table are irrelevant. And the check for local delivery only compares the destination addresses with the list of router's addresses.

As I wrote above, the issue you see is most probably because the router has no IP address to compare to when in the implicit VRF. As a result, the packet is not for local delivery, and the routes are used to determine the next-hop and exit interfaces.

And yes, I agree that they might need to rename new-routing-mark to new-vrf once they are done with putting VRF everywhere in RouterOS. But there are issues with breaking old scripts and tutorials.


And as we can see, if when the destination is one of the router's addresses, the routing tables are not important, then it's very logical to add dst-address-type=!local to all the mangle rules that try to manipulate the routing.

@CGGXANNX

Thanks, yeah, I understand it now, and root of problem for now is confusing RT/VRF, because only switch table is will leave packet in the same “interfaces space” and all will work fine.

About “new-vrf” and break old scripts → not change old, but just add new, so old one will only switch table, and new one “new-vrf” will also switch vrf. This difference can be done just by one BIT internally.

Definitely the added "dst-address-type=!local" is (IMHO) the cleanest solution (if using mangle).
Routing rules seem to me easier, but of course it depends on personal preferences, and lacking experience with mangle rules, I would have thought at routing rules first as they appear more suitable, even in the name.

A few questions:

  1. with the vrf and the added dummy interface it works as expected because it is given the IP address 192.168.0.1(/32) or because that address creates a dynamic route to 192.168.0.0 (with lower distance)?
  2. this dummy interface is in the example a bridge, but could it be also another type of interface?
  3. and if yes, which type of interfaces can be used for this kind of tricks?

No, it doesn't matter, it works the same if I added

/ip address
add address=192.168.0.1/32 interface=dummy network=10.20.30.40

In this case, the added connected route to the mytable2 routing table has the totally unrelated dst-address=10.20.30.40/32. Nothing for 192.168.0.X/XX exists in that table. And it will still work normally. As I wrote above, the content of the routing table is not consulted when the destination is an IP address of the router. It can contain anything. The content is only used if it decided that the packet is not for the local router but needs to be forwarded elsewhere.

You can use any interface that allow you to add to a VRF. But the dummy empty bridge probably has the least side effect.

I am having an ongoing conversation with MikroTik Support about the issue in the OP. I hope they'll clarify what really happens the background and maybe update the documentation.

Thanks, all is clear now.

Faced similar issue after updating to Ros7.20, where my mangles suddenly stopped working. After trying different mangles configuration without success, I finally “moved” all configuration into /routing/rules.

I've created a support ticket asking MikroTik to clarify the behavior that OP sees in this thread. Support has summarized the behavior and I am pasting the extract from their responses here, because it's more detailed than that is in the documentation. In these situations (what in quotes are MikroTik's words):

In summary:

  • linux splits "main" table and "local" table, where local table contains all locally reachable addresses from the main table.
  • "main" can reach only addresses from internal "local" table.
  • there is no "local" table for any of other VRFs except main
  • VRF can reach only addresses that are set on VRF interfaces
  • mangle rule overrides any subsequential routing rules

Further beta versions will expose the use of "local" table and will have the possibility to override the sequence of rules, current behavior represented in routing rules is like this:

[admin@CCR2004_2XS_111] /routing/rule> print
Flags: X - disabled, I - inactive; * - default
0 * action=mangle

1 * action=lookup vrf

2 * action=unreachable vrf

3 * action=lookup table=local

4 * action=lookup table=main

When VRFs are involved, and the destination address is one of the router's addresses (but can also be an address not in the chosen VRF):

  • Setup: you have a mangle that redirects to VRF and VRF does have an address
    Result: first rule performs the mangle action and due to adjusted routing mark performs vrf lookup. destination is resolvable in vrf -> process the packet locally

  • Setup: you have a mangle that redirects to VRF and VRF does not have an address
    Result: first rule performs the mangle action and due to adjusted routing mark performs vrf lookup. destination cannot be resolved -> drop the packet

  • Setup: you have a mangle that redirects to VRF and VRF does not have an address but has a default route
    Result: first rule performs the mangle action and due to adjusted routing mark performs vrf lookup. destination resolved by defautl route-> forward the packet

When routing mark does not point to a VRF:

  • Setup: You have mangle rule, you try to reach forwarded address that cannot be resolved in custom table
    Result: mangle action performed, lookup in custom table -> destination is not resolved, fallback to the main table -> packet is forwarded

  • Setup: You have mangle rule, you try to reach local address that cannot be resolved in custom table
    Result: mangle action performed, lookup in custom table -> destination is not resolved, fallback to the main table -> destination is resolved using routes in the main table (this part is important, because fallback is to the main table not "local" table)

Thus (the mentioned exception was dst-address-type=!local):

As you already mentioned yourself there must be exception for "local" traffic when setting mangle rules (unless there is specific need to override resolving destinations in local table).

When there is no mangle rules:

  • Setup: You do not have mangle rule, you try to reach forwarded address
    Result: mangle action skipped, vrf lookup skipped, lookup in "local" table skipped, resolve the destination in main table -> packet is forwarded

  • Setup: You do not have mangle rule, you try to reach local address
    Result: mangle action skipped, vrf lookup skipped, lookup in "local" table resolvse the destination -> packet is processed locally

And this is their conclusion:

That is the current behavior that in current version cannot be altered.

From my understanding of reading through this, the case in the opening post is the bolded paragraph above. It falls back to main and not local thus there is no match with the local addresses.

So it's NOT a hidden VRF like I speculated!

>>So it's NOT a hidden VRF like I speculated!

Well, that’s going very strange because it’s worked with routing rules, but seems it’s switch table on routing decision…. may be rules apply after some tings already happen, or may be we still have “local” table context attached at this point. And when it’s happen with mangle - it’s already without “local” on routing decision.

@CGGXANNX
It seems to me that your speculation about "implied, hidden vrf" has been replaced by "implied, hidden local table", not that much of a difference, all in all.

But your speculation was easy to understand, the "right" explanation is - to say the least - convoluted.

I've been following Mikrotik's vrf journey for some time now. It's generally heading in a predictable and right direction. Unfortunately this breaks some of the v6 compatible assumptions, while transitioning towards the Linux mainstream.

They are trying to do this gently, but exposing the vrf interfaces, removing the routing table option from features and substituting it with vrf, the addition of vrf support for many/most of the internal services, traces out a clear line.

Exposing the local table is one of the logical next steps.

(I would have bet a significant amount of money when initially reading this thread that the one given was going to be the explanation for this behavior.)

Linux's view of vrfs is nice and consistent, so the change is ultimately for the better. While this is going on, things will unfortunately keep changing little by little