diff --git a/minissdp.c b/minissdp.c index 60d69ec..7789076 100644 --- a/minissdp.c +++ b/minissdp.c @@ -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); } } }