lwoods 发表于 2012-11-15 17:33

linux下socket编程求助

平台linux,网络IPV6。
在创建socket成功,在调用bind()函数的时候返回22,invalid argument。
google了半天,发现是sockaddr_in6中的sin6_scope_id没有设置导致,继续查,这个字段的含义好像是标示interface index,也就是使用的是ETH0,ETH1还是其他。
请教各位大侠,这个ifindex在C程序可以怎么取出来了?现在只是知道IP,devicename(eth0, eth1, etc)这些都不清楚。
或者有没有什么方法,可以不设置sin6_scope_id就能bind成功(我试了,把sin6_scope_id设置为相应的ifindex后bind能成功,但是不知道在C中怎么获取这个index)?
PS:使用system()系统调用,通过ip link |grep 这样的操作来获取的方法pass,因为很可能用户没有那么高的权限去使用这些shell命令,或者这些命令也可能被屏蔽掉

linux_c_py_php 发表于 2012-11-15 20:10

   Address Format
         struct sockaddr_in6 {
               sa_family_t   sin6_family;   /* AF_INET6 */
               in_port_t       sin6_port;   /* port number */
               uint32_t      sin6_flowinfo; /* IPv6 flow information */
               struct in6_addr sin6_addr;   /* IPv6 address */
               uint32_t      sin6_scope_id; /* Scope ID (new in 2.4) */
         };

         struct in6_addr {
               unsigned char   s6_addr;   /* IPv6 address */
         };

       sin6_family is always set to AF_INET6; sin6_port is the protocol port (see sin_port in ip(7));sin6_flowinfoistheIPv6flowidentifier;
       sin6_addr is the 128-bit IPv6 address.sin6_scope_id is an ID depending on the scope of the address.It is new in Linux 2.4.Linux only sup-
       ports it for link-local addresses, in that case sin6_scope_id contains the interface index (see netdevice(7))我也不懂, 试着用getaddrinfo吧, 应该会帮你填好, 另外我觉得一般填0都没问题, 所以结构体先bzero一下.

lwoods 发表于 2012-11-16 10:56

linux_c_py_php 发表于 2012-11-15 20:10 static/image/common/back.gif
我也不懂, 试着用getaddrinfo吧, 应该会帮你填好, 另外我觉得一般填0都没问题, 所以结构体先bzero一下.

现在使用的ipv6地址就是一个local link地址,FE08开头的,所以这个值应该是需要填写的吧,使用0的时候,bind就会失败。
关于getaddrinfo,这个函数是返回的一个列表,里面包含了多个地址相关信息,但是我这里需要指定是UDP,TCP或者SCTP,直接用这个函数的话,是不是还需要自己再做一次查找,找到list中的匹配项?

linux_c_py_php 发表于 2012-11-16 11:38

可以指定第三个参数hint进行过滤, 只返回希望得到的结果, 自己manpage一下.

如果是local link(应该是同一子网下的目标机吧?), inet6 addr: fe80::216:3eff:fe62:fc12/64 Scope:Link , 我ifconfig获得的这个地址是不是呢? getaddrinfo应该会填上吧, 除非你的网卡没有自动生成.

lwoods 发表于 2012-11-16 10:56 static/image/common/back.gif
现在使用的ipv6地址就是一个local link地址,FE08开头的,所以这个值应该是需要填写的吧,使用0的时候, ...

lwoods 发表于 2012-11-16 14:37

linux_c_py_php 发表于 2012-11-16 11:38 static/image/common/back.gif
可以指定第三个参数hint进行过滤, 只返回希望得到的结果, 自己manpage一下.

