From 7f47555c66c694e6f35cf6b5d1fc1afc5b149a6f Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Fri, 12 Dec 2014 19:08:00 +0100 Subject: [PATCH] miniupnpc-libevent: subscribe and receive UPNP events ! enable with -DENABLE_UPNP_EVENTS libevent need to support SUBSCRIBE and NOTIFY requests see https://github.com/miniupnp/libevent/tree/upnp_ext --- miniupnpc-libevent/Makefile | 2 + miniupnpc-libevent/miniupnpc-libevent.c | 141 ++++++++++++++++++++++++ miniupnpc-libevent/miniupnpc-libevent.h | 15 +++ miniupnpc-libevent/upnpc-libevent.c | 16 +++ 4 files changed, 174 insertions(+) diff --git a/miniupnpc-libevent/Makefile b/miniupnpc-libevent/Makefile index c35f804..55d1f20 100644 --- a/miniupnpc-libevent/Makefile +++ b/miniupnpc-libevent/Makefile @@ -13,6 +13,8 @@ CFLAGS += -D_BSD_SOURCE CFLAGS += -D_POSIX_C_SOURCE=200112L CFLAGS += -I/usr/local/include +#CFLAGS += -DENABLE_UPNP_EVENTS + LDFLAGS = -levent LDFLAGS += -L/usr/local/lib diff --git a/miniupnpc-libevent/miniupnpc-libevent.c b/miniupnpc-libevent/miniupnpc-libevent.c index cc75ea3..2ab06ff 100644 --- a/miniupnpc-libevent/miniupnpc-libevent.c +++ b/miniupnpc-libevent/miniupnpc-libevent.c @@ -411,6 +411,30 @@ static void upnpc_desc_received(struct evhttp_request * req, void * pvoid) } } +#ifdef ENABLE_UPNP_EVENTS +static void upnpc_subscribe_response(struct evhttp_request * req, void * pvoid) +{ + size_t len; + unsigned char * data; + struct evbuffer * input_buffer; + upnpc_device_t * d = (upnpc_device_t *)pvoid; + + input_buffer = evhttp_request_get_input_buffer(req); + len = evbuffer_get_length(input_buffer); + data = evbuffer_pullup(input_buffer, len); + debug_printf("%s %d (%d bytes)\n", __func__, evhttp_request_get_response_code(req), (int)len); + d->state &= ~UPNPC_DEVICE_SOAP_REQ; + if(evhttp_request_get_response_code(req) != HTTP_OK) { + /* TODO ERROR */ + } else { + const char * sid; + struct evkeyvalq * headers = evhttp_request_get_input_headers(req); + sid = evhttp_find_header(headers, "sid"); + debug_printf("SID=%s\n", sid); + } +} +#endif /* ENABLE_UPNP_EVENTS */ + static void upnpc_soap_response(struct evhttp_request * req, void * pvoid) { size_t len; @@ -606,6 +630,52 @@ static int upnpc_send_soap_request(upnpc_device_t * p, const char * url, return 0; } +#ifdef ENABLE_UPNP_EVENTS +void upnpc_event_conn_req(struct evhttp_request * req, void * data) +{ + size_t len; + char * xml_data; + struct evbuffer * input_buffer; + struct evkeyvalq * headers; + const char * sid; + const char * nts; + const char * seq; + struct NameValueParserData parsed_data; + struct NameValue * nv; + upnpc_device_t * d = (upnpc_device_t *)data; + + debug_printf("%s(%p, %p)\n", __func__, req, d); + input_buffer = evhttp_request_get_input_buffer(req); + len = evbuffer_get_length(input_buffer); + if(len == 0) { + evhttp_send_reply(req, 406, "Not Acceptable", NULL); + return; + } + xml_data = (char *)evbuffer_pullup(input_buffer, len); + headers = evhttp_request_get_input_headers(req); + sid = evhttp_find_header(headers, "sid"); + nts = evhttp_find_header(headers, "nts"); + seq = evhttp_find_header(headers, "seq"); + debug_printf("SID=%s NTS=%s SEQ=%s\n", sid, nts, seq); + if(sid == NULL || nts == NULL || seq == NULL) { + evhttp_send_reply(req, 412, "Precondition Failed", NULL); + return; + } + /*debug_printf("%.*s\n", len, xml_data);*/ + ParseNameValue(xml_data, len, &parsed_data); + for(nv = parsed_data.l_head; nv != NULL; nv = nv->l_next) { + if(d->parent->value_changed_cb) { + d->parent->value_changed_cb(d->parent, d, d->parent->cb_data, d->conn_service_type, nv->name, nv->value); + } else { + debug_printf("%s=%s\n", nv->name, nv->value); + } + } + ClearNameValueList(&parsed_data); + /* response : 200 OK */ + evhttp_send_reply(req, 200, "OK", NULL); +} +#endif /* ENABLE_UPNP_EVENTS */ + /* public functions */ int upnpc_init(upnpc_t * p, struct event_base * base, const char * multicastif, upnpc_callback_fn ready_cb, upnpc_callback_fn soap_cb, void * cb_data) @@ -693,6 +763,15 @@ int upnpc_set_local_address(upnpc_t * p, const char * address, uint16_t port) return UPNPC_OK; } +#ifdef ENABLE_UPNP_EVENTS +int upnpc_set_event_callback(upnpc_t * p, upnpc_event_callback_fn cb) +{ + if(!p || !cb) return UPNPC_ERR_INVALID_ARGS; + p->value_changed_cb = cb; + return UPNPC_OK; +} +#endif /* ENABLE_UPNP_EVENTS */ + static void upnpc_device_finalize(upnpc_device_t * d) { d->state = 0; @@ -743,9 +822,71 @@ int upnpc_finalize(upnpc_t * p) p->devices = d->next; free(d); } + free(p->local_address); + p->local_address = NULL; +#ifdef ENABLE_UPNP_EVENTS + if(p->http_server) { + evhttp_free(p->http_server); + p->http_server = NULL; + } +#endif /* ENABLE_UPNP_EVENTS */ return UPNPC_OK; } +#ifdef ENABLE_UPNP_EVENTS +int upnpc_event_subscribe(upnpc_device_t * p) +{ + char hostname[MAXHOSTNAMELEN+1]; + char hostname_port[MAXHOSTNAMELEN+1+6]; + unsigned short port; + char * path; + unsigned int scope_id; + struct evhttp_request * req; + struct evkeyvalq * headers; + char callback_header[7+15+1+5+9+2+1]; + + if(p->parent->http_server == NULL) { + /* HTTP server to receive event notifications */ + p->parent->http_server = evhttp_new(p->parent->base); + if(p->parent->http_server == NULL) { + debug_printf("evhttp_new() FAILED\n"); + return -1; + } + evhttp_set_allowed_methods(p->parent->http_server, EVHTTP_REQ_NOTIFY); + evhttp_set_cb(p->parent->http_server, "/evt_conn", upnpc_event_conn_req, p); + if(evhttp_bind_socket(p->parent->http_server, p->parent->local_address, p->parent->local_port) < 0) { + debug_printf("evhttp_bind_socket() FAILED\n"); + return -1; + } + } + /*if(!parseURL(p->event_cif_url, hostname, &port, &path, &scope_id)) {*/ + if(!parseURL(p->event_conn_url, hostname, &port, &path, &scope_id)) { + return -1; + } + if(port != 80) + snprintf(hostname_port, sizeof(hostname_port), "%s:%hu", hostname, port); + else + strncpy(hostname_port, hostname, sizeof(hostname_port)); + if(p->soap_conn == NULL) { + p->soap_conn = evhttp_connection_base_new(p->parent->base, NULL, hostname, port); + } + req = evhttp_request_new(upnpc_subscribe_response, p); + headers = evhttp_request_get_output_headers(req); + /*buffer = evhttp_request_get_output_buffer(req);*/ + evhttp_add_header(headers, "Host", hostname_port); + /*evhttp_add_header(headers, "User-Agent", "***");*/ + snprintf(callback_header, sizeof(callback_header), "", p->parent->local_address, p->parent->local_port); + evhttp_add_header(headers, "Callback", callback_header); + evhttp_add_header(headers, "NT", "upnp:event"); + /*evhttp_add_header(headers, "NTS", "");*/ + evhttp_add_header(headers, "Timeout", "3600"); + /*evbuffer_add(buffer, body, body_len);*/ + evhttp_make_request(p->soap_conn, req, EVHTTP_REQ_SUBSCRIBE, path); + p->state |= UPNPC_DEVICE_SOAP_REQ; + return 0; +} +#endif /* ENABLE_UPNP_EVENTS */ + int upnpc_get_external_ip_address(upnpc_device_t * p) { return upnpc_send_soap_request(p, p->control_conn_url, diff --git a/miniupnpc-libevent/miniupnpc-libevent.h b/miniupnpc-libevent/miniupnpc-libevent.h index 0b55f49..0c97588 100644 --- a/miniupnpc-libevent/miniupnpc-libevent.h +++ b/miniupnpc-libevent/miniupnpc-libevent.h @@ -49,6 +49,9 @@ typedef struct upnpc_device upnpc_device_t; typedef struct upnpc upnpc_t; typedef void(* upnpc_callback_fn)(int, upnpc_t *, upnpc_device_t *, void *); +#ifdef ENABLE_UPNP_EVENTS +typedef void(* upnpc_event_callback_fn)(upnpc_t *, upnpc_device_t *, void *, const char *, const char *, const char *); +#endif /* ENABLE_UPNP_EVENTS */ struct upnpc_device { upnpc_t * parent; @@ -76,6 +79,10 @@ struct upnpc { upnpc_callback_fn ready_cb; upnpc_callback_fn soap_cb; void * cb_data; +#ifdef ENABLE_UPNP_EVENTS + struct evhttp * http_server; + upnpc_event_callback_fn value_changed_cb; +#endif /* ENABLE_UPNP_EVENTS */ char * local_address; uint16_t local_port; }; @@ -85,10 +92,18 @@ int upnpc_init(upnpc_t * p, struct event_base * base, const char * multicastif, int upnpc_set_local_address(upnpc_t * p, const char * address, uint16_t port); +#ifdef ENABLE_UPNP_EVENTS +int upnpc_set_event_callback(upnpc_t * p, upnpc_event_callback_fn cb); +#endif /* ENABLE_UPNP_EVENTS */ + int upnpc_start(upnpc_t * p); int upnpc_finalize(upnpc_t * p); +#ifdef ENABLE_UPNP_EVENTS +int upnpc_event_subscribe(upnpc_device_t * p); +#endif /* ENABLE_UPNP_EVENTS */ + int upnpc_get_external_ip_address(upnpc_device_t * p); int upnpc_get_link_layer_max_rate(upnpc_device_t * p); diff --git a/miniupnpc-libevent/upnpc-libevent.c b/miniupnpc-libevent/upnpc-libevent.c index d8326a6..ce545a9 100644 --- a/miniupnpc-libevent/upnpc-libevent.c +++ b/miniupnpc-libevent/upnpc-libevent.c @@ -45,7 +45,11 @@ static void ready(int code, upnpc_t * p, upnpc_device_t * d, void * data) printf("READY ! %d\n", code); printf(" root_desc_location='%s'\n", d->root_desc_location); /* 1st request */ +#ifdef ENABLE_UPNP_EVENTS + upnpc_event_subscribe(d); +#else upnpc_get_status_info(d); +#endif /* ENABLE_UPNP_EVENTS */ } else { printf("DISCOVER ERROR : %d\n", code); switch(code) { @@ -122,6 +126,15 @@ static void soap(int code, upnpc_t * p, upnpc_device_t * d, void * data) } } +#ifdef ENABLE_UPNP_EVENTS +/* event callback */ +static void event_callback(upnpc_t * p, upnpc_device_t * d, void * data, + const char * service_id, const char * property_name, const char * property_value) +{ + printf("PROPERTY VALUE CHANGE (service=%s): %s=%s\n", service_id, property_name, property_value); +} +#endif /* ENABLE_UPNP_EVENTS */ + /* use a UDP "connection" to 8.8.8.8 * to retrieve local address */ int find_local_address(void) @@ -216,6 +229,9 @@ int main(int argc, char * * argv) return 1; } upnpc_set_local_address(&upnp, local_address, 50000); +#ifdef ENABLE_UPNP_EVENTS + upnpc_set_event_callback(&upnp, event_callback); +#endif /* ENABLE_UPNP_EVENTS */ if(upnpc_start(&upnp) != UPNPC_OK) { fprintf(stderr, "upnp_start() failed\n"); return 1;