linux 下 tcpdump详解 后篇(自己实现抓包过滤)

2023-05-16

一 概述

在了解了tcpdump的原理后,你有没有想过自己去实现抓包过滤? 可能你脑子里有个大概的思路,但是知道了理论知识,其实并不能代表你完全的理解。只要运用后,你才知道哪些点需要注意,之前没有考虑到的。

二 如何实现抓包过滤

在写代码前,先捋下思路,和相应的理论知识。

libpcap 库中实现抓包关键代码

sock_fd = cooked ?

socket(PF_PACKET, SOCK_DGRAM, protocol) :

socket(PF_PACKET, SOCK_RAW, protocol);

libpcap库中pcap_open_live 函数最终会调用上面这行代码,而创建的这socket就可以接收数据链路层的数据包。而protocol 可以指定数据链路层协议帧类型,例如IPv4帧,可以传入htons(ETH_P_IP),接收到数据链路层所有协议帧,可以传入htons(ETH_P_ALL)

· socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL))

当指定SOCK_DGRAM时,获取的数据包是去掉了数据链路层的头(link-layer header)

· socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))

当指定SOCK_RAW时,获取的数据包是一个完整的数据链路层数据包

已经可以抓数据链路层的数据包,但是如何设置过滤规则呢?

在libpcap 设置过滤规则用到了两个接口,pcap_compile()和 pcap_setfilter ()

函数,其中pcap_compile()主要将我们输入的过滤规则表达式编译成BPF代码,然后存入bpf_program的结构中。而pcap_setfilter 就是将过滤的规则注入内核。这里不关注pcap_compile 如何编译成bpf代码,然后存入bpf_program。 bpf深入的话,其实里面的东西还有很多(例如输入的过滤的规则怎么转发对应的bpf代码,应用层是如何注入bpf规则到内核,内核又是如何不必要重新编译代码,就可以怎么根据不同的socket上的bpf规则过滤数据包的,等等)。言归正传,主要看pcap_setfilter 关键代码,如下

struct sock_fprog *fcode

ret = setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,

fcode, sizeof(*fcode));

其实在liunx上,你只需要简单的创建你的filter代码,通过SO_ATTTACH_FILTER选项发送到内核,并且你的filter代码能通过内核的检查,这样你就可以立即过滤socket上面的数据了。

而 struct sock_fprog 结构如下

struct sock_fprog { /* Required for SO_ATTACH_FILTER. */

unsigned short   len; /* Number of filter blocks */

struct sock_filter __user *filter;

};

struct sock_filter { /* Filter block */

__u16 code;   /* Actual filter code */

__u8 jt; /* Jump true */

__u8 jf; /* Jump false */

__u32 k;      /* Generic multiuse field */

};

其中code元素是一个16位宽的操作码,具有特定的指令编码。jt和jf是两个8位宽的跳转目标,一个用于条件“跳转如果真”,另一个“跳转如果假”。最后k元素包含一个可以用不同方式解析的杂项参数,依赖于code给定的指令。

那么过滤规则如何转化为sock_filter 结构体对应的规则呢?不是说不需要深入bpf,其实tcpdump 提供了一种方法,可以将过滤规则转化成对应liunx c下sock_filter 规则。

例如你需要过滤经过本机端口22所有的数据。在liunx 终端安装了tcpdump 。只需在终端输入 tcpdump port 22 -nn -dd 就可生产对应规则,如下图

至此你其实已经完全可以根据要过滤的包,自己实现抓包过滤了。程序如下

int create_link_raw_socket(){

struct sock_filter bpf_code[] = {

// tcpdump  port 22 -nn -dd

{ 0x28, 0, 0, 0x0000000c },

{ 0x15, 0, 8, 0x000086dd },

{ 0x30, 0, 0, 0x00000014 },

{ 0x15, 2, 0, 0x00000084 },

{ 0x15, 1, 0, 0x00000006 },

{ 0x15, 0, 17, 0x00000011 },

{ 0x28, 0, 0, 0x00000036 },

{ 0x15, 14, 0, 0x00000016 },

{ 0x28, 0, 0, 0x00000038 },

{ 0x15, 12, 13, 0x00000016 },

{ 0x15, 0, 12, 0x00000800 },

{ 0x30, 0, 0, 0x00000017 },

{ 0x15, 2, 0, 0x00000084 },

{ 0x15, 1, 0, 0x00000006 },

{ 0x15, 0, 8, 0x00000011 },

{ 0x28, 0, 0, 0x00000014 },

{ 0x45, 6, 0, 0x00001fff },

{ 0xb1, 0, 0, 0x0000000e },

{ 0x48, 0, 0, 0x0000000e },

{ 0x15, 2, 0, 0x00000016 },

{ 0x48, 0, 0, 0x00000010 },

{ 0x15, 0, 1, 0x00000016 },

{ 0x6, 0, 0, 0x0000ffff },

{ 0x6, 0, 0, 0x00000000 }

};

int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

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));

}

return fd;

}

通过上面的代码你根据返回的 fd ,调用recvfrom 接收到的包就是经过过滤的22端口的数据包。但是你要是想抓的包是去除数据链路层的头,用socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL)); 然后根据tcpdump -dd生成的规则,你会发现加入的规则起不到作用。这里推测 tcpdump -dd 生成的规则只针对链路层。

同时在socket除了你可以添加你要过滤的规则,还有几个两个与bpf规则相关的系统调用

· 在套接字socket 附加filter规则 :

setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &val, sizeof(val));

· 把filter从socket上移除 :

setsockopt(sockfd, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val));

· 运行中锁定附加到socket上的filter:

setsockopt(sockfd, SOL_SOCKET, SO_LOCK_FILTER, &val, sizeof(val));

其中SO_DETACH_FILTER选项可以把filter从socket上移除。这可能不会被经常使用,因为当你关闭socket的时候如果有filter会被自动移除。另外一个不太常见的情况是在同一个socket上添加不同的filter,当你还有另一个filter正在运行:如果你的新filter代码能够通过内核检查,内核小心的把旧的filter移除把新的filter换上,如果检查失败旧的filter将继续保留在socket上。

SO_LOCK_FILTER选项运行锁定附加到socket上的filter。一旦设置,filter不能被移除或者改变。这种允许一个进程设置一个socket、附加一个filter、锁定它们并放弃特权,确保这个filter保持到socket的关闭。

三 抓包过滤应用实现

