网络穿透:TCP 打洞、UDP 打洞与 UPnP

server/2024/9/22 19:43:57/

在现代网络中,很多设备都处于 NAT(网络地址转换)或防火墙后面,这使得直接访问这些设备变得困难。在这种情况下,网络穿透技术就显得非常重要。本文将介绍三种常用的网络穿透技术:TCP 打洞、UDP 打洞和 UPnP。
在这里插入图片描述

一、TCP 打洞

1.1 什么是 TCP 打洞?

TCP 打洞(TCP Hole Punching)是一种使 NAT 后的两个客户端通过第三方服务器建立直接连接的方法。NAT 通常会阻止外部主机直接与内部主机通信,因此需要借助外部服务器来协调连接。

1.2 工作原理

  1. 建立与中继服务器的连接:两个 NAT 后的客户端 A 和 B 先分别与公共服务器 S 建立连接。
  2. 交换外部地址:服务器 S 了解 A 和 B 的外部 IP 和端口,并将这些信息发送给彼此。
  3. 尝试直接连接:A 和 B 分别尝试使用彼此的外部 IP 和端口进行连接,如果两端的 NAT 设备允许,则连接成功。

在这里插入图片描述

1.3 示例代码

以下是一个简单的 Python 示例,演示了通过 TCP 打洞进行连接的过程。

import socket# Server listens for incoming connections and exchanges client information
def server():s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(('0.0.0.0', 12345))s.listen(2)print("Server waiting for connections...")conn_a, addr_a = s.accept()print(f"Client A connected: {addr_a}")conn_b, addr_b = s.accept()print(f"Client B connected: {addr_b}")# Exchange addressesconn_a.send(f"{addr_b[0]}:{addr_b[1]}".encode())conn_b.send(f"{addr_a[0]}:{addr_a[1]}".encode())conn_a.close()conn_b.close()s.close()# Clients attempt to connect to each other using exchanged information
def client():s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(('server_ip', 12345))  # Replace 'server_ip' with the actual IP of the serverpeer_info = s.recv(1024).decode()peer_ip, peer_port = peer_info.split(':')# Attempt to connect to peertry:peer_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)peer_socket.connect((peer_ip, int(peer_port)))print("Connected to peer!")except Exception as e:print(f"Failed to connect to peer: {e}")s.close()

二、UDP 打洞

2.1 什么是 UDP 打洞?

UDP 打洞(UDP Hole Punching)与 TCP 打洞类似,是一种让处于 NAT 后的两台主机通过第三方服务器建立直接 UDP 连接的技术。与 TCP 不同的是,UDP 是无连接的协议,允许 NAT 主机更容易接受来自外部的连接请求。

2.2 工作原理

  1. 与服务器通信:两台客户端 A 和 B 分别与公共服务器 S 进行通信,服务器记录它们的外部 IP 和端口。
  2. 交换地址:服务器将 A 和 B 的外部 IP 和端口互相传递。
  3. 直接发送 UDP 数据包:A 和 B 尝试通过彼此的外部地址直接发送 UDP 数据包,利用 NAT 会话表进行数据传输。
    在这里插入图片描述

2.3 示例代码

import socket# UDP Server to exchange addresses
def udp_server():s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.bind(('0.0.0.0', 12345))print("Server waiting for messages...")data_a, addr_a = s.recvfrom(1024)print(f"Received from A: {addr_a}")data_b, addr_b = s.recvfrom(1024)print(f"Received from B: {addr_b}")# Exchange addressess.sendto(f"{addr_b[0]}:{addr_b[1]}".encode(), addr_a)s.sendto(f"{addr_a[0]}:{addr_a[1]}".encode(), addr_b)# UDP Client to communicate through hole punching
def udp_client():s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.sendto(b'Hello from client', ('server_ip', 12345))  # Replace 'server_ip' with actual server IPpeer_info, _ = s.recvfrom(1024)peer_ip, peer_port = peer_info.decode().split(':')# Send message to peers.sendto(b'Hello peer!', (peer_ip, int(peer_port)))try:response, _ = s.recvfrom(1024)print(f"Received from peer: {response}")except socket.timeout:print("No response from peer")s.close()

三、UPnP(通用即插即用)

3.1 什么是 UPnP?

UPnP(Universal Plug and Play,通用即插即用)是一种网络协议,允许设备自动发现和与网络中的其他设备进行通信。在 NAT 环境下,UPnP 可以自动打开路由器的端口,从而允许外部设备访问位于内网中的设备。

UPnP 主要用于家庭网络和小型局域网,它通过设备的自动配置来简化网络中的设备通信过程。

