TCP 和端口过滤/带 WinSock 的防火墙






4.50/5 (4投票s)
关于在 TcpClient 类中暴露 TCP Sockets 超时和重试参数的必要性讨论
引言
这是参考以下文章
https://codeproject.org.cn/Articles/85602/PortQry-Implementation-using-TcpClient-Socket-and
我已经有几年没看这个了,最近收到有人回复的通知。首先,我想同意 emilio_grv 的回复,即应用程序程序员在处理应用程序内的超时时应该*非常*小心。与任何应用程序开发一样,请确保尽快清理所有未使用的资源,特别是套接字,因为您会耗尽可用的源端口。在 Windows 环境中,默认是 3977。
背景
由于连接超时不是 TcpClient.BeginConnect()
或 TcpClient.Connect()
函数中可用的参数,这对于那些需要及时完成大规模进程的人来说会带来问题。
使用网络中的防火墙,我们做出了妥协:为了安全而牺牲可见性。我们不再能从远端的 TCP 堆栈获得响应,有时甚至在数据包通过防火墙(无论是否有问题)时也无法获得 ICMP 消息。更令人沮丧的是,我们可能别无选择,因为另一个组或组织可能负责管理防火墙,因此策略更改可能难以甚至不可能实现。
第 4 层(TCP 会话)可见性工具
以前有一种简单的方法可以以编程方式检查端口可用性。使用 L4Trace 和 L4Ping 等工具,程序员使用 RAW 套接字创建带有空有效负载的 TCP 头部,以尝试建立会话,并提前超时连接,用 FIN 终止会话。这允许对防火墙所在的路径进行深入分析,通过查看最后一个响应 TCP 会话建立请求(TTL 递增)的人,来查看谁在阻止端口。
黑客登场
各种恶意软件制造商和其他巧妙的人利用此功能向主机发送 SYN 洪水以尝试入侵它们,或制造格式错误的 TCP 会话运行条件来崩溃服务器或溢出缓冲区。这导致 Microsoft 在 XP Service Pack 3 中移除了 Windows 的 RAW 套接字功能,从而破坏了 L4Trace 和 L4Ping 以及其他非常有用的工具。
对挂起连接问题的响应
提供的代码(在先前引用的文章中)在指定超时时间到期时,通过 Windows TCP/IP 堆栈提供的 API 终止连接。即使需要这样做,也不是理想情况。我认为 Microsoft 应该在 TcpClient.BeginConnect()
函数中提供两个参数。第一个是设置超时(在以下流程图中标记为 x),第二个是在调用连接失败之前重试的次数。至少,糟糕/恶意的编码将导致客户端系统上可用套接字的消除,因为套接字将不会被释放(锁定套接字),直到尝试处理 TcpClient 或调用 EndConnect()
函数后的 30 秒 TCP 清理期到期。
不幸的是,我们没有这个奢侈,更重要的是,在 Windows 基础结构中,(据我所见)没有任何方法可以在不重新实现所有 WINSOCK 的情况下生成自己的 TCP 数据包,因为 RAW 套接字已不再可用/实现。在 Windows 7 中,这变得更加复杂,因为您需要构建一个内核驱动程序才能在 WinSock 处理之前与网卡或数据包进行交互。
显示 WinSock TCP 建立的当前流程图
AVAILABLE TCP PORT UNAVAILABLE TCP PORT FIREWALLED TCP PORT TcpClient.BeginConnect() TcpClient.BeginConnect() TcpClient.BeginConnect() CLIENT..............SERVER CLIENT..............SERVER CLIENT..............SERVER SYN ---------------------> SYN ---------------------> SYN ---------------------> <----------------- SYN-ACK <----------------- RST-ACK ......WAIT 3 SECONDS...... ACK ---------------------> SYN ---------------------> <----------------- RST-ACK SYN ---------------------> ..x*3 SECONDS TRANSPIRED.. SYN ---------------------> ......WAIT 6 SECONDS...... <----------------- RST-ACK SYN ---------------------> ..CONNECTION ESTABLISHED.. ..x*6 SECONDS TRANSPIRED.. .....WAIT 12 SECONDS...... OS Triggers Async Callback OS Triggers Async Callback OS Triggers Async Callback TcpClient.EndConnect() FIN ---------------------> <----------------- FIN-ACK ACK ---------------------> OS Triggers Async Callback ...WAIT 30s FOR CLEANUP... ...WAIT 30s FOR CLEANUP... ....WAIT 30s FOR CLEANUP... TcpClient.Dispose() TcpClient.Dispose() TcpClient.Dispose() Local and Foreign TCP Port Local and Foreign TCP Port .Local TCP Port freed for.. are available again for are available again for ......reuse by system...... use. use.
传播
在完美路径(无数据丢失)和 3 秒 RTT(TCP 考虑的所有内容,以及 TCP 有效工作的实际范围)的情况下,如果端口可用,我们只需在建立连接后终止连接,应用程序的往返时间就是 9 秒。
connect-time
(x*3) = ?, (3*3) = 9 秒
当端口不可用时(服务器上没有应用程序连接到端口),但未被防火墙过滤,我们会收到 TCP 消息,从而在 18 秒内获得响应。
connect-reset-time
(x*6) = ?, (3*6) = 18 秒
当端口不可用,路由表中存在到服务器的路由,托管服务器所在子网的路由器具有用于将 IP 转换为 MAC 地址的 ARP 条目,并且防火墙位于客户端和服务器之间时,向应用程序返回连接失败的总时间是 21 秒。
tcp-timeout + (tcp-timeout*(2^1)) + (tcp-timeout*(2^2))
(3) + (6) + (12) = 21 秒
看另一个极端,假设 4 毫秒的往返时间
可用 TCP 端口:(0.004*3 + (0.004*3) = 24 毫秒
不可用 TCP 端口:(0.004*6)= 24 毫秒
过滤的 TCP 端口:(3)+(6)+(12)= 21 秒
Application
在当今时代,缓慢的响应是 700-900 毫秒 RTT,卫星通信由于信号从地球到 LEO 再返回的物理距离最慢。
我运行了一份我们网络上的报告,96% 的主机延迟(RTT)来自管理系统在 60 毫秒以内,92% 少于 40 毫秒,99% 少于 100 毫秒。在 RFC1918(私有)空间提供的 1900 万个地址中,我们有超过 200 万个地址在使用。
假设我使用 WinSock 中定义的标准 TCP 超时来轮询每个设备上最常见的 25 个端口。在一个可以建立 3900 个 TCP 连接(WinSock 的默认动态端口限制向下取整)的系统的多线程应用程序中,我们假设响应主机的平均延迟为 40 毫秒。再假设由于防火墙策略,活动主机上只有 5% 的端口是可访问的,并且 60% 的无响应主机不在防火墙后面。
RFC1918_HOSTS: = | 19,000,000 | 总 RFC1918 地址 | |
ACTIVE_HOSTS: = | 2,000,000 | 活动主机 | |
SOCKETS_POLLED: = | 25 | 每台主机的轮询端口 | |
%_RESP_SOCKETS: = | 20% | 活动主机上的响应端口 | |
LATENCY: = | 40 | 毫秒主机主要延迟 | |
TIMEOUT: = | 3 | WinSock TCP 超时默认值 | |
RETRIES: = | 3 | Winsock TCP 重试默认值 | |
PORTS_AVAILABLE: = | 3900 | Winsock 开放动态源 TCP 连接默认值(向下取整) | |
UNFILTERED_HOSTS: = |
60% | 未过滤的未使用端点的百分比 | |
DEAD-HOSTS: |
17,000,000 | 未使用地址 |
|
RESPONSIVE_SOCKETS:
|
10,000,000 | 响应端口 |
|
UNRESPONSIVE_SOCKETS: |
465,000,000 | 无响应端口 |
|
RESPONSIVE_SOCKETS_TIME: |
308 | 时间(秒) 轮询响应端点 |
|
RESET_SOCKETS_TIME: |
17,169 | 时间(秒) 轮询重置-响应主机 |
|
UNRESPONSIVE_SOCKETS_TIME: |
1,001,538 | 时间(秒) 轮询无响应(已过滤)端点 |
|
1,019,015 | seconds | ||
16,984 | 分钟 | ||
283 | hours | ||
12 | 天 | ||
RESPONSIVE_SOCKETS_TIME: |
308 | 时间(秒) 轮询响应端点 |
|
RESET_SOCKETS_TIME: |
5,723 | 时间(秒) 轮询重置-响应主机 |
|
UNRESPONSIVE_SOCKETS_TIME: |
143,077 | 时间(秒) 轮询无响应(已过滤)端点 |
|
149,108 | seconds | ||
2,485 | 分钟 | ||
41 | hours | ||
2 | 天 | ||
RFC1918_HOSTS: = | 19,000,000 | 总 RFC1918 地址 | |
ACTIVE_HOSTS: = | 2,000,000 | 活动主机 | |
SOCKETS_POLLED: = | 25 | 每台主机的轮询端口 | |
%_RESP_SOCKETS: = | 20% | 活动主机上的响应端口 | |
LATENCY: = | 40 | 毫秒主机主要延迟 | |
TIMEOUT: = | 3 | WinSock TCP 超时默认值 | |
RETRIES: = | 3 | Winsock TCP 重试默认值 | |
PORTS_AVAILABLE: = | 3900 | Winsock 开放动态源 TCP 连接默认值(向下取整) | |
UNFILTERED_HOSTS: = | 60% | 未过滤的未使用端点的百分比 | |
DEAD-HOSTS: | 17,000,000 | 未使用地址 |
|
RESPONSIVE_SOCKETS:
|
10,000,000 | 响应端口 |
|
UNRESPONSIVE_SOCKETS: |
465,000,000 | 无响应端口 |
|
RESPONSIVE_SOCKETS_TIME: |
308 | 时间(秒) 轮询响应端点 |
|
RESET_SOCKETS_TIME: |
17,169 | 时间(秒) 轮询重置-响应主机 |
|
UNRESPONSIVE_SOCKETS_TIME: |
1,001,538 | 时间(秒) 轮询无响应(已过滤)端点 |
|
1,019,015 | seconds | ||
16,984 | 分钟 | ||
283 | hours | ||
12 | 天 | ||
RESPONSIVE_SOCKETS_TIME: |
308 | 时间(秒) 轮询响应端点 |
|
RESET_SOCKETS_TIME: |
5,723 | 时间(秒) 轮询重置-响应主机 |
|
UNRESPONSIVE_SOCKETS_TIME: |
143,077 | 时间(秒) 轮询无响应(已过滤)端点 |
|
149,108 | seconds | ||
2,485 | 分钟 | ||
41 | hours | ||
2 | 天 |
如上所示,第一组计算是使用先前定义的参数和 WinSocks 超时和重试的默认值对整个系统执行基本 TCP 会话轮询所需的时间。只需将超时更改为 2 秒且不重试,我们就可以将轮询 RFC1918 空间内所有设备所需的时间从默认值的 12 天缩短到仅 2 天。
我个人认为,这为在 TcpClient.BeginConnect
和 TcpClient.Connect
函数中调整 TCP 参数的能力,或实现外部计时机制来跟踪和终止应用程序层中的陈旧会话的必要性提供了充分的依据。
希望这对阅读的各位有所帮助。我感谢 emilio_grv 的回复。它给了我一个机会,让我回顾了 WinSock 的 TCP/IP 和套接字实现,并花了一些时间学习 TCP 本身。
历史
创建时间:2012/4/9,作者:xk2600。