原始套接字

Posted cs0915

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原始套接字相关的知识,希望对你有一定的参考价值。

标准套接字不能对IP首部或TCP、UDP首部进行操作,如果开发底层的应用,比如发送一个自定义的IP包、UDP包、TCP包、ICMP包,伪装本机IP地址,捕获所有经过本机的数据包,就要用到原始套接字。

一、面向链路层的原始套接字

可以获取链路层的数据包

创建原始套接字

<netinet/in.h>
int socket(int family,int type, int protocol);

family:面向链路层取PF_PACKET;type:SOCK_RAW,接收的帧包含MAC头部信息,发送帧时也要自己加上MAC头部信息;SOCK_DGRAM,收到的帧无MAC头部信息,已经经过处理,发送时也无需添加头部信息;protocol:指定要收发的数据包类型,ETH_P_IP、ETH_P_ARP、ETH_P_RARP、ETH_P_ALL.注意传入参数时候,要htons转换,比如(ETH_P_ALL)。

接收数据

ssize_t recvfrom(int sock, void* buf, size_t len, int flags, struct sockaddr* from, socklen_t* fromlen);

倒数第二个参数传入时需要的是一个sockaddr_ll* 类型,

使用如下

struct sockaddr_ll sa_recv;
recvfrom(fd,buf,sizeof(buf), (struct sockaddr* )& sarecv, &sa_len)

sockaddr_ll结构体——表示的是一个与物理设备无关的物理层地址     P542

struct sockaddr_ll
{
  unsigned short sll_family;
  __be16 sll_protocol;
  int sll_ifindex;
  unsigned short sll_hatype;
  unsigned char sll_pkttype;
  unsigned char sll_halen;//物理层地址
  unsigned char sll_addr;//地址长度
}

sll_ifindex为网络接口类型,单网卡时可以为0,表示处理所有接口,对于多网卡,则要获取网卡的接口索引然后赋值

struct sockaddr_ll sll;
struct ifreq ifr;
strcpy(ifr.ifr_name, "eth0");
ioctl(sockfd, SIOCGIFINDEX, &ifr);
sll.sll_ifindex=ifr.ifr_ifindex;

发送数据sendto

struct sockaddr_ll sa;
sendto(fd,buf sizeof(buf), 0, (struct sockaddr* )&sa, sizeof(struct sockaddr_ll);

 

以太网帧,头文件<linux/if_ether.h>      P546

struct ethhdr
{
 unsigned charr h_dest[ETH_ALEN];//目的mac地址
 unsigned cahr h_source[ETH_ALEN];//源mac地址
 u16 h_proto;//网络层所使用的协议类型
}

获取网络接口信息

网络接口请求结构体      P548

struct ifreq
{
  union
 {
   char ifrn_name[IFNAMSIZ];//网络接口名字
 }ifr_ifrn;
 union
 {
   struct sockaddr ifru_addr;
   struct sockaddr ifru_dstaddr;
   struct sockaddr ifru_broadaddr;
   struct sockaddr ifru_netmask;
  struct sockaddr ifru_hwaddr;
...省略 } }

获取网络接口信息,一般把网络接口名字传给ifrn_name,调用ioctl来获取所需要的信息。

获取mac地址信息        P549

struct sockaddr_ll device;
struct ifreq ifr;
int sd=socket(PF_PACKET,SOCK_DGRAM, htons(ETH_P_ALL));
memcpy(ifr.ifr_name,"ens32",sizeof(struct ifreq));
ioctl(sd,SIOCGIFHWADDR,&ifr)
unsigned char dest_mac[6]={0};
memcpy(dest_mac,ifr.ifr_hwaddr.sa_data,6);
close(sd);

默认情况下,从任何接口收到的符合指定协议的数据报文都会出传到原始PACKET套接字口,而使用bind系统调用并以一个sockaddr_ll结构体对象将PACKET套接字与某个接口网络像绑定,可以使我们的原始套接字只接收指定接口的数据报文。

绑定网络接口

struct sockaddr_ll sa;
int fd=socket(PF_PACKET,SOCK_RAW,htons(0x8902));
//初始化sa memset(
&sa,0,sizeof(sa)); sa.sll_family=PF_PACKET; sa.sll_protocol=htons(0x8902); strcpy(ifr.ifr_name,if_name); ioctl(fd,SIOCGIFFLAGES,&ifr); ioctl(fd,SIOCGIFINDEX,&ifr); sa.sll_ifindex=ifr.ifr_ifindex;

//绑定 bind(fd,(
struct sockaddr*)&sa, sizeof(struct sockaddr_ll);

 

混杂模式

网卡工作模式

广播模式:接收广播帧

多播模式:不管是不是组内成员,均接收多播帧

直接模式:只接收发给自己的帧

混杂模式:接收所有经过自己网卡的帧

无论套接字是否绑定网卡,都会全部接收通过本主机(可能有多个网卡)所有网卡的所有帧。

设置网卡混杂模式    P579

struct ifreq ifr;
strcpy(ifr.ifr_name,if_name);
ioctl(fd,SIOCGIFFLAGS,&ifr);
ifr.ifr_flags|=IFF_PROMISC;
ioctl(fd,SIOCSIFFLAGS,&ifr);

二、面向IP层的原始套接字

可以获取网络层的数据包

socket(AF_INET,SOCK_RAW,protocol);

protocol:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP、IPPROTO_RAW,可以多个进行或操作。此处不需要htons,与链路层原始套接字不同。

接收包,能接收到完整的IP包,包括IP头部。

发送包时,不用IP头部,内核自动封装,即发送从IP后的第一个字节开始。除非设置了IP_HDRINCL的socket项。不能收到自己发送出去的包,需要自己组织TCP UDP ICMP等头部。

获取网卡IP地址信息    P598

int sock=socket(AF_INET,SOCK_RAW, IPPROTO_TCP);
strcpy(ifr.ifr_name,"ens32");
ioctl(sock,SIOCGIFADDR,&ifr);
printf("%s
",inet_ntoa(((struct sockaddr_in*)&(ifr.ifr_addr))->sin_addr));

 

以上是关于原始套接字的主要内容,如果未能解决你的问题,请参考以下文章

使用原始套接字 (c++)

即使设置 cap_net_raw 也无法在 linux 容器中打开原始套接字

原始套接字的数据包碎片

为啥数据不通过 Windows 中的原始套接字发送

如何在 Python 中使用原始套接字?

原始 UDP 套接字卡在 recvfrom 上