通过上面了解,貌似自己实现抓包过滤并不存在啥技术难度。相反是不是感觉很简单。其实并不见得,本章要实现抓包过滤的应用功能,本质上是类似实现nat转换的功能。大概就是经过本机指定的srcip,srcPort过滤数据包,然后修改数据包,给该数据转发到另一台设备上destip,destPort。

1. 数据链路层抓包

notes : 只给出关键代码

int create_link_raw_socket(){

struct sock_filter bpf_code[] = {

// tcpdump  src  10.68.22.140 and port 7777 -nn -dd

{ 0x28, 0, 0, 0x0000000c },

{ 0x15, 0, 14, 0x00000800 },

{ 0x20, 0, 0, 0x0000001a },

{ 0x15, 0, 12, 0x0a44168c },

{ 0x30, 0, 0, 0x00000017 },

{ 0x15, 2, 0, 0x00000084 },

{ 0x15, 1, 0, 0x00000006 },

{ 0x15, 0, 8, 0x00000011 },

{ 0x28, 0, 0, 0x00000014 },

{ 0x45, 6, 0, 0x00001fff },

{ 0xb1, 0, 0, 0x0000000e },

{ 0x48, 0, 0, 0x0000000e },

{ 0x15, 2, 0, 0x00001e61 },

{ 0x48, 0, 0, 0x00000010 },

{ 0x15, 0, 1, 0x00001e61 },

{ 0x6, 0, 0, 0x0000ffff },

{ 0x6, 0, 0, 0x00000000 }

};

int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

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));

}

return fd;

}

首先创建了socket ,从数据链路层开始过滤,只抓src 10.68.22.140 并且 7777 端口的数据包,这部分代码前面做过说明。

while (1) {

memset(buffer, 0, BUFFER_SIZE);

struct sockaddr_in t_addr;

socklen_t addr_len = sizeof(struct sockaddr_in);

recv_ret = recvfrom(ptm->raw_fd, buffer, BUFFER_SIZE,0, (struct sockaddr *) &t_addr, &addr_len);

if (recv_ret <= 0) {

continue;

}

if (ptm->do_message_parse) {

d_len = ptm->do_message_parse(buffer, recv_ret,ptm);

if (d_len <= 0) {

continue;

}

}

struct sockaddr_ll addr;

memset( &addr, 0, sizeof(addr) );

addr.sll_family = AF_PACKET;

struct ifreq ifstruct;

strcpy(ifstruct.ifr_name, "eth0");

ioctl(ptm->raw_fd, SIOCGIFINDEX, &ifstruct);

addr.sll_ifindex = ifstruct.ifr_ifindex;

addr.sll_protocol = htons(ETH_P_ALL);

send_ret= sendto(ptm->raw_fd, buffer, d_len, 0, &addr, sizeof(struct sockaddr_ll));

if (send_ret < 0)

{

printf("error:%s\n",strerror(errno));

}

if (send_ret <= 0) {

continue;

}

}

while循环里三个功能

· 1 循环抓包 recvfrom

· 2 抓包处理分析 do_message_parse

· 3 转发包 sendto

其中抓包处理是核心,下面会具体说明,这里说下 struct sockaddr_ll 结构体

/*设备无关的物理层地址结构,数据链路层的头信息通常定义在 sockaddr_ll 的结构体中,通过setsockopt可以设置网卡的多播或混杂模式*/

struct sockaddr_ll

{

unsigned short int sll_family; /* 一般为AF_PACKET */

unsigned short int sll_protocol; /* 上层协议 */

int  sll_ifindex; /* 接口类型 */

unsigned short int sll_hatype; /* 报头类型 */

unsigned char sll_pkttype; /* 包类型 */

unsigned char sll_halen; /* 地址长度 */

unsigned char sll_addr[8]; /* MAC地址 */

};

/ * ---------------------------------------------------

sll_ifindex: interface索引,0 匹配所有的网络接口卡

sll_pkttype: 包含了packet类型。

                 PACK_HOST                  包地址为本地主机地址。

                 PACK_BROADCAST    物理层广播包。

                 PACK_MULTICAST      发送到物理层多播地址的包。

                 PACK_OTHERHOST    发往其它在混杂模式下被设备捕获的主机的包。

                 PACK_OUTGOING       本地回环包(从本机发出的,不小心loopback到当前socket了)

当发送数据包时,指定 sll_family, sll_addr, sll_halen, sll_ifindex, sll_protocol 就足够了。其它字段设置为0; sll_hatype和 sll_pkttype是在接收数据包时使用的; 如果要bind, 只需要使用 sll_protocol和 sll_ifindex;

------------------------------------------------------------*/        

若是你想iPv4的struct sockaddr_in 套接字地址,发送数据。sendto 会报错,显示无效的地址。正如你抓的包是从链路层开始抓的,想要发送出去,套接字地址也得从链路层开始设置。

最后看关键处理部分 do_message_parse

