这是我在 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!回想起来,这是一篇过长的帖子。本来可以更简洁一些。希望有人在搜索时发现这篇漫无边际的文章有用。