1.概要
    相信在IPv6的时代到来之前,NAT仍然是解决大多数人上网的主要途径,而且它在企业内网Intranet中也扮演着十分重要的角色.
    NAT的全称是Network Address Translator(网络地址转换),其主要作用是把内网IP地址转换成为全球唯一的可定位的外部IP地址,从而使得 局域网内的所有用户可以通过一个或者少数几个IP地址与全球的Internet通信,不仅节约了IP地址,而且在一定程度上保护了内部网络.
    由于工作需要,笔者希望编写一个具有NAT功能的软件,将同一个网段内把本机设为网关的拥有私有IP的主机发来的数据包转发到外部网络,并把响应信息返回给对应的主机.这个问题在不同的层次上做就有不同的解决方案,由于笔者也是网络新手,走了不少弯路:
    首先,企图在用户层利用原始套接字(Raw Socket)来实现,但是 系统拥有对未开放端口的自动复位功能,每当我们转发一个数据包时,需要占用 系统的一个端口,但是这点 系统并不知道,它接收到对于这个端口的回应信息时,会认为本端口不存在,并发送一个带有复位标志的数据包请求对方断开连接.这就阻拦了所有非本机请求的连接,所以这个方案首先被否定了.
    随后,不得不往 系统下面走,准备在核心态实现.当然越简单越好,于是笔者选择了Filter-Hook驱动.Filter-Hook Driver, 事实上不是一种新的网络驱动,它只是扩展了IP过滤驱动(IP Filter Driver)的功能,是一种内核模式驱动(Kernel Mode Driver). 在Filter-Hook Driver中我们提供回调函数(callback),然后使用IP Filter Driver注册回调函数。这样当数据包发送和接收时,IP Filter Driver会调用回调函数。可惜梦想再一次破灭,这个回调函数的返回值只有PF_FORWARD,PF_DROP,PF_PASS三种,并不能把修改后的数据包主动发送出去.
    只有在向底层走了,NDIS应该是必经之路.而且经过两次失败,发现闭门造车是不可行的,偶然在网上搜索到了几篇文章,听说在NDIS的中间层驱动中可以实现NAT,新的探索之路就这样开始了......

2.NAT简介
    NAT(Network Address Translator)的出现并不是偶然的,一方面是由于IPv4的创造者们没有想到,Internet以及TCP/IP发展如此迅速,在他们还们完全享受TCP/IP的成功带来的快感之前,32位的IP地址竟然不够用了;另一方面我们必须保证某些特殊的主机在于 局域网络连接的同时,保持对外界直接曝光,但是由需要与外界在受控的情况下通讯.下图是一个典型的NAT示意.
       / | /                  .                               /
   +---------------+  WAN     .           +-----------------+/
   |Regional Router|----------------------|Stub Router w/NAT|---
   +---------------+          .           +-----------------+/
                              .                      |        /
                              .                      |  LAN
                              .               ---------------
                        Stub border

下面举一个具体的例子说明两个处于内网的主机是如何通过NAT通信的
                                       / | /
                                     +---------------+
                                     |Regional Router|
                                     +---------------+
                                   WAN |           | WAN
                                       |           |
                   Stub A .............|....   ....|............ Stub B
                                       |           |
                     {s=198.76.29.7,^  |           |  v{s=198.76.29.7,
                      d=198.76.28.4}^  |           |  v d=198.76.28.4}
                       +-----------------+       +-----------------+
                       |Stub Router w/NAT|       |Stub Router w/NAT|
                       +-----------------+       +-----------------+
                             |                         |
                             |  LAN               LAN  |
                       -------------             -------------
                                 |                 |
               {s=10.33.96.5, ^  |                 |  v{s=198.76.29.7,
                d=198.76.28.4}^ +--+             +--+ v d=10.81.13.22}
                                |--|             |--|
                               /____/           /____/
                             10.33.96.5       10.81.13.22

