X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=src%2Fwps%2Fwps_upnp_event.c;h=8c3ded8d73e92504dadce72ddb619320eccb591b;hb=3904567d0bd5b5ae946ae0e954da257f960e77f8;hp=12e16f44bb2156b76e5a4848ee0b339eeadb854d;hpb=e80e5163f8b53f20f816f0d06d618f54ce3d79aa;p=libeap.git diff --git a/src/wps/wps_upnp_event.c b/src/wps/wps_upnp_event.c index 12e16f4..8c3ded8 100644 --- a/src/wps/wps_upnp_event.c +++ b/src/wps/wps_upnp_event.c @@ -3,19 +3,19 @@ * Copyright (c) 2000-2003 Intel Corporation * Copyright (c) 2006-2007 Sony Corporation * Copyright (c) 2008-2009 Atheros Communications - * Copyright (c) 2009, Jouni Malinen + * Copyright (c) 2009-2010, Jouni Malinen * * See wps_upnp.c for more details on licensing and code history. */ #include "includes.h" #include -#include #include "common.h" #include "eloop.h" #include "uuid.h" -#include "httpread.h" +#include "http_client.h" +#include "wps_defs.h" #include "wps_upnp.h" #include "wps_upnp_i.h" @@ -31,7 +31,7 @@ */ #define MAX_EVENTS_QUEUED 20 /* How far behind queued events */ -#define EVENT_TIMEOUT_SEC 30 /* Drop sending event after timeout */ +#define MAX_FAILURES 10 /* Drop subscription after this many failures */ /* How long to wait before sending event */ #define EVENT_DELAY_SECONDS 0 @@ -46,50 +46,34 @@ * TODO: As an optimization we could share data between subscribers. */ struct wps_event_ { - struct wps_event_ *next; - struct wps_event_ *prev; /* double linked list */ + struct dl_list list; struct subscription *s; /* parent */ unsigned subscriber_sequence; /* which event for this subscription*/ - int retry; /* which retry */ + unsigned int retry; /* which retry */ struct subscr_addr *addr; /* address to connect to */ struct wpabuf *data; /* event data to send */ - /* The following apply while we are sending an event message. */ - int sd; /* -1 or socket descriptor for open connection */ - int sd_registered; /* nonzero if we must cancel registration */ - struct httpread *hread; /* NULL or open connection for event msg */ + struct http_client *http_event; }; -static void event_timeout_handler(void *eloop_data, void *user_ctx); - /* event_clean -- clean sockets etc. of event * Leaves data, retry count etc. alone. */ static void event_clean(struct wps_event_ *e) { - if (e->s->current_event == e) { - eloop_cancel_timeout(event_timeout_handler, NULL, e); + if (e->s->current_event == e) e->s->current_event = NULL; - } - if (e->sd_registered) { - eloop_unregister_sock(e->sd, EVENT_TYPE_WRITE); - e->sd_registered = 0; - } - if (e->sd != -1) { - close(e->sd); - e->sd = -1; - } - if (e->hread) - httpread_destroy(e->hread); - e->hread = NULL; + http_client_free(e->http_event); + e->http_event = NULL; } /* event_delete -- delete single unqueued event * (be sure to dequeue first if need be) */ -void event_delete(struct wps_event_ *e) +static void event_delete(struct wps_event_ *e) { + wpa_printf(MSG_DEBUG, "WPS UPnP: Delete event %p", e); event_clean(e); wpabuf_free(e->data); os_free(e); @@ -101,63 +85,17 @@ void event_delete(struct wps_event_ *e) */ static struct wps_event_ *event_dequeue(struct subscription *s) { - struct wps_event_ **event_head = &s->event_queue; - struct wps_event_ *e = *event_head; - if (e == NULL) - return NULL; - e->next->prev = e->prev; - e->prev->next = e->next; - if (*event_head == e) { - if (e == e->next) { - /* last in queue */ - *event_head = NULL; - } else { - *event_head = e->next; - } + struct wps_event_ *e; + e = dl_list_first(&s->event_queue, struct wps_event_, list); + if (e) { + wpa_printf(MSG_DEBUG, "WPS UPnP: Dequeue event %p for " + "subscription %p", e, s); + dl_list_del(&e->list); } - s->n_queue--; - e->next = e->prev = NULL; - /* but parent "s" is still valid */ return e; } -/* event_enqueue_at_end -- add event to end of queue */ -static void event_enqueue_at_end(struct subscription *s, struct wps_event_ *e) -{ - struct wps_event_ **event_head = &s->event_queue; - if (*event_head == NULL) { - *event_head = e->next = e->prev = e; - } else { - e->next = *event_head; - e->prev = e->next->prev; - e->prev->next = e; - e->next->prev = e; - } - s->n_queue++; -} - - -/* event_enqueue_at_begin -- add event to begin of queue - * (appropriate for retrying event only) - */ -static void event_enqueue_at_begin(struct subscription *s, - struct wps_event_ *e) -{ - struct wps_event_ **event_head = &s->event_queue; - if (*event_head == NULL) { - *event_head = e->next = e->prev = e; - } else { - e->prev = *event_head; - e->next = e->prev->next; - e->prev->next = e; - e->next->prev = e; - *event_head = e; - } - s->n_queue++; -} - - /* event_delete_all -- delete entire event queue and current event */ void event_delete_all(struct subscription *s) { @@ -181,113 +119,37 @@ static void event_retry(struct wps_event_ *e, int do_next_address) struct subscription *s = e->s; struct upnp_wps_device_sm *sm = s->sm; + wpa_printf(MSG_DEBUG, "WPS UPnP: Retry event %p for subscription %p", + e, s); event_clean(e); /* will set: s->current_event = NULL; */ - if (do_next_address) + if (do_next_address) { e->retry++; - if (e->retry >= s->n_addr) { - wpa_printf(MSG_DEBUG, "WPS UPnP: Giving up on sending event"); + wpa_printf(MSG_DEBUG, "WPS UPnP: Try address %d", e->retry); + } + if (e->retry >= dl_list_len(&s->addr_list)) { + wpa_printf(MSG_DEBUG, "WPS UPnP: Giving up on sending event " + "for %s", e->addr->domain_and_port); + event_delete(e); + s->last_event_failed = 1; + if (!dl_list_empty(&s->event_queue)) + event_send_all_later(s->sm); return; } - event_enqueue_at_begin(s, e); + dl_list_add(&s->event_queue, &e->list); event_send_all_later(sm); } -/* called if the overall event-sending process takes too long */ -static void event_timeout_handler(void *eloop_data, void *user_ctx) +static struct wpabuf * event_build_message(struct wps_event_ *e) { - struct wps_event_ *e = user_ctx; - struct subscription *s = e->s; - - assert(e == s->current_event); - - wpa_printf(MSG_DEBUG, "WPS UPnP: Event send timeout"); - event_retry(e, 1); -} - - -/* event_got_response_handler -- called back when http response is received. */ -static void event_got_response_handler(struct httpread *handle, void *cookie, - enum httpread_event en) -{ - struct wps_event_ *e = cookie; - struct subscription *s = e->s; - struct upnp_wps_device_sm *sm = s->sm; - struct httpread *hread = e->hread; - int reply_code = 0; - - assert(e == s->current_event); - eloop_cancel_timeout(event_timeout_handler, NULL, e); - - if (en == HTTPREAD_EVENT_FILE_READY) { - if (httpread_hdr_type_get(hread) == HTTPREAD_HDR_TYPE_REPLY) { - reply_code = httpread_reply_code_get(hread); - if (reply_code == HTTP_OK) { - wpa_printf(MSG_DEBUG, - "WPS UPnP: Got event reply OK"); - event_delete(e); - goto send_more; - } else { - wpa_printf(MSG_DEBUG, "WPS UPnP: Got event " - "error reply code %d", reply_code); - goto bad; - } - } else { - wpa_printf(MSG_DEBUG, "WPS UPnP: Got bogus event " - "response %d", en); - } - } else { - wpa_printf(MSG_DEBUG, "WPS UPnP: Event response timeout/fail"); - goto bad; - } - event_retry(e, 1); - goto send_more; - -send_more: - /* Schedule sending more if there is more to send */ - if (s->event_queue) - event_send_all_later(sm); - return; - -bad: - /* - * If other side doesn't like what we say, forget about them. - * (There is no way to tell other side that we are dropping - * them...). - * Alternately, we could just do event_delete(e) - */ - wpa_printf(MSG_DEBUG, "WPS UPnP: Deleting subscription due to errors"); - subscription_unlink(s); - subscription_destroy(s); -} - - -/* event_send_tx_ready -- actually write event message - * - * Prequisite: subscription socket descriptor has become ready to - * write (because connection to subscriber has been made). - * - * It is also possible that we are called because the connect has failed; - * it is possible to test for this, or we can just go ahead and then - * the write will fail. - */ -static void event_send_tx_ready(int sock, void *eloop_ctx, void *sock_ctx) -{ - struct wps_event_ *e = sock_ctx; - struct subscription *s = e->s; struct wpabuf *buf; char *b; - assert(e == s->current_event); - assert(e->sd == sock); - buf = wpabuf_alloc(1000 + wpabuf_len(e->data)); - if (buf == NULL) { - event_retry(e, 0); - goto bad; - } + if (buf == NULL) + return NULL; wpabuf_printf(buf, "NOTIFY %s HTTP/1.1\r\n", e->addr->path); wpabuf_put_str(buf, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"); wpabuf_printf(buf, "HOST: %s\r\n", e->addr->domain_and_port); @@ -296,7 +158,7 @@ static void event_send_tx_ready(int sock, void *eloop_ctx, void *sock_ctx) "NTS: upnp:propchange\r\n"); wpabuf_put_str(buf, "SID: uuid:"); b = wpabuf_put(buf, 0); - uuid_bin2str(s->uuid, b, 80); + uuid_bin2str(e->s->uuid, b, 80); wpabuf_put(buf, os_strlen(b)); wpabuf_put_str(buf, "\r\n"); wpabuf_printf(buf, "SEQ: %u\r\n", e->subscriber_sequence); @@ -304,43 +166,83 @@ static void event_send_tx_ready(int sock, void *eloop_ctx, void *sock_ctx) (int) wpabuf_len(e->data)); wpabuf_put_str(buf, "\r\n"); /* terminating empty line */ wpabuf_put_buf(buf, e->data); + return buf; +} - /* Since the message size is pretty small, we should be - * able to get the operating system to buffer what we give it - * and not have to come back again later to write more... - */ -#if 0 - /* we could: Turn blocking back on? */ - fcntl(e->sd, F_SETFL, 0); -#endif - if (send_wpabuf(e->sd, buf) < 0) { + +static void event_addr_failure(struct wps_event_ *e) +{ + struct subscription *s = e->s; + + e->addr->num_failures++; + wpa_printf(MSG_DEBUG, "WPS UPnP: Failed to send event %p to %s " + "(num_failures=%u)", + e, e->addr->domain_and_port, e->addr->num_failures); + + if (e->addr->num_failures < MAX_FAILURES) { + /* Try other addresses, if available */ event_retry(e, 1); - goto bad; + return; } - wpabuf_free(buf); - buf = NULL; - if (e->sd_registered) { - e->sd_registered = 0; - eloop_unregister_sock(e->sd, EVENT_TYPE_WRITE); - } - /* Set up to read the reply */ - e->hread = httpread_create(e->sd, event_got_response_handler, - e /* cookie */, - 0 /* no data expected */, - EVENT_TIMEOUT_SEC); - if (e->hread == NULL) { - wpa_printf(MSG_ERROR, "WPS UPnP: httpread_create failed"); - event_retry(e, 0); - goto bad; + /* + * If other side doesn't like what we say, forget about them. + * (There is no way to tell other side that we are dropping them...). + */ + wpa_printf(MSG_DEBUG, "WPS UPnP: Deleting subscription %p " + "address %s due to errors", s, e->addr->domain_and_port); + dl_list_del(&e->addr->list); + subscr_addr_delete(e->addr); + e->addr = NULL; + + if (dl_list_empty(&s->addr_list)) { + /* if we've given up on all addresses */ + wpa_printf(MSG_DEBUG, "WPS UPnP: Removing subscription %p " + "with no addresses", s); + dl_list_del(&s->list); + subscription_destroy(s); + return; } - return; -bad: - /* Schedule sending more if there is more to send */ - if (s->event_queue) - event_send_all_later(s->sm); - wpabuf_free(buf); + /* Try other addresses, if available */ + event_retry(e, 0); +} + + +static void event_http_cb(void *ctx, struct http_client *c, + enum http_client_event event) +{ + struct wps_event_ *e = ctx; + struct subscription *s = e->s; + + wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP client callback: e=%p c=%p " + "event=%d", e, c, event); + switch (event) { + case HTTP_CLIENT_OK: + wpa_printf(MSG_DEBUG, + "WPS UPnP: Got event %p reply OK from %s", + e, e->addr->domain_and_port); + e->addr->num_failures = 0; + s->last_event_failed = 0; + event_delete(e); + + /* Schedule sending more if there is more to send */ + if (!dl_list_empty(&s->event_queue)) + event_send_all_later(s->sm); + break; + case HTTP_CLIENT_FAILED: + wpa_printf(MSG_DEBUG, "WPS UPnP: Event send failure"); + event_addr_failure(e); + break; + case HTTP_CLIENT_INVALID_REPLY: + wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid reply"); + event_addr_failure(e); + break; + case HTTP_CLIENT_TIMEOUT: + wpa_printf(MSG_DEBUG, "WPS UPnP: Event send timeout"); + event_addr_failure(e); + break; + } } @@ -367,91 +269,64 @@ bad: static int event_send_start(struct subscription *s) { struct wps_event_ *e; - int itry; + unsigned int itry; + struct wpabuf *buf; /* * Assume we are called ONLY with no current event and ONLY with * nonempty event queue and ONLY with at least one address to send to. */ - assert(s->addr_list != NULL); - assert(s->current_event == NULL); - assert(s->event_queue != NULL); + if (dl_list_empty(&s->addr_list)) + return -1; + if (s->current_event) + return -1; + if (dl_list_empty(&s->event_queue)) + return -1; s->current_event = e = event_dequeue(s); - /* Use address acc. to no. of retries */ - e->addr = s->addr_list; - for (itry = 0; itry < e->retry; itry++) - e->addr = e->addr->next; - - e->sd = socket(AF_INET, SOCK_STREAM, 0); - if (e->sd < 0) { - event_retry(e, 0); - return -1; - } - /* set non-blocking so we don't sleep waiting for connection */ - if (fcntl(e->sd, F_SETFL, O_NONBLOCK) != 0) { - event_retry(e, 0); + /* Use address according to number of retries */ + itry = 0; + dl_list_for_each(e->addr, &s->addr_list, struct subscr_addr, list) + if (itry++ == e->retry) + break; + if (itry < e->retry) return -1; - } - /* - * Start the connect. It might succeed immediately but more likely will - * return errno EINPROGRESS. - */ - if (connect(e->sd, (struct sockaddr *) &e->addr->saddr, - sizeof(e->addr->saddr))) { - if (errno != EINPROGRESS) { - event_retry(e, 1); - return -1; - } - } - /* Call back when ready for writing (or on failure...). */ - if (eloop_register_sock(e->sd, EVENT_TYPE_WRITE, event_send_tx_ready, - NULL, e)) { + + buf = event_build_message(e); + if (buf == NULL) { event_retry(e, 0); return -1; } - e->sd_registered = 1; - /* Don't wait forever! */ - if (eloop_register_timeout(EVENT_TIMEOUT_SEC, 0, event_timeout_handler, - NULL, e)) { + + e->http_event = http_client_addr(&e->addr->saddr, buf, 0, + event_http_cb, e); + if (e->http_event == NULL) { + wpabuf_free(buf); event_retry(e, 0); return -1; } + return 0; } /* event_send_all_later_handler -- actually send events as needed */ -void event_send_all_later_handler(void *eloop_data, void *user_ctx) +static void event_send_all_later_handler(void *eloop_data, void *user_ctx) { struct upnp_wps_device_sm *sm = user_ctx; - struct subscription *s; - struct subscription *s_old; + struct subscription *s, *tmp; int nerrors = 0; sm->event_send_all_queued = 0; - s = sm->subscriptions; - if (s == NULL) - return; - do { - if (s->addr_list == NULL) { - /* if we've given up on all addresses */ - wpa_printf(MSG_DEBUG, "WPS UPnP: Removing " - "subscription with no addresses"); - s_old = s; - s = s_old->next; - subscription_unlink(s_old); - subscription_destroy(s_old); - } else { - if (s->current_event == NULL /* not busy */ && - s->event_queue != NULL /* more to do */) { - if (event_send_start(s)) - nerrors++; - } - s = s->next; + dl_list_for_each_safe(s, tmp, &sm->subscriptions, struct subscription, + list) { + if (s->current_event == NULL /* not busy */ && + !dl_list_empty(&s->event_queue) /* more to do */) { + if (event_send_start(s)) + nerrors++; } - } while (sm->subscriptions != NULL && s != sm->subscriptions); + } if (nerrors) { /* Try again later */ @@ -494,32 +369,48 @@ void event_send_stop_all(struct upnp_wps_device_sm *sm) * event_add - Add a new event to a queue * @s: Subscription * @data: Event data (is copied; caller retains ownership) - * Returns: 0 on success, 1 on error + * @probereq: Whether this is a Probe Request event + * Returns: 0 on success, -1 on error, 1 on max event queue limit reached */ -int event_add(struct subscription *s, const struct wpabuf *data) +int event_add(struct subscription *s, const struct wpabuf *data, int probereq) { struct wps_event_ *e; + unsigned int len; - if (s->n_queue >= MAX_EVENTS_QUEUED) { + len = dl_list_len(&s->event_queue); + if (len >= MAX_EVENTS_QUEUED) { wpa_printf(MSG_DEBUG, "WPS UPnP: Too many events queued for " - "subscriber"); + "subscriber %p", s); return 1; } + if (s->last_event_failed && probereq && len > 0) { + /* + * Avoid queuing frames for subscribers that may have left + * without unsubscribing. + */ + wpa_printf(MSG_DEBUG, "WPS UPnP: Do not queue more Probe " + "Request frames for subscription %p since last " + "delivery failed", s); + return -1; + } + e = os_zalloc(sizeof(*e)); if (e == NULL) - return 1; + return -1; + dl_list_init(&e->list); e->s = s; - e->sd = -1; e->data = wpabuf_dup(data); if (e->data == NULL) { os_free(e); - return 1; + return -1; } e->subscriber_sequence = s->next_subscriber_sequence++; if (s->next_subscriber_sequence == 0) s->next_subscriber_sequence++; - event_enqueue_at_end(s, e); + wpa_printf(MSG_DEBUG, "WPS UPnP: Queue event %p for subscriber %p " + "(queue len %u)", e, s, len + 1); + dl_list_add_tail(&s->event_queue, &e->list); event_send_all_later(s->sm); return 0; }