两行分别展示仍可返回 ACK 的 TCP 对端内核与可能保持沉默的对端应用;底部提示探测 TCP 之外还须设置应用等待期限。

TCP 保活探测空闲连接

TCP keepalive 询问的是:连接安静了一段时间后,对面的 TCP 端点是否仍然响应。它不询问 HTTP handler 是否正常、数据库查询是否推进、后台工作线程是否卡住。即使应用已经停顿,内核仍然可能确认收到的数据。因此,“传输对端能够回应”和“应用给出了有用结果”必须作为两个不同的观察项。 TCP 内核确认与应用进展是不同观察项。原有 Go 实验设置空闲 30 秒、间隔 10 秒、3 次探针,随后 200 毫秒读取期限到达。对端稍后读取原始 ping 并回复 pong;客户端重设读取期限后在同一连接收到回复。该实验没有测试真实保活探针。 仅为原有实验示意,本轮配图未重跑。 传输层探针不是应用心跳 保活有助于暴露远端连接状态已经消失,或者网络路径悄悄停止传递报文的情况。实现会在满足空闲条件后发送探针,并按照未响应探针的策略最终报告失败;但单个探针丢失不等于对端死亡,不能把网络中的临时丢包直接解释成确定性故障。 探针在应用字节流以下处理,不会变成 HTTP 请求、数据库查询或 WebSocket 心跳。如果连接终止在代理上,探测对象就是代理自身的 TCP 端点,不是代理后面的源站。某些中间设备的连接跟踪可能受探针影响,但不能由此承诺刷新应用的空闲计时,更不能保证所有 NAT 映射都会一直保留。 ...

十月 5, 2027 · 2 分钟 · XB Field Notes
原文预期情景,不是实测记录。客户端、路由器与目标沿请求方向相连;被叉断的返回线表示删除目标的返回默认路由。客户端网关未变,HTTP 却超时,说明有去路仍须有回路。

默认路由匹配最宽范围,不会挽救所有失败路径

IPv4 默认路由是前缀 0.0.0.0/0,匹配所有 IPv4 目的地,但具体程度最低。在已经选择的路由表中,更长的匹配前缀通常优先。默认路由不是一个“其他具体路径失败后再重试”的过程。路由策略决定相关表,最终路由描述怎样转发,而不保证完整往返一定成功。匹配范围与路径健康是两类不同属性。 坏掉的样子 没有默认路由的机器,仍能访问直连或明确路由覆盖的目的地;反过来,添加默认路由也不证明能够访问互联网。下一跳需要可达,转发与过滤要允许流量,回复还需要返回路径。显示一个网关,只满足契约的一部分。真正配置是否共同工作,应由实际应用通信证明,而不是从路由文件中有一行 default 就推导全部能力。 更具体的拒绝路由会胜过可工作的默认路由。Linux 的 unreachable 等路由类型,会有意停止匹配目的地的转发。选定网关的邻居查询失败,也不意味着内核自动寻找更健康、却更宽泛的路径。静态路由优先级和 metric 不是服务健康监控或自动故障转移机制,不能把配置顺序当成逐个尝试可用出口的流程。 返回路径错误尤其容易误导。客户端选中预期网关并把流量送往监听中的服务器,服务器却可能无法把回复路由回客户端。反复修改客户端默认路由,可能破坏其他正常目的地,却没有修复缺失的反向路径。将所有连接超时归因于发送方网关之前,应查看两个方向和真实源地址。请求能到与响应能回,需要分别成立。 有去路,还要有回路。 仅为原文预期模型。实验前提缺失,可执行验收受阻;不声称具有保留的运行证据。 快速验证 2026-10-18 源码审阅说明:下述预期结果与历史报告没有在本次审阅中重新运行,当前执行验证仍受阻。 这些命令要求明确记录的 Linux 实验安全策略允许嵌套命名空间和 proc 挂载。NET_ADMIN 与 SYS_ADMIN 不会覆盖 Docker 内置 AppArmor 策略的挂载禁令。初始化权限错误不属于选路测试结果。 将脚本保存为 check-default-route.py,准备 Docker 后运行 python3 check-default-route.py。Python 3.11.16 和 iproute2 6.1.0 在一次性 Linux 容器内建立客户端、路由器、目标拓扑。NET_ADMIN 与 SYS_ADMIN 支持嵌套命名空间和临时 proc 挂载。接口全是新建 veth,不向宿主发布端口或路由,也不修改现有网络设备: ...

