原始套接字使用总结 (端口扫描和流量统计实现)

1. 网络中的四层

应用层
传输层
网络层
数据链路层

数据链路层:解决点对点的通讯 (mac地址)

网络层:解决主机到主机的通讯 (ip地址)

传输层:解决一台主机的任意的进程和另一台主机的任意进程的通讯 (端口)

应用层:解决应用程序个各种业务问题

1.1 传输层

tcp套接字的使用

int fd = socket(AF_INET,SOCK_STREAM,0);

AF_INET/AF_INET6 ipv4/ipv6

这种套接字可以直接使用tcp协议,发送和接受都是只要tcp的数据区的内容,无需关注tcp报文的内容

udp套接字的使用

int fd = socket(AF_INET,SOCK_DGRAM,0);

AF_INET/AF_INET6 ipv4/ipv6

和tcp套接字一样只需要关注收发的数据

1.2 网络层

网络层的原始套接字使用

int sfd = socket(AF_INET, SOCK_RAW, IPPROTO_xxx);

AF_INET/AF_INET6 ipv4/ipv6
AF_INET和SOCK_RAW组合表示网络层的原始套接字
IPPROTO_xxx 协议

这里的协议表示封装在ip报文中的协议

协议可选定义在 /usr/include/netinet/in.h 头文件中

enum
  {
    IPPROTO_IP = 0,	   /* Dummy protocol for TCP.  */
#define IPPROTO_IP		IPPROTO_IP
    IPPROTO_ICMP = 1,	   /* Internet Control Message Protocol.  */
#define IPPROTO_ICMP		IPPROTO_ICMP
    IPPROTO_IGMP = 2,	   /* Internet Group Management Protocol. */
#define IPPROTO_IGMP		IPPROTO_IGMP
    IPPROTO_IPIP = 4,	   /* IPIP tunnels (older KA9Q tunnels use 94).  */
#define IPPROTO_IPIP		IPPROTO_IPIP
    IPPROTO_TCP = 6,	   /* Transmission Control Protocol.  */
#define IPPROTO_TCP		IPPROTO_TCP
    IPPROTO_EGP = 8,	   /* Exterior Gateway Protocol.  */
#define IPPROTO_EGP		IPPROTO_EGP
    IPPROTO_PUP = 12,	   /* PUP protocol.  */
#define IPPROTO_PUP		IPPROTO_PUP
    IPPROTO_UDP = 17,	   /* User Datagram Protocol.  */
#define IPPROTO_UDP		IPPROTO_UDP
    IPPROTO_IDP = 22,	   /* XNS IDP protocol.  */
#define IPPROTO_IDP		IPPROTO_IDP
    IPPROTO_TP = 29,	   /* SO Transport Protocol Class 4.  */
#define IPPROTO_TP		IPPROTO_TP
    IPPROTO_DCCP = 33,	   /* Datagram Congestion Control Protocol.  */
#define IPPROTO_DCCP		IPPROTO_DCCP
    IPPROTO_IPV6 = 41,     /* IPv6 header.  */
#define IPPROTO_IPV6		IPPROTO_IPV6
    IPPROTO_RSVP = 46,	   /* Reservation Protocol.  */
#define IPPROTO_RSVP		IPPROTO_RSVP
    IPPROTO_GRE = 47,	   /* General Routing Encapsulation.  */
#define IPPROTO_GRE		IPPROTO_GRE
    IPPROTO_ESP = 50,      /* encapsulating security payload.  */
#define IPPROTO_ESP		IPPROTO_ESP
    IPPROTO_AH = 51,       /* authentication header.  */
#define IPPROTO_AH		IPPROTO_AH
    IPPROTO_MTP = 92,	   /* Multicast Transport Protocol.  */
#define IPPROTO_MTP		IPPROTO_MTP
    IPPROTO_BEETPH = 94,   /* IP option pseudo header for BEET.  */
#define IPPROTO_BEETPH		IPPROTO_BEETPH
    IPPROTO_ENCAP = 98,	   /* Encapsulation Header.  */
#define IPPROTO_ENCAP		IPPROTO_ENCAP
    IPPROTO_PIM = 103,	   /* Protocol Independent Multicast.  */
#define IPPROTO_PIM		IPPROTO_PIM
    IPPROTO_COMP = 108,	   /* Compression Header Protocol.  */
#define IPPROTO_COMP		IPPROTO_COMP
    IPPROTO_SCTP = 132,	   /* Stream Control Transmission Protocol.  */
#define IPPROTO_SCTP		IPPROTO_SCTP
    IPPROTO_UDPLITE = 136, /* UDP-Lite protocol.  */
#define IPPROTO_UDPLITE		IPPROTO_UDPLITE
    IPPROTO_MPLS = 137,    /* MPLS in IP.  */
#define IPPROTO_MPLS		IPPROTO_MPLS
    IPPROTO_RAW = 255,	   /* Raw IP packets.  */
#define IPPROTO_RAW		IPPROTO_RAW
    IPPROTO_MAX
  };

