16d49e1aeSJan Lentfer /*
26d49e1aeSJan Lentfer * UPnP SSDP for WPS
36d49e1aeSJan Lentfer * Copyright (c) 2000-2003 Intel Corporation
46d49e1aeSJan Lentfer * Copyright (c) 2006-2007 Sony Corporation
56d49e1aeSJan Lentfer * Copyright (c) 2008-2009 Atheros Communications
63ff40c12SJohn Marino * Copyright (c) 2009-2013, Jouni Malinen <j@w1.fi>
76d49e1aeSJan Lentfer *
86d49e1aeSJan Lentfer * See wps_upnp.c for more details on licensing and code history.
96d49e1aeSJan Lentfer */
106d49e1aeSJan Lentfer
116d49e1aeSJan Lentfer #include "includes.h"
126d49e1aeSJan Lentfer
136d49e1aeSJan Lentfer #include <fcntl.h>
146d49e1aeSJan Lentfer #include <sys/ioctl.h>
156d49e1aeSJan Lentfer #include <net/route.h>
163ff40c12SJohn Marino #ifdef __linux__
173ff40c12SJohn Marino #include <net/if.h>
183ff40c12SJohn Marino #endif /* __linux__ */
196d49e1aeSJan Lentfer
206d49e1aeSJan Lentfer #include "common.h"
216d49e1aeSJan Lentfer #include "uuid.h"
226d49e1aeSJan Lentfer #include "eloop.h"
236d49e1aeSJan Lentfer #include "wps.h"
246d49e1aeSJan Lentfer #include "wps_upnp.h"
256d49e1aeSJan Lentfer #include "wps_upnp_i.h"
266d49e1aeSJan Lentfer
276d49e1aeSJan Lentfer #define UPNP_CACHE_SEC (UPNP_CACHE_SEC_MIN + 1) /* cache time we use */
286d49e1aeSJan Lentfer #define UPNP_CACHE_SEC_MIN 1800 /* min cachable time per UPnP standard */
296d49e1aeSJan Lentfer #define UPNP_ADVERTISE_REPEAT 2 /* no more than 3 */
306d49e1aeSJan Lentfer #define MAX_MSEARCH 20 /* max simultaneous M-SEARCH replies ongoing */
316d49e1aeSJan Lentfer #define SSDP_TARGET "239.0.0.0"
326d49e1aeSJan Lentfer #define SSDP_NETMASK "255.0.0.0"
336d49e1aeSJan Lentfer
346d49e1aeSJan Lentfer
356d49e1aeSJan Lentfer /* Check tokens for equality, where tokens consist of letters, digits,
366d49e1aeSJan Lentfer * underscore and hyphen, and are matched case insensitive.
376d49e1aeSJan Lentfer */
token_eq(const char * s1,const char * s2)386d49e1aeSJan Lentfer static int token_eq(const char *s1, const char *s2)
396d49e1aeSJan Lentfer {
406d49e1aeSJan Lentfer int c1;
416d49e1aeSJan Lentfer int c2;
426d49e1aeSJan Lentfer int end1 = 0;
436d49e1aeSJan Lentfer int end2 = 0;
446d49e1aeSJan Lentfer for (;;) {
456d49e1aeSJan Lentfer c1 = *s1++;
466d49e1aeSJan Lentfer c2 = *s2++;
476d49e1aeSJan Lentfer if (isalpha(c1) && isupper(c1))
486d49e1aeSJan Lentfer c1 = tolower(c1);
496d49e1aeSJan Lentfer if (isalpha(c2) && isupper(c2))
506d49e1aeSJan Lentfer c2 = tolower(c2);
516d49e1aeSJan Lentfer end1 = !(isalnum(c1) || c1 == '_' || c1 == '-');
526d49e1aeSJan Lentfer end2 = !(isalnum(c2) || c2 == '_' || c2 == '-');
536d49e1aeSJan Lentfer if (end1 || end2 || c1 != c2)
546d49e1aeSJan Lentfer break;
556d49e1aeSJan Lentfer }
566d49e1aeSJan Lentfer return end1 && end2; /* reached end of both words? */
576d49e1aeSJan Lentfer }
586d49e1aeSJan Lentfer
596d49e1aeSJan Lentfer
606d49e1aeSJan Lentfer /* Return length of token (see above for definition of token) */
token_length(const char * s)616d49e1aeSJan Lentfer static int token_length(const char *s)
626d49e1aeSJan Lentfer {
636d49e1aeSJan Lentfer const char *begin = s;
646d49e1aeSJan Lentfer for (;; s++) {
656d49e1aeSJan Lentfer int c = *s;
666d49e1aeSJan Lentfer int end = !(isalnum(c) || c == '_' || c == '-');
676d49e1aeSJan Lentfer if (end)
686d49e1aeSJan Lentfer break;
696d49e1aeSJan Lentfer }
706d49e1aeSJan Lentfer return s - begin;
716d49e1aeSJan Lentfer }
726d49e1aeSJan Lentfer
736d49e1aeSJan Lentfer
746d49e1aeSJan Lentfer /* return length of interword separation.
756d49e1aeSJan Lentfer * This accepts only spaces/tabs and thus will not traverse a line
766d49e1aeSJan Lentfer * or buffer ending.
776d49e1aeSJan Lentfer */
word_separation_length(const char * s)786d49e1aeSJan Lentfer static int word_separation_length(const char *s)
796d49e1aeSJan Lentfer {
806d49e1aeSJan Lentfer const char *begin = s;
816d49e1aeSJan Lentfer for (;; s++) {
826d49e1aeSJan Lentfer int c = *s;
836d49e1aeSJan Lentfer if (c == ' ' || c == '\t')
846d49e1aeSJan Lentfer continue;
856d49e1aeSJan Lentfer break;
866d49e1aeSJan Lentfer }
876d49e1aeSJan Lentfer return s - begin;
886d49e1aeSJan Lentfer }
896d49e1aeSJan Lentfer
906d49e1aeSJan Lentfer
916d49e1aeSJan Lentfer /* No. of chars through (including) end of line */
line_length(const char * l)926d49e1aeSJan Lentfer static int line_length(const char *l)
936d49e1aeSJan Lentfer {
946d49e1aeSJan Lentfer const char *lp = l;
956d49e1aeSJan Lentfer while (*lp && *lp != '\n')
966d49e1aeSJan Lentfer lp++;
976d49e1aeSJan Lentfer if (*lp == '\n')
986d49e1aeSJan Lentfer lp++;
996d49e1aeSJan Lentfer return lp - l;
1006d49e1aeSJan Lentfer }
1016d49e1aeSJan Lentfer
1026d49e1aeSJan Lentfer
1036d49e1aeSJan Lentfer /***************************************************************************
1046d49e1aeSJan Lentfer * Advertisements.
1056d49e1aeSJan Lentfer * These are multicast to the world to tell them we are here.
1066d49e1aeSJan Lentfer * The individual packets are spread out in time to limit loss,
1076d49e1aeSJan Lentfer * and then after a much longer period of time the whole sequence
1086d49e1aeSJan Lentfer * is repeated again (for NOTIFYs only).
1096d49e1aeSJan Lentfer **************************************************************************/
1106d49e1aeSJan Lentfer
1116d49e1aeSJan Lentfer /**
1126d49e1aeSJan Lentfer * next_advertisement - Build next message and advance the state machine
1136d49e1aeSJan Lentfer * @a: Advertisement state
1146d49e1aeSJan Lentfer * @islast: Buffer for indicating whether this is the last message (= 1)
1156d49e1aeSJan Lentfer * Returns: The new message (caller is responsible for freeing this)
1166d49e1aeSJan Lentfer *
1176d49e1aeSJan Lentfer * Note: next_advertisement is shared code with msearchreply_* functions
1186d49e1aeSJan Lentfer */
1196d49e1aeSJan Lentfer static struct wpabuf *
next_advertisement(struct upnp_wps_device_sm * sm,struct advertisement_state_machine * a,int * islast)1203ff40c12SJohn Marino next_advertisement(struct upnp_wps_device_sm *sm,
1213ff40c12SJohn Marino struct advertisement_state_machine *a, int *islast)
1226d49e1aeSJan Lentfer {
1236d49e1aeSJan Lentfer struct wpabuf *msg;
1246d49e1aeSJan Lentfer char *NTString = "";
1256d49e1aeSJan Lentfer char uuid_string[80];
1263ff40c12SJohn Marino struct upnp_wps_device_interface *iface;
1276d49e1aeSJan Lentfer
1286d49e1aeSJan Lentfer *islast = 0;
1293ff40c12SJohn Marino iface = dl_list_first(&sm->interfaces,
1303ff40c12SJohn Marino struct upnp_wps_device_interface, list);
131*a1157835SDaniel Fojt if (!iface)
132*a1157835SDaniel Fojt return NULL;
1333ff40c12SJohn Marino uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string));
1346d49e1aeSJan Lentfer msg = wpabuf_alloc(800); /* more than big enough */
1356d49e1aeSJan Lentfer if (msg == NULL)
136*a1157835SDaniel Fojt return NULL;
1376d49e1aeSJan Lentfer switch (a->type) {
1386d49e1aeSJan Lentfer case ADVERTISE_UP:
1396d49e1aeSJan Lentfer case ADVERTISE_DOWN:
1406d49e1aeSJan Lentfer NTString = "NT";
1416d49e1aeSJan Lentfer wpabuf_put_str(msg, "NOTIFY * HTTP/1.1\r\n");
1426d49e1aeSJan Lentfer wpabuf_printf(msg, "HOST: %s:%d\r\n",
1436d49e1aeSJan Lentfer UPNP_MULTICAST_ADDRESS, UPNP_MULTICAST_PORT);
1446d49e1aeSJan Lentfer wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
1456d49e1aeSJan Lentfer UPNP_CACHE_SEC);
1466d49e1aeSJan Lentfer wpabuf_printf(msg, "NTS: %s\r\n",
1476d49e1aeSJan Lentfer (a->type == ADVERTISE_UP ?
1486d49e1aeSJan Lentfer "ssdp:alive" : "ssdp:byebye"));
1496d49e1aeSJan Lentfer break;
1506d49e1aeSJan Lentfer case MSEARCH_REPLY:
1516d49e1aeSJan Lentfer NTString = "ST";
1526d49e1aeSJan Lentfer wpabuf_put_str(msg, "HTTP/1.1 200 OK\r\n");
1536d49e1aeSJan Lentfer wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
1546d49e1aeSJan Lentfer UPNP_CACHE_SEC);
1556d49e1aeSJan Lentfer
1566d49e1aeSJan Lentfer wpabuf_put_str(msg, "DATE: ");
1576d49e1aeSJan Lentfer format_date(msg);
1586d49e1aeSJan Lentfer wpabuf_put_str(msg, "\r\n");
1596d49e1aeSJan Lentfer
1606d49e1aeSJan Lentfer wpabuf_put_str(msg, "EXT:\r\n");
1616d49e1aeSJan Lentfer break;
1626d49e1aeSJan Lentfer }
1636d49e1aeSJan Lentfer
1646d49e1aeSJan Lentfer if (a->type != ADVERTISE_DOWN) {
1656d49e1aeSJan Lentfer /* Where others may get our XML files from */
1666d49e1aeSJan Lentfer wpabuf_printf(msg, "LOCATION: http://%s:%d/%s\r\n",
1673ff40c12SJohn Marino sm->ip_addr_text, sm->web_port,
1686d49e1aeSJan Lentfer UPNP_WPS_DEVICE_XML_FILE);
1696d49e1aeSJan Lentfer }
1706d49e1aeSJan Lentfer
1716d49e1aeSJan Lentfer /* The SERVER line has three comma-separated fields:
1726d49e1aeSJan Lentfer * operating system / version
1736d49e1aeSJan Lentfer * upnp version
1746d49e1aeSJan Lentfer * software package / version
1756d49e1aeSJan Lentfer * However, only the UPnP version is really required, the
1766d49e1aeSJan Lentfer * others can be place holders... for security reasons
1776d49e1aeSJan Lentfer * it is better to NOT provide extra information.
1786d49e1aeSJan Lentfer */
1796d49e1aeSJan Lentfer wpabuf_put_str(msg, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n");
1806d49e1aeSJan Lentfer
1816d49e1aeSJan Lentfer switch (a->state / UPNP_ADVERTISE_REPEAT) {
1826d49e1aeSJan Lentfer case 0:
1836d49e1aeSJan Lentfer wpabuf_printf(msg, "%s: upnp:rootdevice\r\n", NTString);
1846d49e1aeSJan Lentfer wpabuf_printf(msg, "USN: uuid:%s::upnp:rootdevice\r\n",
1856d49e1aeSJan Lentfer uuid_string);
1866d49e1aeSJan Lentfer break;
1876d49e1aeSJan Lentfer case 1:
1886d49e1aeSJan Lentfer wpabuf_printf(msg, "%s: uuid:%s\r\n", NTString, uuid_string);
1896d49e1aeSJan Lentfer wpabuf_printf(msg, "USN: uuid:%s\r\n", uuid_string);
1906d49e1aeSJan Lentfer break;
1916d49e1aeSJan Lentfer case 2:
1926d49e1aeSJan Lentfer wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:device:"
1936d49e1aeSJan Lentfer "WFADevice:1\r\n", NTString);
1946d49e1aeSJan Lentfer wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
1956d49e1aeSJan Lentfer "org:device:WFADevice:1\r\n", uuid_string);
1966d49e1aeSJan Lentfer break;
1976d49e1aeSJan Lentfer case 3:
1986d49e1aeSJan Lentfer wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:service:"
1996d49e1aeSJan Lentfer "WFAWLANConfig:1\r\n", NTString);
2006d49e1aeSJan Lentfer wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
2016d49e1aeSJan Lentfer "org:service:WFAWLANConfig:1\r\n", uuid_string);
2026d49e1aeSJan Lentfer break;
2036d49e1aeSJan Lentfer }
2046d49e1aeSJan Lentfer wpabuf_put_str(msg, "\r\n");
2056d49e1aeSJan Lentfer
2066d49e1aeSJan Lentfer if (a->state + 1 >= 4 * UPNP_ADVERTISE_REPEAT)
2076d49e1aeSJan Lentfer *islast = 1;
2086d49e1aeSJan Lentfer
2096d49e1aeSJan Lentfer return msg;
2106d49e1aeSJan Lentfer }
2116d49e1aeSJan Lentfer
2126d49e1aeSJan Lentfer
2136d49e1aeSJan Lentfer static void advertisement_state_machine_handler(void *eloop_data,
2146d49e1aeSJan Lentfer void *user_ctx);
2156d49e1aeSJan Lentfer
2166d49e1aeSJan Lentfer
2176d49e1aeSJan Lentfer /**
2186d49e1aeSJan Lentfer * advertisement_state_machine_stop - Stop SSDP advertisements
2196d49e1aeSJan Lentfer * @sm: WPS UPnP state machine from upnp_wps_device_init()
2206d49e1aeSJan Lentfer * @send_byebye: Send byebye advertisement messages immediately
2216d49e1aeSJan Lentfer */
advertisement_state_machine_stop(struct upnp_wps_device_sm * sm,int send_byebye)2226d49e1aeSJan Lentfer void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm,
2236d49e1aeSJan Lentfer int send_byebye)
2246d49e1aeSJan Lentfer {
2256d49e1aeSJan Lentfer struct advertisement_state_machine *a = &sm->advertisement;
2266d49e1aeSJan Lentfer int islast = 0;
2276d49e1aeSJan Lentfer struct wpabuf *msg;
2286d49e1aeSJan Lentfer struct sockaddr_in dest;
2296d49e1aeSJan Lentfer
2306d49e1aeSJan Lentfer eloop_cancel_timeout(advertisement_state_machine_handler, NULL, sm);
2316d49e1aeSJan Lentfer if (!send_byebye || sm->multicast_sd < 0)
2326d49e1aeSJan Lentfer return;
2336d49e1aeSJan Lentfer
2346d49e1aeSJan Lentfer a->type = ADVERTISE_DOWN;
2356d49e1aeSJan Lentfer a->state = 0;
2366d49e1aeSJan Lentfer
2376d49e1aeSJan Lentfer os_memset(&dest, 0, sizeof(dest));
2386d49e1aeSJan Lentfer dest.sin_family = AF_INET;
2396d49e1aeSJan Lentfer dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
2406d49e1aeSJan Lentfer dest.sin_port = htons(UPNP_MULTICAST_PORT);
2416d49e1aeSJan Lentfer
2426d49e1aeSJan Lentfer while (!islast) {
2433ff40c12SJohn Marino msg = next_advertisement(sm, a, &islast);
2446d49e1aeSJan Lentfer if (msg == NULL)
2456d49e1aeSJan Lentfer break;
2466d49e1aeSJan Lentfer if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg),
2476d49e1aeSJan Lentfer 0, (struct sockaddr *) &dest, sizeof(dest)) < 0) {
2486d49e1aeSJan Lentfer wpa_printf(MSG_INFO, "WPS UPnP: Advertisement sendto "
2496d49e1aeSJan Lentfer "failed: %d (%s)", errno, strerror(errno));
2506d49e1aeSJan Lentfer }
2516d49e1aeSJan Lentfer wpabuf_free(msg);
2526d49e1aeSJan Lentfer a->state++;
2536d49e1aeSJan Lentfer }
2546d49e1aeSJan Lentfer }
2556d49e1aeSJan Lentfer
2566d49e1aeSJan Lentfer
advertisement_state_machine_handler(void * eloop_data,void * user_ctx)2576d49e1aeSJan Lentfer static void advertisement_state_machine_handler(void *eloop_data,
2586d49e1aeSJan Lentfer void *user_ctx)
2596d49e1aeSJan Lentfer {
2606d49e1aeSJan Lentfer struct upnp_wps_device_sm *sm = user_ctx;
2616d49e1aeSJan Lentfer struct advertisement_state_machine *a = &sm->advertisement;
2626d49e1aeSJan Lentfer struct wpabuf *msg;
2636d49e1aeSJan Lentfer int next_timeout_msec = 100;
2646d49e1aeSJan Lentfer int next_timeout_sec = 0;
2656d49e1aeSJan Lentfer struct sockaddr_in dest;
2666d49e1aeSJan Lentfer int islast = 0;
2676d49e1aeSJan Lentfer
2686d49e1aeSJan Lentfer /*
2696d49e1aeSJan Lentfer * Each is sent twice (in case lost) w/ 100 msec delay between;
2706d49e1aeSJan Lentfer * spec says no more than 3 times.
2716d49e1aeSJan Lentfer * One pair for rootdevice, one pair for uuid, and a pair each for
2726d49e1aeSJan Lentfer * each of the two urns.
2736d49e1aeSJan Lentfer * The entire sequence must be repeated before cache control timeout
2746d49e1aeSJan Lentfer * (which is min 1800 seconds),
2756d49e1aeSJan Lentfer * recommend random portion of half of the advertised cache control age
2766d49e1aeSJan Lentfer * to ensure against loss... perhaps 1800/4 + rand*1800/4 ?
2776d49e1aeSJan Lentfer * Delay random interval < 100 msec prior to initial sending.
2786d49e1aeSJan Lentfer * TTL of 4
2796d49e1aeSJan Lentfer */
2806d49e1aeSJan Lentfer
2816d49e1aeSJan Lentfer wpa_printf(MSG_MSGDUMP, "WPS UPnP: Advertisement state=%d", a->state);
2823ff40c12SJohn Marino msg = next_advertisement(sm, a, &islast);
2836d49e1aeSJan Lentfer if (msg == NULL)
2846d49e1aeSJan Lentfer return;
2856d49e1aeSJan Lentfer
2866d49e1aeSJan Lentfer os_memset(&dest, 0, sizeof(dest));
2876d49e1aeSJan Lentfer dest.sin_family = AF_INET;
2886d49e1aeSJan Lentfer dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
2896d49e1aeSJan Lentfer dest.sin_port = htons(UPNP_MULTICAST_PORT);
2906d49e1aeSJan Lentfer
2916d49e1aeSJan Lentfer if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
2926d49e1aeSJan Lentfer (struct sockaddr *) &dest, sizeof(dest)) == -1) {
2936d49e1aeSJan Lentfer wpa_printf(MSG_ERROR, "WPS UPnP: Advertisement sendto failed:"
2946d49e1aeSJan Lentfer "%d (%s)", errno, strerror(errno));
2956d49e1aeSJan Lentfer next_timeout_msec = 0;
2966d49e1aeSJan Lentfer next_timeout_sec = 10; /* ... later */
2976d49e1aeSJan Lentfer } else if (islast) {
2986d49e1aeSJan Lentfer a->state = 0; /* wrap around */
2996d49e1aeSJan Lentfer if (a->type == ADVERTISE_DOWN) {
3006d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_DOWN->UP");
3016d49e1aeSJan Lentfer a->type = ADVERTISE_UP;
3026d49e1aeSJan Lentfer /* do it all over again right away */
3036d49e1aeSJan Lentfer } else {
3046d49e1aeSJan Lentfer u16 r;
3056d49e1aeSJan Lentfer /*
3066d49e1aeSJan Lentfer * Start over again after a long timeout
3076d49e1aeSJan Lentfer * (see notes above)
3086d49e1aeSJan Lentfer */
3096d49e1aeSJan Lentfer next_timeout_msec = 0;
310*a1157835SDaniel Fojt if (os_get_random((void *) &r, sizeof(r)) < 0)
311*a1157835SDaniel Fojt r = 32768;
3126d49e1aeSJan Lentfer next_timeout_sec = UPNP_CACHE_SEC / 4 +
3136d49e1aeSJan Lentfer (((UPNP_CACHE_SEC / 4) * r) >> 16);
3146d49e1aeSJan Lentfer sm->advertise_count++;
3156d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_UP (#%u); "
3166d49e1aeSJan Lentfer "next in %d sec",
3176d49e1aeSJan Lentfer sm->advertise_count, next_timeout_sec);
3186d49e1aeSJan Lentfer }
3196d49e1aeSJan Lentfer } else {
3206d49e1aeSJan Lentfer a->state++;
3216d49e1aeSJan Lentfer }
3226d49e1aeSJan Lentfer
3236d49e1aeSJan Lentfer wpabuf_free(msg);
3246d49e1aeSJan Lentfer
3256d49e1aeSJan Lentfer eloop_register_timeout(next_timeout_sec, next_timeout_msec,
3266d49e1aeSJan Lentfer advertisement_state_machine_handler, NULL, sm);
3276d49e1aeSJan Lentfer }
3286d49e1aeSJan Lentfer
3296d49e1aeSJan Lentfer
3306d49e1aeSJan Lentfer /**
3316d49e1aeSJan Lentfer * advertisement_state_machine_start - Start SSDP advertisements
3326d49e1aeSJan Lentfer * @sm: WPS UPnP state machine from upnp_wps_device_init()
3336d49e1aeSJan Lentfer * Returns: 0 on success, -1 on failure
3346d49e1aeSJan Lentfer */
advertisement_state_machine_start(struct upnp_wps_device_sm * sm)3356d49e1aeSJan Lentfer int advertisement_state_machine_start(struct upnp_wps_device_sm *sm)
3366d49e1aeSJan Lentfer {
3376d49e1aeSJan Lentfer struct advertisement_state_machine *a = &sm->advertisement;
3386d49e1aeSJan Lentfer int next_timeout_msec;
3396d49e1aeSJan Lentfer
3406d49e1aeSJan Lentfer advertisement_state_machine_stop(sm, 0);
3416d49e1aeSJan Lentfer
3426d49e1aeSJan Lentfer /*
3436d49e1aeSJan Lentfer * Start out advertising down, this automatically switches
3446d49e1aeSJan Lentfer * to advertising up which signals our restart.
3456d49e1aeSJan Lentfer */
3466d49e1aeSJan Lentfer a->type = ADVERTISE_DOWN;
3476d49e1aeSJan Lentfer a->state = 0;
3486d49e1aeSJan Lentfer /* (other fields not used here) */
3496d49e1aeSJan Lentfer
3506d49e1aeSJan Lentfer /* First timeout should be random interval < 100 msec */
3516d49e1aeSJan Lentfer next_timeout_msec = (100 * (os_random() & 0xFF)) >> 8;
3526d49e1aeSJan Lentfer return eloop_register_timeout(0, next_timeout_msec,
3536d49e1aeSJan Lentfer advertisement_state_machine_handler,
3546d49e1aeSJan Lentfer NULL, sm);
3556d49e1aeSJan Lentfer }
3566d49e1aeSJan Lentfer
3576d49e1aeSJan Lentfer
3586d49e1aeSJan Lentfer /***************************************************************************
3596d49e1aeSJan Lentfer * M-SEARCH replies
3606d49e1aeSJan Lentfer * These are very similar to the multicast advertisements, with some
3616d49e1aeSJan Lentfer * small changes in data content; and they are sent (UDP) to a specific
3626d49e1aeSJan Lentfer * unicast address instead of multicast.
3636d49e1aeSJan Lentfer * They are sent in response to a UDP M-SEARCH packet.
3646d49e1aeSJan Lentfer **************************************************************************/
3656d49e1aeSJan Lentfer
3666d49e1aeSJan Lentfer /**
3676d49e1aeSJan Lentfer * msearchreply_state_machine_stop - Stop M-SEARCH reply state machine
3686d49e1aeSJan Lentfer * @a: Selected advertisement/reply state
3696d49e1aeSJan Lentfer */
msearchreply_state_machine_stop(struct advertisement_state_machine * a)3706d49e1aeSJan Lentfer void msearchreply_state_machine_stop(struct advertisement_state_machine *a)
3716d49e1aeSJan Lentfer {
3726d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH stop");
3733ff40c12SJohn Marino dl_list_del(&a->list);
3746d49e1aeSJan Lentfer os_free(a);
3756d49e1aeSJan Lentfer }
3766d49e1aeSJan Lentfer
3776d49e1aeSJan Lentfer
msearchreply_state_machine_handler(void * eloop_data,void * user_ctx)3786d49e1aeSJan Lentfer static void msearchreply_state_machine_handler(void *eloop_data,
3796d49e1aeSJan Lentfer void *user_ctx)
3806d49e1aeSJan Lentfer {
3816d49e1aeSJan Lentfer struct advertisement_state_machine *a = user_ctx;
3823ff40c12SJohn Marino struct upnp_wps_device_sm *sm = eloop_data;
3836d49e1aeSJan Lentfer struct wpabuf *msg;
3846d49e1aeSJan Lentfer int next_timeout_msec = 100;
3856d49e1aeSJan Lentfer int next_timeout_sec = 0;
3866d49e1aeSJan Lentfer int islast = 0;
3876d49e1aeSJan Lentfer
3886d49e1aeSJan Lentfer /*
3896d49e1aeSJan Lentfer * Each response is sent twice (in case lost) w/ 100 msec delay
3906d49e1aeSJan Lentfer * between; spec says no more than 3 times.
3916d49e1aeSJan Lentfer * One pair for rootdevice, one pair for uuid, and a pair each for
3926d49e1aeSJan Lentfer * each of the two urns.
3936d49e1aeSJan Lentfer */
3946d49e1aeSJan Lentfer
3956d49e1aeSJan Lentfer /* TODO: should only send the requested response types */
3966d49e1aeSJan Lentfer
3976d49e1aeSJan Lentfer wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply state=%d (%s:%d)",
3986d49e1aeSJan Lentfer a->state, inet_ntoa(a->client.sin_addr),
3996d49e1aeSJan Lentfer ntohs(a->client.sin_port));
4003ff40c12SJohn Marino msg = next_advertisement(sm, a, &islast);
4016d49e1aeSJan Lentfer if (msg == NULL)
4026d49e1aeSJan Lentfer return;
4036d49e1aeSJan Lentfer
4046d49e1aeSJan Lentfer /*
4056d49e1aeSJan Lentfer * Send it on the multicast socket to avoid having to set up another
4066d49e1aeSJan Lentfer * socket.
4076d49e1aeSJan Lentfer */
4086d49e1aeSJan Lentfer if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
4096d49e1aeSJan Lentfer (struct sockaddr *) &a->client, sizeof(a->client)) < 0) {
4106d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply sendto "
4116d49e1aeSJan Lentfer "errno %d (%s) for %s:%d",
4126d49e1aeSJan Lentfer errno, strerror(errno),
4136d49e1aeSJan Lentfer inet_ntoa(a->client.sin_addr),
4146d49e1aeSJan Lentfer ntohs(a->client.sin_port));
4156d49e1aeSJan Lentfer /* Ignore error and hope for the best */
4166d49e1aeSJan Lentfer }
4176d49e1aeSJan Lentfer wpabuf_free(msg);
4186d49e1aeSJan Lentfer if (islast) {
4196d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply done");
4206d49e1aeSJan Lentfer msearchreply_state_machine_stop(a);
4216d49e1aeSJan Lentfer return;
4226d49e1aeSJan Lentfer }
4236d49e1aeSJan Lentfer a->state++;
4246d49e1aeSJan Lentfer
4256d49e1aeSJan Lentfer wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply in %d.%03d sec",
4266d49e1aeSJan Lentfer next_timeout_sec, next_timeout_msec);
4276d49e1aeSJan Lentfer eloop_register_timeout(next_timeout_sec, next_timeout_msec,
4286d49e1aeSJan Lentfer msearchreply_state_machine_handler, sm, a);
4296d49e1aeSJan Lentfer }
4306d49e1aeSJan Lentfer
4316d49e1aeSJan Lentfer
4326d49e1aeSJan Lentfer /**
4336d49e1aeSJan Lentfer * msearchreply_state_machine_start - Reply to M-SEARCH discovery request
4346d49e1aeSJan Lentfer * @sm: WPS UPnP state machine from upnp_wps_device_init()
4356d49e1aeSJan Lentfer * @client: Client address
4366d49e1aeSJan Lentfer * @mx: Maximum delay in seconds
4376d49e1aeSJan Lentfer *
4386d49e1aeSJan Lentfer * Use TTL of 4 (this was done when socket set up).
4396d49e1aeSJan Lentfer * A response should be given in randomized portion of min(MX,120) seconds
4406d49e1aeSJan Lentfer *
4416d49e1aeSJan Lentfer * UPnP-arch-DeviceArchitecture, 1.2.3:
4426d49e1aeSJan Lentfer * To be found, a device must send a UDP response to the source IP address and
4436d49e1aeSJan Lentfer * port that sent the request to the multicast channel. Devices respond if the
4446d49e1aeSJan Lentfer * ST header of the M-SEARCH request is "ssdp:all", "upnp:rootdevice", "uuid:"
4456d49e1aeSJan Lentfer * followed by a UUID that exactly matches one advertised by the device.
4466d49e1aeSJan Lentfer */
msearchreply_state_machine_start(struct upnp_wps_device_sm * sm,struct sockaddr_in * client,int mx)4476d49e1aeSJan Lentfer static void msearchreply_state_machine_start(struct upnp_wps_device_sm *sm,
4486d49e1aeSJan Lentfer struct sockaddr_in *client,
4496d49e1aeSJan Lentfer int mx)
4506d49e1aeSJan Lentfer {
4516d49e1aeSJan Lentfer struct advertisement_state_machine *a;
4526d49e1aeSJan Lentfer int next_timeout_sec;
4536d49e1aeSJan Lentfer int next_timeout_msec;
4543ff40c12SJohn Marino int replies;
4556d49e1aeSJan Lentfer
4563ff40c12SJohn Marino replies = dl_list_len(&sm->msearch_replies);
4576d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply start (%d "
4583ff40c12SJohn Marino "outstanding)", replies);
4593ff40c12SJohn Marino if (replies >= MAX_MSEARCH) {
4606d49e1aeSJan Lentfer wpa_printf(MSG_INFO, "WPS UPnP: Too many outstanding "
4616d49e1aeSJan Lentfer "M-SEARCH replies");
4626d49e1aeSJan Lentfer return;
4636d49e1aeSJan Lentfer }
4646d49e1aeSJan Lentfer
4656d49e1aeSJan Lentfer a = os_zalloc(sizeof(*a));
4666d49e1aeSJan Lentfer if (a == NULL)
4676d49e1aeSJan Lentfer return;
4686d49e1aeSJan Lentfer a->type = MSEARCH_REPLY;
4696d49e1aeSJan Lentfer a->state = 0;
4706d49e1aeSJan Lentfer os_memcpy(&a->client, client, sizeof(*client));
4716d49e1aeSJan Lentfer /* Wait time depending on MX value */
4726d49e1aeSJan Lentfer next_timeout_msec = (1000 * mx * (os_random() & 0xFF)) >> 8;
4736d49e1aeSJan Lentfer next_timeout_sec = next_timeout_msec / 1000;
4746d49e1aeSJan Lentfer next_timeout_msec = next_timeout_msec % 1000;
4756d49e1aeSJan Lentfer if (eloop_register_timeout(next_timeout_sec, next_timeout_msec,
4766d49e1aeSJan Lentfer msearchreply_state_machine_handler, sm,
4776d49e1aeSJan Lentfer a)) {
4786d49e1aeSJan Lentfer /* No way to recover (from malloc failure) */
4796d49e1aeSJan Lentfer goto fail;
4806d49e1aeSJan Lentfer }
4816d49e1aeSJan Lentfer /* Remember for future cleanup */
4823ff40c12SJohn Marino dl_list_add(&sm->msearch_replies, &a->list);
4836d49e1aeSJan Lentfer return;
4846d49e1aeSJan Lentfer
4856d49e1aeSJan Lentfer fail:
4866d49e1aeSJan Lentfer wpa_printf(MSG_INFO, "WPS UPnP: M-SEARCH reply failure!");
4876d49e1aeSJan Lentfer eloop_cancel_timeout(msearchreply_state_machine_handler, sm, a);
4886d49e1aeSJan Lentfer os_free(a);
4896d49e1aeSJan Lentfer }
4906d49e1aeSJan Lentfer
4916d49e1aeSJan Lentfer
4926d49e1aeSJan Lentfer /**
4936d49e1aeSJan Lentfer * ssdp_parse_msearch - Process a received M-SEARCH
4946d49e1aeSJan Lentfer * @sm: WPS UPnP state machine from upnp_wps_device_init()
4956d49e1aeSJan Lentfer * @client: Client address
4966d49e1aeSJan Lentfer * @data: NULL terminated M-SEARCH message
4976d49e1aeSJan Lentfer *
4986d49e1aeSJan Lentfer * Given that we have received a header w/ M-SEARCH, act upon it
4996d49e1aeSJan Lentfer *
5006d49e1aeSJan Lentfer * Format of M-SEARCH (case insensitive!):
5016d49e1aeSJan Lentfer *
5026d49e1aeSJan Lentfer * First line must be:
5036d49e1aeSJan Lentfer * M-SEARCH * HTTP/1.1
5046d49e1aeSJan Lentfer * Other lines in arbitrary order:
5056d49e1aeSJan Lentfer * HOST:239.255.255.250:1900
5066d49e1aeSJan Lentfer * ST:<varies -- must match>
5076d49e1aeSJan Lentfer * MAN:"ssdp:discover"
5086d49e1aeSJan Lentfer * MX:<varies>
5096d49e1aeSJan Lentfer *
5106d49e1aeSJan Lentfer * It should be noted that when Microsoft Vista is still learning its IP
5116d49e1aeSJan Lentfer * address, it sends out host lines like: HOST:[FF02::C]:1900
5126d49e1aeSJan Lentfer */
ssdp_parse_msearch(struct upnp_wps_device_sm * sm,struct sockaddr_in * client,const char * data)5136d49e1aeSJan Lentfer static void ssdp_parse_msearch(struct upnp_wps_device_sm *sm,
5146d49e1aeSJan Lentfer struct sockaddr_in *client, const char *data)
5156d49e1aeSJan Lentfer {
5163ff40c12SJohn Marino #ifndef CONFIG_NO_STDOUT_DEBUG
5176d49e1aeSJan Lentfer const char *start = data;
5183ff40c12SJohn Marino #endif /* CONFIG_NO_STDOUT_DEBUG */
5196d49e1aeSJan Lentfer int got_host = 0;
5206d49e1aeSJan Lentfer int got_st = 0, st_match = 0;
5216d49e1aeSJan Lentfer int got_man = 0;
5226d49e1aeSJan Lentfer int got_mx = 0;
5236d49e1aeSJan Lentfer int mx = 0;
5246d49e1aeSJan Lentfer
5256d49e1aeSJan Lentfer /*
5266d49e1aeSJan Lentfer * Skip first line M-SEARCH * HTTP/1.1
5276d49e1aeSJan Lentfer * (perhaps we should check remainder of the line for syntax)
5286d49e1aeSJan Lentfer */
5296d49e1aeSJan Lentfer data += line_length(data);
5306d49e1aeSJan Lentfer
5316d49e1aeSJan Lentfer /* Parse remaining lines */
5326d49e1aeSJan Lentfer for (; *data != '\0'; data += line_length(data)) {
5336d49e1aeSJan Lentfer if (token_eq(data, "host")) {
5346d49e1aeSJan Lentfer /* The host line indicates who the packet
5356d49e1aeSJan Lentfer * is addressed to... but do we really care?
5366d49e1aeSJan Lentfer * Note that Microsoft sometimes does funny
5376d49e1aeSJan Lentfer * stuff with the HOST: line.
5386d49e1aeSJan Lentfer */
5396d49e1aeSJan Lentfer #if 0 /* could be */
5406d49e1aeSJan Lentfer data += token_length(data);
5416d49e1aeSJan Lentfer data += word_separation_length(data);
5426d49e1aeSJan Lentfer if (*data != ':')
5436d49e1aeSJan Lentfer goto bad;
5446d49e1aeSJan Lentfer data++;
5456d49e1aeSJan Lentfer data += word_separation_length(data);
5466d49e1aeSJan Lentfer /* UPNP_MULTICAST_ADDRESS */
5476d49e1aeSJan Lentfer if (!str_starts(data, "239.255.255.250"))
5486d49e1aeSJan Lentfer goto bad;
5496d49e1aeSJan Lentfer data += os_strlen("239.255.255.250");
5506d49e1aeSJan Lentfer if (*data == ':') {
5516d49e1aeSJan Lentfer if (!str_starts(data, ":1900"))
5526d49e1aeSJan Lentfer goto bad;
5536d49e1aeSJan Lentfer }
5546d49e1aeSJan Lentfer #endif /* could be */
5556d49e1aeSJan Lentfer got_host = 1;
5566d49e1aeSJan Lentfer continue;
5576d49e1aeSJan Lentfer } else if (token_eq(data, "st")) {
5586d49e1aeSJan Lentfer /* There are a number of forms; we look
5596d49e1aeSJan Lentfer * for one that matches our case.
5606d49e1aeSJan Lentfer */
5616d49e1aeSJan Lentfer got_st = 1;
5626d49e1aeSJan Lentfer data += token_length(data);
5636d49e1aeSJan Lentfer data += word_separation_length(data);
5646d49e1aeSJan Lentfer if (*data != ':')
5656d49e1aeSJan Lentfer continue;
5666d49e1aeSJan Lentfer data++;
5676d49e1aeSJan Lentfer data += word_separation_length(data);
5686d49e1aeSJan Lentfer if (str_starts(data, "ssdp:all")) {
5696d49e1aeSJan Lentfer st_match = 1;
5706d49e1aeSJan Lentfer continue;
5716d49e1aeSJan Lentfer }
5726d49e1aeSJan Lentfer if (str_starts(data, "upnp:rootdevice")) {
5736d49e1aeSJan Lentfer st_match = 1;
5746d49e1aeSJan Lentfer continue;
5756d49e1aeSJan Lentfer }
5766d49e1aeSJan Lentfer if (str_starts(data, "uuid:")) {
5776d49e1aeSJan Lentfer char uuid_string[80];
5783ff40c12SJohn Marino struct upnp_wps_device_interface *iface;
5793ff40c12SJohn Marino iface = dl_list_first(
5803ff40c12SJohn Marino &sm->interfaces,
5813ff40c12SJohn Marino struct upnp_wps_device_interface,
5823ff40c12SJohn Marino list);
583*a1157835SDaniel Fojt if (!iface)
584*a1157835SDaniel Fojt continue;
5856d49e1aeSJan Lentfer data += os_strlen("uuid:");
5863ff40c12SJohn Marino uuid_bin2str(iface->wps->uuid, uuid_string,
5876d49e1aeSJan Lentfer sizeof(uuid_string));
5886d49e1aeSJan Lentfer if (str_starts(data, uuid_string))
5896d49e1aeSJan Lentfer st_match = 1;
5906d49e1aeSJan Lentfer continue;
5916d49e1aeSJan Lentfer }
5926d49e1aeSJan Lentfer #if 0
5936d49e1aeSJan Lentfer /* FIX: should we really reply to IGD string? */
5946d49e1aeSJan Lentfer if (str_starts(data, "urn:schemas-upnp-org:device:"
5956d49e1aeSJan Lentfer "InternetGatewayDevice:1")) {
5966d49e1aeSJan Lentfer st_match = 1;
5976d49e1aeSJan Lentfer continue;
5986d49e1aeSJan Lentfer }
5996d49e1aeSJan Lentfer #endif
6006d49e1aeSJan Lentfer if (str_starts(data, "urn:schemas-wifialliance-org:"
6016d49e1aeSJan Lentfer "service:WFAWLANConfig:1")) {
6026d49e1aeSJan Lentfer st_match = 1;
6036d49e1aeSJan Lentfer continue;
6046d49e1aeSJan Lentfer }
6056d49e1aeSJan Lentfer if (str_starts(data, "urn:schemas-wifialliance-org:"
6066d49e1aeSJan Lentfer "device:WFADevice:1")) {
6076d49e1aeSJan Lentfer st_match = 1;
6086d49e1aeSJan Lentfer continue;
6096d49e1aeSJan Lentfer }
6106d49e1aeSJan Lentfer continue;
6116d49e1aeSJan Lentfer } else if (token_eq(data, "man")) {
6126d49e1aeSJan Lentfer data += token_length(data);
6136d49e1aeSJan Lentfer data += word_separation_length(data);
6146d49e1aeSJan Lentfer if (*data != ':')
6156d49e1aeSJan Lentfer continue;
6166d49e1aeSJan Lentfer data++;
6176d49e1aeSJan Lentfer data += word_separation_length(data);
6186d49e1aeSJan Lentfer if (!str_starts(data, "\"ssdp:discover\"")) {
6196d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: Unexpected "
6206d49e1aeSJan Lentfer "M-SEARCH man-field");
6216d49e1aeSJan Lentfer goto bad;
6226d49e1aeSJan Lentfer }
6236d49e1aeSJan Lentfer got_man = 1;
6246d49e1aeSJan Lentfer continue;
6256d49e1aeSJan Lentfer } else if (token_eq(data, "mx")) {
6266d49e1aeSJan Lentfer data += token_length(data);
6276d49e1aeSJan Lentfer data += word_separation_length(data);
6286d49e1aeSJan Lentfer if (*data != ':')
6296d49e1aeSJan Lentfer continue;
6306d49e1aeSJan Lentfer data++;
6316d49e1aeSJan Lentfer data += word_separation_length(data);
6326d49e1aeSJan Lentfer mx = atol(data);
6336d49e1aeSJan Lentfer got_mx = 1;
6346d49e1aeSJan Lentfer continue;
6356d49e1aeSJan Lentfer }
6366d49e1aeSJan Lentfer /* ignore anything else */
6376d49e1aeSJan Lentfer }
6386d49e1aeSJan Lentfer if (!got_host || !got_st || !got_man || !got_mx || mx < 0) {
6396d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid M-SEARCH: %d %d %d "
6406d49e1aeSJan Lentfer "%d mx=%d", got_host, got_st, got_man, got_mx, mx);
6416d49e1aeSJan Lentfer goto bad;
6426d49e1aeSJan Lentfer }
6436d49e1aeSJan Lentfer if (!st_match) {
6446d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored M-SEARCH (no ST "
6456d49e1aeSJan Lentfer "match)");
6466d49e1aeSJan Lentfer return;
6476d49e1aeSJan Lentfer }
6486d49e1aeSJan Lentfer if (mx > 120)
6496d49e1aeSJan Lentfer mx = 120; /* UPnP-arch-DeviceArchitecture, 1.2.3 */
6506d49e1aeSJan Lentfer msearchreply_state_machine_start(sm, client, mx);
6516d49e1aeSJan Lentfer return;
6526d49e1aeSJan Lentfer
6536d49e1aeSJan Lentfer bad:
6546d49e1aeSJan Lentfer wpa_printf(MSG_INFO, "WPS UPnP: Failed to parse M-SEARCH");
6556d49e1aeSJan Lentfer wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH data:\n%s", start);
6566d49e1aeSJan Lentfer }
6576d49e1aeSJan Lentfer
6586d49e1aeSJan Lentfer
6596d49e1aeSJan Lentfer /* Listening for (UDP) discovery (M-SEARCH) packets */
6606d49e1aeSJan Lentfer
6616d49e1aeSJan Lentfer /**
6626d49e1aeSJan Lentfer * ssdp_listener_stop - Stop SSDP listered
6636d49e1aeSJan Lentfer * @sm: WPS UPnP state machine from upnp_wps_device_init()
6646d49e1aeSJan Lentfer *
6653ff40c12SJohn Marino * This function stops the SSDP listener that was started by calling
6666d49e1aeSJan Lentfer * ssdp_listener_start().
6676d49e1aeSJan Lentfer */
ssdp_listener_stop(struct upnp_wps_device_sm * sm)6686d49e1aeSJan Lentfer void ssdp_listener_stop(struct upnp_wps_device_sm *sm)
6696d49e1aeSJan Lentfer {
6706d49e1aeSJan Lentfer if (sm->ssdp_sd_registered) {
6716d49e1aeSJan Lentfer eloop_unregister_sock(sm->ssdp_sd, EVENT_TYPE_READ);
6726d49e1aeSJan Lentfer sm->ssdp_sd_registered = 0;
6736d49e1aeSJan Lentfer }
6746d49e1aeSJan Lentfer
6756d49e1aeSJan Lentfer if (sm->ssdp_sd != -1) {
6766d49e1aeSJan Lentfer close(sm->ssdp_sd);
6776d49e1aeSJan Lentfer sm->ssdp_sd = -1;
6786d49e1aeSJan Lentfer }
6796d49e1aeSJan Lentfer
6806d49e1aeSJan Lentfer eloop_cancel_timeout(msearchreply_state_machine_handler, sm,
6816d49e1aeSJan Lentfer ELOOP_ALL_CTX);
6826d49e1aeSJan Lentfer }
6836d49e1aeSJan Lentfer
6846d49e1aeSJan Lentfer
ssdp_listener_handler(int sd,void * eloop_ctx,void * sock_ctx)6856d49e1aeSJan Lentfer static void ssdp_listener_handler(int sd, void *eloop_ctx, void *sock_ctx)
6866d49e1aeSJan Lentfer {
6876d49e1aeSJan Lentfer struct upnp_wps_device_sm *sm = sock_ctx;
6886d49e1aeSJan Lentfer struct sockaddr_in addr; /* client address */
6896d49e1aeSJan Lentfer socklen_t addr_len;
6906d49e1aeSJan Lentfer int nread;
6916d49e1aeSJan Lentfer char buf[MULTICAST_MAX_READ], *pos;
6926d49e1aeSJan Lentfer
6936d49e1aeSJan Lentfer addr_len = sizeof(addr);
6946d49e1aeSJan Lentfer nread = recvfrom(sm->ssdp_sd, buf, sizeof(buf) - 1, 0,
6956d49e1aeSJan Lentfer (struct sockaddr *) &addr, &addr_len);
6966d49e1aeSJan Lentfer if (nread <= 0)
6976d49e1aeSJan Lentfer return;
6986d49e1aeSJan Lentfer buf[nread] = '\0'; /* need null termination for algorithm */
6996d49e1aeSJan Lentfer
7006d49e1aeSJan Lentfer if (str_starts(buf, "NOTIFY ")) {
7016d49e1aeSJan Lentfer /*
7026d49e1aeSJan Lentfer * Silently ignore NOTIFYs to avoid filling debug log with
7036d49e1aeSJan Lentfer * unwanted messages.
7046d49e1aeSJan Lentfer */
7056d49e1aeSJan Lentfer return;
7066d49e1aeSJan Lentfer }
7076d49e1aeSJan Lentfer
7086d49e1aeSJan Lentfer pos = os_strchr(buf, '\n');
7096d49e1aeSJan Lentfer if (pos)
7106d49e1aeSJan Lentfer *pos = '\0';
7116d49e1aeSJan Lentfer wpa_printf(MSG_MSGDUMP, "WPS UPnP: Received SSDP packet from %s:%d: "
7126d49e1aeSJan Lentfer "%s", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), buf);
7136d49e1aeSJan Lentfer if (pos)
7146d49e1aeSJan Lentfer *pos = '\n';
7156d49e1aeSJan Lentfer
7166d49e1aeSJan Lentfer /* Parse first line */
7176d49e1aeSJan Lentfer if (os_strncasecmp(buf, "M-SEARCH", os_strlen("M-SEARCH")) == 0 &&
7186d49e1aeSJan Lentfer !isgraph(buf[strlen("M-SEARCH")])) {
7196d49e1aeSJan Lentfer ssdp_parse_msearch(sm, &addr, buf);
7206d49e1aeSJan Lentfer return;
7216d49e1aeSJan Lentfer }
7226d49e1aeSJan Lentfer
7236d49e1aeSJan Lentfer /* Ignore anything else */
7246d49e1aeSJan Lentfer }
7256d49e1aeSJan Lentfer
7266d49e1aeSJan Lentfer
ssdp_listener_open(void)7273ff40c12SJohn Marino int ssdp_listener_open(void)
7286d49e1aeSJan Lentfer {
7296d49e1aeSJan Lentfer struct sockaddr_in addr;
7306d49e1aeSJan Lentfer struct ip_mreq mcast_addr;
7316d49e1aeSJan Lentfer int on = 1;
7326d49e1aeSJan Lentfer /* per UPnP spec, keep IP packet time to live (TTL) small */
7336d49e1aeSJan Lentfer unsigned char ttl = 4;
7343ff40c12SJohn Marino int sd;
7356d49e1aeSJan Lentfer
7363ff40c12SJohn Marino sd = socket(AF_INET, SOCK_DGRAM, 0);
737*a1157835SDaniel Fojt if (sd < 0 ||
738*a1157835SDaniel Fojt fcntl(sd, F_SETFL, O_NONBLOCK) != 0 ||
739*a1157835SDaniel Fojt setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
7406d49e1aeSJan Lentfer goto fail;
7416d49e1aeSJan Lentfer os_memset(&addr, 0, sizeof(addr));
7426d49e1aeSJan Lentfer addr.sin_family = AF_INET;
7436d49e1aeSJan Lentfer addr.sin_addr.s_addr = htonl(INADDR_ANY);
7446d49e1aeSJan Lentfer addr.sin_port = htons(UPNP_MULTICAST_PORT);
7456d49e1aeSJan Lentfer if (bind(sd, (struct sockaddr *) &addr, sizeof(addr)))
7466d49e1aeSJan Lentfer goto fail;
7476d49e1aeSJan Lentfer os_memset(&mcast_addr, 0, sizeof(mcast_addr));
7486d49e1aeSJan Lentfer mcast_addr.imr_interface.s_addr = htonl(INADDR_ANY);
7496d49e1aeSJan Lentfer mcast_addr.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
7506d49e1aeSJan Lentfer if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
751*a1157835SDaniel Fojt (char *) &mcast_addr, sizeof(mcast_addr)) ||
752*a1157835SDaniel Fojt setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
7536d49e1aeSJan Lentfer &ttl, sizeof(ttl)))
7546d49e1aeSJan Lentfer goto fail;
7553ff40c12SJohn Marino
7563ff40c12SJohn Marino return sd;
7573ff40c12SJohn Marino
7583ff40c12SJohn Marino fail:
7593ff40c12SJohn Marino if (sd >= 0)
7603ff40c12SJohn Marino close(sd);
7613ff40c12SJohn Marino return -1;
7623ff40c12SJohn Marino }
7633ff40c12SJohn Marino
7643ff40c12SJohn Marino
7653ff40c12SJohn Marino /**
7663ff40c12SJohn Marino * ssdp_listener_start - Set up for receiving discovery (UDP) packets
7673ff40c12SJohn Marino * @sm: WPS UPnP state machine from upnp_wps_device_init()
7683ff40c12SJohn Marino * Returns: 0 on success, -1 on failure
7693ff40c12SJohn Marino *
7703ff40c12SJohn Marino * The SSDP listener is stopped by calling ssdp_listener_stop().
7713ff40c12SJohn Marino */
ssdp_listener_start(struct upnp_wps_device_sm * sm)7723ff40c12SJohn Marino int ssdp_listener_start(struct upnp_wps_device_sm *sm)
7733ff40c12SJohn Marino {
7743ff40c12SJohn Marino sm->ssdp_sd = ssdp_listener_open();
7753ff40c12SJohn Marino
7763ff40c12SJohn Marino if (eloop_register_sock(sm->ssdp_sd, EVENT_TYPE_READ,
7773ff40c12SJohn Marino ssdp_listener_handler, NULL, sm))
7786d49e1aeSJan Lentfer goto fail;
7796d49e1aeSJan Lentfer sm->ssdp_sd_registered = 1;
7806d49e1aeSJan Lentfer return 0;
7816d49e1aeSJan Lentfer
7826d49e1aeSJan Lentfer fail:
7836d49e1aeSJan Lentfer /* Error */
7846d49e1aeSJan Lentfer wpa_printf(MSG_ERROR, "WPS UPnP: ssdp_listener_start failed");
7856d49e1aeSJan Lentfer ssdp_listener_stop(sm);
7866d49e1aeSJan Lentfer return -1;
7876d49e1aeSJan Lentfer }
7886d49e1aeSJan Lentfer
7896d49e1aeSJan Lentfer
7906d49e1aeSJan Lentfer /**
7916d49e1aeSJan Lentfer * add_ssdp_network - Add routing entry for SSDP
7926d49e1aeSJan Lentfer * @net_if: Selected network interface name
7936d49e1aeSJan Lentfer * Returns: 0 on success, -1 on failure
7946d49e1aeSJan Lentfer *
7956d49e1aeSJan Lentfer * This function assures that the multicast address will be properly
7966d49e1aeSJan Lentfer * handled by Linux networking code (by a modification to routing tables).
7976d49e1aeSJan Lentfer * This must be done per network interface. It really only needs to be done
7986d49e1aeSJan Lentfer * once after booting up, but it does not hurt to call this more frequently
7996d49e1aeSJan Lentfer * "to be safe".
8006d49e1aeSJan Lentfer */
add_ssdp_network(const char * net_if)8013ff40c12SJohn Marino int add_ssdp_network(const char *net_if)
8026d49e1aeSJan Lentfer {
8036d49e1aeSJan Lentfer #ifdef __linux__
8046d49e1aeSJan Lentfer int ret = -1;
8056d49e1aeSJan Lentfer int sock = -1;
8066d49e1aeSJan Lentfer struct rtentry rt;
8076d49e1aeSJan Lentfer struct sockaddr_in *sin;
8086d49e1aeSJan Lentfer
8096d49e1aeSJan Lentfer if (!net_if)
8106d49e1aeSJan Lentfer goto fail;
8116d49e1aeSJan Lentfer
8126d49e1aeSJan Lentfer os_memset(&rt, 0, sizeof(rt));
8136d49e1aeSJan Lentfer sock = socket(AF_INET, SOCK_DGRAM, 0);
8146d49e1aeSJan Lentfer if (sock < 0)
8156d49e1aeSJan Lentfer goto fail;
8166d49e1aeSJan Lentfer
8173ff40c12SJohn Marino rt.rt_dev = (char *) net_if;
8186d49e1aeSJan Lentfer sin = aliasing_hide_typecast(&rt.rt_dst, struct sockaddr_in);
8196d49e1aeSJan Lentfer sin->sin_family = AF_INET;
8206d49e1aeSJan Lentfer sin->sin_port = 0;
8216d49e1aeSJan Lentfer sin->sin_addr.s_addr = inet_addr(SSDP_TARGET);
8226d49e1aeSJan Lentfer sin = aliasing_hide_typecast(&rt.rt_genmask, struct sockaddr_in);
8236d49e1aeSJan Lentfer sin->sin_family = AF_INET;
8246d49e1aeSJan Lentfer sin->sin_port = 0;
8256d49e1aeSJan Lentfer sin->sin_addr.s_addr = inet_addr(SSDP_NETMASK);
8266d49e1aeSJan Lentfer rt.rt_flags = RTF_UP;
8276d49e1aeSJan Lentfer if (ioctl(sock, SIOCADDRT, &rt) < 0) {
8286d49e1aeSJan Lentfer if (errno == EPERM) {
8296d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "add_ssdp_network: No "
8306d49e1aeSJan Lentfer "permissions to add routing table entry");
8316d49e1aeSJan Lentfer /* Continue to allow testing as non-root */
8326d49e1aeSJan Lentfer } else if (errno != EEXIST) {
8336d49e1aeSJan Lentfer wpa_printf(MSG_INFO, "add_ssdp_network() ioctl errno "
8346d49e1aeSJan Lentfer "%d (%s)", errno, strerror(errno));
8356d49e1aeSJan Lentfer goto fail;
8366d49e1aeSJan Lentfer }
8376d49e1aeSJan Lentfer }
8386d49e1aeSJan Lentfer
8396d49e1aeSJan Lentfer ret = 0;
8406d49e1aeSJan Lentfer
8416d49e1aeSJan Lentfer fail:
8426d49e1aeSJan Lentfer if (sock >= 0)
8436d49e1aeSJan Lentfer close(sock);
8446d49e1aeSJan Lentfer
8456d49e1aeSJan Lentfer return ret;
8466d49e1aeSJan Lentfer #else /* __linux__ */
8476d49e1aeSJan Lentfer return 0;
8486d49e1aeSJan Lentfer #endif /* __linux__ */
8496d49e1aeSJan Lentfer }
8506d49e1aeSJan Lentfer
8516d49e1aeSJan Lentfer
ssdp_open_multicast_sock(u32 ip_addr,const char * forced_ifname)8523ff40c12SJohn Marino int ssdp_open_multicast_sock(u32 ip_addr, const char *forced_ifname)
8536d49e1aeSJan Lentfer {
8543ff40c12SJohn Marino int sd;
8556d49e1aeSJan Lentfer /* per UPnP-arch-DeviceArchitecture, 1. Discovery, keep IP packet
8566d49e1aeSJan Lentfer * time to live (TTL) small */
8576d49e1aeSJan Lentfer unsigned char ttl = 4;
8586d49e1aeSJan Lentfer
8593ff40c12SJohn Marino sd = socket(AF_INET, SOCK_DGRAM, 0);
8606d49e1aeSJan Lentfer if (sd < 0)
8616d49e1aeSJan Lentfer return -1;
8626d49e1aeSJan Lentfer
8633ff40c12SJohn Marino if (forced_ifname) {
8643ff40c12SJohn Marino #ifdef __linux__
8653ff40c12SJohn Marino struct ifreq req;
8663ff40c12SJohn Marino os_memset(&req, 0, sizeof(req));
8673ff40c12SJohn Marino os_strlcpy(req.ifr_name, forced_ifname, sizeof(req.ifr_name));
8683ff40c12SJohn Marino if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &req,
8693ff40c12SJohn Marino sizeof(req)) < 0) {
8703ff40c12SJohn Marino wpa_printf(MSG_INFO, "WPS UPnP: Failed to bind "
8713ff40c12SJohn Marino "multicast socket to ifname %s: %s",
8723ff40c12SJohn Marino forced_ifname, strerror(errno));
8733ff40c12SJohn Marino close(sd);
8746d49e1aeSJan Lentfer return -1;
8753ff40c12SJohn Marino }
8763ff40c12SJohn Marino #endif /* __linux__ */
8773ff40c12SJohn Marino }
8783ff40c12SJohn Marino
8793ff40c12SJohn Marino #if 0 /* maybe ok if we sometimes block on writes */
8803ff40c12SJohn Marino if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0) {
8813ff40c12SJohn Marino close(sd);
8823ff40c12SJohn Marino return -1;
8833ff40c12SJohn Marino }
8846d49e1aeSJan Lentfer #endif
8856d49e1aeSJan Lentfer
8866d49e1aeSJan Lentfer if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF,
8873ff40c12SJohn Marino &ip_addr, sizeof(ip_addr))) {
8883ff40c12SJohn Marino wpa_printf(MSG_DEBUG, "WPS: setsockopt(IP_MULTICAST_IF) %x: "
8893ff40c12SJohn Marino "%d (%s)", ip_addr, errno, strerror(errno));
8903ff40c12SJohn Marino close(sd);
8916d49e1aeSJan Lentfer return -1;
8923ff40c12SJohn Marino }
8936d49e1aeSJan Lentfer if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
8943ff40c12SJohn Marino &ttl, sizeof(ttl))) {
8953ff40c12SJohn Marino wpa_printf(MSG_DEBUG, "WPS: setsockopt(IP_MULTICAST_TTL): "
8963ff40c12SJohn Marino "%d (%s)", errno, strerror(errno));
8973ff40c12SJohn Marino close(sd);
8986d49e1aeSJan Lentfer return -1;
8993ff40c12SJohn Marino }
9006d49e1aeSJan Lentfer
9016d49e1aeSJan Lentfer #if 0 /* not needed, because we don't receive using multicast_sd */
9026d49e1aeSJan Lentfer {
9036d49e1aeSJan Lentfer struct ip_mreq mreq;
9046d49e1aeSJan Lentfer mreq.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
9053ff40c12SJohn Marino mreq.imr_interface.s_addr = ip_addr;
9066d49e1aeSJan Lentfer wpa_printf(MSG_DEBUG, "WPS UPnP: Multicast addr 0x%x if addr "
9076d49e1aeSJan Lentfer "0x%x",
9086d49e1aeSJan Lentfer mreq.imr_multiaddr.s_addr,
9096d49e1aeSJan Lentfer mreq.imr_interface.s_addr);
9106d49e1aeSJan Lentfer if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
9116d49e1aeSJan Lentfer sizeof(mreq))) {
9126d49e1aeSJan Lentfer wpa_printf(MSG_ERROR,
9136d49e1aeSJan Lentfer "WPS UPnP: setsockopt "
9146d49e1aeSJan Lentfer "IP_ADD_MEMBERSHIP errno %d (%s)",
9156d49e1aeSJan Lentfer errno, strerror(errno));
9163ff40c12SJohn Marino close(sd);
9176d49e1aeSJan Lentfer return -1;
9186d49e1aeSJan Lentfer }
9196d49e1aeSJan Lentfer }
9206d49e1aeSJan Lentfer #endif /* not needed */
9216d49e1aeSJan Lentfer
9226d49e1aeSJan Lentfer /*
9236d49e1aeSJan Lentfer * TODO: What about IP_MULTICAST_LOOP? It seems to be on by default?
9246d49e1aeSJan Lentfer * which aids debugging I suppose but isn't really necessary?
9256d49e1aeSJan Lentfer */
9266d49e1aeSJan Lentfer
9273ff40c12SJohn Marino return sd;
9283ff40c12SJohn Marino }
9293ff40c12SJohn Marino
9303ff40c12SJohn Marino
9313ff40c12SJohn Marino /**
9323ff40c12SJohn Marino * ssdp_open_multicast - Open socket for sending multicast SSDP messages
9333ff40c12SJohn Marino * @sm: WPS UPnP state machine from upnp_wps_device_init()
9343ff40c12SJohn Marino * Returns: 0 on success, -1 on failure
9353ff40c12SJohn Marino */
ssdp_open_multicast(struct upnp_wps_device_sm * sm)9363ff40c12SJohn Marino int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
9373ff40c12SJohn Marino {
9383ff40c12SJohn Marino sm->multicast_sd = ssdp_open_multicast_sock(sm->ip_addr, NULL);
9393ff40c12SJohn Marino if (sm->multicast_sd < 0)
9403ff40c12SJohn Marino return -1;
9416d49e1aeSJan Lentfer return 0;
9426d49e1aeSJan Lentfer }
943