Fedora linux 41 gateway with IPv4 and IPv6

I have a little gateway I bought. It’s pretty neat. It has an Intel(R) Celeron(R) N5100 @ 1.10GHz and 4 Intel NICs.

I’ve set it up with Fedora 41 and I want to use it as my gateway.

Networking looks like this:

root@id0:~# nmcli 
enp3s0: connected to enp3s0
        "Intel I226-V"
        ethernet (igc), 60:BE:B4:09:31:E1, hw, mtu 1500
        ip4 default, ip6 default
        inet4 192.168.0.128/24
        route4 192.168.0.0/24 metric 100
        route4 default via 192.168.0.1 metric 100
        inet6 2806:261:41a:847d::1/128
        inet6 2806:261:41a:847d:aa79:398a:aa6f:e3aa/64
        inet6 fe80::bc89:f3f2:bab:2b8b/64
        route6 fe80::/64 metric 1024
        route6 2806:261:41a:847d::/64 metric 100
        route6 2806:261:41a:847d::/64 via fe80::ee5c:68ff:fef8:15a4 metric 105
        route6 default via fe80::ee5c:68ff:fef8:15a4 metric 100
        route6 2806:261:41a:847d::1/128 metric 100

enp2s0: connected to enp2s0
        "Intel I226-V"
        ethernet (igc), 60:BE:B4:09:31:E0, hw, mtu 1500
        inet4 192.168.10.1/24
        route4 192.168.10.0/24 metric 100
        inet6 2806:261:41a:847d:80ff:8dde:f5d1:afe8/64
        inet6 fe80::6a58:fe98:8428:c117/64
        route6 fe80::/64 metric 1024
        route6 2806:261:41a:847d::/64 metric 100

lo: connected (externally) to lo
        "lo"
        loopback (unknown), 00:00:00:00:00:00, sw, mtu 65536
        inet4 127.0.0.1/8
        inet6 ::1/128
        route6 default metric 1024

enp4s0: disconnected
        "Intel I226-V"
        1 connection available
        ethernet (igc), 60:BE:B4:09:31:E2, hw, mtu 1500

enp5s0: disconnected
        "Intel I226-V"
        ethernet (igc), 60:BE:B4:09:31:E3, hw, mtu 1500

DNS configuration:
        servers: 127.0.0.1

Use "nmcli device show" to get complete information about known devices and
"nmcli connection show" to get an overview on active connection profiles.

Consult nmcli(1) and nmcli-examples(7) manual pages for complete usage details.

root@id0:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 60:be:b4:09:31:e0 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.1/24 brd 192.168.10.255 scope global noprefixroute enp2s0
       valid_lft forever preferred_lft forever
    inet6 2806:261:41a:847d:80ff:8dde:f5d1:afe8/64 scope global dynamic noprefixroute 
       valid_lft 86079sec preferred_lft 14079sec
    inet6 fe80::6a58:fe98:8428:c117/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 60:be:b4:09:31:e1 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.128/24 brd 192.168.0.255 scope global dynamic noprefixroute enp3s0
       valid_lft 603sec preferred_lft 603sec
    inet6 2806:261:41a:847d::1/128 scope global dynamic noprefixroute 
       valid_lft 2589578sec preferred_lft 602378sec
    inet6 2806:261:41a:847d:aa79:398a:aa6f:e3aa/64 scope global dynamic noprefixroute 
       valid_lft 2591981sec preferred_lft 604781sec
    inet6 fe80::bc89:f3f2:bab:2b8b/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
4: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 60:be:b4:09:31:e2 brd ff:ff:ff:ff:ff:ff
5: enp5s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 60:be:b4:09:31:e3 brd ff:ff:ff:ff:ff:ff

root@id0:~# ip -6 route
2806:261:41a:847d::1 dev enp3s0 proto kernel metric 100 pref medium
2806:261:41a:847d::/64 dev enp2s0 proto ra metric 100 pref medium
2806:261:41a:847d::/64 dev enp3s0 proto ra metric 100 pref medium
2806:261:41a:847d::/64 via fe80::ee5c:68ff:fef8:15a4 dev enp3s0 proto ra metric 105 pref medium
fe80::/64 dev enp2s0 proto kernel metric 1024 pref medium
fe80::/64 dev enp3s0 proto kernel metric 1024 pref medium
default via fe80::ee5c:68ff:fef8:15a4 dev enp3s0 proto ra metric 100 pref medium
default dev lo proto ra metric 1024 pref medium

