码迷,mamicode.com
首页 > 其他好文 > 详细

Raw_Socket原始套接字

时间:2015-09-23 17:15:11      阅读:288      评论:0      收藏:0      [点我收藏+]

标签:protocol   root权限   address   family   数据包   

对于raw socket,只有root权限才能够创建.
raw socket的作用主要有三个方面:
1.通过raw socket来接收发向本机的ICMP,IGMP协议包,或者用来发送这些协议包.
2.接收发向本机但TCP/IP栈不能够处理的IP包:现在许多操作系统在实现网络部分的时候,通常只实现了常用的几种协议,
如tcp,udp,icmp等,但象其它的如ospf,ggp等协议,操作系统往往没有实现,如果
自己有必要编写位于其上的应用,就必须借助raw socket来实现,这是因为操作
系统遇到自己不能够处理的数据包(ip头中的protocol所指定的上层协议不能处
理)就将这个包交给协议对应的raw socket.
3.用来发送一些自己制定源地址等特殊作用的IP包(自己写IP头,TCP头等等)
   

    raw socket的建立是通过如下方式的:
   
    sockfd = socket(PF_INET, SOCK_RAW, protocol);

    第一个参数PF_INET和AF_INET的区别 :指定address family时一般设置为AF_INET,即使用IP;指定protocal family时一般设置PF_INET。
#define AF_INET 0
#define PF_INET AF_INET
所以AF_INET与PF_INET完全一样
第二个参数说明建立的是一个raw socket
第三个参数分三种情况:
    1.参数protocol用来指明所要接收的协议号,如果是象IPPROTO_TCP(6)这
    种非0,非255号的协议,则内核碰到ip头中protocol域和创建socket所使用参
    数protocol相同的IP包,就会交给这个rawsocket来处理.因此,一般说来,
    要想接收什么样的数据包,就应该用参数protocol指定相应的协议.当
    内核向此raw socket交付数据包的时候,是包括整个IP头的,并且已经是重
    组好的IP包. 如下:
    ---------------------------------------------------------------
    |ip header|tcp header(or x header)|             data          |
    ---------------------------------------------------------------        
    用recvfrom收到的数据包括一个IP头,一个相应的协议头,然后是数据(数
    据也可以为空,就看实际情况了). 但当我们发送IP包的时候,却不用亲自
    处理IP包头,只需要填 充参数protocol所指定的相应的协议头即可.也就
    是说,用sendto的时候,我们提供给它的缓冲区数据是从IP包头的第一个字
    节开始,如下,只需要构造这么一个缓冲区就可以了.
    --------------------------------------------------------------
    |tcp header(or udp header or x header)|           data       |
    --------------------------------------------------------------
    如果想自己也想亲自处理IP头,则需要IP_HDRINCL的socket选项.如下:
    int flag = 1;
    setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(int));

    这样,发送时所要提供的缓冲区有成如下形式:
    ---------------------------------------------------------------
    |ip header|tcp header(or x header)|             data          |
    ---------------------------------------------------------------        
    但是,即使是这种情况,在我们发送IP包的时候.也不是填充ip头的所有字
    段,而是应该将ip头的id(identification)字段设置为0,表示让内核来处
    理这个字段.同时,内核还帮你完成ip头的校验和的计算,并随后填充check
    字段.

    2.如果protocol是IPPROTO_RAW(255),这个socket只能用来发送IP包,而不能接收任何数据.发送的数据需要自己填充IP包头,并且自己计算校验和.
    3.如果protocol是IPPROTO_IP(0).在linux和sco unix上是不允许建立的.

 

