#include "ruby.h" #ifndef RUBY_19 #include "rubysig.h" #endif #include "netifaces.h" #if !defined(WIN32) #if !HAVE_GETNAMEINFO #undef getnameinfo #undef NI_NUMERICHOST #define getnameinfo our_getnameinfo #define NI_NUMERICHOST 1 /* A very simple getnameinfo() for platforms without */ static int getnameinfo (const struct sockaddr *addr, int addr_len, char *buffer, int buflen, char *buf2, int buf2len, int flags) { switch (addr->sa_family) { case AF_INET: { const struct sockaddr_in *sin = (struct sockaddr_in *)addr; const unsigned char *bytes = (unsigned char *)&sin->sin_addr.s_addr; char tmpbuf[20]; sprintf (tmpbuf, "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3]); strncpy (buffer, tmpbuf, buflen); } break; #ifdef AF_INET6 case AF_INET6: { const struct sockaddr_in6 *sin = (const struct sockaddr_in6 *)addr; const unsigned char *bytes = sin->sin6_addr.s6_addr; int n; char tmpbuf[80], *ptr = tmpbuf; int done_double_colon = FALSE; int colon_mode = FALSE; for (n = 0; n < 8; ++n) { unsigned char b1 = bytes[2 * n]; unsigned char b2 = bytes[2 * n + 1]; if (b1) { if (colon_mode) { colon_mode = FALSE; *ptr++ = ':'; } sprintf (ptr, "%x%02x", b1, b2); ptr += strlen (ptr); *ptr++ = ':'; } else if (b2) { if (colon_mode) { colon_mode = FALSE; *ptr++ = ':'; } sprintf (ptr, "%x", b2); ptr += strlen (ptr); *ptr++ = ':'; } else { if (!colon_mode) { if (done_double_colon) { *ptr++ = '0'; *ptr++ = ':'; } else { if (n == 0) *ptr++ = ':'; colon_mode = TRUE; done_double_colon = TRUE; } } } } if (colon_mode) { colon_mode = FALSE; *ptr++ = ':'; *ptr++ = '\0'; } else { *--ptr = '\0'; } strncpy (buffer, tmpbuf, buflen); } break; #endif /* AF_INET6 */ default: return -1; } return 0; } #endif static int string_from_sockaddr (struct sockaddr *addr, char *buffer, int buflen) { if (!addr || addr->sa_family == AF_UNSPEC) return -1; if (getnameinfo (addr, SA_LEN(addr), buffer, buflen, NULL, 0, NI_NUMERICHOST) != 0) { int n, len; char *ptr; const char *data; len = SA_LEN(addr); #if HAVE_AF_LINK /* BSD-like systems have AF_LINK */ if (addr->sa_family == AF_LINK) { struct sockaddr_dl *dladdr = (struct sockaddr_dl *)addr; len = dladdr->sdl_alen; if(len >=0) data = LLADDR(dladdr); } else { #endif #if defined(AF_PACKET) /* Linux has AF_PACKET instead */ if (addr->sa_family == AF_PACKET) { struct sockaddr_ll *lladdr = (struct sockaddr_ll *)addr; len = lladdr->sll_halen; //amaloteaux: openbsd and maybe other systems have a len of 0 for enc0,pflog0 .. interfaces if(len >=0) data = (const char *)lladdr->sll_addr; } else { #endif /* We don't know anything about this sockaddr, so just display the entire data area in binary. */ len -= (sizeof (struct sockaddr) - sizeof (addr->sa_data)); data = addr->sa_data; #if defined(AF_PACKET) } #endif #if HAVE_AF_LINK } #endif if ((buflen < 3 * len) || len <= 0) return -1; ptr = buffer; buffer[0] = '\0'; for (n = 0; n < len; ++n) { sprintf (ptr, "%02x:", data[n] & 0xff); ptr += 3; } *--ptr = '\0'; } return 0; } #endif /* !defined(WIN32) */ static VALUE add_to_family(VALUE result, VALUE family, VALUE value) { Check_Type(result, T_HASH); Check_Type(family, T_FIXNUM); Check_Type(value, T_HASH); VALUE list; list = rb_hash_aref(result, family); if (list == Qnil) list = rb_ary_new(); else Check_Type(list, T_ARRAY); rb_ary_push(list, value); rb_hash_aset(result, family, list); return result; } VALUE rbnetifaces_s_addresses (VALUE class, VALUE dev) { Check_Type(dev, T_STRING); VALUE result; int found = FALSE; result = rb_hash_new(); #if defined(WIN32) PIP_ADAPTER_INFO pAdapterInfo = NULL; PIP_ADAPTER_INFO pInfo = NULL; ULONG ulBufferLength = 0; DWORD dwRet; PIP_ADDR_STRING str; //First, retrieve the adapter information. We do this in a loop, in //case someone adds or removes adapters in the meantime. do { dwRet = GetAdaptersInfo(pAdapterInfo, &ulBufferLength); if (dwRet == ERROR_BUFFER_OVERFLOW) { if (pAdapterInfo) free (pAdapterInfo); pAdapterInfo = (PIP_ADAPTER_INFO)malloc (ulBufferLength); if (!pAdapterInfo) { rb_raise(rb_eRuntimeError, "Unknow error at OS level"); return Qnil; } } } while (dwRet == ERROR_BUFFER_OVERFLOW); // If we failed, then fail in Ruby too if (dwRet != ERROR_SUCCESS && dwRet != ERROR_NO_DATA) { if (pAdapterInfo) free (pAdapterInfo); rb_raise(rb_eRuntimeError, "Unable to obtain adapter information."); return Qnil; } for (pInfo = pAdapterInfo; pInfo; pInfo = pInfo->Next) { char buffer[256]; if (strcmp (pInfo->AdapterName, StringValuePtr(dev)) != 0) continue; VALUE rbhardw = Qnil; VALUE rbaddr = Qnil; VALUE rbnetmask = Qnil; VALUE rbbraddr = Qnil; found = TRUE; // Do the physical address if (256 >= 3 * pInfo->AddressLength) { VALUE hash_hardw; hash_hardw = rb_hash_new(); char *ptr = buffer; unsigned n; *ptr = '\0'; for (n = 0; n < pInfo->AddressLength; ++n) { sprintf (ptr, "%02x:", pInfo->Address[n] & 0xff); ptr += 3; } *--ptr = '\0'; rbhardw = rb_str_new2(buffer); rb_hash_aset(hash_hardw, rb_str_new2("addr"), rbhardw); result = add_to_family(result, INT2FIX(AF_LINK), hash_hardw); } for (str = &pInfo->IpAddressList; str; str = str->Next) { VALUE result2; result2 = rb_hash_new(); if(str->IpAddress.String) rbaddr = rb_str_new2(str->IpAddress.String); if(str->IpMask.String) rbnetmask = rb_str_new2(str->IpMask.String); //If this isn't the loopback interface, work out the broadcast //address, for better compatibility with other platforms. if (pInfo->Type != MIB_IF_TYPE_LOOPBACK) { unsigned long inaddr = inet_addr (str->IpAddress.String); unsigned long inmask = inet_addr (str->IpMask.String); struct in_addr in; char *brstr; in.S_un.S_addr = (inaddr | ~inmask) & 0xfffffffful; brstr = inet_ntoa (in); if (brstr) rbbraddr = rb_str_new2(brstr); } if (rbaddr) rb_hash_aset(result2, rb_str_new2("addr"), rbaddr); if (rbnetmask) rb_hash_aset(result2, rb_str_new2("netmask"), rbnetmask); if (rbbraddr) rb_hash_aset(result2, rb_str_new2("broadcast"), rbbraddr); result = add_to_family(result, INT2FIX(AF_INET), result2); } } // for free (pAdapterInfo); #elif HAVE_GETIFADDRS struct ifaddrs *addrs = NULL; struct ifaddrs *addr = NULL; if (getifaddrs (&addrs) < 0) { rb_raise(rb_eRuntimeError, "Unknow error at OS level"); } for (addr = addrs; addr; addr = addr->ifa_next) { char buffer[256]; VALUE rbaddr = Qnil; VALUE rbnetmask = Qnil; VALUE rbbraddr = Qnil; if (strcmp (addr->ifa_name, StringValuePtr(dev)) != 0) continue; /* Sometimes there are records without addresses (e.g. in the case of a dial-up connection via ppp, which on Linux can have a link address record with no actual address). We skip these as they aren't useful. Thanks to Christian Kauhaus for reporting this issue. */ if (!addr->ifa_addr) continue; found = TRUE; if (string_from_sockaddr (addr->ifa_addr, buffer, sizeof (buffer)) == 0) rbaddr = rb_str_new2(buffer); if (string_from_sockaddr (addr->ifa_netmask, buffer, sizeof (buffer)) == 0) rbnetmask = rb_str_new2(buffer); if (string_from_sockaddr (addr->ifa_broadaddr, buffer, sizeof (buffer)) == 0) rbbraddr = rb_str_new2(buffer); VALUE result2; result2 = rb_hash_new(); if (rbaddr) rb_hash_aset(result2, rb_str_new2("addr"), rbaddr); if (rbnetmask) rb_hash_aset(result2, rb_str_new2("netmask"), rbnetmask); if (rbbraddr) { if (addr->ifa_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) rb_hash_aset(result2, rb_str_new2("peer"), rbbraddr); else rb_hash_aset(result2, rb_str_new2("broadcast"), rbbraddr); } if (rbaddr || rbnetmask || rbbraddr) result = add_to_family(result, INT2FIX(addr->ifa_addr->sa_family), result2); } freeifaddrs (addrs); #elif HAVE_SOCKET_IOCTLS int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { rb_raise(rb_eRuntimeError, "Unknow error at OS level"); return Qnil; } struct CNAME(ifreq) ifr; char buffer[256]; int is_p2p = FALSE; VALUE rbaddr = Qnil; VALUE rbnetmask = Qnil; VALUE rbbraddr = Qnil; VALUE rbdstaddr = Qnil; strncpy (ifr.CNAME(ifr_name), StringValuePtr(dev), IFNAMSIZ); #if HAVE_SIOCGIFHWADDR if (ioctl (sock, SIOCGIFHWADDR, &ifr) == 0) { if (string_from_sockaddr (&(ifr.CNAME(ifr_addr)), buffer, sizeof (buffer)) == 0) { found = TRUE; VALUE rbhardw = Qnil; VALUE hash_hardw; hash_hardw = rb_hash_new(); rbhardw = rb_str_new2(buffer); rb_hash_aset(hash_hardw, rb_str_new2("addr"), rbhardw); result = add_to_family(result, INT2FIX(AF_LINK), hash_hardw); } } #endif #if HAVE_SIOCGIFADDR #if HAVE_SIOCGLIFNUM if (ioctl (sock, SIOCGLIFADDR, &ifr) == 0) { #else if (ioctl (sock, SIOCGIFADDR, &ifr) == 0) { #endif if (string_from_sockaddr ((struct sockaddr *)&ifr.CNAME(ifr_addr), buffer, sizeof (buffer)) == 0) { found = TRUE; rbaddr = rb_str_new2(buffer); } } #endif #if HAVE_SIOCGIFNETMASK #if HAVE_SIOCGLIFNUM if (ioctl (sock, SIOCGLIFNETMASK, &ifr) == 0) { #else if (ioctl (sock, SIOCGIFNETMASK, &ifr) == 0) { #endif if (string_from_sockaddr ((struct sockaddr *)&ifr.CNAME(ifr_addr), buffer, sizeof (buffer)) == 0) { found = TRUE; rbnetmask = rb_str_new2(buffer); } } #endif #if HAVE_SIOCGIFFLAGS #if HAVE_SIOCGLIFNUM if (ioctl (sock, SIOCGLIFFLAGS, &ifr) == 0) { #else if (ioctl (sock, SIOCGIFFLAGS, &ifr) == 0) { #endif if (ifr.CNAME(ifr_flags) & IFF_POINTOPOINT) { is_p2p = TRUE; } } #endif #if HAVE_SIOCGIFBRDADDR #if HAVE_SIOCGLIFNUM if (!is_p2p && ioctl (sock, SIOCGLIFBRDADDR, &ifr) == 0) { #else if (!is_p2p && ioctl (sock, SIOCGIFBRDADDR, &ifr) == 0) { #endif if (string_from_sockaddr ((struct sockaddr *)&ifr.CNAME(ifr_addr), buffer, sizeof (buffer)) == 0) { found = TRUE; rbbraddr = rb_str_new2(buffer); } } #endif #if HAVE_SIOCGIFDSTADDR #if HAVE_SIOCGLIFNUM if (is_p2p && ioctl (sock, SIOCGLIFBRDADDR, &ifr) == 0) { #else if (is_p2p && ioctl (sock, SIOCGIFBRDADDR, &ifr) == 0) { #endif if (string_from_sockaddr ((struct sockaddr *)&ifr.CNAME(ifr_addr), buffer, sizeof (buffer)) == 0) { found = TRUE; rbdstaddr = rb_str_new2(buffer); } } #endif VALUE result2; result2 = rb_hash_new(); if (rbaddr) rb_hash_aset(result2, rb_str_new2("addr"), rbaddr); if (rbnetmask) rb_hash_aset(result2, rb_str_new2("netmask"), rbnetmask); if (rbbraddr) rb_hash_aset(result2, rb_str_new2("broadcast"), rbbraddr); if (rbdstaddr) rb_hash_aset(result2, rb_str_new2("peer"), rbbraddr); if (rbaddr || rbnetmask || rbbraddr || rbdstaddr) result = add_to_family(result, INT2FIX(AF_INET), result2); close (sock); #endif /* HAVE_SOCKET_IOCTLS */ if (found) return result; else return Qnil; } VALUE rbnetifaces_s_interfaces (VALUE self) { VALUE result; result = rb_ary_new(); #if defined(WIN32) PIP_ADAPTER_INFO pAdapterInfo = NULL; PIP_ADAPTER_INFO pInfo = NULL; ULONG ulBufferLength = 0; DWORD dwRet; // First, retrieve the adapter information do { dwRet = GetAdaptersInfo(pAdapterInfo, &ulBufferLength); if (dwRet == ERROR_BUFFER_OVERFLOW) { if (pAdapterInfo) free (pAdapterInfo); pAdapterInfo = (PIP_ADAPTER_INFO)malloc (ulBufferLength); if (!pAdapterInfo) { rb_raise(rb_eRuntimeError, "Unknow error at OS level"); } } } while (dwRet == ERROR_BUFFER_OVERFLOW); // If we failed, then fail in Ruby too if (dwRet != ERROR_SUCCESS && dwRet != ERROR_NO_DATA) { if (pAdapterInfo) free (pAdapterInfo); rb_raise(rb_eRuntimeError, "Unknow error at OS level"); return Qnil; } if (dwRet == ERROR_NO_DATA) { free (pAdapterInfo); return result; } for (pInfo = pAdapterInfo; pInfo; pInfo = pInfo->Next) { VALUE ifname = rb_str_new2(pInfo->AdapterName); //VALUE ifname = rb_str_new2(pInfo->Description); if(!rb_ary_includes(result, ifname)) rb_ary_push(result, ifname); } free (pAdapterInfo); #elif HAVE_GETIFADDRS const char *prev_name = NULL; struct ifaddrs *addrs = NULL; struct ifaddrs *addr = NULL; if (getifaddrs (&addrs) < 0) { rb_raise(rb_eRuntimeError, "Unknow error at OS level"); } for (addr = addrs; addr; addr = addr->ifa_next) { if (!prev_name || strncmp (addr->ifa_name, prev_name, IFNAMSIZ) != 0) { VALUE ifname = rb_str_new2(addr->ifa_name); if(!rb_ary_includes(result, ifname)) rb_ary_push(result, ifname); prev_name = addr->ifa_name; } } freeifaddrs (addrs); #elif HAVE_SIOCGIFCONF const char *prev_name = NULL; int fd = socket (AF_INET, SOCK_DGRAM, 0); struct CNAME(ifconf) ifc; int len = -1, n; if (fd < 0) { rb_raise(rb_eRuntimeError, "Unknow error at OS level"); return Qnil; } // Try to find out how much space we need #if HAVE_SIOCGSIZIFCONF if (ioctl (fd, SIOCGSIZIFCONF, &len) < 0) len = -1; #elif HAVE_SIOCGLIFNUM #error This code need to be checked first /* { struct lifnum lifn; lifn.lifn_family = AF_UNSPEC; lifn.lifn_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES; ifc.lifc_family = AF_UNSPEC; ifc.lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES; if (ioctl (fd, SIOCGLIFNUM, (char *)&lifn) < 0) len = -1; else len = lifn.lifn_count; } */ #endif // As a last resort, guess if (len < 0) len = 64; ifc.CNAME(ifc_len) = len * sizeof (struct CNAME(ifreq)); ifc.CNAME(ifc_buf) = malloc (ifc.CNAME(ifc_len)); if (!ifc.CNAME(ifc_buf)) { close (fd); rb_raise(rb_eRuntimeError, "Not enough memory"); return Qnil; } #if HAVE_SIOCGLIFNUM if (ioctl (fd, SIOCGLIFCONF, &ifc) < 0) { #else if (ioctl (fd, SIOCGIFCONF, &ifc) < 0) { #endif free (ifc.CNAME(ifc_req)); close (fd); rb_raise(rb_eRuntimeError, "Unknow error at OS level"); return Qnil; } struct CNAME(ifreq) *pfreq = ifc.CNAME(ifc_req); for (n = 0; n < ifc.CNAME(ifc_len)/sizeof(struct CNAME(ifreq));n++,pfreq++) { if (!prev_name || strncmp (prev_name, pfreq->CNAME(ifr_name), IFNAMSIZ) != 0) { VALUE ifname = rb_str_new2(pfreq->CNAME(ifr_name)); if(!rb_ary_includes(result, ifname)) rb_ary_push(result, ifname); prev_name = pfreq->CNAME(ifr_name); } } free (ifc.CNAME(ifc_buf)); close (fd); #endif // return result; }