这篇文章介绍了Linux下如何实现定制化的类wireshark程序的一些要点(不需要装pcap,利用Linux 的 socket 自带的一些功能)。

Ethernet frame

以太网链路(Ethernet link )上的数据包(data packet)叫作以太网数据包(Ethernet packet),它包含(装载)了以太网帧(Ethernet frame)。

真正通过Linux的Raw Socket得到的Ethernet包是到Ethernet frame这个层次(并且不包含CRC,因为系统会对其进行校验,能传给socket接收缓冲区的都是有效的frame,也不需要用CRC信息了),能否到整个Ethernet packet这个层次没有经过验证,不太清楚。

Ethernet frame前面是preamble 和start frame delimiter (SFD)一共8个字节,属于Ethernet packet 在 physical layer的一部分。而Interpacket gap也不属于Frame的范畴,所以真正有用的信息也就是Frame就够了。可以看到payload 的大小可以到1500 octets(MTU)。可以看如下的表格,加深理解(Markdown编辑器还不太会弄,凑合看吧 - -#):

802.3 Ethernet packet and frame structure:
Layer
Preamble
Start of frame delimiter
MAC destination
MAC source
802.1Q tag(optional)
Ethertype (Ethernet II) or length (IEEE 802.3)
Payload
Frame check sequence (32‑bit CRC)
Interpacket gap
7 octets
1 octet
6 octets
6 octets
(4 octets)
2 octets
46(42)–1500 octets
4 octets
12 octets
Layer 2 Ethernet frame
From MAC destination To Frame check sequence (32‑bit CRC) ← 64–1518(1522) octets →
Layer 1 Ethernet packet
From Preamble To Frame check sequence (32‑bit CRC)← 72–1526(1530) octets →
AF_INET 与 PF_PACKET

平常所用到的网络编程都是在应用层收发数据,某些情况下我们需要执行更底层的操作,比如监听所有本机收发的数据、修改报头等。

通过原始套接字,我们可以抓取本机收到的IP包(包括IP头和TCP/UDP/ICMP包头),也可以抓取本机收到的帧(包括数据链路层协议头)。SOCK_RAW提供了这种可能,利用原始套接字,我们可以自己构造IP头。

有两种原始套接字:

一种是处理IP层即其上的数据,通过指定socket第一个参数为AF_INET来创建这种socket。

另一种是处理数据链路层即其上的数据,通过指定socket第一个参数为AF_PACKET来创建这种socket。

AF_INET表示获取从网络层开始的数据

socket(AF_INET, SOCK_RAW, …)

当接收包时,表示用户获得完整的包含IP报头的数据包,即数据从IP报头开始算起。

当发送包时,用户只能发送包含TCP报头或UDP报头或包含其他传输协议的报文,IP报头以及以太网帧头则由内核自动加封。除非是设置了IP_HDRINCL的socket选项。

如果第二个参数为SOCK_STREAM, SOCK_DGRAM,表示接收的数据直接为应用层数据。

PF_PACKET表示获取的数据是从数据链路层开始的数据

socket(PF_PACKET,SOCK_RAW,htos(ETH_P_IP)):表示获得IPV4的数据链路层帧,即数据包含以太网帧头。14+20+(8:udp 或 20:tcp)。

ETH_P_IP: 在 中定义,可以查看该文件了解支持的其它协议。

SOCK_RAW, SOCK_DGRAM两个参数都可以使用,区别在于使用SOCK_DGRAM收到的数据不包括数据链路层协议头。

socket(AF_INET,SOCK_RAW,IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)发送接收ip数据包。

能:该套接字可以接收协议类型为(tcp udp icmp等)发往本机的ip数据包。

不能:收到非发往本地ip的数据包(ip软过滤会丢弃这些不是发往本机ip的数据包)。

不能:收到从本机发送出去的数据包。

发送的话需要自己组织tcp udp icmp等头部.可以setsockopt来自己包装ip头部。

这种套接字用来写个ping程序比较适合。

socket(PF_PACKET,SOCK_RAW|SOCK_DGRAM,htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))发送接收以太网数据帧。

这种socket可以监听网卡上的所有数据帧。

能: 接收发往本地mac的数据帧

能: 接收从本机发送出去的数据帧(第3个参数需要设置为ETH_P_ALL)

能: 接收非发往本地mac的数据帧(网卡需要设置为promisc混杂模式)

协议类型一共有四个:

ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧

ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧

ETH_P_RARP 0x8035 只接受发往本机mac的rarp类型的数据帧

ETH_P_ALL 0x3 接收发往本机mac的所有类型ip arp rarp的数据帧,

接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧)。

理解一下SOCK_RAW的原理,比如网卡收到了一个 14+20+8+100+4 bytes的udp的以太网数据帧。

首先,网卡对该数据帧进行硬过滤(根据网卡的模式不同会有不同的动作,如果设置了promisc混杂模式的话,则不做任何过滤直接交给下一层输入例程,否则非本机mac或者广播mac会被直接丢弃)。按照上面的ip例子,如果网卡关通过的话,会进入ip输入例程。但是在进入ip输入例程之前,会检查ip是否跟系统相匹配,另外系统会检查数据中是否有该socket所需要的套接字的协议。如果有,系统就给每个这样的socket接收缓冲区发送一个数据拷贝。然后进入下一步,用户便可以使用这块数据。

ps:如果校验和出错,内核会直接丢弃该数据包。不会拷贝给sock_raw的套接字,因为校验和都出错了,数据肯定有问题,其包括所有信息都没有意义了。

Tips:设置网卡混杂模式的命令为: 在终端输入 sudo ifconfig eth0 promisc 并且重新启动电脑之后,网卡就取消了混杂模式,开机需要重新设置它为混杂模式。 可以使用ifconfig 查看eth0是否有了promisc这个混杂模式的标志。 取消混杂模式的命令为:sudo ifconfig eth0 -promisc