如何在Linux上禁用IPv6

487 阅读15分钟

一般来说,IPv6对互联网是件好事,但我发现它对大多数家庭和中小型企业的使用来说是不必要的复杂。像其他许多人一样,我继续为我自己的内部网络和那些我负有某种程度责任的网络使用私人IPv4地址范围。反正我的ISP只提供IPv4地址,所以当所有外部数据包都是IPv4时,在内部使用IPv6就没有意义了。此外,IPv4要简单得多,而我的Linux哲学宗旨之一就是 "寻找简单"。

因此,我在我的所有主机上禁用了IPv6。起初,这似乎很容易。下面是我的操作方法。

测试IPv6

我的主机运行的是Fedora 36 Xfce spin,以及我在初始默认安装后进行的许多软件包和配置更改。我所有的主机都安装了最新的更新。其中一个主机是我的防火墙/路由器,它是我禁用 IPv6 的第一个主机。

你可以检查一下,看看你的Linux主机上的IPv6目前是否处于激活状态。下面的nmcli 命令结果是来自我的路由器/防火墙主机。所有的活动网卡都有IPv4和IPv6地址。

# nmcli
enp4s0: connected to enp4s0
        "Realtek RTL8111/8168/8411"
        ethernet (r8169), 84:16:F9:04:44:03, hw, mtu 1500
        ip4 default
        inet4 45.20.209.41/29
        route4 45.20.209.40/29 metric 102
        route4 default via 45.20.209.46 metric 102
        inet6 2600:1700:7c0:860:8616:f9ff:fe04:4403/64
        inet6 fe80::8616:f9ff:fe04:4403/64
        route6 fe80::/64 metric 256
        route6 default via fe80::a698:13ff:fee5:fa10 metric 1024
        route6 2600:1700:7c0:860::/64 metric 256

enp1s0: connected to enp1s0
        "Realtek RTL8111/8168/8411"
        ethernet (r8169), 84:16:F9:03:E9:89, hw, mtu 1500
        inet4 192.168.10.1/24
        route4 192.168.10.0/24 metric 101
        inet6 fe80::8616:f9ff:fe03:e989/64
        route6 fe80::/64 metric 256

enp2s0: connected to enp2s0
        "Realtek RTL8111/8168/8411"
        ethernet (r8169), 84:16:F9:03:FD:85, hw, mtu 1500
        inet4 192.168.0.254/24
        route4 192.168.0.0/24 metric 100
        inet6 fe80::8616:f9ff:fe03:fd85/64
        route6 fe80::/64 metric 256

lo: unmanaged
        "lo"
        loopback (unknown), 00:00:00:00:00:00, sw, mtu 65536

DNS configuration:
        servers: 192.168.0.52 8.8.8.8 8.8.4.4
        interface: enp4s0

        servers: 192.168.0.52 8.8.8.8
        interface: enp2s0

        servers: 192.168.0.52 8.8.8.8
        interface: enp1s0

使用GRUB来配置内核

GRUB--Grand Unified Bootloader--将内核加载到内存中,并在Linux启动过程中设置许多内核参数。目前的引导程序实际上是GRUB2,即GRUB的第二个主要版本,但叫它GRUB更容易。

GRUB配置文件/boot/grub2/grub.cfg ,为内核提供启动配置,但你不应该直接修改它。UEFI GRUB配置文件的位置可能因你的发行版而异。然而,它的位置对这个目的来说是不重要的。Linux启动时的内核配置可以使用/etc/default/grub 配置文件轻松修改,无论是否使用UEFI启动,都可以正确配置内核。

有两个命令行参数可以添加到/etc/default/grub 文件中,以配置Linux内核禁用IPv6。第一个,GRUB_CMDLINE_LINUX="ipv6.disable=1 "在内核命令行中加入"ipv6.disable=1"

第二种,GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 quiet splash "就比较复杂了。当你把GRUB_DISABLE_RECOVERY设置为**"true**"时,每个Linux内核将产生一个菜单项。当设置为**"false**"时,为每个内核创建两个菜单项。一个默认条目和一个恢复模式的条目。这个选项列出了只添加到默认菜单项的命令行参数,在GRUB_CMDLINE_LINUX中列出的参数之后。

