UDP打洞原理与N2N内网穿透

UDP打洞原理

通过UDP路由验证实现NAT穿越是一种在处于使用了NAT的私有网络中的Internet主机之间建立双向UDP连接的方法。由于NAT的行为是非标准化的,因此它并不能应用于所有类型的NAT。
其基本思想是这样的:让位于NAT后的两台主机都与处于公共地址空间的、众所周知的第三台服务器相连,然后,一旦NAT设备建立好UDP状态信息就转为直接通信,并寄希望于NAT设备会在分组其实是从另外一个主机传送过来的情况下仍然保持当前状态。
这项技术需要一个圆锥型NAT设备才能够正常工作。对称型NAT不能使用这项技术。
这项技术在P2P软件和VoIP电话领域被广泛采用。它是Skype用以绕过防火墙和NAT设备的技术之一。
相同的技术有时还被用于TCP连接——尽管远没有UDP成功。

原理

在这里插入图片描述
假设有两台分别处于各自的私有网络中的主机:A和B;NA和NB是两个网络的NAT设备,分别拥有IP地址P1和P2;S是一个使用了一个众所周知的、从全球任何地方都能访问得到的IP地址的公共服务器

步骤一:A和B分别和S建立UDP连接;NAT设备NA和NB创建UDP转换状态并分配临时的外部端口号

步骤二:S检查UDP包,看A和B的端口是否是正在被使用的(否则的话N1和N2应该是应用了端口随机分配,这会让路由验证变得更麻烦)

步骤三:如果端口不是随机化的,那么A和B各自选择端口X和Y,并告知S。S会让A发送UDP包到P2:Y,让B发送UDP包到P1:X

步骤四:A和B通过转换好的IP地址和端口直接联系到对方的NAT设备;

Server 端部分代码
	//这里是对UDT的启动记性初始化操作
	 if (UDT::ERROR == UDT::startup())
	 {
		 cout<<"startup: "<<UDT::getlasterror().getErrorMessage()<<endl;
	 }else{
		cout<<"startup suc..."<<endl;
	 }

	 //socket
	//像声明一个普通的socket一样声明一个UDTSOCKET
	 UDTSOCKET serv = UDT::socket(AF_INET, SOCK_DGRAM, 0);
	 if (UDT::ERROR == serv)
	{
		cout<<"socket: "<<UDT::getlasterror().getErrorMessage()<<endl;
	}else{
		cout<<"client suc..."<<endl;
	}

	 //声明udp socket,这里是udp的哈,不是udt
	 int sersocket = socket(AF_INET,SOCK_DGRAM,0);
	 if (SOCKET_ERROR == sersocket)
	{
		cout<<"udp socket error!"<<endl;
	}else{
		cout<<"clientsocket suc..."<<endl;
	}

	//为了能够在局域网中直接进行处理,先默认设置两个
	 sockaddr_in my_addr,client_addr;
	 my_addr.sin_family = AF_INET;
	 my_addr.sin_port = htons(atoi(argv[1]));
	 my_addr.sin_addr.s_addr = INADDR_ANY;
	 memset(&(my_addr.sin_zero), '\0', 8);
	 bind(sersocket,(struct sockaddr*)&my_addr,sizeof(my_addr));

	 client_addr.sin_family = AF_INET;
	 client_addr.sin_port = htons(atoi(argv[3]));
	 client_addr.sin_addr.s_addr = inet_addr(argv[2]);
	 //client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	 memset(&(client_addr.sin_zero), '\0', 8);

	 int mss = 1052;//最大传输单位
	 //设置收发缓冲区大小 接收限时  和地址重用
    if(   !( UDT::ERROR != (UDT::setsockopt(serv, 0, UDT_SNDBUF, new int(32000), sizeof(int)))
        && UDT::ERROR != (UDT::setsockopt(serv, 0, UDP_RCVBUF, new int(32000), sizeof(int)))
        && UDT::ERROR != (UDT::setsockopt(serv,0,UDT_REUSEADDR,new int(1),sizeof(int)))
        && UDT::ERROR != (UDT::setsockopt(serv, 0, UDT_RENDEZVOUS, new bool(true), sizeof(bool))))
		&& UDT::ERROR != (UDT::setsockopt(serv, 0, UDT_MSS, &mss, sizeof(int)) ))
    {
		cout<<"udt socket: "<<UDT::getlasterror().getErrorMessage()<<endl;
		UDT::close(serv);
        return 0;
    }
	//这里是直接将udp的接口绑定在udt的接口之上,如果不这样做的话是没法使用UDT中的SOCK_DGRAM的
	 if (UDT::ERROR == UDT::bind2(serv,sersocket))
	 {
		cout<<"udt bind2:"<<UDT::getlasterror().getErrorMessage()<<endl;
		return 0;
	 }else{
		cout<<"bind2 suc"<<endl;
	 }
	//这里也是关键部分,与client端对应的connect操作,就是相互之间的打洞处理
	 if (UDT::ERROR == UDT::connect(serv, (sockaddr*)&client_addr, sizeof(client_addr)))
	 {
	   cout << "connect: " << UDT::getlasterror().getErrorMessage();
	   UDT::close(serv);
	   return 0;
	 }else{
		cout<<"connetc suc"<<endl;
	 }
