学习NAT穿透技术,若没有公网服务器,很难做代码的测试。本文通过Ensp + VMWare搭建一个测试代码的环境,并实现简单的NAT穿透的测试代码。
文中的源码主要引自:NAT穿透技术详解(udp打洞精髓附代码)_nat 打洞可以使用sendto吗_戴着眼镜看不清的博客-CSDN博客
一、环境搭建
1、Ensp项目设备图
2、Cloud和VMWare虚拟机连接情况
4台Cloud分别连接4个VMWare虚拟机,4个虚拟机分别名为 Client1 Client2 Sever7 Server8
Client1 Client2是不同网段的内网主机,主要用于NAT穿透测试的两个客户端,由于AR1 AR2路由器上没有配置Client1 Client2的路由,它们只能通过动态NAT技术实现外网(不是指internet,是指本虚拟环境下不同网段)访问,两者不能实现直接访问。
Sever7 只是同Server8配合来测试AR1 AR2的Cone模式是否设置成功。
Server8则是用于NAT穿透测试的中转服务器。
Cloud1连接 VMNet1网段,VMNet1网段有VMWare虚拟机,网络模式是仅主机模式,虚拟机是XP系统,IP地址192.168.10.128,网关指定为AR1路由器,网关IP 192.168.10.100
Cloud2连接 VMNet2网段,VMNet2网段有VMWare虚拟机,网络模式是仅主机模式,虚拟机是XP系统,IP地址192.168.20.128,网关指定为AR2路由器,网关IP 192.168.20.100
Cloud7连接 VMNet7网段,VMNet7网段有VMWare虚拟机,网络模式是仅主机模式,虚拟机是Win7系统,IP地址192.168.70.128,网关指定为AR7路由器,网关IP 192.168.70.100
Cloud8连接 VMNet8网段,VMNet8网段有VMWare虚拟机,网络模式是NAT模式,虚拟机是Win7系统,IP地址192.168.80.128,网关指定为AR1路由器,网关IP 192.168.80.100
3、AR1 AR2 AR7 AR8的配置
<R1>disp current-configuration
interface GigabitEthernet0/0/0
ip address 192.168.10.100 255.255.255.0
interface GigabitEthernet0/0/1
ip address 17.0.0.1 255.255.255.0
acl number 2000
rule 5 permit source 192.168.10.0 0.0.0.255
nat address-group 1 17.0.0.2 17.0.0.2
nat filter-mode endpoint-independent # 这两句是关键,将路由器NAT转换为Cone模式
nat mapping-mode endpoint-independent
interface GigabitEthernet0/0/1
nat outbound 2000 address-group 1
rip 100
version 2
network 17.0.0.0 # 不能再加 network 192.168.10.0,这样VMNet1主机就是内网主机
<R2>disp current-configuration
interface GigabitEthernet0/0/0
ip address 192.168.20.100 255.255.255.0
interface GigabitEthernet0/0/1
ip address 28.0.0.2 255.255.255.0
acl number 2000
rule 5 permit source 192.168.20.0 0.0.0.255
nat address-group 1 28.0.0.1 28.0.0.1
nat filter-mode endpoint-independent
nat mapping-mode endpoint-independent
interface GigabitEthernet0/0/1
nat outbound 2000 address-group 1
rip 100
version 2
network 28.0.0.0 # 不能再加 network 192.168.20.0,这样VMNet2主机就是内网主机
<R7>disp current-configuration
interface GigabitEthernet0/0/0
ip address 192.168.70.100 255.255.255.0
interface GigabitEthernet0/0/1
ip address 17.0.0.7 255.255.255.0
interface GigabitEthernet0/0/2
ip address 78.0.0.7 255.255.255.0
rip 100
version 2
network 17.0.0.0
network 192.168.70.0
network 78.0.0.0
<R8>disp current-configuration
interface GigabitEthernet0/0/0
ip address 192.168.80.100 255.255.255.0
interface GigabitEthernet0/0/1
ip address 28.0.0.8 255.255.255.0
interface GigabitEthernet0/0/2
ip address 78.0.0.8 255.255.255.0
rip 100
version 2
network 28.0.0.0
network 192.168.80.0 # 加上这句才能作为NAT的服务器
network 78.0.0.0
二、Client1的代码
// PatClientA.cpp : Defines the entry point for the console application.
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define SVR_IP "192.168.80.128"
#define SVR_PORT 8888
typedef struct _CLIENT_INFO {
struct in_addr ip;
USHORT port;
}CLIENT_INFO;
void CommWithPatClient( SOCKET sk, sockaddr* pPatAddr )
{
char buf[1024];
int ret = -1;
int addrlen = sizeof(sockaddr_in);
printf("Start receive data from Client B\n");
while(1) {
ZeroMemory( buf, 1024);
ret = recvfrom( sk, buf, 1024, 0, pPatAddr, &addrlen );
if( strcmp(buf,"") != 0 )
printf( "\t%s\n", buf);
ret = sendto(sk, buf, strlen(buf), 0, pPatAddr, addrlen );
// Sleep(1000);
if( strcmp(buf,"exit") == 0 )
break;
}
}
int main(int argc, char* argv[])
{
sockaddr_in saddr = {0};
sockaddr_in pataddr = {0};
WSADATA wsa = {0};
SOCKET skLocal = INVALID_SOCKET;
char ch = 'a';
char buf[] = "TO BBBBBBBB";
int ret = -1;
int saddrlen = sizeof(saddr);
CLIENT_INFO info = {0};
WSAStartup( MAKEWORD(2,2), &wsa );
skLocal = socket( AF_INET, SOCK_DGRAM, 0 );
if( INVALID_SOCKET == skLocal ) {
printf("socket() failed, errno=%d\n", WSAGetLastError() );
goto _CLEAN;
}
// send to server
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr(SVR_IP);
saddr.sin_port = htons(SVR_PORT);
ret = sendto( skLocal, (const char*)&ch, 1, 0, (sockaddr*)&saddr, sizeof(saddr) );
if( SOCKET_ERROR == ret ) {
printf("sendto() failed, errno=%d\n", WSAGetLastError() );
goto _CLEAN;
}
// recv IP and Port of Client B from server
ret = recvfrom( skLocal, (char*)&info, sizeof(info), 0, (sockaddr*)&saddr, &saddrlen );
if( SOCKET_ERROR == ret ) {
printf("recvfrom() failed, errno=%d\n", WSAGetLastError() );
goto _CLEAN;
}
printf( "Received IP:%s PORT:%d\n", inet_ntoa(info.ip), ntohs(info.port) );
pataddr.sin_addr = info.ip;
pataddr.sin_port = info.port;
pataddr.sin_family = AF_INET;
// dig hole to Client B
ret = sendto( skLocal, buf, strlen(buf), 0, (sockaddr*)&pataddr, sizeof(saddr) );
// communication with Client B
CommWithPatClient( skLocal, (sockaddr*)&pataddr );
_CLEAN:
if( INVALID_SOCKET != skLocal ) {
shutdown(skLocal, SD_BOTH);
closesocket(skLocal);
}
WSACleanup();
return 0;
}
三、Client2的代码
// PatClientA.cpp : Defines the entry point for the console application.
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define SVR_IP "192.168.80.128"
#define SVR_PORT 8888
typedef struct _CLIENT_INFO {
struct in_addr ip;
USHORT port;
}CLIENT_INFO;
void CommWithPatClient( SOCKET sk, sockaddr* pPatAddr )
{
char buf[1024];
int ret = -1;
int addrlen = sizeof(sockaddr_in);
while(1) {
ZeroMemory( buf, 1024);
printf( "Please input data to send:\n");
fgets( buf, sizeof(buf)-1, stdin );
ret = sendto(sk, buf, strlen(buf), 0, pPatAddr, addrlen );
printf("Start receive data from Client A\n");
ZeroMemory( buf, 1024);
ret = recvfrom( sk, buf, 1024, 0, pPatAddr, &addrlen );
if( strcmp(buf,"") != 0 )
printf( "\t%s\n", buf);
if( strcmp(buf,"exit") == 0 )
break;
}
}
int main(int argc, char* argv[])
{
sockaddr_in saddr = {0};
sockaddr_in pataddr = {0};
WSADATA wsa = {0};
SOCKET skLocal = INVALID_SOCKET;
char ch = 'a';
char buf[] = "TO AAAAAAAA";
int ret = -1;
int saddrlen = sizeof(saddr);
CLIENT_INFO info = {0};
WSAStartup( MAKEWORD(2,2), &wsa );
skLocal = socket( AF_INET, SOCK_DGRAM, 0 );
if( INVALID_SOCKET == skLocal ) {
printf("socket() failed, errno=%d\n", WSAGetLastError() );
goto _CLEAN;
}
// send to server
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr(SVR_IP);
saddr.sin_port = htons(SVR_PORT);
ret = sendto( skLocal, (const char*)&ch, 1, 0, (sockaddr*)&saddr, sizeof(saddr) );
if( SOCKET_ERROR == ret ) {
printf("sendto() failed, errno=%d\n", WSAGetLastError() );
goto _CLEAN;
}
// recv IP and Port of Client A from server
ret = recvfrom( skLocal, (char*)&info, sizeof(info), 0, (sockaddr*)&saddr, &saddrlen );
if( SOCKET_ERROR == ret ) {
printf("recvfrom() failed, errno=%d\n", WSAGetLastError() );
goto _CLEAN;
}
printf( "Received IP:%s PORT:%d\n", inet_ntoa(info.ip), ntohs(info.port) );
pataddr.sin_addr = info.ip;
pataddr.sin_port = info.port;
pataddr.sin_family = AF_INET;
// dig hole to Client A
ret = sendto( skLocal, buf, strlen(buf), 0, (sockaddr*)&pataddr, sizeof(saddr) );
// communication with Client B
CommWithPatClient( skLocal, (sockaddr*)&pataddr );
_CLEAN:
if( INVALID_SOCKET != skLocal ) {
shutdown(skLocal, SD_BOTH);
closesocket(skLocal);
}
WSACleanup();
return 0;
}
四、Sever8的代码
// PatServer.cpp : 定义控制台应用程序的入口点。
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define SVR_PORT 8888
typedef struct _CLIENT_INFO {
struct in_addr ip;
USHORT port;
}CLIENT_INFO;
int main(int argc, char* argv[])
{
CLIENT_INFO info[2] = {0};;
sockaddr_in saddr = {0};;
sockaddr_in raddr = {0};;
WSADATA wsa = {0};
SOCKET skServer = INVALID_SOCKET;
int ret = -1;
char buf[10] = {0};
int raddrlen = 0;
WSAStartup( MAKEWORD(2,2), &wsa );
skServer = socket( AF_INET, SOCK_DGRAM, 0 );
if( INVALID_SOCKET == skServer ) {
printf("socket() error, errno=%d\n", WSAGetLastError());
goto _CLEAN;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
saddr.sin_port = htons(SVR_PORT);
ret = bind( skServer, (sockaddr*)&saddr, sizeof(saddr) );
if( SOCKET_ERROR == ret ) {
printf("bind() error, errno=%d\n", WSAGetLastError());
goto _CLEAN;
}
while(1) {
// recv from A
ZeroMemory( buf, 10);
raddrlen = sizeof(raddr);
ret = recvfrom( skServer, buf, 10, 0, (sockaddr*)&raddr, &raddrlen );
if( SOCKET_ERROR == ret ) {
printf("recvfrom(A) error, errno=%d\n", WSAGetLastError());
break;
}
info[0].ip = raddr.sin_addr;
info[0].port = raddr.sin_port;
printf("Client A IP(%s), PORT(%d)\n", inet_ntoa(info[0].ip), ntohs(info[0].port) );
// recv from B
ZeroMemory( buf, 10);
raddrlen = sizeof(raddr);
ret = recvfrom( skServer, buf, 10, 0, (sockaddr*)&raddr, &raddrlen );
if( SOCKET_ERROR == ret ) {
printf("recvfrom(B) error, errno=%d\n", WSAGetLastError());
break;
}
info[1].ip = raddr.sin_addr;
info[1].port = raddr.sin_port;
printf("Client B IP(%s), PORT(%d)\n", inet_ntoa(info[1].ip), ntohs(info[1].port) );
// send info[1] to A
raddr.sin_family = AF_INET;
raddr.sin_addr = info[0].ip;
raddr.sin_port = info[0].port;
ret = sendto( skServer, (const char *)&info[1], sizeof(CLIENT_INFO), 0, (sockaddr*)&raddr, sizeof(raddr) );
if( SOCKET_ERROR == ret ) {
printf("sendto(A) error, errno=%d\n", WSAGetLastError());
break;
}
printf("sendto(A) OK\n");
// send info[0] to B
raddr.sin_family = AF_INET;
raddr.sin_addr = info[1].ip;
raddr.sin_port = info[1].port;
ret = sendto( skServer, (const char *)&info[0], sizeof(CLIENT_INFO), 0, (sockaddr*)&raddr, sizeof(raddr) );
if( SOCKET_ERROR == ret ) {
printf("sendto(B) error, errno=%d\n", WSAGetLastError());
break;
}
printf("sendto(B) OK\n");
}
_CLEAN:
if( INVALID_SOCKET != skServer ) {
closesocket(skServer);
}
WSACleanup();
return 0;
}