Linux PF_PACKET 和 SOCK_RAW
SOCK_RAW
关于 SOCK_RAW 的详细内容,参见man 7 raw
,简单来说就是
raw_socket = socket(AF_INET, SOCK_RAW, int protocol);
可以创建一个 raw socket,可以发送纯 IP 报文,如果protocol
设置为IPPROTO_RAW
,报文所有细节,包括首部都需要自己填写,可以使用netinet/ip.h
头文件中的struct ip
等结构体,如果要发送 ICMP 报文,可以设置protocol
为IPPROTO_ICMP
,参见netinet/ip_icmp.h
。
但是,根据man
文档所说,IPPROTO_RAW 和 IPPROTO_ICMP 都是只能发送不能接收的,要接收的话,还得使用
sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
PA_PACKET
详情参见man 7 packet
,下面是我自己写的一个简单抓包程序,为了方便演示,只写出解析 ICMP_ECHO 和 ICMP_ECHOREPLY 的代码,而且只能处理 IPv4:
#include "all.h"
int main(int argc, char **argv)
{
int n, fd, addr, iphdrlen, ethhdrlen;
char buf[MAXLINE];
struct sockaddr_in sin;
struct icmp *icmp;
struct ip *ip;
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
if (fd < 0)
errsys("socket error");
ethhdrlen = sizeof(struct ethhdr); /* 链路层首部长度 */
memset(&sin, 0, sizeof(struct sockaddr_in));
inet_pton(AF_INET, "127.0.0.1", &addr);
sin.sin_family = AF_PACKET;
sin.sin_addr.s_addr = addr;
again:
n = read(fd, buf, MAXLINE);
if (n < 0)
errsys("Recvived form ether error\n");
ip = (struct ip *) (buf + ethhdrlen); /* 跳过链路层首部 */
iphdrlen = ip->ip_hl << 2; /* IP 首部长度字段乘以 4,得到首部字节数 */
printf("Received %d bytes data, protocol %s\n",
n - ethhdrlen - iphdrlen, /* IP 报文承载的长度 */
(ip->ip_p == IPPROTO_UDP) ? "UDP" :
(ip->ip_p == IPPROTO_TCP) ? "TCP" :
(ip->ip_p == IPPROTO_ICMP) ? "ICMP" : "Unknown"
);
/* 跳过链路层和 IP 首部,得到 ICMP 报文地址 */
icmp = (struct icmp *) (buf + ethhdrlen + iphdrlen);
if ((icmp->icmp_type != ICMP_ECHO) &&
(icmp->icmp_type != ICMP_ECHOREPLY)) {
;
} else {
printf("type %d, code %x, checksum %x, id %hu, sequence %hu\n",
icmp->icmp_type, icmp->icmp_code, icmp->icmp_cksum,
icmp->icmp_id, icmp->icmp_seq);
}
goto again;
return 0;
}
默认情况下,所有符合protocol
的链路层报文都会在传给内核之前传给PF_PACKET
,如果设置ETH_P_ALL
,就会收到所有的报文了,所以就可以很容易地做一个抓包程序。
然后,是一些解析包必要的头文件,linux/udp.h
,linux/tcp.h
,netinet/ip.h
,netinet/ip_icmp.h
,linux/if_ether.h
。
ETH_P_ALL
ETH_P_ALL
和ETH_P_IP
的区别,Google 一下一大堆,就不再赘述了。一个要点就是,一定记得加 htons(),为什么呢?因为 802.2 和 802.3 定义的帧使用两个字节表示协议类型,而 IP 数据报使用一个字节表示上层协议,所以就不用管字节序了。
struct sockaddr_ll
因为我要接收的仅仅是 IP 数据报,所以就使用了标准的 sockaddr_in 结构体,如果要做很强的设备相关的编程,建议详细阅读man
文档。