Client 端部分代码
	 //startup
	 if (UDT::ERROR == UDT::startup())
	 {
		cout<<"startup: "<<UDT::getlasterror().getErrorMessage()<<endl;
	 }else{
		cout<<"startup suc..."<<endl;
	 }

	 //Initialize the UDT library
	UDTSOCKET client = UDT::socket(AF_INET, SOCK_DGRAM, 0);
	if (UDT::ERROR == client)
	{
		cout<<"socket: "<<UDT::getlasterror().getErrorMessage()<<endl;
	}else{
		cout<<"client suc..."<<endl;
	}

	//声明udp socket
	int clientsocket = socket(AF_INET,SOCK_DGRAM,0);
	if (SOCKET_ERROR == clientsocket)
	{
		cout<<"udp socket error!"<<endl;
	}else{
		cout<<"clientsocket suc..."<<endl;
	}

	 sockaddr_in serv_addr,my_addr;
	 serv_addr.sin_family = AF_INET;
	 serv_addr.sin_port = htons(atoi(argv[3]));
	 serv_addr.sin_addr.s_addr = inet_addr(argv[2]);
	 memset(&(serv_addr.sin_zero), '\0', 8);

	 my_addr.sin_family = AF_INET;
	 my_addr.sin_port = htons(atoi(argv[1]));
	 my_addr.sin_addr.s_addr = INADDR_ANY;
	 memset(&(my_addr.sin_zero), '\0', 8);
	 bind(clientsocket,(struct sockaddr*)&my_addr,sizeof(my_addr));

	 int mss = 1052;//最大传输单位
	 //设置收发缓冲区大小 接收限时  和地址重用
    if(   !( UDT::ERROR != (UDT::setsockopt(client, 0, UDT_SNDBUF, new int(32000), sizeof(int)))
        && UDT::ERROR != (UDT::setsockopt(client, 0, UDP_RCVBUF, new int(32000), sizeof(int)))
        && UDT::ERROR != (UDT::setsockopt(client,0,UDT_REUSEADDR,new int(1),sizeof(int)))
        && UDT::ERROR != (UDT::setsockopt(client, 0, UDT_RENDEZVOUS, new bool(true), sizeof(bool))))
		&& UDT::ERROR != (UDT::setsockopt(client, 0, UDT_MSS, &mss, sizeof(int))))
    {
		cout<<"udt socket: "<<UDT::getlasterror().getErrorMessage()<<endl;
		UDT::close(client);
        return 0;
    }

	 if (UDT::ERROR == UDT::bind2(client,clientsocket))
	 {
		cout<<"udt bind2:"<<UDT::getlasterror().getErrorMessage()<<endl;
		return 0;
	 }else{
		cout<<"bind2 suc"<<endl;
	 }
	 
	 // connect to the server, implict bind
	 if (UDT::ERROR == UDT::connect(client, (sockaddr*)&serv_addr, sizeof(serv_addr)))
	 {
	   cout << "connect: " << UDT::getlasterror().getErrorMessage();
	   UDT::close(client);
	   return 0;
	 }else{
		cout<<"connect suc"<<endl;
	 }

