Search code examples
clinuxsocketsnetworkingioctl

How to find the network interface used by a connected socket


How to find the interface used by a connected socket.So that i can set status codes for different interfaces.I used the below code.But I didnt get it.

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.

          #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;

Solution

  • Idea based on another post

    1. Create socket
    2. Connect
    3. Get interface address
    4. Get interface id and name from interface address
    $ gcc -std=gnu11 -Wall so_q_63899229.c
    $ ./a.out 93.184.216.34 # example.org
    interface index   : 2
    interface name    : wlp2s0
    interface address : 192.168.1.223
    remote    address : 93.184.216.34
    

    so_q_63899229.c

    #include <arpa/inet.h>
    #include <assert.h>
    #include <net/if.h>
    #include <netinet/in.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/socket.h>
    #include <unistd.h>
    
    int sockfd=-1;
    
    void connect2(const char *const dst){
    
      sockfd=socket(AF_INET,SOCK_STREAM,0);
      assert(sockfd>=3);
    
      struct sockaddr_in sin={
        .sin_family=AF_INET,
        .sin_port=htons(80),
        .sin_addr={}
      };
      assert(1==inet_pton(AF_INET,dst,&(sin.sin_addr)));
    
      assert(0==connect(sockfd,(struct sockaddr*)(&sin),sizeof(struct sockaddr_in)));
    
    }
    
    void getsockname2(struct sockaddr_in *const sin){
      socklen_t addrlen=sizeof(struct sockaddr_in);
      assert(0==getsockname(sockfd,(struct sockaddr*)sin,&addrlen));
      assert(addrlen==sizeof(struct sockaddr_in));
    }
    
    void disconnect(){
      close(sockfd);
      sockfd=-1;
    }
    
    void addr2iface_ifconf(const struct in_addr *const sin_addr,int *const index,char *const name){
    
      struct ifconf ifc={
        .ifc_len=0,
        .ifc_req=NULL
      };
    
      int ioctlfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
      assert(ioctlfd>=3);
      assert(0==ioctl(ioctlfd,SIOCGIFCONF,&ifc));
    
      const int sz=ifc.ifc_len;
      assert(sz%sizeof(struct ifreq)==0);
      const int n=sz/sizeof(struct ifreq);
    
      char buf[sz];
      bzero(buf,sz);
      ifc.ifc_buf=buf;
      assert(0==ioctl(ioctlfd,SIOCGIFCONF,&ifc));
      assert(
        ifc.ifc_len==sz &&
        (char*)ifc.ifc_req==buf
      );
    
      for(int i=0;i<n;++i)if(0==memcmp(
        &(((struct sockaddr_in*)(&(ifc.ifc_req[i].ifr_addr)))->sin_addr),
        sin_addr,
        sizeof(struct in_addr)
      )){
        *index=ifc.ifc_req[i].ifr_ifindex;
        assert(name==strncpy(name,ifc.ifc_req[i].ifr_name,IFNAMSIZ));
        return;
      }
    
      assert(0);
    
    }
    
    int main(int argc,const char *argv[]){
    
      assert(argc==2);
      assert(argv[1]&&strlen(argv[1]));
      const char *const remoteaddr_s=argv[1];
      // const char *const remoteaddr_s="93.184.216.34";
    
      connect2(remoteaddr_s);
    
      struct sockaddr_in ifaddr={};
      getsockname2(&ifaddr);
    
      disconnect();
    
      int index=0;
      char ifname[IFNAMSIZ]={};
      addr2iface_ifconf(&(ifaddr.sin_addr),&index,ifname);
    
      char ifaddr_s[INET_ADDRSTRLEN]={};
      assert(ifaddr_s==inet_ntop(AF_INET,&(ifaddr.sin_addr),ifaddr_s,INET_ADDRSTRLEN));
    
      printf("interface index   : %d\n",index);
      printf("interface name    : %s\n",ifname);
      printf("interface address : %s\n",ifaddr_s);
      printf("remote    address : %s\n",remoteaddr_s);
      // printf("#%d %s %s -> %s\n",
      //   index,
      //   ifname,
      //   ifaddr_s,
      //   remoteaddr_s
      // );
    
      return 0;
    
    }
    

    Also there doesn't seem to be an identifier named IFNAMSZ. IFNAMSIZ defined in <net/if.h> should be the maxinum legth (including '\0') allowed for the name of any interface IMHO.