图中有两个残桩网络A和B,现在假设A中的一台主机10.33.96.5需要同B中的10.81.13.22通信,它必须把自己发送的数据报的目的地址设置为B的一个外网地址198.76.28.4,并在NAT中把源地址转换成A的外部地址198.76.29.7,才能使数据包顺利抵达广域网中的路由器,并转到B,在由B网的NAT把数据包发送给10.81.13.22.
    随着NAT多年的发展,出现了很多不同风格,应用于不同场合的NAT.笔者实现的是传统NAT中的一种特殊情况NAPT(Network Address Port Translation),它把所有内网的IP地址都转换成同一个外部IP地址,并通过不同的端口来区分各个不同的内部主机.

3.中间层驱动NDIS Intermediate Drivers
    所谓中间层驱动是指位于微端口和协议之间的驱动,实际上它是微软在网络驱动中留出来的接口,便于用户实现自己对数据包的处理.在协议驱动层看来,它就是微端口;在微端口看来,它又是协议层驱动.因此,如果需要在此层实现自己对数据包的处理函数,不仅要在上边缘注册MiniportXxx Function,还要在下边缘注册ProtocolXxx Function.一般在这个层次做工作的同志都会学习并了解DDK的一个经典Sample:Passthru.如果对它不了解,可以去看看Addylee前几天的文章"基于PassThru的NDIS中间层驱动程序扩展",讲得很清晰.

4.NAPT的具体实现
    程序整体框架依然是基于PaaThru的,具体要注意的问题有以下几点:
4.1 转发表的维护
typedef struct _PortNode
{
    USHORT inport;                      //内网端口
    USHORT export;                //转发端口
    USHORT report;                //远程端口
    ULONG inip;                //内网IP
    ULONG reip;                //远程IP
    struct _PortNode * next;            //链表指针
}PortNode;

PortNode * first = NULL;                    //全局变量,转发表的头结点

NTSTATUS
DriverEntry(
    IN    PDRIVER_OBJECT        DriverObject,
    IN    PUNICODE_STRING        RegistryPath
    )
{
        ......
    Status = NdisAllocateMemory(&first,sizeof(PortNode), 0,HighestAcceptableMax);
    if(Status == NDIS_STATUS_SUCCESS)
    {
        NdisZeroMemory(first,sizeof(PortNode));
        //首结点的inip表示本主机地址
        first->inip = 本主机IP            
        //首结点的reip表示本主机所在的网络地址
        first->reip = first->inip & 0x00ffffff;
    }
    ......
}

4.2 校验和的计算
USHORT CheckSum(USHORT *buffer, int size) 
{
    unsigned long cksum=0;
    while(size >1) 
    {
        cksum += * buffer++;
        size -=sizeof(USHORT);
    }
    if(size) 
    {
        cksum += *(UCHAR*)buffer;
    }
    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >>16);
    return (USHORT)(~cksum);
}
   IP TCP UDP三种包校验和的计算方法是一致的,本文采用的方法是简单地重新计算整个包的校验和,在RFC1631中,作者提出了一种差量计算法以提高计算速度,并且给出了C语言版的源代码.
