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

Sir*_*ity 5 c sockets linux

首先,我是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
    ...
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.

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

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

可能的解决方案:

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

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

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

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

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

TLDR:

如果你不能依赖路由表,是否有一种确定性的方法来选择具有多个主机的"正确的出口接口"(如果存在这样的东西)?

Aco*_*gua 1

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

要获取所有接口的列表,请参阅此处。您仍然需要标准来选择合适的。我找到了发送接收原始以太网帧的代码示例,包括从命令行参数选择接口。我没有验证它们是否有效,但你可以看看。

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

最后只是一个提示:如果您的程序仍然无法工作,您可能没有适当的权限(CAP_NET_RAW),如此处所述