/* $Id: minissdp.c,v 1.107 2024/01/15 00:20:21 nanard Exp $ */
/* vim: tabstop=4 shiftwidth=4 noexpandtab
 * MiniUPnP project
 * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
 * (c) 2006-2024 Thomas Bernard
 * This software is subject to the conditions detailed
 * in the LICENCE file provided within the distribution */

#include <stdio.h>
#include <stdlib.h>
#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>
#include <syslog.h>

#ifdef IP_RECVIF
#include <sys/types.h>
#include <sys/uio.h>
#include <net/if.h>
#include <net/if_dl.h>
#endif

#include "config.h"
#if defined(ENABLE_IPV6) && defined(UPNP_STRICT)
#include <ifaddrs.h>
#endif /* defined(ENABLE_IPV6) && defined(UPNP_STRICT) */

#include "upnpdescstrings.h"
#include "miniupnpdpath.h"
#include "upnphttp.h"
#include "upnpglobalvars.h"
#include "minissdp.h"
#include "upnputils.h"
#include "getroute.h"
#include "asyncsendto.h"
#include "codelength.h"
#include "macros.h"

#ifndef MIN
#define MIN(x,y) (((x)<(y))?(x):(y))
#endif /* MIN */

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

/* AddMulticastMembership()
 * param s			socket
 * param lan_addr	lan address
 */
static int
AddMulticastMembership(int s, struct lan_addr_s * lan_addr)
{
#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 */

	/* 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 */
	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 */
	imr.imr_ifindex = if_nametoindex(lan_addr->ifname);
#endif	/* ENABLE_IPV6 */
#else	/* MULTIPLE_EXTERNAL_IP */
	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)
	{
		syslog(LOG_ERR, "setsockopt(udp, IP_ADD_MEMBERSHIP): %m");
		return -1;
	}

	return 0;
}

/* AddMulticastMembershipIPv6()
 * param s	socket (IPv6)
 * param ifindex : interface index (0 : All interfaces) */
#ifdef ENABLE_IPV6
static int
AddMulticastMembershipIPv6(int s, unsigned int ifindex)
{
	struct ipv6_mreq mr;

	memset(&mr, 0, sizeof(mr));
	mr.ipv6mr_interface = ifindex;	/* 0 : all interfaces */
#ifndef IPV6_ADD_MEMBERSHIP
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#endif
	inet_pton(AF_INET6, LL_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, 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;
	}
	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
 * 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;

	if( (s = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0)) < 0)
	{
		syslog(LOG_ERR, "%s: socket(udp): %m",
		       "OpenAndConfSSDPReceiveSocket");
		return -1;
	}

	memset(&sockname, 0, sizeof(struct sockaddr_storage));
#ifdef ENABLE_IPV6
	if(ipv6)
	{
		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;
		sockname_len = sizeof(struct sockaddr_in6);
	}
	else
#endif /* ENABLE_IPV6 */
	{
		struct sockaddr_in * saddr = (struct sockaddr_in *)&sockname;
		saddr->sin_family = AF_INET;
		saddr->sin_port = htons(SSDP_PORT);
		/* NOTE : it seems it doesn't work when binding on the specific address */
		/*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)
	{
		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) */

	if(!set_non_blocking(s))
	{
		syslog(LOG_WARNING, "%s: set_non_blocking(): %m",
		       "OpenAndConfSSDPReceiveSocket");
	}

#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)
		    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) */

	if(bind(s, (struct sockaddr *)&sockname, sockname_len) < 0)
	{
		syslog(LOG_ERR, "%s: bind(udp%s): %m",
		       "OpenAndConfSSDPReceiveSocket", ipv6 ? "6" : "");
		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",
				       strlen(lan_addr->str) ? lan_addr->str : "NULL");
			}
		}
	}
	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)
			{
				syslog(LOG_WARNING,
				       "Failed to add multicast membership for interface %s",
				       strlen(lan_addr->str) ? lan_addr->str : "NULL");
			}
		}
	}

	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)
{
	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
	struct in_addr mc_if;
#else	/* HAVE_IP_MREQN */
	struct ip_mreqn mc_if;
#endif	/* HAVE_IP_MREQN */
	struct sockaddr_in sockname;

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

	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");
	}

	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 */
	memset(&sockname, 0, sizeof(struct sockaddr_in));
	sockname.sin_family = AF_INET;
	sockname.sin_addr.s_addr = lan_addr->addr.s_addr;	/*inet_addr(addr);*/

	if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
	{
		syslog(LOG_ERR, "bind(udp_notify): %m");
		close(s);
		return -1;
	}

	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;
	/* 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;
	}
	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

int
OpenAndConfSSDPNotifySockets(int * sockets)
/*OpenAndConfSSDPNotifySockets(int * sockets,
                             struct lan_addr_s * lan_addr, int n_lan_addr)*/
{
	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)
	{
		sockets[i] = OpenAndConfSSDPNotifySocket(lan_addr);
		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
	}
	return 0;
