获取本机所有接口和所有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,
这是代码:
- #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;
- }