九月 14, 2027 · 2 分钟 · XB Field Notes
对同一目的地 192.0.2.77,直连 /24 行直接选择 lab0;高亮的更具体 /32 行胜出,经网关 192.0.2.1。行表示路由具体程度,不是连续两跳转发。

子网前缀影响直连路由,最终路径由路由表选择

IPv4 前缀长度表示地址中有多少前导位标识网络。对于 192.0.2.5/24,网段是 192.0.2.0/24,普通广播地址是 192.0.2.255。配置接口通常会建立相应直连路由,但前缀不是完整路由算法。内核还要依据路由策略与路由表,为具体目的地选择路径,不能只看地址是否位于某个网段就推断全部行为。 坏掉的样子 前缀太短,会让主机把原本远端的目的地当成直接相连。保留地址 192.0.2.5,却把 /24 改成 /16,就把 192.0.3.9 纳入直连范围。在真实以太网中,这种错误可能让主机尝试解析一个根本不在当前链路上的邻居。于是地址看起来正确,下一跳决策却已经改变;反复核对地址字符串本身无法发现问题。 “不在自己的子网就走默认网关”也不完整。更具体的路由可以选择其他网关或接口;在已经选择的路由表内,更长的匹配前缀优先。明确的主机路由甚至可以覆盖本来位于接口子网内的目的地。应检查实际查询结果,而不是仅凭接口地址重新猜测内核会怎么做。默认路由只处理没有更具体匹配的相应流量。 地址数量计算也需要上下文。普通 /24 的网络地址与定向广播地址通常不用于主机分配,但“减二”不是统一公式。RFC 3021 允许点到点 /31 链路的两个地址都用作端点,/32 则标识单个地址。这些规则解释地址语义,没有证明配置中的两端能够通过当前物理拓扑交换数据。可用地址集合和实际通信仍须区分。 更长前缀可以覆盖子网。 依据原文与明确限制静态绘制;未为本图重跑原文夹具。 快速验证 2026-10-18 源码审阅说明:下述预期结果与历史报告没有在本次审阅中重新运行,当前执行验证仍受阻。 将脚本保存为 check-subnet.py,准备 Docker 后执行 python3 check-subnet.py。程序给固定 Python 镜像安装 iproute2 6.1.0,再只向一次性容器授予 NET_ADMIN;容器没有外部网络接入。脚本创建虚拟 dummy 接口并修改该容器的路由,不改变宿主接口、路由或生产网关。构建下载与运行时路由实验也处于不同阶段: ...

九月 13, 2027 · 2 分钟 · XB Field Notes
文章两字段模型中,同一前缀的本地偏好 200 三段 AS 路径,优先于偏好 100 的两段较短路径。

BGP 组成了互联网的路由表

BGP 在独立运营的网络之间交换可达性,但互联网并不存在一张统一、权威的转发表。每台路由器都学习经过策略过滤的视图,选择路由,再尝试把可用下一跳安装进转发系统。两台路由器为同一个前缀选择不同路径,完全可能是正常情况。理解 BGP,需要把通告、选路策略和数据包转发分开,不能把它们合并成一个放之四海皆准的答案。 AS 路径同时承载历史与策略 普通 eBGP 场景中,路由器向外宣告时会在 AS 路径前加上自己的号码。接收方通常排除已经包含自己 AS 的路径,用于避免自治系统之间的环路,但存在显式配置的例外和协议扩展。自治系统内部的 iBGP 通常不再增加 AS 号码;它的转发通告限制及路由反射器规则处理的是另一类内部环路问题。 AS_PATH 还是过滤和选路的输入。重复前插可能让一条路由在看重路径长度的网络里吸引力下降,却不能推翻对方优先级更高的本地策略。也没有一条通用规则说,前插之后路由就会被过滤。必须结合真正的运营商策略、客户授权和接收条件,理解该字段在当前网络里的作用。 LOCAL_PREF 会作为属性分发给内部 BGP 对等体,通常不传给外部对等体。部分实现提供的 weight 则只属于本地路由器,也不是标准 BGP 路径属性。把两者都描述成不会进入更新报文,会掩盖关键区别:本地优先级可以影响自治系统内其他设备的决策,而 weight 的效果一般局限在一台路由器。 先比较同一个前缀 最佳路径过程比较的是同一前缀的多个候选路径。具体顺序取决于实现和配置,包括路径资格、本地策略、AS 路径长度、MED 比较范围及下一跳可达性。某个产品的 weight 优先列表并不是协议完整定义。MED 通常在来自相同邻居 AS 的路径之间比较,但实现可以提供改变这一行为的选项,不能把默认规则写成不可改变的限制。 之后,转发过程在已安装目的路由中做最长前缀匹配。一个 /25 与覆盖它的 /24,并不是通过同一套最佳路径平局规则互相竞争。只要两者均被接受并安装,前者就匹配那一半地址,即使后者的 AS 路径更短。策略路由或其他额外转发机制还需要分别检查,不能从这条普通查找规则推断全部设备行为。 ...

