linux原始套接字raw_socket

一.原始套接字的用途:

通常情况下程序员接所接触到的套接字(Socket)为两类:
(1).流式套接字(SOCK_STREAM):  一种面向连接的 Socket,针对于面向连接的TCP 服务应用;
(2).数据报式套接字(SOCK_DGRAM):  一种无连接的 Socket,对应于无连接的 UDP 服务应用。

从用户的角度来看,SOCK_STREAM、SOCK_DGRAM 这两类套接字似乎的确涵盖了 TCP/IP 应用的全部,因为基于 TCP/IP 的应用,从协议栈的层次上讲,在传输层的确只可能建立于 TCP 或 UDP 协议之上,而 SOCK_STREAM、SOCK_DGRAM 又分别对应于 TCP 和 UDP,所以几乎所有的应用都可以用这两类套接字实现

但是,当我们面对如下问题时,SOCK_STREAM、SOCK_DGRAM 将显得这样无助:

(1)怎样发送一个自定义的 IP 包?
(2)怎样发送一个 ICMP 协议包?
(3)怎样分析所有经过网络的包,而不管这样包是否是发给自己的?
(4)怎样伪装本地的 IP 地址?

 这使得我们必须面对另外一个深刻的主题---原始套接字(SOCK_RAW)。原始套接字广泛应用于高级网络编程,也是一种广泛的黑客手段。著名的网络sniffer(一种基于被动侦听原理的网络分析方式),拒绝服务攻击(DOS),IP 欺骗等都可以通过原始套接字实现。

原始套接字(SOCK_RAW)可以用来自行组装数据包,可以接收本机网卡上所有的数据帧(数据包),对于监听网络流量和分析网络数据很有作用。

原始套接字是基于 IP 数据包的编程(SOCK_PACKET 是基于数据链路层的编程)。另外,必须在管理员权限下才能使用原始套接字

原始套接字(SOCK_RAW)与标准套接字(SOCK_STREAM、SOCK_DGRAM)的区别在于原始套接字直接置“根”于操作系统网络核心(Network Core),而  SOCK_STREAM、SOCK_DGRAM 则“悬浮”于 TCP 和 UDP 协议的外围。

 

流式套接字只能收发 TCP 协议的数据,数据报套接字只能收发 UDP 协议的数据,原始套接字可以收发内核没有处理的数据包。

 

二.原始套接字编程:

原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据。区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧(数据包)。另外,必须在管理员权限下才能使用原始套接字。

(1).原始套接字的创建

int socket ( int family, int type, int protocol );

参数:

family:协议族 这里写 PF_PACKET

type:  套接字类,这里写 SOCK_RAW

protocol:协议类别,指定可以接收或发送的数据包类型,不能写 “0”,取值如下,注意,传参时需要用 htons() 进行字节序转换。

    ETH_P_IP:IPV4数据包

    ETH_P_ARP:ARP数据包

    ETH_P_ALL:任何协议类型的数据包

返回值:

成功( >0 ):套接字,这里为链路层的套接字

失败( <0 ):出错

 

(2).获取链路层的数据包

ssize_t recvfrom(  int sockfd, void *buf, size_t nbytes,int flags,struct sockaddr *from, socklen_t *addrlen );

参数:

sockfd:原始套接字

buf:接收数据缓冲区

nbytes:接收数据缓冲区的大小

flags:套接字标志(常为0)

from:这里没有用,写 NULL

addrlen:这里没有用,写 NULL

返回值:

成功:接收到的字符数

失败:-1

 

(3).混杂模式

 默认的情况下,我们接收数据,目的地址是本地地址,才会接收。有时候我们想接收所有经过网卡的所有数据流,而不论其目的地址是否是它,这时候我们需要设置网卡为混杂模式

网卡的混杂模式一般在网络管理员分析网络数据作为网络故障诊断手段时用到,同时这个模式也被网络黑客利用来作为网络数据窃听的入口。在 Linux 操作系统中设置网卡混杂模式时需要管理员权限。在 Windows 操作系统和 Linux 操作系统中都有使用混杂模式的抓包工具,比如著名的开源软件 Wireshark。

