C-如何在不使用sockaddr_ll的情况下使用sendto通过RAW以太网套接字发送数据



我正在从事一个项目,在该项目中我们使用ENEA OSE,这是一个实时嵌入式操作系统。我猜你们中的许多人都不熟悉此操作系统,但我认为您可以回答我的问题。我想使用接口" ETH1"在2张卡之间发送原始以太网框架,这些接口通过以太网电缆直接连接到Oneanother(无开关或任何内容)。" ETH1"接口未分配任何IP地址。

我可以简单地使用

声明插座
int s = socket(AF_INET, RAW_SOCKET, ...)

我可以使用:

将套接字绑定到适当的设备接口
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth1", 4);

我的计划是将数据从一张卡发送到另一张卡,是手动填充带有源和目标MAC Adresses的以太网缓冲区阵列。然后使用sendto(....)

发送数据

但是, sendto 例程也需要 sockaddr 结构:

  sendto(int sockfd, const void *buf, size_t len, int flags,
           const struct sockaddr *dest_addr, socklen_t addrlen);

在我在Internet上找到的示例中,当知道IP adress并为原始框架中 sockaddr_ll 时, sockaddr_in 。但是,OSE不提供sockaddr_ll struct。但是,它提供了 sockaddr 我不知道我是否可以用于原始以太网框架?

struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
};

我还尝试使用null,用于调用sendto()时sockaddr字段,发送失败。这使我提出了我的问题。我可以使用简单的sockaddr结构在不使用任何IP的情况下填充目标Mac-Adress吗?那看起来怎么样?(我没有看到任何显示此内容的代码)。为什么我们甚至必须提供 sockaddr 结构?以太网数据缓冲区是否已经包含此信息?最后,我试图在不为网络设备分配IP地址的情况下实现的目标吗?

我知道我想做的事情似乎很奇特,但是有一个原因:)如果您向我展示如何完成此操作而无需使用 sockaddr_ll struct。

对不起先前的模糊答案。这是我的完整答案。希望它有帮助。

回答您的问题:是的,您可以仅使用MAC地址发送。

像这样声明插座:

int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)

错误,sockfd是套接字文件描述符将返回-1。

可以这样构建以太网框架的结构:

struct ethernet_msg{
        char destination_mac[6];
        char source_mac[6];
        char transport_protocol[2];
        char data1[6];
        char data2[6];
        char data3[8];
        char data4[8];
        char data5[8];
        char data6[2];
        char data7[2];
        char data8[6];
        char data9[4];
        char data10[6];
};
struct ethernet_msg my_msg = {
        {0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00},//destination_mac
        {0x08, 0x00, 0x27, 0x90, 0x5f, 0xae},//source_mac
        {0x22, 0xf0},//transport_protocol
        {0xfc, 0x06, 0x00, 0x2c, 0x00, 0x00},//data1
        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},//data2
        {0x08, 0x00, 0x27, 0xff, 0xfe, 0x90, 0x5f, 0xae},//data3
        {0xd8, 0x80, 0x39, 0xff, 0xfe, 0xd0, 0xac, 0xb5},//data4
        {0xd8, 0x80, 0x39, 0xff, 0xfe, 0xd0, 0x9b, 0xc8},//data5
        {0x00, 0x00},//data6
        {0x00, 0x00},//data7
        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},//data8
        {0x00, 0x00, 0x00, 0x5f},//data9
        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}//data10
};

请注意,我如何将不同长度的字符阵列在框架内。您可以根据需要设置此设置。要获取接口的MAC地址:

void get_interface_MAC(char interface_name[IFNAMSIZ], int sockfd)
{
        struct ifreq if_mac;
        memset(&if_mac, 0, sizeof(struct ifreq));
        strncpy(if_mac.ifr_name, interface_name, IFNAMSIZ-1);
        if (ioctl(sockfd, SIOCGIFHWADDR, &if_mac) < 0)
                perror("SIOCGIFHWADDR");
}

然后获取网络设备的索引:

/* Get the index of the interface to send on */
int get_interface_index(char interface_name[IFNAMSIZ], int sockfd)
{
        struct ifreq if_idx;
        memset(&if_idx, 0, sizeof(struct ifreq));
        strncpy(if_idx.ifr_name, interface_name, IFNAMSIZ-1);
        if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0)
                perror("SIOCGIFINDEX");
        return if_idx.ifr_ifindex;
}
socket_address.sll_ifindex = get_interface_index(interface_name, sockfd);
/* Address length*/
socket_address.sll_halen = ETH_ALEN;
/* Destination MAC */
memcpy(socket_address.sll_addr, avdecc_msg.destination_mac, ETH_ALEN);

,然后最终通过插座发送:

/* Send packet */
if (sendto(sockfd, &avdecc_msg, sizeof(avdecc_msg), 0, (struct sockaddr*)&socket_address, sizeof(struct sockaddr_ll)) < 0)
                printf("Send failedn");

希望有帮助。我使用了此链接,并完善了代码来实现这一目标。它对我有用。

据我所知,这些标头

#include <sys/socket.h>
#include <netpacket/packet.h>

后者声明struct sockaddr_ll,它应该允许您将以太网数据包发送到类型

的插座
int sockfd = socket(AF_PACKET, type, protocol);

类型可以是SOCK_DGRAMSOCK_RAW(您需要自己创建以太网标题)。

我找不到以太网协议的正确标头。您需要搜索ETH_*的标题定义并找到正确的以太网标题

我没有一个好的答案,但是在绑定地址显示为显示的地址后,您可以使用写入。那么,为什么要使用sendto()?

首先要在linux中发送原始数据包(我不知道您的RTOS,但您应该在该操作系统提供的Include文件中寻找丢失的定义)您需要在系统上扎根或使用setCap命令添加原始功能例如:

sudo setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' /path/to/your/executable

然后在您的程序中:

#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
int main()
{
    char* ifname = "eth0";
    unsigned char srcMac[6];
    unsigned char dstMac[6] = {0, 0xDE, 0xAD, 0xBE, 0xEF, 0};
    int fd = socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ALL ) );
     /* bind to interface */
    struct ifreq req;
    memset( &req, 0, sizeof( req ) );
    strcpy( (char*)req.ifr_name, (char*)ifname );
    if ( ioctl( fd, SIOCGIFINDEX, &req ) < 0 )
    {
        perror( "init: ioctl" );
        close( fd );
        return -1;
    }
    struct sockaddr_ll addr;
    memset( &addr, 0, sizeof( addr ) );
    addr.sll_family   = PF_PACKET;
    addr.sll_protocol = 0;
    addr.sll_ifindex  = req.ifr_ifindex;
    if ( bind( fd, (const struct sockaddr*)&addr, sizeof( addr ) ) < 0 )
    {
        perror( "init: bind fails" );
        close( fd );
        return -1;
    }
    if ( ioctl( fd, SIOCGIFHWADDR, &req ) < 0 )
    {
        perror( "init: ioctl SIOCGIFHWADDR" );
        close( fd );
        return -1;
    }
    /* store your mac address somewhere you'll need it! in your packet */
    memcpy( srcMac, (unsigned char*)req.ifr_hwaddr.sa_data, ETH_ALEN );
    int length = 60;
    unsigned char txPkt[ length ];
    unsigned char* pkt = txPkt;
    memset( txPkt, 0, sizeof( txPkt ) );
    //destination mac address
    memcpy( pkt, dstMac, 6 );
    pkt += 6;
    // source mac address
    memcpy( pkt, srcMac, 6 );
    pkt += 6;
    // add more data, like a type field!  etc
    int bytes = write( fd, txPkt, length );
}

当您编写一个原始数据包时,您必须自己放入MAC地址等。FC可以被排除在外(应该由HW处理),因此最小的数据包大小为60字节,我相信您必须发送60个字节。如果您在发出的卡上捕获,您将不会看到FCS。

我想您应该能够使用sendto()函数代替写入,但请注意,struct sockaddr_ll不包含任何目的地信息,那么为什么要打扰呢?

最新更新