Support multiple local IPs on an interface.
[darkstat] / localip.c
1 /* darkstat 3
2 * copyright (c) 2001-2011 Emil Mikulic.
3 *
4 * localip.c: determine local IPs of an interface
5 *
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
8 */
9
10 #include "addr.h"
11 #include "config.h" /* for HAVE_IFADDRS_H */
12 #include "conv.h"
13 #include "err.h"
14 #include "localip.h"
15 #include "bsd.h" /* for strlcpy */
16
17 #include <sys/socket.h>
18 #include <net/if.h>
19 #include <assert.h>
20 #include <errno.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24 #include <unistd.h>
25
26 #ifdef HAVE_IFADDRS_H
27 # include <ifaddrs.h>
28 #else
29 # ifdef HAVE_SYS_SOCKIO_H
30 # include <sys/sockio.h> /* for SIOCGIFADDR, especially on Solaris */
31 # endif
32 # include <sys/ioctl.h>
33 #endif
34
35 struct local_ips *local_ips;
36
37 struct local_ips *localip_make(void) {
38 struct local_ips *ips = xmalloc(sizeof(*ips));
39
40 ips->is_valid = 0;
41 ips->last_update = 0;
42 ips->num_addrs = 0;
43 ips->addrs = NULL;
44 return ips;
45 }
46
47 void localip_free(struct local_ips *ips) {
48 if (ips->addrs != NULL)
49 free(ips->addrs);
50 free(ips);
51 }
52
53 static void add_ip(const char *iface, struct local_ips *ips,
54 int *index, struct addr *a) {
55 if (ips->num_addrs <= *index) {
56 /* Grow. */
57 ips->addrs = xrealloc(ips->addrs, sizeof(*(ips->addrs)) * (*index + 1));
58 ips->num_addrs++;
59 assert(ips->num_addrs > *index);
60 verbosef("interface '%s' gained new address %s", iface, addr_to_str(a));
61 } else {
62 /* Warn about changed address. */
63 if (!addr_equal(ips->addrs + *index, a)) {
64 static char before[INET6_ADDRSTRLEN];
65 strncpy(before, addr_to_str(ips->addrs + *index), INET6_ADDRSTRLEN);
66 verbosef("interface '%s' address %d/%d changed from %s to %s",
67 iface, *index+1, ips->num_addrs, before, addr_to_str(a));
68 }
69 }
70 ips->addrs[*index] = *a;
71 (*index)++;
72 }
73
74 /* Returns 0 on failure. */
75 void localip_update(const char *iface, struct local_ips *ips) {
76 struct addr a;
77 int new_addrs = 0;
78
79 if (iface == NULL) {
80 /* reading from capfile */
81 ips->is_valid = 0;
82 return;
83 }
84
85 #ifdef HAVE_IFADDRS_H
86 {
87 struct ifaddrs *ifas, *ifa;
88
89 if (getifaddrs(&ifas) < 0)
90 err(1, "getifaddrs() failed");
91
92 for (ifa=ifas; ifa; ifa=ifa->ifa_next) {
93 if (strncmp(ifa->ifa_name, iface, IFNAMSIZ))
94 continue; /* Wrong interface. */
95
96 if (!ifa->ifa_addr)
97 continue; /* This can be NULL, e.g. for ppp0. */
98
99 if (ifa->ifa_addr->sa_family == AF_INET) {
100 a.family = IPv4;
101 a.ip.v4 = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
102 add_ip(iface, ips, &new_addrs, &a);
103 } else if (ifa->ifa_addr->sa_family == AF_INET6) {
104 struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)ifa->ifa_addr;
105 # if 0
106 if ( IN6_IS_ADDR_LINKLOCAL(&(sa6->sin6_addr))
107 || IN6_IS_ADDR_SITELOCAL(&(sa6->sin6_addr)) )
108 continue;
109 # endif
110 a.family = IPv6;
111 memcpy(&(a.ip.v6), &sa6->sin6_addr, sizeof(a.ip.v6));
112 add_ip(iface, ips, &new_addrs, &a);
113 # ifdef AF_PACKET
114 } else if (ifa->ifa_addr->sa_family == AF_PACKET) {
115 /* ignore */
116 # endif
117 } else
118 verbosef("unknown sa_family=%d on interface '%s'",
119 (int)ifa->ifa_addr->sa_family, iface);
120 }
121 freeifaddrs(ifas);
122 }
123 #else /* don't HAVE_IFADDRS_H */
124 {
125 int tmp = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
126 struct ifreq ifr;
127 struct sockaddr sa;
128
129 strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
130 ifr.ifr_addr.sa_family = AF_INET;
131 if (ioctl(tmp, SIOCGIFADDR, &ifr) != -1) {
132 sa = ifr.ifr_addr;
133 a.family = IPv4;
134 a.ip.v4 = ((struct sockaddr_in*)(&ifr.ifr_addr))->sin_addr.s_addr;
135 add_ip(iface, ips, &new_addrs, &a);
136 }
137 close(tmp);
138 }
139 #endif
140 if (new_addrs == 0) {
141 if (ips->is_valid)
142 verbosef("interface '%s' no longer has any addresses", iface);
143 ips->is_valid = 0;
144 } else {
145 if (!ips->is_valid)
146 verbosef("interface '%s' now has addresses", iface);
147 ips->is_valid = 1;
148 if (ips->num_addrs != new_addrs)
149 verbosef("interface '%s' number of addresses decreased from %d to %d",
150 iface, ips->num_addrs, new_addrs);
151 ips->num_addrs = new_addrs;
152 }
153 ips->last_update = time(NULL);
154 }
155
156 int is_localip(const struct addr * const a,
157 const struct local_ips * const ips) {
158 int i;
159
160 for (i=0; i<ips->num_addrs; i++) {
161 if (addr_equal(a, ips->addrs+i))
162 return 1;
163 }
164 return 0;
165 }
166
167 /* vim:set ts=3 sw=3 tw=80 et: */