通过命令给 Linux 网卡设置混杂模式(需要管理员权限)

设置混杂模式:ifconfig eth0 promisc

取消混杂模式:ifconfig eth0 -promisc

 

 通过代码给 Linux 网卡设置混杂模式

(4).发送自定义的数据包:

ssize_t sendto (int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *to,socklen_t addrlen);

 参数:

sockfd:原始套接字

buf:发送数据缓冲区

nbytes:发送数据缓冲区的大小

flags:一般为 0

to:本机网络接口,指发送的数据应该从本机的哪个网卡出去,而不是以前的目的地址

addrlen:to 所指向内容的长度

返回值:

成功:发送数据的字符数

失败: -1

本机网络接口的定义:

 

(5).接收的链路层数据包

MAC 头部(有线局域网)

注意:CRC、PAD 在组包时可以忽略

 

以太网头部封装的结构体(所需要头文件:#include <net/ethernet.h>)

 

示例代码如下:

 1 /*
 2  * raw_socket_recvdata.c
 3  * Created on: Oct 26, 2016
 4  * Author: zhangming
 5  */
 6 #include <stdio.h>
 7 #include <string.h>
 8 #include <stdlib.h>
 9 #include <sys/socket.h>
10 #include <netinet/in.h>
11 #include <arpa/inet.h>
12 #include <netinet/ether.h>
13 #include <net/ethernet.h>         // 以太网头部 头文件
14 #include <netinet/ip.h>           // ip头部 头文件
15 //#include <net/if_arp.h>         // arp头部 头文件
16 
17 //struct ether_header{
18 //    u_int8_t  ether_dhost[ETH_ALEN];    /* 目的MAC地址 */
19 //    u_int8_t  ether_shost[ETH_ALEN];    /* 源MAC地址     */
20 //    u_int16_t ether_type;                /* 帧类型(IP数据包,ARP数据包,RARP数据包) */
21 //};
22 
23 int main(int argc,char *argv[])
24 {
25     int i = 0;
26     unsigned char buf[1024] = "";
27     int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
28     while(1)
29     {
30         unsigned char src_mac[18] = "";
31         unsigned char dst_mac[18] = "";
32         //获取链路层的数据帧
33         recvfrom(sock_raw_fd, buf, sizeof(buf),0,NULL,NULL);
34 
35         //从数据中提取mac首部信息(14个字节)
36         struct ether_header *ethdr = NULL;
37         ethdr = (struct ether_header *)buf;
38 
39         //从buf里提取目的mac、源mac
40         sprintf(dst_mac,"%02x:%02x:%02x:%02x:%02x:%02x", ethdr->ether_dhost[0], ethdr->ether_dhost[1],ethdr->ether_dhost[2],ethdr->ether_dhost[3],ethdr->ether_dhost[4],ethdr->ether_dhost[5]);
41         sprintf(src_mac,"%02x:%02x:%02x:%02x:%02x:%02x", ethdr->ether_shost[0], ethdr->ether_shost[1],ethdr->ether_shost[2],ethdr->ether_shost[3],ethdr->ether_shost[4],ethdr->ether_shost[5]);
42 
43         //判断是否为IP数据包
44         if( 0x0800 == ntohs(ethdr->ether_type) )
45         {
46             printf("______________IP数据报_______________\n");
47             printf("MAC:%s >> %s\n",src_mac,dst_mac);
48 
49         }//0x0806为ARP数据包,0x8035为RARP数据包
50         else if( 0x0806 == ntohs(ethdr->ether_type) || 0x8035 == ntohs(ethdr->ether_type) )
51         {
52             printf("______________ARP数据报_______________\n");
53             printf("MAC:%s >> %s\n",src_mac,dst_mac);
54         }
55 
56     }
57     return 0;
58 }

 

运行结果截图:

posted @ 2016-10-27 13:39  水火379  阅读(2832)  评论(0)    收藏  举报