Linux中国论坛's Archiver

lydr 发表于 2006-12-15 16:41

获取本机所有接口和所有IP地址的函数

最近一直在忙一个程序,为了保护自己机器上所有IP,便要获取它们。
虽然IPv6中根本没有ARP,但是我编写代码的原则一直希望能够独力于协议版本,所以不管怎么说一定要能获取IPv6地址了。
要说只要IPv4,就没必要写这篇文章了。
我首先考虑的就是使用NETLINK访问,可我不想现在用,因为我想过几天程序写完了,把哪些陈旧的ioctl彻底用NETLINK再重新写一遍的。
想起书上(UNPv3)说BSD中有getifaddrs函数,我就man 了一下,结果我的机器上也有,(LINUX啦)。看了手册页和ifaddrs.h
文件又迷惑了,这样的函数能支持IPv6么,因为struct ifaddrs结构里的成员只有struct sockaddr的指针,装个V4还可以,V6地址
行么?
往上搜了一下,结果在外国的一个邮件列表中照到了一个煎蛋(简单)代码。发现直接可以把那个struct sockaddr *ifa_addr成员根据
它之中的sa_family成员的值直接转换成相应的类型。感觉好多了。获取是没问题了,不果感觉返回来那么多地址,可lo接口地址对我根本
没用啊,想象就是在实际中的代码也很少去关心还回接口啊,变想办法过滤下。再看那个struct ifaddrs,明显是链表节点的类型(太自以为是了)
便自作聪明的以为是个真正的链表,只要把它给修正下就可以了。
于是忙活了半天,写了个小函数,这是片段:(写的有点变态,也没保证)
while (ifa) {
tmp = ifa;
tmp->ifa_next = NULL;
if (ifa->ifa_addr->sa_family != family){
if (family != AF_UNSPEC && (ifa->ifa_addr == NULL ||((ifa->ifa_flags & IFF_UP) == 0) || !strcmp(ifa->ifa_name, "lo"))) {
freeifaddrs(tmp);
if (flag) {
ifaddrs = ifa->ifa_next; /** never get here twice **/
flag =0;
}
}
ifa = ifa->ifa_next;
}
}
用地址类型做为参数,获取机器上指定类型的所有除还回地址以外所有活动的接口IP,
然后发现总是出错。因为第一个返回的是V4的还回地址,我要给它释放掉。可运行时总是提示:
lo: 127.0.0.1
eth0: 10.0.119.163
lo: ::1
eth0: fe80::203:dff:fe2f:9149
*** glibc detected *** d: free(): invalid pointer: 0x0804a4d4 ***
======= Backtrace: =========
/lib/libc.so.6[0xb7eda911]
/lib/libc.so.6(__libc_free+0x84)[0xb7edbf84]
/lib/libc.so.6(freeifaddrs+0x1d)[0xb7f512dd]
d[0x8048989]
d[0x80486a5]
/lib/libc.so.6(__libc_start_main+0xdc)[0xb7e8c87c]
d[0x8048491]
======= Memory map: ========
08048000-08049000 r-xp 00000000 03:07 48637 /home/souldump/bin/d
08049000-0804a000 rw-p 00000000 03:07 48637 /home/souldump/bin/d
0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]
b7d00000-b7d21000 rw-p b7d00000 00:00 0
b7d21000-b7e00000 ---p b7d21000 00:00 0
b7e76000-b7e77000 rw-p b7e76000 00:00 0
b7e77000-b7f90000 r-xp 00000000 03:05 16184 /lib/libc-2.4.so
b7f90000-b7f92000 r--p 00118000 03:05 16184 /lib/libc-2.4.so
b7f92000-b7f94000 rw-p 0011a000 03:05 16184 /lib/libc-2.4.so
b7f94000-b7f98000 rw-p b7f94000 00:00 0
b7fab000-b7fb5000 r-xp 00000000 03:05 20108 /lib/libgcc_s.so.1
b7fb5000-b7fb6000 rw-p 00009000 03:05 20108 /lib/libgcc_s.so.1
b7fb6000-b7fb7000 rw-p b7fb6000 00:00 0
b7fb7000-b7fd1000 r-xp 00000000 03:05 16177 /lib/ld-2.4.so
b7fd1000-b7fd3000 rw-p 00019000 03:05 16177 /lib/ld-2.4.so
bfb2b000-bfb41000 rw-p bfb2b000 00:00 0 [stack]
ffffe000-fffff000 ---p 00000000 00:00 0 [vdso]
已放弃

