Found a solution. This solution should work for any linux service that can't listen on specific interfaces, but only on all (0.0.0.0) or one particular, like MySQL, OpenVPN and many others. So we make the service listen on one iface and add netfilter/iproute2 rules to redirect all requests for the same protocol and port on another iface to our service on the first iface.
The "session" (despite being UDP in the OpenVPN case) is actually maintained by netfilter, and there is a module conntrack that permits to reference packets from a specific session. In this case I added a rule for OUTPUT in mangle table to mark all packets from the DNATed sessions with a mark. And then I use this mark to route the packets.
So, the commands are:
Define the variables
iface_int2=eth2 # the second internal iface
ip_int2=xx.xx.xx.xx # the IP of the second internal iface
proto=udp # the protocol of the connection
service_port=1194 # the incoming service port
ip_int1=yy.yy.yy.yy # the IP of the first internal iface
ip_gw2=xx.xx.xx.1 # the IP of the default gateway for the second internal iface
This command instructs netfilter to overwrite the destination IP of the incoming connections on our second iface.
iptables -t nat -A PREROUTING -i $iface_int2 -d $ip_int2 -p $proto --dport \
$service_port -j DNAT --to $ip_int1
This command instructs netfilter to set-mark for the outgoing packets (the reply of the service) of the overwritten (DNATed) incoming connection. --ctorigdst
is the original (pre-DNATed) destination IP of the incoming connection
iptables -t mangle -A OUTPUT -p $proto --sport $service_port -m conntrack \
--ctstate DNAT --ctorigdst $ip_int2 -j MARK --set-mark 0x75
This command instructs iproute2 to route set-marked packets via the route definitions of the table 100. Prio is necessary to set the highest priority for this rule, as it's very specific and won't interfere with other rules. If prio is not specified, the routing rules for the first internal iface may get higher priority.
ip rule add prio 10 fwmark 0x75 table 100
This command adds a default gateway to the table 100
ip route add default via $ip_gw2 table 100
For all this to work, it's necessary to lessen the grip of the return path filter on the second internal iface.
# rp_filter - INTEGER
# 0 - No source validation.
# 1 - Strict mode as defined in RFC3704 Strict Reverse Path
# Each incoming packet is tested against the FIB and if the interface
# is not the best reverse path the packet check will fail.
# By default failed packets are discarded.
# 2 - Loose mode as defined in RFC3704 Loose Reverse Path
# Each incoming packet's source address is also tested against the FIB
# and if the source address is not reachable via any interface
# the packet check will fail.
echo 2 > /proc/sys/net/ipv4/conf/$iface_int2/rp_filter
# -OR-
sysctl -w "net.ipv4.conf.$iface_int2.rp_filter=2"
# -OR-
echo "net.ipv4.conf.$iface_int2.rp_filter=2" >> /etc/sysctl.conf
sysctl -p /etc/sysctl.conf