跨网段把包交给网关,为什么目标IP不是网关?用隔离实验看清IP与MAC

发布时间:2026/10/12 3:49:59

跨网段把包交给网关,为什么目标IP不是网关?用隔离实验看清IP与MAC 跨网段把包交给网关为什么目标IP不是网关用隔离实验看清IP与MAC学网络时我经常看到一句话同网段直接发送跨网段交给网关。如果真的把包交给网关目标地址是不是就该填网关网关又怎么知道我最终想访问谁这个疑问来自两个“目标”混在一起IP包的最终目标和当前这段链路上以太网帧的接收者。这次不改家庭路由器。我们在 Ubuntu 的隔离网络里建一台主机 A、一台路由器 R、一台远端主机 B抓到两处实际帧再用一个断路对照确认网关的作用。1. 先搭一张没有互联网的网络环境为 WSL 的 Ubuntu-24.04内核5.15.167.4-microsoft-standard-WSL2iproute26.1.0。三个网络命名空间只用虚拟网线 veth 相连没有物理上联也没有 NAT。地址使用文档示例网段不需要公网或家里的宽带账号。节点接口地址默认网关A / eth0192.0.2.2/24192.0.2.1R / left192.0.2.1/24本实验不需要R / right198.51.100.1/24本实验不需要B / eth0198.51.100.2/24198.51.100.1R 只在自己的命名空间里开启 IPv4 转发。B 的返回路由也必须配置好否则 A 的请求能到达 B回复却未必能回到 A。Linux 网络命名空间隔离网络接口、路由等资源。这里的删路由操作只作用于新建的 A不触及 WSL 宿主或 Windows 网络。network_namespaces 手册2. 掩码怎样帮助判断“同网段”192.0.2.2/24的 IPv4 掩码是255.255.255.0。地址与掩码按位与得到网络前缀192.0.2.2 255.255.255.0 192.0.2.0 192.0.2.1 255.255.255.0 192.0.2.0 198.51.100.2 255.255.255.0 198.51.100.0所以在这张简单网络里A 到 R 的左侧地址属于本地直连网络B 则不在其中。但真实系统不是每次只拿自己的掩码做一个“同/不同”的判断。配置地址后系统会有相应的直连路由发送时还要查询路由表可能存在更具体的路由和其他策略。掩码解释直连网络的范围路由表决定这次发送的下一跳。不要把入门口诀当成忽略全部路由配置的规则。3. 先看系统怎么选再看实际怎么发实验脚本内部a保存新建的 A 命名空间名称。下面这些命令在命名空间存活期间执行不是在实验退出后直接照抄变量名。ip -n $a route get 192.0.2.1 ip -n $a route get 198.51.100.2实际输出的关键字段是192.0.2.1 dev eth0 src 192.0.2.2 198.51.100.2 via 192.0.2.1 dev eth0 src 192.0.2.2第一条没有额外的via属于直连选路第二条明确指定经由192.0.2.1。ip route get查询的是内核对这个目标的路由解析结果并不等于已经成功通信。ip-route 手册接下来真正发送一次 ICMP Echo Request并分别在 A 的 eth0 出口、R 的 right 出口观察。两处记录如下MAC 是实验手动配置的本地管理地址不是家里设备的地址。字段A发出时R向B转发时帧的源MAC02:00:00:00:00:10A02:00:00:00:00:20R右侧帧的目标MAC02:00:00:00:00:11R左侧02:00:00:00:00:21BIP源地址192.0.2.2192.0.2.2IP目标地址198.51.100.2198.51.100.2TTL6463ICMP序号11这次没有 NAT所以 IP 源、目标地址都没有被转换。R 仍然会减小 TTL、更新 IPv4 头校验和“IP目标不变”不等于“IP头所有字段都不变”。校验和更新属于协议机制说明本次采集器没有把它列为验证字段。4. 原来“交给网关”发生在这一层A 的 IP 包里保留最终目标 BIP目标198.51.100.2但 A 当前只能在自己的链路上发送以太网帧。路由结果告诉它下一跳是 R 的左侧接口。因此这一跳的帧以 R 左侧的 MAC 为目标。R 收到后根据包里的最终目标查询自己的路由从 right 接口发送另一帧。这一段链路直接连着 B帧的目标 MAC 就变成 B。可以把两层理解为IP目标最终要送到哪里 当前帧目标这一段链路先交给谁以太网上的 IPv4 通信通常通过 ARP 获取下一跳对应的 MAC。本次 A 的邻居表只出现了网关192.0.2.1 - 02:00:00:00:00:11没有出现远端 B 的 MAC。ARP规范 RFC 826注意证据边界邻居表证明缓存了这个映射采集器没有保存 ARP 请求/应答不能说我们已经展示了完整 ARP 交换过程。IPv6 也不能照搬这部分它使用邻居发现机制而非 ARP。5. 删除默认路由为什么局域网还通有了前面的认识可以做一个有明确预测的实验只删 A 的默认路由直连路由保留。A 还能访问同网段的 R但不能再访问另一个子网的 B。在 A 命名空间内部执行ip -n $a route del default ip netns exec $a ping -c 1 -W 2 192.0.2.1 ip -n $a route get 198.51.100.2 ip netns exec $a ping -c 1 -W 2 198.51.100.2实际结果操作结果ping同网段R1发1收成功查询到B的路由Network is unreachableping Bconnect: Network is unreachable这里不是“B收到了但没有回复”也不是“已经发出然后超时”。A 当前就没有可用于这个目标的路由。排障时可以先把失败位置分开本次删路由得到的是“发送前就找不到路径”DHCP错误网关实验则保留了路由但下一跳地址不存在邻居解析停在INCOMPLETE。两者都可能让人觉得“网络不通”却需要检查不同的配置。这里是对两组已有实验的比较不是新增抓包结论。恢复 A 的默认路由后再 ping B得到 1 发 1 收。这组成功、失败、恢复的对照比单次 ping 更能说明默认路由在这张拓扑里的作用。图片是实验日志节选的排版不是终端窗口截图。它展示的虚拟链路延迟不用于判断家庭宽带速度。6. 验证程序为什么这样设计我们没有在家里的 LAN 上制造故障而是把故障限制在三个新建命名空间里。命名空间名称带随机标识脚本只清理本次创建的资源不删除别人的环境。脚本做三层验证选路验证比较直连目标和跨网段目标的ip route get。帧字段验证对两处出站 ICMP 请求做断言检查 IP目标不变、MAC目标改变、TTL减1。因果对照删默认路由确认直连可达而远端失败恢复后再次确认可达。Ubuntu 里没有预装 tcpdump所以这次使用 Python 标准库的AF_PACKET原始套接字只监听隔离实验接口。采集器限定出站 IPv4 ICMP Echo Request 和目标 B保存首个符合条件的帧字段。它不是通用抓包工具只适合这个没有 VLAN、没有隧道的实验不能拿去解析任意网络流量。需要广泛协议分析时应使用成熟工具而不是不断扩展这个小程序。为了避免“刚启动采集器就发送结果没抓到”的时序问题采集器在绑定接口后写就绪标记主脚本看到两处标记才发送请求。采集完成等待进程退出异常时同样执行清理。完整源码、依赖准备和运行命令放在文末附录。正文中的命名空间变量由编排脚本创建不需要在真实网卡上手动制造故障。7. 把口诀补全“同网段直接发跨网段交给网关”可以帮助入门但最好继续追问这次匹配的是哪条路由是否还有更具体的路由IP最终目标是谁当前链路的下一跳是谁反方向有没有路由中间设备有没有转发和过滤规则这篇实验说明把包交给网关不需要把IP最终目标改成网关下一跳与最终目标属于两个层次。我们验证的是没有 NAT 的两个子网之间的 IPv4 转发不是所有网络配置的验收也不证明家庭光猫、OpenWrt 或外网入口已经配置正确。附录完整源码与复现以下是完整文件不需要先下载仓库。环境限定为 Ubuntu 24.04 amd64本次为 WSL2需要 Bash、iproute2、ping、Python 3、sysctl、timeout 和 dpkg-deb不能把 Windows PowerShell 当成 Bash。实验需要 root 和创建网络命名空间的权限普通受限容器可能无法运行。不要在生产路由器上运行。在 Linux 中建立任意工作目录例如mkdir -p ~/network-lab-next-hop/scripts cd ~/network-lab-next-hop将下面每个代码块按标题文件名放入 scripts 目录使用 UTF-8 和 LF 换行。脚本按自身位置寻找文件不依赖我的 D 盘路径。本篇不需要 dnsmasq、BusyBox、nftables 或 conntrack只使用系统已有工具与 Python 标准库。文件scripts/verify-network-next-hop.sh#!/usr/bin/env bash set -euo pipefail base$(cd -- $(dirname -- $0)/.. pwd) out$base/data/verification/network-next-hop-$(date %Y%m%d-%H%M%S) mkdir -p $out exec (tee $out/stdout.txt) 21 for tool in ip ping python3; do command -v $tool /dev/null; done test $(id -u) 0 token$(cat /proc/sys/kernel/random/uuid) abl-${token:0:8}-a rbl-${token:0:8}-r bbl-${token:0:8}-b created() pids() cleanup() { for pid in ${pids[]}; do kill $pid 2/dev/null || true; wait $pid 2/dev/null || true; done for ns in ${created[]}; do ip netns del $ns; done } trap cleanup EXIT for ns in $a $r $b; do ip netns add $ns; created($ns); ip -n $ns link set lo up; done # All veth endpoints live in new namespaces, never on the host network. ip -n $a link add eth0 type veth peer name left netns $r ip -n $r link add right type veth peer name eth0 netns $b ip -n $a link set eth0 address 02:00:00:00:00:10 up ip -n $r link set left address 02:00:00:00:00:11 up ip -n $r link set right address 02:00:00:00:00:20 up ip -n $b link set eth0 address 02:00:00:00:00:21 up ip -n $a addr add 192.0.2.2/24 dev eth0 ip -n $r addr add 192.0.2.1/24 dev left ip -n $r addr add 198.51.100.1/24 dev right ip -n $b addr add 198.51.100.2/24 dev eth0 ip -n $a route add default via 192.0.2.1 ip -n $b route add default via 198.51.100.1 ip netns exec $r sysctl -qw net.ipv4.ip_forward1 echo Versions uname -r ip -V echo Same-subnet route ip -n $a route get 192.0.2.1 ip netns exec $a ping -c 1 -W 2 192.0.2.1 echo Remote-subnet route ip -n $a route get 198.51.100.2 ip netns exec $a python3 $base/scripts/capture-lab-icmp.py eth0 $out/a-frame.json $out/a.ready pids($!) ip netns exec $r python3 $base/scripts/capture-lab-icmp.py right $out/r-frame.json $out/r.ready pids($!) for attempt in $(seq 1 100); do if test -f $out/a.ready test -f $out/r.ready; then break; fi sleep 0.05 done test -f $out/a.ready test -f $out/r.ready ip netns exec $a ping -c 1 -W 2 198.51.100.2 for pid in ${pids[]}; do wait $pid; done pids() echo A neighbor table ip -n $a neigh show echo Remove only A lab default route ip -n $a route del default ip netns exec $a ping -c 1 -W 2 192.0.2.1 if ip -n $a route get 198.51.100.2; then echo Unexpected remote route; exit 1; fi if ip netns exec $a ping -c 1 -W 2 198.51.100.2; then echo Unexpected remote reachability; exit 1; fi ip -n $a route add default via 192.0.2.1 ip netns exec $a ping -c 1 -W 2 198.51.100.2 python3 - $out PY import json import sys from pathlib import Path p Path(sys.argv[1]) a json.loads((p / a-frame.json).read_text()) r json.loads((p / r-frame.json).read_text()) assert a[ethernet_destination] 02:00:00:00:00:11 assert r[ethernet_destination] 02:00:00:00:00:21 assert a[ip_source] r[ip_source] 192.0.2.2 assert a[ip_destination] r[ip_destination] 198.51.100.2 assert a[icmp_sequence] r[icmp_sequence] assert a[ttl] r[ttl] 1 (p / result.json).write_text(json.dumps({passed: True, a_frame: a, router_frame: r, scope: isolated Linux namespaces, no NAT, no host routing changes}, indent2)) print(ASSERTIONS PASSED: IP destination unchanged, next-hop MAC changed, TTL decreased by one) PY echo Evidence: $out文件scripts/capture-lab-icmp.pyimport json import socket import struct import sys from pathlib import Path interface, output, ready sys.argv[1:] with socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) as sock: sock.bind((interface, 0)) sock.settimeout(10) Path(ready).touch() while True: frame, metadata sock.recvfrom(65535) if metadata[2] ! socket.PACKET_OUTGOING or len(frame) 42: continue if frame[12:14] ! b\x08\x00 or frame[23] ! 1: continue header_length (frame[14] 15) * 4 if frame[14 header_length] ! 8: continue destination socket.inet_ntoa(frame[30:34]) if destination ! 198.51.100.2: continue result { interface: interface, ethernet_destination: :.join(f{x:02x} for x in frame[:6]), ethernet_source: :.join(f{x:02x} for x in frame[6:12]), ip_source: socket.inet_ntoa(frame[26:30]), ip_destination: destination, ttl: frame[22], icmp_sequence: struct.unpack(!H, frame[14 header_length 6:14 header_length 8])[0], } Path(output).write_text(json.dumps(result, indent2), encodingutf-8) print(json.dumps(result)) break运行与结果判断在上述工作目录执行sudo bash scripts/verify-network-next-hop.sh输出保存在工作目录的 data/verification 下以运行时间命名。正常完成会显示断言通过还应检查本次目录的 result.json 中 passed 为 true。预设的失败对照属于实验内容不意味着脚本整体失败依赖缺失、权限不足或断言失败则不能算通过。不要使用 python -O 或 PYTHONOPTIMIZE 禁用断言。脚本只修改自己新建的随机名称网络命名空间正常退出及普通错误退出会执行清理。断电、强制终止或 kill -9 不能保证清理可用 ip netns list 检查只处理确认属于本次实验的资源不要批量删除其他命名空间。输出中的地址来自隔离实验不需要填写家庭账号、真实设备地址或认证信息。
延伸阅读