4.3 对收到的数据包的过滤和转发
INT
PtReceivePacket(
    IN    NDIS_HANDLE            ProtocolBindingContext,
    IN    PNDIS_PACKET        Packet
    )
{
    ......

PUCHAR       pPacketContent;
        PUCHAR       pBuf;
        UINT         BufLength;
        MDL          * pNext;
        UINT         i,j;
    BOOLEAN      transflag = FALSE;
    PNDIS_BUFFER MyBuffer;
    PIP_Header   pIPHeader;
    
    ......

NdisDprAllocatePacket(&Status,
                           &MyPacket,
                           pAdapt->RecvPacketPoolHandle);

if(Status == NDIS_STATUS_SUCCESS)
    {
        //add by thinking 06.6.3
        //把数据包内容从Packet拷贝到pPacketContent
        Status= NdisAllocateMemory( &pPacketContent, 2000, 0,HighestAcceptableMax);
        if (Status!=NDIS_STATUS_SUCCESS )     return Status;
        NdisZeroMemory (pPacketContent, 2000);
        NdisQueryBufferSafe(Packet->Private.Head, &pBuf, &BufLength, 32 );
        NdisMoveMemory(pPacketContent, pBuf, BufLength);
        i = BufLength;
        pNext = Packet->Private.Head;
        for(;;)
        {
            if(pNext == Packet->Private.Tail)
                break;
            pNext = pNext->Next;
            if(pNext == NULL) 
                break;
            NdisQueryBufferSafe(pNext,&pBuf,&BufLength,32);
            NdisMoveMemory(pPacketContent+i,pBuf,BufLength);
            i+=BufLength;
        }
        if(pPacketContent[12] == 8 &&  pPacketContent[13] == 0 )  //is ip packet
        {
            ULONG netip;
            pIPHeader = (PIP_Header)(pPacketContent+14);
            netip = pIPHeader->ipSource & 0x00ffffff;
            //对收到的数据包进行过滤,只转发需要转发的包
            if(pIPHeader->ipDestination == first->inip && netip != first->reip)
            //如果目的地址是本主机,并且源IP不是本网段地址,则转发给内网主机
            {
                DbgPrint("/nTransInPacket.../n");
                for(j=0;j<=i;j++)
                    DbgPrint("%x ",pPacketContent[j]);
                //修改发给内网的数据包头
                transflag = TransIn(pPacketContent);
            }
            else if(pIPHeader->ipDestination != 0xffffffff &&
                (pIPHeader->ipDestination & 0x00ffffff) != first->reip &&
                netip == first->reip)
            //如果目的地址不是广播地址,而且是外网地址,源地址是内网IP,则转发给外网
            {
                DbgPrint("/nTransOutPacket.../n");
                for(j=0;j<=i;j++)
                    DbgPrint("%x ",pPacketContent[j]);
                //修改发给外网的数据包头
                transflag = TransOut(pPacketContent);
            }
        }

if(!transflag)
        {
               //按照原来的方式往上提交    
               ......
        }
        else
        {
            //转发的一段关键代码
            NdisAllocateBuffer(&Status,&MyBuffer,pAdapt->SendPacketPoolHandle,pPacketContent,i);
            NdisChainBufferAtFront(MyPacket, MyBuffer);
            Resvd =(PRSVD)(MyPacket->ProtocolReserved);
            Resvd->OriginalPkt = MyPacket;
                MyPacket->Private.Head->Next = NULL;
            MyPacket->Private.Tail = NULL;
            NdisSetPacketFlags(MyPacket, NDIS_FLAGS_DONT_LOOPBACK);
            NdisReturnPackets(&Packet, 1);
            NdisSend(&Status,pAdapt->BindingHandle,MyPacket);
            if(Status != NDIS_STATUS_PENDING)
            {
                NdisUnchainBufferAtFront(MyPacket ,&MyBuffer); //从MyPacket中解除buffer
                NdisQueryBufferSafe(MyBuffer, &pPacketContent, &BufLength,32 );
                if(pPacketContent != NULL)
                    NdisFreeMemory(pPacketContent,BufLength, 0);
                NdisFreeBuffer(MyBuffer);
            }
            return 0;
        }
    ......
}

4.4 数据包头的修改
    根据具体情况修改数据包的IP包头,TCP包头,或者UDP包头,并且在修改的同时继续维护转发表,下面只给出TransIn的代码,TransOut与其原理相同.
BOOLEAN TransIn(PUCHAR pPacketContent) 
{
    PortNode * inmap;
    PIP_Header pIPHeader = (PIP_Header)(pPacketContent+14);
    USHORT iphdrlen = (pIPHeader->iphVerLen & 0x0f) * sizeof(ULONG);
    UCHAR checkbuff[2000] = {0};

if(pIPHeader->ipProtocol == 6)
    {
        PTCP_Header pTCPHeader;
        USHORT tcphdrlen;
        pTCPHeader = (PTCP_Header)(pPacketContent+14 + iphdrlen);
        //tcphdrlen = ((pTCPHeader->dataoffset & 0xf0) >> 4) * sizeof(ULONG);
        tcphdrlen = htons(pIPHeader->ipLength) - iphdrlen;
        inmap = InMapping(pIPHeader->ipSource,pTCPHeader->sourcePort,
            pTCPHeader->destinationPort);
        if(inmap == NULL)
            return FALSE;

//修改目的地址和目的端口,校验和
        pIPHeader->ipDestination = inmap->inip;
        pTCPHeader->destinationPort = inmap->inport;
        pIPHeader->ipChecksum = 0;
        pTCPHeader->checksum = 0;

//填充TCP伪首部
        psdhdr.saddr = pIPHeader->ipSource;
        psdhdr.daddr = pIPHeader->ipDestination;
        psdhdr.len = htons(tcphdrlen);
        psdhdr.mbz = 0;
        psdhdr.proto = 6;

//计算TCP首部校验和
        NdisMoveMemory(checkbuff,&psdhdr,sizeof(psdhdr));
        NdisMoveMemory(checkbuff+sizeof(psdhdr),pTCPHeader,tcphdrlen);
        pTCPHeader->checksum = CheckSum((USHORT *)checkbuff,sizeof(psdhdr)+tcphdrlen);

//计算IP首部校验和
        pIPHeader->ipChecksum = CheckSum((USHORT *)pIPHeader,iphdrlen);

return TRUE;
    }
    else if(pIPHeader->ipProtocol == 17)
    {
        PUDP_Header pUDPHeader;
        USHORT udplen;
        pUDPHeader = (PUDP_Header)(pPacketContent+14 + iphdrlen);
        udplen = htons(pUDPHeader->len);
        inmap = InMapping(pIPHeader->ipSource,pUDPHeader->sourcePort,
            pUDPHeader->destinationPort);
        if(inmap == NULL)
            return FALSE;

//修改目的地址和目的端口,校验和
        pIPHeader->ipDestination = inmap->inip;
        pUDPHeader->destinationPort = inmap->inport;
        pIPHeader->ipChecksum = 0;
        pUDPHeader->checksum = 0;

//填充UDP伪首部
        psdhdr.saddr = pIPHeader->ipSource;
        psdhdr.daddr = pIPHeader->ipDestination;
        psdhdr.len = pUDPHeader->len;
        psdhdr.mbz = 0;
        psdhdr.proto = 17;

//计算UDP校验和,包括所有UDP数据
        NdisMoveMemory(checkbuff,&psdhdr,sizeof(psdhdr));
        NdisMoveMemory(checkbuff+sizeof(psdhdr),pUDPHeader,udplen);
        pUDPHeader->checksum = CheckSum((USHORT *)checkbuff,sizeof(psdhdr)+udplen);

//计算IP首部校验和
        pIPHeader->ipChecksum = CheckSum((USHORT *)pIPHeader,iphdrlen);

return TRUE;
    }
    else
        return FALSE;
}

5.小结
    本文简单介绍了传统NAT在中间层驱动中的实现,很多地方都可以进行改进.例如:校验和的计算可以采用差量计算法以减少计算延迟;转发表的维护可以采用树型结构(而不是本文中的链表)以减少转发表的查找时间;定时对转发表进行清理,释放长时间不用的端口,以节约 系统资源;构建ARP机制,并动态维护相关主机的MAC地址;通过共享内存或者修改驱动对象的DispatchTable与用户层进行通信,从而动态调整驱动功能.

参考文献:
    RFC1631: The IP Network Address Translator (NAT)
    RFC2663: IP Network Address Translator (NAT) Terminology and Considerations
       Addylee "基于PassThru的NDIS中间层驱动程序扩展" http://www.xfocus.net/articles/200605/865.html
       Jesús O "开发Windows 2000/XP下的防火墙" http://www.vckbase.com/document/viewdoc/?id=1067

【转帖】NAT在NDIS中间层驱动中的实现相关推荐

  1. NAT在NDIS中间层驱动中的实现

    1.概要 相信在IPv6的时代到来之前,NAT仍然是解决大多数人上网的主要途径,而且它在企业内网Intranet中也扮演着十分重要的角色. NAT的全称是Network Address Transla ...

  2. 【转帖】基于NDIS(网络驱动接口标准)包拦截技术

    通常用户都知道,NDIS协议驱动程序是通过填写一张NDIS_PROTOCOL_CHARACTERISTICS的表,并调用NDIS API函数NdisRegisterProtocol进行注册.现在我们来 ...

  3. Windows网络驱动、NDIS驱动(微端口驱动、中间层驱动、协议驱动)、TDI驱动(网络传输层过滤)、WFP(Windows Filtering Platfrom))

    catalog 0.引言 1.Windows 2000网络结构和OSI模型 2.NDIS驱动 3.NDIS微端口驱动编程实例 4.NDIS中间层驱动编程实例 5.TDI驱动 6.TDI驱动 7.TDI ...

  4. NDIS中间层的驱动包截获技术教程

    NDIS(Network Driver Interface Specification)是网络驱动程序接口规范的简称.它横跨传输层.网络层和数据链路层,定义了网卡或网卡驱动程序与上层协议驱动程序之间的 ...

  5. windows7以上平台NDIS6框架的NDIS协议驱动开发

    by fanxiushu 2019-01-30 转载或引用请注明原始作者. 提到NDIS协议驱动,可能比较陌生,因为毕竟用得挺少的. 但是一提到WireShark或ethereal等抓包软件,大家就不 ...

  6. 基于NDIS(网络驱动接口标准)包拦截技术

    看了很多提供数据包的拦截技术,其中最多的是编写IM DRIVER在NDIS中间层对MINIPORT(网卡驱动程序)和协议驱动程序之间的数据包进行拦截.但编写该过滤程序拦截程序非常的复杂,这里介绍一种更 ...

  7. Android GPS中间层驱动开发调试

    ~.Android GPS中间层驱动开发调试小结  // rkeclair_v1.02_sdkdemo , ublox芯片       调通GPS功能,用串口可打印出位置数据,并可在gpslogger ...

  8. Ndis网卡驱动是如何操控硬件的

    在 DriverEntry 里设置一个_NDIS_MINIPORT_DRIVER_CHARACTERISTICS结构,初作为参数提供给 NdisMRegisterMiniportDriver 函数 N ...

  9. ndis协议驱动开发

    协议驱动的开发流程: 首先,一个协议驱动调用函数ndisRegisterProtocol()先把自己注册为协议驱动,此举的意义告诉windows,我是一个ndis协议驱动,并将约定好的回调函数的列表告 ...

