]> git.meshlink.io Git - catta/blob - src/iface-windows.c
9a57f7c2b7f8eda2268e7b740abb0fff17e3e841
[catta] / src / iface-windows.c
1 /***
2   This file is part of catta.
3
4   catta is free software; you can redistribute it and/or modify it
5   under the terms of the GNU Lesser General Public License as
6   published by the Free Software Foundation; either version 2.1 of the
7   License, or (at your option) any later version.
8
9   catta is distributed in the hope that it will be useful, but WITHOUT
10   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
12   Public License for more details.
13
14   You should have received a copy of the GNU Lesser General Public
15   License along with catta; if not, write to the Free Software
16   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17   USA.
18 ***/
19
20 #include "iface.h"
21 #include "iface-windows.h"
22
23 #include <stdlib.h> // wcstombs
24 #include <catta/malloc.h>
25 #include <catta/log.h>
26 #include <iphlpapi.h>
27 #include <assert.h>
28 #include "hashmap.h"
29 #include "util.h"   // catta_format_mac_address
30 #include "fdutil.h" // catta_set_nonblock
31
32
33 typedef enum {
34     INTERFACE_CHANGE_EVENT,
35     ADDRESS_CHANGE_EVENT
36 } ChangeEventType;
37
38 struct ChangeEvent {
39     CATTA_LLIST_FIELDS(ChangeEvent, event);
40     ChangeEventType type;
41     MIB_NOTIFICATION_TYPE notification_type;
42     union {
43         MIB_IPINTERFACE_ROW iface;
44         MIB_UNICASTIPADDRESS_ROW addr;
45     } data;
46 };
47
48
49 // helper: determine the global_scope flag for an address
50 static void set_global_scope_flag(CattaInterfaceAddress *ifaddr, const CattaAddress *addr)
51 {
52     if(addr->proto == CATTA_PROTO_INET6) {
53         const struct in6_addr *ia = (struct in6_addr *)addr->data.ipv6.address;
54         ifaddr->global_scope = !(IN6_IS_ADDR_LINKLOCAL(ia) || IN6_IS_ADDR_MULTICAST(ia));
55     } else {
56         ifaddr->global_scope = 1;
57     }
58 }
59
60 // integrate the information from an IP_ADAPTER_UNICAST_ADDRESS structure for
61 // given CattaHwInterface into the CattaInterfaceMonitor
62 static void ip_adapter_unicast_address(CattaInterfaceMonitor *m,
63                                        CattaHwInterface *hw,
64                                        IP_ADAPTER_UNICAST_ADDRESS *a)
65 {
66     CattaInterface *iface;
67     CattaAddress addr;
68     CattaInterfaceAddress *ifaddr;
69     struct sockaddr *sa = a->Address.lpSockaddr;
70
71     // skip transient addresses; to quote MSDN: "The IP address is a cluster
72     // address and should not be used by most applications."
73     // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366066(v=vs.85).aspx
74     if(a->Flags & IP_ADAPTER_ADDRESS_TRANSIENT)
75         return;
76
77     // fill addr struct for address lookup
78     switch(sa->sa_family) {
79     case AF_INET:
80         memcpy(addr.data.data, &((struct sockaddr_in *)sa)->sin_addr, sizeof(struct in_addr));
81         break;
82     case AF_INET6:
83         memcpy(addr.data.data, &((struct sockaddr_in6 *)sa)->sin6_addr, sizeof(struct in6_addr));
84         break;
85     default:
86         catta_log_debug("unexpected address family on interface %d: %u", hw->index, sa->sa_family);
87         return;
88     }
89     addr.proto = catta_af_to_proto(sa->sa_family);
90
91     // get protocol-specific CattaInterface object
92     if(!(iface = catta_interface_monitor_get_interface(m, hw->index, addr.proto))) {
93         catta_log_error("CattaInterface (index %d, proto %d) not found", hw->index, addr.proto);
94         return;
95     }
96
97     // find or allocate a CattaInterfaceAddress struct for this address
98     if(!(ifaddr = catta_interface_monitor_get_address(m, iface, &addr))) {
99         if(!(ifaddr = catta_interface_address_new(m, iface, &addr, a->OnLinkPrefixLength))) {
100             catta_log_error("out of memory in ip_adapter_unicast_address");
101             return;
102         }
103     }
104
105     set_global_scope_flag(ifaddr, &addr);
106 }
107
108 // integrate the information from an IP_ADAPTER_ADDRESSES structure
109 // as returned by GetAdaptersAddresses into the CattaInterfaceMonitor
110 static void ip_adapter(CattaInterfaceMonitor *m, IP_ADAPTER_ADDRESSES *p)
111 {
112     IP_ADAPTER_UNICAST_ADDRESS *a;
113     CattaIfIndex idx;
114     CattaHwInterface *hw;
115     size_t n;
116
117     // we want an index specific to the hardware interface, but Windows
118     // has one for IPv4 and one for IPv6. it seems like these are always the
119     // same unless one of the protocols is not available. let's have a bunch of
120     // checks...
121     if(!p->IfIndex && !p->Ipv6IfIndex) {
122         return; // no usable protocols
123     } else if(!p->IfIndex) {
124         idx = p->Ipv6IfIndex;   // IPv6 but no IPv4 (huh!)
125     } else if(!p->Ipv6IfIndex) {
126         idx = p->IfIndex;       // IPv4 but no IPv6
127     } else if(p->IfIndex == p->Ipv6IfIndex) {
128         idx = p->IfIndex;       // same index for both protocols
129     } else {
130         // both indexes valid but not equal
131         catta_log_error("unsupported interface: %ls (IfIndex and Ipv6IfIndex differ: %u/%u)",
132             p->FriendlyName, (unsigned int)p->IfIndex, (unsigned int)p->Ipv6IfIndex);
133         return;
134     }
135
136     // find the CattaHwInterface by index or allocate a new one
137     if((hw = catta_interface_monitor_get_hw_interface(m, idx)) == NULL) {
138         if((hw = catta_hw_interface_new(m, idx)) == NULL) {
139             catta_log_error("catta_hw_interface_new failed in ip_adapter_address");
140             return;
141         }
142     }
143
144     // fill the CattaHwInterface struct with data
145     // notice: this code is essentially duplicated in update_hw_interface()
146     hw->flags_ok =
147         (p->OperStatus == IfOperStatusUp) &&
148         !(p->IfType == IF_TYPE_SOFTWARE_LOOPBACK) &&
149         !(p->Flags & IP_ADAPTER_NO_MULTICAST) &&
150         (m->server->config.allow_point_to_point || !(p->IfType == IF_TYPE_PPP));
151             // XXX what about IF_TYPE_TUNNEL?
152
153     n = wcstombs(NULL, p->FriendlyName, 0) + 1;
154     catta_free(hw->name);
155     hw->name = catta_new(char, n);
156     wcstombs(hw->name, p->FriendlyName, n);
157
158     hw->mtu = p->Mtu;
159
160     hw->mac_address_size = p->PhysicalAddressLength;
161     if(hw->mac_address_size > CATTA_MAC_ADDRESS_MAX)
162         hw->mac_address_size = CATTA_MAC_ADDRESS_MAX;
163     memcpy(hw->mac_address, p->PhysicalAddress, hw->mac_address_size);
164
165     // process addresses
166     // XXX remove addresses that are no longer in the list
167     for(a=p->FirstUnicastAddress; a; a=a->Next)
168         ip_adapter_unicast_address(m, hw, a);
169 }
170
171
172 // place the event into the queue to be handled (by the main thread)
173 // and wake the event handler if necessary
174 static void queue_event(CattaInterfaceMonitor *m, ChangeEvent *ev)
175 {
176     char c = 'X';
177
178     if(!ev)
179         return;
180
181     if(!pthread_mutex_lock(&m->osdep.mutex)) {
182         // queue the event
183         CATTA_LLIST_APPEND(ChangeEvent, event, m->osdep.events, ev);
184
185         // wake the handler
186         writepipe(m->osdep.pipefd[1], &c, sizeof(c));
187
188         pthread_mutex_unlock(&m->osdep.mutex);
189     } else {
190         catta_log_debug(__FILE__": queue_event: could not lock mutex");
191         catta_free(ev);
192     }
193 }
194
195 // copy the given data row into an appropriate change event struct
196 static ChangeEvent *new_event(ChangeEventType type, MIB_NOTIFICATION_TYPE ntype, void *row, size_t n)
197 {
198     ChangeEvent *ev;
199
200     if(!row)
201         return NULL;
202
203     if(!(ev = catta_new(ChangeEvent, 1)))
204         return NULL;
205
206     ev->type = type;
207     ev->notification_type = ntype;
208     memcpy(&ev->data, row, n);
209
210     return ev;
211 }
212
213 static void WINAPI icn_callback(void *m, MIB_IPINTERFACE_ROW *row, MIB_NOTIFICATION_TYPE type)
214 {
215     queue_event(m, new_event(INTERFACE_CHANGE_EVENT, type, row, sizeof(*row)));
216 }
217
218 static void WINAPI acn_callback(void *m, MIB_UNICASTIPADDRESS_ROW *row, MIB_NOTIFICATION_TYPE type)
219 {
220     queue_event(m, new_event(ADDRESS_CHANGE_EVENT, type, row, sizeof(*row)));
221 }
222
223 static void update_hw_interface(CattaHwInterface *hw)
224 {
225     MIB_IF_ROW2 row;
226     DWORD r;
227     size_t n;
228     int multicast;  // synthetic flag
229
230     row.InterfaceLuid.Value = 0;
231     row.InterfaceIndex = hw->index;
232     if((r = GetIfEntry2(&row)) != NO_ERROR) {
233         catta_log_error("GetIfEntry2 failed for iface %d (error %u)", hw->index, (unsigned int)r);
234         return;
235     }
236
237     // fill the CattaHwInterface struct with data
238     // notice: this code is essentially duplicated from ip_adapter()
239     // notice: not sure where to find the IP_ADAPTER_NO_MULTICAST flag from an
240     //         MIB_IF_ROW2 struct, so try to deduce it otherwise
241     //         cf. http://msdn.microsoft.com/en-us/windows/desktop/ff568739(v=vs.100).aspx
242     multicast = row.AccessType == NET_IF_ACCESS_BROADCAST ||
243                 row.AccessType == NET_IF_ACCESS_POINT_TO_POINT;
244     hw->flags_ok =
245         (row.OperStatus == IfOperStatusUp) &&
246         !(row.Type == IF_TYPE_SOFTWARE_LOOPBACK) &&
247         multicast &&
248         (hw->monitor->server->config.allow_point_to_point || !(row.Type == IF_TYPE_PPP));
249             // XXX what about IF_TYPE_TUNNEL?
250
251     n = wcstombs(NULL, row.Alias, 0) + 1;
252     catta_free(hw->name);
253     hw->name = catta_new(char, n);
254     wcstombs(hw->name, row.Alias, n);
255
256     hw->mtu = row.Mtu;
257
258     hw->mac_address_size = row.PhysicalAddressLength;
259     if(hw->mac_address_size > CATTA_MAC_ADDRESS_MAX)
260         hw->mac_address_size = CATTA_MAC_ADDRESS_MAX;
261     memcpy(hw->mac_address, row.PhysicalAddress, hw->mac_address_size);
262
263     catta_hw_interface_check_relevant(hw);
264     catta_hw_interface_update_rrs(hw, 0);
265 }
266
267 static void handle_iface_event(CattaInterfaceMonitor *m, MIB_IPINTERFACE_ROW *row, MIB_NOTIFICATION_TYPE type)
268 {
269     CattaIfIndex idx = row->InterfaceIndex;
270     CattaProtocol proto = catta_af_to_proto(row->Family);
271     const char *protostr = catta_proto_to_string(proto);
272     CattaInterface *iface;
273     CattaHwInterface *hw;
274
275     // see if we know this interface
276     iface = catta_interface_monitor_get_interface(m, idx, proto);
277     hw = iface ? iface->hardware : catta_interface_monitor_get_hw_interface(m, idx);
278
279     // print debug messages for some unexpected cases
280     if(type==MibParameterNotification && !iface)
281         catta_log_debug("ParameterNotification received for unknown interface %d (%s)", idx, protostr);
282     if(type==MibDeleteInstance && !iface)
283         catta_log_debug("DeleteInstance received for unknown interface %d (%s)", idx, protostr);
284     if(type==MibAddInstance && iface)
285         catta_log_debug("AddInstance received for existing interface %d (%s)", idx, protostr);
286     if(iface && !hw)
287         catta_log_debug("missing CattaHwInterface for interface %d (%s)", idx, protostr);
288
289     switch(type) {
290     case MibParameterNotification:
291     case MibAddInstance:
292         // create the physical interface if it is missing
293         if(!hw) {
294             if((hw = catta_hw_interface_new(m, idx)) == NULL) {
295                 catta_log_error("catta_hw_interface_new failed in handle_iface_event");
296                 return;
297             }
298         }
299
300         // create the protocol-specific interface if it is missing
301         if(!iface) {
302             if((iface = catta_interface_new(m, hw, proto)) == NULL) {
303                 catta_log_error("catta_interface_new failed in handle_iface_event");
304                 return;
305             }
306         }
307
308         assert(iface != NULL);
309         assert(hw != NULL);
310         assert(iface->hardware == hw);
311
312         update_hw_interface(hw);
313         break;
314     case MibDeleteInstance:
315         if(iface)
316             catta_interface_free(iface, 0);
317
318         // free the hardware interface when there are no more protocol-specific interfaces
319         if(hw && !hw->interfaces)
320             catta_hw_interface_free(hw, 0);
321         break;
322     default:
323         catta_log_debug("unexpected type (%d) of interface change notification received", type);
324     }
325 }
326
327 static void handle_addr_event(CattaInterfaceMonitor *m, MIB_UNICASTIPADDRESS_ROW *row, MIB_NOTIFICATION_TYPE type)
328 {
329     CattaIfIndex idx = row->InterfaceIndex;
330     CattaInterfaceAddress *ifaddr;
331     CattaInterface *iface;
332     CattaAddress addr;
333     const char *protostr;
334
335     // fill addr struct for address lookup
336     switch(row->Address.si_family) {
337     case AF_INET:
338         memcpy(addr.data.data, &row->Address.Ipv4.sin_addr, sizeof(struct in_addr));
339         break;
340     case AF_INET6:
341         memcpy(addr.data.data, &row->Address.Ipv6.sin6_addr, sizeof(struct in6_addr));
342         break;
343     default:
344         catta_log_debug("unexpected address family on interface %d: %u", idx, row->Address.si_family);
345         return;
346     }
347     addr.proto = catta_af_to_proto(row->Address.si_family);
348     protostr = catta_proto_to_string(addr.proto);
349
350     // see if we know this address/interface
351     iface = catta_interface_monitor_get_interface(m, idx, addr.proto);
352     ifaddr = iface ? catta_interface_monitor_get_address(m, iface, &addr) : NULL;
353
354     // print debug messages for some unexpected cases
355     if(type==MibParameterNotification && !ifaddr)
356         catta_log_debug("ParameterNotification received for unknown address on interface %d (%s)", idx, protostr);
357     if(type==MibDeleteInstance && !ifaddr)
358         catta_log_debug("DeleteInstance received for unknown address on interface %d (%s)", idx, protostr);
359     if(type==MibAddInstance && ifaddr)
360         catta_log_debug("AddInstance received for existing address on interface %d (%s)", idx, protostr);
361     if(ifaddr && !iface)
362         catta_log_debug("missing CattaInterface for address on interface %d (%s)", idx, protostr);
363
364     switch(type) {
365     case MibParameterNotification:
366     case MibAddInstance:
367         // fetch the full event data
368         if(GetUnicastIpAddressEntry(row) != NO_ERROR) {
369             catta_log_error("GetUnicastIpAddressEntry failed in handle_addr_event");
370             return;
371         }
372
373         // skip addresses that are not suitable as source addresses
374         if(row->SkipAsSource)
375             return;
376
377         // create the interface if it is missing
378         if(!iface) {
379             CattaHwInterface *hw;
380
381             if((hw = catta_interface_monitor_get_hw_interface(m, idx)) == NULL) {
382                 catta_log_error("interface %d not found in handle_addr_event", idx);
383                 return;
384             }
385
386             if((iface = catta_interface_new(m, hw, addr.proto)) == NULL) {
387                 catta_log_error("catta_interface_new failed in handle_addr_event");
388                 return;
389             }
390         }
391         assert(iface != NULL);
392
393         // create the interface-associated address if it is missing
394         if(!ifaddr) {
395             unsigned prefixlen = row->OnLinkPrefixLength;
396
397             if((ifaddr = catta_interface_address_new(m, iface, &addr, prefixlen)) == NULL) {
398                 catta_log_error("catta_interface_address_new failed in handle_addr_event");
399                 return;
400             }
401         }
402         assert(ifaddr != NULL);
403
404         set_global_scope_flag(ifaddr, &addr);
405         break;
406     case MibDeleteInstance:
407         if(ifaddr)
408             catta_interface_address_free(ifaddr);
409         break;
410     default:
411         catta_log_debug("unexpected type (%d) of address change notification received", type);
412     }
413
414     if(iface) {
415         catta_interface_check_relevant(iface);
416         catta_interface_update_rrs(iface, 0);
417     }
418 }
419
420 static void handle_events(CattaInterfaceMonitor *m)
421 {
422     char buf[16];
423     ChangeEvent *ev;
424
425     if(!pthread_mutex_lock(&m->osdep.mutex)) {
426         // clear the pipe
427         while(readpipe(m->osdep.pipefd[0], buf, sizeof(buf)) == sizeof(buf)) {}
428
429         while((ev = m->osdep.events) != NULL) {
430             CATTA_LLIST_REMOVE(ChangeEvent, event, m->osdep.events, ev);
431
432             // dispatch to the appropriate handler
433             switch(ev->type) {
434             case INTERFACE_CHANGE_EVENT:
435                 handle_iface_event(m, &ev->data.iface, ev->notification_type);
436                 break;
437             case ADDRESS_CHANGE_EVENT:
438                 handle_addr_event(m, &ev->data.addr, ev->notification_type);
439                 break;
440             default:
441                 catta_log_debug("unhandled change event type in handle_events");
442             }
443
444             catta_free(ev);
445         }
446
447         pthread_mutex_unlock(&m->osdep.mutex);
448     }
449 }
450
451 static void pipe_callback(CattaWatch *w, int fd, CattaWatchEvent event, void *m)
452 {
453     // silence "unused parameter" warnings
454     (void)w;
455     (void)fd;
456     (void)event;
457
458     handle_events(m);
459 }
460
461
462 int catta_interface_monitor_init_osdep(CattaInterfaceMonitor *m)
463 {
464     DWORD r;
465
466     pthread_mutex_init(&m->osdep.mutex, NULL);
467
468     CATTA_LLIST_HEAD_INIT(ChangeEvent, m->osdep.events);
469
470     if(pipe(m->osdep.pipefd) < 0) {
471         catta_log_error("pipe() in catta_interface_monitor_init_osdep failed");
472         return -1;
473     }
474     catta_set_nonblock(m->osdep.pipefd[0]);
475     catta_set_nonblock(m->osdep.pipefd[1]);
476
477     m->osdep.icnhandle = NULL;
478     m->osdep.acnhandle = NULL;
479
480     // register handler for change events
481     m->osdep.watch = m->server->poll_api->watch_new(m->server->poll_api,
482                                                     m->osdep.pipefd[0],
483                                                     CATTA_WATCH_IN,
484                                                     pipe_callback,
485                                                     m);
486     if(!m->osdep.watch) {
487         catta_log_error(__FILE__": Failed to create watch.");
488         return -1;
489     }
490
491     // request async notification on interface changes
492     r = NotifyIpInterfaceChange(AF_UNSPEC,
493                                 // icn_callback needs to be WINAPI but
494                                 // MingW up to 3.1.0 erroneously defines
495                                 // PIPINTERFACE_CHANGE_CALLBACK without it
496                                 (PIPINTERFACE_CHANGE_CALLBACK)icn_callback,
497                                 m, FALSE, &m->osdep.icnhandle);
498     if(r != NO_ERROR)
499         catta_log_error("NotifyIpInterfaceChange failed: %u", (unsigned int)r);
500
501     // request async notification on address changes
502     r = NotifyUnicastIpAddressChange(AF_UNSPEC, acn_callback, m, FALSE,
503                                      &m->osdep.acnhandle);
504     if(r != NO_ERROR)
505         catta_log_error("NotifyUnicastIpAddressChange failed: %u", (unsigned int)r);
506
507     return 0;
508 }
509
510 void catta_interface_monitor_free_osdep(CattaInterfaceMonitor *m)
511 {
512     ChangeEvent *ev;
513
514     // unregister callbacks
515     if(m->osdep.icnhandle) CancelMibChangeNotify2(m->osdep.icnhandle);
516     if(m->osdep.acnhandle) CancelMibChangeNotify2(m->osdep.acnhandle);
517
518     // unregister event handler
519     m->server->poll_api->watch_free(m->osdep.watch);
520
521     // close pipe
522     closepipe(m->osdep.pipefd[0]);
523     closepipe(m->osdep.pipefd[1]);
524
525     // make sure no stray events can come in during destruction
526     pthread_mutex_lock(&m->osdep.mutex);
527
528     // free all events that are still in the queue
529     while((ev = m->osdep.events) != NULL) {
530         CATTA_LLIST_REMOVE(ChangeEvent, event, m->osdep.events, ev);
531         catta_free(ev);
532     }
533
534     pthread_mutex_unlock(&m->osdep.mutex);
535     pthread_mutex_destroy(&m->osdep.mutex);
536 }
537
538 void catta_interface_monitor_sync(CattaInterfaceMonitor *m)
539 {
540     IP_ADAPTER_ADDRESSES *buf = NULL;
541     IP_ADAPTER_ADDRESSES *p;
542     ULONG bufsize = 15000;
543     ULONG r;
544
545     // allocate a buffer and call GetAdaptersAddresses
546     // retry with the correct size if the buffer was too small
547     do {
548         catta_free(buf);    // no-op on first iteration
549         if((buf = catta_malloc(bufsize)) == NULL) {
550             catta_log_error("malloc failed in catta_interface_monitor_sync");
551             return;
552         }
553
554         r = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, buf, &bufsize);
555     } while(r == ERROR_BUFFER_OVERFLOW);
556
557     if(r != NO_ERROR) {
558         catta_log_error("GetAdaptersAddresses failed: %u", (unsigned int)r);
559         return;
560     }
561
562     // XXX remove interfaces for adapters that are no longer in the list
563
564     // create 'CattaInterface's for every adapter
565     for(p=buf; p; p=p->Next)
566         ip_adapter(m, p);
567
568     catta_free(buf);
569
570     m->list_complete = 1;
571     catta_interface_monitor_check_relevant(m);
572     catta_interface_monitor_update_rrs(m, 0);
573     catta_log_info("Network interface enumeration completed.");
574 }