更多相关文章

2026/10/12 3:49:59

从状态码到强类型建模,深入理解 ABAP CDS Enumerated Type 的定义与设计

在很多 SAP 业务系统里,状态字段看起来都很不起眼。销售订单可能用一个字符表示处理状态,发票可能用 C 表示已创建,用 P 表示已支付,审批流程可能使用 0、1、2 表示不同阶段。这样的设计在传统 ABAP 系统里已经存在几十年,数据库里保存的往往只是一个 CHAR1、NUMC1 或整数…

2026/10/12 3:44:59

【xilem0.4基础语法学与练】第34课 task 异步任务视图

前言 参考官方文档:https://docs.rs/xilem/latest/xilem/view/fn.task.html 版本:Xilem 0.4 一、task基础概念 task 是一个特殊的视图组件,用来在UI树内部运行异步Future任务。 普通按钮回调是同步代码;如果需要定时循环、网络请求…

2026/10/12 3:44:59

【Xilem0.4基础语法学与练】第28课 button 通用按钮组件

前言 文档参考:https://docs.rs/xilem/latest/xilem/view/fn.button.html 版本:Xilem 0.4 一、button基础概念 button 是通用可自定义内容按钮组件,按钮内部可以放置任意非交互式视图:label、flex、sized_box、z_stack等布局组合…

