blob: dc47b9e3038981bba0be012f116717e4f20cd943 [file] [log] [blame]
/*
* Copyright (c) 2014 Netflix, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include "mongoose.h"
#include "url_lib.h"
// TODO: Partners should define this port
#define SSDP_PORT (56790)
static char gBuf[4096];
// TODO: Partners should get the friendlyName from the system and insert here.
// TODO: Partners should get the UUID from the system and insert here.
static const char ddxml[] = ""
"<?xml version=\"1.0\"?>"
"<root"
" xmlns=\"urn:schemas-upnp-org:device-1-0\""
" xmlns:r=\"urn:restful-tv-org:schemas:upnp-dd\">"
" <specVersion>"
" <major>1</major>"
" <minor>0</minor>"
" </specVersion>"
" <device>"
" <deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>"
" <friendlyName>%s</friendlyName>"
" <manufacturer> </manufacturer>"
" <modelName>%s</modelName>"
" <UDN>uuid:%s</UDN>"
" </device>"
"</root>";
// TODO: Partners should get the UUID from the system and insert here.
static const char ssdp_reply[] = "HTTP/1.1 200 OK\r\n"
"LOCATION: http://%s:%d/dd.xml\r\n"
"CACHE-CONTROL: max-age=1800\r\n"
"EXT:\r\n"
"BOOTID.UPNP.ORG: 1\r\n"
"SERVER: Linux/2.6 UPnP/1.0 quick_ssdp/1.0\r\n"
"ST: urn:dial-multiscreen-org:service:dial:1\r\n"
"USN: uuid:%s::"
"urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
static int dial_port = 0;
static int my_port = 0;
static char friendly_name[256];
static char uuid[256];
static char model_name[256];
static struct mg_context *ctx;
extern int dial_allowed();
static void *request_handler(enum mg_event event,
struct mg_connection *conn,
const struct mg_request_info *request_info) {
if (event == MG_NEW_REQUEST) {
if (!dial_allowed())
return "done";
if (!strcmp(request_info->uri, "/dd.xml") &&
!strcmp(request_info->request_method, "GET")) {
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Type: application/xml\r\n"
"Application-URL: http://%s:%d/apps/\r\n"
"\r\n", ip_addr, dial_port);
mg_printf(conn, ddxml, friendly_name, model_name, uuid);
} else {
mg_send_http_error(conn, 404, "Not Found", "Not Found");
}
return "done";
}
return NULL;
}
static void get_local_address() {
struct ifconf ifc;
char buf[4096];
int s, i;
if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
perror("socket");
exit(1);
}
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (0 > ioctl(s, SIOCGIFCONF, &ifc)) {
perror("SIOCGIFCONF");
exit(1);
}
if (ifc.ifc_len == sizeof(buf)) {
fprintf(stderr, "SIOCGIFCONF output too long");
exit(1);
}
close(s);
for (i = 0; i < ifc.ifc_len/sizeof(ifc.ifc_req[0]); i++) {
strcpy(ip_addr,
inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr));
// exit if we found a non-loopback address
if (strcmp("127.0.0.1", ip_addr)) {
break;
}
}
}
static void handle_mcast() {
int s, one = 1, bytes;
socklen_t addrlen;
struct sockaddr_in saddr;
struct ip_mreq mreq;
char send_buf[sizeof(ssdp_reply) + INET_ADDRSTRLEN + 256 + 256] = {0,};
int send_size;
send_size = snprintf(send_buf, sizeof(send_buf), ssdp_reply, ip_addr, my_port, uuid);
if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
perror("socket");
exit(1);
}
if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
perror("reuseaddr");
exit(1);
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
saddr.sin_port = htons(1900);
if (-1 == bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) {
perror("bind");
exit(1);
}
mreq.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
mreq.imr_interface.s_addr = inet_addr(ip_addr);
if (-1 == setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq, sizeof(mreq))) {
perror("add_membership");
exit(1);
}
//printf("Starting Multicast handling on 239.255.255.250\n");
while (1) {
addrlen = sizeof(saddr);
if (-1 == (bytes = recvfrom(s, gBuf, sizeof(gBuf) - 1, 0,
(struct sockaddr *)&saddr, &addrlen))) {
perror("recvfrom");
continue;
}
gBuf[bytes] = 0;
// sophisticated SSDP parsing algorithm
if (!strstr(gBuf, "urn:dial-multiscreen-org:service:dial:1") &&
!strstr(gBuf, "ST: urn:dial-multiscreen-org:device:dial:1"))
{
#if 0 // use for debugging
fprintf(stderr, "Dropping: \n");
{
int i;
for (i = 0; i < bytes; i++)
{
putchar(gBuf[i]);
}
}
fprintf(stderr, "\n##### End of DROP #######\n");
#endif
continue;
}
if (dial_allowed()) {
fprintf(stderr, "Sending SSDP reply to %s:%d\n",
inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
if (-1 == sendto(s, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) {
perror("sendto");
continue;
}
} else {
fprintf(stderr, "Dial server disabled, dropping SSDP reply to %s:%d\n",
inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
}
}
}
void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid, const char **ppIpAddress) {
struct sockaddr sa;
socklen_t len = sizeof(sa);
if(pFriendlyName) {
xmlencode(friendly_name, pFriendlyName, sizeof(friendly_name));
} else {
strcpy(friendly_name, "DIAL server sample");
}
if(pModelName) {
xmlencode(model_name, pModelName, sizeof(model_name));
} else {
strcpy(model_name, "deadbeef-dead-beef-dead-beefdeadbeef");
}
if(pUuid) {
xmlencode(uuid, pUuid, sizeof(uuid));
} else {
strcpy(uuid, "deadbeef-dead-beef-dead-beefdeadbeef");
}
dial_port = port;
get_local_address();
*ppIpAddress = ip_addr;
ctx = mg_start(&request_handler, NULL, SSDP_PORT);
if (mg_get_listen_addr(ctx, &sa, &len)) {
my_port = ntohs(((struct sockaddr_in *)&sa)->sin_port);
}
fprintf(stderr, "SSDP listening on %s:%d\n", ip_addr, my_port);
handle_mcast();
}