原始套接字有3种方式创建socket:
  1.socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)发送接收ip数据包
  2.socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))发送接收以太网数据帧
  3.socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))过时了,不要用啊
  理解一下SOCK_RAW的原理, 比如网卡收到了一个 14+20+8+100+4 的udp的以太网数据帧.
  首先,网卡对该数据帧进行硬过滤(根据网卡的模式不同会有不同的动作,
如果设置了promisc混杂模式的话,则不做任何过滤直接交给上一层输入例程,
否则非本机mac或者广播mac会被直接丢弃).按照上面的例子,如果成功的话,
会进入ip输入例程.但是在进入ip输入例程之前,系统会检查系统中是否有通过
socket(AF_PACKET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,系统就给每个这样的socket
接收缓冲区发送一个数据帧拷贝. 然后,进入了ip输入例程,ip层会对该数据包
进行软过滤,就是检查校验或者丢弃非本机ip或者广播ip的数据包等,如果成功的
话会进入udp输入例程.但是在交给udp输入例程之前,系统会检查系统中是否有
通过socket(AF_INET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,
在这个例子中就是需要IPPROTO_UDP类型.系统就给每个这样的socket接收缓冲
区发送一个数据帧拷贝. 最后,进入udp输入例程。

  1. socket(AF_INET, SOCK_RAW, IPPROTO_UDP); -处理网络层的数据
  能:该套接字可以接收协议类型为(tcp udp icmp等)发往本机的ip数据包,从上面看到就是20+8+100.
  不能:不能收到非发往本地ip的数据包(ip软过滤会丢弃这些不是发往本机ip的数据包).
  不能:不能收到从本机发送出去的数据包.
  发送的话需要自己组织tcp udp icmp等头部.可以setsockopt来自己包装ip头部

  2. socket(PF_PACKET, SOCK_RAW, htons(x)); -处理数据链路层的数据
  能: 接收发往本地mac的数据帧
  能: 接收从本机发送出去的数据帧(第3个参数需要设置为ETH_P_ALL)
  能: 接收非发往本地mac的数据帧(网卡需要设置为promisc混杂模式)

  协议类型一共有四个
  ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧
  ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧
  ETH_P_ARP 0x8035 只接受发往本机mac的rarp类型的数据帧
  ETH_P_ALL 0x3 接收发往本机mac的所有类型ip arp rarp的数据帧, 接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧)
  3. socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)),这个一般用于抓包程序。
总结使用方法:
  1.只想收到发往本机某种协议的ip数据包则用第一种就足够了
  2.更多的详细的内容请使用第二种.包括ETH_P_ALL参数和混杂模式都可以使它的能力不断的加强.

 二. 把网卡置为混杂模式
  在正常的情况下,一个网络接口在数据链路层应该只响应两种数据帧:
  一种是与自己硬件地址相匹配的数据帧
  一种是发向所有机器的广播数据帧
  如果要网卡接收所有通过它的数据, 而不管是不是发给它的, 那么必须把网卡置于混杂模式.
 用 Raw Socket 实现代码如下:

    setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag); //设置 IP 头操作选项
    bind(sockRaw, (PSOCKADDR)&addrLocal, sizeof(addrLocal); //把 sockRaw 绑定到本地网卡上
    ioctlsocket(sockRaw, SIO_RCVALL, &dwValue);       //让 sockRaw 接受所有的数据

  flag 标志是用来设置 IP 头操作的, 也就是说要亲自处理 IP 头: bool flag = ture;
  addrLocal 为本地地址: SOCKADDR_IN addrLocal;
  dwValue 为输入输出参数, 为 1 时执行, 0 时取消: DWORD dwValue = 1;

 

下面的程序利用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;
}


2、内核接收网络数据后在rawsocket上处理原则:
(1):对于UDP/TCP产生的IP数据包,内核不将它传递给任何原始套接字,而只是将这些数据交给对应的UDP/TCP数据处理句柄(所以,如果你想要通过原始套接字来访问TCP/UDP或者其它类型的数据,调用socket函数创建原始套接字第三个参数应该指定为htons(ETH_P_IP),也就是通过直接访问数据链路层来实现
(2):对于ICMP和EGP等使用IP数据包承载数据但又在传输层之下的协议类型的IP数据包,内核不管是否已经有注册了的句柄来处理这些数据,都会将这些IP数据包复制一份传递给协议类型匹配的原始套接字.
(3):对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负责处理这些数据,如果有的话,就会将这些IP数据包复制一份传递给匹配的原始套接字,否则,内核将会丢弃这个IP数据包,并返回一个ICMP主机不可达的消息给源主机.
(4):如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如果某个原始套接字没有bind地址,核心就会把收到的所有IP数据包发给这个原始套接字.
(5):如果原始套接字调用了connect函数,则核心只将源地址为connect连接的IP地址的IP数据包传递给这个原始套接字.
(6):如果原始套接字没有调用bind和connect函数,则核心会将所有协议匹配的IP数据包传递给这个原始套接字.

本文出自 “tech记录” 博客,谢绝转载!

Raw_Socket原始套接字

标签:protocol   root权限   address   family   数据包   

原文地址:http://a1liujin.blog.51cto.com/5519498/1697465

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!