RAW SOCKET探索

在Linux实现抓取以太网络上的数据包主要有libpcap,raw socket以及从内核中获取的方式。

我尝试选择使用raw socket来抓取以太网络上的数据包。

一、socket地址域(协议簇)

在Linux的manual中,有两个篇包含socket相关的说明,分别是socket(2)和socket(7),这两篇中的socket定义不完全相同:

socket(2): int socket(int domain, int type, int protocol);

socket(7): mysocket = socket(int socket_family, int socket_type, int protocol);

主要的区别在第一个参数,一个是域(domain),其参数的定义皆为AF_XXX;一个是簇(family),其参数定义皆为PF_XXX。AF与PF的区别一个是地址域,一个是协议簇,从定义上来说这两者是有细微区别,但是从Linux中的定义可以看到这两者是基本上无需区分的(sys/socket.h)。

在较新的socket(2)手册中对域的解释是通信域,在此理解为socket所使用的通信协议吧。

在以太网络的socket通信中,使用了两类协议,AF_INET和AF_PACKET。

  1. AF_INET指代IPv4协议,使用该协议的socket与内核进行网络层的通信,将网络层的数据包发往内核,可以由用户决定IP首部由用户还是socket进行创建。
  2. AF_PACKET指代(Low level packet interface),这是socket(7)中关于AF_PACKET解释的原话,在以太网中自然指代的是以太链路层。

可以理解为使用AF_INET即是对IP层以上的数据进行处理,使用AF_PACKET即是对链路层以上的数据进行处理。

socket的第二个参数是socket_type,其表示在socket所要处理的通信域上使用何种协议进行通信。例如,使用TCP或UDP协议通信即是在AF_INET域上使用SOCK_STREAM或SOCK_DGRAM进行流式或报文式的通信。SOCK_RAW即是与这些通信方式平等的一种,但是操作系统并不帮助用户对数据报进行管理,用户需要自主组建上层或当前层网络协议首部,这就给了用户发送报文更大的自由。

socket的第三个参数在定义里为protocol(协议),在创建TCP或UDP通信socket的过程中,通常将该参数置为0,其含义表示由socket自己选择合适的协议。实际上当使用SOCK_STREAM时socket自动选择了IPPRPTO_TCP,使用SOCK_DGRAM时选择了IPPROTO_UDP。由IPPROTO_XXX可以看出,实际上这是表示IP以上所使用的协议是什么的协议标识,当编写链路层的SOCK_RAW套接字是,我们也需要选择所使用的网络协议标识是ETH_P_IP、ETH_P_ARP、ETH_P_ALL或是其他,前三者即分别表示链路层以上使用IP、ARP或者是全部协议都可以。

  • AF_INET域的SOCK_RAW在 RAW(7) 和 IP(7) 中有较为详细的描述
  • AF_PACKET域的SOCK_RAW在 PACKET(7) 中有较为详细的描述

创建网络层和以太网层的原始套接字语句即为:

socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

AF_INET域SOCK_RAW的默认协议当为IPPROTO_RAW,该socket类型仅用于发送数据包(是否包含IP首部在本文后续进行说明),若需要接收网络层的IP数据包,则考虑使用AF_PACKET域的SOCK_RAW,并将socket的协议设定为ETH_P_IP(参见RAW(7))。

在使用AF_PACKET域的SOCK_RAW进行以太网嗅探时,ETH_P_ALL可替换为ETH_P_IP或其他定义在<if_ether.h>中的网络协议标识来获取特定协议的以太网数据包。

二、设置socket——setsockopt

可以使用 setsockopt 来对许多的socket选项进行设置,同时可以使用 getsockopt 来获取这些选项的值。 setsockopt的定义为:

setsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen)

setsockopt的第二个参数为level,这里我就理解为层,例如SOL_SOCKET表示设置socket层面的选项,而IPPROTO_IP表示设置IP层面的选项。第三个和第四个选项是设置的参数值以及值的长度。

