本文的主线是一个使用原始套接字发送数据的程序,工作在数据链路层,采用自定义以太网帧协议,靠MAC地址识别目标主机。所以不涉及到IP地址和端口号。程序主要用于互联的两台机器之间进行丢帧率计算。以下部分都是围绕它而展开说明的。内容分为以下几部分:
- 原始套接字概述
- 原始套接字的创建
- 自定义协议
- 发送端程序流程、实现
- 接收端程序的开发
一、原始套接字概述
先来看看socket函数原型:
int socket(int domain, int type, int protocol);
我们知道,当进行网络编程的时候,通常会用到socket函数。而且主要有两种,一种是第二个参数为SOCK_STREAM,面向连接的 Socket,针对于面向连接的TCP 服务应用,另外一种是第二个参数为SOCK_DGRAM,面向无连接的 Socket,针对于无连接的 UDP 服务应用。对于TCP或UDP,我们不能修改其头部的格式,只能依照系统开放给我们定义好的头部进行编程开发。而今天,介绍的原始套接字却跟前面两种大不相同。原始套接字可以提供普通的TCP和UDP套接字不支持的能力。比如:
- 发送一个自定义的以太网帧。(这将是本节实现的重点)
- 发送一个 ICMP 协议包。
- 发送一个自定义的 IP 包。
- 分析所有经过网络的包,而不管这样包是否是发给自己的。
- 伪装本地的 IP 地址。
注意:原始套接字需要在root权限下使用!
二、原始套接字的创建
创建原始套接字有如下步骤:
这里把socket函数第一个参数指定为PF_PACKET,因为本文程序利用PF_PACKET接口操作链路层的数据。
第二个参数指定为SOCK_RAW。第三个参数指定为一个字符串”0x980A”来标识自定义协议。
int sock;
//这里把protocol自定义为0x980A
if((sock = socket(PF_PACKET, SOCK_RAW, htons(protocol))) < 0)
{
perror("socket");
return -1;
}
创建完成之后就可以利用函数sendto函数和recvfrom函数来发送和接收数据链路层的数据包了。
三、自定义协议
自定义数据包格式:
typedef struct {
unsigned char tmac[6]; //目的主机mac地址
unsigned char smac[6]; //本机mac地址
unsigned short type; //自定义为0x980A
unsigned short len; //暂定为46
unsigned char reserve[3]; //保留,暂填写为00
unsigned char opcode; //探测请求 = 0x08;响应 = 0x00
unsigned short seqnum; //报文序号,从0-65535
unsigned int datetime; //填充当前发送测试包时间,精确到毫秒
unsigned char content[34];//填充,以0123456798ABCDEF0123456……模式循环
} __attribute__((packed)) stbtest_packet_t ;
其中,attribute ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
四、发送端程序流程、实现
先来看下程序的主函数:
int main(int argc, char **argv){
stbtest_packet_t packet;
struct sockaddr_ll addrSrc;
u_int nLen = 0;
int ret = -1,i = 0;
int fd = -1;
char pidstr[20] = {
0};
struct sockaddr_ll socketAddrReply;
//解析程序输入参数,采用./stb_test 50 30 00:00:00:00:00:00 11:11:11:11:11:11格式
//50 表示发送频率,30表示发送周期,00:00:00:00:00:00表示本机的mac地址,11:11:11:11:11:11表示目的mac
for(i=0; i<argc; i++)
{
printf("%d ",i);
printf("=%s\n",argv[i]);
}
if(argc < 4)
{
printf("usage:%s <freqency> <send-period> <src-mac> <dst-mac>\r\n", argv[0]);
printf("eg: ./stb_test 50 30 dc:0e:a1:68:6a:98 dc:0e:a1:68:6a:98\r\n");
exit(1);
}
stbTestCfg.freqency = atoi(argv[1]);
stbTestCfg.send_period = atoi(argv[