对于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数据包传递给这个原始套接字.