NAT穿透的学习环境搭建与代码实现

学习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;
}

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值