From: Gerrit Renker Date: Mon, 2 Feb 2009 19:58:49 +0000 (+0100) Subject: Support for UDPv4/v6 multicast streaming X-Git-Tag: v0.3.4~62 X-Git-Url: http://git.tue.mpg.de/?a=commitdiff_plain;h=82ec559332572814f76a8db3ed6751792687570d;p=paraslash.git Support for UDPv4/v6 multicast streaming This provides self-contained support for UDPv4/v6 multicast: * receiver joins multicast v4/v6 address; * sender, on encountering a multicast address, sets TTL/IPv6 number of multicast hops, and (as a precaution) enables looping back of multicast packets. The use of the sender is still restricted to UDPv4, since it is also necessary to perform changes to the (IPv4-based) udp_target struct, which should be done in a separate patch. --- diff --git a/ggo/server.m4 b/ggo/server.m4 index 30c3b41a..65da7436 100644 --- a/ggo/server.m4 +++ b/ggo/server.m4 @@ -292,11 +292,19 @@ option "udp_ttl" t #~~~~~~~~~~~~~~~~~ "set time to live value" int typestr="num" -default="10" +default="-1" optional details=" - This option instructs the udp sender to set the time to live to - \"num\" for the sending udp socket. Only useful for multicast - udp streaming. + This option applies exclusively to multicast UDPv4/v6 streaming. + + For the sending UDPv4 socket it sets the multicast Time-To-Live + value to \"num\". Traditional TTL scope values are: 0=host, + 1=network, 32=same site, 64=same region, 128=same continent, + 255=unrestricted. Please note however that this scoping is not + a good solution: RFC 2365 e.g. presents a better alternative. + + When using UDPv6 multicasting, the option sets the number of + multicast hops (as described in RFC 3493); a value of -1 + allows the kernel to auto-select the hop value. " diff --git a/net.c b/net.c index 312a6854..b510dc18 100644 --- a/net.c +++ b/net.c @@ -755,142 +755,3 @@ out: free(buf); return ret; } - -static int resolve(const char *hostname, unsigned short port, - struct sockaddr_in *addr) -{ - struct hostent *host; - - assert(hostname); - host = gethostbyname(hostname); - if (!host) - return -ERRNO_TO_PARA_ERROR(h_errno); - if (addr) { - memcpy(&addr->sin_addr, host->h_addr_list[0], host->h_length); - addr->sin_port = port; - } - return 1; -} - -/* - * Create an UDP socket. - * - * If the given address is a multicast adress, the socket will be set - * to use the multicast TTL ttl and sets the datagrams to loop back. - * - * \return The fd of the socket on success, negative on errors. - */ -static int create_udp_socket(struct sockaddr_in *addr, - unsigned short port, unsigned char ttl) -{ - int ret, fd, yes = 1; - - assert(addr); - ret = socket(PF_INET, SOCK_DGRAM, 0); - if (ret < 0) - return -ERRNO_TO_PARA_ERROR(errno); - fd = ret; - /* reuse addresses */ - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - if (ret < 0) { - ret = -ERRNO_TO_PARA_ERROR(errno); - goto err; - } - addr->sin_family = AF_INET; - addr->sin_port = htons(port); - /* set the TTL and turn on multicast loop */ - if (IN_MULTICAST(htonl(addr->sin_addr.s_addr))) { - unsigned char loop = 1; - ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, - sizeof(ttl)); - if (ret < 0) { - ret = -ERRNO_TO_PARA_ERROR(errno); - goto err; - } - ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, - sizeof(loop)); - if (ret < 0) { - ret = -ERRNO_TO_PARA_ERROR(errno); - goto err; - } - } - return fd; -err: - close(fd); - return ret; -} - -/** - * Create and connect a sending UDP socket. - * - * \param hostname Where to send to (name or IPv4 address). - * \param port The udp port to use. - * \param ttl Time to live (only relevant for multicast). - * - * \return The fd of the socket on success, negative on error. - */ -int create_udp_send_socket(char *hostname, unsigned short port, - unsigned char ttl) -{ - struct sockaddr_in addr; - int fd, ret = resolve(hostname, port, &addr); - - if (ret < 0) - return ret; - ret = create_udp_socket(&addr, port, ttl); - if (ret < 0) - return ret; - fd = ret; - ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); - if (ret >= 0) - return fd; - ret = -ERRNO_TO_PARA_ERROR(errno); - close(fd); - return ret; -} - -/** - * Create and bind a receiving UDP socket. - * - * Bind the created UDP socket to \a hostname, and add multicast membership if - * hostname is a multicast hostname. - * - * \param hostname Name or IPv4 address to receive from. - * \param port The udp port. - * - * \return The fd of the socket on success, negative on errors. - */ -int create_udp_recv_socket(char *hostname, unsigned short port) -{ - struct sockaddr_in addr; - int fd, ret = resolve(hostname, port, &addr); - - if (ret < 0) - memset(&addr.sin_addr, 0, sizeof(addr.sin_addr)); - ret = create_udp_socket(&addr, port, 1); - if (ret < 0) - return ret; - fd = ret; - ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); - if (ret < 0) { - ret = -ERRNO_TO_PARA_ERROR(errno); - goto err; - } - /* Add multicast membership */ - if (IN_MULTICAST(htonl(addr.sin_addr.s_addr))) { - struct ip_mreq mreq; - - mreq.imr_multiaddr.s_addr = addr.sin_addr.s_addr; - mreq.imr_interface.s_addr = htonl(INADDR_ANY); - ret = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, - &mreq, sizeof(mreq)); - if (ret < 0) { - ret = -ERRNO_TO_PARA_ERROR(errno); - goto err; - } - } - return fd; -err: - close(fd); - return ret; -} diff --git a/net.h b/net.h index 7cfee2eb..3c6628c2 100644 --- a/net.h +++ b/net.h @@ -61,6 +61,3 @@ int recv_pattern(int fd, const char *pattern, size_t bufsize); void enable_crypt(int fd, crypt_function *recv_f, crypt_function *send_f, void *private_data); void disable_crypt(int fd); -int create_udp_recv_socket(char *hostname, unsigned short port); -int create_udp_send_socket(char *hostname, unsigned short port, - unsigned char ttl); diff --git a/udp_recv.c b/udp_recv.c index bdc25411..ccd769b0 100644 --- a/udp_recv.c +++ b/udp_recv.c @@ -217,6 +217,50 @@ static void *udp_recv_parse_config(int argc, char **argv) return NULL; } +/* + * Perform AF-independent joining of multicast receive addresses. + * + * \param fd Bound socket descriptor. + * + * \return Zero if okay, negative on error. + */ +static int mcast_receiver_setup(int fd) +{ + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + + if (getsockname(fd, (struct sockaddr *)&ss, &sslen) < 0) + goto err; + + switch (ss.ss_family) { + case AF_INET: + if (IN_MULTICAST(htonl(((struct sockaddr_in *)&ss)->sin_addr.s_addr))) { + struct ip_mreq m4; + + memset(&m4, 0, sizeof(m4)); + m4.imr_multiaddr = ((struct sockaddr_in *)&ss)->sin_addr; + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &m4, sizeof(m4)) < 0) + break; + } + return 0; + case AF_INET6: + if (IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)&ss)->sin6_addr)) { + struct ipv6_mreq m6; + + memset(&m6, 0, sizeof(m6)); + memcpy(&m6.ipv6mr_multiaddr, &((struct sockaddr_in6 *)&ss)->sin6_addr, 16); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &m6, sizeof(m6)) < 0) + break; + } + return 0; + default: + PARA_ERROR_LOG("address family %d not supported", ss.ss_family); + return -E_ADDRESS_LOOKUP; + } +err: + return -ERRNO_TO_PARA_ERROR(errno); +} + static int udp_recv_open(struct receiver_node *rn) { struct private_udp_recv_data *purd; @@ -226,10 +270,18 @@ static int udp_recv_open(struct receiver_node *rn) rn->buf = para_calloc(UDP_RECV_CHUNK_SIZE); rn->private_data = para_calloc(sizeof(struct private_udp_recv_data)); purd = rn->private_data; - ret = create_udp_recv_socket(c->host_arg, c->port_arg); + + ret = makesock(AF_UNSPEC, IPPROTO_UDP, 1, c->host_arg, c->port_arg); if (ret < 0) goto err; purd->fd = ret; + + ret = mcast_receiver_setup(purd->fd); + if (ret < 0) { + close(purd->fd); + return ret; + } + ret = mark_fd_nonblocking(purd->fd); if (ret < 0) goto err; diff --git a/udp_send.c b/udp_send.c index 3804c39c..8eee7e61 100644 --- a/udp_send.c +++ b/udp_send.c @@ -68,6 +68,62 @@ static void udp_delete_target(struct udp_target *ut, const char *msg) free(ut); } +/** + * Perform AF-independent multicast sender setup. + * + * \param fd The connected socket descriptor. + * \param ttl UDPv4 multicast TTL or UDPv6 multicast number of hops. + * Use -1 to mean default, 0..255 otherwise. + + ** \return Zero if okay, negative on error. + */ +static int mcast_sender_setup(struct udp_target *ut, int ttl) +{ + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + + const int on = 1; + + if (getpeername(ut->fd, (struct sockaddr *)&ss, &sslen) < 0) + goto err; + + /* RFC 3493, 5.2: -1 means 'use kernel default' */ + if (ttl < 0 || ttl > 255) + ttl = -1; + + switch (ss.ss_family) { + case AF_INET: + if (!IN_MULTICAST(htonl(((struct sockaddr_in *)&ss)->sin_addr.s_addr))) + return 0; + /* + * Enable receiving multicast messages generated on the local host + * At least on Linux, this is enabled by default. + */ + if (setsockopt(ut->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(on)) < 0) + break; + + /* Default: use local subnet (do not flood out into the WAN) */ + if (ttl == -1) + ttl = 1; + if (setsockopt(ut->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) + break; + return 0; + case AF_INET6: + if (!IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)&ss)->sin6_addr)) + return 0; + if (setsockopt(ut->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on, sizeof(on)) < 0) + break; + if (setsockopt(ut->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) < 0) + break; + return 0; + default: + PARA_ERROR_LOG("address family %d not supported", ss.ss_family); + return -E_ADDRESS_LOOKUP; + } +err: + return -ERRNO_TO_PARA_ERROR(errno); +} + /** The maximal size of the per-target chunk queue. */ #define UDP_CQ_BYTES 40000 @@ -77,11 +133,18 @@ static int udp_init_session(struct udp_target *ut) if (ut->fd >= 0) /* nothing to do */ return 0; - ret = create_udp_send_socket(TARGET_ADDR(ut), ut->port, - conf.udp_ttl_arg); + + ret = makesock(AF_UNSPEC, IPPROTO_UDP, 0, TARGET_ADDR(ut), ut->port); if (ret < 0) return ret; ut->fd = ret; + + ret = mcast_sender_setup(ut, conf.udp_ttl_arg); + if (ret < 0) { + close(ut->fd); + return ret; + } + ret = mark_fd_nonblocking(ut->fd); if (ret < 0) { close(ut->fd);