最新文章

  1. GTONE清理维护建议方案
  2. 研究你为啥看着淘宝想剁手,阿里达摩院论文登上NeurIPS 2019
  3. JavaPairRDD方法中几种存储方式的坑
  4. 在EXT中前后台传数据的方式
  5. MySQL(七):InnoDB 自适应Hash索引(Adaptive Hash Index)
  6. Tomcat启动Name or service not known错误解决
  7. IdentityServer4系列 | 简化模式
  8. oracle查看登录时间黑屏,oracle 11g默认用户名、密码解锁 以及安装后重启黑屏问题.doc...
  9. 嵌入式Linux多任务编程 进程 管道 命名管道
  10. C语言根据日期(年,月,日)判断星期几(使用基姆拉尔森计算公式)
  11. 上周的工作总结和下周的学习安排
  12. 基于STM32F103ZET6 HC_SR501人体红外感应
  13. mysql describe 作为字段_mysql中的describe语法 以及查看 当前库中所有表和字段信息...
  14. 人员能力模型及能力培养设计
  15. 研表究明,汉字的序顺并不定一能影阅响读,比如当你看完这句话后,才发这现里的字全是都乱的。...
  16. 如何制作刷爆朋友圈的H5
  17. Li feifei How we're teaching computers to understand pictures
  18. 2022-2028年全球与中国氨(NH3)气体传感器行业发展趋势及投资战略分析
  19. 阿里云服务器部署网站
  20. python实现mysql二叉树_python环境下使用mysql数据及数据结构和二叉树算法(图)...

热门文章

  1. 用python写一个自动注册脚本_js自己写脚本自动操作注册插件基于chrome浏览器
  2. 【转】很有哲理的句子,每天都值得看一遍
  3. HTML的相关标记和属性
  4. 安阳工学院计算机专业宿舍,安阳工学院宿舍条件,宿舍环境图片(10篇)
  5. 压力面试问题——当你和好友,同一天同一家公司面试同一个岗位,你更希望谁入选?3种回答方式解析|智测优聘总结
  6. Android任务栈的理解
  7. Java创建对象方式初谈
  8. java内嵌_Java内嵌类
  9. 在Linux上解压缩和打包WAR文件
  10. 程序员的高效工作场所