Add basic support for the UPnP protocol (client)
This commit is contained in:
parent
91e5c9f7a8
commit
1debbee964
4 changed files with 276 additions and 0 deletions
3
Makefile
3
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 \
|
||||
|
|
|
@ -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);
|
||||
|
|
219
src/upnp.c
Normal file
219
src/upnp.c
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
47
src/upnp.h
Normal file
47
src/upnp.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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_ */
|
Loading…
Add table
Reference in a new issue