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

How to find the network interface used by a connected socket

如何找到连接的 socket.So 使用的接口,我可以为不同的 interfaces.I 设置状态代码使用下面的 code.But 我没明白。

我在下面的测试代码中尝试了两种不同的方法,但都失败了。第一个连接到远程服务器,并使用 ioctl 和 SIOCGIFNAME,但这失败了 'no such device'。第二个改为使用带有 SO_BINDTODEVICE 的 getsockopt,但这再次失败(它将名称长度设置为 0)。

关于这些失败的原因或如何获得 I/F 名称的任何想法?编译后,运行 测试代码为 test "a.b.c.d",其中 a.b.c.d 是监听 80 端口的任意 IPV4 地址。注意我是在 Centos 7 上编译的, 中似乎没有 IFNAMSZ,因此您可能必须注释掉 #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, 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;

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

}

而且似乎没有名为 IFNAMSZ 的标识符。 中定义的 IFNAMSIZ 应该是任何接口名称允许的最大长度(包括'\0')恕我直言。