比较常用的是IPPROTO_TCP/IPPROTO_UDP/IPPROTO_ICMP,tcp/udp/icmp协议

这些字段填入第三个参数,这个原始套接字就会接受协议的数据,数据是从ip协议开始的。默认的发送数据不需要填充ip协议

如果要自己构造ip协议需要开启IP_HDRINCL选项

int ip_on = 1;
setsockopt(sfd, IPPROTO_IP, IP_HDRINCL, &ip_on, sizeof(int));

这一层中常用的结构体都在 /usr/include/netinet目录下

ip协议结构体,tcp协议结构体

//ip协议结构体
struct ip
{
#if __BYTE_ORDER == __LITTLE_ENDIAN //大小端影响
    unsigned int ip_hl:4;		//ip头的长度  ip_hl *4
    unsigned int ip_v:4;		//版本
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
    unsigned int ip_v:4;		/* version */
    unsigned int ip_hl:4;		/* header length */
#endif
    uint8_t ip_tos;				//tos服务类型
    unsigned short ip_len;		//整个ip包的总长度
    unsigned short ip_id;		//标识 每发送一个数据报,其值就加1
    unsigned short ip_off;		//ip分片相关
#define	IP_RF 0x8000			/* reserved fragment flag */
#define	IP_DF 0x4000			/* dont fragment flag */
#define	IP_MF 0x2000			/* more fragments flag */
#define	IP_OFFMASK 0x1fff		/* mask for fragmenting bits */
    uint8_t ip_ttl;				//生命周期
    uint8_t ip_p;				//协议
    unsigned short ip_sum;		//src校验和
    struct in_addr ip_src, ip_dst;	//源ip和目的ip
};

//tcp协议结构体
struct tcphdr
  {
    __extension__ union
    {
      struct
      {
	uint16_t th_sport;	//源端口号
	uint16_t th_dport;	//目的端口号
	tcp_seq th_seq;		//发送序列化
	tcp_seq th_ack;		//确认序列化
# if __BYTE_ORDER == __LITTLE_ENDIAN
	uint8_t th_x2:4;	//保留
	uint8_t th_off:4;	//tcp数据区的偏移(可以理解为tcp头的大小) 值是th_off * 4
# endif
# if __BYTE_ORDER == __BIG_ENDIAN
	uint8_t th_off:4;	/* data offset */
	uint8_t th_x2:4;	/* (unused) */
# endif
	uint8_t th_flags;   //tcp flasg  syn ack等标识,对应的值赋值推荐下面的结构体定义
# define TH_FIN	0x01
# define TH_SYN	0x02
# define TH_RST	0x04
# define TH_PUSH	0x08
# define TH_ACK	0x10
# define TH_URG	0x20
	uint16_t th_win;	//窗口大小
	uint16_t th_sum;	//crc校验和
	uint16_t th_urp;	//紧急指针
      };
      struct //和上面结构体类似 但是标志位赋值更为方便
      {
	uint16_t source;
	uint16_t dest;
	uint32_t seq;
	uint32_t ack_seq;
# if __BYTE_ORDER == __LITTLE_ENDIAN
	uint16_t res1:4;
	uint16_t doff:4;
	uint16_t fin:1;
	uint16_t syn:1;
	uint16_t rst:1;
	uint16_t psh:1;
	uint16_t ack:1;
	uint16_t urg:1;
	uint16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
	uint16_t doff:4;
	uint16_t res1:4;
	uint16_t res2:2;
	uint16_t urg:1;
	uint16_t ack:1;
	uint16_t psh:1;
	uint16_t rst:1;
	uint16_t syn:1;
	uint16_t fin:1;
# else
#  error "Adjust your <bits/endian.h> defines"
# endif
	uint16_t window;
	uint16_t check;
	uint16_t urg_ptr;
      };
    };
};

