Home linux网络专题 Linux网络安全 NAT在NDIS中间层驱动中的实现
NAT在NDIS中间层驱动中的实现
创建时间:2006-06-17文章属性:原创文章提交: (thinkingfh_at_163.com)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;                    //全局变量,转发表的头结点NTSTATUSDriverEntry(    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 对收到的数据包的过滤和转发INTPtReceivePacket(    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中间层驱动程序扩展"        Jesús O "开发Windows 2000/XP下的防火墙"

Tags: nat  ndis  间层  中间层  驱动  实现  



Shares:Google书签Yahoo书签雅虎收藏夹365Key网摘新浪ViVi百度收藏天极网摘diglog和讯网摘POCO网摘YouNote网摘博拉网天下图摘 spurl blogmarksBlinkListredditdiggDel.icio.us