void ProcessSSDPData(int s, const char *bufr, int n, const struct sockaddr * sender, 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. */ /* 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(lan_addr == NULL) { syslog(LOG_WARNING, "SSDP packet sender %s not from a LAN, ignoring", sender_str); 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 == ' ' || *st == '\t') && (st < bufr + n)) st++; while(st[st_len]!='\r' && st[st_len]!='\n' && (st + st_len < bufr + n)) st_len++; l = st_len; while(l > 0 && st[l-1] != ':') l--; st_ver = atoi(st+l); 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 == ' ' || *mx == '\t') && (mx < bufr + n)) mx++; while(mx[mx_len]!='\r' && mx[mx_len]!='\n' && (mx + mx_len < bufr + n)) mx_len++; mx_value = atoi(mx); syslog(LOG_DEBUG, "MX: %.*s (value=%d)", mx_len, mx, mx_value); } #endif } #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 */ if(sender->sa_family == AF_INET) { if (lan_addr == NULL) { syslog(LOG_ERR, "Can't find in which sub network the client is"); return; } announced_host = lan_addr->str; } #ifdef ENABLE_IPV6 else { /* 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(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; } else { 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 /* 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); } }
/* ProcessSSDPRequest() * process SSDP M-SEARCH requests and responds to them */ void 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); n = recvfrom(s, bufr, sizeof(bufr)-1, 0, (struct sockaddr *)&sendername, &len_r); if (n < 0) { DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno)); return; } bufr[n] = '\0'; n -= 2; if (memcmp(bufr, "NOTIFY", 6) == 0) { char *loc = NULL, *srv = NULL, *nts = NULL, *nt = NULL; int loc_len = 0; //DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP notify:\n%.*s", n, bufr); for (i = 0; i < n; i++) { if( bufr[i] == '*' ) break; } if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL) return; while (i < n) { while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n')) i++; i += 2; if (strncasecmp(bufr+i, "SERVER:", 7) == 0) { srv = bufr+i+7; while (*srv == ' ' || *srv == '\t') srv++; } else if (strncasecmp(bufr+i, "LOCATION:", 9) == 0) { loc = bufr+i+9; while (*loc == ' ' || *loc == '\t') loc++; while (loc[loc_len]!='\r' && loc[loc_len]!='\n') loc_len++; } else if (strncasecmp(bufr+i, "NTS:", 4) == 0) { nts = bufr+i+4; while (*nts == ' ' || *nts == '\t') nts++; } else if (strncasecmp(bufr+i, "NT:", 3) == 0) { nt = bufr+i+3; while(*nt == ' ' || *nt == '\t') nt++; } } if (!loc || !srv || !nt || !nts || (strncmp(nts, "ssdp:alive", 10) != 0) || (strncmp(nt, "urn:schemas-upnp-org:device:MediaRenderer", 41) != 0)) return; loc[loc_len] = '\0'; if ((strncmp(srv, "Allegro-Software-RomPlug", 24) == 0) || /* Roku */ (strstr(loc, "SamsungMRDesc.xml") != NULL) || /* Samsung TV */ (strstrc(srv, "DigiOn DiXiM", '\r') != NULL)) /* Marantz Receiver */ { /* Check if the client is already in cache */ struct client_cache_s *client = SearchClientCache(sendername.sin_addr, 1); if (client) { if (client->type->type < EStandardDLNA150 && client->type->type != ESamsungSeriesA) { client->age = uptime(); return; } } ParseUPnPClient(loc); } } else if (memcmp(bufr, "M-SEARCH", 8) == 0) { int st_len = 0, mx_len = 0, mx_val = 0; //DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s\n", n, bufr); for (i = 0; i < n; i++) { if (bufr[i] == '*') break; } if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL) return; while (i < n) { while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n')) i++; i += 2; if (strncasecmp(bufr+i, "ST:", 3) == 0) { st = bufr+i+3; st_len = 0; while (*st == ' ' || *st == '\t') st++; while (st[st_len]!='\r' && st[st_len]!='\n') st_len++; } else if (strncasecmp(bufr+i, "MX:", 3) == 0) { mx = bufr+i+3; mx_len = 0; while (*mx == ' ' || *mx == '\t') mx++; while (mx[mx_len]!='\r' && mx[mx_len]!='\n') mx_len++; mx_val = strtol(mx, &mx_end, 10); } else if (strncasecmp(bufr+i, "MAN:", 4) == 0) { man = bufr+i+4; man_len = 0; while (*man == ' ' || *man == '\t') man++; while (man[man_len]!='\r' && man[man_len]!='\n') man_len++; } } /*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port) );*/ if (GETFLAG(DLNA_STRICT_MASK) && (ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900)) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port)); } else if (!man || (strncmp(man, "\"ssdp:discover\"", 15) != 0)) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n", inet_ntoa(sendername.sin_addr), "MAN", man_len, man); } else if (!mx || mx == mx_end || mx_val < 0) { DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n", inet_ntoa(sendername.sin_addr), "MX", mx_len, mx); } else if (st && (st_len > 0)) { int l; int iface = 0; /* find in which sub network the client is */ for (i = 0; i < n_lan_addr; i++) { if((sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr) == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr)) { iface = i; break; } } if (n_lan_addr == i) { DPRINTF(E_DEBUG, L_SSDP, "Ignoring SSDP M-SEARCH on other interface [%s]\n", inet_ntoa(sendername.sin_addr)); return; } DPRINTF(E_DEBUG, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n", inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port), st_len, st, mx_len, mx, man_len, man); /* Responds to request with a device as ST header */ for (i = 0; known_service_types[i]; i++) { l = strlen(known_service_types[i]); if ((l > st_len) || (memcmp(st, known_service_types[i], l) != 0)) continue; if (st_len != l) { /* Check version number - we only support 1. */ if ((st[l-1] == ':') && (st[l] == '1')) l++; while (l < st_len) { if (isdigit(st[l])) break; if (isspace(st[l])) { l++; continue; } DPRINTF(E_MAXDEBUG, L_SSDP, "Ignoring SSDP M-SEARCH with bad extra data '%c' [%s]\n", st[l], inet_ntoa(sendername.sin_addr)); break; } if (l != st_len) break; } _usleep(random()>>20); SendSSDPResponse(s, sendername, i, lan_addr[iface].str, port); return; } /* Responds to request with ST: ssdp:all */ /* strlen("ssdp:all") == 8 */ if ((st_len == 8) && (memcmp(st, "ssdp:all", 8) == 0)) { for (i=0; known_service_types[i]; i++) { l = strlen(known_service_types[i]); SendSSDPResponse(s, sendername, i, lan_addr[iface].str, port); } } }