root@id0:~# firewall-cmd --zone=public --list-all
public (default, active)
  target: default
  ingress-priority: 0
  egress-priority: 0
  icmp-block-inversion: no
  interfaces: enp3s0
  sources: 
  services: dhcpv6-client mdns ssh
  ports: 
  protocols: ipv6-icmp
  forward: yes
  masquerade: yes
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

root@id0:~# firewall-cmd --zone=trusted --list-all
trusted (active)
  target: ACCEPT
  ingress-priority: 0
  egress-priority: 0
  icmp-block-inversion: no
  interfaces: enp2s0
  sources: 
  services: 
  ports: 
  protocols: 
  forward: yes
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

IPv4 works fine. I just masqueraded the public zone and added the WAN interface there (enp3s0).

for IPv6, I’ve installed radvd and configured it as follows:

root@id0:~# cat /etc/radvd.conf 
interface enp2s0 {
    AdvSendAdvert on;
    AdvOtherConfigFlag on;

    prefix 2806:261:41a:847d::/64 {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
    };
};

My client (desktop) get’s the IPv6 configuration instantly and is able to ping local-link (it’s default ipv6 gateway) without issue. Yet, it cannot ping anything on the Internet via IPv6.

The networking on my client looks as follows:

root@desktop:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: enp8s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 9c:6b:00:2f:0e:1e brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.99/24 brd 192.168.10.255 scope global noprefixroute enp8s0
       valid_lft forever preferred_lft forever
    inet6 2806:261:41a:847d:9e6b:ff:fe2f:e1e/64 scope global dynamic noprefixroute 
       valid_lft 86159sec preferred_lft 14159sec
    inet6 fe80::9e6b:ff:fe2f:e1e/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: wlp6s0: <BROADCAST,MULTICAST> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 36:d1:90:87:7f:37 brd ff:ff:ff:ff:ff:ff permaddr 98:43:fa:23:72:3d

root@desktop:~# ip -6 route
2806:261:41a:847d::/64 dev enp8s0 proto ra metric 100 pref medium
fe80::/64 dev enp8s0 proto kernel metric 1024 pref medium
default via fe80::6a58:fe98:8428:c117 dev enp8s0 proto ra metric 20100 pref medium

root@desktop:~# firewall-cmd --list-all
public (default, active)
  target: default
  ingress-priority: 0
  egress-priority: 0
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: dhcpv6-client mdns ssh
  ports: 1024-65535/tcp 1024-65535/udp
  protocols: 
  forward: yes
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

I tried debugging this using Gemini 2.0 Experimental Advanced and it gave me some pretty good insights, yet, we weren’t able to figure it out. I wanted to share the session but I don’t seem to be able to.

My next step will be getting rid of NetworkManager and Firewalld and use plain systemd-networkd and nftables.

Let’s see how that goes. Wish me luck and/or suggest anything that comes to mind. I’m happy to try stuff out.

2 Likes

Make sure your upstream and downstream prefixes are different, otherwise makes IPv6 deployment problematic.
You need to ask a separate IPv6 prefix from the ISP, so it can be split and assigned to the downstream networks.
Getting a /48 or /56 is the best option as each downstream network requires at least a /64.
NetworkManager/systemd-networkd and Firewalld should be enough for this task, radvd is redundant.
This should basically just work by following the relevant documentation and common sense.

2 Likes

Thank you for the feedback.

I’m getting a /64 from mi ISP. It’s a home Internet connection.

Thank you for mentioning the software requirements. I thought radvd wasn’t necessary but I was not getting any IPs otherwise.

If getting a separate prefix is not possible, you can use IPv6 masquerading:

sudo nmcli connection modify LAN_CONNECTION \
    ipv6.method shared \
    ipv6.addresses 2001:db8::1/64
sudo nmcli connection up LAN_CONNECTION
...
sudo firewall-cmd --permanent --policy=internal-external \
    --add-rich-rule="rule family=ipv6 masquerade"
sudo firewall-cmd --reload

See also:
NAT router with 2 interfaces, how to do with firewalld and Centos 9 Stream - #5 by vgaetera

To be clear, your current setup is broken due to using the same IPv6 prefix/route for both upstream and downstream interfaces.

2 Likes

OK, I had a positive change thanks to your suggestion.

So, I removed radvd and changed the IP of enp2s0 of my gateway (id0) just like you suggested:

dnf -y remove radvd
rm -f /etc/radvd.conf

nmcli conn modify enp2s0 ipv6.method shared ipv6.addresses fc07::1/64

As soon as I did this, my client (desktop) got an appropriate address:

2: enp8s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 9c:6b:00:2f:0e:1e brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.99/24 brd 192.168.10.255 scope global noprefixroute enp8s0
       valid_lft forever preferred_lft forever
    inet6 fc07::9e6b:ff:fe2f:e1e/64 scope global noprefixroute 
       valid_lft forever preferred_lft forever
    inet6 fe80::9e6b:ff:fe2f:e1e/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

And the routes got updated:

fc07::/64 dev enp8s0 proto ra metric 100 pref medium
fe80::/64 dev enp8s0 proto kernel metric 1024 pref medium
default via fe80::6a58:fe98:8428:c117 dev enp8s0 proto ra metric 20100 pref medium

So, I created the policy you mentioned:

firewall-cmd --permanent --new-policy=public-internal
firewall-cmd --permanent --policy=public-internal --set-target=ACCEPT
firewall-cmd --permanent --policy=public-internal --add-masquerade
firewall-cmd --permanent --policy=public-internal --add-ingress-zone=trusted
firewall-cmd --permanent --policy=public-internal --add-egress-zone=public
firewall-cmd --reload

But, so far, ping doesn’t work yet. But, now, I, at least, get a warning (desktop):

From 2806:261:41a:847d:ee5c:68ff:fef8:15a4 icmp_seq=319 Destination unreachable: Source address failed ingress/egress policy

I think I’m close. Thanks a lot.

I am able to see this on my gateway while monitoring enp3s0, which is my WAN:

root@id0:~# tshark -i enp3s0 -Y icmpv6 host fc07::1e47:23d1:af97:7af0
Running as user "root" and group "root". This could be dangerous.
Capturing on 'enp3s0'
    1 0.000000000 fc07::1e47:23d1:af97:7af0 → 2607:f8b0:4012:829::200e ICMPv6 118 Echo (ping) request id=0x010f, seq=51, hop limit=63
    2 0.000286521 2806:261:41a:847d:ee5c:68ff:fef8:15a4 → fc07::1e47:23d1:af97:7af0 ICMPv6 166 Destination Unreachable (Source address failed ingress/egress policy)
    3 1.024010776 fc07::1e47:23d1:af97:7af0 → 2607:f8b0:4012:829::200e ICMPv6 118 Echo (ping) request id=0x010f, seq=52, hop limit=63
    4 1.024299324 2806:261:41a:847d:ee5c:68ff:fef8:15a4 → fc07::1e47:23d1:af97:7af0 ICMPv6 166 Destination Unreachable (Source address failed ingress/egress policy)
    5 2.048084941 fc07::1e47:23d1:af97:7af0 → 2607:f8b0:4012:829::200e ICMPv6 118 Echo (ping) request id=0x010f, seq=53, hop limit=63
    6 2.048360456 2806:261:41a:847d:ee5c:68ff:fef8:15a4 → fc07::1e47:23d1:af97:7af0 ICMPv6 166 Destination Unreachable (Source address failed ingress/egress policy)
    7 3.072112614 fc07::1e47:23d1:af97:7af0 → 2607:f8b0:4012:829::200e ICMPv6 118 Echo 

Yet, id0 can ping desktop:

root@id0:~# ping fc07::1e47:23d1:af97:7af0
PING fc07::1e47:23d1:af97:7af0 (fc07::1e47:23d1:af97:7af0) 56 data bytes
64 bytes from fc07::1e47:23d1:af97:7af0: icmp_seq=1 ttl=64 time=0.194 ms
64 bytes from fc07::1e47:23d1:af97:7af0: icmp_seq=2 ttl=64 time=0.226 ms
64 bytes from fc07::1e47:23d1:af97:7af0: icmp_seq=3 ttl=64 time=0.230 ms

Hah! I did it!

The trick was, what @vgaetera already had told me to do. I added:

firewall-cmd --permanent --policy=public-internal --add-rich-rule="rule family=ipv6 masquerade"

I thought that doing: firewall-cmd --permanent --policy=public-internal --add-masquerade would be enough but no.

I will work out the whole method and put it here.

Thanks a ton, @vgaetera. You rock. :love_you_gesture:

OK, done!

I tested it by inverting LAN and WAN interfaces and it all worked fine.

# linux gateway with IPv4 and IPv6

# config
wan_nic=enp2s0
wan_zone=public

lan_nic=enp3s0
lan_zone=trusted
lan_ipv4=192.168.10.1/24
lan_ipv6=fc07::1/64

# pre-requisites
dnf -y install NetworkManager firewalld
systemctl enable --now NetworkManager firewalld