以上代码使用UDT协议进行互相打洞,没有经过Server服务器(没有),简单的实现了网络穿透,可穿越防火墙。

N2N网络穿透

N2N是一个开放源代码的2层跨越3层的×××程序,该程序利用了点对点的架构来处理网络间的成员关系和路由,N2N的原理如下图,在搭建的过程中需要一个super节点和多个edge节点,super节点建立一个通信中心,用来路由edge之间的通讯,对于×××使用来说,super node节点必须有一个公网的IP地址

在这里插入图片描述

安装N2N

N2N GitHub地址

sudo apt-get install libssl-dev //安装openssl
git clone https://github.com/meyerd/n2n
cd n2n/n2n_v2
cmake CMakeLists.txt
make
make install

需要root权限

配置Supernode
supernode -l 1000 -v >/dev/null &   //监听1000端口
root@ubuntu16:# supernode -h  //可使用 -h查看命令参数
supernode usage
-l <lport>      Set UDP main listen port to <lport>  // UDP 监听端口
-f              Run in foreground.    //前台运行
-u <UID>        User ID (numeric) to use when privileges are dropped.  // 指定运行所用的UID
-g <GID>        Group ID (numeric) to use when privileges are dropped. // 指定运行所用的GID
-v              Increase verbosity. Can be used multiple times.   // 输出比较详细的log
-h              This help message.
配置Edgenode
edge -d n2n0 -c mynetwork -k encryptme -a 10.10.10.3 -l X.55.150.X:1000 >/dev/null &
-d 虚拟网卡名
-a [static:| dhcp:](虚拟网段ip)
-c 用于区分节点的组名
-k 用于加密的字符串
-l supernode的IP:端口,可以指定多个supernode

以上为节点配置,配置完成后 ifconfig 会多出个虚拟网卡
在这里插入图片描述
可在服务器上配置 supernode节点, 其余内网client配置edge节点,-a 自定义ip(网段需一致)-l 参数填服务器与监听的端口

Ping测试

在这里插入图片描述
搭建成功则可互相ping通

一键脚本代码

shell脚本代码(抄来的,略作修改),需root权限运行,centos系统 apt-get 改为 yum

