一、通信协议的详细方案
前文说到,我们设计了这么一个BTP(BWB Transport Protocol)通信协议:
序号 | BTP字段名 | 占用空间 | 说明 |
---|---|---|---|
1 | 协议标识 | 1字节 | 0x42(大写的'B') |
2 | 协议版本 | 1字节 | 0x01(1.0版本) |
3 | 包类型 | 1字节 | 握手请求包:0x01 握手响应包:0x02 心跳请求包:0x03 心跳响应包:0x04 数据包:0x05 断开请求包:0x06 断开响应包:0x07 |
4 | 包序号 | 1字节 | 0x00~0xFF循环使用 |
5 | 数据长度 | 2字节 | 0x0000~0xFFFF |
6 | 数据 | 0~65535字节 | 要传输的数据,可以为空 |
7 | 校验和 | 4字节 | 采用经典的CRC32 |
(1)包序号
每次开始发送数据时,总是从0x00到0xFF随机挑选一个初始数字,发包和回包必须携带相同的数字,并且下一个包为这一个包序号+1,如果超过0xFF,则从0x00重新开始。
(2)握手请求包0x01与握手响应包0x02
a. 规定数据长度为0,数据为空。
b. 发送者发送握手请求包,接收者接收握手请求包后立即响应握手应答包。
c. 只发送一次,1秒超时则认为握手失败。
d. 握手必须确认协议标识和版本号,否则认为不是BTP包或者版本不相同进行丢弃(一般的协议都是返回一个带错误码的包,这里直接进行简单丢弃)。
(3)心跳请求包0x03与心跳响应包0x04
a. 规定数据长度为0,数据为空。
b. 在成功握手之后,每隔5秒发送一次心跳请求包,接收者必须立即回应心跳响应包,用来维护链路。
c. 1秒超时则认为链路断开。
(4)数据包0x05
携带着要发送的数据。
(5)断开请求包0x06和断开响应包0x07
a. 规定数据长度为0,数据为空。
b. 当接收者接收到发送者的断开请求包后,立即回应断开响应包,双方各自进行清理工作,结束通信。
OK,方案设计完成。
二、winpcap的发送者和接收者的实现
1、实现思路
前面已经实现了基本的发包和收包,要想实现发送者和接收者,核心是要让程序能够同时进行发包和收包。为了方便说明,仍然使用PA进行作为通信发起者,PB作为通信接收者。
方案一
对于PB来说,用一个全局变量维护当前状态,然后在回调函数里面收到包后进行发包操作即可。
对于PA来说,也使用全局变量维护当前状态,在发送第一个握手包之后,就陷入监听,然后在回调函数里实现类似的收包发包操作。
但这种方案维护起来很麻烦,比较差。
方案二(采用)
多线程,用全局变量来进行同步,使用互斥锁进行写操作。
【PB】
只需要进行反射发包,所以仅仅需要收到特定包后回送特定包,根据状态机前进,两个线程,一个主线程发射发包,一个计时线程计时心跳。
如果心跳超时,强行中断所有线程。
(这和直接接受心跳包然后计算时间差来判断有什么区别呢?区别在于,如果对方断开了连接,那么永远也接受不到心跳包,也就计算不了时间差了;而线程自己计时则能够自己控制)
【PA】
线程1为发包线程,在特定条件满足后,开始发送特定包(数据包)或进行特定操作(开启心跳线程)。
线程2为接收线程,在接收到特定包后(比如接收到握手包),进行唤醒操作(比如每隔1秒唤醒发包线程,每隔5秒同时唤醒心跳线程)。
线程3为心跳线程,在特定条件满足后,开始发送心跳包。
线程4为计时线程,如果超时,强行中断所有线程。
哎,就很舒服~但是实现起来比较麻烦,一步一步来。
2、实现过程
(1)配置pthread2.9.1 下载地址(密码:vyqi)
和前面配置winpcap类似,首先把pthread源代码文件解压到英文目录,比如E:\pthreads2.9.1
。
然后添加lib支持,告诉编译器链接的时候先去找这个库。调试→属性→配置属性→链接器→输入,添加:
pthreadVC2.lib;pthreadVCE2.lib;pthreadVSE2.lib;
最后添加包含目录和库目录,调试→属性→配置属性→VC++目录,添加:
包含目录添加 E:\pthreads2.9.1\Pre-built.2\include;
库目录添加 E:\pthreads2.9.1\Pre-built.2\lib\x86;
这里很可能还是没配置好,运行时还会报错:
这是因为之前配置的winpcap在很多软件安装的时候都会自动安装,比如安装wireshark都会提示你安装winpcap否则wireshark不会正常工作,他们把dll文件都拷贝进了C:\Windows\System32里面。所以之前能够找到winpcap的动态链接库dll,这里却找不到了。pthread很可能我们只用一次而已,拷贝进系统dll不可取,拷贝进项目dll不方便,因此我们依然采用设置的方法:
调试→属性→配置属性→调试→环境,添加:
path=E:\pthreads2.9.1\Pre-built.2\dll\x86
编译环境就OK了。但是我们这个exe是要放到windows xp下运行的,由于官方的lib是动态的,依然会提示错误,有兴趣可以看看 《pthread-win32静态库的编译和使用方法》 , 这时候我们采用复制dll的方法,把需要的dll赋值到EXE所在目录,反正都是虚拟机,无所谓,这样就能成功运行啦:
(2)编写接收者
BTPrecver.h
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#include <winsock.h>
#include <pthread.h>
#define SEND_BUFSIZE 1024
#define TIMER_SLEEPTIME 2000
#define ERROR_GENERAL -1
#define ERROR_FINDALLDEVS_FAILURE -2
#define ERROR_INTERFACES_NOT_FOUND -3
#define ERROR_BAD_INPUT -4
#define ERROR_OPEN_ADAPTER_FAILURE -5
#define ERROR_SENDING_FAILURE -6
#define ERROR_INVALID_DATALINK_TYPE -7
#define ERROR_COMPILE_FILTER_FALIURE -8
#define ERROR_SET_FILTER_FALIURE -9
#define ERROR_CREATE_THREAD -10
#define ERROR_BTP_HELLO_FAILED -11
#define ERROR_BTP_HEARTBEAT_FAILED -12
#define ERROR_BTP_BYE_FAILED -13
#define ERROR_BTP_TIMEOUT -14
#define ERROR_BAD_VERSION -15
#define BTP_HELLO_REQUEST 0x01
#define BTP_HELLO_RESPONSE 0x02
#define BTP_HEARTBEAT_REQUEST 0x03
#define BTP_HEARTBEAT_RESPONSE 0x04
#define BTP_DATA 0x05
#define BTP_BYE_REQUEST 0x06
#define BTP_BYE_RESPONSE 0x07
#define BTP_PROTOCOL 0x42
#define BTP_VERSION 0x01
typedef struct ETH_HEADER
{
u_char dest_mac[6];
u_char src_mac[6];
u_short etype;
}ETH_HEADER;
typedef struct BTP_HEADER{
u_char protocol;
u_char version;
u_char type;
u_char pid;
u_short data_length;
}BTP_HEADER;
typedef struct BTP_STATE{
int state; /* 状态:0=初始(可握手) 1=握手后(可心跳、可数据、可断开)*/
pthread_t pids[1]; /* 只有一个心跳线程 */
int timeout_flag; /* 心跳超时标记,每次收包都进行+1,如果计时线程发现两次的flag都相同,说明断开了 */
pcap_t *adapter; /* 网卡句柄 */
}BTP_STATE;
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);/* 抓包回调函数 */
void format_mac(LPSTR lpHWAddrStr, const unsigned char *HWAddr);/* mac地址格式化函数 */
int btp_send(ETH_HEADER* eth_header, BTP_HEADER *btp_header, u_char type);
void *timer(void *arg);
void kill_all(char *msg, int error_code);
void copy_mac(u_char *mac1, u_char *mac2);
char* rec_type(u_char type);
BTPrecver.c
#include "BTPrecver.h"
BTP_STATE btp_state;
int main()
{
char errbuf[PCAP_ERRBUF_SIZE]; /* 错误信息buffer */
u_int netmask; /* 掩码信息 */
char packet_filter[] = "ether src 00:50:56:C0:00:08 and ether dst 00:0C:29:86:B8:C8"; /* 过滤规则 */
struct bpf_program fcode; /* 存储编译好的过滤码 */
pcap_if_t *alldevs; /* 全部网卡列表 */
pcap_if_t *d; /* 一个网卡 */
int did; /* 选择的网卡ID */
int i = 0; /* 迭代 */
/*查找网卡*/
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) {
fprintf(stderr, "[ERROR] pcap_findalldevs error: %s\n", errbuf);
return ERROR_FINDALLDEVS_FAILURE;
}
/* 选择网卡d */
for (d = alldevs, i = 0; d; d = d->next) {
if (d->description)
printf("NO.%d: %s\n", ++i, d->description);
else
printf("[WARN] No description available\n");
}
if (i == 0) {
printf("[ERROR] No interfaces found! Make sure WinPcap is installed.\n");
return ERROR_INTERFACES_NOT_FOUND;
}
printf("[INFO] Enter the interface number (1-%d):", i);
scanf("%d", &did);
if (did < 1 || did > i) {
printf("[ERROR] Interface number out of range.\n");
pcap_freealldevs(alldevs);
return ERROR_BAD_INPUT;
}
for (d = alldevs, i = 0; i < did - 1; d = d->next, i++);
/* 打开网卡 */
if ((btp_state.adapter = pcap_open_live(d->name, /* 设备名 */
65536, /* 捕获数据包的长度(65536捕获所有数据包) */
1, /* 混杂模式(非0表示使用混杂模式) */
1000, /* 超时时间(0表示没有超时限制) */
errbuf /* 错误缓存(存储错误信息) */
)) == NULL) {
fprintf(stderr, "[ERROR] Unable to open the adapter. %s is not supported by WinPcap\n");
pcap_freealldevs(alldevs);
return ERROR_OPEN_ADAPTER_FAILURE;
}
/* 检查链路层类型 */
if (pcap_datalink(btp_state.adapter) != DLT_EN10MB) /* DLT_EN10MB指10Mb以太网 */
{
fprintf(stderr, "[ERROR] This program works only on Ethernet networks.\n");
pcap_freealldevs(alldevs);
return ERROR_INVALID_DATALINK_TYPE;
}
/* 检查地址类型 */
if (d->addresses != NULL) /* 如果有IP地址 */
netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; /* 使用第一个掩码 */
else /* 如果没有IP地址,说明是C类网络(局域网) */
netmask = 0xffffff; /* 掩码设置为255.255.255.0 */
/* 编译过滤器 */
if (pcap_compile(btp_state.adapter, &fcode, packet_filter, 1, netmask) < 0) /* 1表示自动进行优化 */
{
fprintf(stderr, "[ERROR] Unable to compile the packet filter. Check the syntax.\n");
pcap_freealldevs(alldevs);
return ERROR_COMPILE_FILTER_FALIURE;
}
/* 应用过滤器 */
if (pcap_setfilter(btp_state.adapter, &fcode) < 0)
{
fprintf(stderr, "[ERROR] Error setting the filter.\n");
pcap_freealldevs(alldevs);
return ERROR_SET_FILTER_FALIURE;
}
/* 开始抓包 */
btp_state.state = 0;
btp_state.timeout_flag = 0;
printf("listening on %s...\n", d->description);
pcap_freealldevs(alldevs);
pcap_loop(btp_state.adapter, 0, packet_handler, NULL);
system("pause");
return 0;
}
/* 抓包回调函数 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
time_t local_tv_sec; /* 时间戳 */
struct tm *ltime; /* 本地时间 */
char timestr[16]; /* 格式化后的本地时间 */
ETH_HEADER *eth_header; /* 以太网帧包头 */
char str_mac[50]; /* 源MAC地址 */
BTP_HEADER *btp_header;
char *data;
int i;
/* 没有使用param */
(VOID)(param);
/* 格式化当前时间 */
local_tv_sec = header->ts.tv_sec;
ltime = localtime(&local_tv_sec);
strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);
/* 解析以太网帧 */
eth_header = (ETH_HEADER *)pkt_data;
format_mac(str_mac, eth_header->src_mac);
printf("[ %s.%.6d ] receive package from %s:\n",
timestr, header->ts.tv_usec, str_mac);
/* 解析数据域 */
btp_header = (BTP_HEADER *)(pkt_data + sizeof(ETH_HEADER));
if (btp_header->protocol == BTP_PROTOCOL){ /* 确认是btp包 */
printf("protocol=\tBTP \nversion=\t%d \ntype=\t%s \npid=\t%d \ndata_length=\t%d \n",
btp_header->version, rec_type(btp_header->type), btp_header->pid, btp_header->data_length);
if (btp_header->version > BTP_VERSION){
kill_all("[ERROR] version mismatch.", ERROR_BAD_VERSION);
}
switch (btp_header->type){
case BTP_HELLO_REQUEST:
if (btp_state.state == 0){/* 初始,可握手 */
if (!btp_send(eth_header, btp_header, BTP_HELLO_RESPONSE)){
if (pthread_create(&btp_state.pids[0], NULL, timer, NULL) == -1){/* 开启心跳线程 */
kill_all("[ERROR] create thread failed.", ERROR_CREATE_THREAD);
}
btp_state.state = 1;/*握手后,可心跳,可数据,可断开*/
}
else{
kill_all("[ERROR] btp hello failed.", ERROR_BTP_HELLO_FAILED);
}
}
break;
case BTP_HEARTBEAT_REQUEST:
if (btp_state.state == 1){
btp_state.timeout_flag = (btp_state.timeout_flag + 1) % 1000;
if (btp_send(eth_header, btp_header, BTP_HEARTBEAT_RESPONSE) != 0)
kill_all("[ERROR] btp heartbeat failed.", ERROR_BTP_HEARTBEAT_FAILED);
}
break;
case BTP_DATA:
if (btp_state.state == 1){
data = (char*)(pkt_data + sizeof(ETH_HEADER)+sizeof(BTP_HEADER));
printf("recv data: ");
for (i = 0; i < btp_header->data_length; i++)
printf("%c", data[i]);
printf("\n");
}
break;
case BTP_BYE_REQUEST:
if (btp_state.state == 1){
if (btp_send(eth_header, btp_header, BTP_BYE_RESPONSE) != 0)
kill_all("[ERROR] btp bye failed.", ERROR_BTP_BYE_FAILED);
else
kill_all("[INFO] btp finished.", 0);
}
break;
}
}
}
int btp_send(ETH_HEADER* eth_header, BTP_HEADER *btp_header, u_char type){
int index;
u_char sendbuf[SEND_BUFSIZE]; /* 发送buffer */
u_char temp_mac[6];
/* 制作新的eth_header */
copy_mac(temp_mac, eth_header->src_mac);
copy_mac(eth_header->src_mac, eth_header->dest_mac);
copy_mac(eth_header->dest_mac, temp_mac);
/* 制作新的btp_header */
btp_header->type = type;
btp_header->data_length = 0;
/* 发包 */
memcpy(sendbuf, eth_header, sizeof(ETH_HEADER));
index = sizeof(ETH_HEADER);
memcpy(&sendbuf[index], btp_header, sizeof(BTP_HEADER));
index += sizeof(BTP_HEADER);
if (pcap_sendpacket(btp_state.adapter, /* 网卡句柄 */
sendbuf, /* 要发送的帧 */
index /* 帧的大小 */
) != 0) {
fprintf(stderr, "[ERROR] Error sending the packet: %s\n", pcap_geterr(btp_state.adapter));
return ERROR_SENDING_FAILURE;
}
printf("response a %s package.\n", rec_type(type));
return 0;
}
void *timer(void *arg){
int timeout_flag;
printf("timer start.\n");
while (1){
timeout_flag = btp_state.timeout_flag;
Sleep(3000);
if (timeout_flag == btp_state.timeout_flag){ /* 没有改变 */
kill_all("[ERROR] time out.", ERROR_BTP_TIMEOUT);
break;
}
printf("timer ok\n");
}
return NULL;
}
void kill_all(char *msg, int error_code){
printf("%s\n", msg);
pcap_breakloop(btp_state.adapter);
system("pause");
exit(error_code);
}
char* rec_type(u_char type){
switch (type){
case BTP_HELLO_REQUEST:
return "BTP_HELLO_REQUEST";
case BTP_HELLO_RESPONSE:
return "BTP_HELLO_RESPONSE";
case BTP_HEARTBEAT_REQUEST:
return "BTP_HEARTBEAT_REQUEST";
case BTP_HEARTBEAT_RESPONSE:
return "BTP_HEARTBEAT_RESPONSE";
case BTP_DATA:
return "BTP_DATA";
case BTP_BYE_REQUEST:
return "BTP_BYE_REQUEST";
case BTP_BYE_RESPONSE:
return "BTP_BYE_RESPONSE";
default:
return "BAD_TYPE";
}
}
void copy_mac(u_char *mac1, u_char *mac2){
mac1[0] = mac2[0];
mac1[1] = mac2[1];
mac1[2] = mac2[2];
mac1[3] = mac2[3];
mac1[4] = mac2[4];
mac1[5] = mac2[5];
}
void format_mac(char* lpHWAddrStr, const unsigned char *HWAddr)
{
int i;
short temp;
char szStr[3];
strcpy(lpHWAddrStr, "");
for (i = 0; i < 6; ++i)
{
temp = (short)(*(HWAddr + i));
_itoa(temp, szStr, 16);
if (strlen(szStr) == 1)
strcat(lpHWAddrStr, "0");
strcat(lpHWAddrStr, szStr);
if (i < 5)
strcat(lpHWAddrStr, ":");
}
}
注意这里的kill_all函数,你可以自己去编写中止线程的方法,比如cancel或者kill,并且pcap_breakloop(btp_state.adapter);
这一句不是必须的,是因为我使用了system("pause");
想查看一下输出结果,造成exit(error_code);
没有执行,所以使用这一句来结束winpcap抓包。正常的来说,你可以使用打印输出到文件而不是到屏幕,然后注释掉这句,让exit执行,就能够中止所有线程了。
整体思路很容易看懂,state就是状态机;pids保存着线程的结构体,用来结束线程;每次收到心跳包后就更新timeout_flag,timer根据这个值是否更新来判断是否心跳超时了,从而判断是否断开连接了,注意接收者是不需要发包计时线程的,因为它只管接受新包和回包,不需要期待对面回包;adapter是网卡句柄。
(3)测试接收者
把sender稍微改造一下。
BTPsender.h
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#include <winsock.h>
#include <pthread.h>
#define SEND_BUFSIZE 1024
#define TIMER_SLEEPTIME 2000
#define ERROR_GENERAL -1
#define ERROR_FINDALLDEVS_FAILURE -2
#define ERROR_INTERFACES_NOT_FOUND -3
#define ERROR_BAD_INPUT -4
#define ERROR_OPEN_ADAPTER_FAILURE -5
#define ERROR_SENDING_FAILURE -6
#define ERROR_INVALID_DATALINK_TYPE -7
#define ERROR_COMPILE_FILTER_FALIURE -8
#define ERROR_SET_FILTER_FALIURE -9
#define ERROR_CREATE_THREAD -10
#define ERROR_BTP_HELLO_FAILED -11
#define ERROR_BTP_HEARTBEAT_FAILED -12
#define ERROR_BTP_BYE_FAILED -13
#define ERROR_BTP_TIMEOUT -14
#define ERROR_BAD_VERSION -15
#define BTP_HELLO_REQUEST 0x01
#define BTP_HELLO_RESPONSE 0x02
#define BTP_HEARTBEAT_REQUEST 0x03
#define BTP_HEARTBEAT_RESPONSE 0x04
#define BTP_DATA 0x05
#define BTP_BYE_REQUEST 0x06
#define BTP_BYE_RESPONSE 0x07
#define BTP_PROTOCOL 0x42
#define BTP_VERSION 0x01
typedef struct ETH_HEADER
{
u_char dest_mac[6];
u_char src_mac[6];
u_short etype;
}ETH_HEADER;
typedef struct BTP_HEADER{
u_char protocol;
u_char version;
u_char type;
u_char pid;
u_short data_length;
}BTP_HEADER;
typedef struct BTP_STATE{
int state; /* 状态:0=初始(可握手) 1=握手后(可心跳、可数据、可断开)*/
pthread_t pids[1]; /* 只有一个心跳线程 */
int timeout_flag; /* 心跳超时标记,每次收包都进行+1,如果计时线程发现两次的flag都相同,说明断开了 */
pcap_t *adapter; /* 网卡句柄 */
}BTP_STATE;
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);/* 抓包回调函数 */
void format_mac(LPSTR lpHWAddrStr, const unsigned char *HWAddr);/* mac地址格式化函数 */
int btp_send(ETH_HEADER* eth_header, BTP_HEADER *btp_header, u_char type);
void *timer(void *arg);
void kill_all(char *msg, int error_code);
void copy_mac(u_char *mac1, u_char *mac2);
char* rec_type(u_char type);
BTPsender.c
#include "BTPrecver.h"
BTP_STATE btp_state;
int main()
{
char errbuf[PCAP_ERRBUF_SIZE]; /* 错误信息buffer */
u_int netmask; /* 掩码信息 */
char packet_filter[] = "ether src 00:50:56:C0:00:08 and ether dst 00:0C:29:86:B8:C8"; /* 过滤规则 */
struct bpf_program fcode; /* 存储编译好的过滤码 */
pcap_if_t *alldevs; /* 全部网卡列表 */
pcap_if_t *d; /* 一个网卡 */
int did; /* 选择的网卡ID */
int i = 0; /* 迭代 */
/*查找网卡*/
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) {
fprintf(stderr, "[ERROR] pcap_findalldevs error: %s\n", errbuf);
return ERROR_FINDALLDEVS_FAILURE;
}
/* 选择网卡d */
for (d = alldevs, i = 0; d; d = d->next) {
if (d->description)
printf("NO.%d: %s\n", ++i, d->description);
else
printf("[WARN] No description available\n");
}
if (i == 0) {
printf("[ERROR] No interfaces found! Make sure WinPcap is installed.\n");
return ERROR_INTERFACES_NOT_FOUND;
}
printf("[INFO] Enter the interface number (1-%d):", i);
scanf("%d", &did);
if (did < 1 || did > i) {
printf("[ERROR] Interface number out of range.\n");
pcap_freealldevs(alldevs);
return ERROR_BAD_INPUT;
}
for (d = alldevs, i = 0; i < did - 1; d = d->next, i++);
/* 打开网卡 */
if ((btp_state.adapter = pcap_open_live(d->name, /* 设备名 */
65536, /* 捕获数据包的长度(65536捕获所有数据包) */
1, /* 混杂模式(非0表示使用混杂模式) */
1000, /* 超时时间(0表示没有超时限制) */
errbuf /* 错误缓存(存储错误信息) */
)) == NULL) {
fprintf(stderr, "[ERROR] Unable to open the adapter. %s is not supported by WinPcap\n");
pcap_freealldevs(alldevs);
return ERROR_OPEN_ADAPTER_FAILURE;
}
/* 检查链路层类型 */
if (pcap_datalink(btp_state.adapter) != DLT_EN10MB) /* DLT_EN10MB指10Mb以太网 */
{
fprintf(stderr, "[ERROR] This program works only on Ethernet networks.\n");
pcap_freealldevs(alldevs);
return ERROR_INVALID_DATALINK_TYPE;
}
/* 检查地址类型 */
if (d->addresses != NULL) /* 如果有IP地址 */
netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; /* 使用第一个掩码 */
else /* 如果没有IP地址,说明是C类网络(局域网) */
netmask = 0xffffff; /* 掩码设置为255.255.255.0 */
/* 编译过滤器 */
if (pcap_compile(btp_state.adapter, &fcode, packet_filter, 1, netmask) < 0) /* 1表示自动进行优化 */
{
fprintf(stderr, "[ERROR] Unable to compile the packet filter. Check the syntax.\n");
pcap_freealldevs(alldevs);
return ERROR_COMPILE_FILTER_FALIURE;
}
/* 应用过滤器 */
if (pcap_setfilter(btp_state.adapter, &fcode) < 0)
{
fprintf(stderr, "[ERROR] Error setting the filter.\n");
pcap_freealldevs(alldevs);
return ERROR_SET_FILTER_FALIURE;
}
/* 开始抓包 */
btp_state.state = 0;
btp_state.timeout_flag = 0;
printf("listening on %s...\n", d->description);
pcap_freealldevs(alldevs);
pcap_loop(btp_state.adapter, 0, packet_handler, NULL);
system("pause");
return 0;
}
/* 抓包回调函数 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
time_t local_tv_sec; /* 时间戳 */
struct tm *ltime; /* 本地时间 */
char timestr[16]; /* 格式化后的本地时间 */
ETH_HEADER *eth_header; /* 以太网帧包头 */
char str_mac[50]; /* 源MAC地址 */
BTP_HEADER *btp_header;
char *data;
int i;
/* 没有使用param */
(VOID)(param);
/* 格式化当前时间 */
local_tv_sec = header->ts.tv_sec;
ltime = localtime(&local_tv_sec);
strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);
/* 解析以太网帧 */
eth_header = (ETH_HEADER *)pkt_data;
format_mac(str_mac, eth_header->src_mac);
printf("[ %s.%.6d ] receive package from %s:\n",
timestr, header->ts.tv_usec, str_mac);
/* 解析数据域 */
btp_header = (BTP_HEADER *)(pkt_data + sizeof(ETH_HEADER));
if (btp_header->protocol == BTP_PROTOCOL){ /* 确认是btp包 */
printf("protocol=\tBTP \nversion=\t%d \ntype=\t%s \npid=\t%d \ndata_length=\t%d \n",
btp_header->version, rec_type(btp_header->type), btp_header->pid, btp_header->data_length);
if (btp_header->version > BTP_VERSION){
kill_all("[ERROR] version mismatch.", ERROR_BAD_VERSION);
}
switch (btp_header->type){
case BTP_HELLO_REQUEST:
if (btp_state.state == 0){/* 初始,可握手 */
if (!btp_send(eth_header, btp_header, BTP_HELLO_RESPONSE)){
if (pthread_create(&btp_state.pids[0], NULL, timer, NULL) == -1){/* 开启心跳线程 */
kill_all("[ERROR] create thread failed.", ERROR_CREATE_THREAD);
}
btp_state.state = 1;/*握手后,可心跳,可数据,可断开*/
}
else{
kill_all("[ERROR] btp hello failed.", ERROR_BTP_HELLO_FAILED);
}
}
break;
case BTP_HEARTBEAT_REQUEST:
if (btp_state.state == 1){
btp_state.timeout_flag = (btp_state.timeout_flag + 1) % 1000;
if (btp_send(eth_header, btp_header, BTP_HEARTBEAT_RESPONSE) != 0)
kill_all("[ERROR] btp heartbeat failed.", ERROR_BTP_HEARTBEAT_FAILED);
}
break;
case BTP_DATA:
if (btp_state.state == 1){
data = (char*)(pkt_data + sizeof(ETH_HEADER)+sizeof(BTP_HEADER));
printf("recv data: ");
for (i = 0; i < btp_header->data_length; i++)
printf("%c", data[i]);
printf("\n");
}
break;
case BTP_BYE_REQUEST:
if (btp_state.state == 1){
if (btp_send(eth_header, btp_header, BTP_BYE_RESPONSE) != 0)
kill_all("[ERROR] btp bye failed.", ERROR_BTP_BYE_FAILED);
else
kill_all("[INFO] btp finished.", 0);
}
break;
}
}
}
int btp_send(ETH_HEADER* eth_header, BTP_HEADER *btp_header, u_char type){
int index;
u_char sendbuf[SEND_BUFSIZE]; /* 发送buffer */
u_char temp_mac[6];
/* 制作新的eth_header */
copy_mac(temp_mac, eth_header->src_mac);
copy_mac(eth_header->src_mac, eth_header->dest_mac);
copy_mac(eth_header->dest_mac, temp_mac);
/* 制作新的btp_header */
btp_header->type = type;
btp_header->data_length = 0;
/* 发包 */
memcpy(sendbuf, ð_header, sizeof(eth_header));
index = sizeof(eth_header);
if (pcap_sendpacket(btp_state.adapter, /* 网卡句柄 */
sendbuf, /* 要发送的帧 */
index /* 帧的大小 */
) != 0) {
fprintf(stderr, "[ERROR] Error sending the packet: %s\n", pcap_geterr(btp_state.adapter));
return ERROR_SENDING_FAILURE;
}
printf("response a %s package.\n", rec_type(type));
return 0;
}
void *timer(void *arg){
int timeout_flag;
printf("timer start.\n");
while (1){
timeout_flag = btp_state.timeout_flag;
Sleep(3000);
if (timeout_flag == btp_state.timeout_flag){ /* 没有改变 */
kill_all("[ERROR] time out.", ERROR_BTP_TIMEOUT);
break;
}
printf("timer ok\n");
}
return NULL;
}
void kill_all(char *msg, int error_code){
printf("%s\n", msg);
pcap_breakloop(btp_state.adapter);
system("pause");
exit(error_code);
}
char* rec_type(u_char type){
switch (type){
case BTP_HELLO_REQUEST:
return "BTP_HELLO_REQUEST";
case BTP_HELLO_RESPONSE:
return "BTP_HELLO_RESPONSE";
case BTP_HEARTBEAT_REQUEST:
return "BTP_HEARTBEAT_REQUEST";
case BTP_HEARTBEAT_RESPONSE:
return "BTP_HEARTBEAT_RESPONSE";
case BTP_DATA:
return "BTP_DATA";
case BTP_BYE_REQUEST:
return "BTP_BYE_REQUEST";
case BTP_BYE_RESPONSE:
return "BTP_BYE_RESPONSE";
default:
return "BAD_TYPE";
}
}
void copy_mac(u_char *mac1, u_char *mac2){
mac1[0] = mac2[0];
mac1[1] = mac2[1];
mac1[2] = mac2[2];
mac1[3] = mac2[3];
mac1[4] = mac2[4];
mac1[5] = mac2[5];
}
void format_mac(char* lpHWAddrStr, const unsigned char *HWAddr)
{
int i;
short temp;
char szStr[3];
strcpy(lpHWAddrStr, "");
for (i = 0; i < 6; ++i)
{
temp = (short)(*(HWAddr + i));
_itoa(temp, szStr, 16);
if (strlen(szStr) == 1)
strcat(lpHWAddrStr, "0");
strcat(lpHWAddrStr, szStr);
if (i < 5)
strcat(lpHWAddrStr, ":");
}
}
看看实验结果:
后面time out是因为那个心跳线程没有直接结束,原因在前面已经说明了。
(4)编写发送者
sender.h
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#include <winsock.h>
#include <pthread.h>
#define ETHERTYPE_IP 0x0800 /* IP */
#define ERROR_GENERAL -1
#define ERROR_FINDALLDEVS_FAILURE -2
#define ERROR_INTERFACES_NOT_FOUND -3
#define ERROR_BAD_INPUT -4
#define ERROR_OPEN_ADAPTER_FAILURE -5
#define ERROR_SENDING_FAILURE -6
#define ERROR_INVALID_DATALINK_TYPE -7
#define ERROR_COMPILE_FILTER_FALIURE -8
#define ERROR_SET_FILTER_FALIURE -9
#define ERROR_CREATE_THREAD -10
#define ERROR_BTP_HELLO_FAILED -11
#define ERROR_BTP_HEARTBEAT_FAILED -12
#define ERROR_BTP_BYE_FAILED -13
#define ERROR_BTP_TIMEOUT -14
#define ERROR_BAD_VERSION -15
#define ERROR_BTP_DATA_FAILED -16
#define SEND_BUFSIZE 1024
#define SEND_TIMES 10000
#define SEND_INTVAL 1000
#define TIMER_SLEEPTIME 2000
#define TIMER_EXPECT_SLEEPTIME 1000
#define BTP_HELLO_REQUEST 0x01
#define BTP_HELLO_RESPONSE 0x02
#define BTP_HEARTBEAT_REQUEST 0x03
#define BTP_HEARTBEAT_RESPONSE 0x04
#define BTP_DATA 0x05
#define BTP_BYE_REQUEST 0x06
#define BTP_BYE_RESPONSE 0x07
#define BTP_PROTOCOL 0x42
#define BTP_VERSION 0x01
typedef struct ETH_HEADER
{
u_char dest_mac[6];
u_char src_mac[6];
u_short etype;
}ETH_HEADER;
typedef struct BTP_HEADER{
u_char protocol;
u_char version;
u_char type;
u_char pid;
u_short data_length;
}BTP_HEADER;
typedef struct BTP_STATE{
int state; /* 状态:0=初始(可发送握手) 1=握手后(可心跳、可数据、可断开)*/
pthread_t pids[3]; /* 0=心跳线程 1=接收线程 2=计时线程 */
int timeout_flag; /* 心跳超时标记,每次收包都进行+1,如果计时线程发现两次的flag都相同,说明断开了 */
pcap_t *adapter; /* 网卡句柄 */
int pid;/* 这个是package id,包序号*/
u_char sendbuf[SEND_BUFSIZE]; /* 发送buffer */
int index; /* 发送buffer偏移 */
}BTP_STATE;
int send_btp_package(pcap_t *adapter, u_char *sendbuf, int index, u_char type, char* data, int len);
char* rec_type(u_char type);
void *timer(void *arg);
void *timer_expect(void *arg);
void *bye(void *arg);
void kill_all(char *msg, int error_code);
void btp_listen();
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
int send_btp_package(pcap_t *adapter, u_char *sendbuf, int index, int *pid, u_char type, char* data, int len, int te);
void format_mac(LPSTR lpHWAddrStr, const unsigned char *HWAddr);/* mac地址格式化函数 */
sender.c
#include "BTPsender.h"
BTP_STATE btp_state;
int main()
{
pcap_t *adapter; /* 网卡句柄 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 错误信息buffer */
u_int netmask; /* 掩码信息 */
char packet_filter[] = "ether src 00:0C:29:86:B8:C8 and ether dst 00:50:56:C0:00:08"; /* 过滤规则 */
struct bpf_program fcode; /* 存储编译好的过滤码 */
ETH_HEADER eth_header; /* 以太网包头 */
pcap_if_t *alldevs; /* 全部网卡列表 */
pcap_if_t *d; /* 一个网卡 */
int did; /* 选择的网卡ID */
int i; /* 迭代 */
/* 查找网卡 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) {
fprintf(stderr, "[ERROR] pcap_findalldevs error: %s\n", errbuf);
return ERROR_FINDALLDEVS_FAILURE;
}
/* 选择网卡d */
for (d = alldevs, i = 0; d; d = d->next) {
if (d->description)
printf("NO.%d: %s\n", ++i, d->description);
else
printf("[WARN] No description available\n");
}
if (i == 0) {
printf("[ERROR] No interfaces found! Make sure WinPcap is installed.\n");
return ERROR_INTERFACES_NOT_FOUND;
}
printf("[INFO] Enter the interface number (1-%d):", i);
scanf("%d", &did);
if (did < 1 || did > i) {
printf("[ERROR] Interface number out of range.\n");
pcap_freealldevs(alldevs);
return ERROR_BAD_INPUT;
}
for (d = alldevs, i = 0; i < did - 1; d = d->next, i++);
/* 打开网卡 */
if ((btp_state.adapter = pcap_open_live(d->name, /* 设备名 */
65536, /* 捕获数据包的长度(65536捕获所有数据包) */
1, /* 混杂模式(非0表示使用混杂模式) */
1000, /* 超时时间(0表示没有超时限制) */
errbuf /* 错误缓存(存储错误信息) */
)) == NULL) {
fprintf(stderr, "[ERROR] Unable to open the adapter. %s is not supported by WinPcap\n");
pcap_freealldevs(alldevs);
return ERROR_OPEN_ADAPTER_FAILURE;
}
/* 检查链路层类型 */
if (pcap_datalink(btp_state.adapter) != DLT_EN10MB) /* DLT_EN10MB指10Mb以太网 */
{
fprintf(stderr, "[ERROR] This program works only on Ethernet networks.\n");
pcap_freealldevs(alldevs);
return ERROR_INVALID_DATALINK_TYPE;
}
/* 检查地址类型 */
if (d->addresses != NULL) /* 如果有IP地址 */
netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; /* 使用第一个掩码 */
else /* 如果没有IP地址,说明是C类网络(局域网) */
netmask = 0xffffff; /* 掩码设置为255.255.255.0 */
/* 编译过滤器 */
if (pcap_compile(btp_state.adapter, &fcode, packet_filter, 1, netmask) < 0) /* 1表示自动进行优化 */
{
fprintf(stderr, "[ERROR] Unable to compile the packet filter. Check the syntax.\n");
pcap_freealldevs(alldevs);
return ERROR_COMPILE_FILTER_FALIURE;
}
/* 应用过滤器 */
if (pcap_setfilter(btp_state.adapter, &fcode) < 0)
{
fprintf(stderr, "[ERROR] Error setting the filter.\n");
pcap_freealldevs(alldevs);
return ERROR_SET_FILTER_FALIURE;
}
/*目的PB的mac地址*/
eth_header.dest_mac[0] = 0x00;
eth_header.dest_mac[1] = 0x0C;
eth_header.dest_mac[2] = 0x29;
eth_header.dest_mac[3] = 0x86;
eth_header.dest_mac[4] = 0xB8;
eth_header.dest_mac[5] = 0xC8;
/*源PA的mac地址*/
eth_header.src_mac[0] = 0x00;
eth_header.src_mac[1] = 0x50;
eth_header.src_mac[2] = 0x56;
eth_header.src_mac[3] = 0xC0;
eth_header.src_mac[4] = 0x00;
eth_header.src_mac[5] = 0x08;
eth_header.etype = htons(ETHERTYPE_IP);
memcpy(btp_state.sendbuf, ð_header, sizeof(eth_header));
btp_state.index = sizeof(eth_header);
/* 开启抓包线程 */
btp_state.state = 0;
btp_state.timeout_flag = 0;
printf("listening on %s...\n", d->description);
pcap_freealldevs(alldevs);
if (pthread_create(&btp_state.pids[1], NULL, btp_listen, NULL) == -1){
kill_all("[ERROR] create thread failed.", ERROR_CREATE_THREAD);
}
/* 发送第一个包 */
btp_state.pid = 1;
if (send_btp_package(btp_state.adapter, btp_state.sendbuf, btp_state.index, &btp_state.pid, BTP_HELLO_REQUEST, NULL, 0, 1) != 0){
kill_all("[ERROR] send hello failed.", ERROR_BTP_HELLO_FAILED);
}
/* 发包
Sleep(SEND_INTVAL);
send_btp_package(adapter, sendbuf, index, &pid, BTP_HEARTBEAT_REQUEST, NULL, 0);
send_btp_package(adapter, sendbuf, index, &pid, BTP_DATA, data, strlen(data));
Sleep(SEND_INTVAL);
send_btp_package(adapter, sendbuf, index, &pid, BTP_BYE_REQUEST, NULL, 0);
*/
pthread_join(btp_state.pids[1], NULL);
pcap_close(btp_state.adapter);
system("pause");
return 0;
}
void *timer(void *arg){
int timeout_flag;
printf("timer start.\n");
while (1){
timeout_flag = btp_state.timeout_flag;
Sleep(3000);
if (timeout_flag == btp_state.timeout_flag){ /* 没有改变 */
kill_all("[ERROR] time out.", ERROR_BTP_TIMEOUT);
break;
}
printf("timer ok\n");
}
return NULL;
}
void *timer_expect(void *arg){
Sleep(TIMER_EXPECT_SLEEPTIME);
pthread_testcancel();
kill_all("[ERROR] timer_expect time out.", ERROR_BTP_TIMEOUT);
}
void *bye(void *arg){
Sleep(10000);
if (send_btp_package(btp_state.adapter, btp_state.sendbuf, btp_state.index, &btp_state.pid,
BTP_BYE_REQUEST, NULL, 0, 1)
!= 0){
kill_all("[ERROR] send bye failed.", ERROR_BTP_BYE_FAILED);
}
}
void kill_all(char *msg, int error_code){
printf("%s\n", msg);
pcap_breakloop(btp_state.adapter);
system("pause");
exit(error_code);
}
void btp_listen(){
pcap_loop(btp_state.adapter, 0, packet_handler, NULL);
}
/* 抓包回调函数 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
time_t local_tv_sec; /* 时间戳 */
struct tm *ltime; /* 本地时间 */
char timestr[16]; /* 格式化后的本地时间 */
ETH_HEADER *eth_header; /* 以太网帧包头 */
char str_mac[50]; /* 源MAC地址 */
BTP_HEADER *btp_header;
char *data;
int i;
/* 没有使用param */
(VOID)(param);
/* 格式化当前时间 */
local_tv_sec = header->ts.tv_sec;
ltime = localtime(&local_tv_sec);
strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);
/* 解析以太网帧 */
eth_header = (ETH_HEADER *)pkt_data;
format_mac(str_mac, eth_header->src_mac);
printf("[ %s.%.6d ] receive package from %s:\n",
timestr, header->ts.tv_usec, str_mac);
/* 解析数据域 */
btp_header = (BTP_HEADER *)(pkt_data + sizeof(ETH_HEADER));
if (btp_header->protocol == BTP_PROTOCOL){ /* 确认是btp包 */
printf("protocol=\tBTP \nversion=\t%d \ntype=\t%s \npid=\t%d \ndata_length=\t%d \n",
btp_header->version, rec_type(btp_header->type), btp_header->pid, btp_header->data_length);
if (btp_header->version > BTP_VERSION){
kill_all("[ERROR] version mismatch.", ERROR_BAD_VERSION);
}
switch (btp_header->type){
case BTP_HELLO_RESPONSE:
if (btp_state.state == 0){/* 初始,可握手 */
if (pthread_create(&btp_state.pids[0], NULL, timer, NULL) == -1){/* 开启心跳线程 */
kill_all("[ERROR] create thread failed.", ERROR_CREATE_THREAD);
}
btp_state.state = 1;/*握手后,可心跳,可数据,可断开*/
pthread_cancel(btp_state.pids[2]);/* 中止hello包的计时器 */
if (send_btp_package(btp_state.adapter, btp_state.sendbuf, btp_state.index, &btp_state.pid,
BTP_HEARTBEAT_REQUEST, NULL, 0, 0)
!= 0){
kill_all("[ERROR] send heartbeat failed.", ERROR_BTP_HEARTBEAT_FAILED);
}
if (send_btp_package(btp_state.adapter, btp_state.sendbuf, btp_state.index, &btp_state.pid,
BTP_DATA, "test btp data.", strlen("test btp data."), 0)
!= 0){
kill_all("[ERROR] send data failed.", ERROR_BTP_DATA_FAILED);
}
if (pthread_create(&btp_state.pids[0], NULL, bye, NULL) == -1){/* 开启bye线程,等待10秒发包 */
kill_all("[ERROR] create thread failed.", ERROR_CREATE_THREAD);
}
}
break;
case BTP_HEARTBEAT_RESPONSE:
if (btp_state.state == 1){
btp_state.timeout_flag = (btp_state.timeout_flag + 1) % 1000;
if (send_btp_package(btp_state.adapter, btp_state.sendbuf, btp_state.index, &btp_state.pid,
BTP_HEARTBEAT_REQUEST, NULL, 0, 0)
!= 0){
kill_all("[ERROR] btp heartbeat failed.", ERROR_BTP_HEARTBEAT_FAILED);
}
}
break;
case BTP_BYE_RESPONSE:
if (btp_state.state == 1){
pthread_cancel(btp_state.pids[2]);/* 中止bye包的计时器 */
kill_all("[INFO] btp finished.", 0);
}
break;
}
}
}
int send_btp_package(pcap_t *adapter, u_char *sendbuf, int index, int *pid, u_char type, char* data, int len, int te){
BTP_HEADER package;
package.protocol = BTP_PROTOCOL;
package.pid = (*pid)++;
package.version = BTP_VERSION;
package.data_length = type == BTP_DATA ? len : 0;
package.type = type;
memcpy(&sendbuf[index], &package, sizeof(package));
index += sizeof(package);
if (type == BTP_DATA){
memcpy(&sendbuf[index], data, len);
index += len;
}
if (pcap_sendpacket(adapter, /* 网卡句柄 */
sendbuf, /* 要发送的帧 */
index /* 帧的大小 */
) != 0) {
fprintf(stderr, "[ERROR] Error sending the packet: %s\n", pcap_geterr(adapter));
return ERROR_SENDING_FAILURE;
}
printf("send a %s btp packet success.\n", rec_type(type));
/* 启动一个计时器,期待回复 */
if (te){
if (pthread_create(&btp_state.pids[2], NULL, timer, NULL) == -1){
kill_all("[ERROR] create thread failed.", ERROR_CREATE_THREAD);
}
}
return 0;
}
char* rec_type(u_char type){
switch (type){
case BTP_HELLO_REQUEST:
return "BTP_HELLO_REQUEST";
case BTP_HELLO_RESPONSE:
return "BTP_HELLO_RESPONSE";
case BTP_HEARTBEAT_REQUEST:
return "BTP_HEARTBEAT_REQUEST";
case BTP_HEARTBEAT_RESPONSE:
return "BTP_HEARTBEAT_RESPONSE";
case BTP_DATA:
return "BTP_DATA";
case BTP_BYE_REQUEST:
return "BTP_BYE_REQUEST";
case BTP_BYE_RESPONSE:
return "BTP_BYE_RESPONSE";
default:
return "BAD_TYPE";
}
}
void format_mac(char* lpHWAddrStr, const unsigned char *HWAddr)
{
int i;
short temp;
char szStr[3];
strcpy(lpHWAddrStr, "");
for (i = 0; i < 6; ++i)
{
temp = (short)(*(HWAddr + i));
_itoa(temp, szStr, 16);
if (strlen(szStr) == 1)
strcat(lpHWAddrStr, "0");
strcat(lpHWAddrStr, szStr);
if (i < 5)
strcat(lpHWAddrStr, ":");
}
}
这里的实现上,没有单独地开一个发送线程,只简单地开了一个等待10秒后发送BTP断开包的线程。
注意发送者的监听需要调换源MAC和目的MAC,因为是虚拟机PB端发过来的。
每次发送期待回复的包后,都会开一个定时器,来判断是否超时。
(5)整体测试
正常实验结果的sender / PA 端:
上来就发了一个hello包,收到回复后开始不断发送和接受心跳包,显示timer start和timer ok,然后10秒后发送了一个bye包:
完成了整个过程。我们再试试中途关闭PB端的程序:
可以看到心跳包超时了。我们再试试一开始就不开PB端的程序:
可以看到PA发送的hello包没有人回复,造成计时器超时了。
三、总结
整个实验过程非常好玩,只不过时间仓促没有测试完所有的分支,如果你也有兴趣,可以在这里(密码:fqqy)下载代码工程文件,原理上或者程序中有什么错误,欢迎邮件或评论指正。
参考资料
4、《linux下pthread_cancel无法取消线程的原因》