如何使用sendto通过原始以太网套接字发送数据而不使用sockaddr_ll?

时间:2014-01-28 16:53:48

标签: c sockets ose

我正在开发一个使用Enea OSE的项目,这是一个实时的嵌入式操作系统。我猜你们很多人都不熟悉这个操作系统,但我认为无论如何你都可以回答我的问题。我想使用接口“eth1”在2张卡之间发送原始以太网帧,这些接口通过以太网电缆(无交换机或任何东西)直接连接到另一个。 “eth1”接口未分配任何IP地址。

我可以使用

简单地声明一个套接字
int s = socket(AF_INET, RAW_SOCKET, ...)

然后我可以使用以下命令将套接字绑定到相应的设备接口:

setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth1", 4);

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

发送数据

然而 sendto 例程还需要一个带有信息的 sockaddr 结构:

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

在互联网上找到的示例中,当知道ip地址时使用 sockaddr_in ,对于原始帧使用 sockaddr_ll 。但是,OSE不提供 sockaddr_ll 结构。但它提供了 sockaddr 我不知道我是否可以使用原始以太网帧?

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

我也尝试使用NULL,当调用sendto()时,对于sockaddr字段,发送失败。这让我想到了我的问题。我可以使用简单的sockaddr结构来填充目标MAC地址而不使用任何IP吗?那怎么样? (我没有看到任何显示此代码的代码)。为什么我们甚至必须提供 sockaddr 结构?以太网数据缓冲区是否已包含此信息?最后,如果没有为网络设备分配IP地址,我是否正在努力实现这一目标?

我知道我想要做的事情似乎很奇怪,但有理由这样做:)如果你能告诉我如何在不使用 sockaddr_ll struct。

3 个答案:

答案 0 :(得分:1)

据我所知,Enea OSE附带了这些标题

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

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

的套接字
int sockfd = socket(AF_PACKET, type, protocol);

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

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

答案 1 :(得分:1)

对以前模糊的回答感到抱歉。 这是我的完整答案。希望它有所帮助。

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

像这样声明套接字:

self._interpolation.before_read

出错时,作为套接字文件描述符的sockfd将返回-1。

以太网帧的结构可以这样构造:

int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)

请注意我如何将不同长度的char数组放在框架内。您可以根据需要进行设置。要获取要发送的接口的MAC地址:

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
};

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

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);

希望有所帮助。 我使用了这个link并对代码进行了改进以实现此目的。它对我有用。

答案 2 :(得分:-1)

我没有一个很好的答案但是在你绑定地址之后在Linux中你可以使用write。 那么你为什么要使用sendto()?

首先在Linux中发送原始数据包(我不知道您的RTOS,但您应该查找该操作系统提供的包含文件中缺少的定义) 您需要成为系统的root用户或使用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地址等。 可以省略FCS(应该由hw处理),因此最小数据包大小为60字节,你必须发送60个字节。 如果你在外出卡上捕获,你将无法看到FCS。

我想您应该能够使用sendto()函数而不是写入,但请注意struct sockaddr_ll不包含任何目标信息,为什么还要麻烦?