1.2.1 校验和算法

unsigned short crcsum(unsigned short *addr,int len)
{
    int nleft=len;
    unsigned int sum=0;
    unsigned short *w=addr;
    unsigned short answer=0;

    while (nleft > 1) {
        sum+=*w++;
        nleft-=2;
    }

    if (nleft == 1) {
        *(unsigned char *)(&answer)=*(unsigned char *)w;
        sum+=answer;
    }

    sum=(sum>>16)+(sum&0xffff);
    sum+=(sum>>16);
    answer=~sum;

    return answer;
}

1.2.2 ip封包

ip报文的校验和计算

iphead->ip_sum = htons(crcsum((unsigned short *)iphead,sizeof(struct ip)));

ip数据包的检验和只需要把ip头进行计算

ip封包函数

int ip_packet(struct ip *iphead,char *src_ip,char *dst_ip,uint16_t len)
{
    if (!iphead) return -1;

    iphead->ip_v = 4;                   //版本
    iphead->ip_hl = 5;                  //ip头的长度  ip_hl *4
    iphead->ip_tos = 0;                 //tos服务类型
    iphead->ip_len = htons(len);        //整个ip数据包的总长度
    iphead->ip_id = htons(54050);       //标识 每发送一个数据报,其值就加1 (随机给)
    //3位标志字段地第一位保留。第二位表示"禁止分片"。如果设置了这个位,IP模块将不对数据报进行分片。在这种情况下,如果IP数据报长度超过MTU的话,IP模块将丢弃该数据报并返回一个ICMP差错报文。第三位表示“更多分片”。除了数据报的最后一个分片外,其他分片都要把它置1
    iphead->ip_off = 0;
    iphead->ip_off = htons(iphead->ip_off |= IP_DF);
    iphead->ip_ttl = 64;                //生命周期
    iphead->ip_p = IPPROTO_TCP;         //协议
    iphead->ip_sum = 0;                 //src校验和 等整个数据包填充完成后计算
    inet_aton(src_ip, &iphead->ip_src); //源ip
    inet_aton(dst_ip, &iphead->ip_dst); //目的ip

    iphead->ip_sum = htons(crcsum((unsigned short *)iphead,sizeof(struct ip)));

    return 0;
}

简单的封装一下ip数据包。大部分字段的值可以使用抓包工具去查看

1.2.3 tcp封包

tcp报文的校验和计算

不同于ip校验和,tcp计算校验和需要整个tcp报文加上伪首部

伪首部的定义

struct tcp_checksum
{
    struct in_addr ip_src;		//源地址
    struct in_addr ip_dst;		//目的地址
    uint8_t reserve;            //保留
    uint8_t protocol;           //协议  tcps是6 IPPROTO_TCP
    uint16_t len;               //tcp报文的大小
    uint8_t data[0];			//可以是tcp报文 / udp报文
};

校验函数

#define CTCPHEADLEN 12

int tcp_checksum(struct tcphdr *tcphead,struct in_addr ip_src,struct in_addr ip_dst,int len)
{
    //tcp + 伪首部长度
    struct tcp_checksum *tcs = calloc(1,len + CTCPHEADLEN);

    tcs->ip_src = ip_src;
    tcs->ip_dst = ip_dst;
    tcs->protocol = IPPROTO_TCP;
    tcs->reserve = 0;
    tcs->len = htons(len);
    memcpy(&(tcs->data),tcphead,len);

    tcphead->th_sum = crcsum((unsigned short *)tcs,len + CTCPHEADLEN);

    free(tcs);

    return 0;
}