2026/10/12 4:55:02

【Linux系统】06 进程概念

目录 ​编辑 1 冯・诺依曼体系结构 2 操作系统 (OS) 定位 2.1 广义与狭义操作系统 2.2 OS 两大目标 2.3 系统调用 & 库函数 3 进程基础概念 & PCB (task_struct) 3.1 什么是进程 3.2 PCB task_struct(Linux 的进程控制块) 3.3 查看进程…

2026/10/12 4:55:02

年终奖不发之后:绩效目标、系数规则与激励修复策略

一进十二月,办公室的气温就跟着年终奖的消息一起浮动。今年我们公司的情况很直接:官方通知就一句话——“鉴于今年公司销量、利润率等指标未达成年终目标,所以今年没有年终激励奖”。没有展开解释,没有缓冲余地,消息一…

2026/10/12 4:55:02

【Linux系统】05 Linux开发工具(下)

目录 1 make 与 Makefile 自动化构建 1.1 为什么需要 Makefile 1.2 Makefile 基础规则 1.3 make 工具推演执行逻辑 1.4 伪目标 .PHONY 1.5 Makefile 进阶语法 自定义变量 三大自动变量(高频面试) wildcard 通配符 后缀替换 模式规则 %.o:%.c …

2026/10/12 4:50:01

page_alloc zone_statistics

zone_statistics() 是页面分配路径上用于更新 NUMA 命中/未命中统计的辅助函数。它追踪分配请求的“首选 zone”与实际分配到的 zone 之间的关系,为 /proc/vmstat 提供 numa_hit、numa_miss、numa_foreign 等计数。核心作用它的职责是:当一次分配发生在 …

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