139beb93cSSam Leffler /* 239beb93cSSam Leffler * UPnP SSDP for WPS 339beb93cSSam Leffler * Copyright (c) 2000-2003 Intel Corporation 439beb93cSSam Leffler * Copyright (c) 2006-2007 Sony Corporation 539beb93cSSam Leffler * Copyright (c) 2008-2009 Atheros Communications 639beb93cSSam Leffler * Copyright (c) 2009, Jouni Malinen <j@w1.fi> 739beb93cSSam Leffler * 839beb93cSSam Leffler * See wps_upnp.c for more details on licensing and code history. 939beb93cSSam Leffler */ 1039beb93cSSam Leffler 1139beb93cSSam Leffler #include "includes.h" 1239beb93cSSam Leffler 1339beb93cSSam Leffler #include <fcntl.h> 1439beb93cSSam Leffler #include <sys/ioctl.h> 1539beb93cSSam Leffler #include <net/route.h> 1639beb93cSSam Leffler 1739beb93cSSam Leffler #include "common.h" 1839beb93cSSam Leffler #include "uuid.h" 1939beb93cSSam Leffler #include "eloop.h" 2039beb93cSSam Leffler #include "wps.h" 2139beb93cSSam Leffler #include "wps_upnp.h" 2239beb93cSSam Leffler #include "wps_upnp_i.h" 2339beb93cSSam Leffler 2439beb93cSSam Leffler #define UPNP_CACHE_SEC (UPNP_CACHE_SEC_MIN + 1) /* cache time we use */ 2539beb93cSSam Leffler #define UPNP_CACHE_SEC_MIN 1800 /* min cachable time per UPnP standard */ 2639beb93cSSam Leffler #define UPNP_ADVERTISE_REPEAT 2 /* no more than 3 */ 2739beb93cSSam Leffler #define MULTICAST_MAX_READ 1600 /* max bytes we'll read for UPD request */ 2839beb93cSSam Leffler #define MAX_MSEARCH 20 /* max simultaneous M-SEARCH replies ongoing */ 2939beb93cSSam Leffler #define SSDP_TARGET "239.0.0.0" 3039beb93cSSam Leffler #define SSDP_NETMASK "255.0.0.0" 3139beb93cSSam Leffler 3239beb93cSSam Leffler 3339beb93cSSam Leffler /* Check tokens for equality, where tokens consist of letters, digits, 3439beb93cSSam Leffler * underscore and hyphen, and are matched case insensitive. 3539beb93cSSam Leffler */ 3639beb93cSSam Leffler static int token_eq(const char *s1, const char *s2) 3739beb93cSSam Leffler { 3839beb93cSSam Leffler int c1; 3939beb93cSSam Leffler int c2; 4039beb93cSSam Leffler int end1 = 0; 4139beb93cSSam Leffler int end2 = 0; 4239beb93cSSam Leffler for (;;) { 4339beb93cSSam Leffler c1 = *s1++; 4439beb93cSSam Leffler c2 = *s2++; 4539beb93cSSam Leffler if (isalpha(c1) && isupper(c1)) 4639beb93cSSam Leffler c1 = tolower(c1); 4739beb93cSSam Leffler if (isalpha(c2) && isupper(c2)) 4839beb93cSSam Leffler c2 = tolower(c2); 4939beb93cSSam Leffler end1 = !(isalnum(c1) || c1 == '_' || c1 == '-'); 5039beb93cSSam Leffler end2 = !(isalnum(c2) || c2 == '_' || c2 == '-'); 5139beb93cSSam Leffler if (end1 || end2 || c1 != c2) 5239beb93cSSam Leffler break; 5339beb93cSSam Leffler } 5439beb93cSSam Leffler return end1 && end2; /* reached end of both words? */ 5539beb93cSSam Leffler } 5639beb93cSSam Leffler 5739beb93cSSam Leffler 5839beb93cSSam Leffler /* Return length of token (see above for definition of token) */ 5939beb93cSSam Leffler static int token_length(const char *s) 6039beb93cSSam Leffler { 6139beb93cSSam Leffler const char *begin = s; 6239beb93cSSam Leffler for (;; s++) { 6339beb93cSSam Leffler int c = *s; 6439beb93cSSam Leffler int end = !(isalnum(c) || c == '_' || c == '-'); 6539beb93cSSam Leffler if (end) 6639beb93cSSam Leffler break; 6739beb93cSSam Leffler } 6839beb93cSSam Leffler return s - begin; 6939beb93cSSam Leffler } 7039beb93cSSam Leffler 7139beb93cSSam Leffler 7239beb93cSSam Leffler /* return length of interword separation. 7339beb93cSSam Leffler * This accepts only spaces/tabs and thus will not traverse a line 7439beb93cSSam Leffler * or buffer ending. 7539beb93cSSam Leffler */ 7639beb93cSSam Leffler static int word_separation_length(const char *s) 7739beb93cSSam Leffler { 7839beb93cSSam Leffler const char *begin = s; 7939beb93cSSam Leffler for (;; s++) { 8039beb93cSSam Leffler int c = *s; 8139beb93cSSam Leffler if (c == ' ' || c == '\t') 8239beb93cSSam Leffler continue; 8339beb93cSSam Leffler break; 8439beb93cSSam Leffler } 8539beb93cSSam Leffler return s - begin; 8639beb93cSSam Leffler } 8739beb93cSSam Leffler 8839beb93cSSam Leffler 8939beb93cSSam Leffler /* No. of chars through (including) end of line */ 9039beb93cSSam Leffler static int line_length(const char *l) 9139beb93cSSam Leffler { 9239beb93cSSam Leffler const char *lp = l; 9339beb93cSSam Leffler while (*lp && *lp != '\n') 9439beb93cSSam Leffler lp++; 9539beb93cSSam Leffler if (*lp == '\n') 9639beb93cSSam Leffler lp++; 9739beb93cSSam Leffler return lp - l; 9839beb93cSSam Leffler } 9939beb93cSSam Leffler 10039beb93cSSam Leffler 10139beb93cSSam Leffler /* No. of chars excluding trailing whitespace */ 10239beb93cSSam Leffler static int line_length_stripped(const char *l) 10339beb93cSSam Leffler { 10439beb93cSSam Leffler const char *lp = l + line_length(l); 10539beb93cSSam Leffler while (lp > l && !isgraph(lp[-1])) 10639beb93cSSam Leffler lp--; 10739beb93cSSam Leffler return lp - l; 10839beb93cSSam Leffler } 10939beb93cSSam Leffler 11039beb93cSSam Leffler 11139beb93cSSam Leffler static int str_starts(const char *str, const char *start) 11239beb93cSSam Leffler { 11339beb93cSSam Leffler return os_strncmp(str, start, os_strlen(start)) == 0; 11439beb93cSSam Leffler } 11539beb93cSSam Leffler 11639beb93cSSam Leffler 11739beb93cSSam Leffler /*************************************************************************** 11839beb93cSSam Leffler * Advertisements. 11939beb93cSSam Leffler * These are multicast to the world to tell them we are here. 12039beb93cSSam Leffler * The individual packets are spread out in time to limit loss, 12139beb93cSSam Leffler * and then after a much longer period of time the whole sequence 12239beb93cSSam Leffler * is repeated again (for NOTIFYs only). 12339beb93cSSam Leffler **************************************************************************/ 12439beb93cSSam Leffler 12539beb93cSSam Leffler /** 12639beb93cSSam Leffler * next_advertisement - Build next message and advance the state machine 12739beb93cSSam Leffler * @a: Advertisement state 12839beb93cSSam Leffler * @islast: Buffer for indicating whether this is the last message (= 1) 12939beb93cSSam Leffler * Returns: The new message (caller is responsible for freeing this) 13039beb93cSSam Leffler * 13139beb93cSSam Leffler * Note: next_advertisement is shared code with msearchreply_* functions 13239beb93cSSam Leffler */ 13339beb93cSSam Leffler static struct wpabuf * 13439beb93cSSam Leffler next_advertisement(struct advertisement_state_machine *a, int *islast) 13539beb93cSSam Leffler { 13639beb93cSSam Leffler struct wpabuf *msg; 13739beb93cSSam Leffler char *NTString = ""; 13839beb93cSSam Leffler char uuid_string[80]; 13939beb93cSSam Leffler 14039beb93cSSam Leffler *islast = 0; 14139beb93cSSam Leffler uuid_bin2str(a->sm->wps->uuid, uuid_string, sizeof(uuid_string)); 14239beb93cSSam Leffler msg = wpabuf_alloc(800); /* more than big enough */ 14339beb93cSSam Leffler if (msg == NULL) 14439beb93cSSam Leffler goto fail; 14539beb93cSSam Leffler switch (a->type) { 14639beb93cSSam Leffler case ADVERTISE_UP: 14739beb93cSSam Leffler case ADVERTISE_DOWN: 14839beb93cSSam Leffler NTString = "NT"; 14939beb93cSSam Leffler wpabuf_put_str(msg, "NOTIFY * HTTP/1.1\r\n"); 15039beb93cSSam Leffler wpabuf_printf(msg, "HOST: %s:%d\r\n", 15139beb93cSSam Leffler UPNP_MULTICAST_ADDRESS, UPNP_MULTICAST_PORT); 15239beb93cSSam Leffler wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n", 15339beb93cSSam Leffler UPNP_CACHE_SEC); 15439beb93cSSam Leffler wpabuf_printf(msg, "NTS: %s\r\n", 15539beb93cSSam Leffler (a->type == ADVERTISE_UP ? 15639beb93cSSam Leffler "ssdp:alive" : "ssdp:byebye")); 15739beb93cSSam Leffler break; 15839beb93cSSam Leffler case MSEARCH_REPLY: 15939beb93cSSam Leffler NTString = "ST"; 16039beb93cSSam Leffler wpabuf_put_str(msg, "HTTP/1.1 200 OK\r\n"); 16139beb93cSSam Leffler wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n", 16239beb93cSSam Leffler UPNP_CACHE_SEC); 16339beb93cSSam Leffler 16439beb93cSSam Leffler wpabuf_put_str(msg, "DATE: "); 16539beb93cSSam Leffler format_date(msg); 16639beb93cSSam Leffler wpabuf_put_str(msg, "\r\n"); 16739beb93cSSam Leffler 16839beb93cSSam Leffler wpabuf_put_str(msg, "EXT:\r\n"); 16939beb93cSSam Leffler break; 17039beb93cSSam Leffler } 17139beb93cSSam Leffler 17239beb93cSSam Leffler if (a->type != ADVERTISE_DOWN) { 17339beb93cSSam Leffler /* Where others may get our XML files from */ 17439beb93cSSam Leffler wpabuf_printf(msg, "LOCATION: http://%s:%d/%s\r\n", 17539beb93cSSam Leffler a->sm->ip_addr_text, a->sm->web_port, 17639beb93cSSam Leffler UPNP_WPS_DEVICE_XML_FILE); 17739beb93cSSam Leffler } 17839beb93cSSam Leffler 17939beb93cSSam Leffler /* The SERVER line has three comma-separated fields: 18039beb93cSSam Leffler * operating system / version 18139beb93cSSam Leffler * upnp version 18239beb93cSSam Leffler * software package / version 18339beb93cSSam Leffler * However, only the UPnP version is really required, the 18439beb93cSSam Leffler * others can be place holders... for security reasons 18539beb93cSSam Leffler * it is better to NOT provide extra information. 18639beb93cSSam Leffler */ 18739beb93cSSam Leffler wpabuf_put_str(msg, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"); 18839beb93cSSam Leffler 18939beb93cSSam Leffler switch (a->state / UPNP_ADVERTISE_REPEAT) { 19039beb93cSSam Leffler case 0: 19139beb93cSSam Leffler wpabuf_printf(msg, "%s: upnp:rootdevice\r\n", NTString); 19239beb93cSSam Leffler wpabuf_printf(msg, "USN: uuid:%s::upnp:rootdevice\r\n", 19339beb93cSSam Leffler uuid_string); 19439beb93cSSam Leffler break; 19539beb93cSSam Leffler case 1: 19639beb93cSSam Leffler wpabuf_printf(msg, "%s: uuid:%s\r\n", NTString, uuid_string); 19739beb93cSSam Leffler wpabuf_printf(msg, "USN: uuid:%s\r\n", uuid_string); 19839beb93cSSam Leffler break; 19939beb93cSSam Leffler case 2: 20039beb93cSSam Leffler wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:device:" 20139beb93cSSam Leffler "WFADevice:1\r\n", NTString); 20239beb93cSSam Leffler wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-" 20339beb93cSSam Leffler "org:device:WFADevice:1\r\n", uuid_string); 20439beb93cSSam Leffler break; 20539beb93cSSam Leffler case 3: 20639beb93cSSam Leffler wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:service:" 20739beb93cSSam Leffler "WFAWLANConfig:1\r\n", NTString); 20839beb93cSSam Leffler wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-" 20939beb93cSSam Leffler "org:service:WFAWLANConfig:1\r\n", uuid_string); 21039beb93cSSam Leffler break; 21139beb93cSSam Leffler } 21239beb93cSSam Leffler wpabuf_put_str(msg, "\r\n"); 21339beb93cSSam Leffler 21439beb93cSSam Leffler if (a->state + 1 >= 4 * UPNP_ADVERTISE_REPEAT) 21539beb93cSSam Leffler *islast = 1; 21639beb93cSSam Leffler 21739beb93cSSam Leffler return msg; 21839beb93cSSam Leffler 21939beb93cSSam Leffler fail: 22039beb93cSSam Leffler wpabuf_free(msg); 22139beb93cSSam Leffler return NULL; 22239beb93cSSam Leffler } 22339beb93cSSam Leffler 22439beb93cSSam Leffler 22539beb93cSSam Leffler static void advertisement_state_machine_handler(void *eloop_data, 22639beb93cSSam Leffler void *user_ctx); 22739beb93cSSam Leffler 22839beb93cSSam Leffler 22939beb93cSSam Leffler /** 23039beb93cSSam Leffler * advertisement_state_machine_stop - Stop SSDP advertisements 23139beb93cSSam Leffler * @sm: WPS UPnP state machine from upnp_wps_device_init() 23239beb93cSSam Leffler */ 23339beb93cSSam Leffler void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm) 23439beb93cSSam Leffler { 23539beb93cSSam Leffler eloop_cancel_timeout(advertisement_state_machine_handler, NULL, sm); 23639beb93cSSam Leffler } 23739beb93cSSam Leffler 23839beb93cSSam Leffler 23939beb93cSSam Leffler static void advertisement_state_machine_handler(void *eloop_data, 24039beb93cSSam Leffler void *user_ctx) 24139beb93cSSam Leffler { 24239beb93cSSam Leffler struct upnp_wps_device_sm *sm = user_ctx; 24339beb93cSSam Leffler struct advertisement_state_machine *a = &sm->advertisement; 24439beb93cSSam Leffler struct wpabuf *msg; 24539beb93cSSam Leffler int next_timeout_msec = 100; 24639beb93cSSam Leffler int next_timeout_sec = 0; 24739beb93cSSam Leffler struct sockaddr_in dest; 24839beb93cSSam Leffler int islast = 0; 24939beb93cSSam Leffler 25039beb93cSSam Leffler /* 25139beb93cSSam Leffler * Each is sent twice (in case lost) w/ 100 msec delay between; 25239beb93cSSam Leffler * spec says no more than 3 times. 25339beb93cSSam Leffler * One pair for rootdevice, one pair for uuid, and a pair each for 25439beb93cSSam Leffler * each of the two urns. 25539beb93cSSam Leffler * The entire sequence must be repeated before cache control timeout 25639beb93cSSam Leffler * (which is min 1800 seconds), 25739beb93cSSam Leffler * recommend random portion of half of the advertised cache control age 25839beb93cSSam Leffler * to ensure against loss... perhaps 1800/4 + rand*1800/4 ? 25939beb93cSSam Leffler * Delay random interval < 100 msec prior to initial sending. 26039beb93cSSam Leffler * TTL of 4 26139beb93cSSam Leffler */ 26239beb93cSSam Leffler 26339beb93cSSam Leffler wpa_printf(MSG_MSGDUMP, "WPS UPnP: Advertisement state=%d", a->state); 26439beb93cSSam Leffler msg = next_advertisement(a, &islast); 26539beb93cSSam Leffler if (msg == NULL) 26639beb93cSSam Leffler return; 26739beb93cSSam Leffler 26839beb93cSSam Leffler os_memset(&dest, 0, sizeof(dest)); 26939beb93cSSam Leffler dest.sin_family = AF_INET; 27039beb93cSSam Leffler dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS); 27139beb93cSSam Leffler dest.sin_port = htons(UPNP_MULTICAST_PORT); 27239beb93cSSam Leffler 27339beb93cSSam Leffler if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0, 27439beb93cSSam Leffler (struct sockaddr *) &dest, sizeof(dest)) == -1) { 27539beb93cSSam Leffler wpa_printf(MSG_ERROR, "WPS UPnP: Advertisement sendto failed:" 27639beb93cSSam Leffler "%d (%s)", errno, strerror(errno)); 27739beb93cSSam Leffler next_timeout_msec = 0; 27839beb93cSSam Leffler next_timeout_sec = 10; /* ... later */ 27939beb93cSSam Leffler } else if (islast) { 28039beb93cSSam Leffler a->state = 0; /* wrap around */ 28139beb93cSSam Leffler if (a->type == ADVERTISE_DOWN) { 28239beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_DOWN->UP"); 28339beb93cSSam Leffler a->type = ADVERTISE_UP; 28439beb93cSSam Leffler /* do it all over again right away */ 28539beb93cSSam Leffler } else { 28639beb93cSSam Leffler u16 r; 28739beb93cSSam Leffler /* 28839beb93cSSam Leffler * Start over again after a long timeout 28939beb93cSSam Leffler * (see notes above) 29039beb93cSSam Leffler */ 29139beb93cSSam Leffler next_timeout_msec = 0; 29239beb93cSSam Leffler os_get_random((void *) &r, sizeof(r)); 29339beb93cSSam Leffler next_timeout_sec = UPNP_CACHE_SEC / 4 + 29439beb93cSSam Leffler (((UPNP_CACHE_SEC / 4) * r) >> 16); 29539beb93cSSam Leffler sm->advertise_count++; 29639beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_UP (#%u); " 29739beb93cSSam Leffler "next in %d sec", 29839beb93cSSam Leffler sm->advertise_count, next_timeout_sec); 29939beb93cSSam Leffler } 30039beb93cSSam Leffler } else { 30139beb93cSSam Leffler a->state++; 30239beb93cSSam Leffler } 30339beb93cSSam Leffler 30439beb93cSSam Leffler wpabuf_free(msg); 30539beb93cSSam Leffler 30639beb93cSSam Leffler eloop_register_timeout(next_timeout_sec, next_timeout_msec, 30739beb93cSSam Leffler advertisement_state_machine_handler, NULL, sm); 30839beb93cSSam Leffler } 30939beb93cSSam Leffler 31039beb93cSSam Leffler 31139beb93cSSam Leffler /** 31239beb93cSSam Leffler * advertisement_state_machine_start - Start SSDP advertisements 31339beb93cSSam Leffler * @sm: WPS UPnP state machine from upnp_wps_device_init() 31439beb93cSSam Leffler * Returns: 0 on success, -1 on failure 31539beb93cSSam Leffler */ 31639beb93cSSam Leffler int advertisement_state_machine_start(struct upnp_wps_device_sm *sm) 31739beb93cSSam Leffler { 31839beb93cSSam Leffler struct advertisement_state_machine *a = &sm->advertisement; 31939beb93cSSam Leffler int next_timeout_msec; 32039beb93cSSam Leffler 32139beb93cSSam Leffler advertisement_state_machine_stop(sm); 32239beb93cSSam Leffler 32339beb93cSSam Leffler /* 32439beb93cSSam Leffler * Start out advertising down, this automatically switches 32539beb93cSSam Leffler * to advertising up which signals our restart. 32639beb93cSSam Leffler */ 32739beb93cSSam Leffler a->type = ADVERTISE_DOWN; 32839beb93cSSam Leffler a->state = 0; 32939beb93cSSam Leffler a->sm = sm; 33039beb93cSSam Leffler /* (other fields not used here) */ 33139beb93cSSam Leffler 33239beb93cSSam Leffler /* First timeout should be random interval < 100 msec */ 33339beb93cSSam Leffler next_timeout_msec = (100 * (os_random() & 0xFF)) >> 8; 33439beb93cSSam Leffler return eloop_register_timeout(0, next_timeout_msec, 33539beb93cSSam Leffler advertisement_state_machine_handler, 33639beb93cSSam Leffler NULL, sm); 33739beb93cSSam Leffler } 33839beb93cSSam Leffler 33939beb93cSSam Leffler 34039beb93cSSam Leffler /*************************************************************************** 34139beb93cSSam Leffler * M-SEARCH replies 34239beb93cSSam Leffler * These are very similar to the multicast advertisements, with some 34339beb93cSSam Leffler * small changes in data content; and they are sent (UDP) to a specific 34439beb93cSSam Leffler * unicast address instead of multicast. 34539beb93cSSam Leffler * They are sent in response to a UDP M-SEARCH packet. 34639beb93cSSam Leffler **************************************************************************/ 34739beb93cSSam Leffler 34839beb93cSSam Leffler static void msearchreply_state_machine_handler(void *eloop_data, 34939beb93cSSam Leffler void *user_ctx); 35039beb93cSSam Leffler 35139beb93cSSam Leffler 35239beb93cSSam Leffler /** 35339beb93cSSam Leffler * msearchreply_state_machine_stop - Stop M-SEARCH reply state machine 35439beb93cSSam Leffler * @a: Selected advertisement/reply state 35539beb93cSSam Leffler */ 35639beb93cSSam Leffler void msearchreply_state_machine_stop(struct advertisement_state_machine *a) 35739beb93cSSam Leffler { 35839beb93cSSam Leffler struct upnp_wps_device_sm *sm = a->sm; 35939beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH stop"); 36039beb93cSSam Leffler if (a->next == a) { 36139beb93cSSam Leffler sm->msearch_replies = NULL; 36239beb93cSSam Leffler } else { 36339beb93cSSam Leffler if (sm->msearch_replies == a) 36439beb93cSSam Leffler sm->msearch_replies = a->next; 36539beb93cSSam Leffler a->next->prev = a->prev; 36639beb93cSSam Leffler a->prev->next = a->next; 36739beb93cSSam Leffler } 36839beb93cSSam Leffler os_free(a); 36939beb93cSSam Leffler sm->n_msearch_replies--; 37039beb93cSSam Leffler } 37139beb93cSSam Leffler 37239beb93cSSam Leffler 37339beb93cSSam Leffler static void msearchreply_state_machine_handler(void *eloop_data, 37439beb93cSSam Leffler void *user_ctx) 37539beb93cSSam Leffler { 37639beb93cSSam Leffler struct advertisement_state_machine *a = user_ctx; 37739beb93cSSam Leffler struct upnp_wps_device_sm *sm = a->sm; 37839beb93cSSam Leffler struct wpabuf *msg; 37939beb93cSSam Leffler int next_timeout_msec = 100; 38039beb93cSSam Leffler int next_timeout_sec = 0; 38139beb93cSSam Leffler int islast = 0; 38239beb93cSSam Leffler 38339beb93cSSam Leffler /* 38439beb93cSSam Leffler * Each response is sent twice (in case lost) w/ 100 msec delay 38539beb93cSSam Leffler * between; spec says no more than 3 times. 38639beb93cSSam Leffler * One pair for rootdevice, one pair for uuid, and a pair each for 38739beb93cSSam Leffler * each of the two urns. 38839beb93cSSam Leffler */ 38939beb93cSSam Leffler 39039beb93cSSam Leffler /* TODO: should only send the requested response types */ 39139beb93cSSam Leffler 39239beb93cSSam Leffler wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply state=%d (%s:%d)", 39339beb93cSSam Leffler a->state, inet_ntoa(a->client.sin_addr), 39439beb93cSSam Leffler ntohs(a->client.sin_port)); 39539beb93cSSam Leffler msg = next_advertisement(a, &islast); 39639beb93cSSam Leffler if (msg == NULL) 39739beb93cSSam Leffler return; 39839beb93cSSam Leffler 39939beb93cSSam Leffler /* 40039beb93cSSam Leffler * Send it on the multicast socket to avoid having to set up another 40139beb93cSSam Leffler * socket. 40239beb93cSSam Leffler */ 40339beb93cSSam Leffler if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0, 40439beb93cSSam Leffler (struct sockaddr *) &a->client, sizeof(a->client)) < 0) { 40539beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply sendto " 40639beb93cSSam Leffler "errno %d (%s) for %s:%d", 40739beb93cSSam Leffler errno, strerror(errno), 40839beb93cSSam Leffler inet_ntoa(a->client.sin_addr), 40939beb93cSSam Leffler ntohs(a->client.sin_port)); 41039beb93cSSam Leffler /* Ignore error and hope for the best */ 41139beb93cSSam Leffler } 41239beb93cSSam Leffler wpabuf_free(msg); 41339beb93cSSam Leffler if (islast) { 41439beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply done"); 41539beb93cSSam Leffler msearchreply_state_machine_stop(a); 41639beb93cSSam Leffler return; 41739beb93cSSam Leffler } 41839beb93cSSam Leffler a->state++; 41939beb93cSSam Leffler 42039beb93cSSam Leffler wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply in %d.%03d sec", 42139beb93cSSam Leffler next_timeout_sec, next_timeout_msec); 42239beb93cSSam Leffler eloop_register_timeout(next_timeout_sec, next_timeout_msec, 42339beb93cSSam Leffler msearchreply_state_machine_handler, sm, a); 42439beb93cSSam Leffler } 42539beb93cSSam Leffler 42639beb93cSSam Leffler 42739beb93cSSam Leffler /** 42839beb93cSSam Leffler * msearchreply_state_machine_start - Reply to M-SEARCH discovery request 42939beb93cSSam Leffler * @sm: WPS UPnP state machine from upnp_wps_device_init() 43039beb93cSSam Leffler * @client: Client address 43139beb93cSSam Leffler * @mx: Maximum delay in seconds 43239beb93cSSam Leffler * 43339beb93cSSam Leffler * Use TTL of 4 (this was done when socket set up). 43439beb93cSSam Leffler * A response should be given in randomized portion of min(MX,120) seconds 43539beb93cSSam Leffler * 43639beb93cSSam Leffler * UPnP-arch-DeviceArchitecture, 1.2.3: 43739beb93cSSam Leffler * To be found, a device must send a UDP response to the source IP address and 43839beb93cSSam Leffler * port that sent the request to the multicast channel. Devices respond if the 43939beb93cSSam Leffler * ST header of the M-SEARCH request is "ssdp:all", "upnp:rootdevice", "uuid:" 44039beb93cSSam Leffler * followed by a UUID that exactly matches one advertised by the device. 44139beb93cSSam Leffler */ 44239beb93cSSam Leffler static void msearchreply_state_machine_start(struct upnp_wps_device_sm *sm, 44339beb93cSSam Leffler struct sockaddr_in *client, 44439beb93cSSam Leffler int mx) 44539beb93cSSam Leffler { 44639beb93cSSam Leffler struct advertisement_state_machine *a; 44739beb93cSSam Leffler int next_timeout_sec; 44839beb93cSSam Leffler int next_timeout_msec; 44939beb93cSSam Leffler 45039beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply start (%d " 45139beb93cSSam Leffler "outstanding)", sm->n_msearch_replies); 45239beb93cSSam Leffler if (sm->n_msearch_replies >= MAX_MSEARCH) { 45339beb93cSSam Leffler wpa_printf(MSG_INFO, "WPS UPnP: Too many outstanding " 45439beb93cSSam Leffler "M-SEARCH replies"); 45539beb93cSSam Leffler return; 45639beb93cSSam Leffler } 45739beb93cSSam Leffler 45839beb93cSSam Leffler a = os_zalloc(sizeof(*a)); 45939beb93cSSam Leffler if (a == NULL) 46039beb93cSSam Leffler return; 46139beb93cSSam Leffler a->type = MSEARCH_REPLY; 46239beb93cSSam Leffler a->state = 0; 46339beb93cSSam Leffler a->sm = sm; 46439beb93cSSam Leffler os_memcpy(&a->client, client, sizeof(client)); 46539beb93cSSam Leffler /* Wait time depending on MX value */ 46639beb93cSSam Leffler next_timeout_msec = (1000 * mx * (os_random() & 0xFF)) >> 8; 46739beb93cSSam Leffler next_timeout_sec = next_timeout_msec / 1000; 46839beb93cSSam Leffler next_timeout_msec = next_timeout_msec % 1000; 46939beb93cSSam Leffler if (eloop_register_timeout(next_timeout_sec, next_timeout_msec, 47039beb93cSSam Leffler msearchreply_state_machine_handler, sm, 47139beb93cSSam Leffler a)) { 47239beb93cSSam Leffler /* No way to recover (from malloc failure) */ 47339beb93cSSam Leffler goto fail; 47439beb93cSSam Leffler } 47539beb93cSSam Leffler /* Remember for future cleanup */ 47639beb93cSSam Leffler if (sm->msearch_replies) { 47739beb93cSSam Leffler a->next = sm->msearch_replies; 47839beb93cSSam Leffler a->prev = a->next->prev; 47939beb93cSSam Leffler a->prev->next = a; 48039beb93cSSam Leffler a->next->prev = a; 48139beb93cSSam Leffler } else { 48239beb93cSSam Leffler sm->msearch_replies = a->next = a->prev = a; 48339beb93cSSam Leffler } 48439beb93cSSam Leffler sm->n_msearch_replies++; 48539beb93cSSam Leffler return; 48639beb93cSSam Leffler 48739beb93cSSam Leffler fail: 48839beb93cSSam Leffler wpa_printf(MSG_INFO, "WPS UPnP: M-SEARCH reply failure!"); 48939beb93cSSam Leffler eloop_cancel_timeout(msearchreply_state_machine_handler, sm, a); 49039beb93cSSam Leffler os_free(a); 49139beb93cSSam Leffler } 49239beb93cSSam Leffler 49339beb93cSSam Leffler 49439beb93cSSam Leffler /** 49539beb93cSSam Leffler * ssdp_parse_msearch - Process a received M-SEARCH 49639beb93cSSam Leffler * @sm: WPS UPnP state machine from upnp_wps_device_init() 49739beb93cSSam Leffler * @client: Client address 49839beb93cSSam Leffler * @data: NULL terminated M-SEARCH message 49939beb93cSSam Leffler * 50039beb93cSSam Leffler * Given that we have received a header w/ M-SEARCH, act upon it 50139beb93cSSam Leffler * 50239beb93cSSam Leffler * Format of M-SEARCH (case insensitive!): 50339beb93cSSam Leffler * 50439beb93cSSam Leffler * First line must be: 50539beb93cSSam Leffler * M-SEARCH * HTTP/1.1 50639beb93cSSam Leffler * Other lines in arbitrary order: 50739beb93cSSam Leffler * HOST:239.255.255.250:1900 50839beb93cSSam Leffler * ST:<varies -- must match> 50939beb93cSSam Leffler * MAN:"ssdp:discover" 51039beb93cSSam Leffler * MX:<varies> 51139beb93cSSam Leffler * 51239beb93cSSam Leffler * It should be noted that when Microsoft Vista is still learning its IP 51339beb93cSSam Leffler * address, it sends out host lines like: HOST:[FF02::C]:1900 51439beb93cSSam Leffler */ 51539beb93cSSam Leffler static void ssdp_parse_msearch(struct upnp_wps_device_sm *sm, 51639beb93cSSam Leffler struct sockaddr_in *client, const char *data) 51739beb93cSSam Leffler { 51839beb93cSSam Leffler const char *start = data; 51939beb93cSSam Leffler const char *end; 52039beb93cSSam Leffler int got_host = 0; 52139beb93cSSam Leffler int got_st = 0, st_match = 0; 52239beb93cSSam Leffler int got_man = 0; 52339beb93cSSam Leffler int got_mx = 0; 52439beb93cSSam Leffler int mx = 0; 52539beb93cSSam Leffler 52639beb93cSSam Leffler /* 52739beb93cSSam Leffler * Skip first line M-SEARCH * HTTP/1.1 52839beb93cSSam Leffler * (perhaps we should check remainder of the line for syntax) 52939beb93cSSam Leffler */ 53039beb93cSSam Leffler data += line_length(data); 53139beb93cSSam Leffler 53239beb93cSSam Leffler /* Parse remaining lines */ 53339beb93cSSam Leffler for (; *data != '\0'; data += line_length(data)) { 53439beb93cSSam Leffler end = data + line_length_stripped(data); 53539beb93cSSam Leffler if (token_eq(data, "host")) { 53639beb93cSSam Leffler /* The host line indicates who the packet 53739beb93cSSam Leffler * is addressed to... but do we really care? 53839beb93cSSam Leffler * Note that Microsoft sometimes does funny 53939beb93cSSam Leffler * stuff with the HOST: line. 54039beb93cSSam Leffler */ 54139beb93cSSam Leffler #if 0 /* could be */ 54239beb93cSSam Leffler data += token_length(data); 54339beb93cSSam Leffler data += word_separation_length(data); 54439beb93cSSam Leffler if (*data != ':') 54539beb93cSSam Leffler goto bad; 54639beb93cSSam Leffler data++; 54739beb93cSSam Leffler data += word_separation_length(data); 54839beb93cSSam Leffler /* UPNP_MULTICAST_ADDRESS */ 54939beb93cSSam Leffler if (!str_starts(data, "239.255.255.250")) 55039beb93cSSam Leffler goto bad; 55139beb93cSSam Leffler data += os_strlen("239.255.255.250"); 55239beb93cSSam Leffler if (*data == ':') { 55339beb93cSSam Leffler if (!str_starts(data, ":1900")) 55439beb93cSSam Leffler goto bad; 55539beb93cSSam Leffler } 55639beb93cSSam Leffler #endif /* could be */ 55739beb93cSSam Leffler got_host = 1; 55839beb93cSSam Leffler continue; 55939beb93cSSam Leffler } else if (token_eq(data, "st")) { 56039beb93cSSam Leffler /* There are a number of forms; we look 56139beb93cSSam Leffler * for one that matches our case. 56239beb93cSSam Leffler */ 56339beb93cSSam Leffler got_st = 1; 56439beb93cSSam Leffler data += token_length(data); 56539beb93cSSam Leffler data += word_separation_length(data); 56639beb93cSSam Leffler if (*data != ':') 56739beb93cSSam Leffler continue; 56839beb93cSSam Leffler data++; 56939beb93cSSam Leffler data += word_separation_length(data); 57039beb93cSSam Leffler if (str_starts(data, "ssdp:all")) { 57139beb93cSSam Leffler st_match = 1; 57239beb93cSSam Leffler continue; 57339beb93cSSam Leffler } 57439beb93cSSam Leffler if (str_starts(data, "upnp:rootdevice")) { 57539beb93cSSam Leffler st_match = 1; 57639beb93cSSam Leffler continue; 57739beb93cSSam Leffler } 57839beb93cSSam Leffler if (str_starts(data, "uuid:")) { 57939beb93cSSam Leffler char uuid_string[80]; 58039beb93cSSam Leffler data += os_strlen("uuid:"); 58139beb93cSSam Leffler uuid_bin2str(sm->wps->uuid, uuid_string, 58239beb93cSSam Leffler sizeof(uuid_string)); 58339beb93cSSam Leffler if (str_starts(data, uuid_string)) 58439beb93cSSam Leffler st_match = 1; 58539beb93cSSam Leffler continue; 58639beb93cSSam Leffler } 58739beb93cSSam Leffler #if 0 58839beb93cSSam Leffler /* FIX: should we really reply to IGD string? */ 58939beb93cSSam Leffler if (str_starts(data, "urn:schemas-upnp-org:device:" 59039beb93cSSam Leffler "InternetGatewayDevice:1")) { 59139beb93cSSam Leffler st_match = 1; 59239beb93cSSam Leffler continue; 59339beb93cSSam Leffler } 59439beb93cSSam Leffler #endif 59539beb93cSSam Leffler if (str_starts(data, "urn:schemas-wifialliance-org:" 59639beb93cSSam Leffler "service:WFAWLANConfig:1")) { 59739beb93cSSam Leffler st_match = 1; 59839beb93cSSam Leffler continue; 59939beb93cSSam Leffler } 60039beb93cSSam Leffler if (str_starts(data, "urn:schemas-wifialliance-org:" 60139beb93cSSam Leffler "device:WFADevice:1")) { 60239beb93cSSam Leffler st_match = 1; 60339beb93cSSam Leffler continue; 60439beb93cSSam Leffler } 60539beb93cSSam Leffler continue; 60639beb93cSSam Leffler } else if (token_eq(data, "man")) { 60739beb93cSSam Leffler data += token_length(data); 60839beb93cSSam Leffler data += word_separation_length(data); 60939beb93cSSam Leffler if (*data != ':') 61039beb93cSSam Leffler continue; 61139beb93cSSam Leffler data++; 61239beb93cSSam Leffler data += word_separation_length(data); 61339beb93cSSam Leffler if (!str_starts(data, "\"ssdp:discover\"")) { 61439beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: Unexpected " 61539beb93cSSam Leffler "M-SEARCH man-field"); 61639beb93cSSam Leffler goto bad; 61739beb93cSSam Leffler } 61839beb93cSSam Leffler got_man = 1; 61939beb93cSSam Leffler continue; 62039beb93cSSam Leffler } else if (token_eq(data, "mx")) { 62139beb93cSSam Leffler data += token_length(data); 62239beb93cSSam Leffler data += word_separation_length(data); 62339beb93cSSam Leffler if (*data != ':') 62439beb93cSSam Leffler continue; 62539beb93cSSam Leffler data++; 62639beb93cSSam Leffler data += word_separation_length(data); 62739beb93cSSam Leffler mx = atol(data); 62839beb93cSSam Leffler got_mx = 1; 62939beb93cSSam Leffler continue; 63039beb93cSSam Leffler } 63139beb93cSSam Leffler /* ignore anything else */ 63239beb93cSSam Leffler } 63339beb93cSSam Leffler if (!got_host || !got_st || !got_man || !got_mx || mx < 0) { 63439beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid M-SEARCH: %d %d %d " 63539beb93cSSam Leffler "%d mx=%d", got_host, got_st, got_man, got_mx, mx); 63639beb93cSSam Leffler goto bad; 63739beb93cSSam Leffler } 63839beb93cSSam Leffler if (!st_match) { 63939beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored M-SEARCH (no ST " 64039beb93cSSam Leffler "match)"); 64139beb93cSSam Leffler return; 64239beb93cSSam Leffler } 64339beb93cSSam Leffler if (mx > 120) 64439beb93cSSam Leffler mx = 120; /* UPnP-arch-DeviceArchitecture, 1.2.3 */ 64539beb93cSSam Leffler msearchreply_state_machine_start(sm, client, mx); 64639beb93cSSam Leffler return; 64739beb93cSSam Leffler 64839beb93cSSam Leffler bad: 64939beb93cSSam Leffler wpa_printf(MSG_INFO, "WPS UPnP: Failed to parse M-SEARCH"); 65039beb93cSSam Leffler wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH data:\n%s", start); 65139beb93cSSam Leffler } 65239beb93cSSam Leffler 65339beb93cSSam Leffler 65439beb93cSSam Leffler /* Listening for (UDP) discovery (M-SEARCH) packets */ 65539beb93cSSam Leffler 65639beb93cSSam Leffler /** 65739beb93cSSam Leffler * ssdp_listener_stop - Stop SSDP listered 65839beb93cSSam Leffler * @sm: WPS UPnP state machine from upnp_wps_device_init() 65939beb93cSSam Leffler * 66039beb93cSSam Leffler * This function stops the SSDP listerner that was started by calling 66139beb93cSSam Leffler * ssdp_listener_start(). 66239beb93cSSam Leffler */ 66339beb93cSSam Leffler void ssdp_listener_stop(struct upnp_wps_device_sm *sm) 66439beb93cSSam Leffler { 66539beb93cSSam Leffler if (sm->ssdp_sd_registered) { 66639beb93cSSam Leffler eloop_unregister_sock(sm->ssdp_sd, EVENT_TYPE_READ); 66739beb93cSSam Leffler sm->ssdp_sd_registered = 0; 66839beb93cSSam Leffler } 66939beb93cSSam Leffler 67039beb93cSSam Leffler if (sm->ssdp_sd != -1) { 67139beb93cSSam Leffler close(sm->ssdp_sd); 67239beb93cSSam Leffler sm->ssdp_sd = -1; 67339beb93cSSam Leffler } 67439beb93cSSam Leffler 67539beb93cSSam Leffler eloop_cancel_timeout(msearchreply_state_machine_handler, sm, 67639beb93cSSam Leffler ELOOP_ALL_CTX); 67739beb93cSSam Leffler } 67839beb93cSSam Leffler 67939beb93cSSam Leffler 68039beb93cSSam Leffler static void ssdp_listener_handler(int sd, void *eloop_ctx, void *sock_ctx) 68139beb93cSSam Leffler { 68239beb93cSSam Leffler struct upnp_wps_device_sm *sm = sock_ctx; 68339beb93cSSam Leffler struct sockaddr_in addr; /* client address */ 68439beb93cSSam Leffler socklen_t addr_len; 68539beb93cSSam Leffler int nread; 68639beb93cSSam Leffler char buf[MULTICAST_MAX_READ], *pos; 68739beb93cSSam Leffler 68839beb93cSSam Leffler addr_len = sizeof(addr); 68939beb93cSSam Leffler nread = recvfrom(sm->ssdp_sd, buf, sizeof(buf) - 1, 0, 69039beb93cSSam Leffler (struct sockaddr *) &addr, &addr_len); 69139beb93cSSam Leffler if (nread <= 0) 69239beb93cSSam Leffler return; 69339beb93cSSam Leffler buf[nread] = '\0'; /* need null termination for algorithm */ 69439beb93cSSam Leffler 69539beb93cSSam Leffler if (str_starts(buf, "NOTIFY ")) { 69639beb93cSSam Leffler /* 69739beb93cSSam Leffler * Silently ignore NOTIFYs to avoid filling debug log with 69839beb93cSSam Leffler * unwanted messages. 69939beb93cSSam Leffler */ 70039beb93cSSam Leffler return; 70139beb93cSSam Leffler } 70239beb93cSSam Leffler 70339beb93cSSam Leffler pos = os_strchr(buf, '\n'); 70439beb93cSSam Leffler if (pos) 70539beb93cSSam Leffler *pos = '\0'; 70639beb93cSSam Leffler wpa_printf(MSG_MSGDUMP, "WPS UPnP: Received SSDP packet from %s:%d: " 70739beb93cSSam Leffler "%s", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), buf); 70839beb93cSSam Leffler if (pos) 70939beb93cSSam Leffler *pos = '\n'; 71039beb93cSSam Leffler 71139beb93cSSam Leffler /* Parse first line */ 71239beb93cSSam Leffler if (os_strncasecmp(buf, "M-SEARCH", os_strlen("M-SEARCH")) == 0 && 71339beb93cSSam Leffler !isgraph(buf[strlen("M-SEARCH")])) { 71439beb93cSSam Leffler ssdp_parse_msearch(sm, &addr, buf); 71539beb93cSSam Leffler return; 71639beb93cSSam Leffler } 71739beb93cSSam Leffler 71839beb93cSSam Leffler /* Ignore anything else */ 71939beb93cSSam Leffler } 72039beb93cSSam Leffler 72139beb93cSSam Leffler 72239beb93cSSam Leffler /** 72339beb93cSSam Leffler * ssdp_listener_start - Set up for receiving discovery (UDP) packets 72439beb93cSSam Leffler * @sm: WPS UPnP state machine from upnp_wps_device_init() 72539beb93cSSam Leffler * Returns: 0 on success, -1 on failure 72639beb93cSSam Leffler * 72739beb93cSSam Leffler * The SSDP listerner is stopped by calling ssdp_listener_stop(). 72839beb93cSSam Leffler */ 72939beb93cSSam Leffler int ssdp_listener_start(struct upnp_wps_device_sm *sm) 73039beb93cSSam Leffler { 73139beb93cSSam Leffler int sd = -1; 73239beb93cSSam Leffler struct sockaddr_in addr; 73339beb93cSSam Leffler struct ip_mreq mcast_addr; 73439beb93cSSam Leffler int on = 1; 73539beb93cSSam Leffler /* per UPnP spec, keep IP packet time to live (TTL) small */ 73639beb93cSSam Leffler unsigned char ttl = 4; 73739beb93cSSam Leffler 73839beb93cSSam Leffler sm->ssdp_sd = sd = socket(AF_INET, SOCK_DGRAM, 0); 73939beb93cSSam Leffler if (sd < 0) 74039beb93cSSam Leffler goto fail; 74139beb93cSSam Leffler if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0) 74239beb93cSSam Leffler goto fail; 74339beb93cSSam Leffler if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) 74439beb93cSSam Leffler goto fail; 74539beb93cSSam Leffler os_memset(&addr, 0, sizeof(addr)); 74639beb93cSSam Leffler addr.sin_family = AF_INET; 74739beb93cSSam Leffler addr.sin_addr.s_addr = htonl(INADDR_ANY); 74839beb93cSSam Leffler addr.sin_port = htons(UPNP_MULTICAST_PORT); 74939beb93cSSam Leffler if (bind(sd, (struct sockaddr *) &addr, sizeof(addr))) 75039beb93cSSam Leffler goto fail; 75139beb93cSSam Leffler os_memset(&mcast_addr, 0, sizeof(mcast_addr)); 75239beb93cSSam Leffler mcast_addr.imr_interface.s_addr = htonl(INADDR_ANY); 75339beb93cSSam Leffler mcast_addr.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS); 75439beb93cSSam Leffler if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, 75539beb93cSSam Leffler (char *) &mcast_addr, sizeof(mcast_addr))) 75639beb93cSSam Leffler goto fail; 75739beb93cSSam Leffler if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, 75839beb93cSSam Leffler &ttl, sizeof(ttl))) 75939beb93cSSam Leffler goto fail; 76039beb93cSSam Leffler if (eloop_register_sock(sd, EVENT_TYPE_READ, ssdp_listener_handler, 76139beb93cSSam Leffler NULL, sm)) 76239beb93cSSam Leffler goto fail; 76339beb93cSSam Leffler sm->ssdp_sd_registered = 1; 76439beb93cSSam Leffler return 0; 76539beb93cSSam Leffler 76639beb93cSSam Leffler fail: 76739beb93cSSam Leffler /* Error */ 76839beb93cSSam Leffler wpa_printf(MSG_ERROR, "WPS UPnP: ssdp_listener_start failed"); 76939beb93cSSam Leffler ssdp_listener_stop(sm); 77039beb93cSSam Leffler return -1; 77139beb93cSSam Leffler } 77239beb93cSSam Leffler 77339beb93cSSam Leffler 77439beb93cSSam Leffler /** 77539beb93cSSam Leffler * add_ssdp_network - Add routing entry for SSDP 77639beb93cSSam Leffler * @net_if: Selected network interface name 77739beb93cSSam Leffler * Returns: 0 on success, -1 on failure 77839beb93cSSam Leffler * 77939beb93cSSam Leffler * This function assures that the multicast address will be properly 78039beb93cSSam Leffler * handled by Linux networking code (by a modification to routing tables). 78139beb93cSSam Leffler * This must be done per network interface. It really only needs to be done 78239beb93cSSam Leffler * once after booting up, but it does not hurt to call this more frequently 78339beb93cSSam Leffler * "to be safe". 78439beb93cSSam Leffler */ 78539beb93cSSam Leffler int add_ssdp_network(char *net_if) 78639beb93cSSam Leffler { 78739beb93cSSam Leffler int ret = -1; 78839beb93cSSam Leffler int sock = -1; 78939beb93cSSam Leffler struct rtentry rt; 79039beb93cSSam Leffler struct sockaddr_in *sin; 79139beb93cSSam Leffler 79239beb93cSSam Leffler if (!net_if) 79339beb93cSSam Leffler goto fail; 79439beb93cSSam Leffler 79539beb93cSSam Leffler os_memset(&rt, 0, sizeof(rt)); 79639beb93cSSam Leffler sock = socket(AF_INET, SOCK_DGRAM, 0); 79739beb93cSSam Leffler if (sock < 0) 79839beb93cSSam Leffler goto fail; 79939beb93cSSam Leffler 80039beb93cSSam Leffler rt.rt_dev = net_if; 80139beb93cSSam Leffler sin = (struct sockaddr_in *) &rt.rt_dst; 80239beb93cSSam Leffler sin->sin_family = AF_INET; 80339beb93cSSam Leffler sin->sin_port = 0; 80439beb93cSSam Leffler sin->sin_addr.s_addr = inet_addr(SSDP_TARGET); 80539beb93cSSam Leffler sin = (struct sockaddr_in *) &rt.rt_genmask; 80639beb93cSSam Leffler sin->sin_family = AF_INET; 80739beb93cSSam Leffler sin->sin_port = 0; 80839beb93cSSam Leffler sin->sin_addr.s_addr = inet_addr(SSDP_NETMASK); 80939beb93cSSam Leffler rt.rt_flags = RTF_UP; 81039beb93cSSam Leffler if (ioctl(sock, SIOCADDRT, &rt) < 0) { 81139beb93cSSam Leffler if (errno == EPERM) { 81239beb93cSSam Leffler wpa_printf(MSG_DEBUG, "add_ssdp_network: No " 81339beb93cSSam Leffler "permissions to add routing table entry"); 81439beb93cSSam Leffler /* Continue to allow testing as non-root */ 81539beb93cSSam Leffler } else if (errno != EEXIST) { 81639beb93cSSam Leffler wpa_printf(MSG_INFO, "add_ssdp_network() ioctl errno " 81739beb93cSSam Leffler "%d (%s)", errno, strerror(errno)); 81839beb93cSSam Leffler goto fail; 81939beb93cSSam Leffler } 82039beb93cSSam Leffler } 82139beb93cSSam Leffler 82239beb93cSSam Leffler ret = 0; 82339beb93cSSam Leffler 82439beb93cSSam Leffler fail: 82539beb93cSSam Leffler if (sock >= 0) 82639beb93cSSam Leffler close(sock); 82739beb93cSSam Leffler 82839beb93cSSam Leffler return ret; 82939beb93cSSam Leffler } 83039beb93cSSam Leffler 83139beb93cSSam Leffler 83239beb93cSSam Leffler /** 83339beb93cSSam Leffler * ssdp_open_multicast - Open socket for sending multicast SSDP messages 83439beb93cSSam Leffler * @sm: WPS UPnP state machine from upnp_wps_device_init() 83539beb93cSSam Leffler * Returns: 0 on success, -1 on failure 83639beb93cSSam Leffler */ 83739beb93cSSam Leffler int ssdp_open_multicast(struct upnp_wps_device_sm *sm) 83839beb93cSSam Leffler { 83939beb93cSSam Leffler int sd = -1; 84039beb93cSSam Leffler /* per UPnP-arch-DeviceArchitecture, 1. Discovery, keep IP packet 84139beb93cSSam Leffler * time to live (TTL) small */ 84239beb93cSSam Leffler unsigned char ttl = 4; 84339beb93cSSam Leffler 84439beb93cSSam Leffler sm->multicast_sd = sd = socket(AF_INET, SOCK_DGRAM, 0); 84539beb93cSSam Leffler if (sd < 0) 84639beb93cSSam Leffler return -1; 84739beb93cSSam Leffler 84839beb93cSSam Leffler #if 0 /* maybe ok if we sometimes block on writes */ 84939beb93cSSam Leffler if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0) 85039beb93cSSam Leffler return -1; 85139beb93cSSam Leffler #endif 85239beb93cSSam Leffler 85339beb93cSSam Leffler if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, 85439beb93cSSam Leffler &sm->ip_addr, sizeof(sm->ip_addr))) 85539beb93cSSam Leffler return -1; 85639beb93cSSam Leffler if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, 85739beb93cSSam Leffler &ttl, sizeof(ttl))) 85839beb93cSSam Leffler return -1; 85939beb93cSSam Leffler 86039beb93cSSam Leffler #if 0 /* not needed, because we don't receive using multicast_sd */ 86139beb93cSSam Leffler { 86239beb93cSSam Leffler struct ip_mreq mreq; 86339beb93cSSam Leffler mreq.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS); 86439beb93cSSam Leffler mreq.imr_interface.s_addr = sm->ip_addr; 86539beb93cSSam Leffler wpa_printf(MSG_DEBUG, "WPS UPnP: Multicast addr 0x%x if addr " 86639beb93cSSam Leffler "0x%x", 86739beb93cSSam Leffler mreq.imr_multiaddr.s_addr, 86839beb93cSSam Leffler mreq.imr_interface.s_addr); 86939beb93cSSam Leffler if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, 87039beb93cSSam Leffler sizeof(mreq))) { 87139beb93cSSam Leffler wpa_printf(MSG_ERROR, 87239beb93cSSam Leffler "WPS UPnP: setsockopt " 87339beb93cSSam Leffler "IP_ADD_MEMBERSHIP errno %d (%s)", 87439beb93cSSam Leffler errno, strerror(errno)); 87539beb93cSSam Leffler return -1; 87639beb93cSSam Leffler } 87739beb93cSSam Leffler } 87839beb93cSSam Leffler #endif /* not needed */ 87939beb93cSSam Leffler 88039beb93cSSam Leffler /* 88139beb93cSSam Leffler * TODO: What about IP_MULTICAST_LOOP? It seems to be on by default? 88239beb93cSSam Leffler * which aids debugging I suppose but isn't really necessary? 88339beb93cSSam Leffler */ 88439beb93cSSam Leffler 88539beb93cSSam Leffler return 0; 88639beb93cSSam Leffler } 887