tcp封包函数

int tcp_packet(struct tcphdr *tcphead,uint16_t sport,uint16_t dport,uint8_t headlen)
{
    if (!tcphead) return -1;

    tcphead->th_sport = htons(sport);     //源端口号
    tcphead->th_dport = htons(dport);     //目的端口号
    tcphead->th_seq = htonl(12345);       //发送序列化
    tcphead->th_ack = 0;                  //确认序列化
    tcphead->th_off = headlen >> 2;       //tcp数据区的偏移  th_off * 4
    tcphead->th_flags = 0;                //清空flags位   这个地方是tcp中比较重要的地方 syn ack等标志位这个函数不做封装
    tcphead->th_win = htons(64240);       //窗口大小
    tcphead->th_sum = 0;                  //crc校验和
    tcphead->th_urp = 0;                  //紧急指针

    return 0;
}

这个函数没有算校验和 和 tcp标志位的封装,函数可用于二次封装

1.2.4 发包

使用sendto函数

//填充地址
struct sockaddr_in dest_addr = {0};
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(DST_PORT);
inet_aton(DST_IP, &dest_addr.sin_addr);

int ret = sendto(sfd,buf,ntohs(iphead->ip_len),0,(struct sockaddr *)&dest_addr,sizeof(struct sockaddr_in));

必须使用sendto函数。因为我踩过坑之前我构造了ip+tcp包去使用send函数去发送,结构报了 “Destination address required” 的错误

不要觉得构造了ip头,应该不需要发送ip。切记切记大坑

1.3 数据链路层

数据链路层的原始套接字使用

int sfd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_xxx));

AF_PACKET  表示使用数据链路层的原始套
SOCK_DGRAM 表示数据是从网络层开始的  SOCK_RAW表示数据是包含mac头的
ETH_P_xxx  协议,可以是ip arp等

第三个参数的取值在 /usr/include/linux/if_ether.h

#define ETH_P_LOOP	0x0060		/* Ethernet Loopback packet	*/
#define ETH_P_PUP	0x0200		/* Xerox PUP packet		*/
#define ETH_P_PUPAT	0x0201		/* Xerox PUP Addr Trans packet	*/
#define ETH_P_TSN	0x22F0		/* TSN (IEEE 1722) packet	*/
#define ETH_P_ERSPAN2	0x22EB		/* ERSPAN version 2 (type III)	*/
#define ETH_P_IP	0x0800		/* Internet Protocol packet	*/
#define ETH_P_X25	0x0805		/* CCITT X.25			*/
#define ETH_P_ARP	0x0806		/* Address Resolution packet	*/
...

比较常用的是

ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧
ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧
ETH_P_RARP 0x8035 只接受发往本机mac的rarp类型的数据帧
ETH_P_ALL 0x3 接收发往本机mac的所有类型的数据帧, 接收从本机发出的所有类型的数据帧.

在第三个参数如果只写一种协议,那么发往接受只能是这个协议,并且无法接受发往别的地方的数据包,只能接受

如果是ETH_P_ALL那么是可以接受发往别的地方的数据包,这个可以用于做抓包

1.3.1发包

使用sendto函数

#include <linux/if_packet.h>

//填充这个结构
struct sockaddr_ll {
    unsigned short sll_family; /* Always AF_PACKET */
    unsigned short sll_protocol; /* Physical-layer protocol */
    int sll_ifindex; /* Interface number */
    unsigned short sll_hatype; /* ARP hardware type */
    unsigned char sll_pkttype; /* Packet type */
    unsigned char sll_halen; /* Length of address */
    unsigned char sll_addr[8]; /* Physical-layer address */
};

和sockaddr_in类似。
    
sockaddr_ll addr;
sendto(fd,buf,len,0,(struct sockaddr *)&addr,sizoef(addr));

没发过链路层的包给不了填充的代码。

2.原始套接字案例

2.1 syn tcp包发送

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <sys/socket.h>

#define SET_U_1(N,D) (*((uint8_t *)(N++)) = D)
#define SET_U_2(N,D) do{ \
    (*((uint16_t *)(N)) = htons(D));N+=2;\
}while(0)

