miniupnp/miniupnpd/minissdp.c

1612 lines
48 KiB
C
Raw Normal View History

2024-01-15 00:01:16 +00:00
/* $Id: minissdp.c,v 1.107 2024/01/15 00:20:21 nanard Exp $ */
2017-05-27 08:25:53 +00:00
/* vim: tabstop=4 shiftwidth=4 noexpandtab
* MiniUPnP project
2018-01-16 01:06:46 +00:00
* http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
2024-01-15 00:01:16 +00:00
* (c) 2006-2024 Thomas Bernard
2011-09-28 19:13:20 +00:00
* This software is subject to the conditions detailed
* in the LICENCE file provided within the distribution */
#include <stdio.h>
#include <stdlib.h>
2011-09-28 19:13:20 +00:00
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
2011-09-28 19:13:20 +00:00
#include <syslog.h>
#ifdef IP_RECVIF
#include <sys/types.h>
#include <sys/uio.h>
#include <net/if.h>
#include <net/if_dl.h>
#endif
2011-09-28 19:13:20 +00:00
#include "config.h"
#if defined(ENABLE_IPV6) && defined(UPNP_STRICT)
#include <ifaddrs.h>
#endif /* defined(ENABLE_IPV6) && defined(UPNP_STRICT) */
2011-09-28 19:13:20 +00:00
#include "upnpdescstrings.h"
#include "miniupnpdpath.h"
#include "upnphttp.h"
#include "upnpglobalvars.h"
#include "minissdp.h"
#include "upnputils.h"
#include "getroute.h"
2014-02-25 10:15:30 +00:00
#include "asyncsendto.h"
2011-09-28 19:13:20 +00:00
#include "codelength.h"
#include "macros.h"
2011-09-28 19:13:20 +00:00
#ifndef MIN
#define MIN(x,y) (((x)<(y))?(x):(y))
#endif /* MIN */
2011-09-28 19:13:20 +00:00
/* SSDP ip/port */
#define SSDP_PORT (1900)
#define SSDP_MCAST_ADDR ("239.255.255.250")
#define LL_SSDP_MCAST_ADDR "FF02::C"
#define SL_SSDP_MCAST_ADDR "FF05::C"
#define GL_SSDP_MCAST_ADDR "FF0E::C"
2011-09-28 19:13:20 +00:00
/* AddMulticastMembership()
* param s socket
* param lan_addr lan address
2011-09-28 19:13:20 +00:00
*/
static int
AddMulticastMembership(int s, struct lan_addr_s * lan_addr)
2011-09-28 19:13:20 +00:00
{
#ifndef HAVE_IP_MREQN
/* The ip_mreqn structure appeared in Linux 2.4. */
struct ip_mreq imr; /* Ip multicast membership */
#else /* HAVE_IP_MREQN */
struct ip_mreqn imr; /* Ip multicast membership */
#endif /* HAVE_IP_MREQN */
2011-09-28 19:13:20 +00:00
2019-05-02 10:09:28 +00:00
/* setting up imr structure */
imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR);
/*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/
#ifndef HAVE_IP_MREQN
imr.imr_interface.s_addr = lan_addr->addr.s_addr;
#else /* HAVE_IP_MREQN */
2019-05-02 10:09:28 +00:00
imr.imr_address.s_addr = lan_addr->addr.s_addr;
#ifndef MULTIPLE_EXTERNAL_IP
#ifdef ENABLE_IPV6
imr.imr_ifindex = lan_addr->index;
#else /* ENABLE_IPV6 */
2019-05-02 10:09:28 +00:00
imr.imr_ifindex = if_nametoindex(lan_addr->ifname);
#endif /* ENABLE_IPV6 */
#else /* MULTIPLE_EXTERNAL_IP */
2019-05-02 10:09:28 +00:00
imr.imr_ifindex = 0;
#endif /* MULTIPLE_EXTERNAL_IP */
#endif /* HAVE_IP_MREQN */
if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(imr)) < 0)
2011-09-28 19:13:20 +00:00
{
2019-05-02 10:09:28 +00:00
syslog(LOG_ERR, "setsockopt(udp, IP_ADD_MEMBERSHIP): %m");
2011-09-28 19:13:20 +00:00
return -1;
2019-05-02 10:09:28 +00:00
}
2011-09-28 19:13:20 +00:00
return 0;
}
/* AddMulticastMembershipIPv6()
* param s socket (IPv6)
* param ifindex : interface index (0 : All interfaces) */
2011-09-28 19:13:20 +00:00
#ifdef ENABLE_IPV6
static int
AddMulticastMembershipIPv6(int s, unsigned int ifindex)
2011-09-28 19:13:20 +00:00
{
struct ipv6_mreq mr;
memset(&mr, 0, sizeof(mr));
mr.ipv6mr_interface = ifindex; /* 0 : all interfaces */
2011-09-28 19:13:20 +00:00
#ifndef IPV6_ADD_MEMBERSHIP
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#endif
inet_pton(AF_INET6, LL_SSDP_MCAST_ADDR, &mr.ipv6mr_multiaddr);
2011-09-28 19:13:20 +00:00
if(setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mr, sizeof(struct ipv6_mreq)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp, IPV6_ADD_MEMBERSHIP): %m");
return -1;
}
inet_pton(AF_INET6, SL_SSDP_MCAST_ADDR, &mr.ipv6mr_multiaddr);
if(setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mr, sizeof(struct ipv6_mreq)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp, IPV6_ADD_MEMBERSHIP): %m");
return -1;
}
inet_pton(AF_INET6, GL_SSDP_MCAST_ADDR, &mr.ipv6mr_multiaddr);
if(setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mr, sizeof(struct ipv6_mreq)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp, IPV6_ADD_MEMBERSHIP): %m");
return -1;
}
2011-09-28 19:13:20 +00:00
return 0;
}
#endif
#if defined(ENABLE_IPV6) && defined(UPNP_STRICT)
static int get_link_local_addr(unsigned scope_id, struct in6_addr * addr6)
{
struct ifaddrs * ifap;
struct ifaddrs * ife;
if(getifaddrs(&ifap)<0) {
syslog(LOG_ERR, "getifaddrs: %m");
return -1;
}
for(ife = ifap; ife != NULL; ife = ife->ifa_next) {
if(ife->ifa_addr == NULL) continue;
if(ife->ifa_addr->sa_family != AF_INET6) continue;
if(!IN6_IS_ADDR_LINKLOCAL(&(((const struct sockaddr_in6 *)ife->ifa_addr)->sin6_addr))) continue;
if(scope_id != if_nametoindex(ife->ifa_name)) continue;
memcpy(addr6, &(((const struct sockaddr_in6 *)ife->ifa_addr)->sin6_addr), sizeof(struct in6_addr));
break;
}
freeifaddrs(ifap);
return 0;
}
#endif /* defined(ENABLE_IPV6) && defined(UPNP_STRICT) */
/* Open and configure the socket listening for
2011-09-28 19:13:20 +00:00
* SSDP udp packets sent on 239.255.255.250 port 1900
* SSDP v6 udp packets sent on FF02::C, or FF05::C, port 1900 */
int
OpenAndConfSSDPReceiveSocket(int ipv6)
{
int s;
struct sockaddr_storage sockname;
socklen_t sockname_len;
struct lan_addr_s * lan_addr;
const int on = 1;
2011-09-28 19:13:20 +00:00
if( (s = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0)) < 0)
{
syslog(LOG_ERR, "%s: socket(udp): %m",
"OpenAndConfSSDPReceiveSocket");
2011-09-28 19:13:20 +00:00
return -1;
}
memset(&sockname, 0, sizeof(struct sockaddr_storage));
#ifdef ENABLE_IPV6
if(ipv6)
{
2011-09-28 19:13:20 +00:00
struct sockaddr_in6 * saddr = (struct sockaddr_in6 *)&sockname;
saddr->sin6_family = AF_INET6;
saddr->sin6_port = htons(SSDP_PORT);
saddr->sin6_addr = ipv6_bind_addr;
2011-09-28 19:13:20 +00:00
sockname_len = sizeof(struct sockaddr_in6);
}
else
#endif /* ENABLE_IPV6 */
{
2011-09-28 19:13:20 +00:00
struct sockaddr_in * saddr = (struct sockaddr_in *)&sockname;
saddr->sin_family = AF_INET;
saddr->sin_port = htons(SSDP_PORT);
2018-01-09 01:32:39 +00:00
/* NOTE : it seems it doesn't work when binding on the specific address */
2011-09-28 19:13:20 +00:00
/*saddr->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/
saddr->sin_addr.s_addr = htonl(INADDR_ANY);
/*saddr->sin_addr.s_addr = inet_addr(ifaddr);*/
sockname_len = sizeof(struct sockaddr_in);
}
if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2011-09-28 19:13:20 +00:00
{
syslog(LOG_WARNING, "setsockopt(udp, SO_REUSEADDR): %m");
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0)
{
syslog(LOG_WARNING, "setsockopt(udp, SO_REUSEPORT): %m");
}
#ifdef IP_RECVIF
/* BSD */
if(!ipv6) {
if(setsockopt(s, IPPROTO_IP, IP_RECVIF, &on, sizeof(on)) < 0)
{
syslog(LOG_WARNING, "setsockopt(udp, IP_RECVIF): %m");
}
}
#elif defined(IP_PKTINFO) /* IP_RECVIF */
/* Linux */
if(!ipv6) {
if(setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)) < 0)
{
syslog(LOG_WARNING, "setsockopt(udp, IP_PKTINFO): %m");
}
}
#endif /* IP_PKTINFO */
#if defined(ENABLE_IPV6) && defined(IPV6_RECVPKTINFO)
if(ipv6) {
if(setsockopt(s, IPPROTO_IP, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0)
{
syslog(LOG_WARNING, "setsockopt(udp, IPV6_RECVPKTINFO): %m");
}
}
#endif /* defined(ENABLE_IPV6) && defined(IPV6_RECVPKTINFO) */
2011-09-28 19:13:20 +00:00
if(!set_non_blocking(s))
{
syslog(LOG_WARNING, "%s: set_non_blocking(): %m",
"OpenAndConfSSDPReceiveSocket");
}
2011-09-28 19:13:20 +00:00
#if defined(SO_BINDTODEVICE) && !defined(MULTIPLE_EXTERNAL_IP)
/* One and only one LAN interface */
if(lan_addrs.lh_first != NULL && lan_addrs.lh_first->list.le_next == NULL
&& lan_addrs.lh_first->ifname[0] != '\0')
{
if(setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
lan_addrs.lh_first->ifname,
strlen(lan_addrs.lh_first->ifname)) < 0)
2015-08-26 08:04:23 +00:00
syslog(LOG_WARNING, "%s: setsockopt(udp%s, SO_BINDTODEVICE, %s): %m",
"OpenAndConfSSDPReceiveSocket", ipv6 ? "6" : "",
lan_addrs.lh_first->ifname);
}
#endif /* defined(SO_BINDTODEVICE) && !defined(MULTIPLE_EXTERNAL_IP) */
2011-09-28 19:13:20 +00:00
if(bind(s, (struct sockaddr *)&sockname, sockname_len) < 0)
{
syslog(LOG_ERR, "%s: bind(udp%s): %m",
"OpenAndConfSSDPReceiveSocket", ipv6 ? "6" : "");
2011-09-28 19:13:20 +00:00
close(s);
return -1;
}
#ifdef ENABLE_IPV6
if(ipv6)
{
for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next)
{
if(AddMulticastMembershipIPv6(s, lan_addr->index) < 0)
{
syslog(LOG_WARNING,
"Failed to add IPv6 multicast membership for interface %s",
2018-03-13 10:53:33 +00:00
strlen(lan_addr->str) ? lan_addr->str : "NULL");
}
}
2011-09-28 19:13:20 +00:00
}
else
#endif
{
for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next)
{
if(AddMulticastMembership(s, lan_addr) < 0)
2011-09-28 19:13:20 +00:00
{
syslog(LOG_WARNING,
"Failed to add multicast membership for interface %s",
strlen(lan_addr->str) ? lan_addr->str : "NULL");
2011-09-28 19:13:20 +00:00
}
}
}
return s;
}
/* open the UDP socket used to send SSDP notifications to
* the multicast group reserved for them */
static int
OpenAndConfSSDPNotifySocket(struct lan_addr_s * lan_addr)
2011-09-28 19:13:20 +00:00
{
int s;
unsigned char loopchar = 0;
int bcast = 1;
unsigned char ttl = 2; /* UDA v1.1 says :
The TTL for the IP packet SHOULD default to 2 and
SHOULD be configurable. */
/* TODO: Make TTL be configurable */
#ifndef HAVE_IP_MREQN
2011-09-28 19:13:20 +00:00
struct in_addr mc_if;
#else /* HAVE_IP_MREQN */
struct ip_mreqn mc_if;
#endif /* HAVE_IP_MREQN */
2011-09-28 19:13:20 +00:00
struct sockaddr_in sockname;
2011-09-28 19:13:20 +00:00
if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
{
syslog(LOG_ERR, "socket(udp_notify): %m");
return -1;
}
#ifndef HAVE_IP_MREQN
mc_if.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/
#else /* HAVE_IP_MREQN */
mc_if.imr_address.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/
#ifdef ENABLE_IPV6
mc_if.imr_ifindex = lan_addr->index;
#else /* ENABLE_IPV6 */
mc_if.imr_ifindex = if_nametoindex(lan_addr->ifname);
#endif /* ENABLE_IPV6 */
#endif /* HAVE_IP_MREQN */
2011-09-28 19:13:20 +00:00
if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %m");
close(s);
return -1;
}
if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_IF): %m");
close(s);
return -1;
}
if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
{
syslog(LOG_WARNING, "setsockopt(udp_notify, IP_MULTICAST_TTL,): %m");
}
2011-09-28 19:13:20 +00:00
if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp_notify, SO_BROADCAST): %m");
close(s);
return -1;
}
/* bind() socket before using sendto() is not mandatory
* (sendto() will implicitly bind the socket when called on
* an unbound socket)
* here it is used to se a specific sending address */
2011-09-28 19:13:20 +00:00
memset(&sockname, 0, sizeof(struct sockaddr_in));
2019-05-02 10:09:28 +00:00
sockname.sin_family = AF_INET;
sockname.sin_addr.s_addr = lan_addr->addr.s_addr; /*inet_addr(addr);*/
2011-09-28 19:13:20 +00:00
2019-05-02 10:09:28 +00:00
if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
2011-09-28 19:13:20 +00:00
{
syslog(LOG_ERR, "bind(udp_notify): %m");
close(s);
return -1;
2019-05-02 10:09:28 +00:00
}
2011-09-28 19:13:20 +00:00
return s;
}
#ifdef ENABLE_IPV6
/* open the UDP socket used to send SSDP notifications to
* the multicast group reserved for them. IPv6 */
static int
OpenAndConfSSDPNotifySocketIPv6(struct lan_addr_s * lan_addr)
{
int s;
unsigned int loop = 0;
2016-02-19 13:20:31 +00:00
/* UDA 2.0 : The hop limit of each IP packet for a Site-Local scope
* multicast message SHALL be configurable and SHOULD default to 10 */
int hop_limit = 10;
struct sockaddr_in6 sockname;
s = socket(PF_INET6, SOCK_DGRAM, 0);
if(s < 0)
{
syslog(LOG_ERR, "socket(udp_notify IPv6): %m");
return -1;
}
if(setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &lan_addr->index, sizeof(lan_addr->index)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp_notify IPv6, IPV6_MULTICAST_IF, %u): %m", lan_addr->index);
close(s);
return -1;
}
if(setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp_notify, IPV6_MULTICAST_LOOP): %m");
close(s);
return -1;
}
2016-02-19 13:20:31 +00:00
if(setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hop_limit, sizeof(hop_limit)) < 0)
{
syslog(LOG_ERR, "setsockopt(udp_notify, IPV6_MULTICAST_HOPS): %m");
close(s);
return -1;
}
/* bind() socket before using sendto() is not mandatory
* (sendto() will implicitly bind the socket when called on
* an unbound socket)
* but explicit bind permits to set port/scope_id/etc. */
memset(&sockname, 0, sizeof(sockname));
sockname.sin6_family = AF_INET6;
sockname.sin6_addr = in6addr_any;
/*sockname.sin6_port = htons(port);*/
/*sockname.sin6_scope_id = if_index;*/
if(bind(s, (struct sockaddr *)&sockname, sizeof(sockname)) < 0)
{
syslog(LOG_ERR, "bind(udp_notify IPv6): %m");
close(s);
return -1;
}
return s;
}
#endif
2011-09-28 19:13:20 +00:00
int
OpenAndConfSSDPNotifySockets(int * sockets)
/*OpenAndConfSSDPNotifySockets(int * sockets,
struct lan_addr_s * lan_addr, int n_lan_addr)*/
{
int i;
2011-09-28 19:13:20 +00:00
struct lan_addr_s * lan_addr;
for(i=0, lan_addr = lan_addrs.lh_first;
lan_addr != NULL;
lan_addr = lan_addr->list.le_next)
2011-09-28 19:13:20 +00:00
{
sockets[i] = OpenAndConfSSDPNotifySocket(lan_addr);
2011-09-28 19:13:20 +00:00
if(sockets[i] < 0)
goto error;
i++;
#ifdef ENABLE_IPV6
if(GETFLAG(IPV6DISABLEDMASK))
{
sockets[i] = -1;
}
else
{
sockets[i] = OpenAndConfSSDPNotifySocketIPv6(lan_addr);
if(sockets[i] < 0)
goto error;
}
i++;
#endif
2011-09-28 19:13:20 +00:00
}
return 0;
error:
while(--i >= 0)
{
close(sockets[i]);
sockets[i] = -1;
}
return -1;
2011-09-28 19:13:20 +00:00
}
/*
* response from a LiveBox (Wanadoo)
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Thu, 01 Jan 1970 04:03:23 GMT
EXT:
LOCATION: http://192.168.0.1:49152/gatedesc.xml
SERVER: Linux/2.4.17, UPnP/1.0, Intel SDK for UPnP devices /1.2
ST: upnp:rootdevice
USN: uuid:75802409-bccb-40e7-8e6c-fa095ecce13e::upnp:rootdevice
* response from a Linksys 802.11b :
HTTP/1.1 200 OK
Cache-Control:max-age=120
Location:http://192.168.5.1:5678/rootDesc.xml
Server:NT/5.0 UPnP/1.0
ST:upnp:rootdevice
USN:uuid:upnp-InternetGatewayDevice-1_0-0090a2777777::upnp:rootdevice
EXT:
*/
/* Responds to a SSDP "M-SEARCH"
* s : socket to use
* addr : peer
* st, st_len : ST: header
* suffix : suffix for USN: header
* host, port : our HTTP host, port
2014-02-25 10:15:30 +00:00
* delay : in milli-seconds
*/
2011-09-28 19:13:20 +00:00
static void
SendSSDPResponse(int s, const struct sockaddr * addr,
const char * st, int st_len, const char * suffix,
const char * host, unsigned short http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
unsigned short https_port,
#endif
const char * uuidvalue, unsigned int delay)
2011-09-28 19:13:20 +00:00
{
int l, n;
char buf[SSDP_PACKET_MAX_LEN];
char addr_str[64];
2011-09-28 19:13:20 +00:00
socklen_t addrlen;
int st_is_uuid;
#ifdef ENABLE_HTTP_DATE
char http_date[64];
time_t t;
struct tm tm;
time(&t);
gmtime_r(&t, &tm);
strftime(http_date, sizeof(http_date),
"%a, %d %b %Y %H:%M:%S GMT", &tm);
#endif
st_is_uuid = (st_len == (int)strlen(uuidvalue)) &&
(memcmp(uuidvalue, st, st_len) == 0);
/*
2011-09-28 19:13:20 +00:00
* follow guideline from document "UPnP Device Architecture 1.0"
* uppercase is recommended.
* DATE: is recommended
* SERVER: OS/ver UPnP/1.0 miniupnpd/1.0
CACHE-CONTROL: max-age=1800 in M-SEARCH response to be compliant with UPnP UDA 1.0, 1.1 and 2.0 fixes #698 UDA 1.0 1.2.3 Discovery: Search: Response (p21) : CACHE-CONTROL Required. Must have max-age directive that specifies number of seconds the advertisement is valid. After this duration, control points should assume the device (or service) is no longer available. Should be greater than or equal to 1800 seconds (30 minutes), although exceptions are defined in the text above. Specified by UPnP vendor. Integer. UDA 1.1 1.3.3 Search response (p34) : CACHE-CONTROL REQUIRED. Field value MUST have the max-age directive (“max-age=”) followed by an integer that specifies the number of seconds the advertisement is valid. After this duration, control points SHOULD assume the device (or service) is no longer available; as long as a control point has received at least one advertisement that is still valid from a root device, any of its embedded devices or any of its services, then the control point can assume that all are available. The number of seconds SHOULD be greater than or equal to 1800 seconds (30 minutes), although exceptions are defined in the text above. Specified by UPnP vendor. Other directives MUST NOT be sent and MUST be ignored when received. UDA 2.0 1.3.3 Search response (p40) : CACHE-CONTROL Required. Field value shall have the max-age directive (“max-age=”) followed by an integer that specifies the number of seconds the advertisement is valid. After this duration, control points should assume the device (or service) is no longer available; as long as a control point has received at least one advertisement that is still valid from a root device, any of its embedded devices or any of its services, then the control point can assume that all are available. The number of seconds should be greater than or equal to 1800 seconds (30 minutes), although exceptions are defined in the text above. Specified by UPnP vendor. Other directives shall not be sent and shall be ignored when received.
2024-01-14 23:15:32 +00:00
* CACHE-CONTROL: Should be greater than or equal to 1800 seconds
*/
2011-09-28 19:13:20 +00:00
l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
CACHE-CONTROL: max-age=1800 in M-SEARCH response to be compliant with UPnP UDA 1.0, 1.1 and 2.0 fixes #698 UDA 1.0 1.2.3 Discovery: Search: Response (p21) : CACHE-CONTROL Required. Must have max-age directive that specifies number of seconds the advertisement is valid. After this duration, control points should assume the device (or service) is no longer available. Should be greater than or equal to 1800 seconds (30 minutes), although exceptions are defined in the text above. Specified by UPnP vendor. Integer. UDA 1.1 1.3.3 Search response (p34) : CACHE-CONTROL REQUIRED. Field value MUST have the max-age directive (“max-age=”) followed by an integer that specifies the number of seconds the advertisement is valid. After this duration, control points SHOULD assume the device (or service) is no longer available; as long as a control point has received at least one advertisement that is still valid from a root device, any of its embedded devices or any of its services, then the control point can assume that all are available. The number of seconds SHOULD be greater than or equal to 1800 seconds (30 minutes), although exceptions are defined in the text above. Specified by UPnP vendor. Other directives MUST NOT be sent and MUST be ignored when received. UDA 2.0 1.3.3 Search response (p40) : CACHE-CONTROL Required. Field value shall have the max-age directive (“max-age=”) followed by an integer that specifies the number of seconds the advertisement is valid. After this duration, control points should assume the device (or service) is no longer available; as long as a control point has received at least one advertisement that is still valid from a root device, any of its embedded devices or any of its services, then the control point can assume that all are available. The number of seconds should be greater than or equal to 1800 seconds (30 minutes), although exceptions are defined in the text above. Specified by UPnP vendor. Other directives shall not be sent and shall be ignored when received.
2024-01-14 23:15:32 +00:00
"CACHE-CONTROL: max-age=1800\r\n"
#ifdef ENABLE_HTTP_DATE
"DATE: %s\r\n"
#endif
2011-09-28 19:13:20 +00:00
"ST: %.*s%s\r\n"
"USN: %s%s%.*s%s\r\n"
2011-09-28 19:13:20 +00:00
"EXT:\r\n"
"SERVER: " MINIUPNPD_SERVER_STRING "\r\n"
#ifndef RANDOMIZE_URLS
2011-09-28 19:13:20 +00:00
"LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n"
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
"SECURELOCATION.UPNP.ORG: https://%s:%u" ROOTDESC_PATH "\r\n"
#endif /* ENABLE_HTTPS */
#else /* RANDOMIZE_URLS */
"LOCATION: http://%s:%u/%s" ROOTDESC_PATH "\r\n"
#ifdef ENABLE_HTTPS
"SECURELOCATION.UPNP.ORG: https://%s:%u/%s" ROOTDESC_PATH "\r\n"
#endif /* ENABLE_HTTPS */
#endif /* RANDOMIZE_URLS */
"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */
2011-09-28 19:13:20 +00:00
"01-NLS: %u\r\n" /* same as BOOTID. UDA v1.1 */
"BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
"CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
"\r\n",
#ifdef ENABLE_HTTP_DATE
http_date, /* DATE: */
#endif
st_len, st, suffix, /* ST: */
uuidvalue, st_is_uuid ? "" : "::", /* USN: 2/5 */
st_is_uuid ? 0 : st_len, st, suffix, /* USN: 3/5 */
#ifdef DYNAMIC_OS_VERSION
os_version, /* SERVER: */
#endif
host, (unsigned int)http_port, /* LOCATION: */
#ifdef RANDOMIZE_URLS
random_url, /* LOCATION: 3/3 */
#endif /* RANDOMIZE_URLS */
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
host, (unsigned int)https_port, /* SECURELOCATION.UPNP.ORG */
#ifdef RANDOMIZE_URLS
random_url, /* SECURELOCATION.UPNP.ORG 3/3 */
#endif /* RANDOMIZE_URLS */
#endif /* ENABLE_HTTPS */
upnp_bootid, /* 01-NLS: */
upnp_bootid, /* BOOTID.UPNP.ORG: */
upnp_configid); /* CONFIGID.UPNP.ORG: */
if(l<0)
{
syslog(LOG_ERR, "%s: snprintf failed %m",
"SendSSDPResponse()");
return;
}
else if((unsigned)l>=sizeof(buf))
{
syslog(LOG_WARNING, "%s: truncated output (%u>=%u)",
"SendSSDPResponse()", (unsigned)l, (unsigned)sizeof(buf));
l = sizeof(buf) - 1;
}
2011-09-28 19:13:20 +00:00
addrlen = (addr->sa_family == AF_INET6)
? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
2014-02-25 10:15:30 +00:00
n = sendto_schedule(s, buf, l, 0,
addr, addrlen, delay);
sockaddr_to_string(addr, addr_str, sizeof(addr_str));
syslog(LOG_DEBUG, "%s: %d bytes to %s ST: %.*s",
"SendSSDPResponse()",
n, addr_str, l, buf);
2011-09-28 19:13:20 +00:00
if(n < 0)
{
syslog(LOG_ERR, "%s: sendto(udp): %m",
"SendSSDPResponse()");
2011-09-28 19:13:20 +00:00
}
}
static struct {
const char * s;
const int version;
const char * uuid;
} const known_service_types[] =
2011-09-28 19:13:20 +00:00
{
{"upnp:rootdevice", 0, uuidvalue_igd},
#ifdef IGD_V2
{"urn:schemas-upnp-org:device:InternetGatewayDevice:", 2, uuidvalue_igd},
{"urn:schemas-upnp-org:device:WANConnectionDevice:", 2, uuidvalue_wcd},
{"urn:schemas-upnp-org:device:WANDevice:", 2, uuidvalue_wan},
{"urn:schemas-upnp-org:service:WANIPConnection:", 2, uuidvalue_wcd},
#ifdef ENABLE_DP_SERVICE
{"urn:schemas-upnp-org:service:DeviceProtection:", 1, uuidvalue_igd},
#endif
#ifdef ENABLE_6FC_SERVICE
{"urn:schemas-upnp-org:service:WANIPv6FirewallControl:", 1, uuidvalue_wcd},
#endif
2015-12-12 07:47:06 +00:00
#else /* IGD_V2 */
/* IGD v1 */
{"urn:schemas-upnp-org:device:InternetGatewayDevice:", 1, uuidvalue_igd},
{"urn:schemas-upnp-org:device:WANConnectionDevice:", 1, uuidvalue_wcd},
{"urn:schemas-upnp-org:device:WANDevice:", 1, uuidvalue_wan},
{"urn:schemas-upnp-org:service:WANIPConnection:", 1, uuidvalue_wcd},
2015-12-12 07:47:06 +00:00
#endif /* IGD_V2 */
{"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:", 1, uuidvalue_wan},
#ifdef ADVERTISE_WANPPPCONN
/* We use WAN IP Connection, not PPP connection,
* but buggy control points may try to use WanPPPConnection
* anyway */
{"urn:schemas-upnp-org:service:WANPPPConnection:", 1, uuidvalue_wcd},
#endif /* ADVERTISE_WANPPPCONN */
2011-09-28 19:13:20 +00:00
#ifdef ENABLE_L3F_SERVICE
{"urn:schemas-upnp-org:service:Layer3Forwarding:", 1, uuidvalue_igd},
2015-12-12 07:47:06 +00:00
#endif /* ENABLE_L3F_SERVICE */
/* we might want to support urn:schemas-wifialliance-org:device:WFADevice:1
* urn:schemas-wifialliance-org:device:WFADevice:1
* in the future */
{0, 0, 0}
2011-09-28 19:13:20 +00:00
};
/* SendSSDPNotify() sends the SSDP NOTIFY to a specific
* destination, for a specific UPnP service or device */
static void
SendSSDPNotify(int s, const struct sockaddr * dest, socklen_t dest_len,
const char * dest_str,
const char * host, unsigned short http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
unsigned short https_port,
#endif
const char * nt, const char * suffix,
const char * usn1, const char * usn2, const char * usn3,
unsigned int lifetime)
{
char bufr[SSDP_PACKET_MAX_LEN];
int n, l;
l = snprintf(bufr, sizeof(bufr),
"NOTIFY * HTTP/1.1\r\n"
"HOST: %s:%d\r\n"
"CACHE-CONTROL: max-age=%u\r\n"
#ifndef RANDOMIZE_URLS
2014-04-09 13:35:06 +00:00
"LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n"
#ifdef ENABLE_HTTPS
"SECURELOCATION.UPNP.ORG: https://%s:%u" ROOTDESC_PATH "\r\n"
#endif /* ENABLE_HTTPS */
#else /* RANDOMIZE_URLS */
"LOCATION: http://%s:%u/%s" ROOTDESC_PATH "\r\n"
#ifdef ENABLE_HTTPS
"SECURELOCATION.UPNP.ORG: https://%s:%u/%s" ROOTDESC_PATH "\r\n"
#endif /* ENABLE_HTTPS */
#endif /* RANDOMIZE_URLS */
"SERVER: " MINIUPNPD_SERVER_STRING "\r\n"
"NT: %s%s\r\n"
"USN: %s%s%s%s\r\n"
"NTS: ssdp:alive\r\n"
"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */
"01-NLS: %u\r\n" /* same as BOOTID field. UDA v1.1 */
"BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
"CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
"\r\n",
dest_str, SSDP_PORT, /* HOST: */
lifetime, /* CACHE-CONTROL: */
host, (unsigned int)http_port, /* LOCATION: */
#ifdef RANDOMIZE_URLS
random_url,
#endif /* RANDOMIZE_URLS */
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
host, (unsigned int)https_port, /* SECURE-LOCATION: */
#ifdef RANDOMIZE_URLS
random_url,
#endif /* RANDOMIZE_URLS */
#endif /* ENABLE_HTTPS */
#ifdef DYNAMIC_OS_VERSION
os_version, /* SERVER: */
#endif
nt, suffix, /* NT: */
usn1, usn2, usn3, suffix, /* USN: */
upnp_bootid, /* 01-NLS: */
upnp_bootid, /* BOOTID.UPNP.ORG: */
upnp_configid ); /* CONFIGID.UPNP.ORG: */
if(l<0) {
2014-04-09 13:35:06 +00:00
syslog(LOG_ERR, "%s: snprintf error", "SendSSDPNotify()");
return;
} else if((unsigned int)l >= sizeof(bufr)) {
syslog(LOG_WARNING, "%s: truncated output (%u>=%u)",
"SendSSDPNotify()", (unsigned)l, (unsigned)sizeof(bufr));
l = sizeof(bufr) - 1;
}
n = sendto_or_schedule(s, bufr, l, 0, dest, dest_len);
if(n < 0) {
syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s,
host ? host : "NULL");
} else if(n != l) {
syslog(LOG_NOTICE, "sendto() sent %d out of %d bytes", n, l);
}
/* Due to the unreliable nature of UDP, devices SHOULD send the entire
* set of discovery messages more than once with some delay between
* sets e.g. a few hundred milliseconds. To avoid network congestion
* discovery messages SHOULD NOT be sent more than three times. */
n = sendto_schedule(s, bufr, l, 0, dest, dest_len, 250);
if(n < 0) {
syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s,
host ? host : "NULL");
}
}
/* SendSSDPNotifies() send SSPD NOTIFY for a specific
* LAN (network interface) for all devices / services */
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
static void
SendSSDPNotifies(int s, const char * host, unsigned short http_port,
2014-04-09 13:35:06 +00:00
unsigned short https_port,
unsigned int lifetime, int ipv6)
#else
static void
SendSSDPNotifies(int s, const char * host, unsigned short http_port,
unsigned int lifetime, int ipv6)
#endif
2011-09-28 19:13:20 +00:00
{
#ifdef ENABLE_IPV6
struct sockaddr_storage sockname;
2016-02-19 13:20:45 +00:00
/* UDA 1.1 AnnexA and UDA 2.0 only allow/define the use of
* Link-Local and Site-Local multicast scopes */
static struct { const char * p1, * p2; } const mcast_addrs[] =
{ { LL_SSDP_MCAST_ADDR, "[" LL_SSDP_MCAST_ADDR "]" }, /* Link Local */
{ SL_SSDP_MCAST_ADDR, "[" SL_SSDP_MCAST_ADDR "]" }, /* Site Local */
2016-02-19 13:20:45 +00:00
#ifndef UPNP_STRICT
{ GL_SSDP_MCAST_ADDR, "[" GL_SSDP_MCAST_ADDR "]" }, /* Global */
2016-02-19 13:20:45 +00:00
#endif /* ! UPNP_STRICT */
{ NULL, NULL } };
int j;
#else /* ENABLE_IPV6 */
2011-09-28 19:13:20 +00:00
struct sockaddr_in sockname;
#endif /* ENABLE_IPV6 */
socklen_t sockname_len;
const char * dest_str;
int i;
char ver_str[4];
#ifndef ENABLE_IPV6
UNUSED(ipv6);
#endif /* ENABLE_IPV6 */
2011-09-28 19:13:20 +00:00
memset(&sockname, 0, sizeof(sockname));
#ifdef ENABLE_IPV6
/* first iterate destinations for this LAN interface (only 1 for IPv4) */
for(j = 0; (mcast_addrs[j].p1 != 0 && ipv6) || j < 1; j++) {
if(ipv6) {
struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockname;
sockname_len = sizeof(struct sockaddr_in6);
p->sin6_family = AF_INET6;
p->sin6_port = htons(SSDP_PORT);
inet_pton(AF_INET6, mcast_addrs[j].p1, &(p->sin6_addr));
dest_str = mcast_addrs[j].p2;
/* UPnP Device Architecture 1.1 :
* Devices MUST multicast SSDP messages for each of the UPnP-enabled
* interfaces. The scope of multicast SSDP messages MUST be
* link local FF02::C if the message is sent from a link local address.
* If the message is sent from a global address it MUST be multicast
* using either global scope FF0E::C or site local scope FF05::C.
* In networks with complex topologies and overlapping sites, use of
* global scope is RECOMMENDED. */
} else {
#else /* ENABLE_IPV6 */
{
#endif /* ENABLE_IPV6 */
/* IPv4 */
struct sockaddr_in *p = (struct sockaddr_in *)&sockname;
sockname_len = sizeof(struct sockaddr_in);
p->sin_family = AF_INET;
p->sin_port = htons(SSDP_PORT);
p->sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
dest_str = SSDP_MCAST_ADDR;
}
/* iterate all services / devices */
for(i = 0; known_service_types[i].s; i++) {
if(i==0)
ver_str[0] = '\0';
else
snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version);
SendSSDPNotify(s, (struct sockaddr *)&sockname, sockname_len, dest_str,
host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
known_service_types[i].s, ver_str, /* NT: */
known_service_types[i].uuid, "::",
known_service_types[i].s, /* ver_str, USN: */
lifetime);
/* for devices, also send NOTIFY on the uuid */
if(i > 0 && /* only known_service_types[0].s is shorter than "urn:schemas-upnp-org:device" */
0==memcmp(known_service_types[i].s,
"urn:schemas-upnp-org:device", sizeof("urn:schemas-upnp-org:device")-1)) {
SendSSDPNotify(s, (struct sockaddr *)&sockname, sockname_len, dest_str,
host, http_port,
#ifdef ENABLE_HTTPS
https_port,
#endif
known_service_types[i].uuid, "", /* NT: */
known_service_types[i].uuid, "", "", /* ver_str, USN: */
lifetime);
}
} /* for(i = 0; known_service_types[i].s; i++) */
#ifdef ENABLE_IPV6
} /* for(j = 0; (mcast_addrs[j].p1 != 0 && ipv6) || j < 1; j++) */
#endif /* ENABLE_IPV6 */
2011-09-28 19:13:20 +00:00
}
/* SendSSDPNotifies2() sends SSDP NOTIFY packets on all interfaces
* for all destinations, all devices / services */
2011-09-28 19:13:20 +00:00
void
SendSSDPNotifies2(int * sockets,
unsigned short http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
unsigned short https_port,
#endif
2011-09-28 19:13:20 +00:00
unsigned int lifetime)
{
int i;
struct lan_addr_s * lan_addr;
for(i = 0, lan_addr = lan_addrs.lh_first;
lan_addr != NULL;
lan_addr = lan_addr->list.le_next) {
SendSSDPNotifies(sockets[i], lan_addr->str, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
lifetime, 0);
i++;
#ifdef ENABLE_IPV6
if(sockets[i] >= 0) {
SendSSDPNotifies(sockets[i], ipv6_addr_for_http_with_brackets, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
lifetime, 1);
}
i++;
#endif /* ENABLE_IPV6 */
2011-09-28 19:13:20 +00:00
}
}
/* ProcessSSDPRequest()
* process SSDP M-SEARCH requests and responds to them */
void
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
ProcessSSDPRequest(int s, unsigned short http_port, unsigned short https_port)
2014-04-09 13:35:06 +00:00
#else
ProcessSSDPRequest(int s, unsigned short http_port)
2014-04-09 13:35:06 +00:00
#endif
2011-09-28 19:13:20 +00:00
{
int n;
char bufr[1500];
#ifdef ENABLE_IPV6
struct sockaddr_storage sendername;
#else
struct sockaddr_in sendername;
#endif
int source_ifindex = -1;
#if defined(IP_RECVIF) || defined(IP_PKTINFO)
#ifdef IP_RECVIF
char cmbuf[CMSG_SPACE(sizeof(struct sockaddr_dl))];
#else /* IP_PKTINFO */
char cmbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];
#endif
struct iovec iovec = {
.iov_base = bufr,
.iov_len = sizeof(bufr)
};
struct msghdr mh = {
.msg_name = &sendername,
.msg_namelen = sizeof(sendername),
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = cmbuf,
.msg_controllen = sizeof(cmbuf)
};
struct cmsghdr *cmptr;
2011-09-28 19:13:20 +00:00
n = recvmsg(s, &mh, 0);
#else
socklen_t len_r;
len_r = sizeof(sendername);
2011-09-28 19:13:20 +00:00
n = recvfrom(s, bufr, sizeof(bufr), 0,
(struct sockaddr *)&sendername, &len_r);
#endif /* defined(IP_RECVIF) || defined(IP_PKTINFO) */
2011-09-28 19:13:20 +00:00
if(n < 0)
{
/* EAGAIN, EWOULDBLOCK, EINTR : silently ignore (try again next time)
* other errors : log to LOG_ERR */
if(errno != EAGAIN &&
errno != EWOULDBLOCK &&
errno != EINTR)
{
#if defined(IP_RECVIF) || defined(IP_PKTINFO)
syslog(LOG_ERR, "recvmsg(udp): %m");
#else
syslog(LOG_ERR, "recvfrom(udp): %m");
#endif
}
2011-09-28 19:13:20 +00:00
return;
}
#if defined(IP_RECVIF) || defined(IP_PKTINFO)
for(cmptr = CMSG_FIRSTHDR(&mh); cmptr != NULL; cmptr = CMSG_NXTHDR(&mh, cmptr))
{
syslog(LOG_DEBUG, "level=%d type=%d", cmptr->cmsg_level, cmptr->cmsg_type);
#ifdef IP_RECVIF
if(cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
{
struct sockaddr_dl *sdl; /* fields : len, family, index, type, nlen, alen, slen, data */
sdl = (struct sockaddr_dl *)CMSG_DATA(cmptr);
syslog(LOG_DEBUG, "sdl_index = %d %s", sdl->sdl_index, link_ntoa(sdl));
source_ifindex = sdl->sdl_index;
}
#elif defined(IP_PKTINFO) /* IP_RECVIF */
if(cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
{
struct in_pktinfo * pi; /* fields : ifindex, spec_dst, addr */
pi = (struct in_pktinfo *)CMSG_DATA(cmptr);
syslog(LOG_DEBUG, "ifindex = %u %s", pi->ipi_ifindex, inet_ntoa(pi->ipi_spec_dst));
source_ifindex = pi->ipi_ifindex;
}
#endif /* IP_PKTINFO */
#if defined(ENABLE_IPV6) && defined(IPV6_RECVPKTINFO)
if(cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == IPV6_RECVPKTINFO)
{
struct in6_pktinfo * pi6; /* fields : ifindex, addr */
pi6 = (struct in6_pktinfo *)CMSG_DATA(cmptr);
syslog(LOG_DEBUG, "ifindex = %u", pi6->ipi6_ifindex);
source_ifindex = pi6->ipi6_ifindex;
}
#endif /* defined(ENABLE_IPV6) && defined(IPV6_RECVPKTINFO) */
}
#endif /* defined(IP_RECVIF) || defined(IP_PKTINFO) */
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
ProcessSSDPData(s, bufr, n, (struct sockaddr *)&sendername, source_ifindex,
http_port, https_port);
2014-04-09 13:35:06 +00:00
#else
ProcessSSDPData(s, bufr, n, (struct sockaddr *)&sendername, source_ifindex,
http_port);
2014-04-09 13:35:06 +00:00
#endif
2011-09-28 19:13:20 +00:00
}
#ifdef ENABLE_HTTPS
2011-09-28 19:13:20 +00:00
void
ProcessSSDPData(int s, const char *bufr, int n,
const struct sockaddr * sender, int source_if,
unsigned short http_port, unsigned short https_port)
2014-04-09 13:35:06 +00:00
#else
void
ProcessSSDPData(int s, const char *bufr, int n,
const struct sockaddr * sender, int source_if,
unsigned short http_port)
2014-04-09 13:35:06 +00:00
#endif
{
2011-09-28 19:13:20 +00:00
int i, l;
struct lan_addr_s * lan_addr = NULL;
const char * st = NULL;
int st_len = 0;
int st_ver = 0;
2011-09-28 19:13:20 +00:00
char sender_str[64];
char ver_str[4];
2011-09-28 19:13:20 +00:00
const char * announced_host = NULL;
#ifdef UPNP_STRICT
#ifdef ENABLE_IPV6
char announced_host_buf[64];
#endif
#endif
#if defined(UPNP_STRICT) || defined(DELAY_MSEARCH_RESPONSE)
int mx_value = -1;
#endif
unsigned int delay = 50; /* Non-zero default delay to prevent flooding */
2014-02-25 10:15:30 +00:00
/* UPnP Device Architecture v1.1. 1.3.3 Search response :
* Devices responding to a multicast M-SEARCH SHOULD wait a random period
* of time between 0 seconds and the number of seconds specified in the
* MX field value of the search request before responding, in order to
* avoid flooding the requesting control point with search responses
* from multiple devices. If the search request results in the need for
* a multiple part response from the device, those multiple part
* responses SHOULD be spread at random intervals through the time period
* from 0 to the number of seconds specified in the MX header field. */
char atoi_buffer[8];
2011-09-28 19:13:20 +00:00
/* get the string representation of the sender address */
sockaddr_to_string(sender, sender_str, sizeof(sender_str));
lan_addr = get_lan_for_peer(sender);
if(source_if > 0)
{
if(lan_addr != NULL)
{
#ifndef MULTIPLE_EXTERNAL_IP
if(lan_addr->index != (unsigned)source_if && lan_addr->index != 0
&& !(lan_addr->add_indexes & (1UL << (source_if - 1))))
#else
2018-04-12 08:49:53 +00:00
if(lan_addr->index != (unsigned)source_if && lan_addr->index != 0)
#endif
{
syslog(LOG_WARNING, "interface index not matching %u != %d", lan_addr->index, source_if);
}
}
else
{
/* use the interface index */
for(lan_addr = lan_addrs.lh_first;
lan_addr != NULL;
lan_addr = lan_addr->list.le_next)
{
if(lan_addr->index == (unsigned)source_if)
break;
}
}
}
if(lan_addr == NULL)
{
syslog(LOG_WARNING, "SSDP packet sender %s (if_index=%d) not from a LAN, ignoring",
sender_str, source_if);
return;
}
2011-09-28 19:13:20 +00:00
if(memcmp(bufr, "NOTIFY", 6) == 0)
{
/* ignore NOTIFY packets. We could log the sender and device type */
return;
}
else if(memcmp(bufr, "M-SEARCH", 8) == 0)
{
i = 0;
while(i < n)
{
while((i < n - 1) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
i++;
i += 2;
if((i < n - 3) && (strncasecmp(bufr+i, "st:", 3) == 0))
{
st = bufr+i+3;
st_len = 0;
while((st < bufr + n) && (*st == ' ' || *st == '\t'))
2011-09-28 19:13:20 +00:00
st++;
while((st + st_len < bufr + n)
&& (st[st_len]!='\r' && st[st_len]!='\n'))
2011-09-28 19:13:20 +00:00
st_len++;
l = st_len;
while(l > 0 && st[l-1] != ':')
l--;
memset(atoi_buffer, 0, sizeof(atoi_buffer));
memcpy(atoi_buffer, st + l, MIN((int)(sizeof(atoi_buffer) - 1), st_len - l));
st_ver = atoi(atoi_buffer);
syslog(LOG_DEBUG, "ST: %.*s (ver=%d)", st_len, st, st_ver);
2011-09-28 19:13:20 +00:00
/*j = 0;*/
/*while(bufr[i+j]!='\r') j++;*/
/*syslog(LOG_INFO, "%.*s", j, bufr+i);*/
}
#if defined(UPNP_STRICT) || defined(DELAY_MSEARCH_RESPONSE)
else if((i < n - 3) && (strncasecmp(bufr+i, "mx:", 3) == 0))
{
const char * mx;
int mx_len;
mx = bufr+i+3;
mx_len = 0;
while((mx < bufr + n) && (*mx == ' ' || *mx == '\t'))
mx++;
while((mx + mx_len < bufr + n)
&& (mx[mx_len]!='\r' && mx[mx_len]!='\n'))
mx_len++;
memset(atoi_buffer, 0, sizeof(atoi_buffer));
memcpy(atoi_buffer, mx, MIN((int)(sizeof(atoi_buffer) - 1), mx_len));
mx_value = atoi(atoi_buffer);
syslog(LOG_DEBUG, "MX: %.*s (value=%d)", mx_len, mx, mx_value);
}
#endif /* defined(UPNP_STRICT) || defined(DELAY_MSEARCH_RESPONSE) */
#if defined(UPNP_STRICT)
/* Fix UDA-1.2.10 Man header empty or invalid */
else if((i < n - 4) && (strncasecmp(bufr+i, "man:", 4) == 0))
{
const char * man;
int man_len;
man = bufr+i+4;
man_len = 0;
while((man < bufr + n) && (*man == ' ' || *man == '\t'))
man++;
while((man + man_len < bufr + n)
&& (man[man_len]!='\r' && man[man_len]!='\n'))
man_len++;
if((man_len < 15) || (strncmp(man, "\"ssdp:discover\"", 15) != 0)) {
syslog(LOG_INFO, "ignoring SSDP packet MAN empty or invalid header");
return;
}
}
#endif /* defined(UPNP_STRICT) */
}
#ifdef UPNP_STRICT
/* For multicast M-SEARCH requests, if the search request does
* not contain an MX header field, the device MUST silently
* discard and ignore the search request. */
if(mx_value < 0) {
syslog(LOG_INFO, "ignoring SSDP packet missing MX: header");
return;
} else if(mx_value > 5) {
/* If the MX header field specifies a field value greater
* than 5, the device SHOULD assume that it contained the
* value 5 or less. */
mx_value = 5;
}
#elif defined(DELAY_MSEARCH_RESPONSE)
if(mx_value < 0) {
mx_value = 1;
} else if(mx_value > 5) {
/* If the MX header field specifies a field value greater
* than 5, the device SHOULD assume that it contained the
* value 5 or less. */
mx_value = 5;
2011-09-28 19:13:20 +00:00
}
#endif
2011-09-28 19:13:20 +00:00
/*syslog(LOG_INFO, "SSDP M-SEARCH packet received from %s",
sender_str );*/
if(st && (st_len > 0))
{
syslog(LOG_INFO, "SSDP M-SEARCH from %s ST: %.*s",
sender_str, st_len, st);
/* find in which sub network the client is */
#ifdef ENABLE_IPV6
if((sender->sa_family == AF_INET) ||
(sender->sa_family == AF_INET6 &&
IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)sender)->sin6_addr)))
#else
2011-09-28 19:13:20 +00:00
if(sender->sa_family == AF_INET)
#endif
2011-09-28 19:13:20 +00:00
{
if (lan_addr == NULL)
{
2015-01-20 13:13:18 +00:00
syslog(LOG_ERR,
"Can't find in which sub network the client %s is",
sender_str);
2011-09-28 19:13:20 +00:00
return;
}
announced_host = lan_addr->str;
}
#ifdef ENABLE_IPV6
else if(sender->sa_family == AF_INET6)
2011-09-28 19:13:20 +00:00
{
/* IPv6 address with brackets */
#ifdef UPNP_STRICT
int index;
struct in6_addr addr6;
size_t addr6_len = sizeof(addr6);
/* retrieve the IPv6 address which
* will be used locally to reach sender */
memset(&addr6, 0, sizeof(addr6));
if(IN6_IS_ADDR_LINKLOCAL(&(((struct sockaddr_in6 *)sender)->sin6_addr))) {
get_link_local_addr(((struct sockaddr_in6 *)sender)->sin6_scope_id, &addr6);
} else if(get_src_for_route_to (sender, &addr6, &addr6_len, &index) < 0) {
syslog(LOG_WARNING, "get_src_for_route_to() failed, using %s", ipv6_addr_for_http_with_brackets);
announced_host = ipv6_addr_for_http_with_brackets;
}
if(announced_host == NULL) {
if(inet_ntop(AF_INET6, &addr6,
announced_host_buf+1,
sizeof(announced_host_buf) - 2)) {
announced_host_buf[0] = '[';
i = strlen(announced_host_buf);
if(i < (int)sizeof(announced_host_buf) - 1) {
announced_host_buf[i] = ']';
announced_host_buf[i+1] = '\0';
} else {
syslog(LOG_NOTICE, "cannot suffix %s with ']'",
announced_host_buf);
}
announced_host = announced_host_buf;
} else {
syslog(LOG_NOTICE, "inet_ntop() failed %m");
announced_host = ipv6_addr_for_http_with_brackets;
}
}
#else
2011-09-28 19:13:20 +00:00
announced_host = ipv6_addr_for_http_with_brackets;
#endif
2011-09-28 19:13:20 +00:00
}
#endif
else
{
syslog(LOG_ERR,
"Unknown address family %d for client %s",
sender->sa_family, sender_str);
return;
}
2011-09-28 19:13:20 +00:00
/* Responds to request with a device as ST header */
for(i = 0; known_service_types[i].s; i++)
2011-09-28 19:13:20 +00:00
{
l = (int)strlen(known_service_types[i].s);
if(l<=st_len && (0 == memcmp(st, known_service_types[i].s, l))
#ifdef UPNP_STRICT
&& (st_ver <= known_service_types[i].version)
/* only answer for service version lower or equal of supported one */
#endif
)
2011-09-28 19:13:20 +00:00
{
/* SSDP_RESPOND_SAME_VERSION :
* response is urn:schemas-upnp-org:service:WANIPConnection:1 when
* M-SEARCH included urn:schemas-upnp-org:service:WANIPConnection:1
* else the implemented versions is included in the response
*
* From UPnP Device Architecture v1.1 :
* 1.3.2 [...] Updated versions of device and service types
* are REQUIRED to be fully backward compatible with
* previous versions. Devices MUST respond to M-SEARCH
* requests for any supported version. For example, if a
* device implements urn:schemas-upnporg:service:xyz:2,
* it MUST respond to search requests for both that type
* and urn:schemas-upnp-org:service:xyz:1. The response
* MUST specify the same version as was contained in the
* search request. [...] */
#ifndef SSDP_RESPOND_SAME_VERSION
if(i==0)
ver_str[0] = '\0';
else
snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version);
#endif
2011-09-28 19:13:20 +00:00
syslog(LOG_INFO, "Single search found");
#ifdef DELAY_MSEARCH_RESPONSE
delay = random() / (1 + RAND_MAX / (1000 * mx_value));
#ifdef DEBUG
syslog(LOG_DEBUG, "mx=%dsec delay=%ums", mx_value, delay);
#endif
#endif
SendSSDPResponse(s, sender,
#ifdef SSDP_RESPOND_SAME_VERSION
st, st_len, "",
#else
known_service_types[i].s, l, ver_str,
#endif
announced_host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
2014-02-25 10:15:30 +00:00
known_service_types[i].uuid,
delay);
2011-09-28 19:13:20 +00:00
break;
}
}
/* Responds to request with ST: ssdp:all */
/* strlen("ssdp:all") == 8 */
if(st_len==8 && (0 == memcmp(st, "ssdp:all", 8)))
{
#ifdef DELAY_MSEARCH_RESPONSE
unsigned int delay_increment = (mx_value * 1000) / 15;
#endif
2011-09-28 19:13:20 +00:00
syslog(LOG_INFO, "ssdp:all found");
for(i=0; known_service_types[i].s; i++)
2011-09-28 19:13:20 +00:00
{
#ifdef DELAY_MSEARCH_RESPONSE
delay += delay_increment;
#endif
if(i==0)
ver_str[0] = '\0';
else
snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version);
l = (int)strlen(known_service_types[i].s);
SendSSDPResponse(s, sender,
known_service_types[i].s, l, ver_str,
announced_host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
2014-02-25 10:15:30 +00:00
known_service_types[i].uuid,
delay);
2011-09-28 19:13:20 +00:00
}
/* also answer for uuid */
#ifdef DELAY_MSEARCH_RESPONSE
delay += delay_increment;
#endif
SendSSDPResponse(s, sender, uuidvalue_igd, strlen(uuidvalue_igd), "",
announced_host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
uuidvalue_igd, delay);
#ifdef DELAY_MSEARCH_RESPONSE
delay += delay_increment;
#endif
SendSSDPResponse(s, sender, uuidvalue_wan, strlen(uuidvalue_wan), "",
announced_host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
uuidvalue_wan, delay);
#ifdef DELAY_MSEARCH_RESPONSE
delay += delay_increment;
#endif
SendSSDPResponse(s, sender, uuidvalue_wcd, strlen(uuidvalue_wcd), "",
announced_host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
uuidvalue_wcd, delay);
2011-09-28 19:13:20 +00:00
}
/* responds to request by UUID value */
l = (int)strlen(uuidvalue_igd);
if(l==st_len)
2011-09-28 19:13:20 +00:00
{
#ifdef DELAY_MSEARCH_RESPONSE
delay = random() / (1 + RAND_MAX / (1000 * mx_value));
#endif
if(0 == memcmp(st, uuidvalue_igd, l))
{
syslog(LOG_INFO, "ssdp:uuid (IGD) found");
SendSSDPResponse(s, sender, st, st_len, "",
announced_host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
uuidvalue_igd, delay);
}
else if(0 == memcmp(st, uuidvalue_wan, l))
{
syslog(LOG_INFO, "ssdp:uuid (WAN) found");
SendSSDPResponse(s, sender, st, st_len, "",
announced_host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
uuidvalue_wan, delay);
}
else if(0 == memcmp(st, uuidvalue_wcd, l))
{
syslog(LOG_INFO, "ssdp:uuid (WCD) found");
SendSSDPResponse(s, sender, st, st_len, "",
announced_host, http_port,
2014-04-09 13:35:06 +00:00
#ifdef ENABLE_HTTPS
https_port,
#endif
uuidvalue_wcd, delay);
}
2011-09-28 19:13:20 +00:00
}
}
else
{
syslog(LOG_INFO, "Invalid SSDP M-SEARCH from %s", sender_str);
}
}
else
{
syslog(LOG_NOTICE, "Unknown udp packet received from %s", sender_str);
}
}
static int
SendSSDPbyebye(int s, const struct sockaddr * dest, socklen_t destlen,
const char * dest_str,
const char * nt, const char * suffix,
const char * usn1, const char * usn2, const char * usn3)
{
int n, l;
char bufr[SSDP_PACKET_MAX_LEN];
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"
"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */
"01-NLS: %u\r\n" /* same as BOOTID field. UDA v1.1 */
"BOOTID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
"CONFIGID.UPNP.ORG: %u\r\n" /* UDA v1.1 */
"\r\n",
dest_str, SSDP_PORT, /* HOST : */
nt, suffix, /* NT: */
usn1, usn2, usn3, suffix, /* USN: */
upnp_bootid, /* 01-NLS: */
upnp_bootid, /* BOOTID.UPNP.ORG: */
upnp_configid); /* CONFIGID.UPNP.ORG: */
if(l<0)
{
syslog(LOG_ERR, "%s: snprintf error", "SendSSDPbyebye()");
return -1;
}
else if((unsigned int)l >= sizeof(bufr))
{
syslog(LOG_WARNING, "%s: truncated output (%u>=%u)",
"SendSSDPbyebye()", (unsigned)l, (unsigned)sizeof(bufr));
l = sizeof(bufr) - 1;
}
n = sendto_or_schedule(s, bufr, l, 0, dest, destlen);
if(n < 0)
{
syslog(LOG_ERR, "sendto(udp_shutdown=%d): %m", s);
return -1;
}
else if(n != l)
{
syslog(LOG_NOTICE, "sendto() sent %d out of %d bytes", n, l);
return -1;
}
return 0;
}
/* This will broadcast ssdp:byebye notifications to inform
2011-09-28 19:13:20 +00:00
* the network that UPnP is going down. */
int
SendSSDPGoodbye(int * sockets, int n_sockets)
{
struct sockaddr_in sockname4;
#ifdef ENABLE_IPV6
struct sockaddr_in6 sockname6;
struct sockaddr * sockname;
socklen_t socknamelen;
int ipv6 = 0;
#endif
2011-09-28 19:13:20 +00:00
int i, j;
char ver_str[4];
int ret = 0;
const char * dest_str;
2011-09-28 19:13:20 +00:00
2019-05-02 10:09:28 +00:00
memset(&sockname4, 0, sizeof(struct sockaddr_in));
sockname4.sin_family = AF_INET;
sockname4.sin_port = htons(SSDP_PORT);
sockname4.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
#ifdef ENABLE_IPV6
memset(&sockname6, 0, sizeof(struct sockaddr_in6));
sockname6.sin6_family = AF_INET6;
sockname6.sin6_port = htons(SSDP_PORT);
inet_pton(AF_INET6, LL_SSDP_MCAST_ADDR, &(sockname6.sin6_addr));
#else
dest_str = SSDP_MCAST_ADDR;
#endif
2011-09-28 19:13:20 +00:00
for(j=0; j<n_sockets; j++)
{
if(sockets[j] < 0)
continue;
#ifdef ENABLE_IPV6
ipv6 = j & 1;
if(ipv6) {
dest_str = "[" LL_SSDP_MCAST_ADDR "]";
sockname = (struct sockaddr *)&sockname6;
socknamelen = sizeof(struct sockaddr_in6);
} else {
dest_str = SSDP_MCAST_ADDR;
sockname = (struct sockaddr *)&sockname4;
socknamelen = sizeof(struct sockaddr_in);
}
#endif
for(i=0; known_service_types[i].s; i++)
2011-09-28 19:13:20 +00:00
{
if(i==0)
ver_str[0] = '\0';
else
snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version);
ret += SendSSDPbyebye(sockets[j],
2012-04-06 17:53:26 +00:00
#ifdef ENABLE_IPV6
sockname, socknamelen,
2012-04-06 17:53:26 +00:00
#else
(struct sockaddr *)&sockname4, sizeof(struct sockaddr_in),
2012-04-06 17:53:26 +00:00
#endif
dest_str,
known_service_types[i].s, ver_str, /* NT: */
known_service_types[i].uuid, "::",
known_service_types[i].s); /* ver_str, USN: */
if(i > 0 && /* only known_service_types[0].s is shorter than "urn:schemas-upnp-org:device" */
0==memcmp(known_service_types[i].s,
"urn:schemas-upnp-org:device", sizeof("urn:schemas-upnp-org:device")-1))
2011-09-28 19:13:20 +00:00
{
ret += SendSSDPbyebye(sockets[j],
#ifdef ENABLE_IPV6
sockname, socknamelen,
#else
(struct sockaddr *)&sockname4, sizeof(struct sockaddr_in),
#endif
dest_str,
known_service_types[i].uuid, "", /* NT: */
known_service_types[i].uuid, "", ""); /* ver_str, USN: */
2011-09-28 19:13:20 +00:00
}
2019-05-02 10:09:28 +00:00
}
2011-09-28 19:13:20 +00:00
}
return ret;
2011-09-28 19:13:20 +00:00
}
/* 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, n;
char ver_str[4];
2011-09-28 19:13:20 +00:00
s = socket(AF_UNIX, SOCK_STREAM, 0);
if(s < 0) {
syslog(LOG_ERR, "socket(unix): %m");
return -1;
}
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, minissdpdsocketpath, sizeof(addr.sun_path));
2015-06-24 05:30:41 +00:00
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
2011-09-28 19:13:20 +00:00
if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
syslog(LOG_ERR, "connect(\"%s\"): %m", minissdpdsocketpath);
close(s);
2011-09-28 19:13:20 +00:00
return -1;
}
for(i = 0; known_service_types[i].s; i++) {
buffer[0] = 4; /* request type 4 : submit service */
if(i==0)
ver_str[0] = '\0';
else
snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version);
/* 4 strings following : ST (service type), USN, Server, Location */
p = buffer + 1;
l = snprintf(strbuf, sizeof(strbuf), "%s%s",
known_service_types[i].s, ver_str);
if(l<0) {
syslog(LOG_WARNING, "SubmitServicesToMiniSSDPD: snprintf %m");
continue;
} else if((unsigned)l>=sizeof(strbuf)) {
l = sizeof(strbuf) - 1;
}
CODELENGTH(l, p);
memcpy(p, strbuf, l);
p += l;
l = snprintf(strbuf, sizeof(strbuf), "%s::%s%s",
known_service_types[i].uuid, known_service_types[i].s, ver_str);
if(l<0) {
syslog(LOG_WARNING, "SubmitServicesToMiniSSDPD: snprintf %m");
continue;
} else if((unsigned)l>=sizeof(strbuf)) {
l = sizeof(strbuf) - 1;
}
2011-09-28 19:13:20 +00:00
CODELENGTH(l, p);
memcpy(p, strbuf, l);
p += l;
#ifdef DYNAMIC_OS_VERSION
l = snprintf(strbuf, sizeof(strbuf), MINIUPNPD_SERVER_STRING,
os_version);
if(l<0) {
syslog(LOG_WARNING, "SubmitServicesToMiniSSDPD: snprintf %m");
continue;
} else if((unsigned)l>=sizeof(strbuf)) {
l = sizeof(strbuf) - 1;
}
CODELENGTH(l, p);
memcpy(p, strbuf, l);
#else
2011-09-28 19:13:20 +00:00
l = (int)strlen(MINIUPNPD_SERVER_STRING);
CODELENGTH(l, p);
memcpy(p, MINIUPNPD_SERVER_STRING, l);
#endif
2011-09-28 19:13:20 +00:00
p += l;
l = snprintf(strbuf, sizeof(strbuf), "http://%s:%u" ROOTDESC_PATH,
host, (unsigned int)port);
if(l<0) {
syslog(LOG_WARNING, "SubmitServicesToMiniSSDPD: snprintf %m");
continue;
} else if((unsigned)l>=sizeof(strbuf)) {
l = sizeof(strbuf) - 1;
}
2011-09-28 19:13:20 +00:00
CODELENGTH(l, p);
memcpy(p, strbuf, l);
p += l;
/* now write the encoded data */
n = p - buffer; /* bytes to send */
p = buffer; /* start */
while(n > 0) {
l = write(s, p, n);
if (l < 0) {
if(errno == EINTR)
continue;
syslog(LOG_ERR, "write(): %m");
close(s);
return -1;
} else if (l == 0) {
syslog(LOG_ERR, "write() returned 0");
close(s);
return -1;
}
p += l;
n -= l;
2011-09-28 19:13:20 +00:00
}
}
close(s);
syslog(LOG_DEBUG, "%d service submitted to MiniSSDPd", i);
2011-09-28 19:13:20 +00:00
return 0;
}