九月 12, 2027 · 1 分钟 · XB Field Notes
客户端发送 SYN,序列号为 x;服务器发送 SYN-ACK,序列号为 y、确认号为 x+1;客户端发送 ACK,序列号为 x+1、确认号为 y+1。

TCP 握手建立序列号空间,不是同步两台机器的时钟

TCP 三次握手建立字节流的两个方向。每端提供自己的初始序列号,并确认另一端的序列号。这是两个独立序列空间,不是共享墙上时钟;应用建立连接不需要两台机器时钟同步。之后的序列号用于标识字节,支持连接内部的排序、重复处理、确认和重传。把序列号与时间混为一谈,会误解握手真正建立的协议状态。 图示普通握手及受控丢包场景;32 位序号按模回绕。connect 成功不等于应用已能提供有效服务。 坏掉的样子 客户端发送带初始序列号的 SYN,服务器回复 SYN-ACK,给出自己的初始序列号并确认客户端 SYN,客户端再确认服务器响应。即使没有应用数据,SYN 也占用序列空间中的一个位置,所以对应确认号增加一。将这些数值当成时间戳,会错误解释报文,还可能把排障引向无关的时钟同步问题,忽略真正缺失的确认或路由。 两端不会在完全相同的时刻改变状态。客户端收到 SYN-ACK 后,connect 可能成功返回,但最后一个确认在到达服务器前丢失。服务器仍可能停留在 SYN-RECEIVED,Linux 工具显示为 SYN-RECV。后续携带适当确认的数据也能完成交换,因此单独第三个报文缺失,并不必然导致永久失败,不能把抓包中的固定三包模式当成唯一合法过程。 TCP 建立成功也不代表应用健康或对端身份可信。服务可能接受连接却无法生成有效响应,代理可能完成客户端这一侧连接,却无法建立另一个独立上游连接。初始序列号选择有助于区分连接实例、抵御预测攻击,但不是身份认证协议,也不会加密字节流。传输状态、应用结果和安全身份需要各自对应的验证,不能由一项成功代替全部。 快速验证 将脚本保存为 check-tcp-handshake.py,准备 Docker 后运行 python3 check-tcp-handshake.py。一次性容器使用 Python 3.11.16、iproute2 6.1.0、nftables 1.0.6 和 tcpdump 4.99.3,在 Linux 6.12.76 上验证。NET_ADMIN 与 SYS_ADMIN 支持嵌套网络命名空间。客户端和目标由新建 veth 相连,不移动宿主接口或发布端口: ...

九月 10, 2027 · 3 分钟 · XB Field Notes
有限动态 DHCPv4 租约在到期前有续租与重绑时点;成功续租会更新期限,服务器给出的合法定时值可以不同于默认比例。

DHCP 只把地址租给你