int do_message_parse( char *packet,int lens,void * param){

raw_chl * ptm = (raw_chl *)param;

if(ptm == NULL){

return lens;

}

// 链路层

struct ethernet *ethernet = (struct ethernet*) (packet);

char tempbuf[6] ={0};

memcpy(tempbuf,ethernet->ether_dhost,6);

memcpy(ethernet->ether_dhost,ethernet->ether_shost,6);

memcpy(ethernet->ether_shost,tempbuf,6);

const struct ndpi_llc_header_snap *llc;

u_short ethernet_type = ntohs(ethernet ->ether_type);

int offset = sizeof(struct ethernet);

int pyld_eth_len = 0;

if(ethernet_type <= 1500){

pyld_eth_len = ethernet_type;

printf("================ethernet_type:%d\n",ethernet_type);

}

if(pyld_eth_len != 0) {

llc = (struct ndpi_llc_header_snap *)(&packet[offset]);

/* check for LLC layer with SNAP extension */

if(llc->dsap == SNAP || llc->ssap == SNAP) {

ethernet_type = llc->snap.proto_ID;

offset += + 8;

}

/* No SNAP extension - Spanning Tree pkt must be discarted */

else if(llc->dsap == BSTP || llc->ssap == BSTP) {

// printf("\n\nWARNING: only IPv4/IPv6 packets are supported in this demo (vg_security supports both IPv4 and IPv6), all other packets will be discarded\n\n");

return lens;

}

}

while (ethernet_type == ETH_P_8021Q) {

ethernet_type = (packet[offset + 2] << 8) + packet[offset + 3];

offset += 4;

}

// 网络层 ip

struct ip * ip_header = (struct ip*) (packet + offset);

u_int ip_len = ntohs(ip_header->ip_len);

u_int ip_size = IP_HL(ip_header) * 4;

u_int msg_size = 0;

printf("msg_c2s_parse befor ===========enter sniffer-flow:src_ip%s\n",(char*)inet_ntoa(ip_header->ip_src));

printf("msg_c2s_parse befor ===========enter sniffer-flow:des_ip:%s\n",(char*)inet_ntoa(ip_header->ip_dst));

ip_header->ip_src.s_addr = ptm->local_addr.n_ip;

ip_header->ip_dst.s_addr = ptm->server_addr.n_ip;

ip_header->ip_sum = 0;

printf("msg_c2s_parse after ===========enter sniffer-flow:src_ip%s\n",vg_sock_get_aip( &ptm->local_addr));

printf("msg_c2s_parse after ===========enter sniffer-flow:des_ip:%s\n",(char*)inet_ntoa(ip_header->ip_dst));

ip_header->ip_sum = in_chksum((u_short *)ip_header,ip_size);

tsd_hdr_t psdHeader;

memset(&psdHeader,0,sizeof(tsd_hdr_t));

//  传输层  udp 和 tcp

switch (ethernet_type) {

case ETH_P_IP:

switch (ip_header->ip_p) {

case IPPROTO_TCP: {

offset += ip_size;

struct tcp* tcp = (struct tcp*) (packet + offset);

u_int tcp_size = TH_OFF(tcp) * 4;

msg_size = ip_len - ip_size - tcp_size;

g_n_port_client = tcp->th_sport;

printf("===src_port:%d\n",ntohs(tcp->th_sport));

printf("===dst_port:%d\n",ntohs(tcp->th_dport));

// tcp->th_sport = ptm->out_addr.n_port;

tcp->th_dport = ptm->server_addr.n_port;

tcp->th_sum = 0;

psdHeader.saddr=ip_header->ip_src.s_addr;

psdHeader.daddr=ip_header->ip_dst.s_addr;

psdHeader.mbz=0;

psdHeader.ptcl=ip_header->ip_p;

psdHeader.tcpl=htons(msg_size + tcp_size);

char szSendBuf[65535] = {0};

memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));

memcpy(szSendBuf+sizeof(psdHeader), packet+offset,tcp_size);

memcpy(szSendBuf+sizeof(psdHeader)+tcp_size,packet+offset+tcp_size ,msg_size);

tcp->th_sum=in_chksum((u_short *)szSendBuf,sizeof(psdHeader)+tcp_size +msg_size);

// memcpy(packet+offset,tcp,sizeof(struct tcp));

}

break;

case IPPROTO_UDP:

offset += ip_size;

struct udphdr * udp = (struct udphdr*) (packet + offset);

g_n_port_client = udp->source;

printf("===src_port:%d\n",ntohs(udp->source));

printf("===dst_port:%d\n",ntohs(udp->dest));

udp->source = ptm->local_addr.n_port;

udp->dest = ptm->server_addr.n_port;

udp->check = 0;

msg_size = ip_len - SIZE_IPNET - SIZE_UDP_HEAD;

psdHeader.saddr=ip_header->ip_src.s_addr;

psdHeader.daddr=ip_header->ip_dst.s_addr;

psdHeader.mbz=0;

psdHeader.ptcl=ip_header->ip_p;

psdHeader.tcpl=htons(8+msg_size);

char szSendBuf[65535] = {0};

memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));

memcpy(szSendBuf+sizeof(psdHeader), udp, 8);

memcpy(szSendBuf+sizeof(psdHeader)+8, packet + offset +8, msg_size);

udp->check=in_chksum((u_short *)szSendBuf,sizeof(psdHeader)+8+msg_size);

// memcpy(packet+offset,udp,sizeof(struct udphdr));

offset =offset +SIZE_UDP_HEAD;

break;

default:

break;

}

break;

default:

break;

}

return lens;

}

对tcp/ip 协议没有了解过的,估计上面代码会看的有点吃力,建议还是先熟悉下tcp/ip协议,tcp/ip 不是本章的讨论点。而上面代码其实做了三件事

· 获取到数据链路层数据,修改src,dst的MAC地址,(sendto 从数据链路层的数据包时,需要正确的MAC地址)

· 获取IP 层数据,修改src,dst的IP地址,同时重新计算网络层校验和

· 获取 udp/tcp 层数据 ,修改src,dst的port ,同时计算传输层校验和

你发现要是从链路层抓包的话,转发的时候还得需要对方的MAC地址,才能转发,而这在大多数情况是无法获取到的,因此上面这种方法是有局限性的。个人觉得也就适合对方发你,然后你组包回对方。

难道抓的包包含链路层的数据,转发发送数据的时候,就必须填充对方的MAC地址吗?当然不是,你可以通过创建两个fd,一个捕获包,另一个发送包。于是上面代码变成。

//创建了发送数据包的套接字

int create_net_raw_socket(){

int fd = socket(AF_INET,SOCK_RAW,IPPROTO_UDP|IPPROTO_TCP);

int flag = 1;

int ret = setsockopt(fd,IPPROTO_IP, IP_HDRINCL,&flag,sizeof(int));

if (ret < 0)

{

printf("setsockopt :%d=====%d======%s\n",ret,errno,strerror(errno));

}

return fd;

}

当需要编写自己的IP数据包首部时,可以在原始套接字上设置套接字选项IP_HDRINCL。我们需要修改src,dst的ip地址。因此需要设置IP_HDRINCL

如果IP_HDRINCL选项未开启,则由内核自动构造IP首部并把它置于来自进程的数据之前,进程让内核发送的数据起始地址指的是IP首部之后的第一个字节。(就是指进程不需要管IP首部)

如果IP_HDRINCL选项开启,则进程让内核发送数据的起始地址指的是IP首部的第一个字节,进程调用输出函数写出的数据量必须包括IP首部的大小,整个IP首部由进程构造,不过IPv4校验可置为0,表示进程让内核来设置该值,IPv4首部校验和字段总是由内核计算并存储。简而言之你不需要计算IP首部校验和,只需要置为0,内核会自动计算。