坏了,这说明不是真正的链表,指针非法。
于是激起了我翻看GLIBC代码的想法,我直到代码里有所有问题的答案。
赶紧翻开以前装LFS时用的glbc-2.3.6源代码。

搜索了下在glibc-2.3.6/glibc-2.3.6/sysdeps/unix/sysv/linux/ifaddrs.c
里找到了getifaddrs的实现,第一眼就郁闷了,还是用NETLINK实现的。早直到我就不费这劲,自己写了。
他定义了这样一个结构,保证容纳每个接口的空间。这就是为什么能转换IPv6的原因了。
struct ifaddrs_storage
{
struct ifaddrs ifa;
union
{
/* Save space for the biggest of the four used sockaddr types and
avoid a lot of casts. */
struct sockaddr sa;
struct sockaddr_ll sl;
struct sockaddr_in s4;
struct sockaddr_in6 s6;
} addr, netmask, broadaddr;
char name[IF_NAMESIZE + 1];
};
大概流程是这样:如果支持NETLINK,发送请求,不然调用fallback_getifaddrs(以前的getifaddrs实现)
函数实现不过只支持IPv4(一个一个接口用ioctl了)。(记得有个邮件列表里有人问过这个函数什么从版本的glibc
开始支持IPv6,不过那个日本人好象没说,只说看下一版本的了。),然后第一遍快速遍历处理返回的数据,
确定接口和地址数量,决定分配空间。然后一次再次遍历数据,初始化每个struct ifaddrs结构的ifa_next
指针(用map_newlink),根据每个rtattr结构类型把对应的项的值memcpy过去。
不子细说了,有兴趣的自己去看,其实只要看freeifaddrs函数就够了,只有free(ifa);一句。
终于弄明白了,既然还是用NETLINK,这次还不好办,有了上次操作路由表的经验和glibc的代码,于是把那个
库函数给改了,封装成一个新函数,留着以后用,用参数AF_INET, AF_INET6, AF_UNSPEC,


这是代码:
[code]#include<stdio.h>
#include<stdlib.h>
#include <stdbool.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<net/if.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<linux/netlink.h>
#include<linux/rtnetlink.h>
#include <assert.h>
#include<errno.h>
#include<ifaddrs.h>
#include<netpacket/packet.h>

void print_ip(struct ifaddrs *ifaddrs);

/** NOTE! caller must call freeifaddrs() after the use the pointer **/
int get_local_ip(struct ifaddrs **ifap, int family);

#define IS_UNSPEC(family) (family == AF_UNSPEC)
#define NO_ADDRS(ifa) (ifa->ifa_addr == NULL)
#define FAMILY_OK(ifa) (ifa->ifa_addr->sa_family == AF_INET ||\
                                ifa->ifa_addr->sa_family == AF_INET6)
#define IN_FAMILY(ifa, family) (ifa->ifa_addr->sa_family == family)
#define TEST_FLAG(ifa, flag) (ifa->ifa_flags | flag)
#define IS_LOOPBACK(ifa) (strncmp(ifa->ifa_name,"lo", 2) == 0)
#define move_ptr(ifa, tmp) do{  ifa->ifa_next = tmp->ifa_next;\
                                ifa->ifa_name = tmp->ifa_name;\
                                ifa->ifa_flags = tmp->ifa_flags;\
                                ifa->ifa_addr = tmp->ifa_addr;\
                                ifa->ifa_netmask = tmp->ifa_netmask;\
                                if (TEST_FLAG(ifa, IFF_BROADCAST)) \
                                        ifa->ifa_broadaddr = tmp->ifa_broadaddr;\
                                else if (TEST_FLAG(ifa, IFF_POINTOPOINT))\
                                        ifa->ifa_dstaddr = tmp->ifa_dstaddr;\
                                } while (0)