如果是local link(应该是同一 ...

hi
试了一下使用getaddrinfo这个函数,bind还是失败,解析了一下它的res出参,scope_id还是为0,如果是手动强制把这个值赋值,就没有问题:
写了个小的测试程序,代码如下:

#include <iostream>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <sys/ioctl.h>
#include <net/if.h>


using namespace std;

int main() {
        int res,sock,s,inInterfaces;
        struct sockaddr_in6 *ip6_addr;
        struct sockaddr *addr;
        struct in6_addr in6;
        char *addrStr = "fe80::20c:29ff:fe80:97d2";
        char *addrStr2 = "fe80::20c:29ff:fe80:97dc";
        intport =5060;
        struct ifaddrs *ifaddr;
        int   ifindex = 0;

        sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
        if (sock < 0)
        {
                printf("creat sock err[%d]\r\n",errno);
        }

        ip6_addr = (sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6));
        memset(ip6_addr,0,sizeof(struct sockaddr_in6));

        inet_pton(AF_INET6, addrStr, &in6);
        ip6_addr->sin6_family = AF_INET6;
        addr = (sockaddr *)malloc(sizeof(struct sockaddr));
        memset(addr,0,sizeof(struct sockaddr));

        memcpy(&ip6_addr->sin6_addr,
                        &in6,sizeof(struct in6_addr));


struct addrinfo hints, *result, *p;
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_INET6;
        hints.ai_protocol = IPPROTO_UDP;
        hints.ai_flags    = AI_PASSIVE;

        res = getaddrinfo(addrStr, "5060", &hints,&result);
                if (res != 0)
                {
                        printf("getaddrinfo err[%d] res[%d]!!!\r\n",errno,res );
                }
                else
                {
                        printf("getaddrinfo ok!!!\r\n" );
                }
        p = result;
    while (p != NULL)
    {
          ifindex = ((sockaddr_in6 *)p->ai_addr)->sin6_scope_id;
          printf("ifindex[%d]\r\n",ifindex);          //这里输出始终为0          
               printf("port[%d]\r\n",htons(((sockaddr_in6 *)p->ai_addr)->sin6_port));
          p = p->ai_next;
    }

        //ip6_addr->sin6_scope_id = 2;
        //ip6_addr->sin6_port   = htons(port);
        //addr =(struct sockaddr *) ip6_addr;
        //res = bind(sock, addr, sizeof(struct sockaddr_in6));

        //((struct sockaddr_in6 *) result->ai_addr)->sin6_scope_id = 2;//如果设置了这个,就能绑定成功       
        res = bind(sock, result->ai_addr, result->ai_addrlen);

        if (res != 0)
        {
                printf("bind socket err[%d] res[%d] ai_addrlen[%d]!!!\r\n",errno,res,result->ai_addrlen );
        }
        else
        {
                printf("bind socket ok!!!\r\n" );
        }

        return 0;
}

linux_c_py_php 发表于 2012-11-16 15:02

It is new in Linux 2.4.Linux only sup-
       ports it for link-local addresses, in that case sin6_scope_id contains the interface index (see netdevice(7))

看了一下man 7 netdevice, 看样你需要用ioctl来获取ethx的intifr_ifindex并填到addr里了.SYNOPSIS
       #include <sys/ioctl.h>
       #include <net/if.h>

DESCRIPTION
       This man page describes the sockets interface which is used to configure network devices.

       Linux supports some standard ioctls to configure network devices.They can be used on any socket’s file descriptor regardless of the family or
       type.They pass an ifreq structure:

         struct ifreq {
               char ifr_name; /* Interface name */
               union {
                   struct sockaddr ifr_addr;
                   struct sockaddr ifr_dstaddr;
                   struct sockaddr ifr_broadaddr;
                   struct sockaddr ifr_netmask;
                   struct sockaddr ifr_hwaddr;
                   short         ifr_flags;
                   int             ifr_ifindex;
                   int             ifr_metric;
                   int             ifr_mtu;
                   struct ifmap    ifr_map;
                   char            ifr_slave;
                   char            ifr_newname;
                   char         *ifr_data;
               };
         };

         struct ifconf {
               int               ifc_len; /* size of buffer */
               union {
                   char         *ifc_buf; /* buffer address */
                   struct ifreq   *ifc_req; /* array of structures */
               };
         };SIOCGIFINDEX
            Retrieve the interface index of the interface into ifr_ifindex.

用这个选项, 填写好网卡名称echx这样, 调用ioctl拿一下ifindex把。

lwoods 发表于 2012-11-16 16:07

linux_c_py_php 发表于 2012-11-16 15:02 static/image/common/back.gif
It is new in Linux 2.4.Linux only sup-
       ports it for link-local addresses, in that case si ...