while (1) {

memset(buffer, 0, BUFFER_SIZE);

struct sockaddr_in t_addr;

socklen_t addr_len = sizeof(struct sockaddr_in);

recv_ret = recvfrom(ptm->raw_fd, buffer, BUFFER_SIZE,0, (struct sockaddr *) &t_addr, &addr_len);

if (recv_ret <= 0) {

continue;

}

printf("proxy_c2s  recv_lens:%d\n",recv_ret);

if (ptm->do_message_parse) {

d_len = ptm->do_message_parse(buffer, recv_ret,ptm);

if (d_len <= 0) {

continue;

}

}

// struct sockaddr_ll addr;

// memset( &addr, 0, sizeof(addr) );

// addr.sll_family = AF_PACKET;

// struct ifreq ifstruct;

// strcpy(ifstruct.ifr_name, "eth0");

// ioctl(ptm->raw_fd, SIOCGIFINDEX, &ifstruct); //??I/O??

// addr.sll_ifindex = ifstruct.ifr_ifindex;

// addr.sll_protocol = htons(ETH_P_ALL);

struct sockaddr_in in;

memset(&in,0,sizeof(struct sockaddr_in));

in.sin_family = AF_INET;

in.sin_addr.s_addr = ptm->server_addr.n_ip;

in.sin_port = ptm->server_addr.n_port;

memset(in.sin_zero, 0, sizeof(in.sin_zero));

send_ret= sendto(ptm->send_fd, buffer, d_len, 0,(struct sockaddr *) &in, sizeof(struct sockaddr_in));

if (send_ret <= 0) {

continue;

}

}

while 循环 其实跟之前结构一样,只是套接字地址用了IPV4的sockaddr_in,同时发送的套接字是由create_net_raw_socket()创建的。而最后虽然捕获的数据包是包含链路层数据的,但是最后sendto的时候数据是将链路层的数据截除后发送的。看do_message_parse 数据处理函数。

int do_message_parse( char *packet,int lens,void * param){

raw_chl * ptm = (raw_chl *)param;

if(ptm == NULL){

return lens;

}

struct ethernet *ethernet = (struct ethernet*) (packet);

const struct ndpi_llc_header_snap *llc;

u_short ethernet_type = ntohs(ethernet ->ether_type);

int offset = sizeof(struct ethernet);

int pyld_eth_len = 0;

if(ethernet_type <= 1500){

pyld_eth_len = ethernet_type;

printf("================ethernet_type:%d\n",ethernet_type);

}

if(pyld_eth_len != 0) {

llc = (struct ndpi_llc_header_snap *)(&packet[offset]);

/* check for LLC layer with SNAP extension */

if(llc->dsap == SNAP || llc->ssap == SNAP) {

ethernet_type = llc->snap.proto_ID;

offset += + 8;

}

/* No SNAP extension - Spanning Tree pkt must be discarted */

else if(llc->dsap == BSTP || llc->ssap == BSTP) {

// printf("\n\nWARNING: only IPv4/IPv6 packets are supported in this demo (vg_security supports both IPv4 and IPv6), all other packets will be discarded\n\n");

return lens;

}

}

while (ethernet_type == ETH_P_8021Q) {

ethernet_type = (packet[offset + 2] << 8) + packet[offset + 3];

offset += 4;

}

int recvlens = lens -offset;

int offflag = offset;

struct ip * ip_header = (struct ip*) (packet + offset);

u_int ip_len = ntohs(ip_header->ip_len);

u_int ip_size = IP_HL(ip_header) * 4;

u_int msg_size = 0;

printf("msg_s2c_parse befor ===========enter sniffer-flow:src_ip%s\n",(char*)inet_ntoa(ip_header->ip_src));

printf("msg_s2c_parse befor ===========enter sniffer-flow:des_ip:%s\n",(char*)inet_ntoa(ip_header->ip_dst));

ip_header->ip_src.s_addr = ip_header->ip_dst.s_addr;

ip_header->ip_dst.s_addr = ptm->server_addr.n_ip;

ip_header->ip_sum = 0;

// printf("msg_s2c_parse after ===========enter sniffer-flow:src_ip%s\n",vg_sock_get_aip( &ptm->in_addr));

printf("msg_s2c_parse after ===========enter sniffer-flow:des_ip:%s\n",(char*)inet_ntoa(ip_header->ip_dst));

// ip_header->ip_sum = in_chksum((u_short *)ip_header,ip_size);

tsd_hdr_t psdHeader;

memset(&psdHeader,0,sizeof(tsd_hdr_t));

switch (ethernet_type) {

case ETH_P_IP:

switch (ip_header->ip_p) {

case IPPROTO_TCP: {

offset += ip_size;

struct tcp* tcp = (struct tcp*) (packet + offset);

u_int tcp_size = TH_OFF(tcp) * 4;

msg_size = ip_len - ip_size - tcp_size;

printf("ip_len:%d ====ip_size:%d==== tcp_size:%d\n",ip_len,ip_size,tcp_size);

// tcp->th_sport = ptm->in_addr.n_port;

tcp->th_dport = ptm->server_addr.n_port;

printf("msg_s2c_parse===src_port:%d\n",ntohs(tcp->th_sport));

printf("msg_s2c_parse===dst_port:%d\n",ntohs(tcp->th_dport));

tcp->th_sum = 0;

psdHeader.saddr=ip_header->ip_src.s_addr;

psdHeader.daddr=ip_header->ip_dst.s_addr;

psdHeader.mbz=0;

psdHeader.ptcl=ip_header->ip_p;

psdHeader.tcpl=htons(msg_size + tcp_size);

char szSendBuf[65535] = {0};

memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));

printf("tcp_size:%d\n",tcp_size);

memcpy(szSendBuf+sizeof(psdHeader), packet+offset,tcp_size);

memcpy(szSendBuf+sizeof(psdHeader)+tcp_size,packet+offset+tcp_size ,msg_size);

tcp->th_sum=in_chksum((u_short *)szSendBuf,sizeof(psdHeader)+tcp_size +msg_size);

// memcpy(packet+offset,tcp,sizeof(struct tcp));

}

break;

case IPPROTO_UDP:

offset += ip_size;

struct udphdr * udp = (struct udphdr*) (packet + offset);

udp->source = udp->dest;

udp->dest = ptm->server_addr.n_port;

udp->check = 0;

msg_size = ip_len - SIZE_IPNET - SIZE_UDP_HEAD;

psdHeader.saddr=ip_header->ip_src.s_addr;

psdHeader.daddr=ip_header->ip_dst.s_addr;

psdHeader.mbz=0;

psdHeader.ptcl=ip_header->ip_p;

psdHeader.tcpl=htons(8+msg_size);

char szSendBuf[65535] = {0};

memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));

memcpy(szSendBuf+sizeof(psdHeader), udp, 8);

memcpy(szSendBuf+sizeof(psdHeader)+8, packet + offset +8, msg_size);