DHCPv4 的动态分配把地址借给客户端一段租期。续租可以延长期限,到期则允许地址再次分配。DHCP 也支持其他分配策略,所以本文讨论的是有限期的动态租约,不把它说成协议唯一能够提供的配置。租期是使用地址的约定,不是设备对地址的永久所有权,也不是证明设备身份的凭据。 借用而非拥有地址 新初始化的客户端发现服务器、选择一份报价,再请求配置。确认后的租约记录允许使用地址的时间。租约数据库可以追踪分配,但不能保证所有机器都遵守协议:未受管理的静态地址、重叠地址池、服务器状态丢失或非法服务器,仍可能制造冲突。不能因为有集中租约表,就假定服务器永远知道整张网络里所有真实占用情况。 续约让租约保持存活 有限租期内,客户端通常在 T1 向原服务器请求续租;若到 T2 还没收到确认,则广播重绑请求,让其他合适的服务器也有机会回答。默认值分别是租期的一半与八分之七,服务器可以提供其他合法定时值。RFC 2131 建议在固定值附近加入随机变化,以减少客户端同步请求。因此默认比例不是必须原样保留的性能调优命令。 确认后的到期时刻按续租请求发送时间加上获准的新租期计算;后续 T1 和 T2 也应按更新后的租约计算,而不是继续沿最初入网时刻推进。续租通常保留地址,却不能保证设备在重启、换网或策略改变后永远得到同一地址。短租期让离场地址较快回收,也增加续租流量;长租期给客户端更多时间应对服务中断,却让已经离场的设备占用地址池更久。固定身份需求应明确配置分配策略。 过期即可回收 客户端可以主动释放地址,但正确运行不能依赖每台离场设备都发送 RELEASE。租约过期后,客户端必须停止使用地址并重新获取配置;服务器也可以重新分配这个地址。设备回来时可能拿到原地址,也可能获得不同地址,不能把换号写成必然结果。管理端删除绑定不会撤回客户端已经获得的租期,因此不能用盲目清空租约来代替冲突调查,否则可能主动制造重复分配。 坏掉的样子 地址池耗尽时,新发现请求可能收不到报价。NAK 则有特定含义,例如拒绝不适合当前网络的请求地址,不能把它统一解释成“地址池已满”。部分客户端在 DHCP 失败后配置 IPv4 链路本地地址,但这是另一种行为,也不能单凭它推断失败原因。先检查池容量、转发路径与服务器日志,再决定是否扩容或调整重试,避免把网络丢包当成地址不足。 租期过短会增加续租负载,并缩短客户端能够容忍服务器中断的时间。集中开机可能让流量聚集,却不意味着整个网络必然同步续租。应核对服务器实际下发的定时值和客户端重试方式,而不是只看名义租期。给新租约调长时间,也不会追溯性地延长之前已经确认的旧租约,观察窗口需要覆盖这部分过渡。 非法服务器可以给出错误网关或解析器。选择第一个报价只是客户端的一种可能策略,并非协议强制规定。服务器标识选项没有提供密码学认证,攻击者可以伪造;单独记住一个标识,并不能建立信任。交换机 DHCP snooping 等网络控制需要正确配置受信任端口和转发路径,才能限制回答来自哪里,不能用调短或调长定时器代替这个边界。 睡眠和计时处理可能暴露旧配置。设备醒来时若租约已经过期,必须先重新获取配置,不能继续占用地址,也不能假定自己只是进入 T2 重绑阶段。排查要结合租约已经经过的时间和实现日志;墙上时钟同步本身不能证明定时逻辑正确。地址冲突检测和 DECLINE 处理是另外的保障,不能因为租约表看起来正常就省略它们。 仅讨论有限动态租约。默认 T1、T2 比例不是通用调优保证;阶段时间线和续租公式仅作解释,未测试 DHCP 报文或完整客户端状态机。 快速验证 把代码保存为 dhcp_lease_sim.py,运行 python3 dhcp_lease_sim.py。它只用标准库,通过虚拟时钟和简化的内存服务器检查租期算术与地址归属。单播和广播只是场景标签,没有发送网络报文,也没有实现完整客户端状态机。这里假定请求与确认瞬间完成,T2 取整秒;实际系统应读取服务器给出的值,不能把示例常量当作所有网络的固定设置。 ...

九月 7, 2027 · 3 分钟 · XB Field Notes
客户端使用一个普通 ClusterIP Service 入口,路径分向合适的 Pod。稳定发现不保证请求连续。

Kubernetes Service 为 Pod 提供稳定入口

既然 Pod 会重建,就很难记住它的地址。Service 在这组 Pod 前面固定一个名字,让调用方不因 Pod 更换而受影响。 前后集合是模型结果,不是传输测试。稳定发现不证明每个请求都会成功。端点条件、端口和数据路径需分别检查。精确本地模型本轮再次通过;未执行 Kubernetes 或传输测试。 一个地址,一组 Pod 普通 ClusterIP Service 在对象存续期间保持虚拟地址,集群 DNS 提供发现名称。调用方可以依赖这个名称,而后端可互换的 Pod 逐渐被替换。这把稳定的客户端契约与单个后端生命周期分开,不需要每替换一个副本就推送客户端配置。但稳定身份仍有明确范围,不能扩写成全部网络行为都不会变化。 无头和 ExternalName Service 具有不同发现行为,无选择器 Service 则可以使用单独管理的 EndpointSlice。虚拟地址稳定的结论应限于普通情形。Deployment 管理工作负载,本身不是发起网络连接的客户端;真正使用发现结果的是应用、网关和其他组件。审阅调用关系时,应区分资源控制与请求转发这两层。 选择规则决定由谁响应 带选择器的 Service 识别本命名空间里匹配的 Pod,EndpointSlice 控制器异步协调地址和条件。就绪通常决定普通流量资格,但未就绪端点可能继续存在,只是就绪条件为假,不一定从 API 对象中彻底删除。列表成员、成员条件和最终是否选择它,是需要分别观察的状态。 ...

