Raw Socket 之网络层数据获取

22 篇文章 0 订阅
文章介绍了如何通过tcpdump生成过滤规则来捕获网络层数据,并结合RawSocket避免端口不可达问题,从而识别广播包和单播包。在正常UDP通信的基础上,利用setsockopt设置过滤器,并通过SOCK_RAW和IPPROTO_UDP组合来抓取和分析包内容。
摘要由CSDN通过智能技术生成

简介

针对网络包,我们一般的发送接收直接使用的是应用层,此时无法分辨接收为广播包还是单播包,为了能够分辨出接收到的是否为广播包,需要接收数据链路层的数据或者网络层的数据。

原理啥的就不复制了,可看参考链接,也可自行搜索。。。------ 》》》

直接上正题

正常使用

我们正常的 udp 通信如下:

	int fd = socket(AF_INET, SOCK_DGRAM|SOCK_NONBLOCK, 0);
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
	sin.sin_addr.s_addr = INADDR_ANY;
	bind(fd, (struct sockaddr *) &sin, sizeof(sin));

tcpdump

需要获取网络层数据,需要使用抓包过滤规则

setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));

bpf 为定义在 <linux/filter.h>

struct sock_filter {    /* Filter block */
        __u16   code;   /* Actual filter code */
        __u8    jt;     /* Jump true */
        __u8    jf;     /* Jump false */
        __u32   k;      /* Generic multiuse field */
};

struct sock_fprog {                     /* Required for SO_ATTACH_FILTER. */
        unsigned short             len; /* Number of filter blocks */
        struct sock_filter __user *filter;
};

生成过滤规则可使用 tcpdump 命令,如下:

$ sudo tcpdump udp and dst  port 8888  -d
(000) ldh      [12]                             //接收包的第12字节处,16位
(001) jeq      #0x86dd          jt 2	jf 6    //判断第12字节处是否等于0x86dd,满足跳到(002),不满足跳到(006)行
(002) ldb      [20]
(003) jeq      #0x11            jt 4	jf 15
(004) ldh      [56]
(005) jeq      #0x22b8          jt 14	jf 15
(006) jeq      #0x800           jt 7	jf 15  //判断第12字节处是否等于0x800,满足跳到(007),不满足跳到(015)行
(007) ldb      [23]							//接收包的第23字节处,8位
(008) jeq      #0x11            jt 9	jf 15 //判断第23字节处是否等于0x11,满足跳到(009),不满足跳到(015)行
(009) ldh      [20]							//接收包的第20字节处,16位
(010) jset     #0x1fff          jt 15	jf 11
(011) ldxb     4*([14]&0xf)	       //接收包的第14字节处,8位,取低四位,然后乘4,抓包看此处一般为 0x45,计算得 20
(012) ldh      [x + 16]           //接收包的第x+16字节处,16位,x在上面计算得20,故为第36字节处
(013) jeq      #0x22b8          jt 14	jf 15  //判断第36字节处是否等于0x22b8,满足跳到(014),不满足跳到(015)行
(014) ret      #262144            //满足,显示
(015) ret      #0                 //不满足,过滤

在这里插入图片描述

在这里插入图片描述

程序与分析

上面生成的主要是数据链路层起始数据,而区分广播包,获取网络层数据即可,上面生成过于复杂,可简化判断接收数据端口号,再加上网络层第一二字节判断

{ 0x28, 0, 0, 0x00000000 },
{ 0x15, 0, 3, 0x00004500 },
{ 0x28, 0, 0, 0x0000016 },
{ 0x15, 0, 1, 0x000022B8 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },

在使用的过程中,发现:若只使用 SOCK_RAW 获取网络层包,则会出现端口不可达
ICMP Destination unreachable (Port unreachable)

在这里插入图片描述

故而推测 socket(AF_INET, SOCK_RAW|SOCK_NONBLOCK, IPPROTO_UDP ) 使用时,不会打开端口,但数据收发也都正常,就是多了一条 ICMP,故而本文做法,打开所在端口,就不会出现端口不可达了,SOCK_RAW主要用于报文的抓取,在内核获取到报文后,会复制一份给 SOCK_RAW 套接字,另一个打开所在端口也可正常接收,例子如下:

#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include<memory.h>
#include<stdlib.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h> // sockaddr_ll
#include<arpa/inet.h>
#include<netinet/if_ether.h>
#include<errno.h>
#include <linux/filter.h>

