Configure two network cards in a different subnet on RHEL 6, RHEL 7, CentOS 6 and CentOS 7

When configuring a Linux host running either Red Hat Linux 6, Red Hat Linux 7, CentOS 6 or CentOS7 with two network interface cards (NIC) that each have an IP address in a different network or subnet, you could end up in a situation where one of the IP addresses isn’t reachable outside it’s own network. Both IP’s will be responding to a ping from another host in the same network as those IP addresses but only one is responding to ping from another network. On most other distributions, like Debian, this issue, which is caused by asymmetric routing, doesn’t seem to exist.

When you’re experiencing this issue, it is most probably caused by asymmetric routing. In short this means that the interface that has received a packet doesn’t respond to the source of the packet by itself but via another interface. While this causes one of the interfaces to be unavailable from the outside on Red Hat based distributions, the problems with asymmetric routing also do exist on the other distributions although it seems to work fine.

Asymmetric routing

Asymmetric routing isn’t a big problem but it can cause issues related to NAT on firewalls and it has some impact on network performance.
routing_symmetricThe above image shows asymmetric routing: a packet sent from client A to the server, enters the server via network-interface eth0 but, due to the routing, it’s routed back to client A via interface eth1 of the server. For the client, it looks like everything is working normal. Packets do flow in and out from the server. It’s clear that interface eth1 will have a higher load that eth0 and while you’re thinking that you have two full 1Gbit interfaces, the speed is limited to the speed of eth1.

rp_filter

Since RHEL 6 (and CentOS 6), asymmetric routing doesn’t work anymore out of the box. The cause of the issue is a change in the default value for kernel parameter rp_filter.

Rp_filter stands for reverse path filtering. The reverse path filter will check if the source of a packet that was received on a certain interface is reachable trough the same interface it was received. The purpose is to prevent spoofed packets, with a changed source address, not being processed/routed further. In a router it could also prevent routing packets that have a private IP as source to the internet as they obviously will never find their way back.

Since RHEL 6 and its derivative CentOS 6, rp_filter, which can be controlled by kernel parameters, is set on a default value of 1. This means that the rp_filter is operational in strict mode and does exactly what it is designed for.

Possible value are:

  • 0: No source validation
  • 1: Strict mode (failed packets are discarded), described in RFC3704
  • 2: Loose mode, only discards the packet when it isn’t routable over any of the interfaces on the host.

To test this behavior, I set up a system that is connected to two networks: 192.168.0.0/24 and 192.168.1.0/24. I have a client in both networks and a client which is in another network.

The server:

[jensd@server ~]$ ip a|grep "inet "
    inet 127.0.0.1/8 scope host lo
    inet 192.168.0.10/22 brd 192.168.0.255 scope global ens192
    inet 192.168.1.10/24 brd 192.168.1.255 scope global ens224
[jensd@server ~]$ ip route
default via 192.168.0.1 dev ens192
169.254.0.0/16 dev ens192  scope link  metric 1002
169.254.0.0/16 dev ens224  scope link  metric 1003
192.168.1.0/24 dev ens224  proto kernel  scope link  src 192.168.1.10
192.168.0.0/24 dev ens192  proto kernel  scope link  src 192.168.0.10
[jensd@server ~]$ sysctl -a|grep rp_filter
...
net.ipv4.conf.all.arp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.arp_filter = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.ens192.arp_filter = 0
net.ipv4.conf.ens192.rp_filter = 1
net.ipv4.conf.ens224.arp_filter = 0
net.ipv4.conf.ens224.rp_filter = 1
net.ipv4.conf.lo.arp_filter = 0
net.ipv4.conf.lo.rp_filter = 0

Ping to the server from a client in the 192.168.0.0/24 network:

[jensd@clienta ~]$ ip a|grep "inet "
    inet 127.0.0.1/8 scope host lo
    inet 192.168.0.5/24 brd 192.168.0.255 scope global eth0
[jensd@clienta ~]$ ping 192.168.0.10 -c 1
PING 192.168.0.10 (192.168.0.10) 56(84) bytes of data.
64 bytes from 192.168.0.10: icmp_seq=1 ttl=64 time=0.300 ms

--- 192.168.0.10 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.300/0.300/0.300/0.000 ms

Ping to the server from a client in the 192.168.1.0/24 network

[jensd@clientb ~]$ ip a|grep "inet "
    inet 127.0.0.1/8 scope host lo
    inet 192.168.1.5/24 brd 192.168.1.255 scope global eth0
[jensd@clientb ~]$ ping 192.168.1.10 -c 1
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
64 bytes from 192.168.1.10: icmp_seq=1 ttl=64 time=0.362 ms

--- 192.168.1.10 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.362/0.362/0.362/0.000 ms

Ping to both IP’s from a client in another network:

[jensd@clientc ~]# ip a|grep "inet "
    inet 127.0.0.1/8 scope host lo
    inet 192.168.2.5/24 brd 192.168.2.255 scope global eth0
[jensd@clientc ~]$ ping 192.168.0.10 -c 1
PING 192.168.0.10 (192.168.0.10) 56(84) bytes of data.
64 bytes from 192.168.0.10: icmp_seq=1 ttl=63 time=1.09 ms

--- 192.168.0.10 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 1ms
rtt min/avg/max/mdev = 1.093/1.093/1.093/0.000 ms
[jensd@clientc ~]$ ping 192.168.1.10 -c 1
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.

--- 192.168.1.10 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 10000ms

As you can see, both IP’s are reachable from within the same network but only one, the one that has it’s default gateway in the routing table, is reachable from other networks.

In the output from the server, you can see that rp_filter for both interfaces has a value of 1, strict mode.

The quick and dirty solution

The easiest solution is just to change the value of rp_filter to allow packets to flow out of another interface that the source interface.

Now, let’s change the value of rp_filter to loose mode, value 2 and see what happens:

[jensd@server ~]$ sudo sysctl net.ipv4.conf.all.rp_filter=2
net.ipv4.conf.all.rp_filter = 2
[jensd@clientc ~]$ ping 192.168.0.10 -c 1
PING 192.168.0.10 (192.168.0.10) 56(84) bytes of data.
64 bytes from 192.168.0.10: icmp_seq=1 ttl=63 time=0.282 ms

--- 192.168.0.10 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.282/0.282/0.282/0.000 ms
[jensd@clientc ~]$ ping 192.168.1.10 -c 1
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
64 bytes from 192.168.1.10: icmp_seq=1 ttl=63 time=0.550 ms

--- 192.168.1.10 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.550/0.550/0.550/0.000 ms

With a minimum of effort, both IP addresses are reachable from the outside. To make the change to rp_filter permanent, you can do the following:

For RHEL 6 and CentOS 6:

Edit /etc/sysctl.conf and change the value for rp_filter:

# Kernel sysctl configuration file for Red Hat Linux
#
# For binary values, 0 is disabled, 1 is enabled.  See sysctl(8) and
# sysctl.conf(5) for more details.

# Controls IP packet forwarding
net.ipv4.ip_forward = 1

# Controls source route verification
net.ipv4.conf.default.rp_filter = 2
...

For RHEL 7 and CentOS 7:

[jensd@server ~]$ echo "net.ipv4.conf.all.rp_filter = 2"|sudo tee /etc/sysctl.d/99-rp_filter.conf

The best solution

Besides the quick solution, there is also a better solution. While changing the value for rp_filter is getting both interfaces and IP-addresses to respond from other networks, the setup is still asymmetric. The best solution is to get rid of the asymmetric routing and let each interface route it’s own packets to the default gateway.

The goal is to become symmetric routing:

routing_asymmetric

Each interface on the server should have it’s own default gateway, which allows that interface to reply itself to incoming packets from other networks.

A normal routing table can only have one default gateway. This is quite logical since it’s the place where to send packets that do not match anything else in the rest of the table. To be able to have two default gateways, one for each interface, you need to setup policy based routing.

Policy based routing allows you to have multiple routing tables. Which table is used, depends on a set of rules.

To setup policy based routing for our example case, we will use two policy based tables. While it is possible to give a nice name to the tables (in /etc/iproute2/rt_tables), it’s not really when you only plan to have a few. Without a name, the tables are automatically created when you’re adding something to them.

Let’s start with adding a route for the network itself (link) and one for the default gateway for each interface. ens192 (192.168.0.10) will use table 1, ens224 (192.168.1.10) will use table 2.

[jensd@server ~]$ sudo ip route add 192.168.0.0/24 dev ens192 tab 1
[jensd@server ~]$ sudo ip route add 192.168.1.0/24 dev ens224 tab 2
[jensd@server ~]$ sudo ip route add default via 192.168.0.1 dev ens192 tab 1
[jensd@server ~]$ sudo ip route add default via 192.168.1.1 dev ens224 tab 2

To define when table 1 or 2 will be used, we’ll add a rule, based on the source of the packet to the policy and refresh the policy based routing:

[jensd@server ~]$ sudo ip rule add from 192.168.0.10/32 tab 1 priority 100
[jensd@server ~]$ sudo ip rule add from 192.168.1.10/32 tab 2 priority 200
[jensd@server ~]$ sudo ip route flush cache

To check if we did everything correctly, let’s list the tables and the rules:

[jensd@server ~]$ ip route show tab 1
default via 192.168.0.1 dev ens192
192.168.0.0/24 dev ens192  scope link
[jensd@server ~]$ ip route show tab 2
default via 192.168.1.1 dev ens224
192.168.1.0/24 dev ens224  scope link
[jensd@server ~]$ ip rule show
0:      from all lookup local
100:    from 192.168.0.10 lookup 1
200:    from 192.168.1.10 lookup 2
32766:  from all lookup main
32767:  from all lookup default
[jensd@server ~]$ ip route
default via 192.168.0.10 dev ens192
169.254.0.0/16 dev ens192  scope link  metric 1002
169.254.0.0/16 dev ens224  scope link  metric 1003
192.168.1.0/24 dev ens224  proto kernel  scope link  src 192.168.1.10
192.168.0.0/24 dev ens192  proto kernel  scope link  src 192.168.0.10

As you can see in the output from ip rule show, our policy based tables have a higher priority than the main table, which can be viewed with ip route. Nevertheless it’s import to still have a default route in the main table since packets leaving the machine itself can have a source IP of 0.0.0.0 and would not match any of the rules in our policy.

Make the changes permanent

Up to now, the changes would get lost after a reboot or restart of the networking. To make the changes permanent, create a route and rule file for every interface. For the above example, the contents would look like this:

[jensd@server ~]$ cat /etc/sysconfig/network-scripts/route-ens192
192.168.0.0/24 dev ens192 tab 1
default via 192.168.0.1 dev ens192 tab 1
[jensd@server ~]$ cat /etc/sysconfig/network-scripts/route-ens224
192.168.1.0/24 dev ens224 tab 2
default via 192.168.1.1 dev ens224 tab 2
[jensd@server ~]$ cat /etc/sysconfig/network-scripts/rule-ens192
from 192.168.0.10/32 tab 1 priority 100
[jensd@server ~]$ cat /etc/sysconfig/network-scripts/rule-ens224
from 192.168.1.10/32 tab 2 priority 200

Now your configuration should be persistent.

Some people pointed out in the comments that, in order for the routers to be persistent, you need to first perform the following actions:

yum install NetworkManager-config-routing-rules
systemctl enable NetworkManager-dispatcher.service
systemctl start NetworkManager-dispatcher.service

While this solution is slightly more work than changing the value for rp_filter, it isn’t that hard and has a lot of advantages over the other solution.

