| /* |
| * Copyright 2014 Google Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * ssdptax (SSDP Taxonomy) |
| * |
| * A client implementing the API described in |
| * http://miniupnp.free.fr/minissdpd.html |
| * |
| * Requests the list of all known SSDP nodes, requests |
| * device info from them, and tries to figure out what |
| * they are. |
| */ |
| |
| #include <arpa/inet.h> |
| #include <asm/types.h> |
| #include <ctype.h> |
| #include <curl/curl.h> |
| #include <getopt.h> |
| #include <net/if.h> |
| #include <netinet/in.h> |
| #include <regex.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| |
| #include <iostream> |
| #include <set> |
| #include <tr1/unordered_map> |
| |
| #include "l2utils.h" |
| |
| /* Encode length by using 7bit per Byte : |
| * Most significant bit of each byte specifies that the |
| * following byte is part of the code */ |
| #define DECODELENGTH(n, p) { \ |
| n = 0; \ |
| do { n = (n << 7) | (*p & 0x7f); } \ |
| while (*(p++)&0x80); \ |
| } |
| |
| #define CODELENGTH(n, p) { \ |
| if(n>=0x10000000) *(p++) = (n >> 28) | 0x80; \ |
| if(n>=0x200000) *(p++) = (n >> 21) | 0x80; \ |
| if(n>=0x4000) *(p++) = (n >> 14) | 0x80; \ |
| if(n>=0x80) *(p++) = (n >> 7) | 0x80; \ |
| *(p++) = n & 0x7f; \ |
| } |
| |
| #define SOCK_PATH "/var/run/minissdpd.sock" |
| |
| |
| typedef struct ssdp_info { |
| ssdp_info(): srv_type(), url(), friendlyName(), ipaddr(), |
| manufacturer(), model(), buffer(), failed(0) {} |
| ssdp_info(const ssdp_info& s): srv_type(s.srv_type), url(s.url), |
| friendlyName(s.friendlyName), ipaddr(s.ipaddr), |
| manufacturer(s.manufacturer), model(s.model), |
| buffer(s.buffer), failed(s.failed) {} |
| std::string srv_type; |
| std::string url; |
| std::string friendlyName; |
| std::string ipaddr; |
| std::string manufacturer; |
| std::string model; |
| |
| std::string buffer; |
| int failed; |
| } ssdp_info_t; |
| |
| |
| typedef std::tr1::unordered_map<std::string, ssdp_info_t*> ResponsesMap; |
| |
| |
| int ssdp_loop = 0; |
| |
| |
| /* SSDP Discover packet */ |
| #define SSDP_PORT 1900 |
| #define SSDP_IP4 "239.255.255.250" |
| #define SSDP_IP6 "ff02::c" |
| const char discover_template[] = "M-SEARCH * HTTP/1.1\r\n" |
| "HOST: %s:%d\r\n" |
| "MAN: \"ssdp:discover\"\r\n" |
| "MX: 2\r\n" |
| "USER-AGENT: ssdptax/1.0\r\n" |
| "ST: %s\r\n\r\n"; |
| |
| |
| static void strncpy_limited(char *dst, size_t dstlen, |
| const char *src, size_t srclen) |
| { |
| size_t i; |
| size_t lim = (srclen >= (dstlen - 1)) ? (dstlen - 2) : srclen; |
| |
| for (i = 0; i < lim; ++i) { |
| unsigned char s = src[i]; |
| if (isspace(s) || s == ';') { |
| dst[i] = ' '; // deliberately convert newline to space |
| } else if (isprint(s)) { |
| dst[i] = s; |
| } else { |
| dst[i] = '_'; |
| } |
| } |
| dst[lim] = '\0'; |
| } |
| |
| |
| static time_t monotime(void) { |
| struct timespec ts; |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| return ts.tv_sec; |
| } |
| |
| |
| /* |
| * Send a request to minissdpd. Returns a std::string containing |
| * minissdpd's response. |
| */ |
| std::string request_from_ssdpd(const char *sock_path, |
| int reqtype, const char *device) |
| { |
| int s = socket(AF_UNIX, SOCK_STREAM, 0); |
| struct sockaddr_un addr; |
| char *buffer; |
| ssize_t len; |
| size_t siz = 256 * 1024; |
| char *p; |
| int device_len = (int)strlen(device); |
| fd_set readfds; |
| struct timeval tv; |
| std::string rc; |
| |
| if (s < 0) { |
| perror("socket AF_UNIX failed"); |
| return rc; |
| } |
| memset(&addr, 0, sizeof(addr)); |
| addr.sun_family = AF_UNIX; |
| strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path)); |
| if (connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { |
| perror("connect to minisspd failed"); |
| return rc; |
| } |
| |
| if ((buffer = (char *)malloc(siz)) == NULL) { |
| fprintf(stderr, "malloc(%zu) failed\n", siz); |
| return rc; |
| } |
| memset(buffer, 0, siz); |
| |
| buffer[0] = reqtype; |
| p = buffer + 1; |
| CODELENGTH(device_len, p); |
| memcpy(p, device, device_len); |
| p += device_len; |
| if (write(s, buffer, p - buffer) < 0) { |
| perror("write to minissdpd failed"); |
| free(buffer); |
| return rc; |
| } |
| |
| FD_ZERO(&readfds); |
| FD_SET(s, &readfds); |
| memset(&tv, 0, sizeof(tv)); |
| tv.tv_sec = 2; |
| |
| if (select(s + 1, &readfds, NULL, NULL, &tv) < 1) { |
| fprintf(stderr, "select failed\n"); |
| free(buffer); |
| return rc; |
| } |
| |
| if ((len = read(s, buffer, siz)) < 0) { |
| perror("read from minissdpd failed"); |
| free(buffer); |
| return rc; |
| } |
| |
| close(s); |
| rc = std::string(buffer, len); |
| free(buffer); |
| return rc; |
| } |
| |
| |
| int get_ipv4_ssdp_socket() |
| { |
| int s; |
| int reuse = 1; |
| struct sockaddr_in sin; |
| struct ip_mreq mreq; |
| struct ip_mreqn mreqn; |
| |
| if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { |
| perror("socket SOCK_DGRAM"); |
| exit(1); |
| } |
| |
| if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse))) { |
| perror("setsockopt SO_REUSEADDR"); |
| exit(1); |
| } |
| |
| if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, |
| &ssdp_loop, sizeof(ssdp_loop))) { |
| perror("setsockopt IP_MULTICAST_LOOP"); |
| exit(1); |
| } |
| |
| memset(&sin, 0, sizeof(sin)); |
| sin.sin_family = AF_INET; |
| sin.sin_port = htons(SSDP_PORT); |
| sin.sin_addr.s_addr = INADDR_ANY; |
| if (bind(s, (struct sockaddr*)&sin, sizeof(sin))) { |
| perror("bind"); |
| exit(1); |
| } |
| |
| memset(&mreqn, 0, sizeof(mreqn)); |
| mreqn.imr_ifindex = if_nametoindex("br0"); |
| if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &mreqn, sizeof(mreqn))) { |
| perror("IP_MULTICAST_IF"); |
| exit(1); |
| } |
| |
| memset(&mreq, 0, sizeof(mreq)); |
| mreq.imr_multiaddr.s_addr = inet_addr(SSDP_IP4); |
| if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, |
| (char *)&mreq, sizeof(mreq))) { |
| perror("IP_ADD_MEMBERSHIP"); |
| exit(1); |
| } |
| |
| return s; |
| } |
| |
| |
| void send_ssdp_ip4_request(int s, const char *search) |
| { |
| struct sockaddr_in sin; |
| char buf[1024]; |
| ssize_t len; |
| |
| snprintf(buf, sizeof(buf), discover_template, SSDP_IP4, SSDP_PORT, search); |
| memset(&sin, 0, sizeof(sin)); |
| sin.sin_family = AF_INET; |
| sin.sin_port = htons(SSDP_PORT); |
| sin.sin_addr.s_addr = inet_addr(SSDP_IP4); |
| len = strlen(buf); |
| if (sendto(s, buf, len, 0, (struct sockaddr*)&sin, sizeof(sin)) != len) { |
| perror("sendto multicast IPv4"); |
| exit(1); |
| } |
| } |
| |
| |
| int get_ipv6_ssdp_socket() |
| { |
| int s; |
| int reuse = 1; |
| struct sockaddr_in6 sin6; |
| struct ipv6_mreq mreq; |
| int idx; |
| int hops; |
| |
| if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { |
| perror("socket SOCK_DGRAM"); |
| exit(1); |
| } |
| |
| if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse))) { |
| perror("setsockopt SO_REUSEADDR"); |
| exit(1); |
| } |
| |
| if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, |
| &ssdp_loop, sizeof(ssdp_loop))) { |
| perror("setsockopt IPV6_MULTICAST_LOOP"); |
| exit(1); |
| } |
| |
| memset(&sin6, 0, sizeof(sin6)); |
| sin6.sin6_family = AF_INET6; |
| sin6.sin6_port = htons(SSDP_PORT); |
| sin6.sin6_addr = in6addr_any; |
| if (bind(s, (struct sockaddr*)&sin6, sizeof(sin6))) { |
| perror("bind"); |
| exit(1); |
| } |
| |
| idx = if_nametoindex("br0"); |
| if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &idx, sizeof(idx))) { |
| perror("IP_MULTICAST_IF"); |
| exit(1); |
| } |
| |
| hops = 2; |
| if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops))) { |
| perror("IPV6_MULTICAST_HOPS"); |
| exit(1); |
| } |
| |
| memset(&mreq, 0, sizeof(mreq)); |
| mreq.ipv6mr_interface = idx; |
| if (inet_pton(AF_INET6, SSDP_IP6, &mreq.ipv6mr_multiaddr) != 1) { |
| fprintf(stderr, "ERR: inet_pton(%s) failed", SSDP_IP6); |
| exit(1); |
| } |
| if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) { |
| perror("ERR: setsockopt(IPV6_JOIN_GROUP)"); |
| exit(1); |
| } |
| |
| return s; |
| } |
| |
| |
| void send_ssdp_ip6_request(int s, const char *search) |
| { |
| struct sockaddr_in6 sin6; |
| char buf[1024]; |
| ssize_t len; |
| |
| snprintf(buf, sizeof(buf), discover_template, SSDP_IP6, SSDP_PORT, search); |
| memset(&sin6, 0, sizeof(sin6)); |
| sin6.sin6_family = AF_INET6; |
| sin6.sin6_port = htons(SSDP_PORT); |
| if (inet_pton(AF_INET6, SSDP_IP6, &sin6.sin6_addr) != 1) { |
| fprintf(stderr, "ERR: inet_pton(%s) failed", SSDP_IP6); |
| exit(1); |
| } |
| len = strlen(buf); |
| if (sendto(s, buf, len, 0, (struct sockaddr*)&sin6, sizeof(sin6)) != len) { |
| perror("sendto multicast IPv6"); |
| exit(1); |
| } |
| } |
| |
| |
| /* |
| * Returns true if the friendlyName appears to include an email address. |
| */ |
| bool contains_email_address(const std::string &friendlyName) |
| { |
| regex_t r_email; |
| int rc; |
| |
| if (regcomp(&r_email, ".+@[a-z0-9.-]+\\.[a-z0-9.-]+", |
| REG_EXTENDED | REG_ICASE | REG_NOSUB)) { |
| fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__); |
| exit(1); |
| } |
| |
| rc = regexec(&r_email, friendlyName.c_str(), 0, NULL, 0); |
| regfree(&r_email); |
| |
| return (rc == 0); |
| } |
| |
| |
| /* |
| * Combine the manufacturer and model. If the manufacturer name |
| * is already present in the model string, don't duplicate it. |
| */ |
| const std::string unfriendly_name(const std::string &manufacturer, |
| const std::string &model) |
| { |
| if (strcasestr(model.c_str(), manufacturer.c_str()) != NULL) { |
| return model; |
| } |
| |
| return manufacturer + " " + model; |
| } |
| |
| |
| std::string format_response(const ssdp_info_t *info, L2Map *l2map) |
| { |
| std::string mac; |
| std::string ipaddr; |
| std::string result; |
| |
| if (info->failed) { |
| /* |
| * We could not fetch information from this client. That often means that |
| * the device was powered off recently. minissdpd still remembers that |
| * it is there, but we cannot contact it. |
| * |
| * Don't print anything for these, as we'd end up calling them "Unknown" |
| * and that is misleading. We only report information about devices which |
| * are active right now. |
| */ |
| return result; |
| } |
| |
| mac = get_l2addr_for_ip(info->ipaddr); |
| if (contains_email_address(info->friendlyName)) { |
| result = "ssdp " + mac + " REDACTED;" + info->srv_type; |
| } else if (info->friendlyName.length() > 0) { |
| result = "ssdp " + mac + " " + info->friendlyName + ";" + |
| unfriendly_name(info->manufacturer, info->model); |
| } else { |
| result = "ssdp " + mac + " Unknown;" + info->srv_type; |
| } |
| |
| return result; |
| } |
| |
| |
| void parse_minissdpd_response(std::string &response, |
| std::string &url, std::string &srv_type) |
| { |
| size_t slen; |
| const char *p; |
| const char *end = response.c_str() + response.length(); |
| char urlbuf[256]; |
| char srv_type_buf[256]; |
| |
| memset(urlbuf, 0, sizeof(urlbuf)); |
| memset(srv_type_buf, 0, sizeof(srv_type_buf)); |
| |
| p = response.c_str(); |
| DECODELENGTH(slen, p); |
| if ((p + slen) > end) { |
| fprintf(stderr, "Unable to parse SSDP response\n"); |
| return; |
| } |
| strncpy_limited(urlbuf, sizeof(urlbuf), p, slen); |
| p += slen; |
| |
| DECODELENGTH(slen, p); |
| if ((p + slen) > end) { |
| fprintf(stderr, "Unable to parse SSDP response\n"); |
| return; |
| } |
| strncpy_limited(srv_type_buf, sizeof(srv_type_buf), p, slen); |
| p += slen; |
| |
| DECODELENGTH(slen, p); |
| if ((p + slen) > end) { |
| fprintf(stderr, "Unable to parse SSDP response\n"); |
| return; |
| } |
| /* Skip over the UUID without processing it. */ |
| p += slen; |
| |
| url = urlbuf; |
| srv_type = srv_type_buf; |
| |
| response.erase(0, (p - response.c_str())); |
| } |
| |
| |
| const char *findXmlField(const char *ptr, const char *label, ssize_t *len) |
| { |
| char openlabel[64], closelabel[64]; |
| const char *start, *end; |
| |
| snprintf(openlabel, sizeof(openlabel), "<%s>", label); |
| snprintf(closelabel, sizeof(closelabel), "</%s>", label); |
| |
| start = strcasestr(ptr, openlabel) + strlen(openlabel); |
| end = strcasestr(ptr, closelabel); |
| |
| if (start < end) { |
| *len = end - start; |
| return start; |
| } |
| |
| return NULL; |
| } |
| |
| |
| /* |
| * Expected value in buffer is an XML blob of |
| * http://upnp.org/specs/basic/UPnP-basic-Basic-v1-Device.pdf |
| * |
| * Like this (a Samsung TV): |
| * <?xml version="1.0"?> |
| * <root xmlns='urn:schemas-upnp-org:device-1-0' ... |
| * <device> |
| * <deviceType>urn:dial-multiscreen-org:device:dialreceiver:1</deviceType> |
| * <friendlyName>[TV]Samsung LED60</friendlyName> |
| * <manufacturer>Samsung Electronics</manufacturer> |
| * <manufacturerURL>http://www.samsung.com/sec</manufacturerURL> |
| * <modelDescription>Samsung TV NS</modelDescription> |
| * <modelName>UN60F6300</modelName> |
| * <modelNumber>1.0</modelNumber> |
| * <modelURL>http://www.samsung.com/sec</modelURL> |
| * ... etc, etc ... |
| */ |
| void extract_fields_from_buffer(ssdp_info_t *info) |
| { |
| const char *ptr = info->buffer.c_str(); |
| const char *p; |
| ssize_t len; |
| |
| if ((p = findXmlField(ptr, "friendlyName", &len)) == NULL) { |
| p = findXmlField(ptr, "modelDescription", &len); |
| } |
| if (p && len > 0) { |
| info->friendlyName = std::string(p, len); |
| } |
| |
| p = findXmlField(ptr, "manufacturer", &len); |
| if (p && len > 0) { |
| info->manufacturer = std::string(p, len); |
| } |
| |
| p = findXmlField(ptr, "modelName", &len); |
| if (p && len > 0) { |
| info->model = std::string(p, len); |
| } |
| } |
| |
| |
| size_t callback(const char *ptr, size_t size, size_t nmemb, void *userdata) |
| { |
| ssdp_info_t *info = (ssdp_info_t *)userdata; |
| info->buffer.append(ptr, size * nmemb); |
| return size * nmemb; |
| } |
| |
| |
| /* |
| * SSDP returned an endpoint URL, use curl to GET its contents. |
| */ |
| void fetch_device_info(const std::string &url, ssdp_info_t *info) |
| { |
| CURL *curl = curl_easy_init(); |
| char *ip; |
| |
| if (!curl) { |
| fprintf(stderr, "curl_easy_init failed\n"); |
| return; |
| } |
| curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); |
| curl_easy_setopt(curl, CURLOPT_PATH_AS_IS, 1L); |
| curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &callback); |
| curl_easy_setopt(curl, CURLOPT_WRITEDATA, info); |
| curl_easy_setopt(curl, CURLOPT_USERAGENT, "ssdptaxonomy/1.0"); |
| curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1L); |
| curl_easy_setopt(curl, CURLOPT_FAILONERROR, true); |
| if (curl_easy_perform(curl) == CURLE_OK) { |
| extract_fields_from_buffer(info); |
| } else { |
| info->failed = 1; |
| } |
| if (curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &ip) == CURLE_OK) { |
| info->ipaddr = ip; |
| } |
| |
| info->buffer.clear(); |
| curl_easy_cleanup(curl); |
| } |
| |
| |
| std::string trim(std::string s) |
| { |
| size_t start = s.find_first_not_of(" \t\v\f\b\r\n"); |
| if (std::string::npos != start && 0 != start) s = s.erase(0, start); |
| |
| size_t end = s.find_last_not_of(" \t\v\f\b\r\n"); |
| if (std::string::npos != end) s = s.substr(0, end + 1); |
| |
| return s; |
| } |
| |
| |
| void parse_ssdp_response(int s, ResponsesMap &responses) |
| { |
| ssdp_info_t *info = new ssdp_info_t; |
| char buffer[4096]; |
| char *p, *saveptr, *strtok_pos; |
| ssize_t pktlen; |
| |
| memset(buffer, 0, sizeof(buffer)); |
| pktlen = recv(s, buffer, sizeof(buffer) - 1, 0); |
| if (pktlen < 0 || (size_t)pktlen >= sizeof(buffer)) { |
| fprintf(stderr, "error receiving SSDP response, pktlen=%zd\n", pktlen); |
| delete info; |
| /* not fatal, just return */ |
| return; |
| } |
| buffer[pktlen] = '\0'; |
| strtok_pos = buffer; |
| |
| while ((p = strtok_r(strtok_pos, "\r\n", &saveptr)) != NULL) { |
| if (strlen(p) > 9 && strncasecmp(p, "location:", 9) == 0) { |
| char urlbuf[512]; |
| p += 9; |
| strncpy_limited(urlbuf, sizeof(urlbuf), p, strlen(p)); |
| info->url = trim(std::string(urlbuf, strlen(urlbuf))); |
| } else if (strlen(p) > 7 && strncasecmp(p, "server:", 7) == 0) { |
| char srv_type_buf[256]; |
| p += 7; |
| strncpy_limited(srv_type_buf, sizeof(srv_type_buf), p, strlen(p)); |
| info->srv_type = trim(std::string(srv_type_buf, strlen(srv_type_buf))); |
| } |
| strtok_pos = NULL; |
| } |
| |
| if (info->url.length() && responses.find(info->url) == responses.end()) { |
| fetch_device_info(info->url, info); |
| responses[info->url] = info; |
| } else { |
| delete info; |
| } |
| } |
| |
| |
| /* Wait for SSDP NOTIFY messages to arrive. */ |
| #define TIMEOUT_SECS 5 |
| void listen_for_responses(int s4, int s6, ResponsesMap &responses) |
| { |
| struct timeval tv; |
| fd_set rfds; |
| int maxfd = (s4 > s6) ? s4 : s6; |
| time_t start = monotime(); |
| |
| memset(&tv, 0, sizeof(tv)); |
| tv.tv_sec = TIMEOUT_SECS; |
| tv.tv_usec = 0; |
| |
| FD_ZERO(&rfds); |
| FD_SET(s4, &rfds); |
| FD_SET(s6, &rfds); |
| |
| while (select(maxfd + 1, &rfds, NULL, NULL, &tv) > 0) { |
| time_t end = monotime(); |
| if (FD_ISSET(s4, &rfds)) { |
| parse_ssdp_response(s4, responses); |
| } |
| if (FD_ISSET(s6, &rfds)) { |
| parse_ssdp_response(s6, responses); |
| } |
| |
| FD_ZERO(&rfds); |
| FD_SET(s4, &rfds); |
| FD_SET(s6, &rfds); |
| |
| if ((end - start) > TIMEOUT_SECS) { |
| /* even on a network filled with SSDP packets, |
| * return after TIMEOUT_SECS. */ |
| break; |
| } |
| } |
| } |
| |
| |
| void usage(char *progname) { |
| printf("usage: %s [-t /path/to/fifo] [-s search]\n", progname); |
| printf("\t-s\tserver type to search for (default ssdp:all)\n"); |
| printf("\t-t\ttest mode, use a fake path instead of minissdpd.\n"); |
| exit(1); |
| } |
| |
| |
| int main(int argc, char **argv) |
| { |
| std::string buffer; |
| ResponsesMap responses; |
| L2Map l2map; |
| int c, s4, s6; |
| const char *sock_path = SOCK_PATH; |
| const char *search = "ssdp:all"; |
| |
| setlinebuf(stdout); |
| alarm(30); |
| |
| if (curl_global_init(CURL_GLOBAL_NOTHING)) { |
| fprintf(stderr, "curl_global_init failed\n"); |
| exit(1); |
| } |
| |
| while ((c = getopt(argc, argv, "s:t:")) != -1) { |
| switch(c) { |
| case 's': search = optarg; break; |
| case 't': |
| sock_path = optarg; |
| ssdp_loop = 1; |
| break; |
| default: usage(argv[0]); break; |
| } |
| } |
| |
| /* Request the list from MiniSSDPd */ |
| buffer = request_from_ssdpd(sock_path, 3, search); |
| if (!buffer.empty()) { |
| int num = buffer.c_str()[0]; |
| buffer.erase(0, 1); |
| while ((num-- > 0) && buffer.length() > 0) { |
| ssdp_info_t *info = new ssdp_info_t; |
| |
| parse_minissdpd_response(buffer, info->url, info->srv_type); |
| if (info->url.length() && responses.find(info->url) == responses.end()) { |
| fetch_device_info(info->url, info); |
| responses[info->url] = info; |
| } else { |
| delete info; |
| } |
| } |
| |
| /* Capture the ARP table in its current state. */ |
| get_l2_map(&l2map); |
| } |
| |
| /* Supplement what we got from MiniSSDPd by sending |
| * our own M-SEARCH and listening for responses. */ |
| s4 = get_ipv4_ssdp_socket(); |
| send_ssdp_ip4_request(s4, search); |
| s6 = get_ipv6_ssdp_socket(); |
| send_ssdp_ip6_request(s6, search); |
| listen_for_responses(s4, s6, responses); |
| close(s4); |
| s4 = -1; |
| close(s6); |
| s6 = -1; |
| |
| /* Capture any new ARP table entries which appeared after sending |
| * our own M-SEARCH. */ |
| get_l2_map(&l2map); |
| |
| typedef std::set<std::string> ResultsSet; |
| ResultsSet results; |
| for (ResponsesMap::const_iterator ii = responses.begin(); |
| ii != responses.end(); ++ii) { |
| std::string r = format_response(ii->second, &l2map); |
| if (r.length() > 0) { |
| results.insert(r); |
| } |
| } |
| |
| /* Many devices advertise multiple URLs with the same |
| * model information in all of them. Suppress duplicate |
| * output using the set. */ |
| for (ResultsSet::const_iterator ii = results.begin(); |
| ii != results.end(); ++ii) { |
| std::cout << *ii << std::endl; |
| } |
| |
| curl_global_cleanup(); |
| exit(0); |
| } |