示例程序主要为通过绑定 RT-net
网卡对数据进行收发操作,采用 linux
通用的 socket
接口
示例程序由回环服务器和客户端两部分组成,通过两套不同的数据发送接收接口完成回环功能
注意本示例程序采用基于 raw_socket 网络链路层协议通信,linux 运行时需添加 root 权限,否则程序会异常退出(无法创建 raw_socket 文件描述符),echo service 接收并过滤特殊 client 发来的报文,并回传对应特殊报文供 client 端解析,上图中 client 通过 service 回传的报文解析出对应的协议内容为 1,2,3,4 依次递增数
Services
static bool FilterData(uint8_t* data, ssize_t size) {
if (size < 16) {
return false;
}
return data[12] == 0x88 && data[13] == 0x8e && data[14] == 0x01 && data[15] == 0x01;
}
static void* Services(void* args) {
(void)args;
// args 为入参,实际为 rtnet 网卡名称,类型为字符数组(char*)
char* networkCard = (char*)args;
// 必要数据变量准备 如:sockfd, sockaddr_ll 结构体等
uint8_t mac_src[ETH_ALEN] = {0};
uint8_t mac_dst[ETH_ALEN] = {0};
ssize_t len = 0;
int sockfd = -1;
uint8_t buffer[K_MAX_BUFFER_SIZE] = {0};
struct sockaddr_ll sll;
// 通过 if_nametoindex 系统调用可以通过 rtnet 网卡名称返回正确的网卡索引并保存
memset(&sll, 0, sizeof(struct sockaddr_ll));
sll.sll_ifindex = (int)if_nametoindex(networkCard);
// 创建 raw socket 文件描述符,用于发送和接受数据
sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd < 0) {
perror("sock fd create failed");
return NULL;
}
while (!g_exit_falg) {
struct sockaddr_ll tmp_sll;
uint32_t length;
// 接收 raw socket 数据,并存入 buffer 中,并缓存此次接受数据的 网卡属性到 tmp_sll 结构当中
len = recvfrom(sockfd, buffer, K_MAX_BUFFER_SIZE, K_RECV_FLAGS, (struct sockaddr *)&tmp_sll, &length);
if (len < 0) {
// 处理系统报错
perror("recvfrom failed");
break;
}
// 过滤数据,确定可以解析自定义类型数据
if (!FilterData(buffer, len)) {
continue;
}
memset(mac_src, 0, ETH_ALEN);
memset(mac_dst, 0, ETH_ALEN);
for (int i = 0; i < ETH_ALEN; ++i) {
mac_src[i] = buffer[i];
mac_dst[i] = buffer[i + ETH_ALEN];
}
// 修改数据包
FixPacket(mac_src, mac_dst);
// 将修改的数据包完整回传至 client
len = sendto(sockfd, s_packet, len, K_SEND_FLAGS, (struct sockaddr*)&tmp_sll, sizeof(tmp_sll));
if (len < 0) {
perror("sendto failed");
break;
}
}
close(sockfd);
}
service 启动参数为实时网卡名称
sudo ./rtnet_raw_socket rteth0
运行结果:
root@sinsegye-sx21:/home/sinsegye/projects/rtnet# ./rtnet_service
args is Network Card name.
example: ./rtnet_service rteth0
root@sinsegye-sx21:/home/sinsegye/projects/rtnet# ./rtnet_service rteth0
will pthread_create()
pthread_join() before
Client
static void* Client(void* args) {
(void)args;
// args 为入参,实际为 rtnet 网卡名称,类型为字符数组(char*)
char* networkCard = (char*)args;
// 必要数据变量准备 如:sockfd, sockaddr_ll 结构体等
ssize_t len = 0;
int sockfd = -1;
uint8_t buffer[K_MAX_BUFFER_SIZE] = {0};
struct sockaddr_ll sll;
memset(&sll, 0, sizeof(struct sockaddr_ll));
// 通过 if_nametoindex 系统调用可以通过 rtnet 网卡名称返回正确的网卡索引并保存
sll.sll_ifindex = (int)if_nametoindex(network_card);
// 创建 raw socket 文件描述符,用于发送和接受数据
sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd < 0) {
perror("sock fd create failed");
return;
}
FixPacket(s_mac_src, s_mac_dst);
while (true) {
len = sendto(sockfd, s_packet, K_MAX_PACKET_SIZE, K_SEND_FLAGS, (struct sockaddr*)&sll, sizeof(sll));
if (len < 0) {
perror("sendto failed");
break;
}
while (true) {
// 接收 raw socket 数据,并存入 buffer 中
len = recvfrom(sockfd, buffer, K_MAX_BUFFER_SIZE, K_RECV_FLAGS, NULL, NULL);
if (len < 0) {
perror("recvfrom failed");
return;
}
// 过滤数据,确定可以解析自定义类型数据
if (!FilterData(buffer, len)) {
continue;
}
// 解析数据
ParseData(buffer, len);
break;
}
// 控制发送频率
sleep(1);
}
}
// main 入口函数将目标机器的 mac 地址以及本机的 mac 地址以及本机的 rtnet 网卡名称依次传入程序
int main(int argc, char** argv) {
if (argc < 4) {
Help();
return 0;
}
// 解析 mac 地址
if (!ParseAddress(argv[1], s_mac_src)) {
printf("parse src mac failed, src mac %s error\n", argv[1]);
return 0;
}
if (!ParseAddress(argv[2], s_mac_dst)) {
printf("parse dst mac failed, src mac %s error\n", argv[2]);
return 0;
}
// 其他处理 ...
return 0;
}
client 启动参数分别为 本机 mac 地址,对端 services mac 地址,本机用于网络通信的网卡名称
sudo ./rtnet_client c8:6b:bc:80:0a:ce C8:6B:BC:80:08:EE enp1s0f2
运行结果:
sinsegye@sinsegye-sx21:~/projects/rtnet$ sudo ./rtnet_client c8:6b:bc:80:0a:ce c8:6b:bc:80:08:ee enp1s0f2
mac src : c8:6b:bc:80:0a:ce
mac dst : c8:6b:bc:80:08:ee
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times: 1
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times: 2
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times: 3
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times: 4
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times: 5
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times: 6
sendto success