原始套接字
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));
以上是关于原始套接字的主要内容,如果未能解决你的问题,请参考以下文章