另外两个参数定义了内核启动时的显示方式。quiet参数禁用了内核在启动和自我配置过程中发出的大部分信息。splash参数导致在内核启动时显示闪屏。闪屏可以是一个图形,比如一个标志或一个动画。

我的/etc/default/grub 文件在做了这些补充后看起来是这样的。

GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 quiet splash"
GRUB_CMDLINE_LINUX="ipv6.disable=1"

在对/etc/default/grub 文件做了这些(或任何)补充后,运行grub2-mkconfig 命令,它将这些参数添加到每个已安装的内核的grub.cfg 配置文件的内核命令行中。这些改变不会立即生效。你必须重新启动主机,使IPv6被禁用。

自动执行此方案

使用Ansible实现这些任务的自动化是很简单的。只需创建一个具有以下设置的playbook,并针对需要改变的主机列表运行它。这个游戏手册的前两个任务附加了所需的配置。

- name: Play 1 - Modify /etc/default/grub to configure kernel boot parameters.
  hosts: f36vm

  tasks:
    - name: Append GRUB_CMDLINE_LINUX_DEFAULT variable to /etc/default/grub
      lineinfile:
        path: /etc/default/grub
        insertafter: EOF
        line: 'GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 quiet splash"'

    - name: Append GRUB_CMDLINE_LINUX variable to /etc/default/grub
      lineinfile:
        path: /etc/default/grub
        insertafter: EOF
        line: 'GRUB_CMDLINE_LINUX="ipv6.disable=1"'

    - name: Run grub2-mkconfig to update /boot/grub/grub.cfg
      command:
        cmd: grub2-mkconfig

再一次,你必须重新启动主机以禁用IPv6。你可以将重启添加到游戏手册中,使更改立即生效,或者等到下次主机因其他原因需要重启时再进行重启。

另一个解决方案

我创造了另一个解决方案;只是它不是最好和最优雅的选择。然而,我想展示我的思考过程,因为这里有一些信息可能是有用的。

在sysctl.d中添加一个本地文件

你可以使用/etc/sysctl 文件来添加必要的配置设置,但是在/etc/sysctl.d 目录下添加一个本地文件要好得多,这样在更新或升级时就不会被覆盖。请注意,已经有一个名为99-sysctl.conf 的文件。你可以使用该文件来设置配置,但我为此目的创建了一个名为99-local-network.conf 的文件,内容如下。这样,如果99-sysctl.conf 随着未来的更新或升级而改变,我的文件将保持不动。这不是一个可执行文件,它是一个配置文件。

################################################################################
# Local NetworkManager settings - Specifically to disable IPV6                 #
################################################################################
#
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

注意:像许多像这样的配置目录中的配置文件一样,该目录中包含的文件是按自然排序的顺序读取的。这意味着在一个排序较晚的文件中声明的变量的值将覆盖同一变量的早期声明。

通常使用重启来使这些变化生效,但我后来学会了如何在不重启的情况下这样做。我重新启动了我的系统,重新执行了nmcli 命令,结果如下。

[root@wally ~]# nmcli
enp4s0: connected to enp4s0
        "Realtek RTL8111/8168/8411"
        ethernet (r8169), 84:16:F9:04:44:03, hw, mtu 1500
        ip4 default
        inet4 45.20.209.41/29
        route4 45.20.209.40/29 metric 101
        route4 default via 45.20.209.46 metric 101

enp1s0: connected to enp1s0
        "Realtek RTL8111/8168/8411"
        ethernet (r8169), 84:16:F9:03:E9:89, hw, mtu 1500
        inet4 192.168.10.1/24
        route4 192.168.10.0/24 metric 102

enp2s0: connected to enp2s0
        "Realtek RTL8111/8168/8411"
        ethernet (r8169), 84:16:F9:03:FD:85, hw, mtu 1500
        inet4 192.168.0.254/24
        route4 192.168.0.0/24 metric 100

lo: unmanaged
        "lo"
        loopback (unknown), 00:00:00:00:00:00, sw, mtu 65536

