软件环境:vivado 2017.4? ? ? ? 硬件平台:XC7Z020
之前已经说了在linux下,怎么建立UDP、TCP-server、TCP-client的应用,而这些都是带有特定协议的以太网数据包,所以就好奇啦,有没有办法发送自定义type类型的以太网数据包呢?当然啦,是有的,切入点就是
int socket( int af, int type, int protocol);
?建立socket套接字函数中的第二个参数type,它常用的有以下几种,分别含义如下。
参数名称 | 解释 | SOCK_STREAM | TCP连接,提供序列化的、可靠的、双向连接的字节流,支持带外数据传输 | SOCK_DGRAM | UDP,支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务 | SOCK_SEQPACKET | 序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出 | SOCK_RAW | RAW类型,提供原始网络协议访问 | SOCK_RDM | 提供可靠的数据报文,不过可能数据会有乱序 | SOCK_PACKET | 这是一个专用类型,直接从设备驱动接收数据 |
今天要举例说明的就是通过SOCK_RAW来发送自定义type类型的以太网数据包。
当然,工程不变。
?接下来,上代码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <bits/ioctls.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <errno.h>
#define ETH_USER_PROTOCOL 0x6678 //自定义的以太网协议type
int main (int argc, char **argv)
{
int i, datalen,frame_length, sd, bytes;
char *interface="eth0";
uint8_t data[IP_MAXPACKET];
uint8_t src_mac[6];
uint8_t dst_mac[6];
uint8_t ether_frame[IP_MAXPACKET];
struct sockaddr_ll device;
struct ifreq ifr;
//第一次创建socket是为了获取本地网卡信息
if((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0)
{
perror("socket() failed to get socket descriptor for using ioctl() ");
exit(EXIT_FAILURE);
}
memset(&ifr, 0, sizeof (ifr));
snprintf(ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface);
//寻找interface所对应的本地网络接口的MAC地址
if(ioctl (sd, SIOCGIFHWADDR, &ifr) < 0)
{
perror("ioctl() failed to get source MAC address ");
return(EXIT_FAILURE);
}
close(sd);
//将获取到的MAC地址,给src_mac变量
memcpy(src_mac, ifr.ifr_hwaddr.sa_data, 6);
printf("MAC address for interface %s is ", interface);
for(i=0;i<5;i++)
{
printf("%02x:", src_mac[i]);
}
printf("%02x\n", src_mac[5]);
memset(&device, 0, sizeof (device));
//获取interface变量所指向的本地网络接口的索引号
if((device.sll_ifindex = if_nametoindex (interface)) == 0)
{
perror("if_nametoindex() failed to obtain interface index ");
exit(EXIT_FAILURE);
}
printf("Index for interface %s is %i\n", interface, device.sll_ifindex);
//设置目的网卡地址
dst_mac[0] = 0x10;
dst_mac[1] = 0x78;
dst_mac[2] = 0xd2;
dst_mac[3] = 0xc6;
dst_mac[4] = 0x2f;
dst_mac[5] = 0x89;
//填充device结构体其余必要参数
device.sll_family = AF_PACKET;
memcpy(device.sll_addr, src_mac, 6);
device.sll_halen = htons(6);
//发送的data,最小数据长度为46,不足的会自动补零处理
datalen = 12;
data[0] = 'h';
data[1] = 'e';
data[2] = 'l';
data[3] = 'l';
data[4] = 'o';
data[5] = ' ';
data[6] = 'j';
data[7] = 'o';
data[8] = 'k';
data[9] = 'e';
data[10] = 'r';
data[11] = '!';
//发送帧总长计算,DST_MAC + SRC_MAC + ETH_USER_PROTOCOL + DATA
frame_length = 6+6+2+datalen;
//发送帧填充
memcpy(ether_frame, dst_mac, 6);
memcpy(ether_frame + 6, src_mac, 6);
ether_frame[12] = ETH_USER_PROTOCOL / 256;
ether_frame[13] = ETH_USER_PROTOCOL % 256;
//填充data字段
memcpy(ether_frame + 14 , data, datalen);
//创建正真发送的socket
if((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0)
{
perror("socket() failed ");
exit(EXIT_FAILURE);
}
//发送自定义以太网包
if((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *) &device, sizeof (device))) <= 0)
{
perror("sendto() failed ");
exit(EXIT_FAILURE);
}
printf("send num=%d,success send num=%d\n",frame_length,bytes);
close(sd);
return (EXIT_SUCCESS);
}
对代码部分解释说明如下:
1.第一次使用socket()函数,是为了获取本地网卡信息,什么意思呢,就是说由于是发送自定义类型、自定义格式的以太网包,在无法获知ARP路由信息的情况下,需预先告知自定义的以太网包数据,从哪个网络接口上发出去。
2.另外,在本地板卡包含多个网络接口时,也需要告诉函数,我由interface参数指定的接口名称的网卡,是否在本地存在,如果存在,这个接口的MAC地址是什么(因为最基本的目的MAC + 源MAC + TYPE这种最基本的包格式还是要遵循的)。
3.紧接着,通过ioctl (sd, SIOCGIFHWADDR, &ifr),将在本地搜索ifr.ifr_name名称对应的网络接口,并将接口信息初始化给ifr结构体,其中,MAC地址就包含在ifr.ifr_hwaddr.sa_data中。
4.从这之后,device结构体才是sendto()时需要真正利用的参数,填充好类型、源MAC地址、地址长度等参数后,就可以开始组自定义数据字段内容了。
5.最后,利用sendto()将组好的包含自定义type和自定义data的以太网数据包发出。
6.这里,在建立socket时,多次使用了ETH_P_ALL这个参数,是什么意思呢,举个例子
socket(PF_PACKET, SOCK_RAW, htons(0x6678));
由于type类型为0x6678的报文没有在if_ether.h中定义,所以套接字在遇见此类型的报文时,就不知道该如何处理,此函数的调用,其实就是告诉套接字,sd在遇到type类型为0x6678的报文时,可以使用sendto()及recvfrom()对此以太网包进行收发,而ETH_P_ALL如字面意思,任何类型的协议包,都进行处理。
将上述代码编译运行在板卡上后,得到的调试打印信息如下。
与此同时,通过wireshark抓包,可以看到,PC端接收到自定义类型的数据包了,且与组包内容相一致。
|