• 原始套接字基础(原始套接字系列二)


    在进入Raw Socket多种强大的应用之前,我们先讲解怎样建立一个Raw Socket及怎样用建立的Raw Socket发送和接收IP包。

      建立Raw Socket

      在Windows平台上,为了使用Raw Socket,需先初始化WINSOCK:

    // 启动 Winsock
    WSAData wsaData;
    if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0)
    {
     cerr << "Failed to find Winsock 2.1 or better." << endl;
     return 1;
    }


      MAKEWORD(2, 1)组成一个版本字段,2.1版,同样的,MAKEWORD(2, 2)意味着2.2版。MAKEWORD本身定义为:

    inline word MakeWord(const byte wHigh, const byte wLow)
    {
     return ((word)wHigh) << 8 | wLow;
    }


      因此MAKEWORD(2, 1)实际等同于0x0201。同样地,0x0101可等同于MAKEWORD(1, 1)。

      与WSAStartup()的函数为WSACleanup(),在所有的socket都使用完后调用,如:

    void sock_cleanup()
    {
     #ifdef WIN32
      sockcount--; 
      if (sockcount == 0)
       WSACleanup();
     #endif
    }


      接下来,定义一个Socket句柄:

    SOCKET sd; // RAW Socket句柄


      创建Socket并将句柄赋值给定义的sd,可以使用WSASocket()函数来完成,其原型为:

    SOCKET WSASocket(int af, int type, int protocol, LPWSAPROTOCOL_INFO
    lpProtocolInfo, GROUP g, DWORD dwFlags);


      其中的参数定义为:

      af:地址家族,一般为AF_INET,指代IPv4(The Internet Protocol version 4)地址家族。

      type:套接字类型,如果创建原始套接字,应该使用SOCK_RAW;

      Protocol:协议类型,如IPPROTO_TCP、IPPROTO_UDP等;

      lpProtocolInfo :WSAPROTOCOL_INFO结构体指针;

      dwFlags:套接字属性标志。

      例如,下面的代码定义ICMP协议类型的原始套接字:

    sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0);


      创建Socket也可以使用socket()函数:

    SOCKET WSAAPI socket( int af, int type, int protocol);


      参数的定义与WSASocket()函数相同。

      为了使用socket()函数创建的Socket,还需要将这个Socket与sockaddr绑定:

    SOCKADDR_IN addr_in;

    addr_in.sin_family = AF_INET;
    addr_in.sin_port = INADDR_ANY;
    addr_in.sin_addr.S_un.S_addr = GetLocalIP();

    nRetCode = bind(sd, (struct sockaddr*) &addr_in, sizeof(addr_in));
    if (SOCKET_ERROR == nRetCode)
    {
     printf("BIND Error!%d ", WSAGetLastError());
    }


      其中使用的struct sockaddr_in(即SOCKADDR_IN)为:

    struct sockaddr_in
    {
     unsigned short sin_family;
     unsigned short int sin_port;
     struct in_addr sin_addr;
     unsigned char sin_zero[8];
    }


      而bind()函数第二个参数的struct sockaddr类型定义为:

    struct sockaddr
    {
     unisgned short as_family;
     char sa_data[14];
    };


      实际上,bind()函数采用struct sockaddr是为了考虑兼容性,最终struct sockaddr和struct sockaddr_in的内存占用是等同的。struct sockaddr_in中的struct in_addr成员占用4个字节,为32位的IP地址,定义为:

    typedef struct in_addr
    {
     union
     {
      struct
      {
       u_char s_b1, s_b2, s_b3, s_b4;
      } S_un_b;
      struct
      {
       u_short s_w1, s_w2;
      } S_un_w;
      u_long S_addr;
     }
     S_un;
    } IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;


      把32位的IP地址定义为上述联合体将使用户可以以字节、半字或字方式读写同一个IP地址。同志们,注意了,这个技巧在许多软件开发中定义数据结构时被广泛采用。

      为了控制包的发送方式,我们可能会用到如下的这个十分重要的函数来设置套接字选项:

    int setsockopt(
     SOCKET s, //套接字句柄
     int level, //选项level,如SOL_SOCKET
     int optname, //选项名,如SO_BROADCAST
     const char* optval, //选项值buffer指针
     int optlen //选项buffer长度
    );


      例如,当level为SOL_SOCKET时,我们可以设置布尔型选项SO_BROADCAST从而控制套接字是否传送和接收广播消息。

      下面的代码通过设置IPPROTO_IP level的IP_HDRINCL选项为TRUE从而使能程序员亲自处理IP包报头

    //设置 IP 头操作选项
    BOOL flag = TRUE;
    setsockopt(sd, IPPROTO_IP, IP_HDRINCL, (char*) &flag, sizeof(flag);


      下面的函数用于控制套接字:

    int ioctlsocket(
     SOCKET s,
     long cmd, //命令
     u_long* argp //命令参数指针
    );


      如下面的代码让socket接收所有报文(sniffer模式):

    u_long iMode = 1;
    ioctlsocket(sd, SIO_RCVALL, & iMode); //让 sockRaw 接受所有的数据

    Raw Socket发送报文

      发送报文的函数为:

    int sendto(
     SOCKET s, //套接字句柄
     const char* buf, //发送缓冲区
     int len, //要发送的字节数
     int flags, //方式标志
     const struct sockaddr* to, //目标地址
     int tolen //目标地址长度
    );


      或

    int send(
     SOCKET s, //已经建立连接的套接字句柄
     const char* buf,
     int len,
     int flags
    );


      send()函数的第1个参数只能是一个已经建立连接的套接字句柄,所以这个函数就不再需要目标地址参数输入。

      函数的返回值为实际发送的字节数,如果返回SOCKET_ERROR,可以通过WSAGetLastError()获得错误原因。请看下面的示例:

    Raw Socket接收报文

      接收报文的函数为:

    int recvfrom(
     SOCKET s, //套接字句柄
     char* buf, //接收缓冲区
     int len, //缓冲区字节数
     int flags, //方式标志
     struct sockaddr* from, //源地址
     int* fromlen 
    );


      或

    int recv(
     SOCKET s, //已经建立连接的套接字句柄
     char* buf,
     int len,
     int flags
    );


      recv()函数的第1个参数只能是一个已经建立连接的套接字句柄,所以这个函数就不再需要源地址参数输入。

      函数的返回值为实际接收的字节数,如果返回SOCKET_ERROR,我们可以通过WSAGetLastError()函数获得错误原因。请看下面的示例:

    int bread = recvfrom(sd, (char*)recv_buf, packet_size + sizeof(IPHeader), 0,
    (sockaddr*) &source, &fromlen);
    if (bread == SOCKET_ERROR)
    {
     //…读失败
     if(WSAGetLastError()==WSAEMSGSIZE)
     {
      //…接收buffer太小
     }
     return - 1;
    }


      原始套接字按如下规则接收报文:若接收的报文中协议类型和定义的原始套接字匹配,那么,接收的所有数据拷贝入套接字中;如果套接字绑定了本地地址,那么只有接收数据IP头中对应的目的地址等于本地地址,接收到的数据才拷贝到套接字中;如果套接字定义了远端地址,那么,只有接收数据IP头中对应的源地址与远端地址匹配,接收的数据才拷贝到套接字中。

    建立报文

      在利用Raw Socket发送报文时,报文的IP头、TCP头、UDP头等需要程序员亲自赋值,从而达到极大的灵活性。下面的程序利用Raw Socket发送TCP报文,并完全手工建立报头:

    int sendTcp(unsigned short desPort, unsigned long desIP)
    {
     WSADATA WSAData;
     SOCKET sock;
     SOCKADDR_IN addr_in;
     IPHEADER ipHeader;
     TCPHEADER tcpHeader;
     PSDHEADER psdHeader;

     char szSendBuf[MAX_LEN] = { 0 };
     BOOL flag;
     int rect, nTimeOver;

     if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
     {
      printf("WSAStartup Error! ");
      return false;
     }

     if ((sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0,
    WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
     {
      printf("Socket Setup Error! ");
      return false;
     }
     flag = true;
     if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*) &flag, sizeof(flag)) ==SOCKET_ERROR)
     {
      printf("setsockopt IP_HDRINCL error! ");
      return false;
     }

     nTimeOver = 1000;
     if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*) &nTimeOver, sizeof
    (nTimeOver)) == SOCKET_ERROR)
     {
      printf("setsockopt SO_SNDTIMEO error! ");
      return false;
     }
     addr_in.sin_family = AF_INET;
     addr_in.sin_port = htons(desPort);
     addr_in.sin_addr.S_un.S_addr = inet_addr(desIP);

     //填充IP报头
     ipHeader.h_verlen = (4 << 4 | sizeof(ipHeader) / sizeof(unsigned long));
     // ipHeader.tos=0;
     ipHeader.total_len = htons(sizeof(ipHeader) + sizeof(tcpHeader));
     ipHeader.ident = 1;
     ipHeader.frag_and_flags = 0;
     ipHeader.ttl = 128;
     ipHeader.proto = IPPROTO_TCP;
     ipHeader.checksum = 0;
     ipHeader.sourceIP = inet_addr("localhost");
     ipHeader.destIP = desIP;

     //填充TCP报头
     tcpHeader.th_dport = htons(desPort);
     tcpHeader.th_sport = htons(SOURCE_PORT); //源端口号
     tcpHeader.th_seq = htonl(0x12345678);
     tcpHeader.th_ack = 0;
     tcpHeader.th_lenres = (sizeof(tcpHeader) / 4 << 4 | 0);
     tcpHeader.th_flag = 2; //标志位探测,2是SYN 
     tcpHeader.th_win = htons(512);
     tcpHeader.th_urp = 0;
     tcpHeader.th_sum = 0;

     psdHeader.saddr = ipHeader.sourceIP;
     psdHeader.daddr = ipHeader.destIP;
     psdHeader.mbz = 0;
     psdHeader.ptcl = IPPROTO_TCP;
     psdHeader.tcpl = htons(sizeof(tcpHeader));

     //计算校验和
     memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));
     memcpy(szSendBuf + sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader));
     tcpHeader.th_sum = checksum((unsigned short*)szSendBuf, sizeof(psdHeader) + sizeof
    (tcpHeader));
     
     memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
     memcpy(szSendBuf + sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader));
     memset(szSendBuf + sizeof(ipHeader) + sizeof(tcpHeader), 0, 4);
     ipHeader.checksum = checksum((unsigned short*)szSendBuf, sizeof(ipHeader) + sizeof
    (tcpHeader));

     memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));

     rect = sendto(sock, szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader), 0,
    (struct sockaddr*) &addr_in, sizeof(addr_in));
     if (rect == SOCKET_ERROR)
     {
      printf("send error!:%d ", WSAGetLastError());
      return false;
     }
     else
      printf("send ok! ");

     closesocket(sock);
     WSACleanup();

     return rect;
    }

    int bwrote = sendto(sd, (char*)send_buf, packet_size, 0, (sockaddr*) &dest,
    sizeof(dest));
    if (bwrote == SOCKET_ERROR)
    {
     //…发送失败
     if(WSAGetLastError()==…)
     {
      //…
     }
     return - 1;
    }
    else if (bwrote < packet_size)
    {
     //…发送字节 < 欲发送字节
    }

  • 相关阅读:
    【转】GitHub 中国区前 100 名到底是什么样的人?
    不同服务器数据库之间的数据操作
    行列互换
    千万级数据查询
    用命令对sql进行备份
    通过SQL Server 2008数据库复制实现数据库同步备份
    各种字符串合并处理示例.
    字符串分解
    四大排序函数
    cross apply 和 outer apply
  • 原文地址:https://www.cnblogs.com/happy-pm/p/3809429.html
Copyright © 2020-2023  润新知