int main(int argc, char *argv[])
{
	//内核会把数据给每一个socket拷贝一份
	uint8_t buf[1024] = { 0 };
	uint8_t buf2[1024] = { 0 };
	int len = 0,len2 = 0;
	int fd, fd2;
	struct sockaddr_in client_addr, client_addr2;
	socklen_t addrlen = sizeof(client_addr);
	//fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));

	fd = socket(AF_INET, SOCK_RAW|SOCK_NONBLOCK, IPPROTO_UDP );
    struct sock_filter bpf_code[] = {
        { 0x28, 0, 0, 0x00000000 },
        { 0x15, 0, 3, 0x00004500 },
        { 0x28, 0, 0, 0x0000016 },
        { 0x15, 0, 1, 0x000022B8 },
        { 0x6, 0, 0, 0x00040000 },
        { 0x6, 0, 0, 0x00000000 },
    };
    struct sock_fprog bpf;
    memset(&bpf,0x00,sizeof(bpf));

    bpf.len = sizeof(bpf_code) / sizeof(struct sock_filter);

    bpf.filter = bpf_code;

    int ret = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));

    if (ret < 0) {
		printf("setsockopt:SO_ATTACH_FILTER>>>>error:%s\n",strerror(errno));
    }
	/*
	如果IP_HDRINCL未开启,由进程让内核发送的数据是从IP首部之后的第一个字节开始的,内核会自动构造合适的IP
	如果IP_HDRINGL开启,进行需要自行构造IP包
	*/
	/*
	int  one = 1;
	const int *val = &one;
	if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, val, sizeof(int))) {
		perror("setsockopt() error");
		exit(-1);
	}
	*/
	
	fd2 = socket(AF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_UDP);
	struct sockaddr_in addrServ;
	addrServ.sin_addr.s_addr= htonl(0);//指定0.0.0.0地址,表示任意地址0xC0A8012e
	addrServ.sin_family = AF_INET;//表示IPv4的套接字类型
	addrServ.sin_port = htons(8888);
	bind(fd2, (struct sockaddr*)&addrServ, sizeof(addrServ));
	
	while (1) {
		if ((len = recvfrom(fd, buf, sizeof(buf), 0,(struct sockaddr *)&client_addr, &addrlen)) == -1){
			//printf("recvfrom failed ! error message : %s\n", strerror(errno));
			continue;
		}

		if ((len2 = recvfrom(fd2, buf2, sizeof(buf2), 0, (struct sockaddr *)&client_addr2, &addrlen)) == -1){
			printf("recvfrom failed ! error message : %d %s\n", errno, strerror(errno));
		}
		if(len){
			printf("%s port: %d, len: %d, data: %x %x %x %x \n",inet_ntoa(client_addr.sin_addr), htons(client_addr.sin_port),  len, buf[0], buf[28], buf[29], buf[30]);
		}
		if(len2){
			printf("%s port: %d, len: %d, data: %x %x %x %x \n",inet_ntoa(client_addr2.sin_addr), htons(client_addr2.sin_port), len2, buf2[0], buf2[1], buf2[2], buf2[3]);
		}
		if(len){
			buf[0]=0x22;
			buf[1]=0xB8;
			buf[2]=0xaf;
			buf[3]=0x13;
			buf[4]=0x0;
			buf[5]=0x22;
			sendto(fd, buf, len, 0, (struct sockaddr *)&client_addr, addrlen);
		}
	}
	return 0;
}

上面程序的接收和发送,都是使用的 Raw Socket 的方式进行,其下为数据收发情况

  • 发送->接收
    在这里插入图片描述
    发送了 20个字节,收到 40个字节

  • 接收->发送
    在这里插入图片描述

其上打印,Raw Socket 的方式接收了 48 字节,首字节从0x45开始,其后包含了源 IP、目的 IP 和端口号等,正常数据位于第28字节处;
而正常的 UDP 模式下接收了 20 字节,开始即是 0x68

报文

在这里插入图片描述

Raw Socket 的方式发送从端口号开始, 可自行组包,源端口,目的端口,数据长度等,其正常数据从上面的调试助手看,从第 8 字节开始
在这里插入图片描述

理解

Raw Socket 比较适用于抓包分析,由于打开套接字不会打开对应端口,故使用时为去除 ICMP 包,需打开正常的端口号,发送可使用正常数据包,无需自行组包。

使用 Raw Socket 方式可解决:区分接收到的是广播数据还是单播数据 这一简单应用。

参考

https://github.com/xgfone/snippet/blob/master/snippet/docs/linux/program/raw-socket.md
http://qiusuoge.com/17205.html
https://www.kernel.org/doc/html/latest/networking/filter.html#networking-filter

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值