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
英文地址,請注明出處:http://en.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…