NAT 打洞

本文基于NAT3+NAT3实现upd打洞(假设你对NAT类型已经很清楚)

如果A网络的NATA+B网络的NATB的值大于6则打洞会失败,需要使用turn中继服务

STUN协议解析

#pragma once
#include "hv/UdpClient.h"
#include "fmt/format.h"
/*
stun 模块,用户获取外网地址
*/namespace stun_cli
{#define ​​MAPPED_ADDRESS (0x0001)
#define XOR_MAPPED_ADDRESS (0x0020)//返回异或处理的公网IP/端口
#define MESSAGE_INTEGRITY (0x0008)//HMAC-SHA1消息完整性校验
#define ERROR_CODE (0x0009)//错误响应代码
#define FINGERPRINT (0x8028)//包尾CRC32校验防止粘包#pragma pack(push, 1)struct StunHeader {uint16_t msg_type;    // 消息类型(高2位为0,中6位方法,低8位类别)uint16_t msg_length;  // 消息长度(不含头部)uint32_t magic_cookie = htonl(0x2112A442); // 固定魔术字uint8_t transaction_id[12]; // 96位事务ID};struct StunAttribute {uint16_t type;     // 属性类型uint16_t length;   // 值长度(需4字节对齐)uint8_t value[];   // 变长数据};
#pragma pack(pop)void build_binding_request(StunHeader* pheader){pheader->msg_type = htons(0x0001); // Binding Requestpheader->msg_length = 0;srand(time(nullptr));for (int i = 0; i < 12; ++i){pheader->transaction_id[i] = std::rand() / 255;}}bool parse_response(uint8_t* buffer, unsigned int buf_size,unsigned short &port,std::string& ip) {StunHeader* hdr = reinterpret_cast<StunHeader*>(buffer);if (buf_size <= sizeof(StunHeader)) return false;if (ntohl(hdr->magic_cookie) != 0x2112A442) return false;uint8_t* attr_start = buffer + sizeof(StunHeader);int offset = 0;while (offset < buf_size) {StunAttribute* attr = reinterpret_cast<StunAttribute*>(attr_start + offset);unsigned short attr_nlen = ntohs(attr->length);switch (ntohs(attr->type)) {case ​​MAPPED_ADDRESS: {//8字节 1-保留 1-Family -2port 4/16 ipunsigned char* pval = attr->value;unsigned char family = pval[1];port = ntohs(*(unsigned short*)(pval + 2));if (family == 0x01)//IPV4 4bytes{unsigned char* pAddr = pval + 4;printf("IP:%d.%d.%d.%d:%d\r\n", pAddr[0], pAddr[1], pAddr[2], pAddr[3], port);ip = fmt::format("{0}.{1}.{2}.{3}", pAddr[0], pAddr[1], pAddr[2], pAddr[3]);return true;}else if (family == 0x02)//IPV6 16bytes{unsigned char* pAddr = pval + 4;}break;}case XOR_MAPPED_ADDRESS:{int a = 0;// 解析异或地址:IP/Port ^ Magic_Cookie//res.public_ip = ntohl(*reinterpret_cast<uint32_t*>(attr->value)) ^ 0x2112A442;break;}case MESSAGE_INTEGRITY:{int a = 0;//verify_hmac(attr->value); // 验证HMAC-SHA1break;}}//长度4字节对齐if (attr_nlen % 4 == 0)offset += attr_nlen;elseoffset += (attr_nlen / 4 + 1) * 4;offset += sizeof(StunAttribute);}return true;}
};

UDP 打洞demo

udp打洞流程
  1. 公网地址交换
1. A、B分别从STUN服务器获取公网地址 A_public:PortA 和 B_public:PortB
2. 通过信令服务器交换地址A、B外网地址
  1. 双向触发​​
1. A向 B_public:PortB 发送探测包​​ → NAT A 创建规则:“允许来自B公网地址的包进入”
2. B向 A_public:PortA 发送探测包​​ → NAT B 创建规则:“允许来自A公网地址的包进入”  #如果在规则创建前收到对方的包,则会被NAT丢去。因为NAT规则不允许。
  1. 直连通信、保持心跳
