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