udp->check=in_chksum((u_short *)szSendBuf,sizeof(psdHeader)+8+msg_size);

// memcpy(packet+offset,udp,sizeof(struct udphdr));

offset =offset +SIZE_UDP_HEAD;

break;

default:

break;

}

break;

default:

break;

}

// 去除链路层的数据

memcpy(packet,packet+offflag,recvlens);

return recvlens;

}

处理函数相比较之前链路层的处理函数:

· 不需要修改链路层的Mac地址

· ip层首部校验和置0即可,内核内部会计算

· 最后返回的数据不包含链路层的数据。

2. 网络层抓包

既然可以链路层抓包,当然也可以抛开数据链路层,直接网络层抓包。而方法前面已经提到 socket(PF_PACKET, SOCK_DGRAM,htons(ETH_P_ALL)); 若是你想起来,自然也应该记得这有个很致命的问题,在不深入了解bpf规则的前提下,用tcpdump -dd生产的规则只适应链路层抓包。 那这问题如何解决呢?你想通过网络层抓包,但又不知道bpf规则如何设置?这问题困扰好久,最终不得不借助libpcap生产规则的接口。

int create_raw_socket(){

static const char filter[] = "port 7777 and host 10.68.22.140";

int sock = socket(PF_PACKET, SOCK_DGRAM,htons(ETH_P_ALL));

pcap_t *pcap = pcap_open_dead(DLT_RAW, 65535);

struct bpf_program bpf_prog;

pcap_compile(pcap, &bpf_prog, filter, 0, PCAP_NETMASK_UNKNOWN);

printf("==========bpf_prog.bf_len:%d\n",bpf_prog.bf_len);

struct sock_fprog linux_bpf = {

   .len = bpf_prog.bf_len,

   .filter = (struct sock_filter *) bpf_prog.bf_insns,

};

int ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &linux_bpf, sizeof(linux_bpf));

if (ret < 0)

{

printf("setsockopt:SO_ATTACH_FILTER>>>>error:%s\n",strerror(errno));

}

return sock;

}

pcap_open_dead()官方文档提到

It is typically used when just using libpcap for compiling BPF code;

· 1

其实就是创建了一个pcap_t 结构体,用于创建bpf代码。该接口用处不大。

pcap_compile() 接口 创建bpf代码的

while (1) {

memset(buffer, 0, BUFFER_SIZE);

struct sockaddr_in t_addr;

socklen_t addr_len = sizeof(struct sockaddr_in);

recv_ret = recvfrom(ptm->raw_fd, buffer, BUFFER_SIZE,0, (struct sockaddr *) &t_addr, &addr_len);

if (recv_ret <= 0) {

continue;

}

printf("proxy_c2s  recv_lens:%d\n",recv_ret);

if (ptm->do_message_parse) {

d_len = ptm->do_message_parse(buffer, recv_ret,ptm);

if (d_len <= 0) {

continue;

}

}

struct sockaddr_in in;

memset(&in,0,sizeof(struct sockaddr_in));

in.sin_family = AF_INET;

in.sin_addr.s_addr = ptm->server_addr.n_ip;

in.sin_port = ptm->server_addr.n_port;

memset(in.sin_zero, 0, sizeof(in.sin_zero));

send_ret= sendto(ptm->send_fd, buffer, d_len, 0,(struct sockaddr *) &in, sizeof(struct sockaddr_in));

if (send_ret <= 0) {

continue;

}

}

再看while循环体,本质还是接收包,处理包,发送包3个流程。不过因为抓的是网络层数据包,获取到的包无需再处理数据链路。

再看do_message_parse 函数 你会发现对链路层的处理部分全都不需要了。

int do_message_parse( char *packet,int lens,void * param){

raw_chl * ptm = (raw_chl *)param;

if(ptm == NULL){

return lens;

}

int offset = 0;

struct ip * ip_header = (struct ip*) (packet + offset);

u_int ip_len = ntohs(ip_header->ip_len);

u_int ip_size = IP_HL(ip_header) * 4;

u_int msg_size = 0;

printf("msg_c2s_parse befor ===========enter sniffer-flow:src_ip%s\n",(char*)inet_ntoa(ip_header->ip_src));

printf("msg_c2s_parse befor ===========enter sniffer-flow:des_ip:%s\n",(char*)inet_ntoa(ip_header->ip_dst));

ip_header->ip_src.s_addr = ptm->local_addr.n_ip;

ip_header->ip_dst.s_addr = ptm->server_addr.n_ip;

ip_header->ip_sum = 0;

printf("msg_c2s_parse after ===========enter sniffer-flow:src_ip%s\n",vg_sock_get_aip( &ptm->local_addr));

printf("msg_c2s_parse after ===========enter sniffer-flow:des_ip:%s\n",(char*)inet_ntoa(ip_header->ip_dst));

ip_header->ip_sum = in_chksum((u_short *)ip_header,ip_size);

tsd_hdr_t psdHeader;

memset(&psdHeader,0,sizeof(tsd_hdr_t));

switch (ip_header->ip_p) {

case IPPROTO_TCP: {

offset += ip_size;

struct tcp* tcp = (struct tcp*) (packet + offset);

u_int tcp_size = TH_OFF(tcp) * 4;

msg_size = ip_len - ip_size - tcp_size;

g_n_port_client = tcp->th_sport;

printf("===src_port:%d\n",ntohs(tcp->th_sport));

printf("===dst_port:%d\n",ntohs(tcp->th_dport));

// tcp->th_sport = ptm->out_addr.n_port;

tcp->th_dport = ptm->server_addr.n_port;

tcp->th_sum = 0;

psdHeader.saddr=ip_header->ip_src.s_addr;

psdHeader.daddr=ip_header->ip_dst.s_addr;

psdHeader.mbz=0;

psdHeader.ptcl=ip_header->ip_p;

psdHeader.tcpl=htons(msg_size + tcp_size);

char szSendBuf[65535] = {0};

memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));

memcpy(szSendBuf+sizeof(psdHeader), packet+offset,tcp_size);

memcpy(szSendBuf+sizeof(psdHeader)+tcp_size,packet+offset+tcp_size ,msg_size);

tcp->th_sum=in_chksum((u_short *)szSendBuf,sizeof(psdHeader)+tcp_size +msg_size);

// memcpy(packet+offset,tcp,sizeof(struct tcp));

}

break;

case IPPROTO_UDP:

offset += ip_size;

struct udphdr * udp = (struct udphdr*) (packet + offset);

g_n_port_client = udp->source;