#经过上述两个规则之后NAT已经创建规则,支持对应的IP:PORT通讯了
1. 双方收到探测包好,确认NAT打洞完成
2. 定期发送空数据包(如每20秒),防止NAT表项超时关闭(默认30-60秒) 
udp代码实现
#pragma once
#include "stun.h"
#include <string>
#include <hv/UdpClient.h>
#include "Heap/XTimer.h"
#include <atomic>class UDPCli
{
public:UDPCli(){m_btunnel_ok = false;std::vector<std::string> ipv4s, ipv6s;get_host_addr("stun.voipbuster.com", ipv4s, ipv6s);m_pcli = std::make_shared<hv::UdpClient>();m_pcli->createsocket(3478, ipv4s[0].c_str());m_pcli->onMessage = [this](const hv::SocketChannelPtr&, hv::Buffer* data) {unsigned short sport = 0;std::string ip;if (!stun_cli::parse_response((uint8_t*)data->data(), data->size(), sport, ip)){//这里简化处理,默认收到非STUP的包则认为探测包成功//实际的时候需要检验包是否为探测包才能标记完成m_btunnel_ok = true;printf("[%I64d] Recv Message:%s\r\n",this, std::string((char *)data->data(), (char *)data->data() + data->size()).c_str());return;}if (!ip.empty())update(ip, sport);};m_pcli->start(true);//增加定时器和STUN通讯,防止UDP丢包,实际应该增加重试次数//demo只为验证流程是否准确,不做过多的业务处理CXTimer::Instance().set_timer(2000, INVALID_TIMER_ID, [this](uint64_t timeid) {if (is_ready()){CXTimer::Instance().kill_timer(timeid);return;}//通过STUN服务获取外网IP:PORTstun_cli::StunHeader req_hdr;stun_cli::build_binding_request(&req_hdr);m_pcli->sendto(&req_hdr, sizeof(req_hdr));}, true);}void set_peer(const std::string& ip, const unsigned short port){std::lock_guard<std::mutex> lk(m_mtx);m_peer_ip = ip;m_peer_port = port;//交换地址后发送探测包do_detect();}void get_own_info(std::string& ip, unsigned short &port){std::lock_guard<std::mutex> lk(m_mtx);ip = m_ip;port = m_port;}private://发送探测包void do_detect(){CXTimer::Instance().set_timer(10000, INVALID_TIMER_ID, [this](uint64_t timeid) {//这里是为了方便代码测试,应该在检测完成之后do_msg//这里直接放定时器里面检查,实际代码不应该这么做if (m_btunnel_ok){CXTimer::Instance().kill_timer(timeid);do_msg();return;}unsigned short sport = 0;std::string ip;get_peer_info(ip, sport);if (!ip.empty()){sockaddr_u local_addr;memset(&local_addr, 0, sizeof(local_addr));int ret = sockaddr_set_ipport(&local_addr, ip.c_str(), sport);m_pcli->sendto("{}", &local_addr.sa);}},true);}void do_msg(){CXTimer::Instance().set_timer(10000, INVALID_TIMER_ID, [this](uint64_t timeid) {unsigned short sport = 0;std::string ip;get_peer_info(ip, sport);sockaddr_u local_addr;memset(&local_addr, 0, sizeof(local_addr));int ret = sockaddr_set_ipport(&local_addr, ip.c_str(), sport);m_pcli->sendto(fmt::format("[{0}] say:hellow {1}",(ULONGLONG)this,time(nullptr)), &local_addr.sa);});}void update(const std::string &ip,const unsigned short port){std::lock_guard<std::mutex> lk(m_mtx);m_ip = ip;m_port = port;}void get_peer_info(std::string& ip, unsigned short &port){std::lock_guard<std::mutex> lk(m_mtx);ip = m_peer_ip;port = m_peer_port;}bool is_ready(){std::lock_guard<std::mutex> lk(m_mtx);return !m_ip.empty();}
private:std::mutex m_mtx;std::shared_ptr<hv::UdpClient>  m_pcli;std::string m_ip;unsigned short m_port;std::atomic_bool m_btunnel_ok;std::string m_peer_ip;unsigned short m_peer_port;};class UdpNAT
{
public:static UdpNAT& Instance(){static UdpNAT sInstance;return sInstance;}//测试通过void Test(){m_pcliA = std::make_shared<UDPCli>();m_pcliB = std::make_shared<UDPCli>();bool bChange = false;while (true){if (!bChange){//交换地址std::string ipA, ipB;unsigned short portA, portB;m_pcliA->get_own_info(ipA, portA);m_pcliB->get_own_info(ipB, portB);if (!ipA.empty() && !ipB.empty()){printf("ready A[%s:%d] B[%s:%d]\r\b", ipA.c_str(), portA,ipB.c_str(), portB);m_pcliA->set_peer(ipB, portB);m_pcliB->set_peer(ipA, portA);bChange = true;}}Sleep(10);}}
private:UdpNAT(){m_pcliA = m_pcliB = nullptr;}private:std::shared_ptr<UDPCli>  m_pcliA;std::shared_ptr<UDPCli>  m_pcliB;
};

测试结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87260.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87260.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java近期工作总结

近期工作中的一些总结 &#xff08;1&#xff09;三层模板和流程 我发现很多东西其实吧&#xff0c;三层就是一个模板和流程&#xff1b; 正向推&#xff0c;从控制层开始&#xff0c;反向从内个sql开始写&#xff0c;大部分应该就是从xml文件开始的&#xff0c;然后写到控制层…

vue中的torefs

在 Vue 中&#xff0c; toRefs(state) 的返回值是一个 新对象&#xff0c;其中每个属性都是对应 state 中原始属性的 ref 对象。具体来说&#xff1a; 返回值的结构与特性 1. 对象结构 - 若输入 state 为 { a: 1, b: text } &#xff0c;则 toRefs(state) 返回&a…

可编程逻辑器件的演进与对比分析

可编程逻辑器件的演进与对比分析 目录 离散逻辑芯片与早期PLD的限制CPLD的诞生与结构特点FPGA的架构创新CPLD与FPGA的核心差异总结 1. 离散逻辑芯片与早期PLD的限制 在还没有发明出可编程逻辑器件&#xff08;PLD: Programmable Logic Device&#xff09;之前&#xff0c;设…

Ubuntu机器开启root用户远程登录

一般正常情况是可以直接使用非root用户登录&#xff0c;但是由于权限问题&#xff0c;所以部分内容需要远程ROOT用户登录&#xff0c;具体如下&#xff1a; 1️⃣配置root用户密码 一般情况下系统中root不能直接登录&#xff0c;所以也没有保存root密码&#xff0c;现在需要登…

rockchip android14 设置不休眠

rockchip android14 设置不休眠 文章目录 rockchip android14 设置不休眠前言一、代码路径二、代码修改前言 在rk 的android14代码中设置开机后永不休眠 一、代码路径 device/rockchip/common/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml二、…

什么是数据孤岛?如何解决数据孤岛问题?

目录 一、数据孤岛的定义与表现 1. 数据孤岛的定义 2. 数据孤岛的表现形式 二、数据孤岛产生的原因 1. 技术层面 2. 组织管理层面 3. 业务流程层面 三、数据孤岛带来的危害 1. 对企业决策的影响 2. 对业务运营效率的影响 3. 对数据治理和安全的影响 四、解决数据孤…

自定义Cereal XML输出容器节点

自定义Cereal XML输出容器节点 CEREAL_SERIALIZE_INTRUSIVE 在 1.优化Cereal宏 一行声明序列化函数 QString、QVector、QList、QMap序列化在2.在Cereal中支持Qt容器序列化 静态成员函数type_node检测在 3.利用SFINAE检测成员函数 &#x1f680; 告别value0&#xff1a;自定义Ce…

Spark 写入hive表解析

FileOutputCommitter中提交mapreduce.fileoutputcommitter.algorithm.version有v1和v2两个版本。 v1版本Spark写入文件的流程&#xff1a; 1.当task完成的时候&#xff0c;会将task的结果文件先写入到临时目录下面。 2.所有的task完成后&#xff0c;将所有的结果文件写入到结…

Linux云计算基础篇(5)

一、sudo是什么&#xff1f; 定义&#xff1a;sudo(SuperUserDO)是一个Linux/Unix系统命令&#xff0c;允许被授权的普通用户以另一个用户&#xff08;通常是超级用户root&#xff09;的身份执行命令。 核心目的&#xff1a; 1.最小权限原则&#xff1a;避免让用户长期拥有ro…

Postgresql通过pgpool进行高可用部署主从,灾备(单机版)

1、bitnami/postgresql-repmgr:15 &#xff08;镜像名&#xff09; Bitnami 的 PostgreSQL-Repmgr 镜像是一个预配置的 Docker 镜像&#xff0c;集成了 PostgreSQL 数据库和 repmgr&#xff08;Replication Manager&#xff09;工具&#xff0c;用于快速搭建高可用&#xff08…

Flink-1.19.0源码详解-番外补充3-StreamGraph图

1.StreamGraph图: StreamGraph是Flink流处理作业的第一个计算调度流图&#xff0c;它是从用户编写的 DataStream API程序转换而来的逻辑图。StreamGraph由StreamNode与StreamEdge组成&#xff0c;StreamNode为记录数据处理的节点&#xff0c;StreamEdge为连接两个StreamNode的边…

linux系统---Nginx反向代理与缓存功能

目录 正向代理和反向代理 正向代理的作用 反向代理可实现的功能 反向代理客户端ip透传 1.初始访问192.168.235.139 结果 2.编辑代理服务器的配置文件 3、重载nginx服务 4、访问代理服务器 实现反向代理负载均衡 1.先启用已用另一台服务端 2.使用192.168.235.140 …

U+平台配置免密登录、安装Hadoop配置集群、Spark配置

文章目录 1、免密登录2、安装hadoop3、Spark配置 具体详细报告见资源部分&#xff0c;全部实验内容已经上传&#xff0c;如有需要请自行下载。 1、免密登录 使用的配置命令&#xff1a; cd ~/.ssh/ssh-keygen -t rsaEnter键回车y回车回车出现如上所示 cat ./id_rsa.pub >…

GitHub vs GitLab 全面对比报告(2025版)

从技术架构到金融估值&#xff0c;深度解析两大代码托管平台的差异化竞争策略 一、技术架构对比 维度GitHub (Microsoft旗下)GitLab (独立上市公司)关键差异核心架构- 分布式Git仓库 Issues/Projects- 全栈DevSecOps平台GitLab集成CI/CD、安全、监控部署模式- SaaS为主 - Git…

Python 数据分析与可视化 Day 14 - 建模复盘 + 多模型评估对比(逻辑回归 vs 决策树)

✅ 今日目标 回顾整个本周数据分析 & 建模流程学会训练第二种模型&#xff1a;决策树&#xff08;Decision Tree&#xff09;掌握多模型对比评估的方法与实践输出综合对比报告&#xff1a;准确率、精确率、召回率、F1 等指标为后续模型调优与扩展打下基础 &#x1fa9c; 一…

本周大模型新动向:KV缓存混合精度量化、个体时空行为生成、个性化问答

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 01 KVmix: Gradient-Based Layer Importance-Aware Mixed-Precision Quantization for KV Cache 大型语言模型&#xff08;LLMs&#xff09;在推理过程中&#xff0c;键值&#xff08;KV&#xff09;缓存的高内…

在 Spring Boot 中使用 WebMvcConfigurer

WebMvcConfigurer 是 Spring MVC 提供的一个扩展接口&#xff0c;用于配置 Spring MVC 的各种功能。在 Spring Boot 应用中&#xff0c;通过实现 WebMvcConfigurer 接口&#xff0c;可以定制和扩展默认的 Spring MVC 配置。以下是对 WebMvcConfigurer 的详细解析及其常见用法。…

w-笔记:uni-app的H5平台和非H5平台的拍照识别功能:

uni-app的H5平台和非H5平台的拍照识别功能&#xff1a; <template><view class"humanVehicleBinding"><view v-if"warn" class"shadow"></view><view class"header"><uni-nav-bar left-icon"l…

TCP 半连接队列和全连接队列(结合 Linux 2.6.32 内核源码分析)

文章目录 一、什么是 TCP 半连接队列和全连接队列二、TCP 全连接队列1、如何查看进程的 TCP 全连接队列大小&#xff1f;注意 2、TCP 全连接队列溢出问题注意 3、TCP 全连接队列最大长度 三、TCP 半连接队列1、TCP 半连接队列溢出问题2、TCP 半连接队列最大长度3、引申问题 一、…

linux下fabric环境搭建

参考教程&#xff1a; https://devpress.csdn.net/cloudnative/66d58e702045de334a569db3.html?dp_tokeneyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MjA2MzY4NywiZXhwIjoxNzQwMzY4MDc0LCJpYXQiOjE3Mzk3NjMyNzQsInVzZXJuYW1lIjoiaHVhbmd0dXBpIn0.oh8e4F6Sw_A4SV2ODQ5W0pYK0…