一.原始套接字概述
raw socket,即原始套接字,可以接收本机网卡上的数据帧或者数据包,对于监听网络的流量和分析是很有作用的,一共可以有4种方式创建这种socket。
在我们学习TCP和UDP通信的时候,所用的知识点其实已经可以满足我们的日常开发需求,那么我们学习原始套接字,加强对网络的理解
在使用网络的过程中,以下情况如何解决:
能否截获网络中的数据?
怎样发送一个自定义的 IP 包?---今天的内容
怎样伪装本地的 IP、 MAC?
网络攻击是怎么回事?
路由器、交换机怎样实现?
1、原始套接字的应用方向
原始套接字(SOCK_RAW)
1、 一种不同于 SOCK_STREAM、 SOCK_DGRAM 的套接字,它实现于系统核心
2、 可以接收本机网卡上所有的数据帧(数据包) ,对于监听网络流量和分析网络数据很有作用
3、 开发人员可发送自己组装的数据包到网络上
4、 广泛应用于高级网络编程
5、 网络专家、黑客通常会用此来编写奇特的网络程序
网络通信协议部分:
|
二.创建原始套接字
int socket(PF_PACKET, SOCK_RAW, protocol)
功能:
创建链路层的原始套接字
参数:
protocol:指定可以接收或发送的数据包类型(协议)
ETH_P_IP:IPV4 数据包
ETH_P_ARP:ARP 数据包
ETH_P_ALL:任何协议类型的数据包
返回值:
成功(>0):链路层套接字
失败(<0):出错
int socket(PF_PACKET, SOCK_RAW, ETH_P_ALL)
1、原始套接字获取套接字的方式:
#include <sys/socket.h>
#include <netinet/ether.h>
sock_raw_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
//已过时,不再使用
sock_raw_fd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL));
2、使用tcp或者UDP的时候,第三个参数的设置:
socket(AF_INET, SOCK_STREAM, 0)
三.数据包详解
使用原始套接字进行编程开发时,首先要对不同协议的数据包进行学习,需要手动对 IP、 TCP、 UDP、 ICMP 等包头进行组装或者拆解
/usr/include/net
在 TCP/IP 协议栈中的每一层为了能够正确解析出上层的数据包,从而使用一些“协议类型”来标记
组包和解包的过程示意图:
比如我们要使用TCP--协议类型6
UDP--协议类型17
1、组装/拆解 udp 数据包流程
发送数据--组包
接收数据--解包
2、MAC报文头(链路层):
目的地址和源地址:MAC地址
类型:协议类型
3、IP数据报头(网络层):
1.版本: IP协议的版本。 通信双方使用过的IP协议的版本必须一致, 目前最广泛使用的IP协议版本号为3
4( 即IPv4 )
2.首部长度: 单位是32位( 4字节)
3.服务类型: 一般不适用, 取值为0。 前3位: 优先级, 第4-7位: 延时, 吞吐量, 可靠性, 花费。 第8
位保留
4.总长度: 指首部加上数据的总长度, 单位为字节。 最大长度为65535字节。
5.标识( identification) : 用来标识主机发送的每一份数据报。 IP软件在存储器中维持一个计数器, 每
产生一个数据报, 计数器就加1, 并将此值赋给标识字段。
6.标志( flag) : 目前只有两位有意义。
标志字段中的最低位记为MF。 MF=1即表示后面“还有分片”的数据报。 MF=0表示这已是若干数据
报片中的最后一个。
标志字段中间的一位记为DF, 意思是“不能分片”, 只有当DF=0时才允许分片
7.片偏移: 指出较长的分组在分片后, 某片在源分组中的相对位置, 也就是说, 相对于用户数据段的
起点, 该片从何处开始。 片偏移以8字节为偏移单位。
8.生存时间: TTL, 表明是数据报在网络中的寿命, 即为“跳数限制”, 由发出数据报的源点设置这个字
段。 路由器在转发数据之前就把TTL值减一, 当TTL值减为零时, 就丢弃这个数据报。 通常设置为32、
64、 128。
9.协议: 指出此数据报携带的数据时使用何种协议, 以便使目的主机的IP层知道应将数据部分上交给
哪个处理过程, 常用的ICMP(1),IGMP(2),TCP(6),UDP(17),IPv6( 41)
10.首部校验和: 只校验数据报的首部, 不包括数据部分。
11.源地址: 发送方IP地址
12.目的地址: 接收方IP地址
13.选项: 用来定义一些任选项; 如记录路径、 时间戳等。 这些选项很少被使用, 同时并不是所有主机
和路由器都支持这些选项。 一般忽略不计。
4、UDP数据报头(传输层):
5、TCP数据报头(传输层):
TCP/IP协议组包的结构:
原始套接字可以接受MAC层传输的数据,换句话说,原始套接字可以接收到我们当前主机通信的所有IP的数据,那么我们就可以直接使用接收函数去进行接收数据,然后进行查看。
6、ARP数据报头(链路层)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/ether.h>
int main(int argv ,char *argc[])
{
int sock_raw_fd =0;
unsigned char buf[1500]="";
sock_raw_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
printf("sock_raw_fd:%d\n",sock_raw_fd);
while(1)
{
//目前处于MAC层
unsigned char dst_mac[18]="";
unsigned char src_mac[18]="";
unsigned short type=0;
int len=recvfrom(sock_raw_fd,buf,sizeof(buf),0,NULL,NULL);
if(len<0)
{
perror("recvfrom:");
exit(-1);
}
sprintf( dst_mac,"%02x:%02x:%02x:%02x:%02x:%02x",buf[0],buf[1],buf[2],buf[3],buf[4],buf[5]);
sprintf( src_mac,"%02x:%02x:%02x:%02x:%02x:%02x",buf[6],buf[7],buf[8],buf[9],buf[10],buf[11]);
printf("src_mac:%s--->dst_mac:%s\n",src_mac,dst_mac);
type=ntohs(*(unsigned short *)(buf+12));
printf("type-->%x\n",type);
sleep(1);
if(type==0x0800)
{
unsigned int ip_len=0;
unsigned char *ip_addr=buf+14;
unsigned char ip_type=0;
//解析IP层数据
printf("/************************IP协议*************************/");
ip_len=(buf[14]&0x0f)*4;
printf("\nip_len:%d\n",ip_len);
//获取IP数据报的总长度
//IP中包含的协议类型
ip_type=buf[23];
printf("ip_type-->%x\n",ip_type);
if(ip_type==0x06)
{
unsigned short dst_port=0;
unsigned short src_port=0;
printf("/************************TCP协议*************************/\n");
src_port=ntohs(*(unsigned short *)(buf+14+ip_len));
dst_port=ntohs(*(unsigned short *)(buf+14+ip_len+2));
printf("src_port:%d---->dst_port:%d\n",src_port,dst_port);
}
else if(ip_type==17)
{
printf("/************************UDP协议*************************/\n");
}
}
else if(type==0x0806)
{
printf("/************************ARP协议*************************/\n");
}
else if(type==0x8035)
{
printf("/************************RARP协议*************************/\n");
}
}
close(sock_raw_fd);
return 0;
}
四.ARP 数据解析
如何去获取ARP表格内容???
首先在学习ARP数据解析,必须清楚MAC报文头内容:
1.发送ARP请求--以广播的形式发送的
获取当前局域网的IP和MAC
(UBUNTU中的ARP表格内容)
Dest MAC: 0xff 0xff 0xff 0xff 0xff 0xff
Src MAC: 0x00 0x0c 0x29 0x21 0x96 0xce
帧类型:0x08 0x06
硬件类型:0x00 0x01
协议类型:0x08 0x00
硬件地址长度:0x06
协议地址长度:0x04
OP: 0X00 0X01
发送端的以太网地址:0x00 0x0c 0x29 0x21 0x96 0xce
发送端的IP地址:192.168.2.93
目的以太网地址:0x00 0x00 0x00 0x00 0x00 0x00
目的IP地址:192.168.2.17
2.接收ARP应答
Dest MAC: 0x00 0x0c 0x29 0x21 0x96 0xce
Src MAC: 88-A4-C2-EA-5D-20
帧类型:0x08 0x06
硬件类型:0x00 0x01
协议类型:0x08 0x00
硬件地址长度:0x06
协议地址长度:0x04
OP: 0X00 0X02
发送端的以太网地址:88-A4-C2-EA-5D-20
发送端的IP地址:192.168.2.17
目的以太网地址:0x00 0x0c 0x29 0x21 0x96 0xce
目的IP地址:192.168.2.93
1、混杂模式
1、 指一台机器的网卡能够接收所有经过它的数据包,而不论其目的地址是否是它。
2、 一般计算机网卡都工作在非混杂模式下,如果设置网卡为混杂模式需要 root 权限
2、sendto 发送数据
sendto(sock_raw_fd, msg, msg_len, 0,(struct sockaddr*)&sll, sizeof(sll));
注意:
1、 sock_raw_fd:原始套接字
2、 msg:发送的消息(封装好的协议数据)
3、 sll:本机网络接口,指发送的数据应该从本机的哪个网卡出去,而不是以前的
目的地址
本机网络接口:
包含了网络传输过程中协议地址及类型
#include <netpacket/packet.h>
struct sockaddr_ll sll;
struct sockaddr_ll
ioctl函数的使用:
ssize_t my_sendto(int socket, const void *message, size_t length, char *if_name)
{
// 获取接口
struct ifreq ethreq;//获取接口--ens33
strncpy(ethreq.ifr_name, if_name, IFNAMSIZ);
if (-1 == ioctl(socket, SIOCGIFINDEX, ðreq))
{
perror("ioctl");
close(socket);
_exit(-1);
}
// 定义接口结构
struct sockaddr_ll sll;
bzero(&sll, sizeof(sll));
sll.sll_ifindex = ethreq.ifr_ifindex;
// 发送帧数据
int len = sendto(socket, message, length, 0, (struct sockaddr *)&sll, sizeof(sll));
return len;
}
3、获取当前局域网内的设备信息
1.使用ARP协议进行广播---发送
ssize_t my_sendto(int socket, const void *message, size_t length, char *if_name)
2.等待局域网内设备给回应--接收
recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/ether.h>
#include <pthread.h>
#include <netpacket/packet.h>
#include <sys/ioctl.h>
#include <net/if.h>
ssize_t my_sendto(int socket, const void *message, size_t length, char *if_name);
void *Rev_Data(void *arg);
int main(void)
{
int sock_raw_fd =0;
pthread_t p_th1;
// unsigned char rev_buf[64]="";
// unsigned char buf[1500]="";
sock_raw_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
printf("sock_raw_fd:%d\n",sock_raw_fd);
//创建一个线程
pthread_create(&p_th1,NULL,Rev_Data,&sock_raw_fd);
//线程分离
pthread_detach(p_th1);
//发送ARP请求
// for()
for(int i=1;i<=255;i++)
{
unsigned char buf[]={
0xff,0xff ,0xff, 0xff ,0xff ,0xff,
0x00 ,0x0c ,0x29 ,0x21 ,0x96, 0xce,
0x08 ,0x06,
0x00 ,0x01,
0x08, 0x00,
0x06,
0x04,
0X00 ,0X01,
0x00 ,0x0c ,0x29 ,0x21 ,0x96, 0xce,
192,168,2,93,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00,
192,168,2,i,
};
//my_sento函数进行发送
int sendlen=my_sendto(sock_raw_fd,buf,42,"ens33");
// printf("%d---%d\n",i,sendlen);
usleep(20000);
}
sleep(2);
close(sock_raw_fd);
return 0;
}
ssize_t my_sendto(int socket, const void *message, size_t length, char *if_name)
{
// 获取接口
struct ifreq ethreq;//获取接口--ens33
strncpy(ethreq.ifr_name, if_name, IFNAMSIZ);
if (-1 == ioctl(socket, SIOCGIFINDEX, ðreq))
{
perror("ioctl");
close(socket);
_exit(-1);
}
// 定义接口结构
struct sockaddr_ll sll;
bzero(&sll, sizeof(sll));
sll.sll_ifindex = ethreq.ifr_ifindex;
// 发送帧数据
int len = sendto(socket, message, length, 0, (struct sockaddr *)&sll, sizeof(sll));
return len;
}
void *Rev_Data(void *arg)
{
int sock_fd=0;
unsigned short op=0;
unsigned char src_mac[18]="";
unsigned char src_ip[16]="";
sock_fd=*(unsigned int *)arg;
printf("sock_fd-->%d\n",sock_fd);
unsigned char rev_buf[64]="";
while(1)
{
int rev_len= recvfrom(sock_fd,rev_buf,64,0,NULL,NULL);
if(rev_len<0)
{
printf("rev_error!!\n");
return 0;
}
op=ntohs(*(unsigned short *)(rev_buf+20));
// printf("op-->%d\n",op);
if(op==0x02) //应答信号
{
sprintf(src_mac,"%02x:%02x:%02x:%02x:%02x:%02x",
rev_buf[6],rev_buf[7],rev_buf[8],rev_buf[9],rev_buf[10],rev_buf[11]);
sprintf(src_ip,"%d.%d.%d.%d",rev_buf[28],rev_buf[29],rev_buf[30],rev_buf[31]);
printf("%s-->%s\n",src_ip,src_mac);
}
}
return NULL;
}
标签:IP,sock,raw,原始,fd,接字,include,buf
From: https://blog.csdn.net/weixin_48471271/article/details/141639405