diff --git a/Makefile b/Makefile index ca7f8a55..bfd2da08 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,9 @@ SRCS = src/version.c \ src/fsmonitor.c \ src/cron.c \ +SRCS-${CONFIG_UPNP} += \ + src/upnp.c + SRCS += \ src/api.c \ src/api/api_status.c \ diff --git a/src/main.c b/src/main.c index 6f818d18..2245ecde 100644 --- a/src/main.c +++ b/src/main.c @@ -41,6 +41,7 @@ #include "tcp.h" #include "access.h" #include "http.h" +#include "upnp.h" #include "webui/webui.h" #include "epggrab.h" #include "spawn.h" @@ -767,6 +768,9 @@ main(int argc, char **argv) tcp_server_init(opt_ipv6); http_server_init(opt_bindaddr); webui_init(); +#if ENABLE_UPNP + upnp_server_init(opt_bindaddr); +#endif service_mapper_init(); @@ -813,6 +817,9 @@ main(int argc, char **argv) mainloop(); +#if ENABLE_UPNP + tvhftrace("main", upnp_server_done); +#endif tvhftrace("main", htsp_done); tvhftrace("main", http_server_done); tvhftrace("main", webui_done); diff --git a/src/upnp.c b/src/upnp.c new file mode 100644 index 00000000..d2ef9c35 --- /dev/null +++ b/src/upnp.c @@ -0,0 +1,219 @@ +/* + * tvheadend, UPnP interface + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tvheadend.h" +#include "tvhpoll.h" +#include "upnp.h" + +int upnp_running; +static pthread_t upnp_tid; +pthread_mutex_t upnp_lock; + +TAILQ_HEAD(upnp_active_services, upnp_service); + +typedef struct upnp_data { + TAILQ_ENTRY(upnp_data) data_link; + struct sockaddr_storage storage; + htsbuf_queue_t queue; +} upnp_data_t; + +TAILQ_HEAD(upnp_data_queue_write, upnp_data); + +static struct upnp_active_services upnp_services; +static struct upnp_data_queue_write upnp_data_write; +static struct sockaddr_storage upnp_ipv4_multicast; + +/* + * + */ +upnp_service_t *upnp_service_create0( upnp_service_t *us ) +{ + pthread_mutex_lock(&upnp_lock); + TAILQ_INSERT_TAIL(&upnp_services, us, us_link); + pthread_mutex_unlock(&upnp_lock); + return us; +} + +void upnp_service_destroy( upnp_service_t *us ) +{ + pthread_mutex_lock(&upnp_lock); + TAILQ_REMOVE(&upnp_services, us, us_link); + us->us_destroy(us); + pthread_mutex_unlock(&upnp_lock); + free(us); +} + +/* + * + */ +void +upnp_send( htsbuf_queue_t *q, struct sockaddr_storage *storage ) +{ + upnp_data_t *data; + + if (!upnp_running) + return; + data = calloc(1, sizeof(upnp_data_t)); + htsbuf_queue_init(&data->queue, 0); + htsbuf_appendq(&data->queue, q); + if (storage == NULL) + data->storage = upnp_ipv4_multicast; + else + data->storage = *storage; + pthread_mutex_lock(&upnp_lock); + TAILQ_INSERT_TAIL(&upnp_data_write, data, data_link); + pthread_mutex_unlock(&upnp_lock); +} + +/* + * Discovery thread + */ +static void * +upnp_thread( void *aux ) +{ + char *bindaddr = aux; + tvhpoll_t *poll = tvhpoll_create(2); + tvhpoll_event_t ev[2]; + upnp_data_t *data; + udp_connection_t *multicast = NULL, *unicast = NULL; + udp_connection_t *conn; + unsigned char buf[16384]; + upnp_service_t *us; + struct sockaddr_storage ip; + socklen_t iplen; + size_t size; + int r; + + multicast = udp_bind("upnp", "upnp_thread_multicast", + "239.255.255.250", 1900, + NULL, 32*1024); + if (multicast == NULL || multicast == UDP_FATAL_ERROR) + goto error; + unicast = udp_bind("upnp", "upnp_thread_unicast", bindaddr, 1900, + NULL, 32*1024); + if (unicast == NULL || unicast == UDP_FATAL_ERROR) + goto error; + + memset(&ev, 0, sizeof(ev)); + ev[0].fd = multicast->fd; + ev[0].events = TVHPOLL_IN; + ev[0].data.u64 = (uint64_t)multicast; + ev[1].fd = unicast->fd; + ev[1].events = TVHPOLL_IN; + ev[1].data.u64 = (uint64_t)unicast; + tvhpoll_add(poll, ev, 2); + + while (upnp_running && multicast->fd >= 0) { + r = tvhpoll_wait(poll, ev, 2, 1000); + + while (r-- > 0) { + if ((ev[r].events & TVHPOLL_IN) != 0) { + conn = (udp_connection_t *)ev[r].data.u64; + iplen = sizeof(ip); + size = recvfrom(conn->fd, buf, sizeof(buf), 0, + (struct sockaddr *)&ip, &iplen); +#if ENABLE_TRACE + if (size > 0) { + char tbuf[256]; + inet_ntop(ip.ss_family, IP_IN_ADDR(ip), tbuf, sizeof(tbuf)); + tvhtrace("upnp", "%s - received data from %s:%hu [size=%li]", + conn == multicast ? "multicast" : "unicast", + tbuf, IP_PORT(ip), size); + tvhlog_hexdump("upnp", buf, size); + } +#endif + /* TODO: a filter */ + TAILQ_FOREACH(us, &upnp_services, us_link) + us->us_received(buf, size, conn, &ip); + } + } + + while (1) { + pthread_mutex_lock(&upnp_lock); + data = TAILQ_FIRST(&upnp_data_write); + if (data) + TAILQ_REMOVE(&upnp_data_write, data, data_link); + pthread_mutex_unlock(&upnp_lock); + if (data == NULL) + break; + udp_write_queue(unicast, &data->queue, &data->storage); + htsbuf_queue_flush(&data->queue); + free(data); + } + } + +error: + upnp_running = 0; + tvhpoll_destroy(poll); + udp_close(unicast); + udp_close(multicast); + return NULL; +} + +/* + * Fire up UPnP server + */ +void +upnp_server_init(const char *bindaddr) +{ + int r; + + memset(&upnp_ipv4_multicast, 0, sizeof(upnp_ipv4_multicast)); + upnp_ipv4_multicast.ss_family = AF_INET; + IP_AS_V4(upnp_ipv4_multicast, port) = htons(1900); + r = inet_pton(AF_INET, "239.255.255.250", &IP_AS_V4(upnp_ipv4_multicast, addr)); + assert(r); + + pthread_mutex_init(&upnp_lock, NULL); + TAILQ_INIT(&upnp_data_write); + TAILQ_INIT(&upnp_services); + upnp_running = 1; + tvhthread_create(&upnp_tid, NULL, upnp_thread, (char *)bindaddr, 0); +} + +void +upnp_server_done(void) +{ + upnp_data_t *data; + upnp_service_t *us; + + upnp_running = 0; + pthread_kill(upnp_tid, SIGTERM); + pthread_join(upnp_tid, NULL); + while ((us = TAILQ_FIRST(&upnp_services)) != NULL) + upnp_service_destroy(us); + while ((data = TAILQ_FIRST(&upnp_data_write)) != NULL) { + TAILQ_REMOVE(&upnp_data_write, data, data_link); + htsbuf_queue_flush(&data->queue); + free(data); + } +} diff --git a/src/upnp.h b/src/upnp.h new file mode 100644 index 00000000..7753e430 --- /dev/null +++ b/src/upnp.h @@ -0,0 +1,47 @@ +/* + * tvheadend, UPnP interface + * 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 . + */ + +#ifndef UPNP_H_ +#define UPNP_H_ + +#include "http.h" +#include "udp.h" + +extern int upnp_running; + +typedef struct upnp_service upnp_service_t; + +struct upnp_service { + TAILQ_ENTRY(upnp_service) us_link; + void (*us_received)(uint8_t *data, size_t len, + udp_connection_t *conn, + struct sockaddr_storage *storage); + void (*us_destroy)(upnp_service_t *us); +}; + +upnp_service_t *upnp_service_create0(upnp_service_t *us); +#define upnp_service_create(us) \ + upnp_service_create0(calloc(1, sizeof(struct us))) +void upnp_service_destroy(upnp_service_t *service); + +void upnp_send(htsbuf_queue_t *q, struct sockaddr_storage *storage); + +void upnp_server_init(const char *bindaddr); +void upnp_server_done(void); + +#endif /* UPNP_H_ */