X-Git-Url: http://git.meshlink.io/?a=blobdiff_plain;f=src%2Fiface-windows.c;h=09ab8209453602ad207aab343cabcb3c0edb5653;hb=d1da9f9f0f312873501e42ff39a9eab26bf2ab50;hp=833112f4b49c8105f3c6f2fd5380a306275ce1b0;hpb=bd3239afa89e2321ad7da1dc56ba8a82e11d7c94;p=catta diff --git a/src/iface-windows.c b/src/iface-windows.c index 833112f..09ab820 100644 --- a/src/iface-windows.c +++ b/src/iface-windows.c @@ -17,71 +17,128 @@ USA. ***/ -#include "iface-windows.h" #include "iface.h" +#include "iface-windows.h" #include // wcstombs #include #include #include +#include #include "hashmap.h" #include "util.h" // catta_format_mac_address +#include "fdutil.h" // catta_set_nonblock + + +typedef enum { + INTERFACE_CHANGE_EVENT, + ADDRESS_CHANGE_EVENT +} ChangeEventType; + +struct ChangeEvent { + CATTA_LLIST_FIELDS(ChangeEvent, event); + ChangeEventType type; + MIB_NOTIFICATION_TYPE notification_type; + union { + MIB_IPINTERFACE_ROW iface; + MIB_UNICASTIPADDRESS_ROW addr; + } data; +}; + + +// integrate the information from an IP_ADAPTER_UNICAST_ADDRESS structure for +// given CattaHwInterface into the CattaInterfaceMonitor +static void ip_adapter_unicast_address(CattaInterfaceMonitor *m, + CattaHwInterface *hw, + IP_ADAPTER_UNICAST_ADDRESS *a) +{ + CattaInterface *iface; + CattaAddress addr; + CattaInterfaceAddress *ifaddr; + struct sockaddr *sa = a->Address.lpSockaddr; + + // skip transient addresses; to quote MSDN: "The IP address is a cluster + // address and should not be used by most applications." + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366066(v=vs.85).aspx + if(a->Flags & IP_ADAPTER_ADDRESS_TRANSIENT) + return; + // fill addr struct for address lookup + switch(sa->sa_family) { + case AF_INET: + memcpy(addr.data.data, &((struct sockaddr_in *)sa)->sin_addr, sizeof(struct in_addr)); + break; + case AF_INET6: + memcpy(addr.data.data, &((struct sockaddr_in6 *)sa)->sin6_addr, sizeof(struct in6_addr)); + break; + default: + catta_log_debug("unexpected address family on interface %d: %u", hw->index, sa->sa_family); + return; + } + addr.proto = catta_af_to_proto(sa->sa_family); -// for the luid-to-idx hashmap -static unsigned luid_hash(const void *data) -{ - return ((NET_LUID *)data)->Info.NetLuidIndex; -} -static int luid_equal(const void *a, const void *b) -{ - return (((NET_LUID *)a)->Value == ((NET_LUID *)b)->Value); -} + // get protocol-specific CattaInterface object + if(!(iface = catta_interface_monitor_get_interface(m, hw->index, addr.proto))) { + catta_log_error("CattaInterface (index %d, proto %d) not found", hw->index, addr.proto); + return; + } -static CattaIfIndex find_ifindex(CattaInterfaceMonitor *m, NET_LUID luid) -{ - CattaIfIndex *pi = NULL; - NET_LUID *key = NULL; - - if((pi = catta_hashmap_lookup(m->osdep.idxmap, &luid)) == NULL) { - // allocate memory for the hashmap key and value - key = catta_malloc(sizeof(luid)); - pi = catta_malloc(sizeof(CattaIfIndex)); - if(!key || !pi) - goto fail; - - *key = luid; - - // find an index for this luid - *pi = m->osdep.nidx; - if(*pi < 0) // overflow - goto fail; - - // register the index - if(catta_hashmap_replace(m->osdep.idxmap, key, pi) < 0) - goto fail; - m->osdep.nidx++; + // find or allocate a CattaInterfaceAddress struct for this address + if(!(ifaddr = catta_interface_monitor_get_address(m, iface, &addr))) { + if(!(ifaddr = catta_interface_address_new(m, iface, &addr, a->OnLinkPrefixLength))) { + catta_log_error("out of memory in ip_adapter_unicast_address"); + return; + } } - return *pi; + // set global scope flag + // XXX should we use the IP_ADAPTER_ADDRESS_DNS_ELIGIBLE flag for this? + // it looks like it gets set for IPv4 addresses that are not localhost + // and for IPv6 addresses with global scope. not sure about multicast + // addresses. + if(addr.proto == CATTA_PROTO_INET6) + ifaddr->global_scope = !(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)addr.data.data) + || IN6_IS_ADDR_MULTICAST((struct in6_addr *)addr.data.data)); + else + ifaddr->global_scope = 1; -fail: - catta_free(key); - catta_free(pi); - return -1; + // XXX debugging, remove + { + char s[CATTA_ADDRESS_STR_MAX]; + catta_log_debug(" address: %s\n" + " global_scope: %d\n" + " flags: 0x%.4x", + catta_address_snprint(s, sizeof(s), &addr), + ifaddr->global_scope, + (unsigned int)a->Flags); + } } // integrate the information from an IP_ADAPTER_ADDRESSES structure // as returned by GetAdaptersAddresses into the CattaInterfaceMonitor -static void ip_adapter_address(CattaInterfaceMonitor *m, IP_ADAPTER_ADDRESSES *p) +static void ip_adapter(CattaInterfaceMonitor *m, IP_ADAPTER_ADDRESSES *p) { + IP_ADAPTER_UNICAST_ADDRESS *a; CattaIfIndex idx; CattaHwInterface *hw; size_t n; - // look up the interface index by LUID - if((idx = find_ifindex(m, p->Luid)) < 0) { - catta_log_error("could not allocate index ip_adapter_address"); + // we want an index specific to the hardware interface, but Windows + // has one for IPv4 and one for IPv6. it seems like these are always the + // same unless one of the protocols is not available. let's have a bunch of + // checks... + if(!p->IfIndex && !p->Ipv6IfIndex) { + return; // no usable protocols + } else if(!p->IfIndex) { + idx = p->Ipv6IfIndex; // IPv6 but no IPv4 (huh!) + } else if(!p->Ipv6IfIndex) { + idx = p->IfIndex; // IPv4 but no IPv6 + } else if(p->IfIndex == p->Ipv6IfIndex) { + idx = p->IfIndex; // same index for both protocols + } else { + // both indexes valid but not equal + catta_log_error("unsupported interface: %ls (IfIndex and Ipv6IfIndex differ: %u/%u)", + p->FriendlyName, (unsigned int)p->IfIndex, (unsigned int)p->Ipv6IfIndex); return; } @@ -95,10 +152,10 @@ static void ip_adapter_address(CattaInterfaceMonitor *m, IP_ADAPTER_ADDRESSES *p // fill the CattaHwInterface struct with data hw->flags_ok = - (p->OperStatus & IfOperStatusUp) && - !(p->IfType & IF_TYPE_SOFTWARE_LOOPBACK) && + (p->OperStatus == IfOperStatusUp) && + !(p->IfType == IF_TYPE_SOFTWARE_LOOPBACK) && !(p->Flags & IP_ADAPTER_NO_MULTICAST) && - (m->server->config.allow_point_to_point || !(p->IfType & IF_TYPE_PPP)); + (m->server->config.allow_point_to_point || !(p->IfType == IF_TYPE_PPP)); // XXX what about IF_TYPE_TUNNEL? n = wcstombs(NULL, p->FriendlyName, 0) + 1; @@ -113,37 +170,248 @@ static void ip_adapter_address(CattaInterfaceMonitor *m, IP_ADAPTER_ADDRESSES *p hw->mac_address_size = CATTA_MAC_ADDRESS_MAX; memcpy(hw->mac_address, p->PhysicalAddress, hw->mac_address_size); - // XXX process addresses - // XXX debugging, remove - { - char mac[256]; - catta_log_debug("======\n name: %s\n index:%d\n mtu:%d\n mac:%s\n flags_ok:%d\n======", - hw->name, hw->index, - hw->mtu, - catta_format_mac_address(mac, sizeof(mac), hw->mac_address, hw->mac_address_size), - hw->flags_ok); - } + { + char mac[256]; + catta_log_debug(" name: %s\n" + " index: %d\n" + " IfIndex: %u\n" + " Ipv6IfIndex: %u\n" + " mtu: %d\n" + " mac: %s\n" + " flags_ok: %d\n" + " type: %u\n" + " status: %u\n" + " multicast: %d\n" + " flags: 0x%.4x", + hw->name, hw->index, + (unsigned int)p->IfIndex, (unsigned int)p->Ipv6IfIndex, + hw->mtu, + catta_format_mac_address(mac, sizeof(mac), hw->mac_address, hw->mac_address_size), + hw->flags_ok, + (unsigned int)p->IfType, + (unsigned int)p->OperStatus, + !(p->Flags & IP_ADAPTER_NO_MULTICAST), + (unsigned int)p->Flags); + } + + // process addresses + // XXX remove addresses that are no longer in the list + for(a=p->FirstUnicastAddress; a; a=a->Next) + ip_adapter_unicast_address(m, hw, a); + catta_log_debug("====="); +} + + +// place the event into the queue to be handled (by the main thread) +// and wake the event handler if necessary +static void queue_event(CattaInterfaceMonitor *m, ChangeEvent *ev) +{ + char c = 'X'; + + if(!ev) + return; + + if(!pthread_mutex_lock(&m->osdep.mutex)) { + // queue the event + // XXX event ordering!! + CATTA_LLIST_PREPEND(ChangeEvent, event, m->osdep.events, ev); + + // wake the handler + writepipe(m->osdep.pipefd[1], &c, sizeof(c)); + + pthread_mutex_unlock(&m->osdep.mutex); + } else { + catta_log_debug(__FILE__": queue_event: could not lock mutex"); + catta_free(ev); + } +} + +// copy the given data row into an appropriate change event struct +static ChangeEvent *new_event(ChangeEventType type, MIB_NOTIFICATION_TYPE ntype, void *row, size_t n) +{ + ChangeEvent *ev; + + if(!row) + return NULL; + + if(!(ev = catta_new(ChangeEvent, 1))) + return NULL; + + ev->type = type; + ev->notification_type = ntype; + memcpy(&ev->data, row, n); + + return ev; +} + +static void WINAPI icn_callback(void *m, MIB_IPINTERFACE_ROW *row, MIB_NOTIFICATION_TYPE type) +{ + queue_event(m, new_event(INTERFACE_CHANGE_EVENT, type, row, sizeof(*row))); +} + +static void WINAPI acn_callback(void *m, MIB_UNICASTIPADDRESS_ROW *row, MIB_NOTIFICATION_TYPE type) +{ + queue_event(m, new_event(ADDRESS_CHANGE_EVENT, type, row, sizeof(*row))); +} + +static void handle_iface_event(CattaInterfaceMonitor *m, MIB_IPINTERFACE_ROW *row, MIB_NOTIFICATION_TYPE type) +{ + catta_log_debug("interface change event on iface %u for address family %u", + (unsigned int)row->InterfaceIndex, (unsigned int)row->Family); + + switch(type) { + case MibParameterNotification: + catta_log_debug(" notification type: ParameterNotification"); + break; + case MibAddInstance: + catta_log_debug(" notification type: AddInstance"); + break; + case MibDeleteInstance: + catta_log_debug(" notification type: DeleteInstance"); + break; + default: + catta_log_debug("unexpected type (%d) of interface change notification received", type); + } +} + +static void handle_addr_event(CattaInterfaceMonitor *m, MIB_UNICASTIPADDRESS_ROW *row, MIB_NOTIFICATION_TYPE type) +{ + catta_log_debug("address change event on iface %u for address family %u", + (unsigned int)row->InterfaceIndex, + (unsigned int)row->Address.si_family); + + switch(type) { + case MibParameterNotification: + catta_log_debug(" notification type: ParameterNotification"); + break; + case MibAddInstance: + catta_log_debug(" notification type: AddInstance"); + break; + case MibDeleteInstance: + catta_log_debug(" notification type: DeleteInstance"); + break; + default: + catta_log_debug("unexpected type (%d) of address change notification received", type); + } +} + +static void handle_events(CattaInterfaceMonitor *m) +{ + char buf[16]; + ChangeEvent *ev; + + if(!pthread_mutex_lock(&m->osdep.mutex)) { + // clear the pipe + while(readpipe(m->osdep.pipefd[0], buf, sizeof(buf)) == sizeof(buf)) {} + + while((ev = m->osdep.events) != NULL) { + CATTA_LLIST_REMOVE(ChangeEvent, event, m->osdep.events, ev); + + // dispatch to the appropriate handler + switch(ev->type) { + case INTERFACE_CHANGE_EVENT: + handle_iface_event(m, &ev->data.iface, ev->notification_type); + break; + case ADDRESS_CHANGE_EVENT: + handle_addr_event(m, &ev->data.addr, ev->notification_type); + break; + default: + catta_log_debug("unhandled change event type in handle_events"); + } + + catta_free(ev); + } + + pthread_mutex_unlock(&m->osdep.mutex); + } +} + +static void pipe_callback(CattaWatch *w, int fd, CattaWatchEvent event, void *m) +{ + // silence "unused parameter" warnings + (void)w; + (void)fd; + (void)event; + + handle_events(m); } int catta_interface_monitor_init_osdep(CattaInterfaceMonitor *m) { - m->osdep.nidx = 0; - m->osdep.idxmap = catta_hashmap_new(luid_hash, luid_equal, catta_free, catta_free); - if(m->osdep.idxmap == NULL) { - catta_log_error("out of memory in catta_interface_monitor_init_osdep"); + DWORD r; + + pthread_mutex_init(&m->osdep.mutex, NULL); + + CATTA_LLIST_HEAD_INIT(ChangeEvent, m->osdep.events); + + if(pipe(m->osdep.pipefd) < 0) { + catta_log_error("pipe() in catta_interface_monitor_init_osdep failed"); + return -1; + } + catta_set_nonblock(m->osdep.pipefd[0]); + catta_set_nonblock(m->osdep.pipefd[1]); + + m->osdep.icnhandle = NULL; + m->osdep.acnhandle = NULL; + + // register handler for change events + m->osdep.watch = m->server->poll_api->watch_new(m->server->poll_api, + m->osdep.pipefd[0], + CATTA_WATCH_IN, + pipe_callback, + m); + if(!m->osdep.watch) { + catta_log_error(__FILE__": Failed to create watch."); return -1; } - // XXX register callbacks to get notified of interface/address changes + // request async notification on interface changes + r = NotifyIpInterfaceChange(AF_UNSPEC, + // icn_callback needs to be WINAPI but + // MingW up to 3.1.0 erroneously defines + // PIPINTERFACE_CHANGE_CALLBACK without it + (PIPINTERFACE_CHANGE_CALLBACK)icn_callback, + m, FALSE, &m->osdep.icnhandle); + if(r != NO_ERROR) + catta_log_error("NotifyIpInterfaceChange failed: %u", (unsigned int)r); + + // request async notification on address changes + r = NotifyUnicastIpAddressChange(AF_UNSPEC, acn_callback, m, FALSE, + &m->osdep.acnhandle); + if(r != NO_ERROR) + catta_log_error("NotifyUnicastIpAddressChange failed: %u", (unsigned int)r); return 0; } void catta_interface_monitor_free_osdep(CattaInterfaceMonitor *m) { - catta_hashmap_free(m->osdep.idxmap); + ChangeEvent *ev; + + // unregister callbacks + if(m->osdep.icnhandle) CancelMibChangeNotify2(m->osdep.icnhandle); + if(m->osdep.acnhandle) CancelMibChangeNotify2(m->osdep.acnhandle); + + // unregister event handler + m->server->poll_api->watch_free(m->osdep.watch); + + // close pipe + closepipe(m->osdep.pipefd[0]); + closepipe(m->osdep.pipefd[1]); + + // make sure no stray events can come in during destruction + pthread_mutex_lock(&m->osdep.mutex); + + // free all events that are still in the queue + while((ev = m->osdep.events) != NULL) { + CATTA_LLIST_REMOVE(ChangeEvent, event, m->osdep.events, ev); + catta_free(ev); + } + + pthread_mutex_unlock(&m->osdep.mutex); + pthread_mutex_destroy(&m->osdep.mutex); } void catta_interface_monitor_sync(CattaInterfaceMonitor *m) @@ -174,7 +442,7 @@ void catta_interface_monitor_sync(CattaInterfaceMonitor *m) // create 'CattaInterface's for every adapter for(p=buf; p; p=p->Next) - ip_adapter_address(m, p); + ip_adapter(m, p); catta_free(buf);