int tcp_syn_opt(struct tcphdr *tcphead)
{
    uint8_t *p = (uint8_t *)(tcphead + 1);

    //最大报文段长度(MSS)选项 kind = 2
    SET_U_1(p,TCPOPT_MAXSEG);
    SET_U_1(p,4);
    SET_U_2(p,1460);
    
    //窗口扩大因子选项 kind = 2
    SET_U_1(p,TCPOPT_NOP);
    SET_U_1(p,TCPOPT_WINDOW);
    SET_U_1(p,3);
    SET_U_1(p,8);

    //选择性确认(Selective Acknowledgment,SACK)选项 kind=4
    SET_U_1(p,TCPOPT_NOP);
    SET_U_1(p,TCPOPT_NOP);
    SET_U_1(p,TCPOPT_SACK_PERMITTED);
    SET_U_1(p,2);

    return 0;
}

#define DST_IP "192.168.224.132"
#define DST_PORT 80

int main(int argc,char *argv[])
{
    //接受tcp报文
    int sfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
    if (sfd == -1) {
        perror("create socket err");
        return -1;
    }
	
    //开启自己构造ip包
    int ip_on = 1;
    setsockopt(sfd, IPPROTO_IP, IP_HDRINCL, &ip_on, sizeof(int));

    //缓冲器定义
    char buf[BUFFSIZE] = {0};
    struct ip *iphead = (struct ip *)buf;
    struct tcphdr *tcphead = (struct tcphdr *)(iphead + 1);

    //封装ip包
    ip_packet(iphead,"172.30.201.131",DST_IP,IPPACKSIZE + TCPPACKSIZE + TCPSYNOPTSIZE);
    //封装tcp包
    tcp_packet(tcphead,60334,DST_PORT,32);
    //tcp可选字段 (抓一个syn跟着填充)
    tcp_syn_opt(tcphead);
    
    //tcp数据包的长度
    uint16_t len = ntohs(iphead->ip_len) - sizeof(struct ip);
    tcp_checksum(tcphead,iphead->ip_src,iphead->ip_dst,len);

    //构造地址,发包
    struct sockaddr_in dest_addr = {0};
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(DST_PORT);
    inet_aton(DST_IP, &dest_addr.sin_addr);

    int ret = sendto(sfd,buf,ntohs(iphead->ip_len),0,(struct sockaddr *)&dest_addr,sizeof(struct sockaddr_in));
    //int ret = send(sfd,buf,ntohs(iphead->ip_len),0);
    if (ret == -1) {
        perror("send pack error");
        return -1;
    }
    
    char ip_src[IPLEN] = {0};
    char ip_dst[IPLEN] = {0};

    while (1) {
        memset(buf,0,BUFFSIZE);
        recv(sfd,buf,BUFFSIZE,0);

        memcpy(ip_src,inet_ntoa(iphead->ip_src),IPLEN);
        memcpy(ip_dst,inet_ntoa(iphead->ip_dst),IPLEN);
        uint16_t sport = ntohs(tcphead->source);
        uint16_t dport = ntohs(tcphead->dest);
		//过滤输出
        if (sport == DST_PORT) {
            printf("src ip %s port %d - dst ip %s port:%d\n",ip_src,sport,ip_dst,dport);
            printf("ack flag = %d\n",tcphead->ack);
            printf("syn flag = %d\n",tcphead->syn);
            printf("seq num = %u\n",ntohl(tcphead->th_seq));
            printf("ack num = %u\n",ntohl(tcphead->th_ack));
        }
    }

    return 0;
}
  • “172.30.201.131” 是我本地WSL的地址

  • “192.168.224.132” 是虚拟机的地址

  • 重复的代码不再展现

看效果:

1.在"192.168.224.132" 是虚拟机启动一个80tcp服务,打开Wireshark

在这里插入图片描述

2.在windows打开抓包工具(切记选对网卡),在wsl上编译完代码

在这里插入图片描述

3.查看结果

windows

在这里插入图片描述

虚拟机

在这里插入图片描述