error:
	while(--i >= 0)
	{
		close(sockets[i]);
		sockets[i] = -1;
	}
	return -1;
}

/*
 * 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
 * delay :      in milli-seconds
 */
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,
#ifdef ENABLE_HTTPS
                 unsigned short https_port,
#endif
                 const char * uuidvalue, unsigned int delay)
{
	int l, n;
	char buf[SSDP_PACKET_MAX_LEN];
	char addr_str[64];
	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);
	/*
	 * 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: Should be greater than or equal to 1800 seconds
	 */
	l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
		"CACHE-CONTROL: max-age=1800\r\n"
#ifdef ENABLE_HTTP_DATE
		"DATE: %s\r\n"
#endif
		"ST: %.*s%s\r\n"
		"USN: %s%s%.*s%s\r\n"
		"EXT:\r\n"
		"SERVER: " MINIUPNPD_SERVER_STRING "\r\n"
#ifndef RANDOMIZE_URLS
		"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 */
		"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" /* UDA v1.1 */
		"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 */
#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;
	}
	addrlen = (addr->sa_family == AF_INET6)
	          ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
	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);
	if(n < 0)
	{
		syslog(LOG_ERR, "%s: sendto(udp): %m",
		       "SendSSDPResponse()");
	}
}

static struct {
	const char * s;
	const int version;
	const char * uuid;
} const known_service_types[] =
{
	{"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
#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},
#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 */
#ifdef ENABLE_L3F_SERVICE
	{"urn:schemas-upnp-org:service:Layer3Forwarding:", 1, uuidvalue_igd},
#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}
};

/* 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,
#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
		"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 */
#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) {
		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 */
#ifdef ENABLE_HTTPS
static void
SendSSDPNotifies(int s, const char * host, unsigned short http_port,
                 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
{
#ifdef ENABLE_IPV6
	struct sockaddr_storage sockname;
	/* 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 */
#ifndef UPNP_STRICT
		  { GL_SSDP_MCAST_ADDR, "[" GL_SSDP_MCAST_ADDR "]" },	/* Global */
#endif /* ! UPNP_STRICT */
		  { NULL, NULL } };
	int j;
#else /* ENABLE_IPV6 */
	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 */

	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,
#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 */
}

/* SendSSDPNotifies2() sends SSDP NOTIFY packets on all interfaces
 * for all destinations, all devices / services */
void
SendSSDPNotifies2(int * sockets,
                  unsigned short http_port,
#ifdef ENABLE_HTTPS
                  unsigned short https_port,
#endif
                  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,
#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,
#ifdef ENABLE_HTTPS
			                 https_port,
#endif
			                 lifetime, 1);
		}
		i++;
#endif	/* ENABLE_IPV6 */
	}
}

/* ProcessSSDPRequest()
 * process SSDP M-SEARCH requests and responds to them */
void
#ifdef ENABLE_HTTPS
ProcessSSDPRequest(int s, unsigned short http_port, unsigned short https_port)
#else
ProcessSSDPRequest(int s, unsigned short http_port)
#endif
{
	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;

	n = recvmsg(s, &mh, 0);
#else
	socklen_t len_r;
	len_r = sizeof(sendername);
	n = recvfrom(s, bufr, sizeof(bufr), 0,
	             (struct sockaddr *)&sendername, &len_r);
#endif /* defined(IP_RECVIF) || defined(IP_PKTINFO) */
	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
		}
		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) */
#ifdef ENABLE_HTTPS
	ProcessSSDPData(s, bufr, n, (struct sockaddr *)&sendername, source_ifindex,
	                http_port, https_port);
#else
	ProcessSSDPData(s, bufr, n, (struct sockaddr *)&sendername, source_ifindex,
	                http_port);
#endif

}

#ifdef ENABLE_HTTPS
void
ProcessSSDPData(int s, const char *bufr, int n,
                const struct sockaddr * sender, int source_if,
                unsigned short http_port, unsigned short https_port)