对AF_INET域的SOCK_RAW需要的使用setsockopt进行的设置可能有:

setsockopt(socket_id, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(flag));

该语句由用户定义IP首部,并认为发送的报文是从IP首部开始的。 (在上个语句中,flag=1,注意flag在这里需要是int型/四字节,设置/获取的选项不同,后两个参数也不同,详细参考 setsockopt |如果不设置IP_HDRINCL,则发送的报文内核认为是从IP首部之后开始的,内核会依据源和目的地址添加IP首部后再进行发送)

setsockopt(socket_id, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag));

该语句设置socket可以发送广播报文,否则进行send/sendto时会回复目的地址错误。

setsockopt(iSockId, SOL_SOCKET, SO_BINDTODEVICE, device, IFNAMSIZ);

该语句设置socket所绑定的接口,如果不对特定的接口进行绑定,Linux内核会将所有抓取到的符合socket对应协议的数据包都发送给该socket(参见RAW(7))。使用该语句进行绑定是将socket与特定的接口进行绑定,而是用bind则是将socket与特定的网络地址进行绑定(猜想,如果一块网卡上有多个IP地址,那么使用setsockopt的SO_BINDTODEVICE进行绑定则会获取到该网卡上所有IP地址接收的数据包?)。

对于AF_PACKET域的SOCK_RAW无法使用setsockopt的SO_BINDTODEVICE进行网络接口的绑定。在SOCKET(7)中特别申明,该方法仅对某些类型的socket生效,AF_INET域的socket是其中之一,而AF_PACKET域不生效,可以使用bind进行接口绑定。

三、设置网络接口及获取其信息

在我使用AF_PACKET域socket的过程中,使用了ioctl来对网络接口进行信息获取及设置。

首先使用ioctl获取接口的index信息,以便AF_PACKET域的socket能够与网络接口进行绑定:

struct ifreq ifr_re;    // ifreq结构用于获取接口信息
strncpy(ifr_re.ifr_name, device, IFNAMSIZ);
// 获取指定网卡接口的INDEX
if(ioctl(iSockId, SIOCGIFINDEX, (char*)&ifr_re))
{
    return -1;
}
struct sockaddr_ll RawHWAddr;
memset(&RawHWAddr, 0, sizeof(RawHWAddr));
RawHWAddr.sll_ifindex   = ifr_re.ifr_ifindex;
RawHWAddr.sll_family    = AF_PACKET;
RawHWAddr.sll_protocol  = htons(ETH_P_ALL);
RawHWAddr.sll_hatype    = 0;
RawHWAddr.sll_pkttype   = PACKET_HOST;
RawHWAddr.sll_halen     = ETH_ALEN;
// 需要使用struct sockaddr_ll 结构绑定RawSocket与硬件地址
// 在 packet(7) 的man手册中有说明, For bind only sll_protocol and sll_ifindex are used
if(bind(iSockId, (struct sockaddr*)&RawHWAddr, sizeof(RawHWAddr)))
{
    return -1;
}

再次使用ioctl对网络接口进行设置开启混杂模式,使得AF_PACKET域的socket能够不仅仅抓取到目的地址为与之绑定的网络接口的数据包,而能够获取到网卡加入的链路上的所有数据包:

struct ifreq ifr_re;    // ifreq结构用于获取接口信息
strncpy(ifr_re.ifr_name, device, IFNAMSIZ);
if(ioctl(iSockId, SIOCGIFFLAGS, (char*)&ifr_re))
{
    return -1;
}
ifr_re.ifr_flags |= IFF_PROMISC;
if(ioctl(iSockId, SIOCSIFFLAGS, (char*)&ifr_re))
{
    return -1;
}

将AF_PACKET域的socket与接口绑定并开启混杂模式后,就能够在特定接口上抓取以太网数据包了。

原文链接:https://my.oschina.net/ureyishere/blog/628844

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值