Annotation of embedaddon/miniupnpd/miniupnpc-libevent/miniupnpc-libevent.c, revision 1.1
1.1 ! misho 1: /* $Id: miniupnpc-libevent.c,v 1.27 2015/07/22 13:51:09 nanard Exp $ */
! 2: /* miniupnpc-libevent
! 3: * Copyright (c) 2008-2016, Thomas BERNARD <miniupnp@free.fr>
! 4: * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
! 5: *
! 6: * Permission to use, copy, modify, and/or distribute this software for any
! 7: * purpose with or without fee is hereby granted, provided that the above
! 8: * copyright notice and this permission notice appear in all copies.
! 9: *
! 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
! 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
! 13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
! 17: #include <stdlib.h>
! 18: #include <string.h>
! 19: #include <sys/types.h>
! 20: #include <sys/socket.h>
! 21: #include <netinet/in.h>
! 22: #include <arpa/inet.h>
! 23: #include <net/if.h>
! 24: #include <stdio.h>
! 25: #include <event2/event.h>
! 26: #include <event2/buffer.h>
! 27: /*#include <event2/bufferevent.h>*/
! 28: #include <event2/http.h>
! 29: #ifdef _WIN32
! 30: #include <winsock2.h>
! 31: #include <ws2tcpip.h>
! 32: #include <io.h>
! 33: #define PRINT_SOCKET_ERROR printf
! 34: #define SOCKET_ERROR GetWSALastError()
! 35: #define WOULDBLOCK(err) (err == WSAEWOULDBLOCK)
! 36: #else /* _WIN32 */
! 37: #include <unistd.h>
! 38: #include <errno.h>
! 39: #define closesocket close
! 40: #define PRINT_SOCKET_ERROR perror
! 41: #define SOCKET_ERROR errno
! 42: #define WOULDBLOCK(err) (err == EAGAIN || err == EWOULDBLOCK)
! 43: #endif /* _WIN32 */
! 44: #include "miniupnpc-libevent.h"
! 45: #include "minixml.h"
! 46: #include "igd_desc_parse.h"
! 47: #include "upnpreplyparse.h"
! 48:
! 49: #ifndef MIN
! 50: #define MIN(x,y) (((x)<(y))?(x):(y))
! 51: #endif /* MIN */
! 52:
! 53: #ifndef MAXHOSTNAMELEN
! 54: #define MAXHOSTNAMELEN 64
! 55: #endif /* MAXHOSTNAMELEN */
! 56:
! 57: #define SSDP_PORT 1900
! 58: #define SSDP_MCAST_ADDR "239.255.255.250"
! 59: #define XSTR(s) STR(s)
! 60: #define STR(s) #s
! 61:
! 62: #ifdef DEBUG
! 63: #define debug_printf(...) fprintf(stderr, __VA_ARGS__)
! 64: #else
! 65: #define debug_printf(...) (void)0
! 66: #endif
! 67:
! 68: /* compare the beginning of a string with a constant string */
! 69: #define COMPARE(str, cstr) (0==memcmp(str, cstr, sizeof(cstr) - 1))
! 70:
! 71: /* stuctures */
! 72:
! 73: struct upnp_args {
! 74: const char * elt;
! 75: const char * val;
! 76: };
! 77:
! 78: /* private functions */
! 79:
! 80: static int upnpc_get_desc(upnpc_device_t * p, const char * url);
! 81: static char * build_url_string(const char * urlbase, const char * root_desc_url, const char * controlurl);
! 82:
! 83: /* data */
! 84: static const char * devices_to_search[] = {
! 85: "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
! 86: "urn:schemas-upnp-org:service:WANIPConnection:1",
! 87: "urn:schemas-upnp-org:service:WANPPPConnection:1",
! 88: "upnp:rootdevice",
! 89: 0
! 90: };
! 91:
! 92: #ifdef DEBUG
! 93: static void upnpc_conn_close_cb(struct evhttp_connection * conn, void * data)
! 94: {
! 95: upnpc_device_t * d = (upnpc_device_t *)data;
! 96: debug_printf("%s %p %p\n", __func__, conn, d);
! 97: }
! 98: #endif /* DEBUG */
! 99:
! 100: /* parse_msearch_reply()
! 101: * the last 4 arguments are filled during the parsing :
! 102: * - location/locationsize : "location:" field of the SSDP reply packet
! 103: * - st/stsize : "st:" field of the SSDP reply packet.
! 104: * The strings are NOT null terminated */
! 105: static void
! 106: parse_msearch_reply(const char * reply, int size,
! 107: const char * * location, int * locationsize,
! 108: const char * * st, int * stsize)
! 109: {
! 110: int a, b, i;
! 111: i = 0; /* current character index */
! 112: a = i; /* start of the line */
! 113: b = 0; /* end of the "header" (position of the colon) */
! 114: while(i<size) {
! 115: switch(reply[i]) {
! 116: case ':':
! 117: if(b==0) {
! 118: b = i; /* end of the "header" */
! 119: }
! 120: break;
! 121: case '\x0a':
! 122: case '\x0d':
! 123: if(b!=0) {
! 124: /* skip the colon and white spaces */
! 125: do { b++; } while(reply[b]==' ' && b<i);
! 126: if(0==strncasecmp(reply+a, "location:", 9)) {
! 127: *location = reply+b;
! 128: *locationsize = i-b;
! 129: } else if(0==strncasecmp(reply+a, "st:", 3)) {
! 130: *st = reply+b;
! 131: *stsize = i-b;
! 132: }
! 133: b = 0;
! 134: }
! 135: a = i+1;
! 136: break;
! 137: default:
! 138: break;
! 139: }
! 140: i++;
! 141: }
! 142: }
! 143:
! 144: static void upnpc_send_ssdp_msearch(evutil_socket_t s, short events, upnpc_t * p)
! 145: {
! 146: /* envoyer les packets de M-SEARCH discovery sur le socket ssdp */
! 147: int n;
! 148: char bufr[1024];
! 149: struct sockaddr_in addr;
! 150: unsigned int mx = 2;
! 151: static const char MSearchMsgFmt[] =
! 152: "M-SEARCH * HTTP/1.1\r\n"
! 153: "HOST: " SSDP_MCAST_ADDR ":" XSTR(SSDP_PORT) "\r\n"
! 154: "ST: %s\r\n"
! 155: "MAN: \"ssdp:discover\"\r\n"
! 156: "MX: %u\r\n"
! 157: "\r\n";
! 158: (void)p;
! 159: (void)events;
! 160:
! 161: memset(&addr, 0, sizeof(struct sockaddr_in));
! 162: addr.sin_family = AF_INET;
! 163: addr.sin_port = htons(SSDP_PORT);
! 164: addr.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
! 165: n = snprintf(bufr, sizeof(bufr),
! 166: MSearchMsgFmt, devices_to_search[p->discover_device_index++], mx);
! 167: debug_printf("%s: %s", __func__, bufr);
! 168: n = sendto(s, bufr, n, 0,
! 169: (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
! 170: if (n < 0) {
! 171: PRINT_SOCKET_ERROR("sendto");
! 172: }
! 173: }
! 174:
! 175: static int upnpc_set_root_desc_location(upnpc_device_t * d, const char * location, int locationsize)
! 176: {
! 177: char * tmp;
! 178: tmp = realloc(d->root_desc_location, locationsize + 1);
! 179: if(tmp == NULL) {
! 180: return -1;
! 181: }
! 182: memcpy(tmp, location, locationsize);
! 183: tmp[locationsize] = '\0';
! 184: d->root_desc_location = tmp;
! 185: return 0;
! 186: }
! 187:
! 188: static upnpc_device_t * upnpc_find_device_with_location(upnpc_t * p, const char * location, int locationsize)
! 189: {
! 190: upnpc_device_t * d;
! 191: for(d = p->devices; d != NULL; d = d->next) {
! 192: if(d->root_desc_location
! 193: && ((int)strlen(d->root_desc_location) == locationsize)
! 194: && (0 == memcmp(location, d->root_desc_location, locationsize)))
! 195: return d;
! 196: }
! 197: return NULL;
! 198: }
! 199:
! 200: static void upnpc_receive_and_parse_ssdp(evutil_socket_t s, short events, upnpc_t * p)
! 201: {
! 202: char bufr[2048];
! 203: ssize_t len;
! 204:
! 205: if(events == EV_TIMEOUT) {
! 206: /* nothing received ... */
! 207: debug_printf("%s() TIMEOUT\n", __func__);
! 208: if(!devices_to_search[p->discover_device_index]) {
! 209: debug_printf("*** NO MORE DEVICES TO SEARCH ***\n");
! 210: event_del(p->ev_ssdp_recv);
! 211: /* no device found : report error */
! 212: p->ready_cb(UPNPC_ERR_NO_DEVICE_FOUND, p, NULL, p->cb_data);
! 213: } else {
! 214: /* send another SSDP M-SEARCH packet */
! 215: if(event_add(p->ev_ssdp_writable, NULL)) {
! 216: debug_printf("event_add FAILED\n");
! 217: }
! 218: }
! 219: return;
! 220: }
! 221: len = recv(s, bufr, sizeof(bufr), 0);
! 222: debug_printf("input %d bytes\n", (int)len);
! 223: if(len < 0) {
! 224: PRINT_SOCKET_ERROR("recv");
! 225: } else if(len == 0) {
! 226: debug_printf("SSDP socket closed ?\n");
! 227: } else {
! 228: const char * location = NULL;
! 229: int locationsize = 0;
! 230: const char * st = NULL;
! 231: int stsize = 0;
! 232: debug_printf("%.*s", (int)len, bufr);
! 233: parse_msearch_reply(bufr, len, &location, &locationsize, &st, &stsize);
! 234: debug_printf("location = '%.*s'\n", locationsize, location);
! 235: debug_printf("st = '%.*s'\n", stsize, st);
! 236: if(location != NULL) {
! 237: upnpc_device_t * device;
! 238: device = upnpc_find_device_with_location(p, location, locationsize);
! 239: if(device) {
! 240: debug_printf("device already known\n");
! 241: } else {
! 242: device = malloc(sizeof(upnpc_device_t));
! 243: if(device == NULL) {
! 244: debug_printf("Memory allocation error\n");
! 245: return;
! 246: }
! 247: memset(device, 0, sizeof(upnpc_device_t));
! 248: device->parent = p;
! 249: device->next = p->devices;
! 250: p->devices = device;
! 251: if(upnpc_set_root_desc_location(device, location, locationsize) < 0) {
! 252: return;
! 253: }
! 254: if(upnpc_get_desc(device, device->root_desc_location)) {
! 255: debug_printf("FAILED to request device root description\n");
! 256: }
! 257: }
! 258: #if 0
! 259: event_del(p->ev_ssdp_recv); /* stop receiving SSDP responses */
! 260: #endif
! 261: } else {
! 262: /* or do nothing ? */
! 263: debug_printf("no location\n");
! 264: }
! 265: }
! 266: }
! 267:
! 268: static int
! 269: parseURL(const char * url,
! 270: char * hostname, unsigned short * port,
! 271: char * * path, unsigned int * scope_id)
! 272: {
! 273: char * p1, *p2, *p3;
! 274: if(!url)
! 275: return 0;
! 276: p1 = strstr(url, "://");
! 277: if(!p1)
! 278: return 0;
! 279: p1 += 3;
! 280: if( (url[0]!='h') || (url[1]!='t')
! 281: ||(url[2]!='t') || (url[3]!='p'))
! 282: return 0;
! 283: memset(hostname, 0, MAXHOSTNAMELEN + 1);
! 284: if(*p1 == '[') {
! 285: /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */
! 286: char * scope;
! 287: scope = strchr(p1, '%');
! 288: p2 = strchr(p1, ']');
! 289: if(p2 && scope && scope < p2 && scope_id) {
! 290: /* parse scope */
! 291: #ifdef IF_NAMESIZE
! 292: char tmp[IF_NAMESIZE];
! 293: int l;
! 294: scope++;
! 295: /* "%25" is just '%' in URL encoding */
! 296: if(scope[0] == '2' && scope[1] == '5')
! 297: scope += 2; /* skip "25" */
! 298: l = p2 - scope;
! 299: if(l >= IF_NAMESIZE)
! 300: l = IF_NAMESIZE - 1;
! 301: memcpy(tmp, scope, l);
! 302: tmp[l] = '\0';
! 303: *scope_id = if_nametoindex(tmp);
! 304: if(*scope_id == 0) {
! 305: *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
! 306: }
! 307: #else /* IF_NAMESIZE */
! 308: /* under windows, scope is numerical */
! 309: char tmp[8];
! 310: int l;
! 311: scope++;
! 312: /* "%25" is just '%' in URL encoding */
! 313: if(scope[0] == '2' && scope[1] == '5')
! 314: scope += 2; /* skip "25" */
! 315: l = p2 - scope;
! 316: if(l >= (int)sizeof(tmp))
! 317: l = sizeof(tmp) - 1;
! 318: memcpy(tmp, scope, l);
! 319: tmp[l] = '\0';
! 320: *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
! 321: #endif /* IF_NAMESIZE */
! 322: }
! 323: p3 = strchr(p1, '/');
! 324: if(p2 && p3) {
! 325: p2++;
! 326: strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
! 327: if(*p2 == ':') {
! 328: *port = 0;
! 329: p2++;
! 330: while( (*p2 >= '0') && (*p2 <= '9')) {
! 331: *port *= 10;
! 332: *port += (unsigned short)(*p2 - '0');
! 333: p2++;
! 334: }
! 335: } else {
! 336: *port = 80;
! 337: }
! 338: *path = p3;
! 339: return 1;
! 340: }
! 341: }
! 342: p2 = strchr(p1, ':');
! 343: p3 = strchr(p1, '/');
! 344: if(!p3)
! 345: return 0;
! 346: if(!p2 || (p2>p3)) {
! 347: strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1)));
! 348: *port = 80;
! 349: } else {
! 350: strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
! 351: *port = 0;
! 352: p2++;
! 353: while( (*p2 >= '0') && (*p2 <= '9')) {
! 354: *port *= 10;
! 355: *port += (unsigned short)(*p2 - '0');
! 356: p2++;
! 357: }
! 358: }
! 359: *path = p3;
! 360: return 1;
! 361: }
! 362:
! 363: static void upnpc_desc_received(struct evhttp_request * req, void * pvoid)
! 364: {
! 365: size_t len;
! 366: unsigned char * data;
! 367: struct evbuffer * input_buffer;
! 368: struct IGDdatas igd;
! 369: struct xmlparser parser;
! 370: upnpc_device_t * d = (upnpc_device_t *)pvoid;
! 371:
! 372: if(req == NULL) {
! 373: debug_printf("%s(%p, %p) NULL argument !\n", __func__, req, pvoid);
! 374: return;
! 375: }
! 376: input_buffer = evhttp_request_get_input_buffer(req);
! 377: len = evbuffer_get_length(input_buffer);
! 378: data = evbuffer_pullup(input_buffer, len);
! 379: debug_printf("%s %d (%d bytes)\n", __func__, evhttp_request_get_response_code(req), (int)len);
! 380: if(evhttp_request_get_response_code(req) != HTTP_OK) {
! 381: d->parent->ready_cb(evhttp_request_get_response_code(req), d->parent, d, d->parent->cb_data);
! 382: return;
! 383: }
! 384: if(data == NULL) {
! 385: d->parent->ready_cb(UPNPC_ERR_ROOT_DESC_ERROR, d->parent, d, d->parent->cb_data);
! 386: return;
! 387: }
! 388: debug_printf("%.*s\n", (int)len, (char *)data);
! 389:
! 390: memset(&igd, 0, sizeof(struct IGDdatas));
! 391: memset(&parser, 0, sizeof(struct xmlparser));
! 392: parser.xmlstart = (char *)data;
! 393: parser.xmlsize = len;
! 394: parser.data = &igd;
! 395: parser.starteltfunc = IGDstartelt;
! 396: parser.endeltfunc = IGDendelt;
! 397: parser.datafunc = IGDdata;
! 398: parsexml(&parser);
! 399: #ifdef DEBUG
! 400: printIGD(&igd);
! 401: #endif /* DEBUG */
! 402: d->control_conn_url = build_url_string(igd.urlbase, d->root_desc_location, igd.first.controlurl);
! 403: d->event_conn_url = build_url_string(igd.urlbase, d->root_desc_location, igd.first.eventsuburl);
! 404: d->conn_service_type = strdup(igd.first.servicetype);
! 405: d->control_cif_url = build_url_string(igd.urlbase, d->root_desc_location, igd.CIF.controlurl);
! 406: d->event_cif_url = build_url_string(igd.urlbase, d->root_desc_location, igd.CIF.eventsuburl);
! 407: d->cif_service_type = strdup(igd.CIF.servicetype);
! 408: debug_printf("control_conn_url='%s'\n (service_type='%s')\n",
! 409: d->control_conn_url, d->conn_service_type);
! 410: debug_printf("event_conn_url='%s'\n", d->event_conn_url);
! 411: debug_printf("control_cif_url='%s'\n (service_type='%s')\n",
! 412: d->control_cif_url, d->cif_service_type);
! 413:
! 414: if((d->cif_service_type == NULL)
! 415: || (d->cif_service_type[0] == '\0')
! 416: || (!COMPARE(d->cif_service_type, "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:"))) {
! 417: d->parent->ready_cb(UPNPC_ERR_NOT_IGD, d->parent, d, d->parent->cb_data);
! 418: } else {
! 419: d->state |= UPNPC_DEVICE_GETSTATUS;
! 420: upnpc_get_status_info(d);
! 421: }
! 422: }
! 423:
! 424: #ifdef ENABLE_UPNP_EVENTS
! 425: static void upnpc_subscribe_response(struct evhttp_request * req, void * pvoid)
! 426: {
! 427: size_t len;
! 428: unsigned char * data;
! 429: struct evbuffer * input_buffer;
! 430: upnpc_device_t * d = (upnpc_device_t *)pvoid;
! 431:
! 432: if(req == NULL) {
! 433: debug_printf("%s(%p, %p) NULL argument !\n", __func__, req, pvoid);
! 434: return;
! 435: }
! 436: input_buffer = evhttp_request_get_input_buffer(req);
! 437: len = evbuffer_get_length(input_buffer);
! 438: data = evbuffer_pullup(input_buffer, len);
! 439: debug_printf("%s %d (%d bytes)\n", __func__, evhttp_request_get_response_code(req), (int)len);
! 440: d->state &= ~UPNPC_DEVICE_SOAP_REQ;
! 441: if(evhttp_request_get_response_code(req) != HTTP_OK) {
! 442: /* TODO ERROR */
! 443: } else {
! 444: const char * sid;
! 445: struct evkeyvalq * headers = evhttp_request_get_input_headers(req);
! 446: sid = evhttp_find_header(headers, "sid");
! 447: debug_printf("SID=%s\n", sid);
! 448: if(sid) {
! 449: if(d->event_conn_sid)
! 450: free(d->event_conn_sid);
! 451: d->event_conn_sid = strdup(sid);
! 452: }
! 453: }
! 454: }
! 455: #endif /* ENABLE_UPNP_EVENTS */
! 456:
! 457: static void upnpc_soap_response(struct evhttp_request * req, void * pvoid)
! 458: {
! 459: size_t len;
! 460: unsigned char * data;
! 461: struct evbuffer * input_buffer;
! 462: upnpc_device_t * d = (upnpc_device_t *)pvoid;
! 463: int code;
! 464:
! 465: if(req == NULL) {
! 466: debug_printf("%s(%p, %p) NULL argument !\n", __func__, req, pvoid);
! 467: return;
! 468: }
! 469: code = evhttp_request_get_response_code(req);
! 470: input_buffer = evhttp_request_get_input_buffer(req);
! 471: len = evbuffer_get_length(input_buffer);
! 472: data = evbuffer_pullup(input_buffer, len);
! 473: debug_printf("%s %d (%d bytes)\n", __func__, code, (int)len);
! 474: debug_printf("%.*s\n", (int)len, (char *)data);
! 475: if(data == NULL)
! 476: return;
! 477:
! 478: ClearNameValueList(&d->soap_response_data);
! 479: ParseNameValue((char *)data, (int)len,
! 480: &d->soap_response_data);
! 481: d->state &= ~UPNPC_DEVICE_SOAP_REQ;
! 482: if(d->state & UPNPC_DEVICE_READY) {
! 483: d->parent->soap_cb(code, d->parent, d, d->parent->cb_data);
! 484: } else if(d->state & UPNPC_DEVICE_GETSTATUS) {
! 485: const char * connection_status;
! 486: d->state &= ~UPNPC_DEVICE_GETSTATUS;
! 487: connection_status = GetValueFromNameValueList(&d->soap_response_data, "NewConnectionStatus");
! 488: d->state |= UPNPC_DEVICE_READY;
! 489: if((code == 200) && connection_status && (0 == strcmp("Connected", connection_status))) {
! 490: d->parent->ready_cb(code, d->parent, d, d->parent->cb_data);
! 491: d->state |= UPNPC_DEVICE_CONNECTED;
! 492: event_del(d->parent->ev_ssdp_recv);
! 493: } else {
! 494: d->parent->ready_cb(UPNPC_ERR_NOT_CONNECTED, d->parent, d, d->parent->cb_data);
! 495: }
! 496: }
! 497: }
! 498:
! 499: static int upnpc_get_desc(upnpc_device_t * d, const char * url)
! 500: {
! 501: char hostname[MAXHOSTNAMELEN+1];
! 502: char hostname_port[MAXHOSTNAMELEN+1+6];
! 503: unsigned short port;
! 504: char * path;
! 505: unsigned int scope_id;
! 506: struct evhttp_request * req;
! 507: struct evkeyvalq * headers;
! 508:
! 509: /* if(d->root_desc_location == NULL) {
! 510: return -1;
! 511: } */
! 512: if(!parseURL(url/*d->root_desc_location*/, hostname, &port,
! 513: &path, &scope_id)) {
! 514: return -1;
! 515: }
! 516: if(port != 80)
! 517: snprintf(hostname_port, sizeof(hostname_port), "%s:%hu", hostname, port);
! 518: else
! 519: strncpy(hostname_port, hostname, sizeof(hostname_port));
! 520: if(d->desc_conn == NULL) {
! 521: d->desc_conn = evhttp_connection_base_new(d->parent->base, NULL, hostname, port);
! 522: }
! 523: #ifdef DEBUG
! 524: evhttp_connection_set_closecb(d->desc_conn, upnpc_conn_close_cb, d);
! 525: #endif /* DEBUG */
! 526: /*evhttp_connection_set_timeout(p->desc_conn, 600);*/
! 527: req = evhttp_request_new(upnpc_desc_received/*callback*/, d);
! 528: headers = evhttp_request_get_output_headers(req);
! 529: evhttp_add_header(headers, "Host", hostname_port);
! 530: evhttp_add_header(headers, "Connection", "close");
! 531: /*evhttp_add_header(headers, "User-Agent", "***");*/
! 532: return evhttp_make_request(d->desc_conn, req, EVHTTP_REQ_GET, path);
! 533: }
! 534:
! 535: static char * build_url_string(const char * urlbase, const char * root_desc_url, const char * controlurl)
! 536: {
! 537: int l, n;
! 538: char * s;
! 539: const char * base;
! 540: char * p;
! 541: /* if controlurl is an absolute url, return it */
! 542: if(0 == memcmp("http://", controlurl, 7))
! 543: return strdup(controlurl);
! 544: base = (urlbase[0] == '\0') ? root_desc_url : urlbase;
! 545: n = strlen(base);
! 546: if(n > 7) {
! 547: p = strchr(base + 7, '/');
! 548: if(p)
! 549: n = p - base;
! 550: }
! 551: l = n + strlen(controlurl) + 1;
! 552: if(controlurl[0] != '/')
! 553: l++;
! 554: s = malloc(l);
! 555: if(s == NULL) return NULL;
! 556: memcpy(s, base, n);
! 557: if(controlurl[0] != '/')
! 558: s[n++] = '/';
! 559: memcpy(s + n, controlurl, l - n);
! 560: return s;
! 561: }
! 562:
! 563: #define SOAPPREFIX "s"
! 564: #define SERVICEPREFIX "u"
! 565: #define SERVICEPREFIX2 'u'
! 566:
! 567: static int upnpc_send_soap_request(upnpc_device_t * p, const char * url,
! 568: const char * service,
! 569: const char * method,
! 570: const struct upnp_args * args, int arg_count)
! 571: {
! 572: char action[128];
! 573: char * body;
! 574: const char fmt_soap[] =
! 575: "<?xml version=\"1.0\"?>\r\n"
! 576: "<" SOAPPREFIX ":Envelope "
! 577: "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" "
! 578: SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
! 579: "<" SOAPPREFIX ":Body>"
! 580: "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">"
! 581: "%s"
! 582: "</" SERVICEPREFIX ":%s>"
! 583: "</" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>"
! 584: "\r\n";
! 585: int body_len;
! 586: char hostname[MAXHOSTNAMELEN+1];
! 587: char hostname_port[MAXHOSTNAMELEN+1+6];
! 588: unsigned short port;
! 589: char * path;
! 590: unsigned int scope_id;
! 591: char * args_xml = NULL;
! 592: struct evhttp_request * req;
! 593: struct evkeyvalq * headers;
! 594: struct evbuffer * buffer;
! 595:
! 596: if(p->state & UPNPC_DEVICE_SOAP_REQ) {
! 597: debug_printf("%s: another SOAP request in progress\n", __func__);
! 598: return UPNPC_ERR_REQ_IN_PROGRESS;
! 599: }
! 600:
! 601: if(arg_count > 0) {
! 602: int i;
! 603: size_t l, n;
! 604: for(i = 0, l = 0; i < arg_count; i++) {
! 605: /* <ELT>VAL</ELT> */
! 606: l += strlen(args[i].elt) * 2 + strlen(args[i].val) + 5;
! 607: }
! 608: args_xml = malloc(++l);
! 609: if(args_xml == NULL) {
! 610: return -1;
! 611: }
! 612: for(i = 0, n = 0; i < arg_count && n < l; i++) {
! 613: /* <ELT>VAL</ELT> */
! 614: n += snprintf(args_xml + n, l - n, "<%s>%s</%s>",
! 615: args[i].elt, args[i].val, args[i].elt);
! 616: }
! 617: }
! 618:
! 619: body_len = snprintf(NULL, 0, fmt_soap, method, service, args_xml?args_xml:"", method);
! 620: body = malloc(body_len + 1);
! 621: if(body == NULL) {
! 622: free(args_xml);
! 623: return -1;
! 624: }
! 625: if(snprintf(body, body_len + 1, fmt_soap, method, service, args_xml?args_xml:"", method) != body_len) {
! 626: debug_printf("%s: snprintf() returned strange value...\n", __func__);
! 627: }
! 628: free(args_xml);
! 629: args_xml = NULL;
! 630: if(!parseURL(url, hostname, &port, &path, &scope_id)) {
! 631: free(body);
! 632: return -1;
! 633: }
! 634: if(port != 80)
! 635: snprintf(hostname_port, sizeof(hostname_port), "%s:%hu", hostname, port);
! 636: else
! 637: strncpy(hostname_port, hostname, sizeof(hostname_port));
! 638: snprintf(action, sizeof(action), "\"%s#%s\"", service, method);
! 639: if(p->soap_conn == NULL) {
! 640: p->soap_conn = evhttp_connection_base_new(p->parent->base, NULL, hostname, port);
! 641: }
! 642: req = evhttp_request_new(upnpc_soap_response, p);
! 643: headers = evhttp_request_get_output_headers(req);
! 644: buffer = evhttp_request_get_output_buffer(req);
! 645: evhttp_add_header(headers, "Host", hostname_port);
! 646: evhttp_add_header(headers, "SOAPAction", action);
! 647: evhttp_add_header(headers, "Content-Type", "text/xml");
! 648: /*evhttp_add_header(headers, "User-Agent", "***");*/
! 649: /*evhttp_add_header(headers, "Cache-Control", "no-cache");*/
! 650: /*evhttp_add_header(headers, "Pragma", "no-cache");*/
! 651: evbuffer_add(buffer, body, body_len);
! 652: evhttp_make_request(p->soap_conn, req, EVHTTP_REQ_POST, path);
! 653: free(body);
! 654: p->state |= UPNPC_DEVICE_SOAP_REQ;
! 655: return 0;
! 656: }
! 657:
! 658: #ifdef ENABLE_UPNP_EVENTS
! 659: #define EVHTTP_REQ_NOTIFY ((EVHTTP_REQ_MAX) << 1)
! 660: #define EVHTTP_REQ_SUBSCRIBE ((EVHTTP_REQ_NOTIFY) << 1)
! 661: #define EVHTTP_REQ_UNSUBSCRIBE ((EVHTTP_REQ_SUBSCRIBE) << 1)
! 662: static int ext_methods_cb(struct evhttp_ext_method *p)
! 663: {
! 664: if(p == NULL)
! 665: return -1;
! 666: if(p->method != NULL) {
! 667: if(strcmp(p->method, "NOTIFY") == 0) {
! 668: p->type = EVHTTP_REQ_NOTIFY;
! 669: p->flags = EVHTTP_METHOD_HAS_BODY;
! 670: } else if(strcmp(p->method, "SUBSCRIBE") == 0) {
! 671: p->type = EVHTTP_REQ_SUBSCRIBE;
! 672: } else if(strcmp(p->method, "UNSUBSCRIBE") == 0) {
! 673: p->type = EVHTTP_REQ_UNSUBSCRIBE;
! 674: } else {
! 675: return -1;
! 676: }
! 677: } else switch(p->type) {
! 678: case EVHTTP_REQ_NOTIFY:
! 679: p->method = "NOTIFY";
! 680: p->flags = EVHTTP_METHOD_HAS_BODY;
! 681: break;
! 682: case EVHTTP_REQ_SUBSCRIBE:
! 683: p->method = "SUBSCRIBE";
! 684: break;
! 685: case EVHTTP_REQ_UNSUBSCRIBE:
! 686: p->method = "UNSUBSCRIBE";
! 687: break;
! 688: default:
! 689: return -1;
! 690: }
! 691: return 0;
! 692: };
! 693:
! 694: void upnpc_event_conn_req(struct evhttp_request * req, void * data)
! 695: {
! 696: size_t len;
! 697: char * xml_data;
! 698: struct evbuffer * input_buffer;
! 699: struct evkeyvalq * headers;
! 700: const char * sid;
! 701: const char * nts;
! 702: const char * nt;
! 703: const char * seq;
! 704: struct NameValueParserData parsed_data;
! 705: struct NameValue * nv;
! 706: upnpc_device_t * d = (upnpc_device_t *)data;
! 707:
! 708: debug_printf("%s(%p, %p)\n", __func__, req, d);
! 709: headers = evhttp_request_get_input_headers(req);
! 710: input_buffer = evhttp_request_get_input_buffer(req);
! 711: len = evbuffer_get_length(input_buffer);
! 712: sid = evhttp_find_header(headers, "sid");
! 713: nts = evhttp_find_header(headers, "nts");
! 714: nt = evhttp_find_header(headers, "nt");
! 715: seq = evhttp_find_header(headers, "seq");
! 716: if(len == 0 || nts == NULL || nt == NULL) {
! 717: /* 400 Bad request :
! 718: * The NT or NTS header field is missing
! 719: * or the request is malformed. */
! 720: evhttp_send_reply(req, 400, "Bad Request", NULL);
! 721: return;
! 722: }
! 723: debug_printf("SID=%s NTS=%s SEQ=%s\n", sid, nts, seq);
! 724: if(sid == NULL || 0 != strcmp(sid, d->event_conn_sid)
! 725: || 0 != strcmp(nt, "upnp:event") || 0 != strcmp(nts, "upnp:propchange")) {
! 726: /* 412 Precondition Failed :
! 727: * An SID does not correspond to a known, un-expired subscription
! 728: * or the NT header field does not equal upnp:event
! 729: * or the NTS header field does not equal upnp:propchange
! 730: * or the SID header field is missing or empty. */
! 731: evhttp_send_reply(req, 412, "Precondition Failed", NULL);
! 732: return;
! 733: }
! 734: xml_data = (char *)evbuffer_pullup(input_buffer, len);
! 735: /*debug_printf("%.*s\n", len, xml_data);*/
! 736: ParseNameValue(xml_data, len, &parsed_data);
! 737: for(nv = parsed_data.l_head; nv != NULL; nv = nv->l_next) {
! 738: if(d->parent->value_changed_cb) {
! 739: d->parent->value_changed_cb(d->parent, d, d->parent->cb_data, d->conn_service_type, nv->name, nv->value);
! 740: } else {
! 741: debug_printf("%s=%s\n", nv->name, nv->value);
! 742: }
! 743: }
! 744: ClearNameValueList(&parsed_data);
! 745: /* response : 200 OK */
! 746: evhttp_send_reply(req, 200, "OK", NULL);
! 747: }
! 748: #endif /* ENABLE_UPNP_EVENTS */
! 749:
! 750: /* public functions */
! 751: int upnpc_init(upnpc_t * p, struct event_base * base, const char * multicastif,
! 752: upnpc_callback_fn ready_cb, upnpc_callback_fn soap_cb, void * cb_data)
! 753: {
! 754: int opt = 1;
! 755: struct sockaddr_in addr;
! 756:
! 757: if(p == NULL || base == NULL)
! 758: return UPNPC_ERR_INVALID_ARGS;
! 759: memset(p, 0, sizeof(upnpc_t)); /* clean everything */
! 760: p->base = base;
! 761: p->ready_cb = ready_cb;
! 762: p->soap_cb = soap_cb;
! 763: p->cb_data = cb_data;
! 764: p->ttl = 2;
! 765: /* open the socket for SSDP */
! 766: p->ssdp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
! 767: if(p->ssdp_socket < 0) {
! 768: return UPNPC_ERR_SOCKET_FAILED;
! 769: }
! 770: /* set multicast TTL */
! 771: if(setsockopt(p->ssdp_socket, IPPROTO_IP, IP_MULTICAST_TTL, &p->ttl, sizeof(p->ttl)) < 0)
! 772: {
! 773: /* not a fatal error */
! 774: debug_printf("setsockopt(%d, ..., IP_MULTICAST_TTL, ...) FAILED\n", p->ssdp_socket);
! 775: }
! 776: /* set REUSEADDR */
! 777: #ifdef _WIN32
! 778: if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0) {
! 779: #else /* _WIN32 */
! 780: if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
! 781: #endif /* _WIN32 */
! 782: /* non fatal error ! */
! 783: debug_printf("setsockopt(%d, SOL_SOCKET, SO_REUSEADDR, ...) FAILED\n", p->ssdp_socket);
! 784: }
! 785: if(evutil_make_socket_nonblocking(p->ssdp_socket) < 0) {
! 786: debug_printf("evutil_make_socket_nonblocking FAILED\n");
! 787: }
! 788:
! 789: /* receive address */
! 790: memset(&addr, 0, sizeof(struct sockaddr_in));
! 791: addr.sin_family = AF_INET;
! 792: addr.sin_addr.s_addr = INADDR_ANY;
! 793: /*addr.sin_port = htons(SSDP_PORT);*/
! 794:
! 795: if(multicastif) {
! 796: struct in_addr mc_if;
! 797: mc_if.s_addr = inet_addr(multicastif);
! 798: addr.sin_addr.s_addr = mc_if.s_addr;
! 799: if(setsockopt(p->ssdp_socket, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) {
! 800: PRINT_SOCKET_ERROR("setsockopt");
! 801: /* non fatal error ! */
! 802: }
! 803: }
! 804:
! 805: /* bind the socket to the ssdp address in order to receive responses */
! 806: if(bind(p->ssdp_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0) {
! 807: close(p->ssdp_socket);
! 808: return UPNPC_ERR_BIND_FAILED;
! 809: }
! 810: return UPNPC_OK;
! 811: }
! 812:
! 813: int upnpc_start(upnpc_t * p)
! 814: {
! 815: struct timeval timeout;
! 816: if(p == NULL || p->base == NULL)
! 817: return UPNPC_ERR_INVALID_ARGS;
! 818: /* event on SSDP */
! 819: p->ev_ssdp_recv = event_new(p->base, p->ssdp_socket,
! 820: EV_READ|EV_PERSIST,
! 821: (event_callback_fn)upnpc_receive_and_parse_ssdp, p);
! 822: timeout.tv_sec = 3;
! 823: timeout.tv_usec = 0;
! 824: if(event_add(p->ev_ssdp_recv, &timeout)) {
! 825: debug_printf("event_add FAILED\n");
! 826: }
! 827: p->ev_ssdp_writable = event_new(p->base, p->ssdp_socket,
! 828: EV_WRITE,
! 829: (event_callback_fn)upnpc_send_ssdp_msearch, p);
! 830: if(event_add(p->ev_ssdp_writable, NULL)) {
! 831: debug_printf("event_add FAILED\n");
! 832: }
! 833: return UPNPC_OK;
! 834: }
! 835:
! 836: int upnpc_set_local_address(upnpc_t * p, const char * address, uint16_t port)
! 837: {
! 838: if(!p || !address) return UPNPC_ERR_INVALID_ARGS;
! 839: p->local_address = strdup(address); /* TODO check error */
! 840: p->local_port = port;
! 841: return UPNPC_OK;
! 842: }
! 843:
! 844: #ifdef ENABLE_UPNP_EVENTS
! 845: int upnpc_set_event_callback(upnpc_t * p, upnpc_event_callback_fn cb)
! 846: {
! 847: if(!p || !cb) return UPNPC_ERR_INVALID_ARGS;
! 848: p->value_changed_cb = cb;
! 849: return UPNPC_OK;
! 850: }
! 851: #endif /* ENABLE_UPNP_EVENTS */
! 852:
! 853: static void upnpc_device_finalize(upnpc_device_t * d)
! 854: {
! 855: d->state = 0;
! 856: free(d->root_desc_location);
! 857: d->root_desc_location = NULL;
! 858: free(d->control_cif_url);
! 859: d->control_cif_url = NULL;
! 860: free(d->event_cif_url);
! 861: d->event_cif_url = NULL;
! 862: free(d->cif_service_type);
! 863: d->cif_service_type = NULL;
! 864: free(d->control_conn_url);
! 865: d->control_conn_url = NULL;
! 866: free(d->event_conn_url);
! 867: d->event_conn_url = NULL;
! 868: free(d->conn_service_type);
! 869: d->conn_service_type = NULL;
! 870: if(d->desc_conn) {
! 871: evhttp_connection_free(d->desc_conn);
! 872: d->desc_conn = NULL;
! 873: }
! 874: if(d->soap_conn) {
! 875: evhttp_connection_free(d->soap_conn);
! 876: d->soap_conn = NULL;
! 877: }
! 878: ClearNameValueList(&d->soap_response_data);
! 879: #ifdef ENABLE_UPNP_EVENTS
! 880: free(d->event_conn_sid);
! 881: d->event_conn_sid = NULL;
! 882: #endif /* ENABLE_UPNP_EVENTS */
! 883: }
! 884:
! 885: int upnpc_finalize(upnpc_t * p)
! 886: {
! 887: if(!p) return UPNPC_ERR_INVALID_ARGS;
! 888: p->discover_device_index = 0;
! 889: if(p->ssdp_socket >= 0) {
! 890: close(p->ssdp_socket);
! 891: p->ssdp_socket = -1;
! 892: }
! 893: if(p->ev_ssdp_recv) {
! 894: event_free(p->ev_ssdp_recv);
! 895: p->ev_ssdp_recv = NULL;
! 896: }
! 897: if(p->ev_ssdp_writable) {
! 898: event_free(p->ev_ssdp_writable);
! 899: p->ev_ssdp_writable = NULL;
! 900: }
! 901: while(p->devices != NULL) {
! 902: upnpc_device_t * d = p->devices;
! 903: upnpc_device_finalize(d);
! 904: p->devices = d->next;
! 905: free(d);
! 906: }
! 907: free(p->local_address);
! 908: p->local_address = NULL;
! 909: #ifdef ENABLE_UPNP_EVENTS
! 910: if(p->http_server) {
! 911: evhttp_free(p->http_server);
! 912: p->http_server = NULL;
! 913: }
! 914: #endif /* ENABLE_UPNP_EVENTS */
! 915: return UPNPC_OK;
! 916: }
! 917:
! 918: #ifdef ENABLE_UPNP_EVENTS
! 919: int upnpc_event_subscribe(upnpc_device_t * p)
! 920: {
! 921: char hostname[MAXHOSTNAMELEN+1];
! 922: char hostname_port[MAXHOSTNAMELEN+1+6];
! 923: unsigned short port;
! 924: char * path;
! 925: unsigned int scope_id;
! 926: struct evhttp_request * req;
! 927: struct evkeyvalq * headers;
! 928: char callback_header[7+15+1+5+9+2+1];
! 929:
! 930: if(p->parent->http_server == NULL) {
! 931: /* HTTP server to receive event notifications */
! 932: p->parent->http_server = evhttp_new(p->parent->base);
! 933: if(p->parent->http_server == NULL) {
! 934: debug_printf("evhttp_new() FAILED\n");
! 935: return -1;
! 936: }
! 937: evhttp_set_ext_method_cmp(p->parent->http_server, ext_methods_cb);
! 938: evhttp_set_allowed_methods(p->parent->http_server, EVHTTP_REQ_NOTIFY);
! 939: evhttp_set_cb(p->parent->http_server, "/evt_conn", upnpc_event_conn_req, p);
! 940: if(evhttp_bind_socket(p->parent->http_server, p->parent->local_address, p->parent->local_port) < 0) {
! 941: debug_printf("evhttp_bind_socket() FAILED\n");
! 942: return -1;
! 943: }
! 944: }
! 945: /*if(!parseURL(p->event_cif_url, hostname, &port, &path, &scope_id)) {*/
! 946: if(!parseURL(p->event_conn_url, hostname, &port, &path, &scope_id)) {
! 947: return -1;
! 948: }
! 949: if(port != 80)
! 950: snprintf(hostname_port, sizeof(hostname_port), "%s:%hu", hostname, port);
! 951: else
! 952: strncpy(hostname_port, hostname, sizeof(hostname_port));
! 953: if(p->soap_conn == NULL) {
! 954: p->soap_conn = evhttp_connection_base_new(p->parent->base, NULL, hostname, port);
! 955: }
! 956: evhttp_connection_set_ext_method_cmp(p->soap_conn, ext_methods_cb);
! 957: req = evhttp_request_new(upnpc_subscribe_response, p);
! 958: headers = evhttp_request_get_output_headers(req);
! 959: /*buffer = evhttp_request_get_output_buffer(req);*/
! 960: evhttp_add_header(headers, "Host", hostname_port);
! 961: /*evhttp_add_header(headers, "User-Agent", "***");*/
! 962: snprintf(callback_header, sizeof(callback_header), "<http://%s:%hu/evt_conn>", p->parent->local_address, p->parent->local_port);
! 963: evhttp_add_header(headers, "Callback", callback_header);
! 964: evhttp_add_header(headers, "NT", "upnp:event");
! 965: /*evhttp_add_header(headers, "NTS", "");*/
! 966: evhttp_add_header(headers, "Timeout", "3600");
! 967: /*evbuffer_add(buffer, body, body_len);*/
! 968: evhttp_make_request(p->soap_conn, req, EVHTTP_REQ_SUBSCRIBE, path);
! 969: p->state |= UPNPC_DEVICE_SOAP_REQ;
! 970: return 0;
! 971: }
! 972: #endif /* ENABLE_UPNP_EVENTS */
! 973:
! 974: int upnpc_get_external_ip_address(upnpc_device_t * p)
! 975: {
! 976: return upnpc_send_soap_request(p, p->control_conn_url,
! 977: p->conn_service_type/*"urn:schemas-upnp-org:service:WANIPConnection:1"*/,
! 978: "GetExternalIPAddress", NULL, 0);
! 979: }
! 980:
! 981: int upnpc_get_link_layer_max_rate(upnpc_device_t * p)
! 982: {
! 983: return upnpc_send_soap_request(p, p->control_cif_url,
! 984: p->cif_service_type/*"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"*/,
! 985: "GetCommonLinkProperties", NULL, 0);
! 986: }
! 987:
! 988: int upnpc_delete_port_mapping(upnpc_device_t * p,
! 989: const char * remote_host, unsigned short ext_port,
! 990: const char * proto)
! 991: {
! 992: struct upnp_args args[3];
! 993: char ext_port_str[8];
! 994:
! 995: if(proto == NULL || ext_port == 0)
! 996: return UPNPC_ERR_INVALID_ARGS;
! 997: snprintf(ext_port_str, sizeof(ext_port_str), "%hu", ext_port);
! 998: args[0].elt = "NewRemoteHost";
! 999: args[0].val = remote_host?remote_host:"";
! 1000: args[1].elt = "NewExternalPort";
! 1001: args[1].val = ext_port_str;
! 1002: args[2].elt = "NewProtocol";
! 1003: args[2].val = proto;
! 1004: return upnpc_send_soap_request(p, p->control_conn_url,
! 1005: p->conn_service_type,/*"urn:schemas-upnp-org:service:WANIPConnection:1",*/
! 1006: "DeletePortMapping",
! 1007: args, 3);
! 1008: }
! 1009:
! 1010: int upnpc_add_port_mapping(upnpc_device_t * p,
! 1011: const char * remote_host, unsigned short ext_port,
! 1012: unsigned short int_port, const char * int_client,
! 1013: const char * proto, const char * description,
! 1014: unsigned int lease_duration)
! 1015: {
! 1016: struct upnp_args args[8];
! 1017: char lease_duration_str[16];
! 1018: char int_port_str[8];
! 1019: char ext_port_str[8];
! 1020:
! 1021: if(int_client == NULL || int_port == 0 || ext_port == 0 || proto == NULL)
! 1022: return UPNPC_ERR_INVALID_ARGS;
! 1023: snprintf(lease_duration_str, sizeof(lease_duration_str), "%u", lease_duration);
! 1024: snprintf(int_port_str, sizeof(int_port_str), "%hu", int_port);
! 1025: snprintf(ext_port_str, sizeof(ext_port_str), "%hu", ext_port);
! 1026: args[0].elt = "NewRemoteHost";
! 1027: args[0].val = remote_host?remote_host:"";
! 1028: args[1].elt = "NewExternalPort";
! 1029: args[1].val = ext_port_str;
! 1030: args[2].elt = "NewProtocol";
! 1031: args[2].val = proto;
! 1032: args[3].elt = "NewInternalPort";
! 1033: args[3].val = int_port_str;
! 1034: args[4].elt = "NewInternalClient";
! 1035: args[4].val = int_client;
! 1036: args[5].elt = "NewEnabled";
! 1037: args[5].val = "1";
! 1038: args[6].elt = "NewPortMappingDescription";
! 1039: args[6].val = description?description:"miniupnpc-libevent";
! 1040: args[7].elt = "NewLeaseDuration";
! 1041: args[7].val = lease_duration_str;
! 1042: return upnpc_send_soap_request(p, p->control_conn_url,
! 1043: p->conn_service_type/*"urn:schemas-upnp-org:service:WANIPConnection:1"*/,
! 1044: "AddPortMapping",
! 1045: args, 8);
! 1046: }
! 1047:
! 1048: int upnpc_get_status_info(upnpc_device_t * p)
! 1049: {
! 1050: return upnpc_send_soap_request(p, p->control_conn_url,
! 1051: p->conn_service_type/*"urn:schemas-upnp-org:service:WANIPConnection:1"*/,
! 1052: "GetStatusInfo", NULL, 0);
! 1053: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>