/* $Id: miniupnpc-libevent.c,v 1.27 2015/07/22 13:51:09 nanard Exp $ */ /* miniupnpc-libevent * Copyright (c) 2008-2016, Thomas BERNARD * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include /*#include */ #include #ifdef _WIN32 #include #include #include #define PRINT_SOCKET_ERROR printf #define SOCKET_ERROR GetWSALastError() #define WOULDBLOCK(err) (err == WSAEWOULDBLOCK) #else /* _WIN32 */ #include #include #define closesocket close #define PRINT_SOCKET_ERROR perror #define SOCKET_ERROR errno #define WOULDBLOCK(err) (err == EAGAIN || err == EWOULDBLOCK) #endif /* _WIN32 */ #include "miniupnpc-libevent.h" #include "minixml.h" #include "igd_desc_parse.h" #include "upnpreplyparse.h" #ifndef MIN #define MIN(x,y) (((x)<(y))?(x):(y)) #endif /* MIN */ #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 64 #endif /* MAXHOSTNAMELEN */ #define SSDP_PORT 1900 #define SSDP_MCAST_ADDR "239.255.255.250" #define XSTR(s) STR(s) #define STR(s) #s #ifdef DEBUG #define debug_printf(...) fprintf(stderr, __VA_ARGS__) #else #define debug_printf(...) (void)0 #endif /* compare the beginning of a string with a constant string */ #define COMPARE(str, cstr) (0==memcmp(str, cstr, sizeof(cstr) - 1)) /* stuctures */ struct upnp_args { const char * elt; const char * val; }; /* private functions */ static int upnpc_get_desc(upnpc_device_t * p, const char * url); static char * build_url_string(const char * urlbase, const char * root_desc_url, const char * controlurl); /* data */ static const char * devices_to_search[] = { "urn:schemas-upnp-org:device:InternetGatewayDevice:1", "urn:schemas-upnp-org:service:WANIPConnection:1", "urn:schemas-upnp-org:service:WANPPPConnection:1", "upnp:rootdevice", 0 }; #ifdef DEBUG static void upnpc_conn_close_cb(struct evhttp_connection * conn, void * data) { upnpc_device_t * d = (upnpc_device_t *)data; debug_printf("%s %p %p\n", __func__, conn, d); } #endif /* DEBUG */ /* parse_msearch_reply() * the last 4 arguments are filled during the parsing : * - location/locationsize : "location:" field of the SSDP reply packet * - st/stsize : "st:" field of the SSDP reply packet. * The strings are NOT null terminated */ static void parse_msearch_reply(const char * reply, int size, const char * * location, int * locationsize, const char * * st, int * stsize) { int a, b, i; i = 0; /* current character index */ a = i; /* start of the line */ b = 0; /* end of the "header" (position of the colon) */ while(idiscover_device_index++], mx); debug_printf("%s: %s", __func__, bufr); n = sendto(s, bufr, n, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); if (n < 0) { PRINT_SOCKET_ERROR("sendto"); } } static int upnpc_set_root_desc_location(upnpc_device_t * d, const char * location, int locationsize) { char * tmp; tmp = realloc(d->root_desc_location, locationsize + 1); if(tmp == NULL) { return -1; } memcpy(tmp, location, locationsize); tmp[locationsize] = '\0'; d->root_desc_location = tmp; return 0; } static upnpc_device_t * upnpc_find_device_with_location(upnpc_t * p, const char * location, int locationsize) { upnpc_device_t * d; for(d = p->devices; d != NULL; d = d->next) { if(d->root_desc_location && ((int)strlen(d->root_desc_location) == locationsize) && (0 == memcmp(location, d->root_desc_location, locationsize))) return d; } return NULL; } static void upnpc_receive_and_parse_ssdp(evutil_socket_t s, short events, upnpc_t * p) { char bufr[2048]; ssize_t len; if(events == EV_TIMEOUT) { /* nothing received ... */ debug_printf("%s() TIMEOUT\n", __func__); if(!devices_to_search[p->discover_device_index]) { debug_printf("*** NO MORE DEVICES TO SEARCH ***\n"); event_del(p->ev_ssdp_recv); /* no device found : report error */ p->ready_cb(UPNPC_ERR_NO_DEVICE_FOUND, p, NULL, p->cb_data); } else { /* send another SSDP M-SEARCH packet */ if(event_add(p->ev_ssdp_writable, NULL)) { debug_printf("event_add FAILED\n"); } } return; } len = recv(s, bufr, sizeof(bufr), 0); debug_printf("input %d bytes\n", (int)len); if(len < 0) { PRINT_SOCKET_ERROR("recv"); } else if(len == 0) { debug_printf("SSDP socket closed ?\n"); } else { const char * location = NULL; int locationsize = 0; const char * st = NULL; int stsize = 0; debug_printf("%.*s", (int)len, bufr); parse_msearch_reply(bufr, len, &location, &locationsize, &st, &stsize); debug_printf("location = '%.*s'\n", locationsize, location); debug_printf("st = '%.*s'\n", stsize, st); if(location != NULL) { upnpc_device_t * device; device = upnpc_find_device_with_location(p, location, locationsize); if(device) { debug_printf("device already known\n"); } else { device = malloc(sizeof(upnpc_device_t)); if(device == NULL) { debug_printf("Memory allocation error\n"); return; } memset(device, 0, sizeof(upnpc_device_t)); device->parent = p; device->next = p->devices; p->devices = device; if(upnpc_set_root_desc_location(device, location, locationsize) < 0) { return; } if(upnpc_get_desc(device, device->root_desc_location)) { debug_printf("FAILED to request device root description\n"); } } #if 0 event_del(p->ev_ssdp_recv); /* stop receiving SSDP responses */ #endif } else { /* or do nothing ? */ debug_printf("no location\n"); } } } static int parseURL(const char * url, char * hostname, unsigned short * port, char * * path, unsigned int * scope_id) { char * p1, *p2, *p3; if(!url) return 0; p1 = strstr(url, "://"); if(!p1) return 0; p1 += 3; if( (url[0]!='h') || (url[1]!='t') ||(url[2]!='t') || (url[3]!='p')) return 0; memset(hostname, 0, MAXHOSTNAMELEN + 1); if(*p1 == '[') { /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */ char * scope; scope = strchr(p1, '%'); p2 = strchr(p1, ']'); if(p2 && scope && scope < p2 && scope_id) { /* parse scope */ #ifdef IF_NAMESIZE char tmp[IF_NAMESIZE]; int l; scope++; /* "%25" is just '%' in URL encoding */ if(scope[0] == '2' && scope[1] == '5') scope += 2; /* skip "25" */ l = p2 - scope; if(l >= IF_NAMESIZE) l = IF_NAMESIZE - 1; memcpy(tmp, scope, l); tmp[l] = '\0'; *scope_id = if_nametoindex(tmp); if(*scope_id == 0) { *scope_id = (unsigned int)strtoul(tmp, NULL, 10); } #else /* IF_NAMESIZE */ /* under windows, scope is numerical */ char tmp[8]; int l; scope++; /* "%25" is just '%' in URL encoding */ if(scope[0] == '2' && scope[1] == '5') scope += 2; /* skip "25" */ l = p2 - scope; if(l >= (int)sizeof(tmp)) l = sizeof(tmp) - 1; memcpy(tmp, scope, l); tmp[l] = '\0'; *scope_id = (unsigned int)strtoul(tmp, NULL, 10); #endif /* IF_NAMESIZE */ } p3 = strchr(p1, '/'); if(p2 && p3) { p2++; strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); if(*p2 == ':') { *port = 0; p2++; while( (*p2 >= '0') && (*p2 <= '9')) { *port *= 10; *port += (unsigned short)(*p2 - '0'); p2++; } } else { *port = 80; } *path = p3; return 1; } } p2 = strchr(p1, ':'); p3 = strchr(p1, '/'); if(!p3) return 0; if(!p2 || (p2>p3)) { strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1))); *port = 80; } else { strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); *port = 0; p2++; while( (*p2 >= '0') && (*p2 <= '9')) { *port *= 10; *port += (unsigned short)(*p2 - '0'); p2++; } } *path = p3; return 1; } static void upnpc_desc_received(struct evhttp_request * req, void * pvoid) { size_t len; unsigned char * data; struct evbuffer * input_buffer; struct IGDdatas igd; struct xmlparser parser; upnpc_device_t * d = (upnpc_device_t *)pvoid; if(req == NULL) { debug_printf("%s(%p, %p) NULL argument !\n", __func__, req, pvoid); return; } 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); if(evhttp_request_get_response_code(req) != HTTP_OK) { d->parent->ready_cb(evhttp_request_get_response_code(req), d->parent, d, d->parent->cb_data); return; } if(data == NULL) { d->parent->ready_cb(UPNPC_ERR_ROOT_DESC_ERROR, d->parent, d, d->parent->cb_data); return; } debug_printf("%.*s\n", (int)len, (char *)data); memset(&igd, 0, sizeof(struct IGDdatas)); memset(&parser, 0, sizeof(struct xmlparser)); parser.xmlstart = (char *)data; parser.xmlsize = len; parser.data = &igd; parser.starteltfunc = IGDstartelt; parser.endeltfunc = IGDendelt; parser.datafunc = IGDdata; parsexml(&parser); #ifdef DEBUG printIGD(&igd); #endif /* DEBUG */ d->control_conn_url = build_url_string(igd.urlbase, d->root_desc_location, igd.first.controlurl); d->event_conn_url = build_url_string(igd.urlbase, d->root_desc_location, igd.first.eventsuburl); d->conn_service_type = strdup(igd.first.servicetype); d->control_cif_url = build_url_string(igd.urlbase, d->root_desc_location, igd.CIF.controlurl); d->event_cif_url = build_url_string(igd.urlbase, d->root_desc_location, igd.CIF.eventsuburl); d->cif_service_type = strdup(igd.CIF.servicetype); debug_printf("control_conn_url='%s'\n (service_type='%s')\n", d->control_conn_url, d->conn_service_type); debug_printf("event_conn_url='%s'\n", d->event_conn_url); debug_printf("control_cif_url='%s'\n (service_type='%s')\n", d->control_cif_url, d->cif_service_type); if((d->cif_service_type == NULL) || (d->cif_service_type[0] == '\0') || (!COMPARE(d->cif_service_type, "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:"))) { d->parent->ready_cb(UPNPC_ERR_NOT_IGD, d->parent, d, d->parent->cb_data); } else { d->state |= UPNPC_DEVICE_GETSTATUS; upnpc_get_status_info(d); } } #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; if(req == NULL) { debug_printf("%s(%p, %p) NULL argument !\n", __func__, req, pvoid); return; } 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); if(sid) { if(d->event_conn_sid) free(d->event_conn_sid); d->event_conn_sid = strdup(sid); } } } #endif /* ENABLE_UPNP_EVENTS */ static void upnpc_soap_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; int code; if(req == NULL) { debug_printf("%s(%p, %p) NULL argument !\n", __func__, req, pvoid); return; } code = evhttp_request_get_response_code(req); 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__, code, (int)len); debug_printf("%.*s\n", (int)len, (char *)data); if(data == NULL) return; ClearNameValueList(&d->soap_response_data); ParseNameValue((char *)data, (int)len, &d->soap_response_data); d->state &= ~UPNPC_DEVICE_SOAP_REQ; if(d->state & UPNPC_DEVICE_READY) { d->parent->soap_cb(code, d->parent, d, d->parent->cb_data); } else if(d->state & UPNPC_DEVICE_GETSTATUS) { const char * connection_status; d->state &= ~UPNPC_DEVICE_GETSTATUS; connection_status = GetValueFromNameValueList(&d->soap_response_data, "NewConnectionStatus"); d->state |= UPNPC_DEVICE_READY; if((code == 200) && connection_status && (0 == strcmp("Connected", connection_status))) { d->parent->ready_cb(code, d->parent, d, d->parent->cb_data); d->state |= UPNPC_DEVICE_CONNECTED; event_del(d->parent->ev_ssdp_recv); } else { d->parent->ready_cb(UPNPC_ERR_NOT_CONNECTED, d->parent, d, d->parent->cb_data); } } } static int upnpc_get_desc(upnpc_device_t * d, const char * url) { 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; /* if(d->root_desc_location == NULL) { return -1; } */ if(!parseURL(url/*d->root_desc_location*/, 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(d->desc_conn == NULL) { d->desc_conn = evhttp_connection_base_new(d->parent->base, NULL, hostname, port); } #ifdef DEBUG evhttp_connection_set_closecb(d->desc_conn, upnpc_conn_close_cb, d); #endif /* DEBUG */ /*evhttp_connection_set_timeout(p->desc_conn, 600);*/ req = evhttp_request_new(upnpc_desc_received/*callback*/, d); headers = evhttp_request_get_output_headers(req); evhttp_add_header(headers, "Host", hostname_port); evhttp_add_header(headers, "Connection", "close"); /*evhttp_add_header(headers, "User-Agent", "OS/version UPnP/1.1 MiniUPnPc-libevent/2.2");*/ return evhttp_make_request(d->desc_conn, req, EVHTTP_REQ_GET, path); } static char * build_url_string(const char * urlbase, const char * root_desc_url, const char * controlurl) { int l, n; char * s; const char * base; char * p; /* if controlurl is an absolute url, return it */ if(0 == memcmp("http://", controlurl, 7)) return strdup(controlurl); base = (urlbase[0] == '\0') ? root_desc_url : urlbase; n = strlen(base); if(n > 7) { p = strchr(base + 7, '/'); if(p) n = p - base; } l = n + strlen(controlurl) + 1; if(controlurl[0] != '/') l++; s = malloc(l); if(s == NULL) return NULL; memcpy(s, base, n); if(controlurl[0] != '/') s[n++] = '/'; memcpy(s + n, controlurl, l - n); return s; } #define SOAPPREFIX "s" #define SERVICEPREFIX "u" #define SERVICEPREFIX2 'u' static int upnpc_send_soap_request(upnpc_device_t * p, const char * url, const char * service, const char * method, const struct upnp_args * args, int arg_count) { char action[128]; char * body; const char fmt_soap[] = "\r\n" "<" SOAPPREFIX ":Envelope " "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" "<" SOAPPREFIX ":Body>" "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" "%s" "" "" "\r\n"; int body_len; char hostname[MAXHOSTNAMELEN+1]; char hostname_port[MAXHOSTNAMELEN+1+6]; unsigned short port; char * path; unsigned int scope_id; char * args_xml = NULL; struct evhttp_request * req; struct evkeyvalq * headers; struct evbuffer * buffer; if(p->state & UPNPC_DEVICE_SOAP_REQ) { debug_printf("%s: another SOAP request in progress\n", __func__); return UPNPC_ERR_REQ_IN_PROGRESS; } if(arg_count > 0) { int i; size_t l, n; for(i = 0, l = 0; i < arg_count; i++) { /* VAL */ l += strlen(args[i].elt) * 2 + strlen(args[i].val) + 5; } args_xml = malloc(++l); if(args_xml == NULL) { return -1; } for(i = 0, n = 0; i < arg_count && n < l; i++) { /* VAL */ n += snprintf(args_xml + n, l - n, "<%s>%s", args[i].elt, args[i].val, args[i].elt); } } body_len = snprintf(NULL, 0, fmt_soap, method, service, args_xml?args_xml:"", method); body = malloc(body_len + 1); if(body == NULL) { free(args_xml); return -1; } if(snprintf(body, body_len + 1, fmt_soap, method, service, args_xml?args_xml:"", method) != body_len) { debug_printf("%s: snprintf() returned strange value...\n", __func__); } free(args_xml); args_xml = NULL; if(!parseURL(url, hostname, &port, &path, &scope_id)) { free(body); return -1; } if(port != 80) snprintf(hostname_port, sizeof(hostname_port), "%s:%hu", hostname, port); else strncpy(hostname_port, hostname, sizeof(hostname_port)); snprintf(action, sizeof(action), "\"%s#%s\"", service, method); if(p->soap_conn == NULL) { p->soap_conn = evhttp_connection_base_new(p->parent->base, NULL, hostname, port); } req = evhttp_request_new(upnpc_soap_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, "SOAPAction", action); evhttp_add_header(headers, "Content-Type", "text/xml; charset=\"utf-8\""); /*evhttp_add_header(headers, "User-Agent", "OS/version UPnP/1.1 MiniUPnPc-libevent/2.2");*/ /*evhttp_add_header(headers, "Cache-Control", "no-cache");*/ /*evhttp_add_header(headers, "Pragma", "no-cache");*/ evbuffer_add(buffer, body, body_len); evhttp_make_request(p->soap_conn, req, EVHTTP_REQ_POST, path); free(body); p->state |= UPNPC_DEVICE_SOAP_REQ; return 0; } #ifdef ENABLE_UPNP_EVENTS #define EVHTTP_REQ_NOTIFY ((EVHTTP_REQ_MAX) << 1) #define EVHTTP_REQ_SUBSCRIBE ((EVHTTP_REQ_NOTIFY) << 1) #define EVHTTP_REQ_UNSUBSCRIBE ((EVHTTP_REQ_SUBSCRIBE) << 1) static int ext_methods_cb(struct evhttp_ext_method *p) { if(p == NULL) return -1; if(p->method != NULL) { if(strcmp(p->method, "NOTIFY") == 0) { p->type = EVHTTP_REQ_NOTIFY; p->flags = EVHTTP_METHOD_HAS_BODY; } else if(strcmp(p->method, "SUBSCRIBE") == 0) { p->type = EVHTTP_REQ_SUBSCRIBE; } else if(strcmp(p->method, "UNSUBSCRIBE") == 0) { p->type = EVHTTP_REQ_UNSUBSCRIBE; } else { return -1; } } else switch(p->type) { case EVHTTP_REQ_NOTIFY: p->method = "NOTIFY"; p->flags = EVHTTP_METHOD_HAS_BODY; break; case EVHTTP_REQ_SUBSCRIBE: p->method = "SUBSCRIBE"; break; case EVHTTP_REQ_UNSUBSCRIBE: p->method = "UNSUBSCRIBE"; break; default: return -1; } return 0; }; 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 * nt; 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); headers = evhttp_request_get_input_headers(req); input_buffer = evhttp_request_get_input_buffer(req); len = evbuffer_get_length(input_buffer); sid = evhttp_find_header(headers, "sid"); nts = evhttp_find_header(headers, "nts"); nt = evhttp_find_header(headers, "nt"); seq = evhttp_find_header(headers, "seq"); if(len == 0 || nts == NULL || nt == NULL) { /* 400 Bad request : * The NT or NTS header field is missing * or the request is malformed. */ evhttp_send_reply(req, 400, "Bad Request", NULL); return; } debug_printf("SID=%s NTS=%s SEQ=%s\n", sid, nts, seq); if(sid == NULL || 0 != strcmp(sid, d->event_conn_sid) || 0 != strcmp(nt, "upnp:event") || 0 != strcmp(nts, "upnp:propchange")) { /* 412 Precondition Failed : * An SID does not correspond to a known, un-expired subscription * or the NT header field does not equal upnp:event * or the NTS header field does not equal upnp:propchange * or the SID header field is missing or empty. */ evhttp_send_reply(req, 412, "Precondition Failed", NULL); return; } xml_data = (char *)evbuffer_pullup(input_buffer, len); /*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) { int opt = 1; struct sockaddr_in addr; if(p == NULL || base == NULL) return UPNPC_ERR_INVALID_ARGS; memset(p, 0, sizeof(upnpc_t)); /* clean everything */ p->base = base; p->ready_cb = ready_cb; p->soap_cb = soap_cb; p->cb_data = cb_data; p->ttl = 2; /* open the socket for SSDP */ p->ssdp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if(p->ssdp_socket < 0) { return UPNPC_ERR_SOCKET_FAILED; } /* set multicast TTL */ if(setsockopt(p->ssdp_socket, IPPROTO_IP, IP_MULTICAST_TTL, &p->ttl, sizeof(p->ttl)) < 0) { /* not a fatal error */ debug_printf("setsockopt(%d, ..., IP_MULTICAST_TTL, ...) FAILED\n", p->ssdp_socket); } /* set REUSEADDR */ #ifdef _WIN32 if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0) { #else /* _WIN32 */ if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { #endif /* _WIN32 */ /* non fatal error ! */ debug_printf("setsockopt(%d, SOL_SOCKET, SO_REUSEADDR, ...) FAILED\n", p->ssdp_socket); } if(evutil_make_socket_nonblocking(p->ssdp_socket) < 0) { debug_printf("evutil_make_socket_nonblocking FAILED\n"); } /* receive address */ memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; /*addr.sin_port = htons(SSDP_PORT);*/ if(multicastif) { struct in_addr mc_if; mc_if.s_addr = inet_addr(multicastif); addr.sin_addr.s_addr = mc_if.s_addr; if(setsockopt(p->ssdp_socket, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { PRINT_SOCKET_ERROR("setsockopt"); /* non fatal error ! */ } } /* bind the socket to the ssdp address in order to receive responses */ if(bind(p->ssdp_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0) { close(p->ssdp_socket); return UPNPC_ERR_BIND_FAILED; } return UPNPC_OK; } int upnpc_start(upnpc_t * p) { struct timeval timeout; if(p == NULL || p->base == NULL) return UPNPC_ERR_INVALID_ARGS; /* event on SSDP */ p->ev_ssdp_recv = event_new(p->base, p->ssdp_socket, EV_READ|EV_PERSIST, (event_callback_fn)upnpc_receive_and_parse_ssdp, p); timeout.tv_sec = 3; timeout.tv_usec = 0; if(event_add(p->ev_ssdp_recv, &timeout)) { debug_printf("event_add FAILED\n"); } p->ev_ssdp_writable = event_new(p->base, p->ssdp_socket, EV_WRITE, (event_callback_fn)upnpc_send_ssdp_msearch, p); if(event_add(p->ev_ssdp_writable, NULL)) { debug_printf("event_add FAILED\n"); } return UPNPC_OK; } int upnpc_set_local_address(upnpc_t * p, const char * address, uint16_t port) { if(!p || !address) return UPNPC_ERR_INVALID_ARGS; p->local_address = strdup(address); /* TODO check error */ p->local_port = 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; free(d->root_desc_location); d->root_desc_location = NULL; free(d->control_cif_url); d->control_cif_url = NULL; free(d->event_cif_url); d->event_cif_url = NULL; free(d->cif_service_type); d->cif_service_type = NULL; free(d->control_conn_url); d->control_conn_url = NULL; free(d->event_conn_url); d->event_conn_url = NULL; free(d->conn_service_type); d->conn_service_type = NULL; if(d->desc_conn) { evhttp_connection_free(d->desc_conn); d->desc_conn = NULL; } if(d->soap_conn) { evhttp_connection_free(d->soap_conn); d->soap_conn = NULL; } ClearNameValueList(&d->soap_response_data); #ifdef ENABLE_UPNP_EVENTS free(d->event_conn_sid); d->event_conn_sid = NULL; #endif /* ENABLE_UPNP_EVENTS */ } int upnpc_finalize(upnpc_t * p) { if(!p) return UPNPC_ERR_INVALID_ARGS; p->discover_device_index = 0; if(p->ssdp_socket >= 0) { close(p->ssdp_socket); p->ssdp_socket = -1; } if(p->ev_ssdp_recv) { event_free(p->ev_ssdp_recv); p->ev_ssdp_recv = NULL; } if(p->ev_ssdp_writable) { event_free(p->ev_ssdp_writable); p->ev_ssdp_writable = NULL; } while(p->devices != NULL) { upnpc_device_t * d = p->devices; upnpc_device_finalize(d); 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_ext_method_cmp(p->parent->http_server, ext_methods_cb); 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); } evhttp_connection_set_ext_method_cmp(p->soap_conn, ext_methods_cb); 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", "OS/version UPnP/1.1 MiniUPnPc-libevent/2.2");*/ 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, p->conn_service_type/*"urn:schemas-upnp-org:service:WANIPConnection:1"*/, "GetExternalIPAddress", NULL, 0); } int upnpc_get_link_layer_max_rate(upnpc_device_t * p) { return upnpc_send_soap_request(p, p->control_cif_url, p->cif_service_type/*"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"*/, "GetCommonLinkProperties", NULL, 0); } int upnpc_delete_port_mapping(upnpc_device_t * p, const char * remote_host, unsigned short ext_port, const char * proto) { struct upnp_args args[3]; char ext_port_str[8]; if(proto == NULL || ext_port == 0) return UPNPC_ERR_INVALID_ARGS; snprintf(ext_port_str, sizeof(ext_port_str), "%hu", ext_port); args[0].elt = "NewRemoteHost"; args[0].val = remote_host?remote_host:""; args[1].elt = "NewExternalPort"; args[1].val = ext_port_str; args[2].elt = "NewProtocol"; args[2].val = proto; return upnpc_send_soap_request(p, p->control_conn_url, p->conn_service_type,/*"urn:schemas-upnp-org:service:WANIPConnection:1",*/ "DeletePortMapping", args, 3); } int upnpc_add_port_mapping(upnpc_device_t * p, const char * remote_host, unsigned short ext_port, unsigned short int_port, const char * int_client, const char * proto, const char * description, unsigned int lease_duration) { struct upnp_args args[8]; char lease_duration_str[16]; char int_port_str[8]; char ext_port_str[8]; if(int_client == NULL || int_port == 0 || ext_port == 0 || proto == NULL) return UPNPC_ERR_INVALID_ARGS; snprintf(lease_duration_str, sizeof(lease_duration_str), "%u", lease_duration); snprintf(int_port_str, sizeof(int_port_str), "%hu", int_port); snprintf(ext_port_str, sizeof(ext_port_str), "%hu", ext_port); args[0].elt = "NewRemoteHost"; args[0].val = remote_host?remote_host:""; args[1].elt = "NewExternalPort"; args[1].val = ext_port_str; args[2].elt = "NewProtocol"; args[2].val = proto; args[3].elt = "NewInternalPort"; args[3].val = int_port_str; args[4].elt = "NewInternalClient"; args[4].val = int_client; args[5].elt = "NewEnabled"; args[5].val = "1"; args[6].elt = "NewPortMappingDescription"; args[6].val = description?description:"miniupnpc-libevent"; args[7].elt = "NewLeaseDuration"; args[7].val = lease_duration_str; return upnpc_send_soap_request(p, p->control_conn_url, p->conn_service_type/*"urn:schemas-upnp-org:service:WANIPConnection:1"*/, "AddPortMapping", args, 8); } int upnpc_get_status_info(upnpc_device_t * p) { return upnpc_send_soap_request(p, p->control_conn_url, p->conn_service_type/*"urn:schemas-upnp-org:service:WANIPConnection:1"*/, "GetStatusInfo", NULL, 0); }