诊断 Linux 路由问题:多个外部 IP 和多个内部子网以及多个路由表

诊断 Linux 路由问题:多个外部 IP 和多个内部子网以及多个路由表

这是我在 Stack Exchange 上的第一篇文章。首先,我要感谢这个社区,在我的计算机工程生涯中,我在这里学到了很多东西。:)

我写这篇文章主要是为了寻找如何诊断和修复路由问题的方向。这是迄今为止我尝试过的最复杂的服务器设置,我觉得问题出在 Linux 如何处理网络接口和流量上。我将继续研究 Linux 网络的复杂性,但希望这里有人可以帮助指导我的搜索。

我的问题是这样的:我有五个可路由的互联网 IP(所有 IP 的 MAC 地址都不同),它们被分配给一个外部接口,而一个内部接口负责管理多个内部 VLAN 子网。将所有这些绑定在一起的是几个路由表,这些路由表为特定的内部服务器 IP 设置了规则,以将它们路由到不同的外部 IP,反之亦然。除了一些看似随机的异常值外,大多数流量都正常流动。例如,内部物理网络上的工作站无法访问 duckduckgo.com 和其他看似随机的网站,而其他工作站则加载正常。

真正让我困惑的是,我有一个桥接的 openvpn 设置,它可以跨两个位置桥接 B 类 /21 子网。两个位置都设置了单独的 DHCP 服务器实例,这些实例将发往互联网的流量直接引导出其相应的连接。当我在第二个 [客户端] 位置时,我可以将工作站的默认路由指向主 [服务器] 位置(有效地从辅助位置遍历第 2 层调谐的 vpn 链接以退出到主位置的互联网),我可以毫无问题地访问所有这些网站,例如 duckduckgo.com。这似乎不是防火墙或路由问题,而是我不知道的 Linux 如何处理流量的细微差别。

一些注意事项:

我的 ISP 是一家规模较小的本地 ISP,在城里提供光纤。与 Charter(本地有线电视公司)处理 IP 寻址的方式相比,他们的网络 IP 寻址配置对我来说很特别。本地光纤 ISP 有多个 /24 互联网可路由 IP 子网,它们似乎都位于同一数据链路层上。例如,在我的配置中,我有一个 DHCP 分配的 IP,它通常位于一个 /24 子网上,而我的四个静态 IP 位于另一个子网上。在我的终端,我将所有 IP 放在不同的 MAC 地址上,但它们都通过相同的物理和数据链路层进行通信。此外,他们的主路由器对所有子网使用相同的 MAC 地址,如以下 arp 输出所示(其中 br14 是 DHCP,mac0-3 是 macvlan 设备):

XXX-XXX-97-1.static.ISP  ether   cc:4e:24:9f:47:00   C                     br14
XXX-XXX-32-1.static.ISP  ether   cc:4e:24:9f:47:00   C                     mac0
XXX-XXX-32-1.static.ISP  ether   cc:4e:24:9f:47:00   C                     mac1
XXX-XXX-32-1.static.ISP  ether   cc:4e:24:9f:47:00   C                     mac2
XXX-XXX-32-1.static.ISP  ether   cc:4e:24:9f:47:00   C                     mac3

虽然这不是我本来会做的,但从我的研究来看,我找不到任何证据表明这是不恰当的。不过我要说的是,当我设置 net.ipv4.conf.all.rp_filter=1 并记录 martians 时,我的系统日志会因不属于我的流量而崩溃。

Feb 10 17:49:44 srv3 kernel: [   12.770624] IPv4: martian source XXX.XXX.33.147 from XXX.XXX.33.1, on dev mac0

我的 sysctl.conf 当前如下:

net.ipv4.conf.all.rp_filter=2

net.ipv4.ip_forward=1

net.ipv4.conf.all.arp_filter=1
net.ipv4.conf.all.arp_announce=2
net.ipv4.conf.all.arp_ignore=1

net.ipv4.conf.all.log_martians=1

在详细介绍如何配置我的设置之前,我想用一段简单的文字重申一下我的情况。我有一个配置了五个 IP 的外部物理接口,使用基于策略的路由将流量引导到内部子网。除了互联网上看似随机的端点外,这在大多数情况下都有效。更奇怪的症状是,当我远程访问并遍历第 2 层隧道 VPN 连接(桥接到本地物理网络工作站使用的同一桥接设备)并以相同的方式退出到互联网时,我不会遇到这些问题。我没有在 openvpn 中使用客户端到客户端指令,所以我的理解是所有流量都应该以相同的方式通过内核处理。

我现在已经束手无策了,理解能力也有限。就我目前所知,我错过了 Linux 内部发生的一些事情。如果有人知道一个简单的修复方法,我会非常高兴。但是,我相当肯定,这将特定于我正在使用的“参数”和我试图创建的范例。我觉得这应该有效,而且我已经很接近了,我正在寻求有关如何深入诊断幕后情况的建议。即使(虽然我并不期望)这与我的 ISP 有关,我也只需要知道如何提出相关数据来指出问题。我与 ISP 的所有者关系很好,所以如果事情发展到这个地步,我会洗耳恭听。同样,我该如何开始真正深入研究这个问题?