DNS configuration:
        servers: 192.168.0.52 8.8.8.8 8.8.4.4
        interface: enp4s0

        servers: 192.168.0.52 8.8.8.8
        interface: enp2s0

        servers: 192.168.0.52 8.8.8.8
        interface: enp1s0

这表明我的简单修复起了作用。

除了...

除了这个解决方案只在我的12台Linux电脑中的一台上运行。在写完本文的上述部分后,我开始在我所有的其他主机上安装这个修复方案。当我意识到这种方法并不总是有效时,我只做了一个系统。然后我在我的一个虚拟机上试了一下,在那里也失败了。据我所知,它只在一台主机上工作--我用作防火墙和路由器的那台。

在虚拟机上做了一些额外的研究后,我发现这些设置也可以作为命令使用sysctl ,这样系统就不需要重启了。我可以在命令行中输入这些命令来停用IPv6。下面是一个例子。

[root@f36vm ~]# sysctl -w net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.all.disable_ipv6 = 1
[root@f36vm ~]# sysctl -w net.ipv6.conf.default.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6 = 1
[root@f36vm ~]# nmcli
enp0s3: connected to Wired connection 1
        "Intel 82540EM"
        ethernet (e1000), 08:00:27:07:CD:FE, hw, mtu 1500
        ip4 default
        inet4 192.168.0.136/24
        route4 192.168.0.0/24 metric 100
        route4 default via 192.168.0.254 metric 100

lo: unmanaged
<SNIP>

我花了一些时间来研究这个问题,并确定我创建的文件在Linux启动时没有被读取,或者至少没有被sysctl 。或者,如果它被读取了,它也被忽略了,或者设置后来被相同的变量用不同的值覆盖了。在这之后,我知道没有更深层次的问题阻止它。

关于sysctl

在这一点上,我更详细地研究了sysctl 命令。sysctl 命令的目的是在/proc 目录下设置内核参数。根用户可以用它从命令行中设置个别参数。此外--这是我的解决方案的关键--你可以用它来查看和设置存储在几个地方的所有内核参数,包括/etc/sysctl.conf 和位于/etc/sysctl.d 的文件。sysctl 命令是在Linux启动时用来设置内核参数的。

我重复一遍:系统和系统管理员可以在系统启动和运行时,在不重启的情况下使用sysctl 命令来查看和设置内核变量。这个命令和它为Linux系统管理员提供的权力是Linux和其他操作系统之间最重要的区别之一。它给了我们一个工具来做其他操作系统上不可能做的事情。

我通过重启虚拟机并运行以下命令来测试,在sysctl man页面上列出的所有位置设置变量。

[root@f36vm ~]# sysctl --system
* Applying /usr/lib/sysctl.d/10-default-yama-scope.conf ...
kernel.yama.ptrace_scope = 0
* Applying /usr/lib/sysctl.d/50-coredump.conf ...
kernel.core_pattern = |/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h
kernel.core_pipe_limit = 16
fs.suid_dumpable = 2
* Applying /usr/lib/sysctl.d/50-default.conf ...
kernel.sysrq = 16
kernel.core_uses_pid = 1
net.ipv4.conf.default.rp_filter = 2
sysctl: setting key "net.ipv4.conf.all.rp_filter": Invalid argument
net.ipv4.conf.default.accept_source_route = 0
sysctl: setting key "net.ipv4.conf.all.accept_source_route": Invalid argument
net.ipv4.conf.default.promote_secondaries = 1
sysctl: setting key "net.ipv4.conf.all.promote_secondaries": Invalid argument
net.ipv4.ping_group_range = 0 2147483647
net.core.default_qdisc = fq_codel
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.protected_regular = 1
fs.protected_fifos = 1
* Applying /usr/lib/sysctl.d/50-libkcapi-optmem_max.conf ...
net.core.optmem_max = 81920
* Applying /usr/lib/sysctl.d/50-libreswan.conf ...
net.ipv6.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
* Applying /usr/lib/sysctl.d/50-pid-max.conf ...
kernel.pid_max = 4194304
* Applying /etc/sysctl.d/99-local-network.conf ...
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
* Applying /etc/sysctl.d/99-sysctl.conf ...
* Applying /etc/sysctl.conf ...