3.2 工作原理

  1. 设备发现:客户端设备通过发送 SSDP(简单服务发现协议)请求,查找网络中的 UPnP 设备。
  2. 获取路由器的设备描述:通过 SSDP 发现的设备提供一个设备描述 XML 文件,描述其功能和端点。
  3. 请求端口映射:客户端通过向路由器发送请求,要求映射一个外部端口到内网设备的特定端口。
    在这里插入图片描述

3.3 示例代码

可以使用第三方库 miniupnpc 来实现 UPnP 端口映射,以下是一个 Python 示例。

pip install miniupnpc
import miniupnpcdef upnp_port_mapping():upnp = miniupnpc.UPnP()upnp.discoverdelay = 200upnp.discover()  # Discover UPnP devicesupnp.selectigd()  # Select Internet Gateway Deviceexternal_port = 12345internal_port = 54321local_ip = upnp.lanaddr  # Get local IP address# Add port mapping (TCP)upnp.addportmapping(external_port, 'TCP', local_ip, internal_port, 'Test Port Mapping', '')print(f"Port {external_port} mapped to {local_ip}:{internal_port} (TCP)")# Optionally, remove the port mapping# upnp.deleteportmapping(external_port, 'TCP')upnp_port_mapping()

四、总结

  • TCP 打洞:通过第三方服务器交换外部地址,尝试建立直接的 TCP 连接。
  • UDP 打洞:类似 TCP 打洞,但使用 UDP 协议,更容易成功。
  • UPnP:通过自动化的端口映射,使内网设备更易于被外部设备访问。

这三种技术在 P2P 应用中非常重要,特别是在 NAT 或防火墙环境下,它们能够显著提高连接的成功率。


http://www.ppmy.cn/server/120425.html

相关文章

CVE-2024-2389 未经身份验证的命令注入

什么是 Progress Flowmon? Progress Flowmon 是一种网络监控和分析工具,可提供对网络流量、性能和安全性的全面洞察。Flowmon 将 Nette PHP 框架用于其 Web 应用程序。 未经身份验证的路由 我们开始在“AllowedModulesDecider.php”文件中枚举未经身份验证的端点,这是一个描…

zookeeper向管控平台上报状态

问题 在你的场景中,由于 Django 应用启动了 4 个 uWSGI 进程,每个进程都会创建一个节点并上报状态,因此出现了 4 次状态上报的情况。这在大多数情况下是不合理的,尤其是在你只期望应用上报一次状态时。 要解决这个问题并优雅地进…

【Unity实战】SO反序列化正确姿势

此篇博文算是【C#实战】Newtonsoft.Json基类子类解析_newtonsoft.json不能转化子类-CSDN博客的一个补充,因为我发现ScriptableObject并不是传统的new()就能解决的问题。 SomeClass must be instantiated using the ScriptableObject.CreateInstance method instead…

C++类和对象(下)

文章目录 const成员函数取地址运算符重载初始化列表类型转换static成员友元内部类匿名对象 const成员函数 概念:将const修饰的成员函数称之为const成员函数 定义:const修饰成员函数时,放到成员函数参数列表的后面 例: class Date…

Selenium等待机制:理解并应用显式等待与隐式等待,解决页面加载慢的问题

目录 引言 等待机制的重要性 显式等待(Explicit Wait) 原理 应用方式 代码示例 优点与缺点 隐式等待(Implicit Wait) 原理 应用方式 代码示例 优点与缺点 解决页面加载慢的问题 1. 合理设置等待时间 2. 优先使用显…

【JavaEE初阶】多线程6(线程池\定时器)

欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误,欢迎指出~ 目录 实例3:线程池 参数解释 核心线程数, 最大线程数 允许空闲的最大时间 ,时间单位 任务队列(阻塞队列) 线程工厂>工厂设计模式 拒绝策略 使用举例 模拟实现一个线…

如何在Linux Centos7系统中挂载群晖共享文件夹

前景:企业信息化各种系统需要上传很多的图片或者是文件,文件如何在群晖中显示,当文件或者图片上传到linux指定文件夹内,而文件夹又与群晖共享文件夹进行挂载,就能保证上传的文件或者图片出现在群晖并在群晖里进行管理。…

Mac 上,终端如何开启 proxy

文章目录 为什么要这么做前提步骤查看 port查看代理的port配置 bash测试 为什么要这么做 mac 上的终端比较孤僻吧,虽然开了,但是终端并不走🪜…产生的现象就是,浏览器可以访问🌍,但是终端不可以访问&#…