---以下是有关服务器在启动时如何配置的冗长且可能不需要的解释。我只想尽可能多地包含数据。感谢您的关注和帮助!---

这是设置,它是一个带有两个物理网卡的 Ubuntu 20.04 主机操作系统。eno1 是位于主板上的外部(互联网)接口。第二个接口是支持 8021q vlan 的卡,用作内部接口。

网络配置可分为三个主要部分:1.) 外部接口配置,具有五个可路由到 Internet 的 IP。2.) 内部接口配置,由多个位于不同 VLAN 上的 B 类和 C 类子网组成。这些子网大部分位于桥接接口上。3.) 将所有这些绑定在一起的是使用 iproute2 的多个路由表,防火墙由 nftables 处理。

大多数接口都是通过 netplan 调出的。需要注意的是:五个外部 IP 都位于单独的虚拟接口上。第一个是主桥接接口 [br14],它通过 DHCP 分配 IP,而其余四个是 macvlan 接口,主要通过 networkd-dispatcher 执行的脚本配置,而它们的静态 IP 是在 netplan 中分配的。

Netplan 会议:

network:
  ethernets:
    eno1:
      match:
        macaddress: B8:XX:XX:XX:XX:A2
    eno2:
      match:
        macaddress: 00:XX:XX:XX:XX:71
      set-name: eno2
    mac0:
      addresses: [XXX.XXX.XXX.66/24]
    mac1:
      addresses: [XXX.XXX.XXX.67/24]
    mac2:
      addresses: [XXX.XXX.XXX.135/24]
    mac3:
      addresses: [XXX.XXX.XXX.136/24]
  vlans:
    eno2-vlan1:
      id: 1
      link: eno2
    eno2-vlan2:
      id: 2
      link: eno2
    eno2-vlan3:
      id: 3
      link: eno2
    eno2-vlan4:
      id: 4
      link: eno2
    eno2-vlan5:
      id: 5
      link: eno2
    eno2-vlan6:
      id: 6
      link: eno2
    eno2-vlan7:
      id: 7
      link: eno2
  bridges:
    br14:
      interfaces: [eno1]
      macaddress: B8:XX:XX:XX:XX:A2
      dhcp4: true
      dhcp4-overrides:
        use-dns: false
      nameservers:
        addresses: [XXX.XXX.XXX.5, 8.8.8.8]
    br0:
      addresses: [192.168.0.6/29]
    br1:
      interfaces: [eno2-vlan1]
      addresses: [172.23.3.30/21]
    br2:
      interfaces: [eno2-vlan2]
      addresses: [172.20.3.30/21]
    br3:
      interfaces: [eno2-vlan3]
      addresses: [172.22.3.30/21]
    br4:
      interfaces: [eno2-vlan4]
      addresses: [192.168.3.6/29]
    br5:
      interfaces: [eno2-vlan5]
      addresses: [172.21.3.30/21]
    br6:
      interfaces: [eno2-vlan6]
      addresses: [192.168.3.14/29]
    br7:
      interfaces: [eno2-vlan7]
      addresses: [192.168.1.93/27]
  version: 2

/etc/networkd-dispatcher/routable.d 中有一个如下所示的脚本:

#!/bin/bash

if [ ! -d "/run/rt_tables" ]; then
    mkdir -m 750 /run/rt_tables
    touch /run/rt_tables/availDevs
    chmod 640 /run/rt_tables/availDevs
fi

echo "$IFACE" >> /run/rt_tables/availDevs

case "$IFACE" in
    'br14')
        ip link add mac0 link br14 address B8:XX:XX:XX:XX:A3 type macvlan
        ip link add mac1 link br14 address B8:XX:XX:XX:XX:A4 type macvlan
        ip link add mac2 link br14 address B8:XX:XX:XX:XX:A5 type macvlan
        ip link add mac3 link br14 address B8:XX:XX:XX:XX:A6 type macvlan
    ;;
    *)
        /usr/local/sbin/checkAvailableDevs.sh
    ;;
esac

到目前为止,这基本上涵盖了我之前分解网络配置的第 1 和第 2 个组件。第三个组件主要通过“checkAvailableDevs.sh”脚本进行设置,如 switch 语句的默认情况中所示。

以下是 checkAvailableDevs.sh 脚本,其简要说明如下:

#!/bin/bash
tableSetupScript="`dirname $0`/rtableSetup.sh"
rt_runDir="/run/rt_tables"
rt_tablesConfDir="\/usr\/lib\/rt_tables"

