Linux 双网卡 Server 与直连 Device 通信:标准路由与 Proxy ARP 两种方案(含勘误)
九月 20, 2026 [网络与安全] #Linux #网络 #routing背景
有一台 Linux Server,拥有两块网卡:
Server
┌─────────────────┐
│ │
eth0│ │eth1
│ │
└─────────────────┘
其中:
eth0连接正常的业务网络;eth1与一个 Device 直接连接;- Server 上的 HTTP 服务绑定到了
eth0的 IP192.168.1.10; - 希望 Device 可以通过
eth1访问这个 HTTP 服务。
Device ───────── eth1 ───────── Server
│
│ eth0
▼
192.168.1.10
HTTP Server
这里有一个非常重要的概念:
HTTP 服务绑定在哪个网卡的 IP 上,并不意味着只有从这个网卡进入的数据包才能访问这个服务。
Linux 判断一个 IP 数据包是否属于本机,主要看:
目标 IP 是否是本机地址
而不是:
数据包是不是从拥有这个 IP 的网卡进入
因此,只要 Device 能够把 dst = 192.168.1.10 的数据包送到 Server,Server 就可以把它交给 192.168.1.10:80 上的 HTTP 服务。
真正需要解决的问题是:
Device 如何把目标为
192.168.1.10的数据包送到 Server 的 eth1?
下面介绍两种不同的方案。
方案一:不同网段 + 默认网关
这是最传统、最标准的三层网络设计。
网络配置:
Server:
eth0 = 192.168.1.10/24
eth1 = 192.168.100.1/24
Device:
eth1 = 192.168.100.2/24
default gateway = 192.168.100.1
拓扑:
Server
┌──────────────────┐
│ │
LAN ──────────────┤ eth0 │
192.168.1.0/24 │ 192.168.1.10 │
│ │
│ eth1 │
│ 192.168.100.1 │
└────────┬─────────┘
│
│ Ethernet
│
192.168.100.2
Device
Device 和 Server 的 eth1 属于 192.168.100.0/24,Server 的 eth0 属于 192.168.1.0/24,两个接口处于不同 IP 网络。
Device 的路由表
Device 配置:
ip addr add 192.168.100.2/24 dev ethX
ip route add default via 192.168.100.1
路由表可以理解为:
192.168.100.0/24 dev ethX
default via 192.168.100.1
当 Device 访问 192.168.1.10 时,目标不属于 192.168.100.0/24,因此匹配 default via 192.168.100.1:
192.168.1.10
│
▼
下一跳:192.168.100.1
│
▼
Server eth1
ARP 的作用
Device 已经知道下一跳是 192.168.100.1,但 Ethernet 发送数据需要 MAC 地址,所以先发 ARP:
Who has 192.168.100.1?
Server 的 eth1 回复:
192.168.100.1 is-at <Server eth1 MAC>
然后 Device 才真正发送 IP 数据包:
Ethernet:
src MAC = Device MAC
dst MAC = Server eth1 MAC
IP:
src = 192.168.100.2
dst = 192.168.1.10
TCP:
src port = random
dst port = 80
注意:ARP 解决的是「下一跳 IP 对应哪个 MAC」,并不负责决定 IP 数据包最终应该去哪里——IP 路由已经在前面决定好了。
Server 收到数据包之后
Server eth1 收到 src = 192.168.100.2, dst = 192.168.1.10。
很多人会认为 eth1 → eth0 所以必须开启 IP forwarding,实际上不需要。因为 192.168.1.10 是 Server 自己的 IP,Linux 发现目标属于本机后直接交给本机协议栈:
eth1
│
▼
Linux IP stack
│
▼
发现目标 IP 是本机地址
│
▼
TCP
│
▼
192.168.1.10:80
│
▼
HTTP Server
这里不是 eth1 → routing → eth0,而是 eth1 → 本机协议栈 → HTTP Server。因此访问 Server 自己的服务时,net.ipv4.ip_forward 不需要开启。
Server 的回包
HTTP Server 回复 src = 192.168.1.10, dst = 192.168.100.2,Server 查路由:
192.168.100.0/24 dev eth1
于是回包从 eth1 发出,直接到达 Device。完整 TCP 流程:
Device Server
192.168.100.2 eth1: 192.168.100.1
│ │
│ TCP SYN │
│ dst=192.168.1.10:80 │
├─────────────────────────────>│
│ │
│ HTTP Server
│ │
│ TCP SYN/ACK │
│<─────────────────────────────┤
│ │
│ TCP ACK │
├─────────────────────────────>│
│ │
方案一的特点
- 两块 Server 网卡使用不同网段;
- Device 有明确的默认网关;
- 不需要任何 ARP 代答机制;
- 网络拓扑清晰,非常符合传统 IP 网络设计。
方案二:同一个地址空间 + /30 精确网络 + ARP 代答
第二种方案:
Server:
eth0 = 192.168.1.10/24
eth1 = 192.168.1.101/30
Device:
192.168.1.102/24
拓扑:
Server
┌──────────────────┐
│ │
LAN ──────────────┤ eth0 │
│ 192.168.1.10/24 │
│ │
│ eth1 │
│ 192.168.1.101/30 │
└────────┬─────────┘
│
│ Ethernet
│
192.168.1.102/24
Device
这个方案看起来很奇怪:为什么让 eth0 和 Device 处在同一个 /24 里?
核心目的就是:
让 Device 认为
192.168.1.10和自己处于同一个/24网络,从而不需要配置 Gateway 或静态路由。
而 Server 又通过 eth1 的 /30 形成一个非常小、更加精确的直连网络:
Server eth1 Device
192.168.1.101 ───────── 192.168.1.102
\ /
\──── /30 ─────────/
为什么 eth1 使用 /30?
192.168.1.101/30 对应的网络是 192.168.1.100/30:
192.168.1.100 network
192.168.1.101 Server
192.168.1.102 Device
192.168.1.103 broadcast
这是点对点链路中 /30 的经典用法。
Server 的路由表与最长前缀匹配
Server 同时拥有:
192.168.1.0/24 dev eth0
192.168.1.100/30 dev eth1
两个网络存在包含关系:
192.168.1.0/24
└─────────────────────────────────────┐
│
192.168.1.100/30 │
└──────────────┐ │
.100 .101 .102 .103
当 Server 访问 192.168.1.102 时,两个路由都能匹配,但 /30 比 /24 更具体,按 Longest Prefix Match(最长前缀匹配) 选择:
Server 192.168.1.10
│
│ dst = 192.168.1.102
▼
Longest Prefix Match
│
▼
192.168.1.100/30 dev eth1
这就是 /30 在这个设计里的作用:让 Server 到 Device 的回程明确走 eth1。
Device 为什么可以直接访问 192.168.1.10?
因为 Device 配置的是 192.168.1.102/24 而不是 /30,它认为 192.168.1.0/24 是自己的本地网络,192.168.1.10 属于其中。所以它不找 Gateway、不查静态路由,而是直接 ARP:
Who has 192.168.1.10?
勘误:代答 ARP 的其实不是 proxy_arp
本文最初的解释是「在 eth1 上开 proxy_arp,Server 就会替 192.168.1.10 回答 ARP」——这是同类教程里非常常见的一种说法,核对内核实际行为、并在隔离 netns 中实测复核后发现并不成立:目标 IP 就是 Server 本机地址(.10 配在 eth0 上)时,代答与 proxy_arp 无关。
误区是怎么来的
方案二确实能通,而 proxy_arp 又经常出现在同类方案里,于是「能通」被归因给了这个开关。实际上:
- 默认配置(
arp_ignore=0、proxy_arp=0)方案二就能工作; - 开了
proxy_arp后再验证,看到的「通了」并不是它的功劳——把proxy_arp关掉、清一次 ARP 缓存,照样通。
这种「先动手、后归因」的顺序很容易制造错误的因果:开关打开时网络是通的,不等于这个开关是通的必要条件。
内核实际怎么处理
Linux 收到 ARP 请求后,先判断目标 IP 是不是本机地址:
- 目标是本机地址(路由类型
RTN_LOCAL,.10属于本机)→ 是否应答由arp_ignore决定:arp_ignore=0(默认):本机在任何接口上的地址,都会在任意入接口上应答——这就是著名的 ARP flux。所以 eth1 收到「谁是.10」时会直接用 eth1 的 MAC 应答;arp_ignore=1:只有目标 IP 就配置在入接口上才应答。
- 目标不是本机地址(需要经其他接口路由过去)→ 才轮到
proxy_arp:Server 以自己的 MAC 替该地址应答,并且这个分支还要求ip_forward=1(proxy ARP 本质上是转发的配套行为)。
由此还能得到两个反直觉的结论:
- 做过 ARP 硬化的机器(
arp_ignore=1)方案二会失效,且proxy_arp=1也救不回来——因为目标 IP 是本机地址,根本不会走到 proxy ARP 分支; - 只有「HTTP 服务实际在 Server 后面的另一台主机上」这种目标 IP 非本机的场景,
proxy_arp=1+ip_forward=1才是必需的。
一句话判定
判断一个场景到底需不需要 proxy_arp,只看目标 IP:目标是本机地址 → 看 arp_ignore;目标不是本机地址 → 才看 proxy_arp + ip_forward。 文末「如何验证方案二」里给出了可复现的判定实验。
注:proxy ARP 并不是把 ARP 广播从 eth1 转发到 eth0,而是 Server 自己代替目标 IP 回答 ARP。
proxy_arp 真正用在哪
既然目标为本机地址时轮不到 proxy_arp,那它到底解决什么问题?它解决的是:
发送方认为目标和自己同一子网、直接 ARP 目标 IP,但 ARP 广播到不了目标,而代理主机能路由到目标。
Linux 上的完整触发链:
- 入接口收到 ARP 请求;
- 目标 IP 不是本机地址(是本机地址就走
arp_ignore那条分支,见上文勘误); - 入接口
proxy_arp=1,并且处于转发状态(通常即ip_forward=1)——实测把ip_forward关掉后,proxy_arp=1也不再代答,所以「只开 proxy_arp」是不够的; - 本机路由表能到达目标(目标若就在入接口的二层网段上,本来就会由目标自己应答,不需要代答);
- 本机用自己的 MAC 替目标应答,发送方把帧发给本机,之后由数据面转发到目标。
真实场景:
- 同一网段被拆到两个物理网段:经典 proxy ARP(RFC 1027)。两边主机以为对方在本地,中间路由器在两侧接口分别代答;思科路由器默认开启 proxy ARP,老网络里很常见;
- VPN 客户端与内网同网段:办公室 LAN 是
192.168.1.0/24,VPN 客户端也拿到192.168.1.200,但不在同一广播域;VPN 服务器在 LAN 接口代答.200,LAN 主机的流量先到服务器、再走隧道; - 移动 IP 的家乡代理:移动节点离开家乡网络后,家乡代理替它的家乡地址应答,家乡 LAN 的主机仍然以为它在本地;
- 容器/虚拟机网络:Pod/VM 互认为同子网、直接 ARP 对端,宿主机或 CNI 组件在相应接口代答,把流量拉进来再转发。本博客 QEMU 虚拟化笔记里「VM 用
/32地址 + 宿主 wlan0 开proxy_arp」就是这类用法; - PVLAN / 端口隔离:同一接口下互相隔离的域要互通时,
proxy_arp_pvlan让隔离域内的 ARP 由代理代答; - 网络迁移/合并过渡期:临时维持连通,避免立刻修改所有主机的配置。
这些场景的共同点是:发送方都「以为目标在本地」,且没有(或不能用)正确的网关。如果发送方正确配置了默认网关,跨网段时它 ARP 的是网关而不是目标,根本不会触发 proxy_arp。
现代网络里它很少被主动使用:proxy_arp 本质上是「合法 ARP 欺骗」,有中间人风险,还会带来 ARP 表膨胀、拓扑不透明、排查困难等问题。能用网关、路由协议、隧道或桥接解决时,都不该用它兜底。
完整数据流
Device Server
│ │
│ ARP: Who has 192.168.1.10? │
├───────────────────────────────────>│
│ │
│ ARP Reply: .10 is-at eth1 MAC │
│ (arp_ignore=0 的默认行为) │
│<───────────────────────────────────┤
│ │
│ TCP SYN │
│ dst=.10:80 │
├───────────────────────────────────>│
│ │
│ HTTP Server│
│ │
│ TCP SYN/ACK │
│<───────────────────────────────────┤
│ │
│ TCP ACK │
├───────────────────────────────────>│
Server 收到 dst = 192.168.1.10 后同样不需要 forwarding(目标是本机地址),直接交给本机 HTTP 服务;回包 src = 192.168.1.10, dst = 192.168.1.102 经最长前缀匹配走 /30 的 eth1 回到 Device。
两种方案的核心区别
两种方案最终都实现 Device → Server 192.168.1.10:80,但 Device 到 Server 的「第一跳」完全不同。
方案一
Device 192.168.100.2/24
│
│ 目标不在本地网段
▼
Default Gateway 192.168.100.1
│
▼
Server eth1
核心:IP Routing。
方案二
Device 192.168.1.102/24
│
│ 目标在本地网段(自认为)
▼
ARP
│
▼
Server eth1
Device 以为 .10 就在本地网段,直接 ARP;Server 侧靠默认的 arp_ignore=0 代答,回包再靠最长前缀匹配走 /30 的 eth1。
核心:同网段 ARP 代答 + 精确路由。
两种方案对比
| 项目 | 方案一 | 方案二 |
|---|---|---|
| Server eth0 | 192.168.1.10/24 | 192.168.1.10/24 |
| Server eth1 | 192.168.100.1/24 | 192.168.1.101/30 |
| Device | 192.168.100.2/24 | 192.168.1.102/24 |
| Device Gateway | 192.168.100.1 | 不需要 |
| Device 静态路由 | 不需要额外路由 | 不需要 |
| ARP 代答 | 不需要 | 需要(默认 arp_ignore=0 即可) |
| proxy_arp | 不需要 | 也不需要(目标为本机地址) |
Device 访问 .10 的方式 | 通过 Gateway | 直接 ARP |
| Server 回 Device | eth1 的 /24 路由 | eth1 的 /30 更具体路由 |
| 两个接口地址空间 | 不重叠 | 有重叠 |
| 网络模型 | 标准三层路由 | 同网段 ARP 代答 + 精确路由 |
| 配置复杂度 | 较低 | 较高,且依赖默认 ARP 行为 |
如何验证方案二
查看路由与最长前缀匹配
ip route
# 192.168.1.0/24 dev eth0
# 192.168.1.100/30 dev eth1
ip route get 192.168.1.102
# 192.168.1.102 dev eth1 src 192.168.1.101
ip route get 是验证 /30 优先于 /24 最直接的方法。
查看 ARP 相关参数
sysctl net.ipv4.conf.eth1.arp_ignore # 默认 0
sysctl net.ipv4.conf.eth1.proxy_arp # 默认 0
抓 ARP
Server 上:
sudo tcpdump -ni eth1 arp
Device 上 ping:
ping 192.168.1.10
可以看到:
ARP, Request who-has 192.168.1.10 tell 192.168.1.102
ARP, Reply 192.168.1.10 is-at <Server eth1 MAC>
这可以直接证明 Device → eth1 → ARP 代答 这条路径成立。
复现勘误:证明代答与 proxy_arp 无关
# 1. Server 上确认 proxy_arp 是关的(默认就是 0)
sysctl net.ipv4.conf.eth1.proxy_arp
# 2. Device 上清掉 ARP 缓存再试,依然能通
ip neigh del 192.168.1.10 dev ethX
ping -c 1 192.168.1.10
# 3. 反证:把 arp_ignore 改成 1,代答立即消失
sudo sysctl -w net.ipv4.conf.eth1.arp_ignore=1
# Device 上再清一次缓存
ip neigh del 192.168.1.10 dev ethX
ping -c 1 192.168.1.10 # 不通:ARP 无人应答
# 4. 实验完恢复默认
sudo sysctl -w net.ipv4.conf.eth1.arp_ignore=0
第 2 步通了、第 3 步不通,就足以推翻「必须开 proxy_arp」的归因:如果代答真由 proxy_arp 提供,proxy_arp=0 时第 2 步就该失败;而改变 arp_ignore 能立刻开关代答,说明真正起作用的是它。
以上结论在隔离的 netns 里按本文拓扑实测复核过(三个命名空间分别模拟 Server、Device 和 eth0 侧的远端主机;下表 proxy_arp 指 eth1 的开关,B 组回程另在 eth0 开了 proxy_arp):
A 组:目标 .10 是 Server 本机地址(配在 eth0 上)
| proxy_arp | arp_ignore | ip_forward | ping .10 | Device 的 ARP 表 |
|---|---|---|---|---|
| 0(默认) | 0(默认) | 0(默认) | OK | .10 → eth1 MAC |
| 1 | 0 | 0 | OK | .10 → eth1 MAC |
| 0 | 1 | 0 | FAIL | INCOMPLETE |
| 1 | 1 | 1 | FAIL | INCOMPLETE |
B 组:目标 .50 非本机,经 eth0 可达
| proxy_arp | arp_ignore | ip_forward | ping .50 | Device 的 ARP 表 |
|---|---|---|---|---|
| 0 | 0 | 1 | FAIL | INCOMPLETE |
| 1 | 0 | 1 | OK | .50 → eth1 MAC |
| 1 | 0 | 0 | FAIL | INCOMPLETE |
上面的对照实验分别证明:
- 全默认(
proxy_arp=0、arp_ignore=0、ip_forward=0)就能代答本机地址——方案二不需要proxy_arp; - 本机地址的代答完全由
arp_ignore控制,arp_ignore=1后代答消失,且proxy_arp=1+ip_forward=1也救不回来; proxy_arp只在目标非本机时生效(B1 → B2);- 即便如此,
proxy_arp也必须配合ip_forward=1才能代答(B3)。
抓 TCP
Server 上:
sudo tcpdump -ni eth1 'tcp port 80'
sudo tcpdump -ni eth0 'tcp port 80'
可以看到 eth1 上有 Device 到 192.168.1.10:80 的 TCP SYN,而 eth0 上通常看不到这条连接——因为它是 eth1 → 本机 TCP socket,而不是 eth1 → eth0 转发。
这能直接证明:
「服务绑定在 eth0 地址」和「数据包必须从 eth0 进入」是两件完全不同的事情。
注意事项:方案二属于非典型网络设计
方案二能工作,但依赖若干默认行为,在复杂环境中要特别小心:
- 依赖
arp_ignore=0:很多服务器会做 ARP 硬化(arp_ignore=1、arp_announce=2)或防火墙限制,这会让方案二直接失效,且开proxy_arp也救不回来; - 地址空间重叠:Server 两个接口的网段互相包含,ARP 行为(ARP flux)、
arp_announce、rp_filter、源地址选择、防火墙规则都需要额外确认; - ARP flux 的副作用:默认行为意味着 Server 在任何接口上都可能代答本机地址,在共享二层网络中可能引起混乱;
- 简单实验环境里方案二很有价值——Device 只配一个
/24地址、不配 Gateway、不配静态路由就能访问 Server 另一接口上的服务;但生产网络优先选择方案一的标准三层设计。
总结
这个问题实际上涉及 Linux 网络中的三个不同层次:
┌──────────────────────────────────────┐
│ Application │
│ HTTP 192.168.1.10:80 │
└──────────────────┬───────────────────┘
│
┌──────────────────▼───────────────────┐
│ IP │
│ Route / Longest Prefix │
└──────────────────┬───────────────────┘
│
┌──────────────────▼───────────────────┐
│ Ethernet │
│ ARP │
└──────────────────────────────────────┘
方案一:不同网段 + 默认网关,靠 IP Routing 把包交给 Server,标准三层设计。
方案二:Device 用 /24 把 .10 认成同网段邻居,直接 ARP;Server 用默认的 arp_ignore=0 在 eth1 上代答,再用 /30 的最长前缀匹配把回程钉在 eth1 上。
最后要记住三点:
- 服务绑定在哪个接口的 IP 上,和「数据包从哪个接口进入」无关——只要目标 IP 是本机地址,内核就交给本机协议栈,不需要 forwarding;
- 方案二里代答 ARP 的是默认的
arp_ignore=0(ARP flux),不是proxy_arp;proxy_arp只在目标 IP 非本机、需要经其他接口路由时才起作用,而且要求ip_forward=1; - 验证时「先关掉可疑开关再看」——默认行为的功劳很容易被记到刚打开的开关上,本文对
proxy_arp的误判就是这么来的。