Completing information not available in OP:
Let's see what's needed for a flow initiated from outside:
original incoming
Apparently nothing special: just dnat everything to the single destination
reply
conntrack stored a conntrack lookup entry for the flow in the previous bullet. But what's missing is the interface it came from. As the same public IP address could have chosen to connect once through vdsl and an other time through ftto this is specific to each flow, not to the remote client. So conntrack needs to be assisted to keep this missing piece of information. For this role, a conntrack mark can be used. In this setup each mark value will be equivalent to an interface name.
Its use is described for example in this blog: Netfilter Connmark. This allows to assign a specific mark at the previous step (incoming) to the flow (rather than just the packet) and use it as selector for outgoing.
This selection can't be done in post routing: this is after the interface choice is needed. It must happen in pre routing and affect the routing stack. The ip rule s must be altered to use marks too
In the end here's what's done:
keep only 192.168.220.128 for the example server
add a type filter hook prerouting chain to handle marks. As it's a router-only case (OP is not interested in traffic initiated by or arriving to the router itself), nothing is needed in the output hook. Here how it will be working:
- if the conntrack mark is set, that means this flow was already handled previously: set the packet mark and skip next settings
- else if it's a new flow, if relevant, set a mark related to the incoming interface to the packet and also in the conntrack entry.
change ip rules to use marks instead of addresses, and complete routing tables
no need to use multiple tables for different hook types, that's an iptables restriction that limits features available. For example, sets and maps have a scope limited to a single table. To factorize rules by using a set, all chains using this set must be in the same table.
I'll use a map to store the association from port to backend server to simplify management.
Nitpicking: the legacy priority of nat/prerouting is -100 rather than 100, this doesn't really matter unless also interacting with iptables's nat.
First, change the ip rule s to the rules and routes below instead: a mark is used as selector instead of any IP address. It will be set for each new flow by nftables, conntrack mark for stateful mechanism and retrieved for each packet belonging to this flow.
ip rule add fwmark 101 lookup vdsl
ip rule add fwmark 102 lookup ftto
If Strict Reverse Path Forwarding is enabled, all routes should be available in each table (hence the duplicated LAN route added below for each routing table):
ip route add table vdsl 192.168.220.0/23 dev lan0
ip route add table vdsl default via vdslgw dev vdsl onlink
ip route add table ftto 192.168.220.0/23 dev lan0
ip route add table ftto default via fttogw dev ftto onlink
Still if in the case of SRPF, the 11 years old, only recently documented, src_valid_mark sysctl toggle must be enabled:
sysctl -w net.ipv4.conf.lan0.src_valid_mark=1
sysctl -w net.ipv4.conf.vdsl.src_valid_mark=1
sysctl -w net.ipv4.conf.ftto.src_valid_mark=1
New ruleset replacing OP's ruleset:
table ip multihomed {
map port2ip {
type inet_service : ipv4_addr
elements = {
25 : 192.168.220.128
}
}
chain handlemarks {
type filter hook prerouting priority -150; policy accept;
ct mark != 0 meta mark set ct mark accept
ct state new meta mark set iifname map { "vdsl" : 101, "ftto" : 102 } ct mark set meta mark accept
}
chain prenat {
type nat hook prerouting priority -100; policy accept;
iifname { "vdsl", "ftto" } dnat to tcp dport map @port2ip;
}
chain postnat {
type nat hook postrouting priority 100; policy accept;
ip saddr 192.168.220.0/23 oifname { "vdsl", "ftto" } masquerade;
}
}
To easily add a (single) service reachable from both public addresses, one can add an entry in the port2ip map. For example the http port mapped to server 192.168.220.130:
nft add element ip multihomed port2ip '{ 80: 192.168.220.130 }'
Note: the last masquerade rule, for traffic initiated from LAN or router won't be subject to policy routing. Usual routing will apply always using the same single interface.