资讯动态

DPDK下手动构建RST报文:三层封装、校验和与工程实现

发布时间:2026/10/23 6:26:08 来源:尧图企业网站定制
1. 项目背景为什么要手动构建RST报文先说结论不是所有RST报文都应该交给内核去发至少在DPDK高性能收发包场景下手动构建RST是绕不开的一环。做过高性能网络开发的朋友应该都有体会DPDK绕过内核协议栈接管网卡后数据包从物理网卡到用户态应用之间TCP的完整状态机其实是处于“半失控”状态的。我们能够拿到的是原始的IP/TCP报文但连接的管理、状态的跟踪、异常的处理全都得自己来做。这种情况下如果发现某个TCP连接需要被强制断开最直接也最有效的办法就是——构造一个RST报文直接发出去。这里顺便说下RST报文的几个典型触发场景你对照一下就知道自己是不是也需要这套能力连接白名单拦截安全网关场景下匹配到规则外的TCP流量直接以RST中断连接。TCP状态跟踪异常用户态维护了连接表发现序号异常、重复握手、老连接复用等非法行为需要立即收掉这个连接。端口扫描抑制遇到全端口扫描工具用RST回应减少无效状态堆积。主动连接管理负载均衡器或代理网关在后端超时或者主动踢掉连接时发送RST让两端快速回收连接资源。内核协议栈会帮你自动发出RST的场景在DPDK环境下是不存在的。你拿到了报文整条链路就都是你的责任。所以掌握构建并发送RST报文这个技能是每一个DPDK网络开发者迟早要面对的事情。这篇博文讲的是第一阶段如何构造一个合法、可被对端识别并接受的RST报文。发送机制的实现用什么队列、怎么调用发送接口、端口配置怎么搞我会放在下一篇展开先把包本身搞明白。2. RST报文结构拆解从链路层到TCP层2.1 三层报文结构的完整视图一个标准的TCP RST报文自下而上分为三个层次层次内容长度典型作用以太网头目的MAC、源MAC、以太网类型14字节二层转发依据IP头版本、头长、总长度、协议、源/目的IP等20字节三层路由依据TCP头源端口、目的端口、序号、标志位、窗口等20字节无选项四层连接控制没有TCP选项、没有载荷Payload一个最精简的RST报文就是54个字节。这也是握手或传输中异常断开时对端最容易识别和处理的报文形态。2.2 为什么RST不需要载荷和ACK、DATA报文不同RST报文的含义本身就是“这个连接我不认了立即终止”。RFC 793中规定RST报文的发送方在发送后进入CLOSED状态接收方收到合法的RST后也会立即关闭连接并丢弃任何后续到达的该连接报文。整个过程不需要确认、不需要重传、也不需要数据协同。所以你不需要给RST报文挂任何应用层数据带了Payload反而可能在某些中间设备的深度包检测下显得异常。精简就是最好的选择。2.3 三种典型场景下的RST形态根据触发场景的不同RST报文在细节上还有区别主动RST本地主动断开本地Socket存在但应用层决定强制关闭此时RST中的序号取当前TCP状态机中下一个要发送的序号。被动RST对方无法连接收到一个不存在的连接请求核心标志是ACK0的RST通常叫Challenge RST。回执RST对非法报文的响应收到序号非法的报文此时RST必须携带ACK标志并将ACK序号设置为收到的非法报文序号载荷长度。在DPDK手动构建RST时我们大多数情况下需要的是第一类和第三类场景的综合体构造出带确认号、能准确对应到目标连接的RST报文。这样对端TCP协议栈能够正确判断报文的合法性不产生误判。3. 构建RST包前的环境和工程准备3.1 DPDK版本与基础接口我这边实践环境使用的是DPDK 21.11 LTS版本这是目前最长维护的一个稳定分支接口和行为都比较成熟遇到问题也容易搜到解法。如果你还在用更老的版本比如17.x、18.x大部分核心接口rte_pktmbuf_alloc、rte_eth_tx_burst、rte_ether_format_addr等没有太大差异但结构体字段命名有少量变化——最大的坑点在于rte_ipv4_hdr结构体在早期版本中不支持字节序自动转换字段src_addr不是rte_be32_t类型如果从老项目迁移代码这里要格外小心。3.2 mbuf的分配与初始化在DPDK中所有报文的数据载体都是rte_mbuf结构。发送RST报文前我们需要先从内存池中取一个空闲的mbufstruct rte_mbuf *mbuf rte_pktmbuf_alloc(mbuf_pool); if (unlikely(mbuf NULL)) { RTE_LOG(ERR, USER1, Failed to allocate mbuf\n); return -ENOMEM; }分配后rte_pktmbuf_alloc返回的mbuf里的数据区是未初始化的需要我们自己通过rte_pktmbuf_append或直接操作指针的方式向其中填充数据。这里要特别提醒一个新手常犯的错误直接往mbuf-data里写数据而不是从rte_pktmbuf_append返回的指针对齐位置写入。mbuf的data指针通常不是指向缓冲区最开始的位置而是预留了headroom空间后的位置默认通常是128字节。如果你强行从最开头写会导致后续解析二层头时的偏移错乱发出去的报文对端网卡可能直接丢弃或者解析成垃圾数据。正确做法char *pkt_data (char *)rte_pktmbuf_append(mbuf, total_len); if (pkt_data NULL) { rte_pktmbuf_free(mbuf); return -ENOMEM; }3.3 内存池的创建构建RST报文的内存池无非是普通的mbuf池。但发送方向的内存池建议和接收方向分开避免收发路径上的内存竞争struct rte_mempool *rst_pool rte_pktmbuf_pool_create( RST_POOL, NB_MBUF, // 池中mbuf数量 256, // 每mbuf私有的cache大小 0, // 私有数据区大小 MBUF_SIZE, // 单个mbuf buffer大小 SOCKET_ID_ANY );MBUF_SIZE一般取值RTE_MBUF_DEFAULT_BUF_SIZE2176字节这个大小至少能容纳2KB的报文64字节的headroom加上2048字节的数据区足以覆盖绝大多数L4负载场景。构建一个54字节的RST报文显然绰绰有余。4. 构建RST包从以太网头到TCP头的逐层封装4.1 填充以太网头先明确报文的封装顺序在内存中以太网头在最前面之后是IP头最后是TCP头。发送时也是按这个顺序将从rte_pktmbuf_append返回的指针逐段填充。struct rte_ether_hdr *eth_hdr (struct rte_ether_hdr *)pkt_data; /* 目的MAC需要找到目标主机的MAC地址 */ rte_ether_addr_copy(target_mac, eth_hdr-dst_addr); /* 源MAC使用当前网卡的MAC地址 */ rte_ether_addr_copy(local_mac, eth_hdr-src_addr); eth_hdr-ether_type rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);这里有个细节dst_addr怎么来在实际项目中目标MAC通常是通过ARP表查询得到的。因为RST报文是要发给连接的对端说明之前已经建立过连接那么ARP缓存大概率是有效的。如果ARP表里查不到说明这个连接本身就不太靠谱并且RST报文发出去了对端也收不到——因为二层根本递不过去。一个简单的ARP表查询实现struct rte_arp_entry *arp_entry; int ret lookup_arp_table(dst_ip, arp_entry); if (ret 0) { RTE_LOG(WARNING, USER1, ARP entry not found for %x, drop RST\n, dst_ip); rte_pktmbuf_free(mbuf); return -1; } rte_ether_addr_copy(arp_entry-mac_addr, eth_hdr-dst_addr);如果应用场景不允许依赖ARP表比如刚启动还没来得及做ARP扫描你可以先发一个ARP请求等应答回来再构造RST。这个场景我后面在常见问题章节再展开。4.2 填充IP头IP头紧跟以太网头之后struct rte_ipv4_hdr *ip_hdr (struct rte_ipv4_hdr *)(pkt_data sizeof(struct rte_ether_hdr)); ip_hdr-version_ihl 0x45; /* IPv4首部长度20字节 */ ip_hdr-type_of_service 0; ip_hdr-total_length rte_cpu_to_be_16(sizeof(struct rte_ipv4_hdr) sizeof(struct rte_tcp_hdr)); ip_hdr-packet_id rte_cpu_to_be_16(packet_id); ip_hdr-fragment_offset 0; ip_hdr-time_to_live 64; ip_hdr-next_proto_id IPPROTO_TCP; ip_hdr-src_addr rte_cpu_to_be_32(src_ip); ip_hdr-dst_addr rte_cpu_to_be_32(dst_ip); /* IP校验和 */ ip_hdr-hdr_checksum rte_ipv4_cksum(ip_hdr);几个关键点挨个说total_lengthIP头加上TCP头的总长度不能包含以太网头。这里就是40字节后面代码要记得换算成网络字节序。packet_idIP标识字段在高频发包场景下需要确保每个报文有不同的ID否则对端或者中间设备的乱序重组模块可能产生混淆。可以维护一个全局递增计数器每次发送时加一。fragment_offsetRST报文不需要分片这个字段必须是0。如果改了其他值可能被对端当成一个分片片段等待其他分片到达而不会立即处理。hdr_checksumDPDK提供了现成的rte_ipv4_cksum函数它会自动计算并填充校验和。注意必须在其他所有字段填充完后调用否则校验和不正确。这个函数内部处理了字节序不需要手动转换。4.3 填充TCP头TCP头是整个RST报文的核心也是最容易出错的地方struct rte_tcp_hdr *tcp_hdr (struct rte_tcp_hdr *)(pkt_data sizeof(struct rte_ether_hdr) sizeof(struct rte_ipv4_hdr)); tcp_hdr-src_port rte_cpu_to_be_16(src_port); tcp_hdr-dst_port rte_cpu_to_be_16(dst_port); tcp_hdr-sent_seq rte_cpu_to_be_32(tcp_seq); tcp_hdr-recv_ack rte_cpu_to_be_32(tcp_ack); tcp_hdr-data_off 0x50; /* TCP头长度20字节 */ tcp_hdr-tcp_flags RTE_TCP_RST_FLAG; /* 只设置RST标志 */ tcp_hdr-rx_win 0; tcp_hdr-cksum 0; tcp_hdr-tcp_urp 0; /* TCP伪头部校验和 */ tcp_hdr-cksum rte_ipv4_udptcp_cksum(ip_hdr, tcp_hdr);4.4 伪头部校验和的计算逻辑rte_ipv4_udptcp_cksum是DPDK提供的一个方便函数它自动读取IP头里的源/目的IP和协议类型再结合TCP头里的数据计算校验和。这里有几个隐藏规则你需要注意传入的tcp_hdr可以指向TCP头起始位置但函数内部会假设TCP头后面还有数据载荷。对于RST报文载荷长度为0函数会自动处理不用担心。计算前cksum字段必须置0否则计算结果等于把旧校验和一起算进去了。rte_ipv4_udptcp_cksum返回值已经是网路字节序的校验和直接赋值给cksum字段即可不要再做字节序转换。这里的“伪头部”概念是TCP校验和计算的基础——它不真正出现在报文中而是参与计算时将IP层的核心信息源IP、目的IP、协议号、TCP长度拼接到TCP报文前面保证校验和能覆盖到四层三层的完整性。4.5 确认序号ACK的选择策略上面代码中tcp_ack这个值的确定是RST报文能否被对端正确响应的关键。场景一作为对非法报文的回应被动RST对端发来一个TCP报文它的序号为recv_seq载荷长度为payload_len。我们回应的RST报文中的recv_ack应该设置为recv_seq payload_len同时必须带上ACK标志tcp_hdr-recv_ack rte_cpu_to_be_32(recv_seq payload_len); tcp_hdr-tcp_flags RTE_TCP_RST_FLAG | RTE_TCP_ACK_FLAG; tcp_hdr-sent_seq 0;场景二主动RST本地状态存在如果本地维护的TCP连接还存活着recv_ack取值为当前连接期待接收的下一个序号等于本地已确认的远程序号。同时sent_seq取值为当前连接的下一个待发送序号并且RST标志与ACK标志同时置位。tcp_hdr-sent_seq rte_cpu_to_be_32(tcp_state-snd_nxt); tcp_hdr-recv_ack rte_cpu_to_be_32(tcp_state-rcv_nxt); tcp_hdr-tcp_flags RTE_TCP_RST_FLAG | RTE_TCP_ACK_FLAG;场景三收到SYN但本地无连接Challenge RST收到一个到本地不存在端口的SYN报文此时回复RSTrecv_ack设置为SYN报文序号1即对SYN的确认sent_seq设置为0标志位只置RST一般不需要ACK标志不过在实践中同时置ACK也不会被拒绝tcp_hdr-sent_seq 0; tcp_hdr-recv_ack rte_cpu_to_be_32(syn_seq 1); tcp_hdr-tcp_flags RTE_TCP_RST_FLAG;很多开发者在这里偷懒全部用同一个模板RSTACKack0。这种包对有些协议栈还能运气好被接受但遇到严格校验的对端直接丢弃。发送RST时一定要根据语义去设置标志位和序号。4.6 完整构建函数参考把上面几段代码合并整理成函数方便复用int build_rst_pkt(struct rte_mbuf *mbuf, const struct rte_ether_addr *src_mac, const struct rte_ether_addr *dst_mac, uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, uint32_t seq, uint32_t ack, uint8_t flags, uint16_t ip_id) { int total_len sizeof(struct rte_ether_hdr) sizeof(struct rte_ipv4_hdr) sizeof(struct rte_tcp_hdr); char *pkt_data (char *)rte_pktmbuf_append(mbuf, total_len); if (pkt_data NULL) return -1; struct rte_ether_hdr *eth_hdr (struct rte_ether_hdr *)pkt_data; rte_ether_addr_copy(dst_mac, eth_hdr-dst_addr); rte_ether_addr_copy(src_mac, eth_hdr-src_addr); eth_hdr-ether_type rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); struct rte_ipv4_hdr *ip_hdr (struct rte_ipv4_hdr *)(pkt_data sizeof(struct rte_ether_hdr)); ip_hdr-version_ihl 0x45; ip_hdr-type_of_service 0; ip_hdr-total_length rte_cpu_to_be_16(sizeof(struct rte_ipv4_hdr) sizeof(struct rte_tcp_hdr)); ip_hdr-packet_id rte_cpu_to_be_16(ip_id); ip_hdr-fragment_offset 0; ip_hdr-time_to_live 64; ip_hdr-next_proto_id IPPROTO_TCP; ip_hdr-src_addr rte_cpu_to_be_32(src_ip); ip_hdr-dst_addr rte_cpu_to_be_32(dst_ip); ip_hdr-hdr_checksum rte_ipv4_cksum(ip_hdr); struct rte_tcp_hdr *tcp_hdr (struct rte_tcp_hdr *)(pkt_data sizeof(struct rte_ether_hdr) sizeof(struct rte_ipv4_hdr)); tcp_hdr-src_port rte_cpu_to_be_16(src_port); tcp_hdr-dst_port rte_cpu_to_be_16(dst_port); tcp_hdr-sent_seq rte_cpu_to_be_32(seq); tcp_hdr-recv_ack rte_cpu_to_be_32(ack); tcp_hdr-data_off 0x50; tcp_hdr-tcp_flags flags; tcp_hdr-rx_win 0; tcp_hdr-cksum 0; tcp_hdr-tcp_urp 0; tcp_hdr-cksum rte_ipv4_udptcp_cksum(ip_hdr, tcp_hdr); mbuf-pkt_len total_len; mbuf-data_len total_len; return 0; }这个函数可以作为模板直接嵌入你的项目需要根据具体场景修改的只有MAC、IP、端口、序号和标志位。5. 发送前必须完成的校验与微调5.1 校验和自检注意把这段代码写在发送函数调用之前而不是之后。因为rte_ipv4_cksum和rte_ipv4_udptcp_cksum在计算后字段的值就已经固定为正确校验和了。如果你想验证计算过程的正确性可以在计算前先备份原值、计算后再覆盖回去做对比。这种方式在排查“对端总是丢弃报文”时会非常有用——先排除校验和错误。DPDK自带的rte_ipv4_cksum和rte_ipv4_udptcp_cksum都是软件计算性能很好在用户态直接调用没有问题。5.2 报文长度与缓冲区的实际占用别忘了设置mbuf-pkt_len和mbuf-data_len。这两个字段关系到后续发送时DMA搬运数据的长度如果不设置或设置错误驱动发送时可能只拷贝默认长度比如128字节头或者拷贝整个缓冲区的数据导致发送出一堆垃圾字节。在实际项目中因为mbuf是从内存池复用来的旧的pkt_len和data_len可能残留为之前某个大包的值导致新构建的54字节报文变成发送几千字节的异常报文。这算是我踩过的一个比较隐蔽的坑。更好的做法是在发送函数末尾统一重建这些字段或者使用rte_pktmbuf_reset重置后再分配。5.3 VLAN标签的考虑如果你的网卡启用了VLAN或者运行在VLAN子接口上二层的以太网类型字段需要在RTE_ETHER_TYPE_IPV4的基础上额外插入VLAN头4字节并且总长度、偏移都要跟着改变struct rte_vlan_hdr *vlan_hdr (struct rte_vlan_hdr *)(pkt_data sizeof(struct rte_ether_hdr)); vlan_hdr-eth_proto rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); vlan_hdr-tci rte_cpu_to_be_16(vlan_tci); ip_hdr (struct rte_ipv4_hdr *)(pkt_data sizeof(struct rte_ether_hdr) sizeof(struct rte_vlan_hdr));并且total_length、IP头偏移、TCP头偏移全都要加上这4个字节。使用RTE_ETHER_TYPE_IPV4的时候要警惕你的网口配置如果实际环境有VLAN而代码不处理发出的报文在交换机侧可能被当作未知类型丢弃。5.4 多队列发送的亲和性RST报文通常需要保证与原始报文的接收路径在同一个网卡队列上发出same_queue_rst原则这样可以避免跨队列查询导致的RSSReceive Side Scaling重哈希、避免报文被分到不同的CPU核上处理。简单说谁收到了这个TCP报文就由谁来发送对应的RST。在代码结构上通常做法是将接收线程/逻辑核的ID与发送队列的ID绑定构建RST包时直接把mbuf塞进本核绑定的TX队列uint16_t sent rte_eth_tx_burst(port_id, queue_id, mbuf, 1); if (unlikely(sent 1)) { rte_pktmbuf_free(mbuf); RTE_LOG(ERR, USER1, Failed to send RST packet\n); }rte_eth_tx_burst在发送失败时不会消耗mbuf需要自己释放。而如果发送成功mbuf会被驱动消费不能再碰。这个内存归属问题是DPDK开发新手最常踩的坑之一发送成功后的mbuf再释放会导致双重释放可能会引起内存池错乱。5.5 报文发送方向的整链路验证构建完并且发送了RST不代表对端就一定能收到。推荐使用Wireshark或tcpdump在接收侧抓包验证tcpdump -i eth0 -nn tcp[tcpflags] (tcp-rst) ! 0如果能看到RST报文并且对端连接正常断开表现为对端应用收到Connection reset by peer错误说明构建成功。如果tcpdump毫无输出优先检查二层目的MAC是否可达、IP校验和、TCP校验和三个点。如果抓包能看到RST但对端不断开连接重点排查序号和ACK序号是否符合对端状态机的预期。这个我在下一节详细讲。6. 常见问题与排查技巧实录6.1 对端收到RST却不断开连接这是最让人头疼的情况抓包软件能看到本地发出的RST报文已经离开本机网卡但对端应用毫无反应。我排查过类似问题最终的结论有几种序号不对。对端TCP状态机要求RST报文中的序号必须落在接收窗口内否则直接丢弃。特别是当RST报文只带RST标志不带ACK标志时序号必须是合法的数据序号。带ACK标志时要求ACK序号在允许范围内。如果不知道当前连接的确切序号建议两条策略都采用构造的RST同时带RSTACKACK取当前抓包得到的最近一段合法序号1。RST被中间设备吞掉。某些防火墙或交换机对RST报文有特殊处理策略可能丢弃或重置。可以用tcpdump在中间设备上抓包确认。对端协议栈对RST的处理语义不同。少数私有协议栈比如某些嵌入式TCP/IP对RST的处理比较“随意”不遵循RFC标准行为。这种只能对症下药。6.2 IP校验和正确但网卡仍丢包检查hdr_checksum计算时是否在IP头所有字段都填好后调用。另一个隐蔽问题是IP头里的total_length填写错误——填成了整个以太网帧的长度而不是IP包长度。DPDK网卡驱动会检查IP头长度是否与DMA数据长度一致不一致就直接丢弃。6.3 TCP校验和计算后总是被判定错误大概率是伪头部参与计算时IP头里的源/目的IP地址写反了。注意src_addr和dst_addr在结构体中已经是网络字节序的rte_be32_t类型直接赋值没问题。但如果你项目中之前用主机字节序存的IP赋值后没有做rte_cpu_to_be_32转换显示层对着看是“对的”计算校验和时就会出错。6.4 目的MAC地址获取不到这个问题在DPDK环境里特别典型。因为标准的DPDK应用不走内核协议栈ARP包不会自动被处理。如果你的DPDK应用在启动时没有主动发送ARP请求、又没做ARP表那么构建RST时查不到目的MAC。两个处理方案被动ARP学习在接收到TCP数据包时解析出源MAC和IP自动写入本地的ARP映射表后续RST报文直接查表。主动ARP探测如果在构建RST时查不到对应MAC先把RST报文挂起或者丢进待处理队列先广播一个ARP请求等回复后补充MAC再发。注意ARP请求的发送也要在DPDK层完成不能依赖内核。6.5 mbuf分配失败导致RST丢失如果系统在高压力下比如每秒钟要构造几百万个RSTrte_pktmbuf_alloc分配失败的概率会升高。原因是接收路径和发送路径共用一套内存池接收突发流量时把空闲mbuf消耗光了。解决思路是给RST报文构建路径单独创建一组专用mbuf池减少和收发数据池的竞争。同时如果接收方向有独立的回收路径尽量在空转周期内及时释放。6.6 多核并发下IP ID或TCP序号的原子性多个核同时构建RST报文时IP ID的递增必须是原子的static rte_atomic32_t ip_id; uint16_t next_id (uint16_t)rte_atomic32_add_return(ip_id, 1);如果不加锁或原子操作并发增量的结果可能是同一个ID被多个报文使用轻则对端丢弃重则引入安全隐患。序号、ACK生成同样也要保证在连接表维度上的原子性。7. 实操性能考量与优化空间7.1 构建RST报文的CPU开销即使是最精简的54字节RST报文每次构建依然涉及内存操作、三层校验和计算等。在PPS每秒包数达到百万级的场景下CPU开销不可忽视。性能测试数据基于Xeon 6230R、单核、3.0GHz操作耗时ns说明rte_pktmbuf_alloc~50内存池分配以太网/IP/TCP头填充~30内存加结构体赋值IP校验和计算~1020字节的IP头TCP校验和计算~2020字节TCP伪头部rte_eth_tx_burst~100~200驱动发送路径单核每秒可以发送约300万~500万RST报文瓶颈一般在PCIe带宽和网卡发送描述符队列深度。如果你的场景对性能有更高要求可以做两个优化预分配模板提前构建好一个只差IP ID和TCP序号不相同的“半成品报文”在内存中构建时直接memcpy模板再改两个字段省掉结构体填充时间。批量构建一次处理多条连接时尽量将多个RST报文批量放入数组再调用rte_eth_tx_burst发送充分利用描述符和DMA聚合能力。7.2 RST报文与CPU亲和性的绑定发送线程绑定到网卡对应的NUMA节点核上并且和接收线程共用同一个核收包、处理、发送在同核完成可以避免跨核缓存同步开销。对于RST这种需要对特定报文做出即时响应的场景收报发报同核是最经济的架构。7.3 大规模连接表下的RST生成策略当应用维护了数十万条TCP连接时不能每来一个非法报文就从头构建一次RST。更好的做法是在建立连接时就预生成RST模板fields固定只有IP ID需要动态生成程序运行时只需替换sent_seq、recv_ack字段一条数据链路生成RST消耗的时间降低到原来的1/5左右。8. 下一步从构建到发送的完整链路今天这篇主要解决了“RST报文长什么样、怎么构建”的问题。报文构建完成后紧接着就要考虑用哪个发送队列发出去rte_eth_tx_burst的具体参数配置。如何保证发出去的RST不被本机接收路径当成新报文处理比如设置网卡的RSS过滤规则。如何处理同一个连接短时间内多次触发RST的抑制策略防止RST风暴。这几块我在下一篇会分别展开这篇先到这里。最后顺手分享一个小技巧调试RST报文时可以临时把目标MAC填成广播地址FF:FF:FF:FF:FF:FF只要交换机不隔离广播域包至少能到达对端网卡再做丢弃决策这样能快速区分是二层问题还是四层问题——实测在拓扑较简单的测试环境里这招很管用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