可以看到回的包中的ack数字,和我们一开始填充tcp seq刚好是+1的关系。说明我们正确的收到了回应包。

当然先声明代码仅供学习参考。如果你只是想实验syn泛洪攻击可以使用虚拟机试用。

这段代码可以去判断对端主机的端口有没有开,如果你收到对应的ack包那么可以确认对端端口是看。当然要设置一个超时时间。

多端口扫描可以创建多个这样的原始套接字,然后开这些套接字加入到epoll去管理,让每个fd携带超时时间,当epoll超时返回是去判断。这样子去实现端口扫描。当然这个思路仅供参考,由于我也没这个需求所以没有相关代码。

2.2 简单抓包工具

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <net/if_packet.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <netpacket/packet.h>
#include <sys/socket.h>

#define SRCIP "172.16.253.52"
#define SRCIPLEN 13
#define IPLEN 16

struct port_flow
{
    uint8_t  port_type;
    char     ip[IPLEN];
    uint16_t port;
    uint64_t fsum;
};

#define PORT_TCP 1
#define PORT_UDP 2

struct flow_sum
{
    time_t stime;       //起始统计流量的时间
    time_t tl;          //统计时长
    time_t etime;       //结束时间
    struct port_flow in_flow[10];
    struct port_flow out_flow[10];
};

struct tcp_opt
{
	uint8_t kind;
	uint8_t length;
};

int main(int argc,char *argv[])
{
    char buf[2048] = {0};

    //创建数据链路层的套接字 SOCK_DGRAM 表示内核封装mac层接收的数据是从ip层开始的 SOCK_RAW可以自己处理mac的数据
    int sfd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));
    if (sfd == -1) {
        perror("create socket err");
        return -1;
    }

    struct ip *iphead = (struct ip *)buf;
    struct tcphdr *tcphead = (struct tcphdr *)(iphead + 1);
	struct tcp_opt *t_op = (struct tcp_opt *)(tcphead + 1);
	//设置超时时间
    struct flow_sum flow = {0};
    flow.stime = time(NULL);
    flow.tl = 10;
    flow.etime = flow.stime + flow.tl;

    char ip_src[IPLEN] = {0};
    char ip_dst[IPLEN] = {0};

    while (1) {
		memset(buf,0,sizeof(buf));
        int n = recv(sfd,buf,sizeof(buf),0); //抓包
        if (n <= 0) {
            perror("recvform error");
            continue;
        }

        memcpy(ip_src,inet_ntoa(iphead->ip_src),IPLEN);
        memcpy(ip_dst,inet_ntoa(iphead->ip_dst),IPLEN);
        uint16_t sport = ntohs(tcphead->source);
        uint16_t dport = ntohs(tcphead->dest);
		//tcp协议过滤
        if (iphead->ip_p == IPPROTO_TCP) {
            //端口过滤
            if ((dport == 80) || (sport == 80)) {
                struct port_flow *pf = &flow.in_flow[0];

                memcpy(pf->ip,ip_src,IPLEN);
                pf->port = ntohs(tcphead->source);
                pf->port_type = PORT_TCP;
                pf->fsum += (ntohs(iphead->ip_len) + sizeof(struct ether_header) + 8 + 4);

                printf("src ip %s port %d - dst ip %s port:%d\n",ip_src,sport,ip_dst,dport);
                printf("ack flag = %d\n",tcphead->ack);
                printf("syn flag = %d\n",tcphead->syn);
                printf("seq num = %u\n",ntohs(tcphead->th_seq));
                printf("ack num = %u\n",ntohs(tcphead->th_ack));

                //printf("ip+tcp+data size %d\n %s\n",(ntohs(iphead->ip_len)),(char *)t_op);
            }
        }

        if (time(NULL) > flow.etime) {
            break;
        }
    }
	//10s内http的流量
    printf("10s http in flow %ld\n",flow.in_flow[0].fsum/10);

    return 0;
}

这段代码起源于我的业务,由于我需要去统计各个端口的出入流量而写的demo。

代码比较简单,创建完原始套接字之后直接去recv即可

让我们对比tcpdump的效果

可以看看这四元组是一样的

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值