2

I'm running Fedora 33 on a host (i5 cpu, 8Gb RAM, SSD and hdd) which is set up as a router; it has 5 NICs. I've managed to get dual internet gateways and dual LANs working reasonably well using nftables.

One gateway is DSL with pppoe, the other a cable modem. Both connect and can see the internet. Both LANs can see the internet and provide services which are seen by the internet. IOW, NAT and forwarding are working well.

Here is the problem: I can't figure out how to set up the routing tables. What's going wrong is that whichever gateway has the lowest metric works with NAT and forwarding to its LAN, but it shuts off NAT and forwarding to the other gateway and LAN. I have everything working on only one gateway at a time from the LAN machines' perspective.

root@gata[~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         67.193.x.x      0.0.0.0         UG    100    0        0 coglink
0.0.0.0         206.248.x.x     0.0.0.0         UG    104    0        0 ppp0
10.0.0.0        0.0.0.0         255.0.0.0       U     103    0        0 tekgw
67.193.56.0     0.0.0.0         255.255.248.0   U     100    0        0 coglink
192.168.1.0     0.0.0.0         255.255.255.0   U     102    0        0 coggw
206.248.155.132 0.0.0.0         255.255.255.255 UH    105    0        0 ppp0

I know it's possible to set up routes so that machines on 10.0.0.0 always use ppp0, and machines on 192.168.1.0 always use coglink, but web searches on how to do it have been fruitless. Same with the internet facing interfaces. If someone can point me to a lucid relevant tutorial on IP routing for multiple interfaces, I'd be very grateful.

Nibs Niven
  • 23
  • 4
  • Here's a very old link (with the load balancing part not working anymore and a few questionable parts) but good enough to understand the topic: https://lartc.org/howto/lartc.rpdb.multiple-links.html – A.B Mar 02 '21 at 20:24
  • Thanks for the link, but it leaves core questions unanswered. I don't know what my DSL or cable gateways are, or their network addresses. The DSL is a static IP address but the cable is dynamic. – Nibs Niven Mar 03 '21 at 07:43
  • Show the outputs of commands `ip r ls table all` and `nft list ruleset`. Likely your issue is caused by inaccurate masquerade\snat rule. – Anton Danilov May 14 '21 at 11:48

1 Answers1

1

This answer will use policy routing where the fate of a packet is not determined only by its destination but also (and first) by other factors (here source IP address and/or incoming interface). Policy routing is not available with the Linux-obsolete route command but only with the newer API using ip rule and ip route (along ip link and ip address). The goal is to split a single view of all routes into multiple routing tables where each of them will offer a specific view of the routes intended to be used as if other not interesting parts didn't exist. Among other things, this allows to define multiple default routes to be used at the same time: one per routing table.

By default only this exists, (where the last rule's default table is empty):

# ip rule show
0:  from all lookup local
32766:  from all lookup main
32767:  from all lookup default

Here OP's case can be expressed with this kind of policy routing when handling a packet:

  • packet from coglink or from coggw interfaces use routes for Cogeco dedicated routing table
  • packet from ppp0 or from tekgw interfaces use routes for Teksavvy dedicated routing table

While one could just add rules to handle ppp0 and leave the main routing table handle coglink I'll still add rules for both, for the sake of symmetry.


Completing some blank spots and choosing arbitrary values (to provide commands without syntax error):

  • coglink's local address is arbitrarily chosen as 67.193.56.92/21
  • coglink's gateway is arbitrarily chosen as 67.193.56.1 instead of 67.193.x.x
  • the routing table for Cogeco has the arbitrarily chosen value 7992
  • ppp0's local address is arbitrarily chosen as 206.248.155.133
  • assuming ppp0 is a layer 3 interface (even if over Ethernet, it's still Point-to-Point), so no gateway is needed for proper routing (else add in appropriate places via 206.248.155.132).
  • the routing table for Teksavvy has the arbitrarily chosen value 5645

Each routing table will include only the WAN to use rather than both, but must also include routes to networks that will make use of it. The duplication of these LAN routes is mandatory if SRPF is enabled, else these LAN routes can usually be omitted (as long as they are in the main routing table).

ip route add 67.193.56.0/21 dev coglink table 7992
ip route add default via 67.193.56.1 dev coglink table 7992
ip route add 192.168.1.0/24 dev coggw table 7992
ip rule add iif coglink lookup 7992
ip rule add iif coggw lookup 7992

ip route add default dev ppp0 table 5645
ip route add 10.0.0.0/8 dev tekgw table 5645
ip rule add iif ppp0 lookup 5645
ip rule add iif tekgw lookup 5645

This won't affect the traffic with the router as end node itself, which will continue to use only the main table (and it's default route with lower metric: coglink's) because:

  • (always the case) incoming traffic to the router's own IP addresses is handled in the local routing table looked up first by the policy rule with preference 0. No further tables are looked up.
  • outgoing traffic won't be selected by any of the added policy rules

So some corner cases will fail for the Fedora router only (when not actually routing), for example choosing ppp0's IP address as source with a destination on Internet will still select the route through coglink and won't work correctly. This is what happens when the router answers a ping on its ppp0 IP address.

So to address the title of the question, to have the router itself select the correct route when binding with the IP address on an interface and routing through the other interface, additional rules can be added selecting routing table from source address:

ip rule add from 67.193.56.92 lookup 7992
ip rule add from 206.248.155.133 lookup 5645

Even only knowing a possible range of addresses (eg: retrieved using the whois command) is good enough by using an additional selector iif lo with the special meaning from local system (rather than from lo interface). The two previous rules can be replaced with:

ip rule add from 67.193.48.0/20 iif lo lookup 7992
ip rule add from 206.248.128.0/18 iif lo lookup 5645

For other cases (when the socket is not bound initially), the main routing table's default route with lowest metric will choose the interface (currently coglink) and by default its IP address as source.


Notes and caveat:

  • the NAT rules provided by iptables or nftables are still needed

    With iptables this could look like this now that the outgoing interface is always chosen correctly:

    iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o coglink -j MASQUERADE
    iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o ppp0 -j MASQUERADE
    

    or with nftables and kernel >= 5.5 ingress interface can be used in postrouting:

    mynat.nft (to be used with nft -f mynat.nft):

    table ip mynat
    delete table ip mynat
    
    table ip mynat {
            chain mypost {
                    type nat hook postrouting priority srcnat; policy accept;
                    iif "coggw" oif "coglink" masquerade
                    iif "tekgw" oifname "ppp0" masquerade
            }
    }
    

    Of course choosing simpler NAT rules would work.

  • should interfaces disappear and reappear, or simply go down and up, many settings previously done will have to be reapplied accordingly: routes will disappear and must be reapplied, and routing rules referencing interfaces might perhaps become stale in some cases.

    • So some integration with the tool(s) handling network at boot and later must be done to keep these routing tables and routing rules working.

    • If the tools have limitations, some policy routing rules could be changed to accomodate. For example with the current topology, iif tekgw is (almost but not exactly) equivalent to from 10.0.0.0/8.

  • even if coglink's address and default route's gateway change, these two pieces of information are still available... on coglink interface and on routes (and maybe also in the DHCP client's lease file if DHCP was used). It's just a case of reusing existing data for integration. Here's an example using JSON output and jq:

    address:

    ip -json -4 address show dev coglink primary | jq -r '.[].addr_info[] | "\(.local)/\(.prefixlen)"'
    

    gateway:

    ip -json -4 route show default dev coglink  | jq -r '.[].gateway'
    

    They could even be directly provided by hooks in the tools handling system networking, like NetworkManager.

A.B
  • 11,090
  • 2
  • 24
  • 45
  • Your answer is informative; it has a more elegant way to determine changes to routing than I am currently using. You raise an important point which I ran headlong into: routes change when the gateways go up/down or change upstream. Therefore monitoring of each gateway must be done to catch and rebuild the routes after these changes. – Nibs Niven Aug 30 '21 at 05:12