Sharing Internet via NAT on Rocky 8.8

Hi, first post here, I’m building a practice cluster but I’m having issues sharing Internet access through NAT on my main machine, right now I only have:

  • 1 server with 2 NIC: 1 connected to a router (Archer C7) and 1 to LAN
  • 1 client w/ 1 NIC connected to LAN

For the server NICs I have the next settings:

  • enp4s0 (Connected to a Router w/ Internet Access):
    Address: 192.168.0.99 /24 (Arbitrary)
    Gateway: 192.168.0.1 (Router’s Gateway)
    DNS Server: 10.1.0.1 (Local enp3s0 address)
    Metric: 101 (default)
    IPv4 Method: Manual
    *for the DNS I copied what’s working on our actual cluster, but changing it to our local DNS or Google’s DNS doesn’t make any difference, I can ping normally to the outside network from the server

  • enp3s0 (Connected to LAN)
    Address: 10.1.0.1 /16 (Arbitrary)
    Metric: 200 *
    IPv4 Method: Manual
    *changed the metrics it in the sysconfig files since it was 100 by default and I couldn’t ping to the outside networks from the server

I already tried 2 methods of Internet sharing, one from the Ubuntu community guides and one from the Arch Linux wiki, which don’t seem to be that different one from another, both use iptables and I tried both methods unsuccessfully:

Ubuntu’s Community Method:

sudo iptables -A FORWARD -o enp4s0 -i enp3s0 -s 10.1.0.0/16 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -o enp4s0 -j MASQUERADE

Arch Linux’s Wiki Method:

# iptables -t nat -A POSTROUTING -o enp4s0 -j MASQUERADE
# iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# iptables -A FORWARD -i enp3s0 -o enp4s0 -j ACCEPT

And I also enabled ipv4 forwarding via a sysctl.d conf file so it changes on boot with:

net.ipv4.conf.all.forwarding = 1
net.ipv4.conf.default.forwarding = 1 
net.ipv4.ip_forward = 1

My client server’s only NIC is configured like this:

  • enp5s0 (Connected to LAN)
    Address: 10.1.0.2 /16
    Gateway: 10.1.0.1
    DNS Server: 10.1.0.1 (Again just copied what’s working on our cluster nodes right now, but changing the DNS to our organization’s own or google’s doesn’t make any difference)

The thing that’s happening is that from my client computer I can ping my main server as before (10.1.0.1), my router and other devices connected to it even wirelessly (192.168.0.X), I can ping even my department’s org network devices (AAA.AAA.AAA.XXX) which are working with public IP addresses and which my router is receiving it’s WAN address from.

But I can’t ping anything that’s not connected to the router, like google, nor can I do anything Internet related, such as installing packages through dnf, etc.

I read on the Arch’s Wiki that this issue is common along Docker users, but I haven’t installed it, and I don’t believe that it’s bundled in Rocky 8.8. I could be overcomplicating things in my ignorance as I’m just starting to experience Linux as a whole.

Any help would be immensely appreciated :two_hearts:

First, iptables is not the way.

  • The default system to manage firewall rules is firewalld.service and you talk to FirewallD with firewall-cmd. The iptables commands that you did show may temporarily modify ruleset in kernel, but FirewallD either resets them or at least does not know about them when system boots the next time
  • The ruleset in kernel is in “nf-tables” subsystem that has taken over from “netfilter”. You can read (and modify) rules with nftnft list ruleset shows all the rules, while iptables -S and iptables -t nat -S do show only a small subset. Besides, the iptables command is merely a wrapper that actually calls nft

Before, already in el7, one could do:

  1. Assign enp4s0 to FirewallD zone “external”
  2. Assign enp3s0 to FirewallD zone “trusted”

In practice one did use nmcli con mod $CONNAME connection.zone external, where CONNAME had the name of connection that uses the enp4s0 device (and similar command for the other connection).
NetworkManager passes the name of device to FirewallD when the connection goes up.

The “external” zone allows almost nothing, but it does set the ip_forward = 1 and add the sNAT rule (masquerade).
The “trusted” zone allows everything, so it would block nothing that comes from enp3s0, i.e. from 10.1.0.0/16.