[root@f36vm ~]#

nmcli 命令检查网卡的状态,发现IPv6已经被禁用,IPv4仍在运行。是的,我确实在该数据流中看到了其他的错误,但我暂时忽略了它们。请务必阅读sysctl 命令的手册页,因为它相当有趣。它提供了一种处理所有带有--service 选项的sysctl 配置文件的方法。

解决方案

现在我明白了问题的真正本质,我可以创建一个真正的解决方案--即使它可能只是一个临时的规避。我能够以一种符合Linux启动顺序的原意和sysctl.d 配置内核的方法来做到这一点。

我把我的新配置文件留在了/etc/sysctl.d 。我创建了下面这个简单的Bash脚本来运行sysctl --system ,并把它存储在/usr/local/bin

#!/bin/bash
<SNIP – discarded a bunch of comments to save space>
sysctl --system

在继续进行之前,我对这个脚本进行了多次测试,以确保它的工作。如果你这样做,一定要多次测试,包括重启和不重启,以返回到原始内核配置。

创建服务

这个解决方案的真正关键是创建一个新的systemd 服务,其工作方式与旧的rc.local SystemV脚本类似。在这种情况下,我把它称为MyStartup.service ,并把我在上面创建的脚本重新命名为MyStartup.sh 。为了创建服务本身,我在/usr/local/lib/systemd/system 目录下创建了一个新的systemd 单元文件,这也是我要做的。这个服务将在启动时运行一次。我将这个新文件命名为MyStartup.service ,并添加了以下内容。

<SNIP – discarded a bunch of comments to save space>
[Unit]
Description=Runs /usr/local/bin/MyStartup.sh

[Service]
ExecStart=/usr/local/bin/MyStartup.sh

[Install]
WantedBy=multi-user.target

注意,像这样的systemd 单位文件不需要是可执行的。它由root.root拥有,权限为664。创建这个服务非常简单,而且我可以用它来完成许多本地启动任务,所以我打算即使在有永久修复现存问题的时候也要保留它。

下面的命令启用了这个新的服务。请注意,systemctl 命令默认搜索新的目录,没有任何选项、参数或我的催促来定位新的单元文件。

[root@f36vm ~]# systemctl enable MyStartup.service
Created symlink /etc/systemd/system/multi-user.target.wants/MyStartup.service → /usr/local/lib/systemd/system/MyStartup.service.

启用该服务并不运行MyStartup.sh 脚本。这个服务将在每次重启时运行该脚本。

测试

虚拟机一重启,我就以root身份登录,并运行以下命令来检查IPv6的状态,但它仍然处于激活状态。经过更多的测试,我确定该服务过早地运行了命令,所以我在MyStartup.sh 脚本中添加了一条命令,在运行命令前睡眠25秒。下面是编辑过的脚本。

#!/bin/bash
<SNIP – discarded a bunch of comments to save space>
# Wait a bit for things to start up and settle. It doesn't work without this.
sleep 25
# Run the sysctl command.
sysctl --system

我再次重启,并在可以登录的情况下验证了状态,结果是服务仍在睡眠。

