Hi,
At a routing level the only stuff the kernel looks at is the destination address.
Every packet however first passes through a rule based engine, which is a sequence of rules dictating which routing table to use. Since this is a sequential check the number of these rules should be kept low (for obvious reasons). These rules can be used to set up alternative routing tables and direct packets to not use the main routing table (there is a little known "local" table which the kernel actually uses to route local destined frames before looking at other rules). As an example of what these rules look like:
jkroon@plastiekpoot ~ $ ip rule show
0: from all lookup local
1000: from 192.168.42.204 lookup srcrt_ppp0
1001: from all fwmark 0x1 lookup srcrt_ppp0
32766: from all lookup main
32767: from all lookup default
This is on my desktop, and to the best of my knowledge routeros only exposes the fwmark option here. default is generally empty, and main is primarily used. Rule priorities 1000 and 1001 here is non-default, and RouterOS uses this mechanism in order to implement "marked routing".
Since the routing table only looks at the destination, and Mikrotik only exposes the fwmark rule from above, you have NO WAY other than using the marks you describe to achieve "the right thing". I use a variation of what you describe here, but I believe for your use-case your rule would be sufficient. You will also note that I use fwmark above AS WELL AS from, that's because the initial routing decision is made *prior* to applying source nat (which is used to "undo" DNAT before placing it back on the wire).
https://upload.wikimedia.org/wikipedia/ ... t-flow.svg - this remains one of the best packet flow diagrams I've ever seen for the Linux kernel. This relates because it give you a better understanding of where you can interact with the whole process, and in many cases, where certain processing in the flow of a packet happens, there are explicit statements here that "nat tables are not consulted except for state new" - which is accurate, but what is not mentioned is that this point in the flow is also where previously NATed frames are adjusted again, depending on whether it's the source or destination being adjusted (both happens for all frames, and could simply be a no-op). Consider your DNAT, the destination gets altered, but for return traffic the *source* needs to get "reverted", so DNAT/SNAT simply indicates the change on the ORIGINAL *NEW* frame, the same gets applied to further frames for the same flow in the same direction, and the reverse on future frames in the opposite direction.
So for not-NEW frames, the NAT table locations also applies the following:
PREROUTING - DNAT
OUTPUT - DNAT
INPUT - SNAT
POSTROUTING - SNAT
There are certain (xfrm - or x transformation, mostly ipsec) cases where it's possible for the "same" frame to pass through the same chains multiple times, but this is not he norm in my experience.
So at this point, when the routing decision is made, the frame goed from 10.20.10.1 to external_ip. So the routing rules can't match on the static IP because it's not IN THE FRAME. As such, the ONLY workable solution is to use packet marks. The reason we use both is because for those applications that allows to specify a bind address (eg, ping -I, ssh -b) they will then also follow the "natural" path.
You mark the specific return traffic which is fine, but a more generic approach is to mark the connections on NEW (not the packet) and to then restore the connection mark to the packet mark (fwmark) upon ingress from the appropriate internal interface. This solves both your concerns of:
1. Not having to add additional mark routes for additional DNATs.
2. Service only being accessible through the static connection.