That “everything” did include traffic from 10.1.0.0/16 to 192.168.0.0/24 (and beyond) – and replies ((ESTABLISHED,RELATED) back.


Alas, the FirewallD has since learned to support “routed traffic” separately. I’ve used el9, so I don’t know where the FirewallD in el8 is about this, but if its manual (man firewall-cmd) mentions policies or policy objects then it should already have that support. However, my attempt to make el9 a router with FirewallD did end with firewalld.service masked out and nftables.service in use.

The ruleset could be something like:

#! /usr/sbin/nft -f

table inet myrules {
        chain raw_PREROUTING {
                type filter hook prerouting priority raw; policy accept;
                icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept
                meta nfproto ipv6 fib saddr . iif oif missing drop
        }

        chain mangle_PREROUTING {
                type filter hook prerouting priority mangle; policy accept;
        }

        chain filter_INPUT {
                type filter hook input priority filter; policy accept;
                ct state established,related accept
                ct status dnat counter accept
                iifname "lo" accept
                jump filter_INPUT_ZONES
                ct state invalid drop
                reject with icmpx type admin-prohibited
        }

        chain filter_FORWARD {
                type filter hook forward priority filter; policy accept;
                ct state established,related accept
                ct status dnat counter accept
                iifname "lo" accept
                ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 type addr-unreachable
                iifname "enp3s0" counter accept
                iifname "enp4s0" counter
                meta l4proto { icmp, ipv6-icmp } counter accept

                ct state invalid drop
                reject with icmpx type admin-prohibited
        }

        chain filter_OUTPUT {
                type filter hook output priority filter; policy accept;
                oifname "lo" accept
                ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 type addr-unreachable
        }

        chain filter_INPUT_ZONES {
                iifname "enp4s0"          counter goto filter_IN_block    # outside
                iifname "enp3s0"          counter goto filter_IN_public   # inside
        }

        chain filter_IN_public {
                tcp dport 22 ct state { new, untracked } accept
                ip6 daddr fe80::/64 udp dport 546 ct state { new, untracked } accept
                tcp dport 9090 ct state { new, untracked } accept
                meta l4proto { icmp, ipv6-icmp } accept
        }

        chain filter_IN_block {
                reject with icmpx type admin-prohibited
        }
}

table ip myrules {
        chain nat_PREROUTING {
                type nat hook prerouting priority dstnat; policy accept;
        }

        chain nat_POSTROUTING {
                type nat hook postrouting priority srcnat; policy accept;
                oifname "enp4s0" counter masquerade
        }
}

table ip6 filter {
        chain INPUT {
                type filter hook input priority filter; policy drop;
                ct state invalid counter drop
                ct state {established, related} counter accept
                iif lo counter accept
                iif != lo ip6 daddr ::1/128 counter drop
                counter reject with icmpv6 type admin-prohibited
        }

        chain forward {
                type filter hook forward priority filter; policy drop;
        }
}

The above could be in file /etc/nftables/myrules.nft and
there would be line

include "/etc/nftables/myrules.nft"

to file /etc/sysconfig/nftables.conf

Since that does not enable forwarding, one has to add another file:

# cat /etc/sysctl.d/sbl.conf
net.ipv4.ip_forward = 1

The last thing is to set correct service into use:

systemctl disable firewalld
systemctl stop firewalld
systemctl mask firewalld
systemctl enable nftables
systemctl start nftables

My ruleset example is a hodge-podge combined from FirewallD had and minimal that a router would need, and also assumes that IPv6 is not in use. (My service provider does not have IPv6 yet. :roll_eyes: )

2 Likes

I just went through something like what you’re doing setting up some VMs so I knew what to expect when I upgrade my CentOS7 boxes to Rocky 9. You can do everything using firewall-cmd.

Quick explanation of my set up. My internal network runs on the typical 192.168.0.0/24 subnet. I have the VMs on a 10.0.0.0/24 subnet. My “gateway” system betweem the 10 net (ens224 NIC) and the 192 net (ens192 NIC) makes the magic happen. This approach uses the preconfigured internal and external zones you get with firewalld.You’ll see them when you do a firewall-cmd --list-al-zones. I used something like (ignore the history sequence numbers):

416 firewall-cmd --list-all-zones
417 firewall-cmd --permanent --set-default-zone external
418 firewall-cmd --get-zone-of-interface=ens192
419 firewall-cmd --get-zone-of-interface=ens224
421 firewall-cmd --zone=internal --change-interface=ens224
422 firewall-cmd --list-all-zones
423 firewall-cmd --complete-reload
424 firewall-cmd --list-all-zones
425 firewall-cmd --permanent --zone=internal --change-interface=ens224
426 firewall-cmd --complete-reload
427 firewall-cmd --list-all-zones
428 firewall-cmd --permanent --zone=external --add-masquerade
430 firewall-cmd --list-all-zones
452 systemctl restart firewalld
466 firewall-cmd --new-policy internal-external --permanent
467 firewall-cmd --reload
468 firewall-cmd --policy internal-external --add-ingress-zone=internal --permanent
469 firewall-cmd --policy internal-external --add-egress-zone=external --permanent
470 firewall-cmd --policy internal-external --set-target=ACCEPT --permanent
471 firewall-cmd --reload
472 firewall-cmd --info-policy internal-external

I followed someone else’s example and did a few other things (opened specific firewall ports/services) plus I did a lot more --list-all-zones and restarts to make sure I was headed in the right direction. I think my outward facing NIC (ens192) was already in the external zone but that has already scrolled off of my history so you may need to do that.

When you’re done, firewall-cmd–list-all-zones output should look something like this for your internal and external zones:

external (active)
target: default
icmp-block-inversion: no
interfaces: ens192
sources:
services: ssh vnc-server
ports:
protocols:
forward: yes
masquerade: yes
forward-ports:
source-ports:
icmp-blocks:
rich rules:

internal (active)
target: default
icmp-block-inversion: no
interfaces: ens224
sources:
services: amanda-client cockpit dhcp dhcpv6-client dns mdns samba-client ssh
ports: 53/tcp 53/udp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

2 Likes

iptables or nftables (preferred) is fine. I much prefer them to firewalld which is just a front end to them and an abomination. Did you turn on ip forwarding? You wont get anywhere without that. Then make sure that the default route is aimed at the router’s inside ip address.

With right zones the FirewallD should do it for you.
Easy to verify too: just do the firewalld config, reboot, and check what sysctl has.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.