printf("===src_port:%d\n",ntohs(udp->source));

printf("===dst_port:%d\n",ntohs(udp->dest));

udp->source = ptm->local_addr.n_port;

udp->dest = ptm->server_addr.n_port;

udp->check = 0;

msg_size = ip_len - SIZE_IPNET - SIZE_UDP_HEAD;

psdHeader.saddr=ip_header->ip_src.s_addr;

psdHeader.daddr=ip_header->ip_dst.s_addr;

psdHeader.mbz=0;

psdHeader.ptcl=ip_header->ip_p;

psdHeader.tcpl=htons(8+msg_size);

char szSendBuf[65535] = {0};

memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));

memcpy(szSendBuf+sizeof(psdHeader), udp, 8);

memcpy(szSendBuf+sizeof(psdHeader)+8, packet + offset +8, msg_size);

udp->check=in_chksum((u_short *)szSendBuf,sizeof(psdHeader)+8+msg_size);

offset =offset +SIZE_UDP_HEAD;

break;

default:

break;

}

return lens;

}

3. 运行结果

测试中本机地址22.189 转发地址是22.140

程序运行在22.189 设备上

22.140:7777 端口 发送给 22.189:8888端口,如下图

程序处理后,会将发往 22.189:8888 的数据包转发给 22.140:9000 端口,如下图

四. lo口抓包发包

为啥要把lo口的抓发包单独提出来呢? 带着这个问题往下看

lo口的有条重要性质:

· 1.ip addr add 127.2.0.1/16 dev lo 添加了127.2.0.1/16的地址 代表127.2.0.1/16网段所有地址都设置好了,即ping 127.2.128.222 也是可以通的 不需要单独再配置 127.2.128.222 地址

这对某些项目部署工程的时候,要是可以用lo口地址配置一个网段地址,所有该网段的地址都起来了。因此项目中可能会用到lo口地址。例如本人之前有个通过lo口代理转发项目。

udp协议lo还发现一个特性,例如我eth0 地址配置了10.68.22.189 ,然后 lo口配置了127.2.0.1/16 网段的地址。当我用10.68.22.189 地址往 127.3.0.11 这个地址发送数据的时候。虽然127.3.0.11 地址是不存在的,但是数据包是能发送出去的。

程序demo执行如下图所示

tcpdump 抓包显示如下

但是要是我以 127.2.0.11 发往 127.3.0.11。 你会发现数据包压根发送不出去。

程序直接报错误,显示无效参数,如下图

后面发现udp要是想lo发送数据包,首先你发送本机设置的127地址必须是存在的,同时发送的服务端地址不管是发往的是127的还是其他地址。必须是本机存在的。lo口地址发送只能发往本机。

这条特性还是要多注意下,之前发现莫名其妙lo口发送的数据,怎么抓包都抓不到,后面才发现这个问题。

lo口相关知识点已经铺垫好了,言归正传,之前不是介绍链路层的两种抓包,还有网络层的一种抓包。后面发现用lo口发送数据,数据发送出来一条,但是抓包确认抓到两条。

test程序 127.2.0.11 往 127.2.0.44 发送了一次数据包,如下图

抓包程序发现接收到了两次相同的包。 而导致lo口发送一次包,转发了两条数据

tcpdump 抓包如下

从tcpdump 你发现他只收到了一次lo口的包,后面两条是我程序转发的两次数据包。那么为啥tcpdump只收到了一次数据包,tcpdump 做了啥处理了吗?

回顾libpcap pcap_read_packet() 接口 里面接收到套接字地址后,会调用

linux_check_direction()函数,里面有一段关键代码如下

struct pcap_linux *handlep = handle->priv;

if (sll->sll_pkttype == PACKET_OUTGOING) {

/*

* Outgoing packet.

* If this is from the loopback device, reject it;

* we'll see the packet as an incoming packet as well,

* and we don't want to see it twice.

*/

if (sll->sll_ifindex == handlep->lo_ifindex)

return 0;

}

看注释的这段话,lo口的设备抓到的包,在incoming ,Outgoing 都会收到一次,我们不需要抓包两次,因此outgoing的包忽略掉

到这里我们已经看到解决方法,下面给出代码关键部分

while (1) {

memset(buffer, 0, BUFFER_SIZE);

struct sockaddr_ll  t_addr;

socklen_t addr_len = sizeof(struct sockaddr_ll);

recv_ret = recvfrom(ptm->raw_fd, buffer, BUFFER_SIZE,0, (struct sockaddr *) &t_addr, &addr_len);

if (recv_ret <= 0) {

printf("recvfrom  error:%s\n",strerror(errno));

continue;

}

if (t_addr.sll_pkttype == PACKET_OUTGOING) {

continue;

}

if (ptm->do_message_parse) {

d_len = ptm->do_message_parse(buffer, recv_ret,ptm);

if (d_len <= 0) {

continue;

}

}

printf("do_message_parse:d_len:%d\n",d_len);

struct sockaddr_in in;

memset(&in,0,sizeof(struct sockaddr_in));

in.sin_family = AF_INET;

in.sin_addr.s_addr = ptm->server_addr.n_ip;

in.sin_port = ptm->server_addr.n_port;

memset(in.sin_zero, 0, sizeof(in.sin_zero));

send_ret= sendto(ptm->send_fd, buffer, d_len, 0,(struct sockaddr *) &in, sizeof(struct sockaddr_in));

if (send_ret <= 0) {

continue;

}

}

测试结果,tcpdump 抓包如下

上图可以看到127.2.0.11 往 127.2.0.44 发送了一条udp数据

然后后面 10.68.22.189:9999 将这条数据转发给了 10.68.22.140。转发的部分就程序做的,只转发了一条,说明抓的数据已经做了过滤。

五. 总结

注意点:

· 链路层抓包,提供了两种处理方式,一种是创建raw_fd抓包,同时还是用raw_fd发包,另一种是raw_fd抓包,send_fd 发包。

· 网络层抓包,需要注意bpf规则的生成。同时只提供了raw_fd抓包,send_fd发包一种情况,试过用raw_fd 抓包发包,会报无效的参数错误,未找到问题。

· lo口发送udp数据包,要注意发送的lo地址必须存在,同时发往的地址也必须本机真实存在。lo口发送包,正常情况会抓到两次数据包,要做过滤。

问题:

· 本篇示例只处理请求转发,并没有处理响应转发。了解了请求转发过程,响应其实是一个道理

· 提供的示例是走的udp协议,相对简单。tcp协议面向连接,必须要三次握手成功,才能互相发送数据。也就是必须处理转发和响应