# create WAN
nmcli conn add \
    type ethernet \
    con-name $wan_nic \
    ifname $wan_nic \
    connection.zone $wan_zone \
    ipv4.method auto \
    ipv4.route-metric 100 \
    ipv4.may-fail no \ 
    ipv6.method auto \ 
    ipv6.route-metric 100 \
    ipv6.may-fail no

# create LAN
nmcli conn add \
    type ethernet \
    con-name $lan_nic \
    ifname $lan_nic \
    connection.zone $lan_zone \
    ipv4.method manual \
    ipv4.addresses $lan_ipv4 \
    ipv4.may-fail no \ 
    ipv4.never-default yes \
    ipv6.method shared \
    ipv6.addresses $lan_ipv6 \
    ipv6.may-fail no \
    ipv6.never-default yes

# firewall masquerade
## IPv4
firewall-cmd \
    --permanent \
    --zone=$wan_zone \
    --add-masquerade

## IPv6
firewall-cmd \
    --permanent \
    --zone=$wan_zone \
    --add-rich-rule="rule family=ipv6 masquerade"

firewall-cmd --reload

Thanks to @vgaetera for his feedback. Couldn’t have done it without him.

Notes:

  • In order to provide a simplified example, I didn’t implement it using firewalld policies. It worked without them.
  • This is only a minimal set of steps to get IPv4/IPv6 masquerading to work. It probably isn’t the best method but it’s what worked with what I have. More feedback is welcome.
3 Likes

And here’s a version that actually worked with 2 WAN providers.

I’m hopping that, when one fails, the other one will be used (due to metric being increased when one fails):

# linux gateway with IPv4 and IPv6 and 2 WAN providers

# config
wan1_nic=enp3s0
wan2_nic=enp4s0
wan_zone=public

lan_nic=enp2s0
lan_zone=trusted
lan_ipv4=192.168.10.1/24
lan_ipv6=fc07::1/64

# pre-requisites
dnf -y install NetworkManager firewalld
systemctl enable --now NetworkManager firewalld

# create WAN1
nmcli conn add \
    type ethernet \
    con-name $wan1_nic \
    ifname $wan1_nic \
    connection.zone $wan_zone \
    ipv4.method auto \
    ipv4.route-metric 100 \
    ipv4.may-fail no \
    ipv6.method auto \
    ipv6.route-metric 100 \
    ipv6.may-fail no

# create WAN2
nmcli conn add \
    type ethernet \
    con-name $wan2_nic \
    ifname $wan2_nic \
    connection.zone $wan_zone \
    ipv4.method auto \
    ipv4.route-metric 200 \
    ipv4.may-fail no \
    ipv6.method auto \
    ipv6.route-metric 200 \
    ipv6.may-fail no

# create LAN
nmcli conn add \
    type ethernet \
    con-name $lan_nic \
    ifname $lan_nic \
    connection.zone $lan_zone \
    ipv4.method manual \
    ipv4.addresses $lan_ipv4 \
    ipv4.may-fail no \
    ipv4.never-default yes \
    ipv6.method shared \
    ipv6.addresses $lan_ipv6 \
    ipv6.may-fail no \
    ipv6.never-default yes

# firewall masquerade
## IPv4
firewall-cmd \
    --permanent \
    --zone=$wan_zone \
    --add-masquerade

## IPv6
firewall-cmd \
    --permanent \
    --zone=$wan_zone \
    --add-rich-rule="rule family=ipv6 masquerade"

firewall-cmd --reload
1 Like

You are supposed to get a /48 or /56 at least as a home connection.
It is worth checking with the ISP, they may have allocated you a /48
but not told you the details. I just had that experience today with my ISP, they routed a /64 to the house. But I had a /48 allocated and needed use the ISP control panel to change the routing to turn it on.

I live in México; in rural México I should say. The ISP here doesn’t have anything close to a control panel that’s worth anything (they do; they tell you how much you owe them there).

Besides that, support is used to simple technical issues and, usually, resolve them by sending a technician that barely knows how to install the “modem” they lend you and, when in trouble, just swap it for another one (used; never new).

In any case, the ISP’s support channel isn’t viable.

… but, you know what, I will just call them and see what they say. I am sure they will no nothing about IPv6 but let’s give 'em the benefit of the doubt.

I will be maintaining my two variations of these scripts here:

(Fedora) Linux gateway with IPv4/IPv6 support:

(Fedora) Linux gateway with IPv4/IPv6 support and 2 WANs:

… in case this one gets old.

2 Likes