function echoTableVarDir() {
    echo $(echo ${rt_tablesConfDir//\\/})
}

tableNames=($(find `echoTableVarDir` -type f | sed -r \
        -e "/`echo $rt_tablesConfDir`\/t_/"'!d' \
        -e "s/(`echo $rt_tablesConfDir`\/t_)(.+)/\2/"))

for tableName in ${tableNames[*]}
do
    source `echoTableVarDir`/t_$tableName

    declare -a availDevs
    for reqDev in ${reqDevs[*]}
    do
        availDevs+=(`
            while read -r availDev; do
                if [ "$reqDev" == "$availDev" ]; then
                    echo "$availDev"
                fi
            done < $rt_runDir/availDevs
        `)
    done

    if [ ${#reqDevs[@]} -eq ${#availDevs[@]} ]; then
        if [ ! -f "$rt_runDir/`echo $tableName`_configured" ]; then
            $tableSetupScript `echoTableVarDir`/t_$tableName
            touch $rt_runDir/"$tableName"_configured
            chmod 640 $rt_runDir/"$tableName"_configured
        fi
    fi

    unset availDevs
done

免责声明:我并不声称自己是最好的 bash 脚本编写者。我主要是一名 Java 工程师,这又是我构建过的最复杂的系统配置。请原谅可能存在的低效率。

接下来,解释一下 checkAvailableDevs.sh:

首先,如 networkd-dispatcher 脚本中所示,对于触发 networkd-dispatcher 脚本的每个接口,接口名称都会附加到文件 /run/rt_tables/availDevs。稍后,当执行 checkAvailableDevs.sh 脚本时,它会查找位于 /usr/lib/rt_tables 中的所有文件,这些文件表示要配置的路由表的配置。这些文件被命名为 t_{tableName}。例如,文件名为 t_mail1,其中“mail1”代表路由表名称。

这些路由表配置文件如下所示:

#!/bin/sh
tableName="mail1"

reqDevs=(mac2 br0 br1 br2 br7)

fromIP="172.XXX.XXX.XXX"
initDefaultGW=1
gwDev="mac2"
gwIP="XXX.XXX.XXX.1"
devRoutes=(br0 br1 br2 br7)

实际情况是,checkAvailableDevs.sh 会根据 networkd-dispatcher 认为可路由的 /run/rt_tables/availDevs 接口列表检查“reqDevs”数组中的设备名称。如果所有设备都可用,则使用 t_{tableName} 文件执行第二个脚本 rtableSetup.sh,以获取路由表所需的特定配置参数。

rtableSetup.sh:

#!/bin/bash

# (function args)
# $1 - dev
#
# [SET] devCDIR
get_dev_CDIR() {
    devCDIR=`ip address show $1 | sed -r \
            -e '/inet\s/ !d' \
            -e 's/(.+inet\s)(.+\..+\..+\..+)(\sbrd.+)/\2/'`
}

# (function args)
# $1 - devCDIR
#
# [SET] netCDIR
get_net_CDIR() {
    netCDIR=`ipcalc $1 | awk '$1 == "Network:" { print $2 }'`
}

# (function args)
# $1 - dev
#
# [SET] netCDIR
get_net_CDIR_from_dev() {
    get_dev_CDIR $1
    get_net_CDIR $devCDIR
}


tableVarFile=$1
if [ "$tableVarFile" ]; then
    source $tableVarFile

    if [ "$tableName" ]; then
        ip rule add from $fromIP table $tableName

        if [ "$removeFromDefaultTable" ] && [ $removeFromDefaultTable == 1 ]; then
            if [ "$fromDev" ]; then
                get_net_CDIR_from_dev $fromDev
                echo "attempting to remove $fromDev from default table"
                echo "ip route del $netCDIR dev $fromDev"
                ip route del $netCDIR dev $fromDev
            else
                echo "Error: \$fromDev not spcified in table var file: $tableVarFile"
                exit
            fi
        fi

        if [ $initDefaultGW == 1 ]; then
            if [ "$gwIP" ] && [ "$gwDev" ]; then
                get_net_CDIR_from_dev $gwDev
                ip route add default via $gwIP dev $gwDev table $tableName
                ip route add $netCDIR dev $gwDev table $tableName
            else
                echo "Error: \$gwIP or \$gwDev not defined in table var file: $tableVarFile"
                exit
            fi
        fi

        if [ ${#devRoutes[@]} -gt 0 ]; then
            for devRoute in ${devRoutes[*]}
            do
                get_net_CDIR_from_dev $devRoute
                ip route add $netCDIR dev $devRoute table $tableName
            done
        else
            echo "Warning: No DevRoutes configured in table var file: $tableVarFile"
        fi
    else
        echo "Error: No tableName defined"
    fi
fi

这三个文件:checkAvailableDevs.sh、t_{tableName}、rtableSetup.sh 负责我的网络设置的第三个组件。我可以验证此网络配置的所有三个组件是否正常工作,因为我期望设置外部接口、内部接口以及最终的路由表。我不需要包含所有这些脚本,但我希望尽可能完整。

答案1

之前解决了这个问题。我将 VPN 上的 MTU 设置为 1492,当将分接接口添加到网桥时,整个网桥的 MTU 会降低到 1492,而外部互联网接口仍为 1500。这会导致碎片化,有些网站不喜欢这种情况。

但是嘿,我在这个过程中学到了一些 tcpdump!回想起来,这是一篇过长的帖子。本来可以更简洁一些。希望有人在搜索时发现这篇漫无边际的文章有用。

相关内容