source: rtems-libbsd/freebsd/contrib/wpa/src/wps/wps_upnp_ssdp.c @ 9c9d11b

55-freebsd-126-freebsd-12
Last change on this file since 9c9d11b was 9c9d11b, checked in by Sichen Zhao <1473996754@…>, on 08/01/17 at 12:43:41

Import wpa from FreeBSD

  • Property mode set to 100644
File size: 25.6 KB
Line 
1#include <machine/rtems-bsd-user-space.h>
2
3/*
4 * UPnP SSDP for WPS
5 * Copyright (c) 2000-2003 Intel Corporation
6 * Copyright (c) 2006-2007 Sony Corporation
7 * Copyright (c) 2008-2009 Atheros Communications
8 * Copyright (c) 2009-2013, Jouni Malinen <j@w1.fi>
9 *
10 * See wps_upnp.c for more details on licensing and code history.
11 */
12
13#include "includes.h"
14
15#include <fcntl.h>
16#include <sys/ioctl.h>
17#include <net/route.h>
18#ifdef __linux__
19#include <net/if.h>
20#endif /* __linux__ */
21
22#include "common.h"
23#include "uuid.h"
24#include "eloop.h"
25#include "wps.h"
26#include "wps_upnp.h"
27#include "wps_upnp_i.h"
28
29#define UPNP_CACHE_SEC (UPNP_CACHE_SEC_MIN + 1) /* cache time we use */
30#define UPNP_CACHE_SEC_MIN 1800 /* min cachable time per UPnP standard */
31#define UPNP_ADVERTISE_REPEAT 2 /* no more than 3 */
32#define MAX_MSEARCH 20          /* max simultaneous M-SEARCH replies ongoing */
33#define SSDP_TARGET  "239.0.0.0"
34#define SSDP_NETMASK "255.0.0.0"
35
36
37/* Check tokens for equality, where tokens consist of letters, digits,
38 * underscore and hyphen, and are matched case insensitive.
39 */
40static int token_eq(const char *s1, const char *s2)
41{
42        int c1;
43        int c2;
44        int end1 = 0;
45        int end2 = 0;
46        for (;;) {
47                c1 = *s1++;
48                c2 = *s2++;
49                if (isalpha(c1) && isupper(c1))
50                        c1 = tolower(c1);
51                if (isalpha(c2) && isupper(c2))
52                        c2 = tolower(c2);
53                end1 = !(isalnum(c1) || c1 == '_' || c1 == '-');
54                end2 = !(isalnum(c2) || c2 == '_' || c2 == '-');
55                if (end1 || end2 || c1 != c2)
56                        break;
57        }
58        return end1 && end2; /* reached end of both words? */
59}
60
61
62/* Return length of token (see above for definition of token) */
63static int token_length(const char *s)
64{
65        const char *begin = s;
66        for (;; s++) {
67                int c = *s;
68                int end = !(isalnum(c) || c == '_' || c == '-');
69                if (end)
70                        break;
71        }
72        return s - begin;
73}
74
75
76/* return length of interword separation.
77 * This accepts only spaces/tabs and thus will not traverse a line
78 * or buffer ending.
79 */
80static int word_separation_length(const char *s)
81{
82        const char *begin = s;
83        for (;; s++) {
84                int c = *s;
85                if (c == ' ' || c == '\t')
86                        continue;
87                break;
88        }
89        return s - begin;
90}
91
92
93/* No. of chars through (including) end of line */
94static int line_length(const char *l)
95{
96        const char *lp = l;
97        while (*lp && *lp != '\n')
98                lp++;
99        if (*lp == '\n')
100                lp++;
101        return lp - l;
102}
103
104
105static int str_starts(const char *str, const char *start)
106{
107        return os_strncmp(str, start, os_strlen(start)) == 0;
108}
109
110
111/***************************************************************************
112 * Advertisements.
113 * These are multicast to the world to tell them we are here.
114 * The individual packets are spread out in time to limit loss,
115 * and then after a much longer period of time the whole sequence
116 * is repeated again (for NOTIFYs only).
117 **************************************************************************/
118
119/**
120 * next_advertisement - Build next message and advance the state machine
121 * @a: Advertisement state
122 * @islast: Buffer for indicating whether this is the last message (= 1)
123 * Returns: The new message (caller is responsible for freeing this)
124 *
125 * Note: next_advertisement is shared code with msearchreply_* functions
126 */
127static struct wpabuf *
128next_advertisement(struct upnp_wps_device_sm *sm,
129                   struct advertisement_state_machine *a, int *islast)
130{
131        struct wpabuf *msg;
132        char *NTString = "";
133        char uuid_string[80];
134        struct upnp_wps_device_interface *iface;
135
136        *islast = 0;
137        iface = dl_list_first(&sm->interfaces,
138                              struct upnp_wps_device_interface, list);
139        if (!iface)
140                return NULL;
141        uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string));
142        msg = wpabuf_alloc(800); /* more than big enough */
143        if (msg == NULL)
144                return NULL;
145        switch (a->type) {
146        case ADVERTISE_UP:
147        case ADVERTISE_DOWN:
148                NTString = "NT";
149                wpabuf_put_str(msg, "NOTIFY * HTTP/1.1\r\n");
150                wpabuf_printf(msg, "HOST: %s:%d\r\n",
151                              UPNP_MULTICAST_ADDRESS, UPNP_MULTICAST_PORT);
152                wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
153                              UPNP_CACHE_SEC);
154                wpabuf_printf(msg, "NTS: %s\r\n",
155                              (a->type == ADVERTISE_UP ?
156                               "ssdp:alive" : "ssdp:byebye"));
157                break;
158        case MSEARCH_REPLY:
159                NTString = "ST";
160                wpabuf_put_str(msg, "HTTP/1.1 200 OK\r\n");
161                wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
162                              UPNP_CACHE_SEC);
163
164                wpabuf_put_str(msg, "DATE: ");
165                format_date(msg);
166                wpabuf_put_str(msg, "\r\n");
167
168                wpabuf_put_str(msg, "EXT:\r\n");
169                break;
170        }
171
172        if (a->type != ADVERTISE_DOWN) {
173                /* Where others may get our XML files from */
174                wpabuf_printf(msg, "LOCATION: http://%s:%d/%s\r\n",
175                              sm->ip_addr_text, sm->web_port,
176                              UPNP_WPS_DEVICE_XML_FILE);
177        }
178
179        /* The SERVER line has three comma-separated fields:
180         *      operating system / version
181         *      upnp version
182         *      software package / version
183         * However, only the UPnP version is really required, the
184         * others can be place holders... for security reasons
185         * it is better to NOT provide extra information.
186         */
187        wpabuf_put_str(msg, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n");
188
189        switch (a->state / UPNP_ADVERTISE_REPEAT) {
190        case 0:
191                wpabuf_printf(msg, "%s: upnp:rootdevice\r\n", NTString);
192                wpabuf_printf(msg, "USN: uuid:%s::upnp:rootdevice\r\n",
193                              uuid_string);
194                break;
195        case 1:
196                wpabuf_printf(msg, "%s: uuid:%s\r\n", NTString, uuid_string);
197                wpabuf_printf(msg, "USN: uuid:%s\r\n", uuid_string);
198                break;
199        case 2:
200                wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:device:"
201                              "WFADevice:1\r\n", NTString);
202                wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
203                              "org:device:WFADevice:1\r\n", uuid_string);
204                break;
205        case 3:
206                wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:service:"
207                              "WFAWLANConfig:1\r\n", NTString);
208                wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
209                              "org:service:WFAWLANConfig:1\r\n", uuid_string);
210                break;
211        }
212        wpabuf_put_str(msg, "\r\n");
213
214        if (a->state + 1 >= 4 * UPNP_ADVERTISE_REPEAT)
215                *islast = 1;
216
217        return msg;
218}
219
220
221static void advertisement_state_machine_handler(void *eloop_data,
222                                                void *user_ctx);
223
224
225/**
226 * advertisement_state_machine_stop - Stop SSDP advertisements
227 * @sm: WPS UPnP state machine from upnp_wps_device_init()
228 * @send_byebye: Send byebye advertisement messages immediately
229 */
230void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm,
231                                      int send_byebye)
232{
233        struct advertisement_state_machine *a = &sm->advertisement;
234        int islast = 0;
235        struct wpabuf *msg;
236        struct sockaddr_in dest;
237
238        eloop_cancel_timeout(advertisement_state_machine_handler, NULL, sm);
239        if (!send_byebye || sm->multicast_sd < 0)
240                return;
241
242        a->type = ADVERTISE_DOWN;
243        a->state = 0;
244
245        os_memset(&dest, 0, sizeof(dest));
246        dest.sin_family = AF_INET;
247        dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
248        dest.sin_port = htons(UPNP_MULTICAST_PORT);
249
250        while (!islast) {
251                msg = next_advertisement(sm, a, &islast);
252                if (msg == NULL)
253                        break;
254                if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg),
255                           0, (struct sockaddr *) &dest, sizeof(dest)) < 0) {
256                        wpa_printf(MSG_INFO, "WPS UPnP: Advertisement sendto "
257                                   "failed: %d (%s)", errno, strerror(errno));
258                }
259                wpabuf_free(msg);
260                a->state++;
261        }
262}
263
264
265static void advertisement_state_machine_handler(void *eloop_data,
266                                                void *user_ctx)
267{
268        struct upnp_wps_device_sm *sm = user_ctx;
269        struct advertisement_state_machine *a = &sm->advertisement;
270        struct wpabuf *msg;
271        int next_timeout_msec = 100;
272        int next_timeout_sec = 0;
273        struct sockaddr_in dest;
274        int islast = 0;
275
276        /*
277         * Each is sent twice (in case lost) w/ 100 msec delay between;
278         * spec says no more than 3 times.
279         * One pair for rootdevice, one pair for uuid, and a pair each for
280         * each of the two urns.
281         * The entire sequence must be repeated before cache control timeout
282         * (which  is min  1800 seconds),
283         * recommend random portion of half of the advertised cache control age
284         * to ensure against loss... perhaps 1800/4 + rand*1800/4 ?
285         * Delay random interval < 100 msec prior to initial sending.
286         * TTL of 4
287         */
288
289        wpa_printf(MSG_MSGDUMP, "WPS UPnP: Advertisement state=%d", a->state);
290        msg = next_advertisement(sm, a, &islast);
291        if (msg == NULL)
292                return;
293
294        os_memset(&dest, 0, sizeof(dest));
295        dest.sin_family = AF_INET;
296        dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
297        dest.sin_port = htons(UPNP_MULTICAST_PORT);
298
299        if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
300                   (struct sockaddr *) &dest, sizeof(dest)) == -1) {
301                wpa_printf(MSG_ERROR, "WPS UPnP: Advertisement sendto failed:"
302                           "%d (%s)", errno, strerror(errno));
303                next_timeout_msec = 0;
304                next_timeout_sec = 10; /* ... later */
305        } else if (islast) {
306                a->state = 0; /* wrap around */
307                if (a->type == ADVERTISE_DOWN) {
308                        wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_DOWN->UP");
309                        a->type = ADVERTISE_UP;
310                        /* do it all over again right away */
311                } else {
312                        u16 r;
313                        /*
314                         * Start over again after a long timeout
315                         * (see notes above)
316                         */
317                        next_timeout_msec = 0;
318                        if (os_get_random((void *) &r, sizeof(r)) < 0)
319                                r = 32768;
320                        next_timeout_sec = UPNP_CACHE_SEC / 4 +
321                                (((UPNP_CACHE_SEC / 4) * r) >> 16);
322                        sm->advertise_count++;
323                        wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_UP (#%u); "
324                                   "next in %d sec",
325                                   sm->advertise_count, next_timeout_sec);
326                }
327        } else {
328                a->state++;
329        }
330
331        wpabuf_free(msg);
332
333        eloop_register_timeout(next_timeout_sec, next_timeout_msec,
334                               advertisement_state_machine_handler, NULL, sm);
335}
336
337
338/**
339 * advertisement_state_machine_start - Start SSDP advertisements
340 * @sm: WPS UPnP state machine from upnp_wps_device_init()
341 * Returns: 0 on success, -1 on failure
342 */
343int advertisement_state_machine_start(struct upnp_wps_device_sm *sm)
344{
345        struct advertisement_state_machine *a = &sm->advertisement;
346        int next_timeout_msec;
347
348        advertisement_state_machine_stop(sm, 0);
349
350        /*
351         * Start out advertising down, this automatically switches
352         * to advertising up which signals our restart.
353         */
354        a->type = ADVERTISE_DOWN;
355        a->state = 0;
356        /* (other fields not used here) */
357
358        /* First timeout should be random interval < 100 msec */
359        next_timeout_msec = (100 * (os_random() & 0xFF)) >> 8;
360        return eloop_register_timeout(0, next_timeout_msec,
361                                      advertisement_state_machine_handler,
362                                      NULL, sm);
363}
364
365
366/***************************************************************************
367 * M-SEARCH replies
368 * These are very similar to the multicast advertisements, with some
369 * small changes in data content; and they are sent (UDP) to a specific
370 * unicast address instead of multicast.
371 * They are sent in response to a UDP M-SEARCH packet.
372 **************************************************************************/
373
374/**
375 * msearchreply_state_machine_stop - Stop M-SEARCH reply state machine
376 * @a: Selected advertisement/reply state
377 */
378void msearchreply_state_machine_stop(struct advertisement_state_machine *a)
379{
380        wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH stop");
381        dl_list_del(&a->list);
382        os_free(a);
383}
384
385
386static void msearchreply_state_machine_handler(void *eloop_data,
387                                               void *user_ctx)
388{
389        struct advertisement_state_machine *a = user_ctx;
390        struct upnp_wps_device_sm *sm = eloop_data;
391        struct wpabuf *msg;
392        int next_timeout_msec = 100;
393        int next_timeout_sec = 0;
394        int islast = 0;
395
396        /*
397         * Each response is sent twice (in case lost) w/ 100 msec delay
398         * between; spec says no more than 3 times.
399         * One pair for rootdevice, one pair for uuid, and a pair each for
400         * each of the two urns.
401         */
402
403        /* TODO: should only send the requested response types */
404
405        wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply state=%d (%s:%d)",
406                   a->state, inet_ntoa(a->client.sin_addr),
407                   ntohs(a->client.sin_port));
408        msg = next_advertisement(sm, a, &islast);
409        if (msg == NULL)
410                return;
411
412        /*
413         * Send it on the multicast socket to avoid having to set up another
414         * socket.
415         */
416        if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
417                   (struct sockaddr *) &a->client, sizeof(a->client)) < 0) {
418                wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply sendto "
419                           "errno %d (%s) for %s:%d",
420                           errno, strerror(errno),
421                           inet_ntoa(a->client.sin_addr),
422                           ntohs(a->client.sin_port));
423                /* Ignore error and hope for the best */
424        }
425        wpabuf_free(msg);
426        if (islast) {
427                wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply done");
428                msearchreply_state_machine_stop(a);
429                return;
430        }
431        a->state++;
432
433        wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply in %d.%03d sec",
434                   next_timeout_sec, next_timeout_msec);
435        eloop_register_timeout(next_timeout_sec, next_timeout_msec,
436                               msearchreply_state_machine_handler, sm, a);
437}
438
439
440/**
441 * msearchreply_state_machine_start - Reply to M-SEARCH discovery request
442 * @sm: WPS UPnP state machine from upnp_wps_device_init()
443 * @client: Client address
444 * @mx: Maximum delay in seconds
445 *
446 * Use TTL of 4 (this was done when socket set up).
447 * A response should be given in randomized portion of min(MX,120) seconds
448 *
449 * UPnP-arch-DeviceArchitecture, 1.2.3:
450 * To be found, a device must send a UDP response to the source IP address and
451 * port that sent the request to the multicast channel. Devices respond if the
452 * ST header of the M-SEARCH request is "ssdp:all", "upnp:rootdevice", "uuid:"
453 * followed by a UUID that exactly matches one advertised by the device.
454 */
455static void msearchreply_state_machine_start(struct upnp_wps_device_sm *sm,
456                                             struct sockaddr_in *client,
457                                             int mx)
458{
459        struct advertisement_state_machine *a;
460        int next_timeout_sec;
461        int next_timeout_msec;
462        int replies;
463
464        replies = dl_list_len(&sm->msearch_replies);
465        wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply start (%d "
466                   "outstanding)", replies);
467        if (replies >= MAX_MSEARCH) {
468                wpa_printf(MSG_INFO, "WPS UPnP: Too many outstanding "
469                           "M-SEARCH replies");
470                return;
471        }
472
473        a = os_zalloc(sizeof(*a));
474        if (a == NULL)
475                return;
476        a->type = MSEARCH_REPLY;
477        a->state = 0;
478        os_memcpy(&a->client, client, sizeof(*client));
479        /* Wait time depending on MX value */
480        next_timeout_msec = (1000 * mx * (os_random() & 0xFF)) >> 8;
481        next_timeout_sec = next_timeout_msec / 1000;
482        next_timeout_msec = next_timeout_msec % 1000;
483        if (eloop_register_timeout(next_timeout_sec, next_timeout_msec,
484                                   msearchreply_state_machine_handler, sm,
485                                   a)) {
486                /* No way to recover (from malloc failure) */
487                goto fail;
488        }
489        /* Remember for future cleanup */
490        dl_list_add(&sm->msearch_replies, &a->list);
491        return;
492
493fail:
494        wpa_printf(MSG_INFO, "WPS UPnP: M-SEARCH reply failure!");
495        eloop_cancel_timeout(msearchreply_state_machine_handler, sm, a);
496        os_free(a);
497}
498
499
500/**
501 * ssdp_parse_msearch - Process a received M-SEARCH
502 * @sm: WPS UPnP state machine from upnp_wps_device_init()
503 * @client: Client address
504 * @data: NULL terminated M-SEARCH message
505 *
506 * Given that we have received a header w/ M-SEARCH, act upon it
507 *
508 * Format of M-SEARCH (case insensitive!):
509 *
510 * First line must be:
511 *      M-SEARCH * HTTP/1.1
512 * Other lines in arbitrary order:
513 *      HOST:239.255.255.250:1900
514 *      ST:<varies -- must match>
515 *      MAN:"ssdp:discover"
516 *      MX:<varies>
517 *
518 * It should be noted that when Microsoft Vista is still learning its IP
519 * address, it sends out host lines like: HOST:[FF02::C]:1900
520 */
521static void ssdp_parse_msearch(struct upnp_wps_device_sm *sm,
522                               struct sockaddr_in *client, const char *data)
523{
524#ifndef CONFIG_NO_STDOUT_DEBUG
525        const char *start = data;
526#endif /* CONFIG_NO_STDOUT_DEBUG */
527        int got_host = 0;
528        int got_st = 0, st_match = 0;
529        int got_man = 0;
530        int got_mx = 0;
531        int mx = 0;
532
533        /*
534         * Skip first line M-SEARCH * HTTP/1.1
535         * (perhaps we should check remainder of the line for syntax)
536         */
537        data += line_length(data);
538
539        /* Parse remaining lines */
540        for (; *data != '\0'; data += line_length(data)) {
541                if (token_eq(data, "host")) {
542                        /* The host line indicates who the packet
543                         * is addressed to... but do we really care?
544                         * Note that Microsoft sometimes does funny
545                         * stuff with the HOST: line.
546                         */
547#if 0   /* could be */
548                        data += token_length(data);
549                        data += word_separation_length(data);
550                        if (*data != ':')
551                                goto bad;
552                        data++;
553                        data += word_separation_length(data);
554                        /* UPNP_MULTICAST_ADDRESS */
555                        if (!str_starts(data, "239.255.255.250"))
556                                goto bad;
557                        data += os_strlen("239.255.255.250");
558                        if (*data == ':') {
559                                if (!str_starts(data, ":1900"))
560                                        goto bad;
561                        }
562#endif  /* could be */
563                        got_host = 1;
564                        continue;
565                } else if (token_eq(data, "st")) {
566                        /* There are a number of forms; we look
567                         * for one that matches our case.
568                         */
569                        got_st = 1;
570                        data += token_length(data);
571                        data += word_separation_length(data);
572                        if (*data != ':')
573                                continue;
574                        data++;
575                        data += word_separation_length(data);
576                        if (str_starts(data, "ssdp:all")) {
577                                st_match = 1;
578                                continue;
579                        }
580                        if (str_starts(data, "upnp:rootdevice")) {
581                                st_match = 1;
582                                continue;
583                        }
584                        if (str_starts(data, "uuid:")) {
585                                char uuid_string[80];
586                                struct upnp_wps_device_interface *iface;
587                                iface = dl_list_first(
588                                        &sm->interfaces,
589                                        struct upnp_wps_device_interface,
590                                        list);
591                                if (!iface)
592                                        continue;
593                                data += os_strlen("uuid:");
594                                uuid_bin2str(iface->wps->uuid, uuid_string,
595                                             sizeof(uuid_string));
596                                if (str_starts(data, uuid_string))
597                                        st_match = 1;
598                                continue;
599                        }
600#if 0
601                        /* FIX: should we really reply to IGD string? */
602                        if (str_starts(data, "urn:schemas-upnp-org:device:"
603                                       "InternetGatewayDevice:1")) {
604                                st_match = 1;
605                                continue;
606                        }
607#endif
608                        if (str_starts(data, "urn:schemas-wifialliance-org:"
609                                       "service:WFAWLANConfig:1")) {
610                                st_match = 1;
611                                continue;
612                        }
613                        if (str_starts(data, "urn:schemas-wifialliance-org:"
614                                       "device:WFADevice:1")) {
615                                st_match = 1;
616                                continue;
617                        }
618                        continue;
619                } else if (token_eq(data, "man")) {
620                        data += token_length(data);
621                        data += word_separation_length(data);
622                        if (*data != ':')
623                                continue;
624                        data++;
625                        data += word_separation_length(data);
626                        if (!str_starts(data, "\"ssdp:discover\"")) {
627                                wpa_printf(MSG_DEBUG, "WPS UPnP: Unexpected "
628                                           "M-SEARCH man-field");
629                                goto bad;
630                        }
631                        got_man = 1;
632                        continue;
633                } else if (token_eq(data, "mx")) {
634                        data += token_length(data);
635                        data += word_separation_length(data);
636                        if (*data != ':')
637                                continue;
638                        data++;
639                        data += word_separation_length(data);
640                        mx = atol(data);
641                        got_mx = 1;
642                        continue;
643                }
644                /* ignore anything else */
645        }
646        if (!got_host || !got_st || !got_man || !got_mx || mx < 0) {
647                wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid M-SEARCH: %d %d %d "
648                           "%d mx=%d", got_host, got_st, got_man, got_mx, mx);
649                goto bad;
650        }
651        if (!st_match) {
652                wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored M-SEARCH (no ST "
653                           "match)");
654                return;
655        }
656        if (mx > 120)
657                mx = 120; /* UPnP-arch-DeviceArchitecture, 1.2.3 */
658        msearchreply_state_machine_start(sm, client, mx);
659        return;
660
661bad:
662        wpa_printf(MSG_INFO, "WPS UPnP: Failed to parse M-SEARCH");
663        wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH data:\n%s", start);
664}
665
666
667/* Listening for (UDP) discovery (M-SEARCH) packets */
668
669/**
670 * ssdp_listener_stop - Stop SSDP listered
671 * @sm: WPS UPnP state machine from upnp_wps_device_init()
672 *
673 * This function stops the SSDP listener that was started by calling
674 * ssdp_listener_start().
675 */
676void ssdp_listener_stop(struct upnp_wps_device_sm *sm)
677{
678        if (sm->ssdp_sd_registered) {
679                eloop_unregister_sock(sm->ssdp_sd, EVENT_TYPE_READ);
680                sm->ssdp_sd_registered = 0;
681        }
682
683        if (sm->ssdp_sd != -1) {
684                close(sm->ssdp_sd);
685                sm->ssdp_sd = -1;
686        }
687
688        eloop_cancel_timeout(msearchreply_state_machine_handler, sm,
689                             ELOOP_ALL_CTX);
690}
691
692
693static void ssdp_listener_handler(int sd, void *eloop_ctx, void *sock_ctx)
694{
695        struct upnp_wps_device_sm *sm = sock_ctx;
696        struct sockaddr_in addr; /* client address */
697        socklen_t addr_len;
698        int nread;
699        char buf[MULTICAST_MAX_READ], *pos;
700
701        addr_len = sizeof(addr);
702        nread = recvfrom(sm->ssdp_sd, buf, sizeof(buf) - 1, 0,
703                         (struct sockaddr *) &addr, &addr_len);
704        if (nread <= 0)
705                return;
706        buf[nread] = '\0'; /* need null termination for algorithm */
707
708        if (str_starts(buf, "NOTIFY ")) {
709                /*
710                 * Silently ignore NOTIFYs to avoid filling debug log with
711                 * unwanted messages.
712                 */
713                return;
714        }
715
716        pos = os_strchr(buf, '\n');
717        if (pos)
718                *pos = '\0';
719        wpa_printf(MSG_MSGDUMP, "WPS UPnP: Received SSDP packet from %s:%d: "
720                   "%s", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), buf);
721        if (pos)
722                *pos = '\n';
723
724        /* Parse first line */
725        if (os_strncasecmp(buf, "M-SEARCH", os_strlen("M-SEARCH")) == 0 &&
726            !isgraph(buf[strlen("M-SEARCH")])) {
727                ssdp_parse_msearch(sm, &addr, buf);
728                return;
729        }
730
731        /* Ignore anything else */
732}
733
734
735int ssdp_listener_open(void)
736{
737        struct sockaddr_in addr;
738        struct ip_mreq mcast_addr;
739        int on = 1;
740        /* per UPnP spec, keep IP packet time to live (TTL) small */
741        unsigned char ttl = 4;
742        int sd;
743
744        sd = socket(AF_INET, SOCK_DGRAM, 0);
745        if (sd < 0 ||
746            fcntl(sd, F_SETFL, O_NONBLOCK) != 0 ||
747            setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
748                goto fail;
749        os_memset(&addr, 0, sizeof(addr));
750        addr.sin_family = AF_INET;
751        addr.sin_addr.s_addr = htonl(INADDR_ANY);
752        addr.sin_port = htons(UPNP_MULTICAST_PORT);
753        if (bind(sd, (struct sockaddr *) &addr, sizeof(addr)))
754                goto fail;
755        os_memset(&mcast_addr, 0, sizeof(mcast_addr));
756        mcast_addr.imr_interface.s_addr = htonl(INADDR_ANY);
757        mcast_addr.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
758        if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
759                       (char *) &mcast_addr, sizeof(mcast_addr)) ||
760            setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
761                       &ttl, sizeof(ttl)))
762                goto fail;
763
764        return sd;
765
766fail:
767        if (sd >= 0)
768                close(sd);
769        return -1;
770}
771
772
773/**
774 * ssdp_listener_start - Set up for receiving discovery (UDP) packets
775 * @sm: WPS UPnP state machine from upnp_wps_device_init()
776 * Returns: 0 on success, -1 on failure
777 *
778 * The SSDP listener is stopped by calling ssdp_listener_stop().
779 */
780int ssdp_listener_start(struct upnp_wps_device_sm *sm)
781{
782        sm->ssdp_sd = ssdp_listener_open();
783
784        if (eloop_register_sock(sm->ssdp_sd, EVENT_TYPE_READ,
785                                ssdp_listener_handler, NULL, sm))
786                goto fail;
787        sm->ssdp_sd_registered = 1;
788        return 0;
789
790fail:
791        /* Error */
792        wpa_printf(MSG_ERROR, "WPS UPnP: ssdp_listener_start failed");
793        ssdp_listener_stop(sm);
794        return -1;
795}
796
797
798/**
799 * add_ssdp_network - Add routing entry for SSDP
800 * @net_if: Selected network interface name
801 * Returns: 0 on success, -1 on failure
802 *
803 * This function assures that the multicast address will be properly
804 * handled by Linux networking code (by a modification to routing tables).
805 * This must be done per network interface. It really only needs to be done
806 * once after booting up, but it does not hurt to call this more frequently
807 * "to be safe".
808 */
809int add_ssdp_network(const char *net_if)
810{
811#ifdef __linux__
812        int ret = -1;
813        int sock = -1;
814        struct rtentry rt;
815        struct sockaddr_in *sin;
816
817        if (!net_if)
818                goto fail;
819
820        os_memset(&rt, 0, sizeof(rt));
821        sock = socket(AF_INET, SOCK_DGRAM, 0);
822        if (sock < 0)
823                goto fail;
824
825        rt.rt_dev = (char *) net_if;
826        sin = aliasing_hide_typecast(&rt.rt_dst, struct sockaddr_in);
827        sin->sin_family = AF_INET;
828        sin->sin_port = 0;
829        sin->sin_addr.s_addr = inet_addr(SSDP_TARGET);
830        sin = aliasing_hide_typecast(&rt.rt_genmask, struct sockaddr_in);
831        sin->sin_family = AF_INET;
832        sin->sin_port = 0;
833        sin->sin_addr.s_addr = inet_addr(SSDP_NETMASK);
834        rt.rt_flags = RTF_UP;
835        if (ioctl(sock, SIOCADDRT, &rt) < 0) {
836                if (errno == EPERM) {
837                        wpa_printf(MSG_DEBUG, "add_ssdp_network: No "
838                                   "permissions to add routing table entry");
839                        /* Continue to allow testing as non-root */
840                } else if (errno != EEXIST) {
841                        wpa_printf(MSG_INFO, "add_ssdp_network() ioctl errno "
842                                   "%d (%s)", errno, strerror(errno));
843                        goto fail;
844                }
845        }
846
847        ret = 0;
848
849fail:
850        if (sock >= 0)
851                close(sock);
852
853        return ret;
854#else /* __linux__ */
855        return 0;
856#endif /* __linux__ */
857}
858
859
860int ssdp_open_multicast_sock(u32 ip_addr, const char *forced_ifname)
861{
862        int sd;
863         /* per UPnP-arch-DeviceArchitecture, 1. Discovery, keep IP packet
864          * time to live (TTL) small */
865        unsigned char ttl = 4;
866
867        sd = socket(AF_INET, SOCK_DGRAM, 0);
868        if (sd < 0)
869                return -1;
870
871        if (forced_ifname) {
872#ifdef __linux__
873                struct ifreq req;
874                os_memset(&req, 0, sizeof(req));
875                os_strlcpy(req.ifr_name, forced_ifname, sizeof(req.ifr_name));
876                if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &req,
877                               sizeof(req)) < 0) {
878                        wpa_printf(MSG_INFO, "WPS UPnP: Failed to bind "
879                                   "multicast socket to ifname %s: %s",
880                                   forced_ifname, strerror(errno));
881                        close(sd);
882                        return -1;
883                }
884#endif /* __linux__ */
885        }
886
887#if 0   /* maybe ok if we sometimes block on writes */
888        if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0) {
889                close(sd);
890                return -1;
891        }
892#endif
893
894        if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF,
895                       &ip_addr, sizeof(ip_addr))) {
896                wpa_printf(MSG_DEBUG, "WPS: setsockopt(IP_MULTICAST_IF) %x: "
897                           "%d (%s)", ip_addr, errno, strerror(errno));
898                close(sd);
899                return -1;
900        }
901        if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
902                       &ttl, sizeof(ttl))) {
903                wpa_printf(MSG_DEBUG, "WPS: setsockopt(IP_MULTICAST_TTL): "
904                           "%d (%s)", errno, strerror(errno));
905                close(sd);
906                return -1;
907        }
908
909#if 0   /* not needed, because we don't receive using multicast_sd */
910        {
911                struct ip_mreq mreq;
912                mreq.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
913                mreq.imr_interface.s_addr = ip_addr;
914                wpa_printf(MSG_DEBUG, "WPS UPnP: Multicast addr 0x%x if addr "
915                           "0x%x",
916                           mreq.imr_multiaddr.s_addr,
917                           mreq.imr_interface.s_addr);
918                if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
919                                sizeof(mreq))) {
920                        wpa_printf(MSG_ERROR,
921                                   "WPS UPnP: setsockopt "
922                                   "IP_ADD_MEMBERSHIP errno %d (%s)",
923                                   errno, strerror(errno));
924                        close(sd);
925                        return -1;
926                }
927        }
928#endif  /* not needed */
929
930        /*
931         * TODO: What about IP_MULTICAST_LOOP? It seems to be on by default?
932         * which aids debugging I suppose but isn't really necessary?
933         */
934
935        return sd;
936}
937
938
939/**
940 * ssdp_open_multicast - Open socket for sending multicast SSDP messages
941 * @sm: WPS UPnP state machine from upnp_wps_device_init()
942 * Returns: 0 on success, -1 on failure
943 */
944int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
945{
946        sm->multicast_sd = ssdp_open_multicast_sock(sm->ip_addr, NULL);
947        if (sm->multicast_sd < 0)
948                return -1;
949        return 0;
950}
Note: See TracBrowser for help on using the repository browser.