Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I bind to a default interface when using packet mode in C?

Tags:

c

linux

sockets

First, I'm new to socket programming, so I might be missing something obvious.

Goal:

I'm trying to write a small utility to hand-craft ARP frames and send them on the wire, then listen for replies. I am using AF_PACKET, SOCK_DGRAM so the kernel handles the Ethernet header/trailer, but I get raw access to the data portion of the frame on send and receive.

The application is diagnostic in nature, so existing APIs and userland tools aren't suitable. I'm trying to find one (or more) devices in a broadcast domain that respond to ARP requests, but I'm not at all concerned with populating the system ARP cache or anything like that.

Question:

I would like the user to be able to specify an interface by name, or use an appropriate default instead. So far, everything works perfectly if I specify the interface... but if I don't, I'm not sure how best to deal with bind() and sendto().

Details:

Here's a representative sample of the code in question. Assume that *cfg points to a pre-allocated struct, with 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
    ...
}

So far so good.

However, I don't know what to tell bind() if I don't have an interface name or an interface index. The man pages are rather sparse on detail when it comes to packet mode. Apparently, I can bind to "0.0.0.0" for all interfaces, but I'm not working at the IP layer here -- I have a sockaddr_ll struct.

Is there a good way to tell bind() to pick an appropriate interface? Is that even a reasonable request at layer-2?

Possible Solutions:

1) Pick the first interface that is UP and not loopback.

2) Go one step further and check the assigned address.

I don't know if either of these is really desirable, though. I kind of want the code to "do the right thing" here -- that is, what the user would expect. So, if there's a conventional order to how any given tool chooses the egress interface, that's what I want to do.

I suspect many tools rely on the routing table to choose the best interface, but that's not a great fit here. Normally, ARP traffic is bound to the subnet you're in. In this case, the user might be looking for misconfigured devices, or devices on a parallel network in the same broadcast domain. (That is: If everything is working properly, they don't need this tool. So all bets are off.)

Now, I'm not 100% certain how other hosts will cope with ARP requests sourced from IPs not in their subnet, but that's a problem for another day.

TLDR:

Is there a deterministic way to pick "the correct egress interface" (if such a thing exists) on a host with more than one, if you can't depend on the routing table?

like image 697
SirNickity Avatar asked Nov 09 '22 04:11

SirNickity


1 Answers

First of all, as you noticed already, you are not working on IP layer - but then, you need a raw socket, so open it with these parameters: AF_PACKET, SOCK_RAW, IPPROTO_RAW.

For getting a list of all interfaces, see here. Still you need criteria to select the appropriate one. I found code examples for sending and receiving raw ethernet frames, including selecting the interface from command line parameter. I did not verify if they are working, but you might have a look at.

If it is sufficient to you just to distinguish if your interface is wired or wireless, see here.

At last just a hint: If your programme still does not work, you might not have the appropriate privilege (CAP_NET_RAW), as described here.

like image 188
Aconcagua Avatar answered Nov 14 '22 21:11

Aconcagua