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_ */