51 thoughts on “Configure two network cards in a different subnet on RHEL 6, RHEL 7, CentOS 6 and CentOS 7

      • It’s not working, I still have to enter the commands after reboot.

        And moreover, it messed up 2 of my network connections (1 nic with multiple aliases), I had to reset them from Network Settings in GUI.

        I’m running Centos 7.

        • Sorry but without any knowledge of your exact config it’s hard to tell exactly what you need to do. The commands and config in this post are what I setup and tested. The idea is that you get the principle and you can use it as an example but you’ll have to adjust it to fit for your needs.

          • I think the problem with this is that while you are creating your initial tables with ‘ip route add’ that it’s not persistent in with the reboot. You probably want to manually add these tables to rt_tables prior?

  1. orientalsniper: On centos 7 to get this to stick use: net.ipv4.conf.all.rp_filter = 2 (all instead of default) in /etc/sysctl.conf worked for me.

  2. Thanks for writing this post! We ran into this problem at work, and this helped to identify and properly resolve the issue real quick.

  3. Thank for the post.
    I would like to add for permanent section that ‘NetworkManager doesn’t natively support policy routing but it can support Legacy route-[interface] files via NetworkManager-config-routing-rules package.’ says raatti on page https://www.centos.org/forums/viewtopic.php?f=50&t=50400
    Therefore permanent changes on CentOS 7 requires;
    yum install NetworkManager-config-routing-rules
    systemctl enable NetworkManager-dispatcher.service
    systemctl start NetworkManager-dispatcher.service

  4. Many thanks for this post!! I spent hours to try and figure why two nics in different vlans wasn’t working properly, while in old CentOS 5 it worked fine. Both solutions here solved these problems!

    • Nice catch. And a good question. I would need to look into this but for sure you could generate the rule-files using a script which is filling in the leased ip dynamically.

  5. how does this apply to earlier versions of redhat like version 5.2?

    i am about to come up against this problem,
    I have two different switches that go to my sonicwall firewall,
    switch a is on the 10.0.1.x subnet and switch b is on the 10.0.3.x subnet,
    on switch A is the Redhat 5.2 fileserver 10.0.1.5
    right now it has 2 active network connections –
    IB0 is an infiniband connection to a separate subnet 192.168.100.x this network is local and doesn’t goto the firewall.
    eth0 which is a 10gbe nic card that is connected to the 10gb switch on 10.0.1.x network, 10.0.1.5/255.255.255.0 gateway 10.0.1.1
    I have 2 spare 1gb ports on the server eth1 and eth2
    I need clients on the 10.0.3.x network to have access to the server without going through the firewall, as the speed through the firewall is horribly slow due to packets being analyzed.

    So i was thinking about running a cat 6 cable from eth1 nic to switch 2 – 10.0.3.x network switch and assigning it an ip 10.0.3.5/255.255.255.0 gateway 10.0.3.1

    and then having the clients that happen to be macs connect via nfs to the server using that ip address 10.0.3.5, however I’m worried about causing broadcast storms or other issues.

    whats the best way to tackle this? or will this work without any other problems

  6. Hello I just what to ask if there is any way that Client A and B can ping one another in this scence. Thanks a lot.

    • The ability for client A & B to ping each other is not really relevant for this article. Both clients should have a route (or via the default route) to each others subnet. In case you want routing to be done by the server, you will need to add routes on the clients for that.

  7. Dear sir,
    in my Scenario i have two nic with different network one is global and one is local with your giving setting both networks are accessible in local network but when i come from another global network (internet) i can’t access those both network only one network can accessible some time global or some time local kindly give me smooth and possible solution of my problem
    Thanks.

      • Hi Mohammad,

        What you’re looking for is not really what this post is about. You should search for a guide to configure NAT and port forwarding. This will allow you to reach the local addresses from the public IP and to reach the public network from the local addresses.

  8. What if server have 3 NICs?
    2 the same as in this case and third (i.e. eth2 and IP 10.10.0.1) is connected to Internet.
    On eth2 masquerade is on. Both nets see the internet, but could connect each other. It is necessary that some or all clients from 192.168.0.0/24 could connect with 192.168.1.0/24 and vice versa and at the same time they should have internet connection using eth2

    • You would need to add another entry for the 3rd card. Same for iptables and masquerade, you will need to specify the interface names on your iptables statements.

  9. Please need your help, i am getting this error

    [root@server iproute2]# ip route add 64.x.x.x/24 dev em1 tab 1
    RTNETLINK answers: Invalid argument

  10. Helo,

    Thank you for this post!)

    Everything works well, but I have warning message in the system log:

    NetworkManager[879]: ‘rule-‘ or ‘rule6-‘ file is present; you will need to use a dispatcher script to apply these routes

  11. If it’s not working for you on a CentOS 7
    just make sure you install the following packages first
    yum -y install NetworkManager-config-routing-rules
    systemctl enable NetworkManager-dispatcher.service

  12. Persistent routing and rules won’t work without following package.
    I think you should add following commands to your great manual

    yum install NetworkManager-config-routing-rules
    systemctl enable NetworkManager-dispatcher.service
    systemctl start NetworkManager-dispatcher.service

    Anyway thank you very much for this article!

  13. There is a missing point (the article is great though):

    The actual difference between for example Debian and RHEL6/7 systems is, that Debian does not have rp_filter enabled AT ALL and it’s configured to a default value of 0. There is no packet forwarding needed.

    What rp_filter actually does – each incoming packet is tested against the FIB (Forwarding Information Base) and if the interface is not the best reverse path the packet check will fail. By default failed packets are discarded.

    Changing the rp_filter value to 0 would change the behavior to the way Debian works without the need of enabling the packet forwarding, but having this on a value of 1 is currently a recommended practice in RFC3704 to prevent IP spoofing from DDos attacks.

    Learning the IP routing behavior and to know how to define a ruleset should be a must-read for all IT admins.

  14. Pingback: Linux multihome host with rule based routing | Programmer's diary

  15. Pingback: Centos7 dual NICs: DMZ & Management - Ringing Liberty

  16. 4 years later your article save my ass…if I could give you a kiss…I send it to you as I am kissing my screen right now. Love ya!

  17. Thank you so much for this great post! I spent two days trying different appoaches to solve the similar issue on ubuntu, and this works like a charm. Just wanted to point out that the second method actually doesn’t work for me, while the quick and dirty way does.

  18. This helped a lot in resolving a similar issue on RHEL 7.7. Really well explained article, thanks for your help!

  19. Almost six years later and this article clarified for me why I was having the problem and solved it. Thanks

  20. Thank you for publishing this, it also resolved an issue for us as we have 2 networks and wanted to ensure any sends out came back in the same network.
    However, we did struggle with the persistency, after adding to the /etc/sysconfig/network-scripts the route and rule, a reboot made it disappeared. Then read this………

    Although it is still possible to define the network configuration through the files in the /etc/sysconfig/network-scripts directory, it’s not the preferred way any more (don’t forget to execute # nmcli con reload if you manually modify the files!).

    nmcli con reload

    and all the config remained yey!

  21. Thank you for this information. I’ve been googling for the last 2 days and this was the best explanation and only solution that worked for me

  22. Pingback: Port forwarding not working for certain LAN IP - Boot Panic

  23. Pingback: Centos7 dual NICs: DMZ & Management - Boot Panic

  24. Pingback: Port forwarding not working for certain LAN IP

Leave a Reply

Your email address will not be published. Required fields are marked *