The idea is taken from the nginx web server, but much simplified and almost no copypaste left. This will allow minidlna to use different event dispatcher APIs, which would be defined at compile time. My personal goal is to convert minidlna to kqueue(2) on FreeBSD. This would later allow for kqueue based directory change notification, which won't conflict with select(2) like the current patch does. Other platforms will also benefit from the pluggability of the event system, Linux can switch to epoll(2) or at least to poll(2). Detailed list of changes: * event.h [New] Our internal API to unify different event dispatch systems. * select.c [New] Much simplified version of nginx's ngx_select_module.c. * minidlna.c - Split out listen socket event processing into separate function ProcessListen(), which matches event_process_t type. - Create and initialize struct event for the monitor socket, SSDP socket, HTTP socket and beacon socket. - Simplify and make more precise timeout calculation using helper timeval functions from utils.c. Treat gettimeofday() error as a fatal event. - Rip out all stuff related to select(2). Just call event_module.process(). * upnpevents.c - Embed struct event into upnp_event_notify. - Merge upnp_event_create_notify() with upnp_event_notify_connect(). Start connecting immediately after socket creation. Garbage collect now useless ECreated state. - Make upnp_event_process_notify() of event_process_t type, and use it as process callback for upnp_event_notify event. - Looks like we always create upnp_event_notify with existing subscriber, and never clear it later. Remove checks for obj->sub and assert that it is never NULL. Simplifies things. - When switching obj state, add/del it to event dispatcher accrodingly. - Garbage collect upnpevents_selectfds(). - Garbage collect select(2) related stuff from upnpevents_processfds(). Rename function to upnpevents_gc(), since the remaining functionality is garbage collecting, not file descriptor processing. Actually, this can be simplified even more. We can safely close sockets and free objects immediately, eliminating need for upnpevents_gc(). But this change would be beyond scope of this commit. * upnphttp.c, upnphttp.h Embed struct event into struct upnphttp. Adjust Process_upnphttp() to match event_process_t type. Add/del to event dispatcher once creating/closing a socket. * minissdp.c, minissdp.h Make ProcessSSDPRequest() of event_process_t type. * getifaddr.c, getifaddr.h Make ProcessMonitorEvent() of event_process_t type.
876 lines
23 KiB
C
876 lines
23 KiB
C
/* MiniDLNA media server
|
|
* This file is part of MiniDLNA.
|
|
*
|
|
* The code herein is based on the MiniUPnP Project.
|
|
* http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
|
|
*
|
|
* Copyright (c) 2006, Thomas Bernard
|
|
* 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.
|
|
* * The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT OWNER 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 "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
|
|
#include "event.h"
|
|
#include "minidlnapath.h"
|
|
#include "upnphttp.h"
|
|
#include "upnpglobalvars.h"
|
|
#include "upnpreplyparse.h"
|
|
#include "getifaddr.h"
|
|
#include "minissdp.h"
|
|
#include "codelength.h"
|
|
#include "utils.h"
|
|
#include "log.h"
|
|
|
|
/* SSDP ip/port */
|
|
#define SSDP_PORT (1900)
|
|
#define SSDP_MCAST_ADDR ("239.255.255.250")
|
|
|
|
static int
|
|
AddMulticastMembership(int s, struct lan_addr_s *iface)
|
|
{
|
|
int ret;
|
|
#ifdef HAVE_STRUCT_IP_MREQN
|
|
struct ip_mreqn imr; /* Ip multicast membership */
|
|
/* setting up imr structure */
|
|
memset(&imr, '\0', sizeof(imr));
|
|
imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR);
|
|
imr.imr_ifindex = iface->ifindex;
|
|
#else
|
|
struct ip_mreq imr; /* Ip multicast membership */
|
|
/* setting up imr structure */
|
|
memset(&imr, '\0', sizeof(imr));
|
|
imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR);
|
|
imr.imr_interface.s_addr = iface->addr.s_addr;
|
|
#endif
|
|
/* Setting the socket options will guarantee, tha we will only receive
|
|
* multicast traffic on a specific Interface.
|
|
* In addition the kernel is instructed to send an igmp message (choose
|
|
* mcast group) on the specific interface/subnet. */
|
|
ret = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(imr));
|
|
if (ret < 0 && errno != EADDRINUSE)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp, IP_ADD_MEMBERSHIP): %s\n",
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Open and configure the socket listening for
|
|
* SSDP udp packets sent on 239.255.255.250 port 1900 */
|
|
int
|
|
OpenAndConfSSDPReceiveSocket(void)
|
|
{
|
|
int s;
|
|
int i = 1;
|
|
struct sockaddr_in sockname;
|
|
|
|
s = socket(PF_INET, SOCK_DGRAM, 0);
|
|
if (s < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "socket(udp): %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
|
|
DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, SO_REUSEADDR): %s\n", strerror(errno));
|
|
#ifdef __linux__
|
|
if (setsockopt(s, IPPROTO_IP, IP_PKTINFO, &i, sizeof(i)) < 0)
|
|
DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, IP_PKTINFO): %s\n", strerror(errno));
|
|
#endif
|
|
memset(&sockname, 0, sizeof(struct sockaddr_in));
|
|
sockname.sin_family = AF_INET;
|
|
sockname.sin_port = htons(SSDP_PORT);
|
|
#ifdef __linux__
|
|
/* NOTE: Binding a socket to a UDP multicast address means, that we just want
|
|
* to receive datagramms send to this multicast address.
|
|
* To specify the local nics we want to use we have to use setsockopt,
|
|
* see AddMulticastMembership(...). */
|
|
sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
|
|
#else
|
|
/* NOTE: Binding to SSDP_MCAST_ADDR on Darwin & *BSD causes NOTIFY replies are
|
|
* sent from SSDP_MCAST_ADDR what forces some clients to ignore subsequent
|
|
* unsolicited NOTIFY packets from the real interface address. */
|
|
sockname.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
#endif
|
|
|
|
if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "bind(udp): %s\n", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/* open the UDP socket used to send SSDP notifications to
|
|
* the multicast group reserved for them */
|
|
int
|
|
OpenAndConfSSDPNotifySocket(struct lan_addr_s *iface)
|
|
{
|
|
int s;
|
|
unsigned char loopchar = 0;
|
|
uint8_t ttl = 4;
|
|
struct in_addr mc_if;
|
|
struct sockaddr_in sockname;
|
|
|
|
s = socket(PF_INET, SOCK_DGRAM, 0);
|
|
if (s < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "socket(udp_notify): %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
mc_if.s_addr = iface->addr.s_addr;
|
|
|
|
if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %s\n", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_IF): %s\n", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
|
|
|
|
memset(&sockname, 0, sizeof(struct sockaddr_in));
|
|
sockname.sin_family = AF_INET;
|
|
sockname.sin_addr.s_addr = iface->addr.s_addr;
|
|
|
|
if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "bind(udp_notify): %s\n", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
if (AddMulticastMembership(sssdp, iface) < 0)
|
|
{
|
|
DPRINTF(E_WARN, L_SSDP, "Failed to add multicast membership for address %s\n",
|
|
iface->str);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static const char * const known_service_types[] =
|
|
{
|
|
uuidvalue,
|
|
"upnp:rootdevice",
|
|
"urn:schemas-upnp-org:device:MediaServer:",
|
|
"urn:schemas-upnp-org:service:ContentDirectory:",
|
|
"urn:schemas-upnp-org:service:ConnectionManager:",
|
|
"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:",
|
|
0
|
|
};
|
|
|
|
static void
|
|
_usleep(long min, long max)
|
|
{
|
|
struct timespec sleep_time;
|
|
long usecs = min + rand() / (RAND_MAX / (max - min + 1) + 1);
|
|
|
|
sleep_time.tv_sec = 0;
|
|
sleep_time.tv_nsec = usecs * 1000;
|
|
nanosleep(&sleep_time, NULL);
|
|
}
|
|
|
|
/* not really an SSDP "announce" as it is the response
|
|
* to a SSDP "M-SEARCH" */
|
|
static void
|
|
SendSSDPResponse(int s, struct sockaddr_in sockname, int st_no,
|
|
const char *host, unsigned short port, socklen_t len_r)
|
|
{
|
|
int l, n;
|
|
char buf[512];
|
|
char tmstr[30];
|
|
time_t tm = time(NULL);
|
|
|
|
/*
|
|
* follow guideline from document "UPnP Device Architecture 1.0"
|
|
* uppercase is recommended.
|
|
* DATE: is recommended
|
|
* SERVER: OS/ver UPnP/1.0 minidlna/1.0
|
|
* - check what to put in the 'Cache-Control' header
|
|
* */
|
|
strftime(tmstr, sizeof(tmstr), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tm));
|
|
l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
|
|
"CACHE-CONTROL: max-age=%u\r\n"
|
|
"DATE: %s\r\n"
|
|
"ST: %s%s\r\n"
|
|
"USN: %s%s%s%s\r\n"
|
|
"EXT:\r\n"
|
|
"SERVER: " MINIDLNA_SERVER_STRING "\r\n"
|
|
"LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n"
|
|
"Content-Length: 0\r\n"
|
|
"\r\n",
|
|
(runtime_vars.notify_interval<<1)+10,
|
|
tmstr,
|
|
known_service_types[st_no],
|
|
(st_no > 1 ? "1" : ""),
|
|
uuidvalue,
|
|
(st_no > 0 ? "::" : ""),
|
|
(st_no > 0 ? known_service_types[st_no] : ""),
|
|
(st_no > 1 ? "1" : ""),
|
|
host, (unsigned int)port);
|
|
DPRINTF(E_DEBUG, L_SSDP, "Sending M-SEARCH response to %s:%d ST: %s\n",
|
|
inet_ntoa(sockname.sin_addr), ntohs(sockname.sin_port),
|
|
known_service_types[st_no]);
|
|
n = sendto(s, buf, l, 0,
|
|
(struct sockaddr *)&sockname, len_r);
|
|
if (n < 0)
|
|
DPRINTF(E_ERROR, L_SSDP, "sendto(udp): %s\n", strerror(errno));
|
|
}
|
|
|
|
void
|
|
SendSSDPNotifies(int s, const char *host, unsigned short port,
|
|
unsigned int interval)
|
|
{
|
|
struct sockaddr_in sockname;
|
|
int l, n, dup, i=0;
|
|
unsigned int lifetime;
|
|
char bufr[512];
|
|
|
|
memset(&sockname, 0, sizeof(struct sockaddr_in));
|
|
sockname.sin_family = AF_INET;
|
|
sockname.sin_port = htons(SSDP_PORT);
|
|
sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
|
|
lifetime = (interval << 1) + 10;
|
|
|
|
for (dup = 0; dup < 2; dup++)
|
|
{
|
|
if (dup)
|
|
_usleep(150000, 250000);
|
|
i = 0;
|
|
while (known_service_types[i])
|
|
{
|
|
l = snprintf(bufr, sizeof(bufr),
|
|
"NOTIFY * HTTP/1.1\r\n"
|
|
"HOST:%s:%d\r\n"
|
|
"CACHE-CONTROL:max-age=%u\r\n"
|
|
"LOCATION:http://%s:%d" ROOTDESC_PATH"\r\n"
|
|
"SERVER: " MINIDLNA_SERVER_STRING "\r\n"
|
|
"NT:%s%s\r\n"
|
|
"USN:%s%s%s%s\r\n"
|
|
"NTS:ssdp:alive\r\n"
|
|
"\r\n",
|
|
SSDP_MCAST_ADDR, SSDP_PORT,
|
|
lifetime,
|
|
host, port,
|
|
known_service_types[i],
|
|
(i > 1 ? "1" : ""),
|
|
uuidvalue,
|
|
(i > 0 ? "::" : ""),
|
|
(i > 0 ? known_service_types[i] : ""),
|
|
(i > 1 ? "1" : ""));
|
|
if (l >= sizeof(bufr))
|
|
{
|
|
DPRINTF(E_WARN, L_SSDP, "SendSSDPNotifies(): truncated output\n");
|
|
l = sizeof(bufr);
|
|
}
|
|
DPRINTF(E_MAXDEBUG, L_SSDP, "Sending ssdp:alive [%d]\n", s);
|
|
n = sendto(s, bufr, l, 0,
|
|
(struct sockaddr *)&sockname, sizeof(struct sockaddr_in));
|
|
if (n < 0)
|
|
DPRINTF(E_ERROR, L_SSDP, "sendto(udp_notify=%d, %s): %s\n", s, host, strerror(errno));
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
ParseUPnPClient(char *location)
|
|
{
|
|
char buf[8192];
|
|
struct sockaddr_in dest;
|
|
int s, n, do_headers = 0, nread = 0;
|
|
struct timeval tv;
|
|
char *addr, *path, *port_str;
|
|
long port = 80;
|
|
char *off = NULL, *p;
|
|
int content_len = sizeof(buf);
|
|
struct NameValueParserData xml;
|
|
struct client_cache_s *client;
|
|
int type = 0;
|
|
char *model, *serial, *name;
|
|
|
|
if (strncmp(location, "http://", 7) != 0)
|
|
return;
|
|
path = location + 7;
|
|
port_str = strsep(&path, "/");
|
|
if (!path)
|
|
return;
|
|
addr = strsep(&port_str, ":");
|
|
if (port_str)
|
|
{
|
|
port = strtol(port_str, NULL, 10);
|
|
if (!port)
|
|
port = 80;
|
|
}
|
|
|
|
memset(&dest, '\0', sizeof(dest));
|
|
if (!inet_aton(addr, &dest.sin_addr))
|
|
return;
|
|
/* Check if the client is already in cache */
|
|
dest.sin_family = AF_INET;
|
|
dest.sin_port = htons(port);
|
|
|
|
s = socket(PF_INET, SOCK_STREAM, 0);
|
|
if (s < 0)
|
|
return;
|
|
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 500000;
|
|
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
|
|
|
if (connect(s, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0)
|
|
goto close;
|
|
|
|
n = snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n"
|
|
"HOST: %s:%ld\r\n\r\n",
|
|
path, addr, port);
|
|
if (write(s, buf, n) < 1)
|
|
goto close;
|
|
|
|
while ((n = read(s, buf+nread, sizeof(buf)-nread-1)) > 0)
|
|
{
|
|
nread += n;
|
|
buf[nread] = '\0';
|
|
n = nread - 4;
|
|
p = buf;
|
|
|
|
while (!off && (n-- > 0))
|
|
{
|
|
if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n')
|
|
{
|
|
off = p + 4;
|
|
do_headers = 1;
|
|
}
|
|
p++;
|
|
}
|
|
if (!off)
|
|
continue;
|
|
|
|
if (do_headers)
|
|
{
|
|
p = buf;
|
|
if (strncmp(p, "HTTP/", 5) != 0)
|
|
goto close;
|
|
while (*p != ' ' && *p != '\t')
|
|
p++;
|
|
/* If we don't get a 200 status, ignore it */
|
|
if (strtol(p, NULL, 10) != 200)
|
|
goto close;
|
|
p = strcasestr(p, "Content-Length:");
|
|
if (p)
|
|
content_len = strtol(p+15, NULL, 10);
|
|
do_headers = 0;
|
|
}
|
|
if ((buf + nread - off) >= content_len)
|
|
break;
|
|
}
|
|
close:
|
|
close(s);
|
|
if (!off)
|
|
return;
|
|
nread -= off - buf;
|
|
ParseNameValue(off, nread, &xml, 0);
|
|
model = GetValueFromNameValueList(&xml, "modelName");
|
|
serial = GetValueFromNameValueList(&xml, "serialNumber");
|
|
name = GetValueFromNameValueList(&xml, "friendlyName");
|
|
if (model)
|
|
{
|
|
int i;
|
|
DPRINTF(E_DEBUG, L_SSDP, "Model: %s\n", model);
|
|
for (i = 0; client_types[i].name; i++)
|
|
{
|
|
if (client_types[i].match_type != EModelName)
|
|
continue;
|
|
if (strstr(model, client_types[i].match) != NULL)
|
|
{
|
|
type = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Special Samsung handling. It's very hard to tell Series A from B */
|
|
if (type > 0 && client_types[type].type == ESamsungSeriesB)
|
|
{
|
|
if (serial)
|
|
{
|
|
DPRINTF(E_DEBUG, L_SSDP, "Serial: %s\n", serial);
|
|
/* The Series B I saw was 20081224DMR. Series A should be older than that. */
|
|
if (atoi(serial) < 20081201)
|
|
type = 0;
|
|
}
|
|
else
|
|
{
|
|
type = 0;
|
|
}
|
|
}
|
|
|
|
if (type == 0 && name != NULL)
|
|
{
|
|
for (i = 0; client_types[i].name; i++)
|
|
{
|
|
if (client_types[i].match_type != EFriendlyNameSSDP)
|
|
continue;
|
|
if (strcmp(name, client_types[i].match) == 0)
|
|
{
|
|
type = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ClearNameValueList(&xml);
|
|
if (!type)
|
|
return;
|
|
/* Add this client to the cache if it's not there already. */
|
|
client = SearchClientCache(dest.sin_addr, 1);
|
|
if (!client)
|
|
{
|
|
AddClientCache(dest.sin_addr, type);
|
|
}
|
|
else
|
|
{
|
|
client->type = &client_types[type];
|
|
client->age = time(NULL);
|
|
}
|
|
}
|
|
|
|
/* ProcessSSDPRequest()
|
|
* process SSDP M-SEARCH requests and responds to them */
|
|
void
|
|
ProcessSSDPRequest(struct event *ev)
|
|
{
|
|
int s = ev->fd;
|
|
int n;
|
|
char bufr[1500];
|
|
struct sockaddr_in sendername;
|
|
int i;
|
|
char *st = NULL, *mx = NULL, *man = NULL, *mx_end = NULL;
|
|
int man_len = 0;
|
|
socklen_t len_r = sizeof(struct sockaddr_in);
|
|
#ifdef __linux__
|
|
char cmbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];
|
|
struct iovec iovec = {
|
|
.iov_base = bufr,
|
|
.iov_len = sizeof(bufr)-1
|
|
};
|
|
struct msghdr mh = {
|
|
.msg_name = &sendername,
|
|
.msg_namelen = sizeof(struct sockaddr_in),
|
|
.msg_iov = &iovec,
|
|
.msg_iovlen = 1,
|
|
.msg_control = cmbuf,
|
|
.msg_controllen = sizeof(cmbuf)
|
|
};
|
|
|
|
n = recvmsg(s, &mh, 0);
|
|
#else
|
|
|
|
n = recvfrom(s, bufr, sizeof(bufr)-1, 0,
|
|
(struct sockaddr *)&sendername, &len_r);
|
|
len_r = MIN(len_r, sizeof(struct sockaddr_in));
|
|
#endif
|
|
if (n < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
bufr[n] = '\0';
|
|
n -= 2;
|
|
|
|
if (memcmp(bufr, "NOTIFY", 6) == 0)
|
|
{
|
|
char *loc = NULL, *srv = NULL, *nts = NULL, *nt = NULL;
|
|
int loc_len = 0;
|
|
//DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP notify:\n%.*s", n, bufr);
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if( bufr[i] == '*' )
|
|
break;
|
|
}
|
|
if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL)
|
|
return;
|
|
while (i < n)
|
|
{
|
|
while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
|
|
i++;
|
|
i += 2;
|
|
if (strncasecmp(bufr+i, "SERVER:", 7) == 0)
|
|
{
|
|
srv = bufr+i+7;
|
|
while (*srv == ' ' || *srv == '\t')
|
|
srv++;
|
|
}
|
|
else if (strncasecmp(bufr+i, "LOCATION:", 9) == 0)
|
|
{
|
|
loc = bufr+i+9;
|
|
while (*loc == ' ' || *loc == '\t')
|
|
loc++;
|
|
while (loc[loc_len]!='\r' && loc[loc_len]!='\n')
|
|
loc_len++;
|
|
}
|
|
else if (strncasecmp(bufr+i, "NTS:", 4) == 0)
|
|
{
|
|
nts = bufr+i+4;
|
|
while (*nts == ' ' || *nts == '\t')
|
|
nts++;
|
|
}
|
|
else if (strncasecmp(bufr+i, "NT:", 3) == 0)
|
|
{
|
|
nt = bufr+i+3;
|
|
while(*nt == ' ' || *nt == '\t')
|
|
nt++;
|
|
}
|
|
}
|
|
if (!loc || !srv || !nt || !nts || (strncmp(nts, "ssdp:alive", 10) != 0) ||
|
|
(strncmp(nt, "urn:schemas-upnp-org:device:MediaRenderer", 41) != 0))
|
|
return;
|
|
loc[loc_len] = '\0';
|
|
if ((strncmp(srv, "Allegro-Software-RomPlug", 24) == 0) || /* Roku */
|
|
(strstr(loc, "SamsungMRDesc.xml") != NULL) || /* Samsung TV */
|
|
(strstrc(srv, "DigiOn DiXiM", '\r') != NULL)) /* Marantz Receiver */
|
|
{
|
|
/* Check if the client is already in cache */
|
|
struct client_cache_s *client = SearchClientCache(sendername.sin_addr, 1);
|
|
if (client)
|
|
{
|
|
if (client->type->type < EStandardDLNA150 &&
|
|
client->type->type != ESamsungSeriesA)
|
|
{
|
|
client->age = time(NULL);
|
|
return;
|
|
}
|
|
}
|
|
ParseUPnPClient(loc);
|
|
}
|
|
}
|
|
else if (memcmp(bufr, "M-SEARCH", 8) == 0)
|
|
{
|
|
int st_len = 0, mx_len = 0, mx_val = 0;
|
|
//DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s\n", n, bufr);
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if (bufr[i] == '*')
|
|
break;
|
|
}
|
|
if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL)
|
|
return;
|
|
while (i < n)
|
|
{
|
|
while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
|
|
i++;
|
|
i += 2;
|
|
if (strncasecmp(bufr+i, "ST:", 3) == 0)
|
|
{
|
|
st = bufr+i+3;
|
|
st_len = 0;
|
|
while (*st == ' ' || *st == '\t')
|
|
st++;
|
|
while (st[st_len]!='\r' && st[st_len]!='\n')
|
|
st_len++;
|
|
}
|
|
else if (strncasecmp(bufr+i, "MX:", 3) == 0)
|
|
{
|
|
mx = bufr+i+3;
|
|
mx_len = 0;
|
|
while (*mx == ' ' || *mx == '\t')
|
|
mx++;
|
|
while (mx[mx_len]!='\r' && mx[mx_len]!='\n')
|
|
mx_len++;
|
|
mx_val = strtol(mx, &mx_end, 10);
|
|
}
|
|
else if (strncasecmp(bufr+i, "MAN:", 4) == 0)
|
|
{
|
|
man = bufr+i+4;
|
|
man_len = 0;
|
|
while (*man == ' ' || *man == '\t')
|
|
man++;
|
|
while (man[man_len]!='\r' && man[man_len]!='\n')
|
|
man_len++;
|
|
}
|
|
}
|
|
/*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n",
|
|
inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port) );*/
|
|
if (GETFLAG(DLNA_STRICT_MASK) && (ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900))
|
|
{
|
|
DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n",
|
|
inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
|
|
}
|
|
else if (!man || (strncmp(man, "\"ssdp:discover\"", 15) != 0))
|
|
{
|
|
DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n",
|
|
inet_ntoa(sendername.sin_addr), "MAN", man_len, man);
|
|
}
|
|
else if (!mx || mx == mx_end || mx_val < 0)
|
|
{
|
|
DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n",
|
|
inet_ntoa(sendername.sin_addr), "MX", mx_len, mx);
|
|
}
|
|
else if (st && (st_len > 0))
|
|
{
|
|
int l;
|
|
#ifdef __linux__
|
|
char host[40] = "127.0.0.1";
|
|
struct cmsghdr *cmsg;
|
|
|
|
/* find the interface we received the msg from */
|
|
for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
|
|
{
|
|
struct in_addr addr;
|
|
struct in_pktinfo *pi;
|
|
/* ignore the control headers that don't match what we want */
|
|
if (cmsg->cmsg_level != IPPROTO_IP ||
|
|
cmsg->cmsg_type != IP_PKTINFO)
|
|
continue;
|
|
|
|
pi = (struct in_pktinfo *)CMSG_DATA(cmsg);
|
|
addr = pi->ipi_spec_dst;
|
|
inet_ntop(AF_INET, &addr, host, sizeof(host));
|
|
}
|
|
#else
|
|
const char *host;
|
|
int iface = 0;
|
|
/* find in which sub network the client is */
|
|
for (i = 0; i < n_lan_addr; i++)
|
|
{
|
|
if((sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr) ==
|
|
(lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
|
|
{
|
|
iface = i;
|
|
break;
|
|
}
|
|
}
|
|
if (n_lan_addr == i)
|
|
{
|
|
DPRINTF(E_DEBUG, L_SSDP, "Ignoring SSDP M-SEARCH on other interface [%s]\n",
|
|
inet_ntoa(sendername.sin_addr));
|
|
return;
|
|
}
|
|
host = lan_addr[iface].str;
|
|
#endif
|
|
DPRINTF(E_DEBUG, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n",
|
|
inet_ntoa(sendername.sin_addr),
|
|
ntohs(sendername.sin_port),
|
|
st_len, st, mx_len, mx, man_len, man);
|
|
/* Responds to request with a device as ST header */
|
|
for (i = 0; known_service_types[i]; i++)
|
|
{
|
|
l = strlen(known_service_types[i]);
|
|
if ((l > st_len) || (memcmp(st, known_service_types[i], l) != 0))
|
|
continue;
|
|
if (st_len != l)
|
|
{
|
|
/* Check version number - we only support 1. */
|
|
if ((st[l-1] == ':') && (st[l] == '1'))
|
|
l++;
|
|
while (l < st_len)
|
|
{
|
|
if (isdigit(st[l]))
|
|
break;
|
|
if (isspace(st[l]))
|
|
{
|
|
l++;
|
|
continue;
|
|
}
|
|
DPRINTF(E_MAXDEBUG, L_SSDP,
|
|
"Ignoring SSDP M-SEARCH with bad extra data '%c' [%s]\n",
|
|
st[l], inet_ntoa(sendername.sin_addr));
|
|
break;
|
|
}
|
|
if (l != st_len)
|
|
break;
|
|
}
|
|
_usleep(13000, 20000);
|
|
SendSSDPResponse(s, sendername, i, host,
|
|
(unsigned short)runtime_vars.port, len_r);
|
|
return;
|
|
}
|
|
/* Responds to request with ST: ssdp:all */
|
|
/* strlen("ssdp:all") == 8 */
|
|
if ((st_len == 8) && (memcmp(st, "ssdp:all", 8) == 0))
|
|
{
|
|
_usleep(13000, 30000);
|
|
for (i=0; known_service_types[i]; i++)
|
|
{
|
|
l = strlen(known_service_types[i]);
|
|
SendSSDPResponse(s, sendername, i, host,
|
|
(unsigned short)runtime_vars.port,
|
|
len_r);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINTF(E_INFO, L_SSDP, "Invalid SSDP M-SEARCH from %s:%d\n",
|
|
inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
|
|
}
|
|
}
|
|
else if (memcmp(bufr, "YOUKU-NOTIFY", 12) == 0)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DPRINTF(E_WARN, L_SSDP, "Unknown udp packet received from %s:%d\n",
|
|
inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
|
|
}
|
|
}
|
|
|
|
/* This will broadcast ssdp:byebye notifications to inform
|
|
* the network that UPnP is going down. */
|
|
int
|
|
SendSSDPGoodbyes(int s)
|
|
{
|
|
struct sockaddr_in sockname;
|
|
int n, l;
|
|
int i;
|
|
int dup, ret = 0;
|
|
char bufr[512];
|
|
|
|
memset(&sockname, 0, sizeof(struct sockaddr_in));
|
|
sockname.sin_family = AF_INET;
|
|
sockname.sin_port = htons(SSDP_PORT);
|
|
sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
|
|
|
|
for (dup = 0; dup < 2; dup++)
|
|
{
|
|
for (i = 0; known_service_types[i]; i++)
|
|
{
|
|
l = snprintf(bufr, sizeof(bufr),
|
|
"NOTIFY * HTTP/1.1\r\n"
|
|
"HOST:%s:%d\r\n"
|
|
"NT:%s%s\r\n"
|
|
"USN:%s%s%s%s\r\n"
|
|
"NTS:ssdp:byebye\r\n"
|
|
"\r\n",
|
|
SSDP_MCAST_ADDR, SSDP_PORT,
|
|
known_service_types[i],
|
|
(i > 1 ? "1" : ""), uuidvalue,
|
|
(i > 0 ? "::" : ""),
|
|
(i > 0 ? known_service_types[i] : ""),
|
|
(i > 1 ? "1" : ""));
|
|
DPRINTF(E_MAXDEBUG, L_SSDP, "Sending ssdp:byebye [%d]\n", s);
|
|
n = sendto(s, bufr, l, 0,
|
|
(struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
|
|
if (n < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "sendto(udp_shutdown=%d): %s\n", s, strerror(errno));
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* SubmitServicesToMiniSSDPD() :
|
|
* register services offered by MiniUPnPd to a running instance of
|
|
* MiniSSDPd */
|
|
int
|
|
SubmitServicesToMiniSSDPD(const char *host, unsigned short port)
|
|
{
|
|
struct sockaddr_un addr;
|
|
int s;
|
|
unsigned char buffer[2048];
|
|
char strbuf[256];
|
|
unsigned char *p;
|
|
int i, l;
|
|
|
|
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (s < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "socket(unix): %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
addr.sun_family = AF_UNIX;
|
|
strncpyt(addr.sun_path, minissdpdsocketpath, sizeof(addr.sun_path));
|
|
if (connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "connect(\"%s\"): %s",
|
|
minissdpdsocketpath, strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
for (i = 0; known_service_types[i]; i++)
|
|
{
|
|
buffer[0] = 4;
|
|
p = buffer + 1;
|
|
l = strlen(known_service_types[i]);
|
|
if (i > 0)
|
|
l++;
|
|
CODELENGTH(l, p);
|
|
memcpy(p, known_service_types[i], l);
|
|
if (i > 0)
|
|
p[l-1] = '1';
|
|
p += l;
|
|
l = snprintf(strbuf, sizeof(strbuf), "%s::%s%s",
|
|
uuidvalue, known_service_types[i], (i==0)?"":"1");
|
|
CODELENGTH(l, p);
|
|
memcpy(p, strbuf, l);
|
|
p += l;
|
|
l = strlen(MINIDLNA_SERVER_STRING);
|
|
CODELENGTH(l, p);
|
|
memcpy(p, MINIDLNA_SERVER_STRING, l);
|
|
p += l;
|
|
l = snprintf(strbuf, sizeof(strbuf), "http://%s:%u" ROOTDESC_PATH,
|
|
host, (unsigned int)port);
|
|
CODELENGTH(l, p);
|
|
memcpy(p, strbuf, l);
|
|
p += l;
|
|
if(write(s, buffer, p - buffer) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_SSDP, "write(): %s", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
}
|
|
close(s);
|
|
return 0;
|
|
}
|
|
|