/* event_send_start -- prepare to send a event message to subscriber * * This gets complicated because: * -- The message is sent via TCP and we have to keep the stream open * for 30 seconds to get a response... then close it. * -- But we might have other event happen in the meantime... * we have to queue them, if we lose them then the subscriber will * be forced to unsubscribe and subscribe again. * -- If multiple URLs are provided then we are supposed to try successive * ones after 30 second timeout. * -- The URLs might use domain names instead of dotted decimal addresses, * and resolution of those may cause unwanted sleeping. * -- Doing the initial TCP connect can take a while, so we have to come * back after connection and then send the data. * * Returns nonzero on error; * * Prerequisite: No current event send (s->current_event == NULL) * and non-empty queue. */ static int event_send_start(struct subscription *s) { struct wps_event_ *e; int itry; /* * 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); 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); 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)) { 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)) { event_retry(e, 0); return -1; } return 0; }
/* 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 from " "%s", e->addr->domain_and_port); event_delete(e); goto send_more; } else { wpa_printf(MSG_DEBUG, "WPS UPnP: Got event " "error reply code %d from %s", reply_code, e->addr->domain_and_port); goto bad; } } else { wpa_printf(MSG_DEBUG, "WPS UPnP: Got bogus event " "response %d from %s", en, e->addr->domain_and_port); } } else { wpa_printf(MSG_DEBUG, "WPS UPnP: Event response timeout/fail " "for %s", e->addr->domain_and_port); 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); }
/* called if the overall event-sending process takes too long */ static void event_timeout_handler(void *eloop_data, void *user_ctx) { 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_send_start -- prepare to send a event message to subscriber * * This gets complicated because: * -- The message is sent via TCP and we have to keep the stream open * for 30 seconds to get a response... then close it. * -- But we might have other event happen in the meantime... * we have to queue them, if we lose them then the subscriber will * be forced to unsubscribe and subscribe again. * -- If multiple URLs are provided then we are supposed to try successive * ones after 30 second timeout. * -- The URLs might use domain names instead of dotted decimal addresses, * and resolution of those may cause unwanted sleeping. * -- Doing the initial TCP connect can take a while, so we have to come * back after connection and then send the data. * * Returns nonzero on error; * * Prerequisite: No current event send (s->current_event == NULL) * and non-empty queue. */ static int event_send_start(struct subscription *s) { struct wps_event_ *e; 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. */ if (dl_list_empty(&s->addr_list) || s->current_event || dl_list_empty(&s->event_queue)) return -1; s->current_event = e = event_dequeue(s); /* 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; buf = event_build_message(e); if (buf == NULL) { event_retry(e, 0); return -1; } 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; }
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); return; } /* * 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; } /* Try other addresses, if available */ event_retry(e, 0); }
/* 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; } 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); wpabuf_put_str(buf, "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n" "NT: upnp:event\r\n" "NTS: upnp:propchange\r\n"); wpabuf_put_str(buf, "SID: uuid:"); b = wpabuf_put(buf, 0); uuid_bin2str(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); wpabuf_printf(buf, "CONTENT-LENGTH: %d\r\n", (int) wpabuf_len(e->data)); wpabuf_put_str(buf, "\r\n"); /* terminating empty line */ wpabuf_put_buf(buf, e->data); /* 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 wpa_printf(MSG_DEBUG, "WPS UPnP: Sending event to %s", e->addr->domain_and_port); if (send_wpabuf(e->sd, buf) < 0) { event_retry(e, 1); goto bad; } 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; } return; bad: /* Schedule sending more if there is more to send */ if (s->event_queue) event_send_all_later(s->sm); wpabuf_free(buf); }