int main ()
{
        struct ifaddrs *ifa, *ifaddrs;
        struct ifaddrs *ifb, *ifc;


        printf("AF_INET\n");
        get_local_ip(&ifa, AF_INET);
        print_ip(ifa);
        printf("AF_INET6\n");
        get_local_ip(&ifb, AF_INET6);
        print_ip(ifb);
        printf("AF_UNSPEC\n");
        get_local_ip(&ifc, AF_UNSPEC);
        print_ip(ifc);


}

void
print_ip(struct ifaddrs *ifaddrs)
{
        struct ifaddrs *ifa;
        struct sockaddr_in *sin;
        struct sockaddr_in6 *sin6;
        char buf[INET6_ADDRSTRLEN];


        for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next)
        {
                if (ifa->ifa_addr == NULL) continue;
                if ((ifa->ifa_flags & IFF_UP) == 0) continue;
      
                if (ifa->ifa_addr->sa_family == AF_INET)
                {
                        sin = (struct sockaddr_in *)(ifa->ifa_addr);
                        if (inet_ntop(ifa->ifa_addr->sa_family, (void *)&(sin->sin_addr), buf, sizeof(buf)) == NULL)
                        {
                                printf("%s: inet_ntop failed!\n", ifa->ifa_name);
                        }
                        else
                        {
                        printf("%s: %s\n", ifa->ifa_name, buf);
                        }
                }
                else if (ifa->ifa_addr->sa_family == AF_INET6)
                {
                        sin6 = (struct sockaddr_in6 *)(ifa->ifa_addr);
                        if (inet_ntop(ifa->ifa_addr->sa_family, (void *)&(sin6->sin6_addr), buf, sizeof(buf)) == NULL)
                        {
                                printf("%s: inet_ntop failed!\n", ifa->ifa_name);
                        }
                        else
                        {
                                printf("%s: %s\n", ifa->ifa_name, buf);
                        }
                }
        }
}

int get_local_ip(struct ifaddrs **ifap, int family)
{
        int n;
        bool change = 0;
        char *name;
        struct ifaddrs *ifa;
        struct ifaddrs *tmp;
        struct sockaddr_in *sin;
        struct sockaddr_in6 *sin6;
        char buf[INET6_ADDRSTRLEN];

        n = getifaddrs(&ifa);
        if (n != 0)
                return -1;
      
        *ifap = ifa;
        for (ifa; (tmp = ifa) != NULL; ifa = ifa->ifa_next) {

                while (tmp && (IS_LOOPBACK(tmp) ||
                                 NO_ADDRS(tmp) || (!TEST_FLAG(tmp, IFF_UP))||
                                        (FAMILY_OK(tmp) && (!IN_FAMILY(tmp, family)) && (!IS_UNSPEC(family))) ||
                                                !FAMILY_OK(tmp))) {
                                change = true;
                                tmp = tmp->ifa_next;
                }
                if (change) {
                        if (tmp)
                                move_ptr(ifa, tmp);
                        else
                                memset(ifa, 0, sizeof (struct ifaddrs));
                                /** An alternative way is use these instead:
                                ifa->ifa_next = NULL;
                                ifa->ifa_name = NULL;
                                ifa->ifa_addr = NULL;
                                ifa->ifa_netmask = NULL;
                                if (TEST_FLAG(ifa, IFF_BROADCAST))
                                        ifa->ifa_broadaddr = NULL;
                                else if (TEST_FLAG(ifa, IFF_POINTOPOINT))
                                        ifa->ifa_dstaddr = NULL;
                                **/

                        change = false;
                }
      
        }

        /** I think even *ifap now is NULL after check, we should not free the space either.
            Since user who called this routine will call freeifaddrs() too, The space will be free safely . **/
        return 0;
}[/code]

hiwoody 发表于 2007-2-28 15:57

代码相当规范 网络编程不算很难

页: [1]

Powered by Discuz! Archiver 6.1.0  © 2001-2007 Comsenz Inc.