First off, I'm new to socket programming, so I might miss something obvious.
Goal:
I am trying to write a small utility for drawing ARP frames and send them on a wire, and then listen to the answers. I use AF_PACKET, SOCK_DGRAM, so the kernel processes the Ethernet header / trailer, but I get raw access to the frame data part when sending and receiving.
The application is diagnostic in nature, therefore the existing APIs and user environment tools are not 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 interested in populating the system ARP cache or something like that.
Question:
I would like the user to be able to specify the interface by name or use the appropriate default instead. So far, everything works fine, if I specify the interface ... but if I do not, I am not sure how best to deal with bind () and sendto ().
Details:
Here is a representative sample code. Suppose * cfg points to a pre-allocated structure, 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 do not know what to say bind () if I do not have an interface name or interface index. The manual pages are pretty detailed when it comes to batch mode. Apparently, I can bind to "0.0.0.0" for all interfaces, but I do not work at the IP level here - I have a sockaddr_ll structure.
Is there a good way to tell bind () to select the appropriate interface? Is this even a reasonable request at level 2?
Possible solutions:
1) Select the first interface, which is UP, not loopback.
2) Go one step further and check the assigned address.
I do not know if one of them is really desirable. I kind of want the code to “do the right thing” here - that is, what the user expected. So, if there is a normal order for any given tool to choose an exit interface, this is what I want to do.
I suspect that many tools rely on the routing table to select the best interface, but this is not very convenient. Typically, ARP traffic is tied to the subnet you are on. In this case, the user can search for improperly configured devices or devices in a parallel network in the same broadcast domain. (That is: if everything works correctly, they do not need this tool. Thus, all bets are disabled.)
Now I’m not 100% sure how other hosts will handle ARP requests received from IP addresses and not on their subnet, but this is a problem for another day.
TL; DR:
Is there a deterministic way to select the “correct exit interface” (if such a thing exists) on a host with more than one if you cannot depend on the routing table?