`
mjluy10w
  • 浏览: 8100 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

原始套接字透析之Raw Socket基础(转自www.cctry.com管理员)

 
阅读更多

原始套接字透析之Raw Socket基础(转自www.cctry.com管理员)
2011年05月31日
  原始套接字透析之Raw Socket基础
  在进入Raw Socket多种强大的应用之前,我们先讲解怎样建立一个Raw Socket及怎样用建立的Raw Socket发送和接收IP包。
    建立Raw Socket
    在Windows平台上,为了使用Raw Socket,需先初始化WINSOCK:
  // 启动 Winsock
  WSAData wsaData;
  if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0)
  {
   cerr 一个版本字段,2.1版,同样的,MAKEWORD(2, 2)意味着2.2版。MAKEWORD本身定义为:
  inline word MakeWord(const byte wHigh, const byte wLow)
  {
   return ((word)wHigh) 函数为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\n", 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()获得错误原因。请看下面的示例:
  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 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!\n");
    return false;
   }
   if ((sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0,
  WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
   {
    printf("Socket Setup Error!\n");
    return false;
   }
   flag = true;
   if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*) &flag, sizeof(flag)) ==SOCKET_ERROR)
   {
    printf("setsockopt IP_HDRINCL error!\n");
    return false;
   }
   nTimeOver = 1000;
   if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*) &nTimeOver, sizeof
  (nTimeOver)) == SOCKET_ERROR)
   {
    printf("setsockopt SO_SNDTIMEO error!\n");
    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\n", WSAGetLastError());
    return false;
   }
   else
    printf("send ok!\n");
   closesocket(sock);
   WSACleanup();
   return rect;
  }
分享到:
评论

相关推荐

    visual C++ 编程技巧精选集1(罗斌)

    全部VC++例子1035个,vc++2005编译环境。实用。

    Visual C++ 编程技巧精选500例

    Visual C++ 编程技巧精选500例 Visual C++ 编程技巧精选500例 Visual C++ 编程技巧精选500例 Visual C++ 编程技巧精选500例 Visual C++ 编程技巧精选500例

    侯捷深入浅出MFC(VC++)

    关于C++学习的好资料,对于初学者来说,深入浅出,容易理解

    VCpp6sp6_Plus_元旦特别版.part1

    在安装目录下有个mytools文件夹,里面放的是我写的VC文件关联助手(用来给绿色版的VC添加文件关联,其关联是和原版的关联一样的,关联的类型包括.dsw,.dsp,.rc,.h,.c,.cpp),还有一个是来自www.cctry.com的VC辅助...

    VCpp6sp6_Plus_元旦特别版.part2

    在安装目录下有个mytools文件夹,里面放的是我写的VC文件关联助手(用来给绿色版的VC添加文件关联,其关联是和原版的关联一样的,关联的类型包括.dsw,.dsp,.rc,.h,.c,.cpp),还有一个是来自www.cctry.com的VC辅助...

    VCpp6sp6_虎年献礼.part1

    在安装目录下有个mytools文件夹,里面放的是我写的VC文件关联助手(用来给绿色版的VC添加文件关联,其关联是和原版的关联一样的,关联的类型包括.dsw,.dsp,.rc,.h,.c,.cpp),还有一个是来自www.cctry.com的VC辅助...

    vc++打造服务管理工具(简易版)

    vc++打造服务管理工具(简易版) 运用vs2008 代码简单 欢迎和大家交流 vc驿站网址 希望大家交流 http://www.cctry.com/?fromuser=xxtext

    C++项目实例案例

    C++项目实例案例,本资源提供29个项目开发实例,用于提高vc++编程能力(密码:www.cctry.com)

    VCpp6sp6_虎年献礼.part2

    括.dsw,.dsp,.rc,.h,.c,.cpp),还有一个是来自www.cctry.com的VC辅助工具 ,能设置的地方很多,是个不错的辅助工具。 添加了多个工程向导,包括汇编(可直接编译)和驱动向导(需要自己安装DDK) 更多功能,还是自己...

    PE 查看修改器 0.1... 原创

    http://www.cctry.com/thread-34912-1-1.html 这里有截图。。 声明是原创 写得不怎么样 适合学习PE格式的同学看看。。。。 高手就不要踩我们这些小鸟了。。

    CcTry[1].CoM.rar_Tabú_tabcontrol控件的讲解

    这个教程是关于Tab control的动画教程,很详细演示和讲解了该控件的使用方法

    VC驿站基础班无KEY高清C++教程下载地址

    ①、VC中常用控件的用法讲解见:http://www.cctry.com/thread-106-1-1.html ②、VS2008中新增控件的用法讲解。 6、定时器讲解 ①、定时器相关函数讲解:SetTimer、OnTimer、KillTimer; ②、利用定时器实现动态...

Global site tag (gtag.js) - Google Analytics