是的,我现在就是在用ioctl获取ifindex,但是现在有个问题,ifq返回的数据中,ifindex,device name都是正确的,但是IP地址貌似不对啊,返回的IPV4的地址和网卡配置的相同,但是IPV6的地址不正确。
现在需要的是能通过IPV6地址,知道ifindex,然后填写scope_id;
部分代码如下:

        s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
        if(s < 0)
    {
      perror("socket");
      return 1;
    }
        ifc.ifc_len = sizeof(buf);
        ifc.ifc_buf = buf;
        if(ioctl(s, SIOCGIFCONF, &ifc) < 0)
        {
          perror("ioctl(SIOCGIFCONF)");
          return 1;
        }

        ifr          = ifc.ifc_req;
        inInterfaces = ifc.ifc_len / sizeof(struct ifreq);
        //res=ioctl(s,SIOCGIFNAME,&ifr);
        for(int i=0; i<inInterfaces; i++)
        {
                res=ioctl(s,SIOCGIFINDEX,&ifr);
                if(res)
                {
                   perror("index error");
                }
                else
                {
                        inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)&(ifr.ifr_ifru.ifru_addr))->sin6_addr), dststr, 28);
                        printf("get ipv6 addr[%s]\r\n",dststr);
                        inet_ntop(AF_INET, &(((struct sockaddr_in *)&(ifr.ifr_ifru.ifru_addr))->sin_addr), dststr, 28);
                        //inet_ntop(AF_INET, &(((struct sockaddr_in *)&(ifr.ifr_ifru.ifru_addr))->sin_addr), dststr, 28);
                        printf(" ifindex[%d] interface[%s],addr[%s] \n",
                                        ifr.ifr_ifindex,
                                        ifr.ifr_name,
                                        dststr);
                }
        }

lwoods 发表于 2012-11-16 17:32

刚才查了一下,iotcl好像是取不到IPV6的地址,换了个函数getifaddr,能够取出保存IPV6在内的所以地址信息,貌似能够解决我现在的问题。写了一小段测试代码,能够符合我的需求,呵呵
int main() {
        int res,sock, ifindex=0;
        struct sockaddr_in6 *ip6_addr, *addr_v6;
        struct sockaddr *addr;
        struct in6_addr in6;
        char *addrStr = "fe80::20c:29ff:fe80:97d2";
        char *addrStr2 = "fe80::20c:29ff:fe80:97dc";
        intport =5060;
        struct ifaddrs *ifaddr, *ifa;
        char *dststr;

        sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP);
        if (sock < 0)
        {
                cout << "create socket err!!!" << endl;
        }

        ip6_addr = (sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6));
        memset(ip6_addr,0,sizeof(struct sockaddr_in6));

        inet_pton(AF_INET6, addrStr2, &in6);
        ip6_addr->sin6_family = AF_INET6;
        addr = (sockaddr *)malloc(sizeof(struct sockaddr));
        memset(addr,0,sizeof(struct sockaddr));

        memcpy(&ip6_addr->sin6_addr,
                        &in6,sizeof(struct in6_addr));

        ip6_addr->sin6_port   = htons(port);

        dststr = (char *)malloc(28);

        if (getifaddrs(&ifaddr) == -1) {
                       perror("getifaddrs");
                       exit(EXIT_FAILURE);
        }
        for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
          if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET6)
                continue;

          addr_v6=(struct sockaddr_in6*)(ifa->ifa_addr);
          inet_ntop(AF_INET6,&(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr),dststr,28);

          for (int j=0;j < 4; j++)
          {
                if(addr_v6->sin6_addr.__in6_u.__u6_addr32 !=
                                ip6_addr->sin6_addr.__in6_u.__u6_addr32)
                {
                        break;
                }

                  if(j == 3)
                  {
                          ifindex = addr_v6->sin6_scope_id;
                    printf("ok ipv6 addr[%s] devicename[%s] ifindex[%d]\r\n",dststr, ifa->ifa_name,addr_v6->sin6_scope_id);
                  }
          }
          if (ifindex != 0)
          {
                  break;
          }
        }

        ip6_addr->sin6_scope_id = ifindex;
        addr =(struct sockaddr *) ip6_addr;

        res = bind(sock, addr, sizeof(struct sockaddr_in6));

        if (res != 0)
        {
                printf("bind socket err[%d] res[%d]!!!\r\n",errno,res );
        }
        else
        {
                printf("bind socket ok!!!\r\n" );
        }

        return 0;
}

