Find the interface used by a connected socket

2019-09-17 09:58发布

I need to find the specific interface which is used by a socket, so that I can keep stats for it, using the sysfs files (/sys/class/net/<IF>/statistics/etc).

I've tried two different approaches in the test code below, but both fail. The first one connects to a remote server, and uses ioctl with SIOCGIFNAME, but this fails with 'no such device'. The second one instead uses getsockopt with SO_BINDTODEVICE, but this again fails (it sets the name length to 0).

Any ideas on why these are failing, or how to get the I/F name? after compiling, run the test code as test "a.b.c.d", where a.b.c.d is any IPV4 address which is listening on port 80. Note that I've compiled this on Centos 7, which doesn't appear to have IFNAMSZ in <net/if.h>, so you may have to comment out the #define IFNAMSZ line to get this to compile on other systems.

Thanks.

EDIT

I've since found that this is essentially a dupe of How can I get the interface name/index associated with a TCP socket?, so I should probably remove this. (Only) one of the answers there is correct (https://stackoverflow.com/a/37987807/785194) - get your local IP address with getsockname, and then look up this address in the list returned by getifaddrs.

On the general issue that sockets are essentially dynamic (mentioned below, and several times in the other question): not really relevant. I've checked the kernel source, and sockets have an interface index and interface name, and the API includes at least three ways to get the current name, and other routines to look up the name from the index, and vice-versa. However, the index is somtimes zero, which is not valid, which is why the getsockopt version below fails. No idea why ioctl fails.

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>

int main(int argc, char **argv) {
   int sock;
   struct sockaddr_in dst_sin;
   struct in_addr     haddr;

   if(argc != 2)
      return 1;

   if(inet_aton(argv[1], &haddr) == 0) {
      printf("'%s' is not a valid IP address\n", argv[1]);
      return 1;
   }

   dst_sin.sin_family = AF_INET;
   dst_sin.sin_port   = htons(80);
   dst_sin.sin_addr   = haddr;

   if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      perror("socket");
      return 1;
   }

   if(connect(sock, (struct sockaddr*)&dst_sin, sizeof(dst_sin)) < 0) {
      perror("connect");
      return 1;
   }

   printf(
      "connected to %s:%d\n",
      inet_ntoa(dst_sin.sin_addr), ntohs(dst_sin.sin_port));

#if 0 // ioctl fails with 'no such device'
   struct ifreq ifr;
   memset(&ifr, 0, sizeof(ifr));

   // get the socket's interface index into ifreq.ifr_ifindex
   if(ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
      perror("SIOCGIFINDEX");
      return 1;
   }

   // get the I/F name for ifreq.ifr_ifindex
   if(ioctl(sock, SIOCGIFNAME, &ifr) < 0) {
      perror("SIOCGIFNAME");
      return 1;
   }

   printf("I/F is on '%s'\n", ifr.ifr_name);

#else // only works on Linux 3.8+
#define IFNAMSZ IFNAMSIZ               // Centos7 bug in if.h??

   char      optval[IFNAMSZ] = {0};
   socklen_t optlen = IFNAMSZ;

   if(getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &optval, &optlen) < 0) {
      perror("getsockopt");
      return 1;
   }

   if(!optlen) {
      printf("invalid optlen\n");
      return 1;
   }

   printf("I/F is on '%s'\n", optval);

#endif

   close(sock);
   return 0;
}

1条回答
Juvenile、少年°
2楼-- · 2019-09-17 10:12

TCP (and UDP) sockets are not bound to interfaces, so there is really no facility for answering this query. Now it's true that in general, a given socket will end up passing packets to a specific interface based on the address of the peer endpoint, but that is nowhere encoded in the socket. That's a routing decision that is made dynamically.

For example, let's say that you are communicating with a remote peer that is not directly on your local LAN. And let's say you have a default gateway configured to be 192.168.2.1 via eth0. There is nothing to prevent your configuring a second gateway, say, 192.168.3.1 via eth1, then taking eth0 down. As long as the new gateway can also reach the remote IP, eth1 can now be used to reach the destination and your session should continue uninterrupted.

So, if you need this info, you'll need to infer it from routing entries (but realize that it is not guaranteed to be static, even though in practice it will likely be so). You can obtain the address of your peer from getpeername(2). You can then examine the available routes to determine which one will get you there.

To do this, you could parse and interpret /proc/net/route for yourself, or you can just ask the ip command. For example, my route to an (arbitrary) ibm.com address goes through my eth0 interface, and connecting a socket to there, my local address will be 192.168.0.102 (which should match what getsockname(2) on the connected socket returns):

$ ip route get 129.42.38.1
129.42.38.1 via 192.168.0.1 dev eth0  src 192.168.0.102
cache
查看更多
登录 后发表回答