tcp 协议篇幅有限,这里没有讨论tcp,若是你实践了tcp转发。这里啰嗦几句, 即使你转发响应都处理了,你会发现当客户端tcp握手一个没有开放监听的端口,syn 包请求包发送后,数据包到了本机应用层,它会发现tcp请求的端口,本机没有监听,开放。会直接返回Rst包重置掉这个请求。导致转发的三次握手一直连接不上。你只需要在iptables filter链 drop 掉 这个syn请求包即可。纳尼? 说的不简洁,是不是云里雾里。总而言之,你得明白你作为转发的本机其实就是捕获数据,并不会去在本机监听开放端口,而当客户端发起tcp连接,连接的端口并不存在,所以本机会发一个rst重置包,关闭这个连接。

断断续续花了两周多的时间整理,把遇到的坑跟大伙分析。希望对大伙有用。至此本篇完。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux 下 tcpdump详解 后篇(自己实现抓包过滤) 的相关文章

  • 如何阅读shell命令的源代码?

    我想阅读编写linux命令的实际源代码 我已经获得了一些使用它们的经验 现在我认为是时候与我的机器进行更深层次的交互了 我在这里找到了一些命令http directory fsf org wiki GNU http directory fs
  • 如何从“git log”中查看 Git 中的特定版本?

    My git log显示为 enter code here git trial git log commit 4c5bc66ae50780cf8dcaf032da98422aea6e2cf7 Author king lt email pro
  • 通过 SSH 将变量传递给远程脚本

    我正在通过 SSH 从本地服务器在远程服务器上运行脚本 首先使用 SCP 复制该脚本 然后在传递一些参数时调用该脚本 如下所示 scp path to script server example org another path ssh s
  • 使用脚本自动输入 SSH 密码

    我需要创建一个自动向 OpenSSH 输入密码的脚本ssh client 假设我需要通过 SSH 进入myname somehost用密码a1234b 我已经尝试过 bin myssh sh ssh myname somehost a123
  • 如何在 Linux x86_64 上模拟 iret

    我正在编写一个基于 Intel VT 的调试器 由于当 NMI Exiting 1 时 iret 指令在 vmx guest 中的性能发生了变化 所以我应该自己处理vmx主机中的NMI 否则 guest会出现nmi可重入错误 我查了英特尔手
  • Linux 中的电源管理通知

    在基于 Linux 的系统中 我们可以使用哪些方法 最简单的方法 来获取电源状态更改的通知 例如 当计算机进入睡眠 休眠状态等时 我需要这个主要是为了在睡眠前保留某些状态 当然 在计算机唤醒后恢复该状态 您只需配置即可获得所有这些事件acp
  • 为什么 ld 无法从 /etc/ld.so.conf 中的路径找到库?

    我想添加 opt vertica lib64进入系统库路径 所以我执行以下步骤 1 添加 opt vertica lib64 into etc ld so conf 然后运行ldconfig 2 检查 bash ldconfig p gre
  • Linux命令列出所有可用命令和别名

    是否有一个 Linux 命令可以列出该终端会话的所有可用命令和别名 就好像您输入 a 并按下 Tab 键一样 但针对的是字母表中的每个字母 或者运行 别名 但也返回命令 为什么 我想运行以下命令并查看命令是否可用 ListAllComman
  • 无关的库链接

    我有一个可能有点愚蠢的问题 因为我很确定我可能已经知道答案了 假设你有静态库A 动态共享库B和你的linux下的程序C 假设库 A 调用库 B 中的函数 并且您的程序调用库 A 中的函数 现在假设 C 在 A 中调用的所有函数都不使用 B
  • python:numpy 运行脚本两次

    当我将 numpy 导入到 python 脚本中时 该脚本会执行两次 有人可以告诉我如何阻止这种情况 因为我的脚本中的所有内容都需要两倍的时间 这是一个例子 usr bin python2 from numpy import print t
  • 套接字:监听积压并接受

    listen sock backlog 在我看来 参数backlog限制连接数量 这是我的测试代码 server initialize the sockaddr of server server sin family AF INET ser
  • 如何查明 Ubuntu 上安装了哪个版本的 GTK+?

    我需要确定 Ubuntu 上安装了哪个版本的 GTK 男人似乎不帮忙 这个建议 https stackoverflow com a 126145 会告诉您安装了哪个 2 0 的次要版本 不同的主要版本将具有不同的包名称 因为它们可以在系统上
  • 操作系统什么时候清除进程的内存

    进程在某些操作系统上成功或异常终止 操作系统何时决定擦除分配给该进程的内存 数据 代码等 在退出时或当它想为新进程分配内存时 这个清除内存分配过程在所有操作系统 winXP Win7 linux Mac 上都相同吗 据我了解 页表具有该进程
  • 停止服务时单元陷入故障状态(状态=143)[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 这是我的问题 我有 CentOS 和 java 进程在上面运行 Java进程是通过启动 停止脚本来操作的 它也创建了 java 实例的 p
  • 无法安装 WWW::Curl::Easy: SZBALINT/WWW-Curl-4.17.tar.gz : make NO

    我正在尝试在我的 Fedora 26 机器上安装 WWW Curl Easy gcc c I usr include D REENTRANT D GNU SOURCE O2 g pipe Wall Werror format securit
  • 标准头文件中的 C 编译器错误 - 未定义的 C++ 定义

    我正在尝试编译 C 程序 但收到许多错误 这些错误是在标准 C 头文件 inttypes h stdio h stat h 等 中遇到的 错误的来源是以下未定义的常量 BEGIN DECLS END DECLS BEGIN NAMESPAC
  • 无法执行'x86_64-conda_cos6-linux-gnu-gcc':没有这样的文件或目录(pysam安装)

    我正在尝试安装 pysam 执行后 python path to pysam master setup py build 这个错误的产生是 unable to execute x86 64 conda cos6 linux gnu gcc
  • 在Linux中断上下文中运行用户线程

    我正在编写一些定制的应用程序 并允许更改 Linux 内核中的中断处理程序代码 我有一个用户线程正在等待中断发生 如果发生中断 那么我要做的第一件事就是执行该用户线程 有什么办法让它发挥作用吗 Thanks 创建一个字符设备 这就是内核所做
  • 无需 cron 在后台发送邮件

    我想知道是否有一种方法可以运行 PHP 循环 以便在后台向订阅者发送几百封电子邮件 我的目标是格式化新闻通讯 单击发送 然后关闭浏览器或更改页面 当然 发送电子邮件的实际过程将在后台运行 不会因浏览器关闭而中断 我知道这可以通过 cron
  • 如何查找连接到 AF_INET 套接字的客户端的 UID?

    有什么方法或类似的东西ucred for AF UNIX如果是AF INET插座 TCP在我的例子中 找出连接到我的套接字的客户端的UID 还有 proc net tcp但它显示了UID of the creator插座的而不是连接的cli

随机推荐

  • linux c 获取文件的时间信息

    gt File Name hello c gt Author lizhu gt Mail gt Created Time 2015年11月20日 星期五 10时30分20秒 include lt time h gt include lt s
  • Linux下C语言来检测USB设备以及自动区分U盘和硬盘并自动挂载

    别的不说直接上代码 作用就是在linux系统上可以直接判断哪个是USB设备节点 xff0c 哪个是硬盘设备节点 xff0c 如果想挂载某一个节点 想屏蔽某一个USB孔的USB设备 xff0c 具体代码 xff0c 正在整理中 xff0c 下
  • glib 交叉编译步骤

    借鉴文章网址为 wiki beyondlogic org index php title 61 Cross Compiling BlueZ Bluetooth tools for ARM 编译glib需要zlib libffi两个库文件我使
  • STM32中SysTick、FCLK、SYSCLK、HCLK

    http rmingwang com the stm32 systick fclk sysclk hclk html 转载原文 在STM32中 xff0c 有五个时钟源 xff0c 为HSI HSE LSI LSE PLL HSI是高速内部
  • qglobal.h:45: error: C1083: 无法打开包括文件:

    看了很多人的错误调试记录就是不行 xff0c 后来在百度知道里面看到这个 xff0c 添加进入后就可以了 在系统环境变量path 添加下面内容即可 SystemRoot system32
  • 卡尔曼滤波器学习笔记(二)

    扩展卡尔曼滤波器的原理及应用 经典的卡尔曼滤波只适用于线性且满足高斯分布的系统 xff0c 但实际工程中并不是这么简单 xff0c 比如飞行器在水平运动时有可能伴随着自身的自旋 xff0c 此时的系统并不是线性的 xff0c 这时就需要应用
  • 串口回环测试

    串口lookback测试 基础知识 通用异步收发传输器 xff08 Universal Asynchronous Receiver Transmitter xff09 xff0c 通常称作UART UART 是一种通用的数据通信协议 xff
  • STM32学习--USART

    串口工作方式 1 查询 xff1a 串口程序不断循环查询 xff0c 看看有没有数据需要它发送 2 中断 xff1a 平时串口只要打开中断即可 如果有中断来即需要传输数据 xff0c 它就马上进行数据的传输 STM32编程 xff1a 1
  • Atitit HTTP 认证机制基本验证 (Basic Authentication) 和摘要验证 (Digest Authentication)attilax总结Atitit HT

    Atitit HTTP 认证机制 基本验证 Basic Authentication 和摘要验证 Digest Authentication attilax总结 1 1 最广泛使用的是基本验证 Basic Authentication 和摘
  • 实验二 读取和理解激光雷达数据

    1 体验内容 xff08 1 xff09 为rplidar添加USB权限 注 xff1a 实验室的rplidar A2买得比较早 xff0c 硬件版本可能与github程序不匹配 xff0c 出现运行错误 xff0c 解决方法为到 Wind
  • Linux下使用Netfilter框架编写内核模块(统计协议层ping特定地址丢包数)

    一 linux内核中neitfilter的处理过程 1 5个HOOK点的执行点说明 xff1a 数据包从进入系统 xff0c 进行IP校验以后 xff0c 首先经过第一个HOOK函数NF IP PRE ROUTING进行处理 xff1b 然
  • 基于TCP 实现客户端之间通信【1】

    前段时间学习了基于TCP协议下实现服务器与一个客户端的通信 xff0c 服务器与多个客户端之间的通信 xff0c 以及客户端之间的互相通信 下面就是我写的利用TCP和多线程技术实现客户端之间互相通信的代码 xff1a 服务器端 xff1a
  • 回环接口(loop-back/loopback)

    回环接口 xff08 loop back loopback xff09 Moakap 整理 Loopback 接口是一个虚拟网络接口 xff0c 在不同的领域 xff0c 其含义也大不一样 1 xff0e TCP IP 协议栈中的 loop
  • socket封装HTTP请求

    之前写过两个socket封装的HTTP GET请求 xff0c 可是知其然 xff0c 不知所以然 这次写POST请求就有点懵逼了 还是从大佬的文章出发 xff1a https blog csdn net a19881029 article
  • access中,如何删除不可见空格

    1 可以使用 Trim 函数 xff0c 删除字符串 首 尾 的正常空格 span class token keyword update span 表名 span class token keyword set span 字段 span c
  • Raspberry Pi树莓派分类和其相似产品介绍

    文章目录 前言一 1代树莓派二 2代树莓派三 3代树莓派四 4代树莓派五 树莓派Pico六 目前可替代板子介绍引用 前言 树莓派官网 作为小型计算机的代表 xff0c 树莓派可是作为开山鼻祖 xff0c 本文聊一下目前树莓派几代板子发展历程
  • ARM汇编指令ldr和MOV的区别

    1 八位图 数据 2 MOV指令 MOV指令可以把立即数或者寄存器内容 xff08 注意 xff1a 这里绝对不可以是内存 xff01 xff01 xff09 传递给一个寄存器 MOV对于立即数是有要求的 xff0c 就是上边的 8位图 数
  • GPS NEMA 0183协议

    一 NMEA0183标准语句 GPS常用语句 GPGGA 例 xff1a GPGGA 092204 999 4250 5589 S 14718 5084 E 1 04 24 4 19 7 M 0000 1F 字段0 xff1a GPGGA
  • 使用setvbuf更改printf的默认buffer 行为

    有3种buffer行为 xff0c 不缓冲 xff0c 基于块的缓冲 和 基于行的缓冲 stdout xff08 printf xff09 默认是基于行的缓冲 xff0c 即写到stdout的字符都会被缓冲起来直到一个换行符输出的时候 xf
  • linux 下 tcpdump详解 后篇(自己实现抓包过滤)

    一 概述 在了解了tcpdump的原理后 xff0c 你有没有想过自己去实现抓包过滤 xff1f 可能你脑子里有个大概的思路 xff0c 但是知道了理论知识 xff0c 其实并不能代表你完全的理解 只要运用后 xff0c 你才知道哪些点需要