[root@f36vm ~]# systemctl status MyStartup.service
 MyStartup.service - Runs /usr/local/bin/MyStartup.sh
     Loaded: loaded (/usr/local/lib/systemd/system/MyStartup.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2022-07-01 13:24:22 EDT; 14s ago
   Main PID: 667 (MyStartup.sh)
      Tasks: 2 (limit: 14129)
     Memory: 592.0K
        CPU: 1ms
     CGroup: /system.slice/MyStartup.service
             ├─ 667 /bin/bash /usr/local/bin/MyStartup.sh
             └─ 669 sleep 25

又等了一段时间后,我再次检查,服务已经成功完成,结果清楚地显示在最后几条日志中。进一步的测试表明,延迟三秒能可靠地工作,而只延迟一秒则总是失败。

Jul 01 13:24:22 f36vm.both.org systemd[1]: Started MyStartup.service - Runs /usr/local/bin/MyStartup.sh.

[root@f36vm ~]# systemctl status MyStartup.service
○ MyStartup.service - Runs /usr/local/bin/MyStartup.sh
     Loaded: loaded (/usr/local/lib/systemd/system/MyStartup.service; enabled; vendor preset: disabled)
     Active: inactive (dead) since Fri 2022-07-01 13:24:47 EDT; 358ms ago
    Process: 667 ExecStart=/usr/local/bin/MyStartup.sh (code=exited, status=0/SUCCESS)
   Main PID: 667 (code=exited, status=0/SUCCESS)
        CPU: 9ms

Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: net.ipv4.conf.all.send_redirects = 0
Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: net.ipv4.conf.all.accept_redirects = 0
Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: * Applying /usr/lib/sysctl.d/50-pid-max.conf ...
Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: kernel.pid_max = 4194304
Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: * Applying /etc/sysctl.d/99-local-network.conf ...
Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: net.ipv6.conf.all.disable_ipv6 = 1
Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: net.ipv6.conf.default.disable_ipv6 = 1
Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: * Applying /etc/sysctl.d/99-sysctl.conf ...
Jul 01 13:24:47 f36vm.both.org MyStartup.sh[1020]: * Applying /etc/sysctl.conf ...
Jul 01 13:24:47 f36vm.both.org systemd[1]: MyStartup.service: Deactivated successfully.

[root@f36vm ~]#

你可以在上面的数据输出中看到,99-sysctl.conf 文件中的配置语句被应用。我还用nmcli 命令来验证IPv6已经被禁用。

实现自动化

在弄清楚如何持续完成这个解决方案后,我在我用来分发新的和更新的配置文件的Ansible playbook中添加了一组任务来做这件事。这使得我很容易管理我目前的12台主机。我还把它添加到我在对新主机或因某种原因需要重新安装的主机进行基本安装后立即使用的playbook中。我将以下任务添加到这些游戏手册中。

这第一组任务是为我的解决方案添加一个新的服务。

    - name: Install 99-local-network.conf file to disable IPV6
      copy:
        src: /root/ansible/system-scripts/files/99-local-network.conf
        dest: /etc/sysctl.d
        mode: 0644
        owner: root
        group: root

    - name: Install MyStartup.sh
      copy:
        src: /root/ansible/system-scripts/files/MyStartup.sh
        dest: /usr/local/bin
        mode: 0754
        owner: root
        group: root

    - name: create /root/ansible/system-scripts/files/ directory
      file:
        path: /root/ansible/system-scripts/files/
        state: directory
        mode: 0755
        owner: root
        group: root  
      
    - name: Install MyStartup.service
      copy:
        src: /root/ansible/system-scripts/files/MyStartup.service
        dest: /usr/local/lib/systemd/system/
        mode: 0664
        owner: root
        group: root

    - name: Enable the MyStartup.service
      systemd:
        name: MyStartup.service
        state: stopped
        enabled: yes

    - name: Run the raw command to disable IPV4 so a reboot is not required at this time
      command:
        cmd: sysctl --system

将配置文件添加到sysctl.d 目录,然后运行带有这一系列任务的playbook,这种更复杂的解决方案的优点是,它无需重启就能禁用IPv6。

最后的想法

虽然只是安装带有正确内核参数的文件并重启,在我的一台主机上运行良好,但在我尝试的其他主机上都失败了。我在我的路由器/防火墙上工作时,寻找可能解释为什么在其他主机上失败的不同之处,但没有发现提供线索说明为什么会这样。我发现这个问题存在于新安装了Fedora 36的主机上,这些主机没有进行任何改变,也没有进行我经常进行的安装后配置,而那些主机则进行了我通常的改变。我确实打算继续调查。我打算向红帽公司提交一份错误报告,这样就有可能对这个问题进行实际的修复,而不是采用这两种规避方法之一。

这两种规避方法都不依赖于任何网卡配置文件的存在--无论是过去位于/etc/sysconfig/network-scripts 的旧式接口配置文件还是位于/etc/NetworkManager/system-connections 目录中的较新的 NetworkManager 接口连接文件。不管有没有这些文件,它都能工作。其结果对主机上的所有网络接口都是全局的。

我自己的解决方案是一个比较通用的方法,你可以用它来代替旧的SystemV启动时的rc.local 脚本。