/* * Copyright (c) 2008-2010 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * Copyright (c) 2007 Bruce M. Simpson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include /* * Print the running system's current multicast group memberships. * As this relies on getifmaddrs(), it may not be used with a core file. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "netstat.h" union sockunion { struct sockaddr_storage ss; struct sockaddr sa; struct sockaddr_dl sdl; struct sockaddr_in sin; struct sockaddr_in6 sin6; }; typedef union sockunion sockunion_t; /* * This may have been defined in . Note that if is * to be included it must be included before this header file. */ #ifndef ifa_broadaddr #define ifa_broadaddr ifa_dstaddr /* broadcast address interface */ #endif //struct ifmaddrs { // struct ifmaddrs *ifma_next; // struct sockaddr *ifma_name; // struct sockaddr *ifma_addr; // struct sockaddr *ifma_lladdr; //}; void ifmalist_dump_af(const struct ifmaddrs * const ifmap, int const af); static int ifmalist_dump_mcstat(struct ifmaddrs *); static void in_ifinfo(struct igmp_ifinfo *); static const char *inm_mode(u_int); static void inm_print_sources_sysctl(uint32_t, struct in_addr); #ifdef INET6 static void in6_ifinfo(struct mld_ifinfo *); static void in6m_print_sources_sysctl(uint32_t, struct in6_addr *); static const char *inet6_n2a(struct in6_addr *); #endif static void printb(const char *, unsigned int, const char *); static const char *sdl_addr_to_hex(const struct sockaddr_dl *, char *, int); extern char *routename6(struct sockaddr_in6 *); #define sa_equal(a1, a2) \ (bcmp((a1), (a2), ((a1))->sa_len) == 0) #define sa_dl_equal(a1, a2) \ ((((struct sockaddr_dl *)(a1))->sdl_len == \ ((struct sockaddr_dl *)(a2))->sdl_len) && \ (bcmp(LLADDR((struct sockaddr_dl *)(a1)), \ LLADDR((struct sockaddr_dl *)(a2)), \ ((struct sockaddr_dl *)(a1))->sdl_alen) == 0)) #define SALIGN (sizeof(uint32_t) - 1) #define SA_RLEN(sa) (sa ? ((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \ (SALIGN + 1)) : 0) #define MAX_SYSCTL_TRY 5 #define RTA_MASKS (RTA_GATEWAY | RTA_IFP | RTA_IFA) void ifmalist_dump_af(const struct ifmaddrs * const ifmap, int const af) { const struct ifmaddrs *ifma; sockunion_t *psa; char myifname[IFNAMSIZ]; char *pcolon; char *pafname, *pifname, *plladdr = NULL, *pgroup = NULL; switch (af) { case AF_INET: pafname = "IPv4"; break; #ifdef INET6 case AF_INET6: pafname = "IPv6"; break; #endif case AF_LINK: pafname = "Link-layer"; break; default: return; /* XXX */ } fprintf(stdout, "%s Multicast Group Memberships\n", pafname); fprintf(stdout, "%-20s\t%-16s\t%s\n", "Group", "Link-layer Address", "Netif"); for (ifma = ifmap; ifma; ifma = ifma->ifma_next) { if (ifma->ifma_name == NULL || ifma->ifma_addr == NULL) continue; /* Group address */ psa = (sockunion_t *)ifma->ifma_addr; if (psa->sa.sa_family != af) continue; switch (psa->sa.sa_family) { case AF_INET: pgroup = inet_ntoa(psa->sin.sin_addr); break; #ifdef INET6 case AF_INET6: pgroup = routename6(&(psa->sin6)); break; #endif case AF_LINK: if ((psa->sdl.sdl_alen == ETHER_ADDR_LEN) || (psa->sdl.sdl_type == IFT_ETHER)) { pgroup = ether_ntoa((struct ether_addr *)&psa->sdl.sdl_data); #ifdef notyet } else { pgroup = addr2ascii(AF_LINK, &psa->sdl, sizeof(struct sockaddr_dl), addrbuf); #endif } break; default: continue; /* XXX */ } /* Link-layer mapping, if any */ psa = (sockunion_t *)ifma->ifma_lladdr; if (psa != NULL) { if (psa->sa.sa_family == AF_LINK) { if ((psa->sdl.sdl_alen == ETHER_ADDR_LEN) || (psa->sdl.sdl_type == IFT_ETHER)) { /* IEEE 802 */ plladdr = ether_ntoa((struct ether_addr *)&psa->sdl.sdl_data); #ifdef notyet } else { /* something more exotic */ plladdr = addr2ascii(AF_LINK, &psa->sdl, sizeof(struct sockaddr_dl), addrbuf); #endif } } else { int i; /* not a link-layer address */ plladdr = ""; for (i = 0; psa->sa.sa_len > 2 && i < psa->sa.sa_len - 2; i++) printf("0x%x ", psa->sa.sa_data[i]); printf("\n"); } } else { plladdr = ""; } /* Interface upon which the membership exists */ psa = (sockunion_t *)ifma->ifma_name; if (psa != NULL && psa->sa.sa_family == AF_LINK) { strlcpy(myifname, link_ntoa(&psa->sdl), sizeof(myifname)); pcolon = strchr(myifname, ':'); if (pcolon) *pcolon = '\0'; pifname = myifname; } else { pifname = ""; } fprintf(stdout, "%-20s\t%-16s\t%s\n", pgroup, plladdr, pifname); } } void ifmalist_dump(void) { struct ifmaddrs *ifmap; if (getifmaddrs(&ifmap)) err(EX_OSERR, "getifmaddrs"); ifmalist_dump_af(ifmap, AF_LINK); fputs("\n", stdout); ifmalist_dump_af(ifmap, AF_INET); #ifdef INET6 fputs("\n", stdout); ifmalist_dump_af(ifmap, AF_INET6); #endif if (sflag) { fputs("\n", stdout); ifmalist_dump_mcstat(ifmap); } freeifmaddrs(ifmap); } static int ifmalist_dump_mcstat(struct ifmaddrs *ifmap) { char thisifname[IFNAMSIZ]; char addrbuf[NI_MAXHOST]; struct ifaddrs *ifap, *ifa; struct ifmaddrs *ifma; sockunion_t lastifasa; sockunion_t *psa, *pgsa, *pllsa, *pifasa; char *pcolon; char *pafname; uint32_t lastifindex, thisifindex; int error; uint32_t ifindex = 0; if (interface != NULL) ifindex = if_nametoindex(interface); error = 0; ifap = NULL; lastifindex = 0; thisifindex = 0; lastifasa.ss.ss_family = AF_UNSPEC; if (getifaddrs(&ifap) != 0) { warn("getifmaddrs"); return (-1); } for (ifma = ifmap; ifma; ifma = ifma->ifma_next) { error = 0; if (ifma->ifma_name == NULL || ifma->ifma_addr == NULL) continue; psa = (sockunion_t *)ifma->ifma_name; if (psa->sa.sa_family != AF_LINK) { fprintf(stderr, "WARNING: Kernel returned invalid data.\n"); error = -1; break; } /* Filter on interface name. */ thisifindex = psa->sdl.sdl_index; if (ifindex != 0 && thisifindex != ifindex) continue; /* Filter on address family. */ pgsa = (sockunion_t *)ifma->ifma_addr; if (af != 0 && pgsa->sa.sa_family != af) continue; strlcpy(thisifname, link_ntoa(&psa->sdl), sizeof(thisifname)); pcolon = strchr(thisifname, ':'); if (pcolon) *pcolon = '\0'; /* Only print the banner for the first ifmaddrs entry. */ if (lastifindex == 0 || lastifindex != thisifindex) { lastifindex = thisifindex; fprintf(stdout, "%s:\n", thisifname); } /* * Currently, multicast joins only take place on the * primary IPv4 address, and only on the link-local IPv6 * address, as per IGMPv2/3 and MLDv1/2 semantics. * Therefore, we only look up the primary address on * the first pass. */ pifasa = NULL; for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if ((strcmp(ifa->ifa_name, thisifname) != 0) || (ifa->ifa_addr == NULL) || (ifa->ifa_addr->sa_family != pgsa->sa.sa_family)) continue; /* * For AF_INET6 only the link-local address should * be returned. If built without IPv6 support, * skip this address entirely. */ pifasa = (sockunion_t *)ifa->ifa_addr; if (pifasa->sa.sa_family == AF_INET6 #ifdef INET6 && !IN6_IS_ADDR_LINKLOCAL(&pifasa->sin6.sin6_addr) #endif ) { pifasa = NULL; continue; } break; } if (pifasa == NULL) continue; /* primary address not found */ if (!vflag && pifasa->sa.sa_family == AF_LINK) continue; /* Parse and print primary address, if not already printed. */ if (lastifasa.ss.ss_family == AF_UNSPEC || ((lastifasa.ss.ss_family == AF_LINK && !sa_dl_equal(&lastifasa.sa, &pifasa->sa)) || !sa_equal(&lastifasa.sa, &pifasa->sa))) { switch (pifasa->sa.sa_family) { case AF_INET: pafname = "inet"; break; case AF_INET6: pafname = "inet6"; break; case AF_LINK: pafname = "link"; break; default: pafname = "unknown"; break; } switch (pifasa->sa.sa_family) { case AF_INET6: #ifdef INET6 { const char *p = inet6_n2a(&pifasa->sin6.sin6_addr); strlcpy(addrbuf, p, sizeof(addrbuf)); break; } #else /* FALLTHROUGH */ #endif case AF_INET: error = getnameinfo(&pifasa->sa, pifasa->sa.sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); if (error) printf("getnameinfo: %s\n", gai_strerror(error)); break; case AF_LINK: { (void) sdl_addr_to_hex(&pifasa->sdl, addrbuf, sizeof (addrbuf)); break; } default: addrbuf[0] = '\0'; break; } fprintf(stdout, "\t%s %s\n", pafname, addrbuf); /* * Print per-link IGMP information, if available. */ if (pifasa->sa.sa_family == AF_INET) { struct igmp_ifinfo igi; size_t mibsize, len; int mib[5]; mibsize = sizeof(mib) / sizeof(mib[0]); if (sysctlnametomib("net.inet.igmp.ifinfo", mib, &mibsize) == -1) { perror("sysctlnametomib"); goto next_ifnet; } mib[mibsize] = thisifindex; len = sizeof(struct igmp_ifinfo); if (sysctl(mib, mibsize + 1, &igi, &len, NULL, 0) == -1) { perror("sysctl net.inet.igmp.ifinfo"); goto next_ifnet; } in_ifinfo(&igi); } #ifdef INET6 /* * Print per-link MLD information, if available. */ if (pifasa->sa.sa_family == AF_INET6) { struct mld_ifinfo mli; size_t mibsize, len; int mib[5]; mibsize = sizeof(mib) / sizeof(mib[0]); if (sysctlnametomib("net.inet6.mld.ifinfo", mib, &mibsize) == -1) { perror("sysctlnametomib"); goto next_ifnet; } mib[mibsize] = thisifindex; len = sizeof(struct mld_ifinfo); if (sysctl(mib, mibsize + 1, &mli, &len, NULL, 0) == -1) { perror("sysctl net.inet6.mld.ifinfo"); goto next_ifnet; } in6_ifinfo(&mli); } #endif /* INET6 */ #if defined(INET6) next_ifnet: #endif lastifasa = *pifasa; } /* Print this group address. */ #ifdef INET6 if (pgsa->sa.sa_family == AF_INET6) { const char *p = inet6_n2a(&pgsa->sin6.sin6_addr); strlcpy(addrbuf, p, sizeof(addrbuf)); } else #endif if (pgsa->sa.sa_family == AF_INET) { error = getnameinfo(&pgsa->sa, pgsa->sa.sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); if (error) printf("getnameinfo: %s\n", gai_strerror(error)); } else { (void) sdl_addr_to_hex(&pgsa->sdl, addrbuf, sizeof (addrbuf)); } fprintf(stdout, "\t\tgroup %s", addrbuf); if (pgsa->sa.sa_family == AF_INET) { inm_print_sources_sysctl(thisifindex, pgsa->sin.sin_addr); } #ifdef INET6 if (pgsa->sa.sa_family == AF_INET6) { in6m_print_sources_sysctl(thisifindex, &pgsa->sin6.sin6_addr); } #endif fprintf(stdout, "\n"); /* Link-layer mapping, if present. */ pllsa = (sockunion_t *)ifma->ifma_lladdr; if (pllsa != NULL) { (void) sdl_addr_to_hex(&pllsa->sdl, addrbuf, sizeof (addrbuf)); fprintf(stdout, "\t\t\tmcast-macaddr %s\n", addrbuf); } } if (ifap != NULL) freeifaddrs(ifap); return (error); } static void in_ifinfo(struct igmp_ifinfo *igi) { printf("\t"); switch (igi->igi_version) { case IGMP_VERSION_1: case IGMP_VERSION_2: case IGMP_VERSION_3: printf("igmpv%d", igi->igi_version); break; default: printf("igmpv?(%d)", igi->igi_version); break; } printb(" flags", igi->igi_flags, "\020\1SILENT\2LOOPBACK"); if (igi->igi_version == IGMP_VERSION_3) { printf(" rv %u qi %u qri %u uri %u", igi->igi_rv, igi->igi_qi, igi->igi_qri, igi->igi_uri); } if (vflag >= 2) { printf(" v1timer %u v2timer %u v3timer %u", igi->igi_v1_timer, igi->igi_v2_timer, igi->igi_v3_timer); } printf("\n"); } static const char *inm_modes[] = { "undefined", "include", "exclude", }; static const char * inm_mode(u_int mode) { if (mode >= MCAST_UNDEFINED && mode <= MCAST_EXCLUDE) return (inm_modes[mode]); return (NULL); } /* * Retrieve per-group source filter mode and lists via sysctl. */ static void inm_print_sources_sysctl(uint32_t ifindex, struct in_addr gina) { #define MAX_SYSCTL_TRY 5 int mib[7]; int ntry = 0; size_t mibsize; size_t len; size_t needed; size_t cnt; int i; char *buf; struct in_addr *pina; uint32_t *p; uint32_t fmode; const char *modestr; mibsize = sizeof(mib) / sizeof(mib[0]); if (sysctlnametomib("net.inet.ip.mcast.filters", mib, &mibsize) == -1) { perror("sysctlnametomib"); return; } needed = 0; mib[5] = ifindex; mib[6] = gina.s_addr; /* 32 bits wide */ mibsize = sizeof(mib) / sizeof(mib[0]); do { if (sysctl(mib, mibsize, NULL, &needed, NULL, 0) == -1) { perror("sysctl net.inet.ip.mcast.filters"); return; } if ((buf = malloc(needed)) == NULL) { perror("malloc"); return; } if (sysctl(mib, mibsize, buf, &needed, NULL, 0) == -1) { if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) { perror("sysctl"); goto out_free; } free(buf); buf = NULL; } } while (buf == NULL); len = needed; if (len < sizeof(uint32_t)) { perror("sysctl"); goto out_free; } p = (uint32_t *)buf; fmode = *p++; len -= sizeof(uint32_t); modestr = inm_mode(fmode); if (modestr) printf(" mode %s", modestr); else printf(" mode (%u)", fmode); if (vflag == 0) goto out_free; cnt = len / sizeof(struct in_addr); pina = (struct in_addr *)p; for (i = 0; i < cnt; i++) { if (i == 0) printf(" srcs "); fprintf(stdout, "%s%s", (i == 0 ? "" : ","), inet_ntoa(*pina++)); len -= sizeof(struct in_addr); } if (len > 0) { fprintf(stderr, "warning: %u trailing bytes from %s\n", (unsigned int)len, "net.inet.ip.mcast.filters"); } out_free: free(buf); #undef MAX_SYSCTL_TRY } #ifdef INET6 static void in6_ifinfo(struct mld_ifinfo *mli) { printf("\t"); switch (mli->mli_version) { case MLD_VERSION_1: case MLD_VERSION_2: printf("mldv%d", mli->mli_version); break; default: printf("mldv?(%d)", mli->mli_version); break; } printb(" flags", mli->mli_flags, "\020\1SILENT"); if (mli->mli_version == MLD_VERSION_2) { printf(" rv %u qi %u qri %u uri %u", mli->mli_rv, mli->mli_qi, mli->mli_qri, mli->mli_uri); } if (vflag >= 2) { printf(" v1timer %u v2timer %u", mli->mli_v1_timer, mli->mli_v2_timer); } printf("\n"); } /* * Retrieve MLD per-group source filter mode and lists via sysctl. * * Note: The 128-bit IPv6 group addres needs to be segmented into * 32-bit pieces for marshaling to sysctl. So the MIB name ends * up looking like this: * a.b.c.d.e.ifindex.g[0].g[1].g[2].g[3] * Assumes that pgroup originated from the kernel, so its components * are already in network-byte order. */ static void in6m_print_sources_sysctl(uint32_t ifindex, struct in6_addr *pgroup) { #define MAX_SYSCTL_TRY 5 char addrbuf[INET6_ADDRSTRLEN]; int mib[10]; int ntry = 0; int *pi; size_t mibsize; size_t len; size_t needed; size_t cnt; int i; char *buf; struct in6_addr *pina; uint32_t *p; uint32_t fmode; const char *modestr; mibsize = sizeof(mib) / sizeof(mib[0]); if (sysctlnametomib("net.inet6.ip6.mcast.filters", mib, &mibsize) == -1) { perror("sysctlnametomib"); return; } needed = 0; mib[5] = ifindex; pi = (int *)pgroup; for (i = 0; i < 4; i++) mib[6 + i] = *pi++; mibsize = sizeof(mib) / sizeof(mib[0]); do { if (sysctl(mib, mibsize, NULL, &needed, NULL, 0) == -1) { perror("sysctl net.inet6.ip6.mcast.filters"); return; } if ((buf = malloc(needed)) == NULL) { perror("malloc"); return; } if (sysctl(mib, mibsize, buf, &needed, NULL, 0) == -1) { if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) { perror("sysctl"); goto out_free; } free(buf); buf = NULL; } } while (buf == NULL); len = needed; if (len < sizeof(uint32_t)) { perror("sysctl"); goto out_free; } p = (uint32_t *)buf; fmode = *p++; len -= sizeof(uint32_t); modestr = inm_mode(fmode); if (modestr) printf(" mode %s", modestr); else printf(" mode (%u)", fmode); if (vflag == 0) goto out_free; cnt = len / sizeof(struct in6_addr); pina = (struct in6_addr *)p; for (i = 0; i < cnt; i++) { if (i == 0) printf(" srcs "); inet_ntop(AF_INET6, (const char *)pina++, addrbuf, INET6_ADDRSTRLEN); fprintf(stdout, "%s%s", (i == 0 ? "" : ","), addrbuf); len -= sizeof(struct in6_addr); } if (len > 0) { fprintf(stderr, "warning: %u trailing bytes from %s\n", (unsigned int)len, "net.inet6.ip6.mcast.filters"); } out_free: free(buf); #undef MAX_SYSCTL_TRY } static const char * inet6_n2a(struct in6_addr *p) { static char buf[NI_MAXHOST]; struct sockaddr_in6 sin6; u_int32_t scopeid; const int niflags = NI_NUMERICHOST; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_len = sizeof(struct sockaddr_in6); sin6.sin6_addr = *p; if (IN6_IS_ADDR_LINKLOCAL(p) || IN6_IS_ADDR_MC_LINKLOCAL(p) || IN6_IS_ADDR_MC_NODELOCAL(p)) { scopeid = ntohs(*(u_int16_t *)&sin6.sin6_addr.s6_addr[2]); if (scopeid) { sin6.sin6_scope_id = scopeid; sin6.sin6_addr.s6_addr[2] = 0; sin6.sin6_addr.s6_addr[3] = 0; } } if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len, buf, sizeof(buf), NULL, 0, niflags) == 0) { return (buf); } else { return ("(invalid)"); } } #endif /* INET6 */ /* * Print a value a la the %b format of the kernel's printf */ void printb(const char *s, unsigned int v, const char *bits) { int i, any = 0; char c; if (bits && *bits == 8) printf("%s=%o", s, v); else printf("%s=%x", s, v); bits++; if (bits) { putchar('<'); while ((i = *bits++) != '\0') { if (v & (1 << (i-1))) { if (any) putchar(','); any = 1; for (; (c = *bits) > 32; bits++) putchar(c); } else for (; *bits > 32; bits++) ; } putchar('>'); } } /* * convert hardware address to hex string for logging errors. */ static const char * sdl_addr_to_hex(const struct sockaddr_dl *sdl, char *orig_buf, int buflen) { char *buf = orig_buf; int i; const u_char *lladdr; int maxbytes = buflen / 3; lladdr = (u_char *)(size_t)sdl->sdl_data + sdl->sdl_nlen; if (maxbytes > sdl->sdl_alen) { maxbytes = sdl->sdl_alen; } *buf = '\0'; for (i = 0; i < maxbytes; i++) { snprintf(buf, 3, "%02x", lladdr[i]); buf += 2; *buf = (i == maxbytes - 1) ? '\0' : ':'; buf++; } return (orig_buf); }