diff options
Diffstat (limited to '3rdparty/openpgm-svn-r1135/pgm/if.c')
-rw-r--r-- | 3rdparty/openpgm-svn-r1135/pgm/if.c | 1598 |
1 files changed, 1598 insertions, 0 deletions
diff --git a/3rdparty/openpgm-svn-r1135/pgm/if.c b/3rdparty/openpgm-svn-r1135/pgm/if.c new file mode 100644 index 0000000..d149608 --- /dev/null +++ b/3rdparty/openpgm-svn-r1135/pgm/if.c @@ -0,0 +1,1598 @@ +/* vim:ts=8:sts=8:sw=4:noai:noexpandtab + * + * network interface handling. + * + * Copyright (c) 2006-2010 Miru Limited. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include <errno.h> +#include <ctype.h> +#include <stdio.h> +#ifndef _WIN32 +# include <sys/types.h> +# include <sys/socket.h> +# include <netdb.h> /* _GNU_SOURCE for EAI_NODATA */ +#endif +#include <impl/i18n.h> +#include <impl/framework.h> +#include <pgm/if.h> + + +//#define IF_DEBUG + +/* temporary structure to contain interface name whilst address family + * has not been resolved. + */ +struct interface_req { + char ir_name[IF_NAMESIZE]; + unsigned int ir_flags; /* from SIOCGIFFLAGS */ + unsigned int ir_interface; /* interface index */ + struct sockaddr_storage ir_addr; /* interface address */ +}; + + +/* locals */ + +#ifndef _WIN32 +# define IF_DEFAULT_GROUP ((in_addr_t)0xefc00001) /* 239.192.0.1 */ +#else +# define IF_DEFAULT_GROUP ((u_long)0xefc00001) +#endif + +/* ff08::1 */ +#define IF6_DEFAULT_INIT { { { 0xff,8,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } } +const struct in6_addr if6_default_group_addr = IF6_DEFAULT_INIT; + + +static inline bool is_in_net (const struct in_addr*restrict, const struct in_addr*restrict, const struct in_addr*restrict) PGM_GNUC_WARN_UNUSED_RESULT; +static inline bool is_in_net6 (const struct in6_addr*restrict, const struct in6_addr*restrict, const struct in6_addr*restrict) PGM_GNUC_WARN_UNUSED_RESULT; +static inline bool is_network_char (const int, const char) PGM_GNUC_CONST; +static const char* pgm_family_string (const int) PGM_GNUC_CONST; + + +/* recommended address space for multicast: + * rfc4607, rfc3180, rfc2365 + * + * avoid 5 high-order bit overlap. + * + * loopback: ffx1::/16 + * segment: ffx2::/16 + * glop: 238/8 + * mysterious admin: 239/8, ffx6::/16 + * site: 239.252-255/16, ffx5::/16 + * org: 239.192/14, ffx8::/16 + * + * internets: 224.0.1.0-238.255.255.255, ffxe::/16 + */ + + +/* dump all interfaces to console. + * + * note that interface indexes are only in regard to the link layer and hence + * no 1-1 mapping between adapter name to index back to address. + */ + +void +pgm_if_print_all (void) +{ + struct pgm_ifaddrs_t *ifap, *ifa; + + if (!pgm_getifaddrs (&ifap, NULL)) + return; + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) + { + const unsigned int i = NULL == ifa->ifa_addr ? 0 : pgm_if_nametoindex (ifa->ifa_addr->sa_family, ifa->ifa_name); + char rname[IF_NAMESIZE * 2 + 3]; + char b[IF_NAMESIZE * 2 + 3]; + + pgm_if_indextoname (i, rname); + sprintf (b, "%s (%s)", + ifa->ifa_name ? ifa->ifa_name : "(null)", rname); + + if (NULL == ifa->ifa_addr || + (ifa->ifa_addr->sa_family != AF_INET && + ifa->ifa_addr->sa_family != AF_INET6) ) + { + pgm_info (_("#%d name %-15.15s ---- %-46.46s scope 0 status %s loop %s b/c %s m/c %s"), + i, + b, + "", + ifa->ifa_flags & IFF_UP ? "UP " : "DOWN", + ifa->ifa_flags & IFF_LOOPBACK ? "YES" : "NO ", + ifa->ifa_flags & IFF_BROADCAST ? "YES" : "NO ", + ifa->ifa_flags & IFF_MULTICAST ? "YES" : "NO " + ); + continue; + } + + char s[INET6_ADDRSTRLEN]; + getnameinfo (ifa->ifa_addr, pgm_sockaddr_len(ifa->ifa_addr), + s, sizeof(s), + NULL, 0, + NI_NUMERICHOST); + pgm_info (_("#%d name %-15.15s IPv%i %-46.46s scope %u status %s loop %s b/c %s m/c %s"), + i, + b, + ifa->ifa_addr->sa_family == AF_INET ? 4 : 6, + s, + (unsigned)pgm_sockaddr_scope_id(ifa->ifa_addr), + ifa->ifa_flags & IFF_UP ? "UP " : "DOWN", + ifa->ifa_flags & IFF_LOOPBACK ? "YES" : "NO ", + ifa->ifa_flags & IFF_BROADCAST ? "YES" : "NO ", + ifa->ifa_flags & IFF_MULTICAST ? "YES" : "NO " + ); + } + + pgm_freeifaddrs (ifap); +} + +static inline +bool +is_in_net ( + const struct in_addr* restrict addr, /* host byte order */ + const struct in_addr* restrict netaddr, + const struct in_addr* restrict netmask + ) +{ + pgm_assert (NULL != addr); + pgm_assert (NULL != netaddr); + pgm_assert (NULL != netmask); + +#ifdef IF_DEBUG + const struct in_addr taddr = { .s_addr = htonl (addr->s_addr) }; + const struct in_addr tnetaddr = { .s_addr = htonl (netaddr->s_addr) }; + const struct in_addr tnetmask = { .s_addr = htonl (netmask->s_addr) }; + char saddr[INET_ADDRSTRLEN], snetaddr[INET_ADDRSTRLEN], snetmask[INET_ADDRSTRLEN]; + pgm_debug ("is_in_net (addr:%s netaddr:%s netmask:%s)", + pgm_inet_ntop (AF_INET, &taddr, saddr, sizeof(saddr)), + pgm_inet_ntop (AF_INET, &tnetaddr, snetaddr, sizeof(snetaddr)), + pgm_inet_ntop (AF_INET, &tnetmask, snetmask, sizeof(snetmask))); +#endif + + if ((addr->s_addr & netmask->s_addr) == (netaddr->s_addr & netmask->s_addr)) + return TRUE; + return FALSE; +} + +static +bool +is_in_net6 ( + const struct in6_addr* restrict addr, + const struct in6_addr* restrict netaddr, + const struct in6_addr* restrict netmask + ) +{ + pgm_assert (NULL != addr); + pgm_assert (NULL != netaddr); + pgm_assert (NULL != netmask); + +#ifdef IF_DEBUG + char saddr[INET6_ADDRSTRLEN], snetaddr[INET6_ADDRSTRLEN], snetmask[INET6_ADDRSTRLEN]; + pgm_debug ("is_in_net6 (addr:%s netaddr:%s netmask:%s)", + pgm_inet_ntop (AF_INET6, addr, saddr, sizeof(saddr)), + pgm_inet_ntop (AF_INET6, netaddr, snetaddr, sizeof(snetaddr)), + pgm_inet_ntop (AF_INET6, netmask, snetmask, sizeof(snetmask))); +#endif + + for (unsigned i = 0; i < 16; i++) + if ((addr->s6_addr[i] & netmask->s6_addr[i]) != (netaddr->s6_addr[i] & netmask->s6_addr[i])) + return FALSE; + return TRUE; +} + +/* parse interface entity into an interface-request structure. + * + * e.g. eth0 + * 1.2.3.4 + * 1.2 + * abcd:: + * [abcd::] + * <hostname> + * <nss network name> + * + * special addresses should be ignored: + * + * local physical link: 169.254.0.0/16, fe80::/64 + * broadcast: 255.255.255.255 + * multicast: 224.0.0.0/4 (224.0.0.0 to 239.255.255.255), ff00::/8 + * + * We could use if_nametoindex() but we might as well check that the interface is + * actually UP and capable of multicast traffic. + * + * returns TRUE on success, FALSE on error and sets error appropriately. + */ + +static +bool +parse_interface ( + int family, /* AF_UNSPEC | AF_INET | AF_INET6 */ + const char* restrict ifname, /* NULL terminated */ + struct interface_req* restrict ir, /* location to write interface details to */ + pgm_error_t** restrict error + ) +{ + bool check_inet_network = FALSE, check_inet6_network = FALSE; + bool check_addr = FALSE; + bool check_ifname = FALSE; + char literal[1024]; + struct in_addr in_addr; + struct in6_addr in6_addr; + struct pgm_ifaddrs_t *ifap, *ifa; + struct sockaddr_storage addr; + unsigned interface_matches = 0; + +/* pre-conditions */ + pgm_assert (AF_INET == family || AF_INET6 == family || AF_UNSPEC == family); + pgm_assert (NULL != ifname); + pgm_assert (NULL != ir); + + pgm_debug ("parse_interface (family:%s ifname:%s%s%s ir:%p error:%p)", + pgm_family_string (family), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : "", + (const void*)ir, + (const void*)error); + +/* strip any square brackets for IPv6 early evaluation */ + if (AF_INET != family && + '[' == ifname[0]) + { + const size_t ifnamelen = strlen(ifname); + if (']' == ifname[ ifnamelen - 1 ]) { + strncpy (literal, ifname + 1, ifnamelen - 2); + literal[ ifnamelen - 2 ] = 0; + family = AF_INET6; /* force IPv6 evaluation */ + check_inet6_network = TRUE; /* may be a network IP or CIDR block */ + check_addr = TRUE; /* cannot be not a name */ + ifname = literal; + } + } + +/* network address: in_addr in host byte order */ + if (AF_INET6 != family && 0 == pgm_inet_network (ifname, &in_addr)) + { +#ifdef IF_DEBUG + struct in_addr t = { .s_addr = htonl (in_addr.s_addr) }; + pgm_debug ("IPv4 network address: %s", inet_ntoa (t)); +#endif + if (IN_MULTICAST(in_addr.s_addr)) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_XDEV, + _("Expecting network interface address, found IPv4 multicast network %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + return FALSE; + } + struct sockaddr_in s4; + memset (&s4, 0, sizeof(s4)); + s4.sin_family = AF_INET; + s4.sin_addr.s_addr = htonl (in_addr.s_addr); + memcpy (&addr, &s4, sizeof(s4)); + + check_inet_network = TRUE; + check_addr = TRUE; + } + if (AF_INET != family && 0 == pgm_inet6_network (ifname, &in6_addr)) + { + if (IN6_IS_ADDR_MULTICAST(&in6_addr)) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_XDEV, + _("Expecting network interface address, found IPv6 multicast network %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + return FALSE; + } + struct sockaddr_in6 s6; + memset (&s6, 0, sizeof(s6)); + s6.sin6_family = AF_INET6; + s6.sin6_addr = in6_addr; + memcpy (&addr, &s6, sizeof(s6)); + + check_inet6_network = TRUE; + check_addr = TRUE; + } + +/* numeric host with scope id */ + if (!check_addr) + { + struct addrinfo hints = { + .ai_family = family, + .ai_socktype = SOCK_STREAM, /* not really, SOCK_RAW */ + .ai_protocol = IPPROTO_TCP, /* not really, IPPROTO_PGM */ + .ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST /* AI_V4MAPPED is unhelpful */ + }, *res; + const int eai = getaddrinfo (ifname, NULL, &hints, &res); + switch (eai) { + case 0: + if (AF_INET == res->ai_family && + IN_MULTICAST(ntohl (((struct sockaddr_in*)(res->ai_addr))->sin_addr.s_addr))) + { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_XDEV, + _("Expecting interface address, found IPv4 multicast address %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + freeaddrinfo (res); + return FALSE; + } + else if (AF_INET6 == res->ai_family && + IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)res->ai_addr)->sin6_addr)) + { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_XDEV, + _("Expecting interface address, found IPv6 multicast address %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + freeaddrinfo (res); + return FALSE; + } + + memcpy (&addr, res->ai_addr, pgm_sockaddr_len (res->ai_addr)); + freeaddrinfo (res); + check_addr = TRUE; + break; + +#if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME + case EAI_NODATA: +#endif + case EAI_NONAME: + break; + + default: + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + pgm_error_from_eai_errno (eai, errno), + _("Numeric host resolution: %s"), + gai_strerror (eai)); + return FALSE; + } + } + +#ifndef _WIN32 +/* network name into network address, can be expensive with NSS network lookup + * + * Only Class A, B or C networks are supported, partitioned networks + * (i.e. network/26 or network/28) are not supported by this facility. + */ + if (!(check_inet_network || check_inet6_network)) + { + const struct netent* ne = getnetbyname (ifname); +/* ne::n_net in host byte order */ + + if (ne) { + switch (ne->n_addrtype) { + case AF_INET: + if (AF_INET6 == family) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("IP address family conflict when resolving network name %s%s%s, found AF_INET when AF_INET6 expected."), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + return FALSE; + } +/* ne->n_net in network order */ + in_addr.s_addr = ne->n_net; + if (IN_MULTICAST(in_addr.s_addr)) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_XDEV, + _("Network name %s%s%s resolves to IPv4 mulicast address."), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + return FALSE; + } + check_inet_network = TRUE; + break; + case AF_INET6: +#ifndef CONFIG_HAVE_IP6_NETWORKS + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("Not configured for IPv6 network name support, %s%s%s is an IPv6 network name."), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + return FALSE; +#else + if (AF_INET == family) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("IP address family conflict when resolving network name %s%s%s, found AF_INET6 when AF_INET expected."), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + return FALSE; + } + if (IN6_IS_ADDR_MULTICAST(&ne->n_net)) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_XDEV, + _("Network name resolves to IPv6 mulicast address %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + return FALSE; + } + in6_addr = *(const struct in6_addr*)&ne->n_net; + check_inet6_network = TRUE; + break; +#endif + default: + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("Network name resolves to non-internet protocol address family %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + return FALSE; + } + } + } +#endif /* _WIN32 */ + +/* hostname lookup with potential DNS delay or error */ + if (!check_addr) + { + struct addrinfo hints = { + .ai_family = family, + .ai_socktype = SOCK_STREAM, /* not really, SOCK_RAW */ + .ai_protocol = IPPROTO_TCP, /* not really, IPPROTO_PGM */ + .ai_flags = AI_ADDRCONFIG, /* AI_V4MAPPED is unhelpful */ + }, *res; + + const int eai = getaddrinfo (ifname, NULL, &hints, &res); + switch (eai) { + case 0: + if (AF_INET == res->ai_family && + IN_MULTICAST(ntohl (((struct sockaddr_in*)(res->ai_addr))->sin_addr.s_addr))) + { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_XDEV, + _("Expecting interface address, found IPv4 multicast name %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + freeaddrinfo (res); + return FALSE; + } + else if (AF_INET6 == res->ai_family && + IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)res->ai_addr)->sin6_addr)) + { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_XDEV, + _("Expecting interface address, found IPv6 multicast name %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + freeaddrinfo (res); + return FALSE; + } + memcpy (&addr, res->ai_addr, pgm_sockaddr_len (res->ai_addr)); + freeaddrinfo (res); + check_addr = TRUE; + break; + +#if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME + case EAI_NODATA: +#endif + case EAI_NONAME: + check_ifname = TRUE; + break; + + default: + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + pgm_error_from_eai_errno (eai, errno), + _("Internet host resolution: %s(%d)"), + gai_strerror (eai), eai); + return FALSE; + } + } + +/* iterate through interface list and match device name, ip or net address */ + if (!pgm_getifaddrs (&ifap, error)) { + pgm_prefix_error (error, + _("Enumerating network interfaces: ")); + return FALSE; + } + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) + { + if (NULL == ifa->ifa_addr) + continue; + + switch (ifa->ifa_addr->sa_family) { +/* ignore raw entries on Linux */ +#ifdef AF_PACKET + case AF_PACKET: + continue; +#endif + case AF_INET: + if (AF_INET6 == family) + continue; + break; + case AF_INET6: + if (AF_INET == family) + continue; + break; + default: + continue; + } + + const unsigned ifindex = pgm_if_nametoindex (ifa->ifa_addr->sa_family, ifa->ifa_name); + pgm_assert (0 != ifindex); + +/* check numeric host */ + if (check_addr && + (0 == pgm_sockaddr_cmp (ifa->ifa_addr, (const struct sockaddr*)&addr))) + { + strcpy (ir->ir_name, ifa->ifa_name); + ir->ir_flags = ifa->ifa_flags; + if (ir->ir_flags & IFF_LOOPBACK) + pgm_warn (_("Interface %s reports as a loopback device."), ir->ir_name); + if (!(ir->ir_flags & IFF_MULTICAST)) + pgm_warn (_("Interface %s reports as a non-multicast capable device."), ir->ir_name); + ir->ir_interface = ifindex; + memcpy (&ir->ir_addr, ifa->ifa_addr, pgm_sockaddr_len (ifa->ifa_addr)); + pgm_freeifaddrs (ifap); + return TRUE; + } + +/* check network address */ + if (check_inet_network && + AF_INET == ifa->ifa_addr->sa_family) + { + const struct in_addr ifaddr = { .s_addr = ntohl (((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr) }; + const struct in_addr netmask = { .s_addr = ntohl (((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr.s_addr) }; + if (is_in_net (&ifaddr, &in_addr, &netmask)) { + strcpy (ir->ir_name, ifa->ifa_name); + ir->ir_flags = ifa->ifa_flags; + if (ir->ir_flags & IFF_LOOPBACK) { + pgm_warn (_("Skipping matching loopback network device %s."), ir->ir_name); + goto skip_inet_network; + } + if (!(ir->ir_flags & IFF_MULTICAST)) { + pgm_warn (_("Skipping matching non-multicast capable network device %s."), ir->ir_name); + goto skip_inet_network; + } + ir->ir_interface = ifindex; + memcpy (&ir->ir_addr, ifa->ifa_addr, pgm_sockaddr_len (ifa->ifa_addr)); + pgm_freeifaddrs (ifap); + return TRUE; + } + } + if (check_inet6_network && + AF_INET6 == ifa->ifa_addr->sa_family) + { + const struct in6_addr ifaddr = ((struct sockaddr_in6*)ifa->ifa_addr)->sin6_addr; + const struct in6_addr netmask = ((struct sockaddr_in6*)ifa->ifa_netmask)->sin6_addr; + if (is_in_net6 (&ifaddr, &in6_addr, &netmask)) { + strcpy (ir->ir_name, ifa->ifa_name); + ir->ir_flags = ifa->ifa_flags; + if (ir->ir_flags & IFF_LOOPBACK) { + pgm_warn (_("Skipping matching loopback network device %s."), ir->ir_name); + goto skip_inet_network; + } + if (!(ir->ir_flags & IFF_MULTICAST)) { + pgm_warn (_("Skipping matching non-multicast capable network device %s."), ir->ir_name); + goto skip_inet_network; + } + ir->ir_interface = ifindex; + memcpy (&ir->ir_addr, ifa->ifa_addr, pgm_sockaddr_len (ifa->ifa_addr)); + pgm_freeifaddrs (ifap); + return TRUE; + } + } +skip_inet_network: + +/* check interface name */ + if (check_ifname) + { + if (0 != strcmp (ifname, ifa->ifa_name)) + continue; + + ir->ir_flags = ifa->ifa_flags; +/* skip loopback and non-multicast capable devices */ + if ((ir->ir_flags & IFF_LOOPBACK) || !(ir->ir_flags & IFF_MULTICAST)) + continue; + +/* check for multiple interfaces */ + if (interface_matches++) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NOTUNIQ, + _("Network interface name not unique %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + pgm_freeifaddrs (ifap); + return FALSE; + } + + ir->ir_interface = ifindex; + strcpy (ir->ir_name, ifa->ifa_name); + memcpy (&ir->ir_addr, ifa->ifa_addr, pgm_sockaddr_len (ifa->ifa_addr)); + continue; + } + + } + + if (0 == interface_matches) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("No matching non-loopback and multicast capable network interface %s%s%s"), + ifname ? "\"" : "", ifname ? ifname : "(null)", ifname ? "\"" : ""); + pgm_freeifaddrs (ifap); + return FALSE; + } + + pgm_freeifaddrs (ifap); + return TRUE; +} + +/* parse one multicast address, conflict resolution of multiple address families of DNS multicast names is + * deferred to libc. + * + * Zone indices are ignored as interface specification is already available. + * + * reserved addresses may flag warnings: + * + * 224.0.0.0/24 for local network control + * 224.0.1/24 for internetwork control + * 169.254.255.255, ff02::1 all local nodes on segment + * ff02::2 all routers + * ff05::1 all nodes + * ff0x::fb multicast DNS + * ff0x::108 NIS + * ff05::1:3 DHCP + * + * returns TRUE on success, FALSE on error and sets error appropriately. + */ + +static +bool +parse_group ( + const int family, /* AF_UNSPEC | AF_INET | AF_INET6 */ + const char* restrict group, /* NULL terminated */ + struct sockaddr* restrict addr, /* pointer to sockaddr_storage for writing */ + pgm_error_t** restrict error + ) +{ +/* pre-conditions */ + pgm_assert (AF_INET == family || AF_INET6 == family || AF_UNSPEC == family); + pgm_assert (NULL != group); + pgm_assert (NULL != addr); + + pgm_debug ("parse_group (family:%s group:%s%s%s addr:%p error:%p)", + pgm_family_string (family), + group ? "\"" : "", group ? group : "(null)", group ? "\"" : "", + (const void*)addr, + (const void*)error); + +/* strip any square brackets for early IPv6 literal evaluation */ + if (AF_INET != family && + '[' == group[0]) + { + const size_t grouplen = strlen(group); + if (']' == group[ grouplen - 1 ]) { + char literal[1024]; + strncpy (literal, group + 1, grouplen - 2); + literal[ grouplen - 2 ] = 0; + if (pgm_inet_pton (AF_INET6, literal, &((struct sockaddr_in6*)addr)->sin6_addr) && + IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)addr)->sin6_addr)) + { + addr->sa_family = AF_INET6; + ((struct sockaddr_in6*)addr)->sin6_port = 0; + ((struct sockaddr_in6*)addr)->sin6_flowinfo = 0; + ((struct sockaddr_in6*)addr)->sin6_scope_id = 0; + return TRUE; + } + } + } + +/* IPv4 address */ + if (AF_INET6 != family && + pgm_inet_pton (AF_INET, group, &((struct sockaddr_in*)addr)->sin_addr) && + IN_MULTICAST(ntohl (((struct sockaddr_in*)addr)->sin_addr.s_addr))) + { + addr->sa_family = AF_INET; + return TRUE; + } + if (AF_INET != family && + pgm_inet_pton (AF_INET6, group, &((struct sockaddr_in6*)addr)->sin6_addr) && + IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)addr)->sin6_addr)) + { + addr->sa_family = AF_INET6; + ((struct sockaddr_in6*)addr)->sin6_port = 0; + ((struct sockaddr_in6*)addr)->sin6_flowinfo = 0; + ((struct sockaddr_in6*)addr)->sin6_scope_id = 0; + return TRUE; + } + +#ifndef _WIN32 +/* NSS network */ + const struct netent* ne = getnetbyname (group); +/* ne::n_net in host byte order */ + if (ne) { + switch (ne->n_addrtype) { + case AF_INET: + if (AF_INET6 == family) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("IP address family conflict when resolving network name %s%s%s, found IPv4 when IPv6 expected."), + group ? "\"" : "", group ? group : "(null)", group ? "\"" : ""); + return FALSE; + } + if (IN_MULTICAST(ne->n_net)) { + addr->sa_family = AF_INET; + ((struct sockaddr_in*)addr)->sin_addr.s_addr = htonl (ne->n_net); + return TRUE; + } + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("IP address class conflict when resolving network name %s%s%s, expected IPv4 multicast."), + group ? "\"" : "", group ? group : "(null)", group ? "\"" : ""); + return FALSE; + case AF_INET6: +#ifndef CONFIG_HAVE_IP6_NETWORKS + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("Not configured for IPv6 network name support, %s%s%s is an IPv6 network name."), + group ? "\"" : "", group ? group : "(null)", group ? "\"" : ""); + return FALSE; +#else + if (AF_INET == family) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("IP address family conflict when resolving network name %s%s%s, found IPv6 when IPv4 expected."), + group ? "\"" : "", group ? group : "(null)", group ? "\"" : ""); + return FALSE; + } + if (IN6_IS_ADDR_MULTICAST(&ne->n_net)) { + addr->sa_family = AF_INET6; + ((struct sockaddr_in6*)addr)->sin6_addr = *(const struct in6_addr*)ne->n_net; + ((struct sockaddr_in6*)&addr)->sin6_port = 0; + ((struct sockaddr_in6*)&addr)->sin6_flowinfo = 0; + ((struct sockaddr_in6*)&addr)->sin6_scope_id = 0; + return TRUE; + } + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("IP address class conflict when resolving network name %s%s%s, expected IPv6 multicast."), + group ? "\"" : "", group ? group : "(null)", group ? "\"" : ""); + return FALSE; +#endif /* CONFIG_HAVE_IP6_NETWORKS */ + default: + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_NODEV, + _("Network name resolves to non-internet protocol address family %s%s%s"), + group ? "\"" : "", group ? group : "(null)", group ? "\"" : ""); + return FALSE; + } + } +#endif /* _WIN32 */ + +/* lookup group through name service */ + struct addrinfo hints = { + .ai_family = family, + .ai_socktype = SOCK_STREAM, /* not really, SOCK_RAW */ + .ai_protocol = IPPROTO_TCP, /* not really, IPPROTO_PGM */ + .ai_flags = AI_ADDRCONFIG, /* AI_V4MAPPED is unhelpful */ + }, *res; + + const int eai = getaddrinfo (group, NULL, &hints, &res); + if (0 != eai) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + pgm_error_from_eai_errno (eai, errno), + _("Resolving receive group: %s"), + gai_strerror (eai)); + return FALSE; + } + + if ((AF_INET6 != family && IN_MULTICAST(ntohl (((struct sockaddr_in*)res->ai_addr)->sin_addr.s_addr))) || + (AF_INET != family && IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)res->ai_addr)->sin6_addr))) + { + memcpy (addr, res->ai_addr, res->ai_addrlen); + freeaddrinfo (res); + return TRUE; + } + + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_INVAL, + _("Unresolvable receive group %s%s%s"), + group ? "\"" : "", group ? group : "(null)", group ? "\"" : ""); + freeaddrinfo (res); + return FALSE; +} + +/* parse an interface entity from a network parameter. + * + * family can be unspecified - AF_UNSPEC, can return interfaces with the unspecified + * address family + * + * examples: "eth0" + * "hme0,hme1" + * "qe0,qe1,qe2" + * "qe0,qe2,qe2" => valid even though duplicate interface name + * + * returns TRUE on success with device_list containing double linked list of devices as + * sockaddr/idx pairs. returns FALSE on error, including multiple matching adapters. + * + * memory ownership of linked list is passed to caller and must be freed with pgm_free + * and the pgm_list_free* api. + */ + +static +bool +parse_interface_entity ( + int family, /* AF_UNSPEC | AF_INET | AF_INET6 */ + const char* restrict entity, /* NULL terminated */ + pgm_list_t** restrict interface_list, /* <struct interface_req*> */ + pgm_error_t** restrict error + ) +{ + struct interface_req* ir; + pgm_list_t* source_list = NULL; + +/* pre-conditions */ + pgm_assert (AF_INET == family || AF_INET6 == family || AF_UNSPEC == family); + pgm_assert (NULL != interface_list); + pgm_assert (NULL == *interface_list); + pgm_assert (NULL != error); + + pgm_debug ("parse_interface_entity (family:%s entity:%s%s%s interface_list:%p error:%p)", + pgm_family_string (family), + entity ? "\"":"", entity ? entity : "(null)", entity ? "\"":"", + (const void*)interface_list, + (const void*)error); + +/* the empty entity, returns in_addr_any for both receive and send interfaces */ + if (NULL == entity) + { + ir = pgm_new0 (struct interface_req, 1); + ir->ir_addr.ss_family = family; + *interface_list = pgm_list_append (*interface_list, ir); + return TRUE; + } + +/* check interface name length limit */ + char** tokens = pgm_strsplit (entity, ",", 10); + int j = 0; + while (tokens && tokens[j]) + { + pgm_error_t* sub_error = NULL; + ir = pgm_new (struct interface_req, 1); + if (!parse_interface (family, tokens[j], ir, &sub_error)) + { +/* mark multiple interfaces for later decision based on group families */ + if (sub_error && PGM_ERROR_NOTUNIQ == sub_error->code) + { + ir->ir_addr.ss_family = AF_UNSPEC; + pgm_error_free (sub_error); + } +/* bail out on first interface with an error */ + else + { + pgm_propagate_error (error, sub_error); + pgm_free (ir); + pgm_strfreev (tokens); + while (source_list) { + pgm_free (source_list->data); + source_list = pgm_list_delete_link (source_list, source_list); + } + return FALSE; + } + } + + source_list = pgm_list_append (source_list, ir); + ++j; + } + + pgm_strfreev (tokens); + *interface_list = source_list; + return TRUE; +} + +/* parse a receive multicast group entity. can contain more than one multicast group to + * support asymmetric fan-out. + * + * if group is ambiguous, i.e. empty or a name mapping then the address family of the matching + * interface is queried. if the interface is also ambiguous, i.e. empty interface and receive group + * then the hostname will be used to determine the default node address family. if the hosts + * node name resolves both IPv4 and IPv6 address families then the first matching value is taken. + * + * e.g. "239.192.0.1" + * "239.192.0.100,239.192.0.101" + * + * unspecified address family interfaces are forced to AF_INET or AF_INET6. + * + * returns TRUE on success, returns FALSE on error and sets error appropriately. + */ + +static +bool +parse_receive_entity ( + int family, /* AF_UNSPEC | AF_INET | AF_INET6 */ + const char* restrict entity, /* NULL terminated */ + pgm_list_t** restrict interface_list, /* <struct interface_req*> */ + pgm_list_t** restrict recv_list, /* <struct group_source_req*> */ + pgm_error_t** restrict error + ) +{ +/* pre-conditions */ + pgm_assert (AF_INET == family || AF_INET6 == family || AF_UNSPEC == family); + pgm_assert (NULL != recv_list); + pgm_assert (NULL == *recv_list); + pgm_assert (NULL != error); + + pgm_debug ("parse_receive_entity (family:%s entity:%s%s%s interface_list:%p recv_list:%p error:%p)", + pgm_family_string (family), + entity ? "\"":"", entity ? entity : "(null)", entity ? "\"":"", + (const void*)interface_list, + (const void*)recv_list, + (const void*)error); + + struct group_source_req* recv_gsr; + struct interface_req* primary_interface = (struct interface_req*)pgm_memdup ((*interface_list)->data, sizeof(struct interface_req)); + +/* the empty entity */ + if (NULL == entity) + { +/* default receive object */ + recv_gsr = pgm_new0 (struct group_source_req, 1); + recv_gsr->gsr_interface = primary_interface->ir_interface; + recv_gsr->gsr_group.ss_family = family; + +/* track IPv6 scope from any resolved interface */ + unsigned scope_id = 0; + +/* if using unspec default group check the interface for address family + */ + if (AF_UNSPEC == recv_gsr->gsr_group.ss_family) + { + if (AF_UNSPEC == primary_interface->ir_addr.ss_family) + { + struct sockaddr_storage addr; + if (!pgm_if_getnodeaddr (AF_UNSPEC, (struct sockaddr*)&addr, sizeof(addr), error)) + { + pgm_prefix_error (error, + _("Node primary address family cannot be determined: ")); + pgm_free (recv_gsr); + pgm_free (primary_interface); + return FALSE; + } + recv_gsr->gsr_group.ss_family = addr.ss_family; + scope_id = pgm_sockaddr_scope_id ((struct sockaddr*)&addr); + +/* was an interface actually specified */ + if (primary_interface->ir_name[0] != '\0') + { + struct interface_req ir; + if (!parse_interface (recv_gsr->gsr_group.ss_family, primary_interface->ir_name, &ir, error)) + { + pgm_prefix_error (error, + _("Unique address cannot be determined for interface %s%s%s: "), + primary_interface->ir_name ? "\"" : "", primary_interface->ir_name ? primary_interface->ir_name : "(null)", primary_interface->ir_name ? "\"" : ""); + pgm_free (recv_gsr); + pgm_free (primary_interface); + return FALSE; + } + + recv_gsr->gsr_interface = ir.ir_interface; + memcpy (&primary_interface->ir_addr, &ir.ir_addr, pgm_sockaddr_len ((struct sockaddr*)&ir.ir_addr)); + scope_id = pgm_sockaddr_scope_id ((struct sockaddr*)&ir.ir_addr); + } + } + else + { +/* use interface address family for multicast group */ + recv_gsr->gsr_group.ss_family = primary_interface->ir_addr.ss_family; + scope_id = pgm_sockaddr_scope_id ((struct sockaddr*)&primary_interface->ir_addr); + } + } + + + pgm_assert (AF_UNSPEC != recv_gsr->gsr_group.ss_family); + if (AF_UNSPEC != primary_interface->ir_addr.ss_family) + { + pgm_assert (recv_gsr->gsr_group.ss_family == primary_interface->ir_addr.ss_family); + } + else + { +/* check if we can now resolve the interface by address family of the receive group */ + if (primary_interface->ir_name[0] != '\0') + { + struct interface_req ir; + if (!parse_interface (recv_gsr->gsr_group.ss_family, primary_interface->ir_name, &ir, error)) + { + pgm_prefix_error (error, + _("Unique address cannot be determined for interface %s%s%s: "), + primary_interface->ir_name ? "\"" : "", primary_interface->ir_name ? primary_interface->ir_name : "(null)", primary_interface->ir_name ? "\"" : ""); + pgm_free (recv_gsr); + pgm_free (primary_interface); + return FALSE; + } + + recv_gsr->gsr_interface = ir.ir_interface; + scope_id = pgm_sockaddr_scope_id ((struct sockaddr*)&ir.ir_addr); + } + } + +/* copy default PGM multicast group */ + switch (recv_gsr->gsr_group.ss_family) { + case AF_INET6: + memcpy (&((struct sockaddr_in6*)&recv_gsr->gsr_group)->sin6_addr, + &if6_default_group_addr, + sizeof(if6_default_group_addr)); + ((struct sockaddr_in6*)&recv_gsr->gsr_group)->sin6_scope_id = scope_id; + break; + + case AF_INET: + ((struct sockaddr_in*)&recv_gsr->gsr_group)->sin_addr.s_addr = htonl(IF_DEFAULT_GROUP); + break; + + default: + pgm_assert_not_reached(); + } + +/* ASM: source = group */ + memcpy (&recv_gsr->gsr_source, &recv_gsr->gsr_group, pgm_sockaddr_len ((struct sockaddr*)&recv_gsr->gsr_group)); + *recv_list = pgm_list_append (*recv_list, recv_gsr); + pgm_free (primary_interface); + return TRUE; + } + +/* parse one or more multicast receive groups. + */ + + int j = 0; + char** tokens = pgm_strsplit (entity, ",", 10); + while (tokens && tokens[j]) + { +/* default receive object */ + recv_gsr = pgm_new0 (struct group_source_req, 1); + recv_gsr->gsr_interface = primary_interface->ir_interface; + recv_gsr->gsr_group.ss_family = family; + + if (AF_UNSPEC == recv_gsr->gsr_group.ss_family) + { + if (AF_UNSPEC == primary_interface->ir_addr.ss_family) + { + pgm_debug ("Address family of receive group cannot be determined from interface."); + } + else + { + recv_gsr->gsr_group.ss_family = primary_interface->ir_addr.ss_family; + ((struct sockaddr_in6*)&recv_gsr->gsr_group)->sin6_scope_id = pgm_sockaddr_scope_id ((struct sockaddr*)&primary_interface->ir_addr); + } + } + + if (!parse_group (recv_gsr->gsr_group.ss_family, tokens[j], (struct sockaddr*)&recv_gsr->gsr_group, error)) + { + pgm_prefix_error (error, + _("Unresolvable receive entity %s%s%s: "), + tokens[j] ? "\"" : "", tokens[j] ? tokens[j] : "(null)", tokens[j] ? "\"" : ""); + pgm_free (recv_gsr); + pgm_strfreev (tokens); + pgm_free (primary_interface); + return FALSE; + } + +/* check if we can now resolve the source interface by address family of the receive group */ + if (AF_UNSPEC == primary_interface->ir_addr.ss_family) + { + if (primary_interface->ir_name[0] != '\0') + { + struct interface_req ir; + if (!parse_interface (recv_gsr->gsr_group.ss_family, primary_interface->ir_name, &ir, error)) + { + pgm_prefix_error (error, + _("Unique address cannot be determined for interface %s%s%s: "), + primary_interface->ir_name ? "\"" : "", primary_interface->ir_name ? primary_interface->ir_name : "(null)", primary_interface->ir_name ? "\"" : ""); + pgm_free (recv_gsr); + pgm_free (primary_interface); + return FALSE; + } + + recv_gsr->gsr_interface = ir.ir_interface; + ((struct sockaddr_in6*)&recv_gsr->gsr_group)->sin6_scope_id = pgm_sockaddr_scope_id ((struct sockaddr*)&ir.ir_addr); + } + } + else + { +/* keep interface scope */ + ((struct sockaddr_in6*)&recv_gsr->gsr_group)->sin6_scope_id = pgm_sockaddr_scope_id ((struct sockaddr*)&primary_interface->ir_addr); + } + +/* ASM: source = group */ + memcpy (&recv_gsr->gsr_source, &recv_gsr->gsr_group, pgm_sockaddr_len ((struct sockaddr*)&recv_gsr->gsr_group)); + *recv_list = pgm_list_append (*recv_list, recv_gsr); + ++j; + } + + pgm_strfreev (tokens); + pgm_free (primary_interface); + return TRUE; +} + +static +bool +parse_send_entity ( + int family, /* AF_UNSPEC | AF_INET | AF_INET6 */ + const char* restrict entity, /* null = empty entity */ + pgm_list_t** restrict interface_list, /* <struct interface_req*> */ + pgm_list_t** restrict recv_list, /* <struct group_source_req*> */ + pgm_list_t** restrict send_list, /* <struct group_source_req*> */ + pgm_error_t** restrict error + ) +{ +/* pre-conditions */ + pgm_assert (AF_INET == family || AF_INET6 == family || AF_UNSPEC == family); + pgm_assert (NULL != recv_list); + pgm_assert (NULL != *recv_list); + pgm_assert (NULL != send_list); + pgm_assert (NULL == *send_list); + pgm_assert (NULL != error); + + pgm_debug ("parse_send_entity (family:%s entity:%s%s%s interface_list:%p recv_list:%p send_list:%p error:%p)", + pgm_family_string (family), + entity ? "\"":"", entity ? entity : "(null)", entity ? "\"":"", + (const void*)interface_list, + (const void*)recv_list, + (const void*)send_list, + (const void*)error); + + struct group_source_req* send_gsr; + const struct interface_req* primary_interface = (struct interface_req*)(*interface_list)->data; + + if (entity == NULL) + { + send_gsr = pgm_memdup ((*recv_list)->data, sizeof(struct group_source_req)); + *send_list = pgm_list_append (*send_list, send_gsr); + return TRUE; + } + +/* default send object */ + send_gsr = pgm_new0 (struct group_source_req, 1); + send_gsr->gsr_interface = primary_interface->ir_interface; + if (!parse_group (family, entity, (struct sockaddr*)&send_gsr->gsr_group, error)) + { + pgm_prefix_error (error, + _("Unresolvable send entity %s%s%s: "), + entity ? "\"":"", entity ? entity : "(null)", entity ? "\"":""); + pgm_free (send_gsr); + return FALSE; + } + +/* check if we can now resolve the source interface by address family of the send group */ + if (AF_UNSPEC == primary_interface->ir_addr.ss_family) + { + if (primary_interface->ir_name[0] != '\0') + { + struct interface_req ir; + if (!parse_interface (send_gsr->gsr_group.ss_family, primary_interface->ir_name, &ir, error)) + { + pgm_prefix_error (error, + _("Unique address cannot be determined for interface %s%s%s: "), + primary_interface->ir_name ? "\"":"", primary_interface->ir_name ? primary_interface->ir_name : "(null)", primary_interface->ir_name ? "\"":""); + pgm_free (send_gsr); + return FALSE; + } + + send_gsr->gsr_interface = ir.ir_interface; + ((struct sockaddr_in6*)&send_gsr->gsr_group)->sin6_scope_id = pgm_sockaddr_scope_id ((struct sockaddr*)&ir.ir_addr); + } + } + +/* ASM: source = group */ + memcpy (&send_gsr->gsr_source, &send_gsr->gsr_group, pgm_sockaddr_len ((struct sockaddr*)&send_gsr->gsr_group)); + *send_list = pgm_list_append (*send_list, send_gsr); + return TRUE; +} + +/* parse network parameter + * + * interface list; receive multicast group list; send multicast group + */ + +#define IS_HOSTNAME(x) ( /* RFC 952 */ \ + isalnum(x) || \ + ((x) == '-') || \ + ((x) == '.') \ + ) +#define IS_IP(x) ( \ + isdigit(x) || \ + ((x) == '.') || \ + ((x) == '/') \ + ) +#define IS_IP6(x) ( \ + isxdigit(x) || \ + ((x) == ':') || \ + ((x) == '/') || \ + ((x) == '.') || \ + ((x) == '[') || \ + ((x) == ']') \ + ) +/* e.g. fe80::1%eth0.620 vlan tag, + * fe80::1%eth0:0 IP alias + * fe80::1%qe0_0 Solaris link name + * + * The Linux kernel generally doesn't care too much, but everything else falls apart with + * random characters in interface names. Hyphen is a popular problematic character. + */ +#define IS_IP6_WITH_ZONE(x) ( \ + IS_IP6(x) || \ + ((x) == '%') || \ + isalpha(x) || \ + ((x) == '_') \ + ) +#define IS_NETPARAM(x) ( \ + ((x) == ',') || \ + ((x) == ';') \ + ) + +static inline +bool +is_network_char ( + const int family, + const char c + ) +{ + if (IS_HOSTNAME(c) || + (AF_INET == family && IS_IP(c)) || + ((AF_INET6 == family || AF_UNSPEC == family) && IS_IP6_WITH_ZONE(c)) || + IS_NETPARAM(c)) + return TRUE; + else + return FALSE; +} + +static +bool +network_parse ( + const char* restrict network, /* NULL terminated */ + int family, /* AF_UNSPEC | AF_INET | AF_INET6 */ + pgm_list_t** restrict recv_list, /* <struct group_source_req*> */ + pgm_list_t** restrict send_list, /* <struct group_source_req*> */ + pgm_error_t** restrict error + ) +{ + bool retval = FALSE; + const char *p = network; + const char *e = p + strlen(network); + enum { ENTITY_INTERFACE, ENTITY_RECEIVE, ENTITY_SEND, ENTITY_ERROR } ec = ENTITY_INTERFACE; + const char *b = p; /* begin of entity */ + pgm_list_t* source_list = NULL; + pgm_error_t* sub_error = NULL; + +/* pre-conditions */ + pgm_assert (NULL != network); + pgm_assert (AF_UNSPEC == family || AF_INET == family || AF_INET6 == family); + pgm_assert (NULL != recv_list); + pgm_assert (NULL != send_list); + + pgm_debug ("network_parse (network:%s%s%s family:%s recv_list:%p send_list:%p error:%p)", + network ? "\"" : "", network ? network : "(null)", network ? "\"" : "", + pgm_family_string (family), + (const void*)recv_list, + (const void*)send_list, + (const void*)error); + + while (p < e) + { + if (!is_network_char (family, *p)) + { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_INVAL, + _("'%c' is not a valid character."), + *p); + goto free_lists; + } + + if (*p == ';') /* end of entity */ + { + if (b == p) /* empty entity */ + { + switch (ec++) { + case ENTITY_INTERFACE: + retval = parse_interface_entity (family, NULL, &source_list, error); + break; + + case ENTITY_RECEIVE: + retval = parse_receive_entity (family, NULL, &source_list, recv_list, error); + break; + + case ENTITY_SEND: + retval = parse_send_entity (family, NULL, &source_list, recv_list, send_list, error); + break; + + default: + pgm_assert_not_reached(); + break; + } + + if (!retval) + goto free_lists; + + b = ++p; + continue; + } + +/* entity from b to p-1 */ + char entity[1024]; + strncpy (entity, b, sizeof(entity)); + entity[p - b] = 0; + + switch (ec++) { + case ENTITY_INTERFACE: + if (parse_interface_entity (family, entity, &source_list, &sub_error)) + break; + if (!(sub_error && PGM_ERROR_XDEV == sub_error->code)) + { +/* fall through on multicast */ + if (!(sub_error && PGM_ERROR_NOTUNIQ == sub_error->code)) + { + pgm_propagate_error (error, sub_error); + goto free_lists; + } + pgm_clear_error (&sub_error); +/* FIXME: too many interfaces */ + if (pgm_list_length (source_list) > 1) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_INVAL, + _("Send group list contains more than one entity.")); + goto free_lists; + } + break; + } + pgm_clear_error (&sub_error); + while (source_list) { + pgm_free (source_list->data); + source_list = pgm_list_delete_link (source_list, source_list); + } + if (!parse_interface_entity (family, NULL, &source_list, &sub_error) && + !(sub_error && PGM_ERROR_NOTUNIQ == sub_error->code)) + { + pgm_propagate_error (error, sub_error); + goto free_lists; + } + pgm_clear_error (&sub_error); + ec++; + + case ENTITY_RECEIVE: + if (!parse_receive_entity (family, entity, &source_list, recv_list, error)) + goto free_lists; + break; + + case ENTITY_SEND: + if (!parse_send_entity (family, entity, &source_list, recv_list, send_list, error)) + goto free_lists; + break; + + default: + pgm_assert_not_reached(); + break; + } + + b = ++p; + continue; + } + + p++; + } + + if (b < e) { + switch (ec++) { + case ENTITY_INTERFACE: + if (parse_interface_entity (family, b, &source_list, &sub_error)) + break; + if (!(sub_error && PGM_ERROR_XDEV == sub_error->code)) + { +/* fall through on multicast */ + if (!(sub_error && PGM_ERROR_NOTUNIQ == sub_error->code)) + { + pgm_propagate_error (error, sub_error); + goto free_lists; + } + pgm_clear_error (&sub_error); + +/* FIXME: too many interfaces */ + if (pgm_list_length (source_list) > 1) { + pgm_set_error (error, + PGM_ERROR_DOMAIN_IF, + PGM_ERROR_INVAL, + _("Send group list contains more than one entity.")); + goto free_lists; + } + break; + } + pgm_clear_error (&sub_error); + while (source_list) { + pgm_free (source_list->data); + source_list = pgm_list_delete_link (source_list, source_list); + } + if (!parse_interface_entity (family, NULL, &source_list, &sub_error) && + !(sub_error && PGM_ERROR_NOTUNIQ == sub_error->code)) + { + pgm_propagate_error (error, sub_error); + goto free_lists; + } + ec++; + + case ENTITY_RECEIVE: + if (!parse_receive_entity (family, b, &source_list, recv_list, error)) + goto free_lists; + break; + + case ENTITY_SEND: + if (!parse_send_entity (family, b, &source_list, recv_list, send_list, error)) + goto free_lists; + break; + + default: + pgm_assert_not_reached(); + break; + } + } + + while (ec <= ENTITY_SEND) + { + switch (ec++) { + case ENTITY_INTERFACE: + if (!parse_interface_entity (family, NULL, &source_list, error)) + goto free_lists; + break; + + case ENTITY_RECEIVE: + if (!parse_receive_entity (family, NULL, &source_list, recv_list, error)) + goto free_lists; + break; + + case ENTITY_SEND: + if (!parse_send_entity (family, NULL, &source_list, recv_list, send_list, error)) + goto free_lists; + break; + + default: + pgm_assert_not_reached(); + break; + } + } + + if (pgm_list_length (source_list) > 1) + goto free_lists; + +/* cleanup source interface list */ + while (source_list) { + pgm_free (source_list->data); + source_list = pgm_list_delete_link (source_list, source_list); + } + + return TRUE; + +free_lists: + while (source_list) { + pgm_free (source_list->data); + source_list = pgm_list_delete_link (source_list, source_list); + } + while (*recv_list) { + pgm_free ((*recv_list)->data); + *recv_list = pgm_list_delete_link (*recv_list, *recv_list); + } + while (*send_list) { + pgm_free ((*send_list)->data); + *send_list = pgm_list_delete_link (*send_list, *send_list); + } + return FALSE; +} + +/* create group_source_req as used by pgm_transport_create which specify port, address & interface. + * gsr_source is copied from gsr_group for ASM, caller needs to populate gsr_source for SSM. + * + * returns TRUE on success, returns FALSE on error and sets error appropriately. + */ + +bool +pgm_getaddrinfo ( + const char* restrict network, + const struct pgm_addrinfo_t* const restrict hints, + struct pgm_addrinfo_t** restrict res, + pgm_error_t** restrict error + ) +{ + struct pgm_addrinfo_t* ai; + const int family = hints ? hints->ai_family : AF_UNSPEC; + pgm_list_t* recv_list = NULL; /* <struct group_source_req*> */ + pgm_list_t* send_list = NULL; /* <struct group_source_req*> */ + + pgm_return_val_if_fail (NULL != network, FALSE); + pgm_return_val_if_fail (AF_UNSPEC == family || AF_INET == family || AF_INET6 == family, FALSE); + pgm_return_val_if_fail (NULL != res, FALSE); + + if (hints) { + pgm_debug ("pgm_getaddrinfo (network:%s%s%s hints: {family:%s} res:%p error:%p)", + network ? "\"" : "", network ? network : "(null)", network ? "\"" : "", + pgm_family_string (family), + (const void*)res, + (const void*)error); + } else { + pgm_debug ("pgm_getaddrinfo (network:%s%s%s hints:%p res:%p error:%p)", + network ? "\"" : "", network ? network : "(null)", network ? "\"" : "", + (const void*)hints, + (const void*)res, + (const void*)error); + } + + if (!network_parse (network, family, &recv_list, &send_list, error)) + return FALSE; + const size_t recv_list_len = pgm_list_length (recv_list); + const size_t send_list_len = pgm_list_length (send_list); + ai = pgm_malloc0 (sizeof(struct pgm_addrinfo_t) + + (recv_list_len + send_list_len) * sizeof(struct group_source_req)); + ai->ai_recv_addrs_len = recv_list_len; + ai->ai_recv_addrs = (void*)((char*)ai + sizeof(struct pgm_addrinfo_t)); + ai->ai_send_addrs_len = send_list_len; + ai->ai_send_addrs = (void*)((char*)ai->ai_recv_addrs + recv_list_len * sizeof(struct group_source_req)); + + size_t i = 0; + while (recv_list) { + memcpy (&ai->ai_recv_addrs[i++], recv_list->data, sizeof(struct group_source_req)); + pgm_free (recv_list->data); + recv_list = pgm_list_delete_link (recv_list, recv_list); + } + i = 0; + while (send_list) { + memcpy (&ai->ai_send_addrs[i++], send_list->data, sizeof(struct group_source_req)); + pgm_free (send_list->data); + send_list = pgm_list_delete_link (send_list, send_list); + } + *res = ai; + return TRUE; +} + +void +pgm_freeaddrinfo ( + struct pgm_addrinfo_t* res + ) +{ + pgm_free (res); +} + + +static +const char* +pgm_family_string ( + const int family + ) +{ + const char* c; + + switch (family) { + case AF_UNSPEC: c = "AF_UNSPEC"; break; + case AF_INET: c = "AF_INET"; break; + case AF_INET6: c = "AF_INET6"; break; + default: c = "(unknown)"; break; + } + + return c; +} + +/* eof */ |