八月 23, 2027 · 2 分钟 · XB Field Notes
建连与两次读取共用整体预算。预算耗尽后不再开始下一次等待;单次读取成功不代表完整响应及时完成。

连接超时阻止无声的停摆

超时首先规定某个操作最多等待多久。它不会自动成为整个请求的总期限,也不能证明远端工作已经停止。选择数值或解释错误之前,要把连接建立、单次读取和调用方的整体预算分开;否则配置看起来都有限,整条调用链仍然可能等得远比预期久。 时间示意,不按比例。数字回环 TCP 实验未约束 DNS 或 TLS。调用方截止点不取消远端工作;仍须验证数据完整性。 连上了,不代表能拿到有用的响应 TCP 建连可以成功,而对端应用始终不产生响应。此时问题属于读取阶段,不是 TCP 握手尚未完成。诊断信息应写出发生超时的阶段;含糊的“数据库连接超时”,可能指连接池获取借用失败,也可能指新建套接字失败,或已经连上后等不到查询结果。它们占用的资源、下一步检查和恢复语义并不相同。 Python 套接字上的超时作用于阻塞操作。循环执行多次成功的 recv(),每次都可能获得一份新的等待额度,因此对端只要持续发送小片段,就可能把整个读取循环拖到远超那个超时值。下面特意每次只读一个字节,便于观察单次操作成功与完整响应及时到达之间的差别,而不是把“一次没有超时”当成整个消息已经满足时限。 如果需要整个接收过程共用预算,应先用 time.monotonic() 建立一个截止点。每次开始下一项操作前,计算剩余时间,再取剩余时间与该操作上限中的较小值;预算耗尽后,就不能继续开始新的等待。单调时钟不会因校正系统日期而突然倒退或跳到另一段墙钟时间,但它也不能保证线程恰好在截止的那一刻获得调度,因此不能把它理解成硬实时执行器。 把这个模式扩展到真实客户端之前,仍要核对所用库的契约。域名解析、多个地址尝试、TLS 协商、发送请求以及从连接池排队获取连接,可能有各自的计时方式。本例只访问一个数字形式的回环地址,并使用普通 TCP;它没有证明 DNS 解析已经受限,也没有实现一个覆盖完整 HTTPS 请求流程的通用总超时。 坏掉的样子 已接受连接但保持沉默的对端,会让调用方读到超时;不断滴送少量数据的对端,则能让每次读取都成功,却使完整响应过慢。只有建连超时,并不能处理连接已经成功之后的这两种情况。反过来,设置得过短,也可能把正常的网络波动或线程调度延迟误判成故障,数值需要结合真实调用预算和工作负载来确定。 失败路径需要释放自己拥有的资源,并保留完整数据与部分数据的区别。套接字读取返回空字节串,表示 EOF,并不等于应用消息必然收齐。实验知道目标帧应该有六个字节,所以提前关闭会被当成失败;不能因为连接已经结束,就把收到的半份结果当作正常响应交给业务。 放弃等待也不等于取消远端工作。调用方超时时,对端可能已经执行了某个副作用。重试需要匹配的重放契约,以及原操作还剩下的预算,而不是每次重新获得一整套等待时间。这里的总期限是在操作边界协作检查的约束,仍有调度与清理开销,并不是对任意线程、系统调用或远端执行的瞬时中断保证。 快速验证 将代码保存为 connection_probe.py,执行 python3 connection_probe.py。脚本只依赖标准库,本次在 macOS 的 Python 3.14.4 上验证。每种情况都创建自己的回环监听端口和对端线程,结束时关闭套接字并等待线程退出;不会连接外部主机,也不修改防火墙或生产服务。 ...

八月 1, 2027 · 2 分钟 · XB Field Notes