在C中使用数据包模式时如何绑定到默认接口?

时间:2015-10-01 02:33:19

标签: c linux sockets

首先,我是socket编程的新手,所以我可能会遗漏一些明显的东西。

目标:

我正在尝试编写一个小工具来手工制作ARP帧并将其发送到线路上,然后收听回复。我正在使用AF_PACKET,SOCK_DGRAM,因此内核处理以太网头/尾部,但我在发送和接收时获得对帧的数据部分的原始访问。

该应用程序本质上是诊断性的,因此现有API和用户态工具不适用。我试图在广播域中找到响应ARP请求的一个(或多个)设备,但我并不关心填充系统ARP缓存或类似的东西。

问题:

我希望用户能够按名称指定接口,或者使用适当的默认值。到目前为止,如果我指定接口,一切都很完美...但如果我不这样做,我不确定如何最好地处理bind()和sendto()。

详细信息:

以下是相关代码的代表性示例。假设* cfg指向预先分配的结构,使用cfg-> interface.name ==“eth0”:

int  i;
int  sock;
void *buf;
struct my_config_options  *cfg;
struct ifreq  ifr;
struct sockaddr_ll  addr;
struct pollfd  pfd;


// Packet buffer
buf = malloc(BUF_LEN);

if (buf == NULL) {
    return 0;
}


// Open a socket
sock = socket(AF_PACKET, SOCK_DGRAM, 0);

if (sock_fd == -1) {
    return 0;
}


// Get interface index
strncpy(ifr.ifr_name, cfg->interface.name, IFNAMSIZ);

if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) {
    return 0;
}

cfg->interface.idx = ifr.ifr_ifindex;


// Get source MAC address
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) {
    return 0;
}

for (i = 0; i < 6; i++) {
    cfg->interface.mac[i] = ifr.ifr_hwaddr.sa_data[i];
}


// Get source IP address
if (ioctl(sock, SIOCGIFADDR, &ifr) == -1) {
    return 0;
}

cfg->interface.ip = ((struct sockaddr_in *)(&ifr.ifr_addr))->sin_addr.s_addr;


// Bind socket to interface
memset(&addr, 0x00, sizeof(addr));

addr.sll_family   = AF_PACKET;
addr.sll_protocol = htons(ETH_P_ARP);
addr.sll_ifindex  = cfg->interface.idx;

if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    close(sock);
    return 0;
}

...

// Send an ARP request
memset(&addr, 0x00, sizeof(addr));

addr.sll_family   = AF_PACKET;
addr.sll_protocol = htons(ETH_P_ARP);
addr.sll_ifindex  = cfg->interface.idx;
addr.sll_halen    = ETH_ALEN;

for (i = 0; i < 6; i++) {
    addr.sll_addr[i] = 0xff;
}

if (sendto(sock, buf, BUF_LEN, 0, (struct sockaddr *)&addr, sizeof(addr)) != BUF_LEN) {
    close(sock);
    return 0;
}

while (1) {

    pfd.fd      = sock;
    pfd.events  = POLLIN;
    pfd.revents = 0;

    i = poll(&pfd, 1, cfg->delay);

    // Error
    if (i < 0) {
        close(sock);
        return 0;
    }

    // Timed out
    if (i == 0) {
        break;
    }


    // Read buffered packet
    i = sizeof(addr);
    i = recvfrom(sock, buf, BUF_LEN, MSG_DONTWAIT, (struct sockaddr*)&addr, &i);

    if (i < 0) {
        close(sock);
        return 0;
    }

    // Handle received data
    ...
}

到目前为止一切顺利。

但是,如果我没有接口名称或接口索引,我不知道告诉bind()。在数据包模式方面,手册页的细节相当稀疏。显然,我可以为所有接口绑定到“0.0.0.0”,但我不在这里的IP层工作 - 我有一个sockaddr_ll结构。

有没有一种好方法告诉bind()选择合适的接口?这是第二层的合理要求吗?

可能的解决方案:

1)选择UP的第一个接口,而不是环回。

2)更进一步检查分配的地址。

但我不知道这些中的任何一个是否真的令人满意。我希望代码在这里“做正确的事” - 即用户期望的内容。因此,如果有任何给定工具选择出口接口的常规顺序,那就是我想要做的。

我怀疑很多工具都依赖于路由表来选择最佳界面,但这并不适合这里。通常,ARP流量绑定到您所在的子网。在这种情况下,用户可能正在查找配置错误的设备或同一广播域中并行网络上的设备。 (也就是说:如果一切正常,他们就不需要这个工具。所以所有的赌注都没有了。)

现在,我不是100%肯定其他主机如何应对来自不在其子网中的IP的ARP请求,但这是另一天的问题。

TLDR:

如果您不能依赖路由表,是否有确定的方法在具有多个主机的主机上选择“正确的出口接口”(如果存在此类事物)?

1 个答案:

答案 0 :(得分:1)

首先,正如您已经注意到的那样,您不是在处理IP层 - 但是,您需要一个原始套接字,因此请使用以下参数打开它:AF_PACKET, SOCK_RAW, IPPROTO_RAW

要获取所有接口的列表,请参阅here。您仍然需要标准来选择合适的标准。我找到了sendingreceiving原始以太网帧的代码示例,包括从命令行参数中选择接口。我没有验证它们是否正常工作,但你可能会看一下。

如果只是区分您的接口是有线还是无线就足够了,请参阅here

最后只是一个提示:如果你的程序仍然不起作用,你可能没有适当的权限(CAP_NET_RAW),如here所述。