linux_c_py_php 发表于 2012-11-16 18:05

本帖最后由 linux_c_py_php 于 2012-11-16 18:06 编辑


       SIOCGIFCONF
            Returnalist of interface (transport layer) addresses.This currently means only addresses of the AF_INET (IPv4) family for compati-
            bility.The user passes a ifconf structure as argument to the ioctl.It contains a pointer to an array of ifreq structures inifc_req
            and its length in bytes in ifc_len.The kernel fills the ifreqs with all current L3 interface addresses that are running: ifr_name con-
            tains the interface name (eth0:1 etc.), ifr_addr the address.The kernel returns with the actual lengthinifc_len.   Ififc_lenis
            equal to the original length the buffer probably has overflowed and you should retry with a bigger buffer to get all addresses.When no
            error occurs the ioctl returns 0; otherwise -1.Overflow is not an error.
   Strictly speaking, SIOCGIFCONF and the other ioctls that only accept or return AF_INET socket addresses, are IP specific and belong in ip(7).

       The names of interfaces with no addresses or that don’t have the IFF_RUNNING flag set can be found via /proc/net/dev.

       Local IPv6 IP addresses can be found via /proc/net or via rtnetlink(7).
的确获取不到IPV6的信息,

# cat/proc/net/if_inet6
00000000000000000000000000000001 01 80 10 80       lo
fe8000000000000002163efffe62fc12 02 40 20 80   eth0

不知道后边几个字段的含义, 你这个接口应该是一个封装, 估计它多考虑了ipv6吧

linux_c_py_php 发表于 2012-11-16 18:09

VERSIONS
       Thegetifaddrs()function first appeared in glibc 2.3, but before glibc 2.3.3, the implementation only supported IPv4 addresses; IPv6 support
       was added in glibc 2.3.3.Support of address families other than IPv4 is only available on kernels that support netlink.
       #include <arpa/inet.h>
       #include <sys/socket.h>
       #include <netdb.h>
       #include <ifaddrs.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <unistd.h>

       int
       main(int argc, char *argv[])
       {
         struct ifaddrs *ifaddr, *ifa;
         int family, s;
         char host;

         if (getifaddrs(&ifaddr) == -1) {
               perror("getifaddrs");
               exit(EXIT_FAILURE);
         }


       int
       main(int argc, char *argv[])
       {
         struct ifaddrs *ifaddr, *ifa;
         int family, s;
         char host;

         if (getifaddrs(&ifaddr) == -1) {
               perror("getifaddrs");
               exit(EXIT_FAILURE);
         }

         /* Walk through linked list, maintaining head pointer so we
            can free list later */

         for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
               if (ifa->ifa_addr == NULL)
                   continue;

               family = ifa->ifa_addr->sa_family;

               /* Display interface name and family (including symbolic
                  form of the latter for the common families) */

               printf("%saddress family: %d%s\n",
                     ifa->ifa_name, family,
                     (family == AF_PACKET) ? " (AF_PACKET)" :
                     (family == AF_INET) ?   " (AF_INET)" :
                     (family == AF_INET6) ?" (AF_INET6)" : "");

               /* For an AF_INET* interface address, display the address */

               if (family == AF_INET || family == AF_INET6) {
                   s = getnameinfo(ifa->ifa_addr,
                           (family == AF_INET) ? sizeof(struct sockaddr_in) :
                                                 sizeof(struct sockaddr_in6),
                           host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
                   if (s != 0) {
                     printf("getnameinfo() failed: %s\n", gai_strerror(s));
                     exit(EXIT_FAILURE);
                   }
                   printf("\taddress: <%s>\n", host);
               }
         }
页: [1] 2
查看完整版本: linux下socket编程求助