567 lines
15 KiB
C
567 lines
15 KiB
C
/*
|
|
* TVHeadend - UDP common routines
|
|
*
|
|
* Copyright (C) 2013 Adam Sutton
|
|
* Copyright (C) 2014 Jaroslav Kysela
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include "tvheadend.h"
|
|
#include "udp.h"
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#include <net/if.h>
|
|
#ifndef IPV6_ADD_MEMBERSHIP
|
|
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
|
|
#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
|
|
#endif
|
|
|
|
extern int tcp_preferred_address_family;
|
|
|
|
static int
|
|
udp_resolve( udp_connection_t *uc, int receiver )
|
|
{
|
|
struct addrinfo hints, *res, *ressave, *use = NULL;
|
|
char port_buf[6];
|
|
int x;
|
|
|
|
snprintf(port_buf, 6, "%d", uc->port);
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_flags = (receiver ? AI_PASSIVE : 0) | AI_NUMERICSERV;
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
|
|
x = getaddrinfo(uc->host, port_buf, &hints, &res);
|
|
if (x < 0) {
|
|
tvhlog(LOG_ERR, uc->subsystem, "getaddrinfo: %s: %s",
|
|
uc->host != NULL ? uc->host : "*",
|
|
x == EAI_SYSTEM ? strerror(errno) : gai_strerror(x));
|
|
return -1;
|
|
}
|
|
|
|
ressave = res;
|
|
while (res) {
|
|
if (res->ai_family == tcp_preferred_address_family) {
|
|
use = res;
|
|
break;
|
|
} else if (use == NULL) {
|
|
use = res;
|
|
}
|
|
res = res->ai_next;
|
|
}
|
|
if (use->ai_family == AF_INET6) {
|
|
uc->ip.ss_family = AF_INET6;
|
|
IP_AS_V6(uc->ip, port) = htons(uc->port);
|
|
memcpy(&IP_AS_V6(uc->ip, addr), &((struct sockaddr_in6 *)use->ai_addr)->sin6_addr,
|
|
sizeof(struct in6_addr));
|
|
uc->multicast = !!IN6_IS_ADDR_MULTICAST(&IP_AS_V6(uc->ip, addr));
|
|
} else if (use->ai_family == AF_INET) {
|
|
uc->ip.ss_family = AF_INET;
|
|
IP_AS_V4(uc->ip, port) = htons(uc->port);
|
|
IP_AS_V4(uc->ip, addr) = ((struct sockaddr_in *)use->ai_addr)->sin_addr;
|
|
uc->multicast = !!IN_MULTICAST(ntohl(IP_AS_V4(uc->ip, addr.s_addr)));
|
|
}
|
|
freeaddrinfo(ressave);
|
|
if (uc->ip.ss_family != AF_INET && uc->ip.ss_family != AF_INET6) {
|
|
tvherror(uc->subsystem, "%s - failed to process host '%s'", uc->name, uc->host);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int
|
|
udp_ifindex_required( udp_connection_t *uc )
|
|
{
|
|
if (!uc->multicast)
|
|
return 0;
|
|
#if defined(PLATFORM_DARWIN)
|
|
if (uc->ip.ss_family == AF_INET)
|
|
return 0;
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
udp_get_ifindex( const char *ifname )
|
|
{
|
|
unsigned int r;
|
|
if (ifname == NULL || *ifname == '\0')
|
|
return 0;
|
|
|
|
r = if_nametoindex(ifname);
|
|
if (!r)
|
|
return -1;
|
|
return r;
|
|
}
|
|
|
|
#if defined(PLATFORM_DARWIN)
|
|
static int
|
|
udp_get_ifaddr( int fd, const char *ifname, struct in_addr *addr )
|
|
{
|
|
struct ifreq ifreq;
|
|
|
|
if (ifname == NULL || *ifname == '\0')
|
|
return -1;
|
|
|
|
memset(&ifreq, 0, sizeof(ifreq));
|
|
strncpy(ifreq.ifr_name, ifname, IFNAMSIZ);
|
|
|
|
if (ioctl(fd, SIOCGIFADDR, &ifreq) < 0)
|
|
return -1;
|
|
|
|
memcpy(addr, &((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr,
|
|
sizeof(struct in_addr));
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
udp_get_solip( void )
|
|
{
|
|
#ifdef SOL_IP
|
|
return SOL_IP;
|
|
#else
|
|
struct protoent *pent;
|
|
pent = getprotobyname("ip");
|
|
return (pent != NULL) ? pent->p_proto : 0;
|
|
#endif
|
|
}
|
|
|
|
udp_connection_t *
|
|
udp_bind ( const char *subsystem, const char *name,
|
|
const char *bindaddr, int port,
|
|
const char *ifname, int rxsize )
|
|
{
|
|
int fd, ifindex, reuse = 1;
|
|
udp_connection_t *uc;
|
|
char buf[256];
|
|
socklen_t addrlen;
|
|
|
|
uc = calloc(1, sizeof(udp_connection_t));
|
|
uc->fd = -1;
|
|
uc->host = bindaddr ? strdup(bindaddr) : NULL;
|
|
uc->port = port;
|
|
uc->ifname = ifname ? strdup(ifname) : NULL;
|
|
uc->subsystem = subsystem ? strdup(subsystem) : NULL;
|
|
uc->name = name ? strdup(name) : NULL;
|
|
uc->rxtxsize = rxsize;
|
|
|
|
if (udp_resolve(uc, 1) < 0) {
|
|
udp_close(uc);
|
|
return UDP_FATAL_ERROR;
|
|
}
|
|
|
|
/* Open socket */
|
|
if ((fd = tvh_socket(uc->ip.ss_family, SOCK_DGRAM, 0)) == -1) {
|
|
tvherror(subsystem, "%s - failed to create socket [%s]",
|
|
name, strerror(errno));
|
|
udp_close(uc);
|
|
return UDP_FATAL_ERROR;
|
|
}
|
|
|
|
/* Mark reuse address */
|
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
|
|
|
/* Bind to interface */
|
|
ifindex = udp_ifindex_required(uc) ? udp_get_ifindex(ifname) : 0;
|
|
if (ifindex < 0) {
|
|
tvherror(subsystem, "%s - could not find interface %s",
|
|
name, ifname);
|
|
goto error;
|
|
}
|
|
|
|
/* IPv4 */
|
|
if (uc->ip.ss_family == AF_INET) {
|
|
#if defined(PLATFORM_DARWIN)
|
|
struct ip_mreq m;
|
|
#else
|
|
struct ip_mreqn m;
|
|
#endif
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
/* Bind */
|
|
if (bind(fd, (struct sockaddr *)&uc->ip, sizeof(struct sockaddr_in)) == -1) {
|
|
inet_ntop(AF_INET, &IP_AS_V4(uc->ip, addr), buf, sizeof(buf));
|
|
tvherror(subsystem, "%s - cannot bind %s:%hu [e=%s]",
|
|
name, buf, ntohs(IP_AS_V4(uc->ip, port)), strerror(errno));
|
|
goto error;
|
|
}
|
|
|
|
if (uc->multicast) {
|
|
/* Join group */
|
|
m.imr_multiaddr = IP_AS_V4(uc->ip, addr);
|
|
#if !defined(PLATFORM_DARWIN)
|
|
m.imr_address.s_addr = 0;
|
|
m.imr_ifindex = ifindex;
|
|
#else
|
|
if (udp_get_ifaddr(fd, ifname, &m.imr_interface) == -1) {
|
|
tvherror(subsystem, "%s - cannot find ip address for interface %s [e=%s]",
|
|
name, ifname, strerror(errno));
|
|
goto error;
|
|
}
|
|
#endif
|
|
|
|
if (setsockopt(fd, udp_get_solip(), IP_ADD_MEMBERSHIP, &m, sizeof(m))) {
|
|
inet_ntop(AF_INET, &m.imr_multiaddr, buf, sizeof(buf));
|
|
tvhwarn(subsystem, "%s - cannot join %s [%s]",
|
|
name, buf, strerror(errno));
|
|
}
|
|
}
|
|
|
|
/* Bind to IPv6 group */
|
|
} else {
|
|
struct ipv6_mreq m;
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
/* Bind */
|
|
if (bind(fd, (struct sockaddr *)&uc->ip, sizeof(struct sockaddr_in6)) == -1) {
|
|
inet_ntop(AF_INET6, &IP_AS_V6(uc->ip, addr), buf, sizeof(buf));
|
|
tvherror(subsystem, "%s - cannot bind %s:%hu [e=%s]",
|
|
name, buf, ntohs(IP_AS_V6(uc->ip, port)), strerror(errno));
|
|
goto error;
|
|
}
|
|
|
|
if (uc->multicast) {
|
|
/* Join group */
|
|
m.ipv6mr_multiaddr = IP_AS_V6(uc->ip, addr);
|
|
m.ipv6mr_interface = ifindex;
|
|
#ifdef SOL_IPV6
|
|
if (setsockopt(fd, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &m, sizeof(m))) {
|
|
inet_ntop(AF_INET, &m.ipv6mr_multiaddr, buf, sizeof(buf));
|
|
tvhwarn(subsystem, "%s - cannot join %s [%s]",
|
|
name, buf, strerror(errno));
|
|
}
|
|
#else
|
|
tvherror(subsystem, "IPv6 multicast not supported");
|
|
goto error;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
addrlen = sizeof(uc->ip);
|
|
getsockname(fd, (struct sockaddr *)&uc->ip, &addrlen);
|
|
|
|
/* Increase RX buffer size */
|
|
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rxsize, sizeof(rxsize)) == -1)
|
|
tvhwarn(subsystem, "%s - cannot increase UDP rx buffer size [%s]",
|
|
name, strerror(errno));
|
|
|
|
uc->fd = fd;
|
|
return uc;
|
|
|
|
error:
|
|
udp_close(uc);
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
udp_bind_double ( udp_connection_t **_u1, udp_connection_t **_u2,
|
|
const char *subsystem, const char *name1,
|
|
const char *name2, const char *host, int port,
|
|
const char *ifname, int rxsize1, int rxsize2 )
|
|
{
|
|
udp_connection_t *u1 = NULL, *u2 = NULL;
|
|
udp_connection_t *ucs[10] = { NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL };
|
|
int pos = 0, i, port2;
|
|
|
|
memset(&ucs, 0, sizeof(ucs));
|
|
while (1) {
|
|
u1 = udp_bind(subsystem, name1, host, port, ifname, rxsize1);
|
|
if (u1 == NULL || u1 == UDP_FATAL_ERROR)
|
|
goto fail;
|
|
port2 = ntohs(IP_PORT(u1->ip));
|
|
/* RTP port should be even, RTCP port should be odd */
|
|
if ((port2 % 2) == 0) {
|
|
u2 = udp_bind(subsystem, name2, host, port2 + 1, ifname, rxsize2);
|
|
if (u2 != NULL && u2 != UDP_FATAL_ERROR)
|
|
break;
|
|
}
|
|
ucs[pos++] = u1;
|
|
if (port || pos >= ARRAY_SIZE(ucs))
|
|
goto fail;
|
|
}
|
|
for (i = 0; i < pos; i++)
|
|
udp_close(ucs[i]);
|
|
*_u1 = u1;
|
|
*_u2 = u2;
|
|
return 0;
|
|
fail:
|
|
for (i = 0; i < pos; i++)
|
|
udp_close(ucs[i]);
|
|
return -1;
|
|
}
|
|
|
|
udp_connection_t *
|
|
udp_connect ( const char *subsystem, const char *name,
|
|
const char *host, int port,
|
|
const char *ifname, int txsize )
|
|
{
|
|
int fd, ifindex;
|
|
udp_connection_t *uc;
|
|
|
|
uc = calloc(1, sizeof(udp_connection_t));
|
|
uc->fd = -1;
|
|
uc->host = host ? strdup(host) : NULL;
|
|
uc->port = port;
|
|
uc->ifname = ifname ? strdup(ifname) : NULL;
|
|
uc->subsystem = subsystem ? strdup(subsystem) : NULL;
|
|
uc->name = name ? strdup(name) : NULL;
|
|
uc->rxtxsize = txsize;
|
|
|
|
if (udp_resolve(uc, 1) < 0) {
|
|
udp_close(uc);
|
|
return UDP_FATAL_ERROR;
|
|
}
|
|
|
|
/* Open socket */
|
|
if ((fd = tvh_socket(uc->ip.ss_family, SOCK_DGRAM, 0)) == -1) {
|
|
tvherror(subsystem, "%s - failed to create socket [%s]",
|
|
name, strerror(errno));
|
|
udp_close(uc);
|
|
return UDP_FATAL_ERROR;
|
|
}
|
|
|
|
/* Bind to interface */
|
|
ifindex = udp_ifindex_required(uc) ? udp_get_ifindex(ifname) : 0;
|
|
if (ifindex < 0) {
|
|
tvherror(subsystem, "%s - could not find interface %s",
|
|
name, ifname);
|
|
goto error;
|
|
}
|
|
|
|
if (uc->multicast) {
|
|
if (uc->ip.ss_family == AF_INET) {
|
|
#if !defined(PLATFORM_DARWIN)
|
|
struct ip_mreqn m;
|
|
memset(&m, 0, sizeof(m));
|
|
m.imr_ifindex = ifindex;
|
|
#else
|
|
struct in_addr m;
|
|
if (udp_get_ifaddr(fd, ifname, &m) == -1) {
|
|
tvherror(subsystem, "%s - cannot find ip address for interface %s [e=%s]",
|
|
name, ifname, strerror(errno));
|
|
goto error;
|
|
}
|
|
#endif
|
|
if (setsockopt(fd, udp_get_solip(), IP_MULTICAST_IF, &m, sizeof(m)))
|
|
tvhwarn(subsystem, "%s - cannot set source interface %s [%s]",
|
|
name, ifname, strerror(errno));
|
|
} else {
|
|
struct ipv6_mreq m;
|
|
memset(&m, 0, sizeof(m));
|
|
m.ipv6mr_interface = ifindex;
|
|
#ifdef SOL_IPV6
|
|
if (setsockopt(fd, SOL_IPV6, IPV6_MULTICAST_IF, &m, sizeof(m))) {
|
|
tvhwarn(subsystem, "%s - cannot set source interface %s [%s]",
|
|
name, ifname, strerror(errno));
|
|
}
|
|
#else
|
|
tvherror(subsystem, "IPv6 multicast not supported");
|
|
goto error;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Increase TX buffer size */
|
|
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &txsize, sizeof(txsize)) == -1)
|
|
tvhwarn(subsystem, "%s - cannot increase UDP tx buffer size [%s]",
|
|
name, strerror(errno));
|
|
|
|
uc->fd = fd;
|
|
return uc;
|
|
|
|
error:
|
|
udp_close(uc);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
udp_close( udp_connection_t *uc )
|
|
{
|
|
if (uc == NULL || uc == UDP_FATAL_ERROR)
|
|
return;
|
|
if (uc->fd >= 0)
|
|
close(uc->fd);
|
|
free(uc->host);
|
|
free(uc->ifname);
|
|
free(uc->subsystem);
|
|
free(uc->name);
|
|
free(uc);
|
|
}
|
|
|
|
int
|
|
udp_write( udp_connection_t *uc, const void *buf, size_t len,
|
|
struct sockaddr_storage *storage )
|
|
{
|
|
int r;
|
|
|
|
if (storage == NULL)
|
|
storage = &uc->ip;
|
|
while (len) {
|
|
r = sendto(uc->fd, buf, len, 0, (struct sockaddr*)storage,
|
|
storage->ss_family == AF_INET6 ?
|
|
sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
|
|
if (r < 0) {
|
|
if (ERRNO_AGAIN(errno)) {
|
|
usleep(100);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
len -= r;
|
|
buf += r;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
int
|
|
udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q,
|
|
struct sockaddr_storage *storage )
|
|
{
|
|
htsbuf_data_t *hd;
|
|
int l, r = 0;
|
|
void *p;
|
|
|
|
while ((hd = TAILQ_FIRST(&q->hq_q)) != NULL) {
|
|
if (!r) {
|
|
l = hd->hd_data_len - hd->hd_data_off;
|
|
p = hd->hd_data + hd->hd_data_off;
|
|
r = udp_write(uc, p, l, storage);
|
|
}
|
|
htsbuf_data_free(q, hd);
|
|
}
|
|
q->hq_size = 0;
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* UDP multi packet receive support
|
|
*/
|
|
|
|
#if !defined (CONFIG_RECVMMSG) && defined(__linux__)
|
|
/* define the syscall - works only for linux */
|
|
#include <linux/unistd.h>
|
|
#ifdef __NR_recvmmsg
|
|
|
|
struct mmsghdr {
|
|
struct msghdr msg_hdr;
|
|
unsigned int msg_len;
|
|
};
|
|
|
|
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
|
|
unsigned int flags, struct timespec *timeout);
|
|
|
|
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
|
|
unsigned int flags, struct timespec *timeout)
|
|
{
|
|
return syscall(__NR_recvmmsg, sockfd, msgvec, vlen, flags, timeout);
|
|
}
|
|
|
|
#define CONFIG_RECVMMSG
|
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#ifndef CONFIG_RECVMMSG
|
|
|
|
struct mmsghdr {
|
|
struct msghdr msg_hdr;
|
|
unsigned int msg_len;
|
|
};
|
|
|
|
static int
|
|
recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
|
|
unsigned int flags, struct timespec *timeout)
|
|
{
|
|
ssize_t r;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < vlen; i++) {
|
|
r = recvmsg(sockfd, &msgvec->msg_hdr, flags);
|
|
if (r < 0)
|
|
return (i > 0) ? i : r;
|
|
msgvec->msg_len = r;
|
|
msgvec++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void
|
|
udp_multirecv_init( udp_multirecv_t *um, int packets, int psize )
|
|
{
|
|
int i;
|
|
|
|
assert(um);
|
|
um->um_psize = psize;
|
|
um->um_packets = packets;
|
|
um->um_data = malloc(packets * psize);
|
|
um->um_iovec = malloc(packets * sizeof(struct iovec));
|
|
um->um_riovec = malloc(packets * sizeof(struct iovec));
|
|
um->um_msg = calloc(packets, sizeof(struct mmsghdr));
|
|
for (i = 0; i < packets; i++) {
|
|
((struct mmsghdr *)um->um_msg)[i].msg_hdr.msg_iov = &um->um_iovec[i];
|
|
((struct mmsghdr *)um->um_msg)[i].msg_hdr.msg_iovlen = 1;
|
|
um->um_iovec[i].iov_base = /* follow thru */
|
|
um->um_riovec[i].iov_base = um->um_data + i * psize;
|
|
um->um_iovec[i].iov_len = psize;
|
|
}
|
|
}
|
|
|
|
void
|
|
udp_multirecv_free( udp_multirecv_t *um )
|
|
{
|
|
if (um == NULL)
|
|
return;
|
|
free(um->um_msg); um->um_msg = NULL;
|
|
free(um->um_riovec); um->um_riovec = NULL;
|
|
free(um->um_iovec); um->um_iovec = NULL;
|
|
free(um->um_data); um->um_data = NULL;
|
|
um->um_psize = 0;
|
|
um->um_packets = 0;
|
|
}
|
|
|
|
int
|
|
udp_multirecv_read( udp_multirecv_t *um, int fd, int packets,
|
|
struct iovec **iovec )
|
|
{
|
|
int n, i;
|
|
if (um == NULL || iovec == NULL) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
if (packets > um->um_packets)
|
|
packets = um->um_packets;
|
|
n = recvmmsg(fd, (struct mmsghdr *)um->um_msg, packets, MSG_DONTWAIT, NULL);
|
|
if (n > 0) {
|
|
for (i = 0; i < n; i++)
|
|
um->um_riovec[i].iov_len = ((struct mmsghdr *)um->um_msg)[i].msg_len;
|
|
*iovec = um->um_riovec;
|
|
}
|
|
return n;
|
|
}
|