ssdp: Allow SSDP M-SEARCH from other subnets

Based on a patch from SF user pasdVn <pasdVn3@gmx.de>.

Rely on the kernel to filter out multicasts from non-member interfaces.
Also get the address of the interface we received the multicast on using
sockopt IP_PKTINFO.

Closes: SF Patch ticket #77.
This commit is contained in:
Justin Maggard 2015-09-08 15:44:08 -07:00
parent 916546d45f
commit 1b75093a43

View File

@ -73,6 +73,10 @@ AddMulticastMembership(int s, struct lan_addr_s *iface)
imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR);
imr.imr_interface.s_addr = iface->addr.s_addr;
#endif
/* Setting the socket options will guarantee, tha we will only receive
* multicast traffic on a specific Interface.
* In addition the kernel is instructed to send an igmp message (choose
* mcast group) on the specific interface/subnet. */
ret = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(imr));
if (ret < 0 && errno != EADDRINUSE)
{
@ -102,12 +106,18 @@ OpenAndConfSSDPReceiveSocket(void)
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, SO_REUSEADDR): %s\n", strerror(errno));
#ifdef __linux__
if (setsockopt(s, IPPROTO_IP, IP_PKTINFO, &i, sizeof(i)) < 0)
DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, IP_PKTINFO): %s\n", strerror(errno));
#endif
memset(&sockname, 0, sizeof(struct sockaddr_in));
sockname.sin_family = AF_INET;
sockname.sin_port = htons(SSDP_PORT);
/* NOTE : it seems it doesnt work when binding on the specific address */
sockname.sin_addr.s_addr = htonl(INADDR_ANY);
/* NOTE: Binding a socket to a UDP multicast address means, that we just want
* to receive datagramms send to this multicast address.
* To specify the local nics we want to use we have to use setsockopt,
* see AddMulticastMembership(...). */
sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
{
@ -476,15 +486,32 @@ ProcessSSDPRequest(int s, unsigned short port)
{
int n;
char bufr[1500];
socklen_t len_r;
struct sockaddr_in sendername;
int i;
char *st = NULL, *mx = NULL, *man = NULL, *mx_end = NULL;
int man_len = 0;
len_r = sizeof(struct sockaddr_in);
#ifdef __linux__
char cmbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];
struct iovec iovec = {
.iov_base = bufr,
.iov_len = sizeof(bufr)-1
};
struct msghdr mh = {
.msg_name = &sendername,
.msg_namelen = sizeof(struct sockaddr_in),
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = cmbuf,
.msg_controllen = sizeof(cmbuf)
};
n = recvmsg(s, &mh, 0);
#else
socklen_t len_r = sizeof(struct sockaddr_in);
n = recvfrom(s, bufr, sizeof(bufr)-1, 0,
(struct sockaddr *)&sendername, &len_r);
#endif
if (n < 0)
{
DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno));
@ -624,6 +651,26 @@ ProcessSSDPRequest(int s, unsigned short port)
else if (st && (st_len > 0))
{
int l;
#ifdef __linux__
char host[40] = "127.0.0.1";
struct cmsghdr *cmsg;
/* find the interface we received the msg from */
for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
{
struct in_addr addr;
struct in_pktinfo *pi;
/* ignore the control headers that don't match what we want */
if (cmsg->cmsg_level != IPPROTO_IP ||
cmsg->cmsg_type != IP_PKTINFO)
continue;
pi = (struct in_pktinfo *)CMSG_DATA(cmsg);
addr = pi->ipi_spec_dst;
inet_ntop(AF_INET, &addr, host, sizeof(host));
}
#else
const char *host;
int iface = 0;
/* find in which sub network the client is */
for (i = 0; i < n_lan_addr; i++)
@ -641,6 +688,8 @@ ProcessSSDPRequest(int s, unsigned short port)
inet_ntoa(sendername.sin_addr));
return;
}
host = lan_addr[iface].str;
#endif
DPRINTF(E_DEBUG, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n",
inet_ntoa(sendername.sin_addr),
ntohs(sendername.sin_port),
@ -675,7 +724,7 @@ ProcessSSDPRequest(int s, unsigned short port)
}
_usleep(random()>>20);
SendSSDPResponse(s, sendername, i,
lan_addr[iface].str, port);
host, port);
return;
}
/* Responds to request with ST: ssdp:all */
@ -686,7 +735,7 @@ ProcessSSDPRequest(int s, unsigned short port)
{
l = strlen(known_service_types[i]);
SendSSDPResponse(s, sendername, i,
lan_addr[iface].str, port);
host, port);
}
}
}