如何查找已连接的套接字使用的网络接口

问题描述

如何查找已连接的套接字使用的接口。以便我可以为不同的接口设置状态代码。我使用了下面的代码。但是我没有得到它。

我在下面的测试代码中尝试了两种不同的方法,但是都失败了。第一个连接到远程服务器,并且将ioctl与SIocgIFNAME一起使用,但是由于“没有这样的设备”而失败。相反,第二个将getockopt与SO_BINDTODEVICE一起使用,但这又失败了(将名称长度设置为0)。

关于为什么这些失败或如何获得I / F名称的任何想法?编译后,将测试代码作为测试“ abcd”运行,其中abcd是在端口80上侦听的任何IPV4地址。请注意,我已经在Centos 7上对此进行了编译,它在,因此您可能必须注释掉#define IFNAMSZ行才能使其在其他系统上编译。

谢谢。

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

解决方法

Idea based on another post

  1. 创建套接字
  2. 连接
  3. 获取接口地址
  4. 从接口地址获取接口ID和名称
$ 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,&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;

}

似乎没有一个名为IFNAMSZ的标识符。 中定义的IFNAMS I Z应该是任何接口恕我直言的名称所允许的最大长度(包括“ \ 0”)。