+static int net_ListenSingle (vlc_object_t *obj, const char *host, int port,
+ int family, int protocol)
+{
+ struct addrinfo hints, *res;
+
+ memset (&hints, 0, sizeof( hints ));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ if (host && !*host)
+ host = NULL;
+
+ msg_Dbg (obj, "net: opening %s datagram port %d", host ?: "any", port);
+
+ int val = vlc_getaddrinfo (obj, host, port, &hints, &res);
+ if (val)
+ {
+ msg_Err (obj, "Cannot resolve %s port %d : %s", host, port,
+ vlc_gai_strerror (val));
+ return -1;
+ }
+
+ val = -1;
+
+ for (const struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
+ {
+ int fd = net_Socket (obj, ptr->ai_family, ptr->ai_socktype,
+ protocol ?: ptr->ai_protocol);
+ if (fd == -1)
+ {
+ msg_Dbg (obj, "socket error: %m");
+ continue;
+ }
+
+ if (ptr->ai_next != NULL)
+ {
+#ifdef IPV6_V6ONLY
+ if ((ptr->ai_family != AF_INET6)
+ || setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &(int){ 0 },
+ sizeof (int)))
+#endif
+ {
+ msg_Err (obj, "Multiple network protocols present");
+ msg_Err (obj, "Please select network protocol manually");
+ }
+ }
+
+ /* Bind the socket */
+#if defined (WIN32) || defined (UNDER_CE)
+ if (net_SockAddrIsMulticast (ptr->ai_addr, ptr->ai_addrlen)
+ && (sizeof (struct sockaddr_storage) >= ptr->ai_addrlen))
+ {
+ struct sockaddr_in6 dumb =
+ {
+ .sin6_family = ptr->ai_addr->sa_family,
+ .sin6_port = ((struct sockaddr_in *)(ptr->ai_addr))->sin_port
+ };
+ bind (fd, (struct sockaddr *)&dumb, ptr->ai_addrlen);
+ }
+ else
+#endif
+ if (bind (fd, ptr->ai_addr, ptr->ai_addrlen))
+ {
+ msg_Err (obj, "socket bind error (%m)");
+ net_Close (fd);
+ continue;
+ }
+
+ if (net_SockAddrIsMulticast (ptr->ai_addr, ptr->ai_addrlen)
+ && net_Subscribe (obj, fd, ptr->ai_addr, ptr->ai_addrlen))
+ {
+ net_Close (fd);
+ continue;
+ }
+
+ val = fd;
+ break;
+ }
+
+ vlc_freeaddrinfo (res);
+ return val;
+}
+
+
+static int net_SetMcastHopLimit( vlc_object_t *p_this,
+ int fd, int family, int hlim )
+{
+ int proto, cmd;
+
+ /* There is some confusion in the world whether IP_MULTICAST_TTL
+ * takes a byte or an int as an argument.
+ * BSD seems to indicate byte so we are going with that and use
+ * int as a fallback to be safe */
+ switch( family )
+ {
+#ifdef IP_MULTICAST_TTL
+ case AF_INET:
+ proto = SOL_IP;
+ cmd = IP_MULTICAST_TTL;
+ break;
+#endif
+
+#ifdef IPV6_MULTICAST_HOPS
+ case AF_INET6:
+ proto = SOL_IPV6;
+ cmd = IPV6_MULTICAST_HOPS;
+ break;
+#endif
+
+ default:
+ errno = EAFNOSUPPORT;
+ msg_Warn( p_this, "%m" );
+ return VLC_EGENERIC;
+ }
+
+ if( setsockopt( fd, proto, cmd, &hlim, sizeof( hlim ) ) < 0 )
+ {
+ /* BSD compatibility */
+ unsigned char buf;
+
+ buf = (unsigned char)(( hlim > 255 ) ? 255 : hlim);
+ if( setsockopt( fd, proto, cmd, &buf, sizeof( buf ) ) )
+ return VLC_EGENERIC;
+ }
+
+ return VLC_SUCCESS;
+}
+
+
+static int net_SetMcastOutIface (int fd, int family, int scope)
+{
+ switch (family)
+ {
+#ifdef IPV6_MULTICAST_IF
+ case AF_INET6:
+ return setsockopt (fd, SOL_IPV6, IPV6_MULTICAST_IF,
+ &scope, sizeof (scope));
+#endif
+
+#ifdef __linux__
+ case AF_INET:
+ {
+ struct ip_mreqn req = { .imr_ifindex = scope };
+
+ return setsockopt (fd, SOL_IP, IP_MULTICAST_IF, &req,
+ sizeof (req));
+ }
+#endif
+ }
+
+ errno = EAFNOSUPPORT;
+ return -1;
+}
+
+
+static inline int net_SetMcastOutIPv4 (int fd, struct in_addr ipv4)
+{
+#ifdef IP_MULTICAST_IF
+ return setsockopt( fd, SOL_IP, IP_MULTICAST_IF, &ipv4, sizeof (ipv4));
+#else
+ errno = EAFNOSUPPORT;
+ return -1;
+#endif
+}
+
+
+static int net_SetMcastOut (vlc_object_t *p_this, int fd, int family,
+ const char *iface, const char *addr)
+{
+ if (iface != NULL)
+ {
+ int scope = if_nametoindex (iface);
+ if (scope == 0)
+ {
+ msg_Err (p_this, "invalid multicast interface: %s", iface);
+ return -1;
+ }
+
+ if (net_SetMcastOutIface (fd, family, scope) == 0)
+ return 0;
+
+ msg_Err (p_this, "%s: %m", iface);
+ }
+
+ if (addr != NULL)
+ {
+ if (family == AF_INET)
+ {
+ struct in_addr ipv4;
+ if (inet_pton (AF_INET, addr, &ipv4) <= 0)
+ {
+ msg_Err (p_this, "invalid IPv4 address for multicast: %s",
+ addr);
+ return -1;
+ }
+
+ if (net_SetMcastOutIPv4 (fd, ipv4) == 0)
+ return 0;
+
+ msg_Err (p_this, "%s: %m", addr);
+ }
+ }
+
+ return -1;
+}
+
+
+/**
+ * Old-style any-source multicast join.
+ * In use on Windows XP/2003 and older.
+ */
+static int
+net_IPv4Join (vlc_object_t *obj, int fd,
+ const struct sockaddr_in *src, const struct sockaddr_in *grp)
+{
+#ifdef IP_ADD_MEMBERSHIP
+ union
+ {
+ struct ip_mreq gr4;
+# ifdef IP_ADD_SOURCE_MEMBERSHIP
+ struct ip_mreq_source gsr4;
+# endif
+ } opt;
+ int cmd;
+ struct in_addr id = { .s_addr = INADDR_ANY };
+ socklen_t optlen;
+
+ /* Multicast interface IPv4 address */
+ char *iface = var_CreateGetNonEmptyString (obj, "miface-addr");
+ if ((iface != NULL)
+ && (inet_pton (AF_INET, iface, &id) <= 0))
+ {
+ msg_Err (obj, "invalid multicast interface address %s", iface);
+ free (iface);
+ goto error;
+ }
+ free (iface);
+
+ memset (&opt, 0, sizeof (opt));
+ if (src != NULL)
+ {
+# ifdef IP_ADD_SOURCE_MEMBERSHIP
+ cmd = IP_ADD_SOURCE_MEMBERSHIP;
+ opt.gsr4.imr_multiaddr = grp->sin_addr;
+ opt.gsr4.imr_sourceaddr = src->sin_addr;
+ opt.gsr4.imr_interface = id;
+ optlen = sizeof (opt.gsr4);
+# else
+ errno = ENOSYS;
+ goto error;
+# endif
+ }
+ else
+ {
+ cmd = IP_ADD_MEMBERSHIP;
+ opt.gr4.imr_multiaddr = grp->sin_addr;
+ opt.gr4.imr_interface = id;
+ optlen = sizeof (opt.gr4);
+ }
+
+ msg_Dbg (obj, "IP_ADD_%sMEMBERSHIP multicast request",
+ (src != NULL) ? "SOURCE_" : "");
+
+ if (setsockopt (fd, SOL_IP, cmd, &opt, optlen) == 0)
+ return 0;
+
+error:
+#endif
+
+ msg_Err (obj, "cannot join IPv4 multicast group (%m)");
+ return -1;
+}
+
+
+static int
+net_IPv6Join (vlc_object_t *obj, int fd, const struct sockaddr_in6 *src)
+{
+#ifdef IPV6_JOIN_GROUP
+ struct ipv6_mreq gr6;
+ memset (&gr6, 0, sizeof (gr6));
+ gr6.ipv6mr_interface = src->sin6_scope_id;
+ memcpy (&gr6.ipv6mr_multiaddr, &src->sin6_addr, 16);
+
+ msg_Dbg (obj, "IPV6_JOIN_GROUP multicast request");
+
+ if (!setsockopt (fd, SOL_IPV6, IPV6_JOIN_GROUP, &gr6, sizeof (gr6)))
+ return 0;
+#else
+ errno = ENOSYS;
+#endif
+
+ msg_Err (obj, "cannot join IPv6 any-source multicast group (%m)");
+ return -1;
+}
+
+
+#if defined (WIN32) && !defined (MCAST_JOIN_SOURCE_GROUP)
+/*
+ * I hate manual definitions: Error-prone. Portability hell.
+ * Developers shall use UP-TO-DATE compilers. Full point.
+ * If you remove the warning, you remove the whole ifndef.
+ */
+# warning Your C headers are out-of-date. Please update.
+
+# define MCAST_JOIN_GROUP 41
+struct group_req
+{
+ ULONG gr_interface;
+ struct sockaddr_storage gr_group;
+};
+
+# define MCAST_JOIN_SOURCE_GROUP 45 /* from <ws2ipdef.h> */
+struct group_source_req
+{
+ uint32_t gsr_interface;
+ struct sockaddr_storage gsr_group;
+ struct sockaddr_storage gsr_source;
+};
+#endif
+
+/**
+ * IP-agnostic multicast join,
+ * with fallback to old APIs, and fallback from SSM to ASM.
+ */
+static int
+net_SourceSubscribe (vlc_object_t *obj, int fd,
+ const struct sockaddr *src, socklen_t srclen,
+ const struct sockaddr *grp, socklen_t grplen)
+{
+ int level, iid = 0;
+
+ char *iface = var_CreateGetNonEmptyString (obj, "miface");
+ if (iface != NULL)
+ {
+ iid = if_nametoindex (iface);
+ if (iid == 0)
+ {
+ msg_Err (obj, "invalid multicast interface: %s", iface);
+ free (iface);
+ return -1;
+ }
+ free (iface);
+ }
+
+ switch (grp->sa_family)
+ {
+#ifdef AF_INET6
+ case AF_INET6:
+ level = SOL_IPV6;
+ if (((const struct sockaddr_in6 *)grp)->sin6_scope_id)
+ iid = ((const struct sockaddr_in6 *)grp)->sin6_scope_id;
+ break;
+#endif
+
+ case AF_INET:
+ level = SOL_IP;
+ break;
+
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ if (src != NULL)
+ switch (src->sa_family)
+ {
+#ifdef AF_INET6
+ case AF_INET6:
+ if (memcmp (&((const struct sockaddr_in6 *)src)->sin6_addr,
+ &in6addr_any, sizeof (in6addr_any)) == 0)
+ src = NULL;
+ break;
+#endif
+
+ case AF_INET:
+ if (((const struct sockaddr_in *)src)->sin_addr.s_addr
+ == INADDR_ANY)
+ src = NULL;
+ break;
+ }
+
+
+ /* Agnostic ASM/SSM multicast join */
+#ifdef MCAST_JOIN_SOURCE_GROUP
+ union
+ {
+ struct group_req gr;
+ struct group_source_req gsr;
+ } opt;
+ socklen_t optlen;
+
+ memset (&opt, 0, sizeof (opt));
+
+ if (src != NULL)
+ {
+ if ((grplen > sizeof (opt.gsr.gsr_group))
+ || (srclen > sizeof (opt.gsr.gsr_source)))
+ return -1;
+
+ opt.gsr.gsr_interface = iid;
+ memcpy (&opt.gsr.gsr_source, src, srclen);
+ memcpy (&opt.gsr.gsr_group, grp, grplen);
+ optlen = sizeof (opt.gsr);
+ }
+ else
+ {
+ if (grplen > sizeof (opt.gr.gr_group))
+ return -1;
+
+ opt.gr.gr_interface = iid;
+ memcpy (&opt.gr.gr_group, grp, grplen);
+ optlen = sizeof (opt.gr);
+ }
+
+ msg_Dbg (obj, "Multicast %sgroup join request", src ? "source " : "");
+
+ if (setsockopt (fd, level,
+ src ? MCAST_JOIN_SOURCE_GROUP : MCAST_JOIN_GROUP,
+ (void *)&opt, optlen) == 0)
+ return 0;
+#endif
+
+ /* Fallback to IPv-specific APIs */
+ if ((src != NULL) && (src->sa_family != grp->sa_family))
+ return -1;
+
+ switch (grp->sa_family)
+ {
+ case AF_INET:
+ if ((grplen < sizeof (struct sockaddr_in))
+ || ((src != NULL) && (srclen < sizeof (struct sockaddr_in))))
+ return -1;
+
+ if (net_IPv4Join (obj, fd, (const struct sockaddr_in *)src,
+ (const struct sockaddr_in *)grp) == 0)
+ return 0;
+ break;
+
+#ifdef AF_INET6
+ case AF_INET6:
+ if ((grplen < sizeof (struct sockaddr_in6))
+ || ((src != NULL) && (srclen < sizeof (struct sockaddr_in6))))
+ return -1;
+
+ /* IPv6-specific SSM API does not exist. So if we're here
+ * it means IPv6 SSM is not supported on this OS and we
+ * directly fallback to ASM */
+
+ if (net_IPv6Join (obj, fd, (const struct sockaddr_in6 *)grp) == 0)
+ return 0;
+ break;
+#endif
+ }
+
+ msg_Err (obj, "Multicast group join error (%m)");
+
+ if (src != NULL)
+ {
+ msg_Warn (obj, "Trying ASM instead of SSM...");
+ return net_Subscribe (obj, fd, grp, grplen);
+ }
+
+ msg_Err (obj, "Multicast not supported");
+ return -1;
+}
+
+
+int net_Subscribe (vlc_object_t *obj, int fd,
+ const struct sockaddr *addr, socklen_t addrlen)
+{
+ return net_SourceSubscribe (obj, fd, NULL, 0, addr, addrlen);
+}
+
+
+static int net_SetDSCP( int fd, uint8_t dscp )
+{
+ struct sockaddr_storage addr;
+ if( getsockname( fd, (struct sockaddr *)&addr, &(socklen_t){ sizeof (addr) }) )
+ return -1;
+
+ int level, cmd;
+
+ switch( addr.ss_family )
+ {
+#ifdef IPV6_TCLASS
+ case AF_INET6:
+ level = SOL_IPV6;
+ cmd = IPV6_TCLASS;
+ break;
+#endif
+
+ case AF_INET:
+ level = SOL_IP;
+ cmd = IP_TOS;
+ break;
+
+ default:
+#ifdef ENOPROTOOPT
+ errno = ENOPROTOOPT;
+#endif
+ return -1;
+ }
+
+ return setsockopt( fd, level, cmd, &(int){ dscp }, sizeof (int));
+}
+
+