#else
void
ProcessSSDPData(int s, const char *bufr, int n,
                const struct sockaddr * sender, int source_if,
                unsigned short http_port)
#endif
{
	int i, l;
	struct lan_addr_s * lan_addr = NULL;
	const char * st = NULL;
	int st_len = 0;
	int st_ver = 0;
	char sender_str[64];
	char ver_str[4];
	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 */
	/* 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];

	/* 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
			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;
	}

	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'))
					st++;
				while((st + st_len < bufr + n)
				      && (st[st_len]!='\r' && st[st_len]!='\n'))
					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);
				/*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;
		}
#endif
		/*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
			if(sender->sa_family == AF_INET)
#endif
			{
				if (lan_addr == NULL)
				{
					syslog(LOG_ERR,
					       "Can't find in which sub network the client %s is",
					       sender_str);
					return;
				}
				announced_host = lan_addr->str;
			}
#ifdef ENABLE_IPV6
			else if(sender->sa_family == AF_INET6)
			{
				/* 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));
				/* UPnP Device Architecture 2.0 - Annex A - IP version 6 support
				 * p112 A2.3 :
				 * a) Devices and control points SHALL use only the Link-Local
				 * unicast address as the source address and when specifying
				 * a literal IP address in LOCATION URLs in all multicast
				 * messages that are multicast to the Link-Local scope FF02::C
				 * for SSDP and FF02::130 for multicast eventing.
				 * f) Devices and control points SHALL use an acquired ULA or
				 * GUA in all multicast messages as the source address and
				 * when specifying a literal IP address in LOCATION URLs
				 * that are multicast to the Site-Local scope addresses of
				 * either FF05::C or FF05::130 */
				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) {
					/* convert addr6 to string with brackets */
					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
				announced_host = ipv6_addr_for_http_with_brackets;
#endif
			}
#endif
			else
			{
				syslog(LOG_ERR,
				       "Unknown address family %d for client %s",
				       sender->sa_family, sender_str);
				return;
			}
			/* Responds to request with a device as ST header */
			for(i = 0; known_service_types[i].s; i++)
			{
				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
				   )
				{
					/* 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
					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,
#ifdef ENABLE_HTTPS
					                 https_port,
#endif
					                 known_service_types[i].uuid,
					                 delay);
					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
				syslog(LOG_INFO, "ssdp:all found");
				for(i=0; known_service_types[i].s; i++)
				{
#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,
#ifdef ENABLE_HTTPS
					                 https_port,
#endif
					                 known_service_types[i].uuid,
					                 delay);
				}
				/* also answer for uuid */
#ifdef DELAY_MSEARCH_RESPONSE
					delay += delay_increment;
#endif
				SendSSDPResponse(s, sender, uuidvalue_igd, strlen(uuidvalue_igd), "",
				                 announced_host, http_port,
#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,
#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,
#ifdef ENABLE_HTTPS
				                 https_port,
#endif
				                 uuidvalue_wcd, delay);
			}
			/* responds to request by UUID value */
			l = (int)strlen(uuidvalue_igd);
			if(l==st_len)
			{
#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,
#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,
#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,
#ifdef ENABLE_HTTPS
					                 https_port,
#endif
					                 uuidvalue_wcd, delay);
				}
			}
		}
		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
 * 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
	int i, j;
	char ver_str[4];
	int ret = 0;
	const char * dest_str;

	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

	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++)
	    {
			if(i==0)
				ver_str[0] = '\0';
			else
				snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version);
			ret += SendSSDPbyebye(sockets[j],
#ifdef ENABLE_IPV6
			                      sockname, socknamelen,
#else
			                      (struct sockaddr *)&sockname4, sizeof(struct sockaddr_in),
#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))
			{
				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: */
			}
		}
	}
	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, n;
	char ver_str[4];

	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));
	addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
	if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
		syslog(LOG_ERR, "connect(\"%s\"): %m", minissdpdsocketpath);
		close(s);
		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;
		}
		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
		l = (int)strlen(MINIUPNPD_SERVER_STRING);
		CODELENGTH(l, p);
		memcpy(p, MINIUPNPD_SERVER_STRING, l);
#endif
		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;
		}
		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;
		}
	}
 	close(s);
	syslog(LOG_DEBUG, "%d service submitted to MiniSSDPd", i);
	return 0;
}