#!/bin/bash
#####此脚本用来实现安装N2N的客户端,实现内网之间的穿透
####应用场景:
###客服的服务器有A、B、C三台,其中有一台可以上外网,此处以A为例子,ABC之间的SSH互通
####N2N的server,即super node为114.114.114.114,端口1000,在阿里云端,可以实现外网访问
####此脚本用来在客户的内网搭建N2N的client,可以实现和阿里云supernode的通信,这样通过阿里云端就可以SSH到客户服务器内网
N2N_super_node_ip=100.100.100.100 ###改为自己的服务器IP
N2N_super_node_port=1000	   ###自行更改
###N2N_edge_ip为搭建的edge的IP,需要设置,网段为10.10.10.*
####但有一个前提,设置的这个IP地址在虚拟局域网中不能冲突,所以需要先判断IP地址是否冲突
N2N_edge_ip=10.10.10.3   ###自行更改
judge_ip_confilct() {
    if `ping -c 2 ${N2N_edge_ip} &>/dev/null`;then
            echo -e "\033[32m                               ${N2N_edge_ip} can ping,has client used,please motified N2N_edge_ip,系统退出\033[0m" 
            exit 0
    else
        echo -e "\033[31m                               ${N2N_edge_ip} not can ping,N2N_edge_ip can be userd\033[0m"
    fi
}
check_super_node_service() {
    ping -c 6 ${N2N_super_node_ip}
    if `ping -c 2 ${N2N_super_node_ip} &>/dev/null`;then
        echo -e "\033[32m                               super node :${N2N_edge_ip} can ping, N2N server can be used\033[0m"
    else    
        echo -e "\033[31m                               super node :${N2N_edge_ip} can not ping ,n2n server can not be used ,please check system quit\033[0m"
        exit
    fi
}
n2n_install_super_node() {
    if `sudo apt-get install bc &>/dev/null`;then
        echo -e "\033[32m                yum can be use,starting yum install n2n relative paket:\033[0m"
        sudo apt-get install subversion gcc-c++ openssl-devel
        echo "git clone install n2n:"
        git clone   https://svn.ntop.org/svn/ntop/trunk/n2n
        if [ -e n2n ];then
            echo "n2n file download successful,beging install n2n"
            cd n2n/n2n_v2
            cmake CMakeLists.txt
            make
            make
            sudo make install
        else
            echo "n2n file download failed ,has some problems ,please check"
            exit 0
        fi
        echo "n2n install over,beginging start n2n services"
        supernode -l ${N2N_super_node_port} -v >/dev/null &
        
        echo "查看 ps -ef | grep supernode"
        ps -ef | grep supernode
        echo "supernode -l ${N2N_super_node_port} -v >/dev/null &"  >> /etc/rc.local
    else
        echo -e "\033[31m                yum not can be use,yum install n2 has some problem,please check\033[0m"
        exit
    fi
}
n2n_install_edge_node() {
    if `sudo apt-get install bc &>/dev/null`;then
        echo -e "\033[32m               yum can be use,starting yum install n2n relative paket:\033[0m"
        sudo apt-get install subversion gcc-c++ openssl-devel
        echo "git clone install n2n:"
        git clone https://github.com/meyerd/n2n.git
        if [ -e n2n ];then
            echo "n2n file download successful,beging install n2n"
            cd n2n/n2n_v2
            cmake CMakeLists.txt
            make
            make
            sudo make install
        else
            echo "n2n file download failed ,has some problems ,please check"
            exit 0
        fi
        echo "check super node server is ok or not"
        check_super_node_service
        echo "n2n install over,begining start edge node"
        edge -d n2n0 -c mynetwork -k encryptme -M 1200 -a $N2N_edge_ip -l $N2N_super_node_ip:$N2N_super_node_port >/dev/null &
    
        echo "查看 ps -ef | grep edge,进程是否启动OK"
        ps -ef | grep edge
        sudo echo "edge -d n2n0 -c mynetwork -k encryptme -a $N2N_edge_ip -l $N2N_super_node_ip:$N2N_super_node_port" >> /etc/rc.local
    else
        echo -e "\033[31m               yum not can be use,yum install n2 has some problem,please check\033[0m"
        exit
    fi
}
real=`grep -l '\^H' /root/.bash_profile`   
if [ $? -eq 1 ];then   
    echo  'stty erase ^H' >> /root/.bash_profile   
    source /root/.bash_profile   #这几行主要就是让在使用read键时能使用回删键。写错了,回删了,重启写。不用这段的话,回删键会变成乱码。 
fi
echo -e '\033[0;33;1m #################nagios################## \033[0m' #让echo能弄点颜色出来好看点。。。     
echo "n2n supernode install please input : 1" 
echo "n2n edgenode  install please input : 2"   
echo -e '\033[0;33;1m ######################################### \033[0m'
read -p "please chose : " frist   #定义输入的值 
if [ $frist -eq 1 ];then
    n2n_install_super_node
else
    n2n_install_edge_node
fi
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值