miniupnpc-async: work to support several devices.

This commit is contained in:
Thomas Bernard 2017-05-19 17:45:26 +02:00
parent e362e84e9e
commit 3e89381fba
3 changed files with 186 additions and 94 deletions

View File

@ -1,6 +1,6 @@
/* $Id: miniupnpc-async.c,v 1.19 2014/11/07 12:05:40 nanard Exp $ */
/* miniupnpc-async
* Copyright (c) 2008-2014, Thomas BERNARD <miniupnp@free.fr>
* Copyright (c) 2008-2017, Thomas BERNARD <miniupnp@free.fr>
* http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
*
* Permission to use, copy, modify, and/or distribute this software for any
@ -72,8 +72,8 @@ struct upnp_args {
/* private functions */
static int upnpc_connect(upnpc_t * p, const char * url);
static int upnpc_send_request(upnpc_t * p);
static int upnpc_connect(upnpc_device_t * p, const char * url);
static int upnpc_send_request(upnpc_device_t * p);
/* parse_msearch_reply()
@ -83,8 +83,8 @@ static int upnpc_send_request(upnpc_t * p);
* 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)
const char * * location, unsigned int * locationsize,
const char * * st, unsigned int * stsize)
{
int a, b, i;
i = 0; /* current character index */
@ -157,16 +157,16 @@ static int upnpc_send_ssdp_msearch(upnpc_t * p, const char * device, unsigned in
return 0;
}
static int upnpc_set_root_desc_location(upnpc_t * p, const char * location, int locationsize)
static int upnpc_set_root_desc_location(upnpc_device_t * d, const char * location, int locationsize)
{
char * tmp;
tmp = realloc(p->root_desc_location, locationsize + 1);
tmp = realloc(d->root_desc_location, locationsize + 1);
if(tmp == 0) {
return -1;
}
memcpy(tmp, location, locationsize);
tmp[locationsize] = '\0';
p->root_desc_location = tmp;
d->root_desc_location = tmp;
return 0;
}
@ -181,20 +181,38 @@ static int upnpc_receive_and_parse_ssdp(upnpc_t * p)
debug_printf("empty packet received\n");
} else {
const char * location = NULL;
int locationsize;
unsigned int locationsize;
const char * st = NULL;
int stsize;
unsigned int stsize;
debug_printf("%.*s", n, bufr);
parse_msearch_reply(bufr, n, &location, &locationsize, &st, &stsize);
debug_printf("location = '%.*s'\n", locationsize, location);
debug_printf("st = '%.*s'\n", stsize, st);
if(location != NULL) {
if(upnpc_set_root_desc_location(p, location, locationsize) < 0) {
upnpc_device_t * dev = p->device_list;
while(dev != NULL) {
if(dev->root_desc_location != NULL
&& strlen(dev->root_desc_location) == locationsize
&& memcmp(dev->root_desc_location, location, locationsize) == 0) {
debug_printf("device already in list (location='%s')\n", dev->root_desc_location);
return -1;
}
dev = dev->next;
}
dev = calloc(1, sizeof(upnpc_device_t));
if(dev == NULL) {
p->state = EError;
return -1;
}
p->state = EGetDescConnect;
upnpc_connect(p, p->root_desc_location);
if(upnpc_set_root_desc_location(dev, location, locationsize) < 0) {
free(dev);
p->state = EError;
return -1;
}
dev->next = p->device_list;
p->device_list = dev;
dev->state = EGetDescConnect;
upnpc_connect(dev, dev->root_desc_location);
} else {
/* or do nothing ? */
p->state = EError;
@ -298,7 +316,7 @@ parseURL(const char * url,
return 1;
}
static int upnpc_connect(upnpc_t * p, const char * url)
static int upnpc_connect(upnpc_device_t * p, const char * url)
{
int r;
char hostname[MAXHOSTNAMELEN+1];
@ -308,10 +326,10 @@ static int upnpc_connect(upnpc_t * p, const char * url)
struct sockaddr_in addr;
socklen_t addrlen;
if(p->root_desc_location == 0) {
/*if(p->root_desc_location == 0) {
p->state = EError;
return -1;
}
}*/
if(!parseURL(url/*p->root_desc_location*/, hostname, &port,
&path, &scope_id)) {
p->state = EError;
@ -353,7 +371,7 @@ static int upnpc_connect(upnpc_t * p, const char * url)
return 0;
}
static int upnpc_complete_connect(upnpc_t * p)
static int upnpc_complete_connect(upnpc_device_t * p)
{
socklen_t len;
int err;
@ -376,7 +394,7 @@ static int upnpc_complete_connect(upnpc_t * p)
return 0;
}
static int upnpc_send_request(upnpc_t * p)
static int upnpc_send_request(upnpc_device_t * p)
{
ssize_t n;
static const char reqfmt[] = "GET %s HTTP/1.1\r\n"
@ -440,7 +458,7 @@ static int upnpc_send_request(upnpc_t * p)
return 0;
}
static int upnpc_parse_headers(upnpc_t * p)
static int upnpc_parse_headers(upnpc_device_t * p)
{
/* search for CR LF CR LF (end of headers)
* recognize also LF LF */
@ -547,7 +565,7 @@ static char * build_url_string(const char * urlbase, const char * root_desc_url,
return s;
}
static int upnpc_get_response(upnpc_t * p)
static int upnpc_get_response(upnpc_device_t * p)
{
ssize_t n;
ssize_t count;
@ -643,7 +661,7 @@ static int upnpc_get_response(upnpc_t * p)
#define SERVICEPREFIX "u"
#define SERVICEPREFIX2 'u'
static int upnpc_build_soap_request(upnpc_t * p, const char * url,
static int upnpc_build_soap_request(upnpc_device_t * p, const char * url,
const char * service,
const char * action,
const struct upnp_args * args, int arg_count)
@ -789,30 +807,35 @@ int upnpc_init(upnpc_t * p, const char * multicastif)
int upnpc_finalize(upnpc_t * p)
{
if(!p) return UPNPC_ERR_INVALID_ARGS;
free(p->root_desc_location);
p->root_desc_location = NULL;
free(p->http_request);
p->http_request = NULL;
free(p->http_response);
p->http_response = NULL;
free(p->control_cif_url);
p->control_cif_url = NULL;
free(p->control_conn_url);
p->control_conn_url = NULL;
if(p->ssdp_socket >= 0) {
close(p->ssdp_socket);
p->ssdp_socket = -1;
}
if(p->http_socket >= 0) {
close(p->http_socket);
p->http_socket = -1;
while(p->device_list) {
upnpc_device_t * next = p->device_list->next;
free(p->device_list->root_desc_location);
p->device_list->root_desc_location = NULL;
free(p->device_list->http_request);
p->device_list->http_request = NULL;
free(p->device_list->http_response);
p->device_list->http_response = NULL;
free(p->device_list->control_cif_url);
p->device_list->control_cif_url = NULL;
free(p->device_list->control_conn_url);
p->device_list->control_conn_url = NULL;
if(p->device_list->http_socket >= 0) {
close(p->device_list->http_socket);
p->device_list->http_socket = -1;
}
ClearNameValueList(&p->device_list->soap_response_data);
free(p->device_list);
p->device_list = next;
}
ClearNameValueList(&p->soap_response_data);
p->state = EFinalized;
return UPNPC_OK;
}
int upnpc_get_external_ip_address(upnpc_t * p)
int upnpc_get_external_ip_address(upnpc_device_t * p)
{
upnpc_build_soap_request(p, p->control_conn_url,
"urn:schemas-upnp-org:service:WANIPConnection:1",
@ -822,7 +845,7 @@ int upnpc_get_external_ip_address(upnpc_t * p)
return 0;
}
int upnpc_get_link_layer_max_rate(upnpc_t * p)
int upnpc_get_link_layer_max_rate(upnpc_device_t * p)
{
upnpc_build_soap_request(p, p->control_cif_url,
"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
@ -832,7 +855,7 @@ int upnpc_get_link_layer_max_rate(upnpc_t * p)
return 0;
}
int upnpc_add_port_mapping(upnpc_t * p,
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,
@ -876,8 +899,32 @@ int upnpc_add_port_mapping(upnpc_t * p,
#ifdef UPNPC_USE_SELECT
int upnpc_select_fds(upnpc_t * p, int * nfds, fd_set * readfds, fd_set * writefds)
{
upnpc_device_t * d;
int n = 0;
if(!p) return UPNPC_ERR_INVALID_ARGS;
for(d = p->device_list; d != NULL; d = d->next) {
switch(d->state) {
case EGetDescConnect:
case EGetDescRequest:
case ESoapConnect:
case ESoapRequest:
FD_SET(d->http_socket, writefds);
if(*nfds < d->http_socket)
*nfds = d->http_socket;
n++;
break;
case EGetDescResponse:
case ESoapResponse:
FD_SET(d->http_socket, readfds);
if(*nfds < d->http_socket)
*nfds = d->http_socket;
n++;
break;
default:
break;
}
}
switch(p->state) {
case ESendSSDP:
FD_SET(p->ssdp_socket, writefds);
@ -886,32 +933,35 @@ int upnpc_select_fds(upnpc_t * p, int * nfds, fd_set * readfds, fd_set * writefd
n++;
break;
case EReceiveSSDP:
default:
/* still receive SSDP responses when processing Description, etc. */
FD_SET(p->ssdp_socket, readfds);
if(*nfds < p->ssdp_socket)
*nfds = p->ssdp_socket;
n++;
break;
case EGetDescConnect:
case EGetDescRequest:
case ESoapConnect:
case ESoapRequest:
FD_SET(p->http_socket, writefds);
if(*nfds < p->http_socket)
*nfds = p->http_socket;
n++;
break;
case EGetDescResponse:
case ESoapResponse:
FD_SET(p->http_socket, readfds);
if(*nfds < p->http_socket)
*nfds = p->http_socket;
n++;
break;
default:
return 0;
}
return n;
}
void upnpc_check_select_fds(upnpc_t * p, const fd_set * readfds, const fd_set * writefds)
{
upnpc_device_t * d;
p->socket_flags = 0;
if(FD_ISSET(p->ssdp_socket, readfds))
p->socket_flags = UPNPC_SSDP_READABLE;
if(FD_ISSET(p->ssdp_socket, writefds))
p->socket_flags = UPNPC_SSDP_WRITEABLE;
for(d = p->device_list; d != NULL; d = d->next) {
d->socket_flags = 0;
if(FD_ISSET(d->http_socket, readfds))
d->socket_flags = UPNPC_HTTP_READABLE;
if(FD_ISSET(d->http_socket, writefds))
d->socket_flags = UPNPC_HTTP_WRITEABLE;
}
}
#endif
static const char * devices_to_search[] = {
@ -924,6 +974,7 @@ static const char * devices_to_search[] = {
int upnpc_process(upnpc_t * p)
{
upnpc_device_t * d;
/*
1) Envoyer les paquets de discovery SSDP
2) Recevoir et traiter les reponses
@ -931,7 +982,36 @@ int upnpc_process(upnpc_t * p)
4) tester les etats
*/
if(!p) return UPNPC_ERR_INVALID_ARGS;
debug_printf("state=%d\n", (int)p->state);
debug_printf("state=%d socket_flags=0x%04x\n", (int)p->state, p->socket_flags);
for(d = p->device_list; d != NULL; d = d->next) {
switch(d->state) {
case EGetDescConnect:
case ESoapConnect:
upnpc_complete_connect(d);
break;
case EGetDescRequest:
case ESoapRequest:
upnpc_send_request(d);
break;
case EGetDescResponse:
case ESoapResponse:
upnpc_get_response(d);
break;
default:
break;
}
}
/* all devices ready => ready */
if(p->device_list != NULL) {
d = p->device_list;
while(d && d->state == EReady) d = d->next;
p->state = (d == NULL) ? EReady : EProcessing;
}
if(p->socket_flags & UPNPC_SSDP_READABLE) {
upnpc_receive_and_parse_ssdp(p);
}
switch(p->state) {
case EInit:
upnpc_send_ssdp_msearch(p, devices_to_search[0], 2);
@ -940,22 +1020,13 @@ int upnpc_process(upnpc_t * p)
upnpc_send_ssdp_msearch(p, devices_to_search[0], 2);
break;
case EReceiveSSDP:
upnpc_receive_and_parse_ssdp(p);
/*upnpc_receive_and_parse_ssdp(p);*/
break;
/*case EGetDesc:
upnpc_connect(p);
break;*/
case EGetDescConnect:
case ESoapConnect:
upnpc_complete_connect(p);
break;
case EGetDescRequest:
case ESoapRequest:
upnpc_send_request(p);
break;
case EGetDescResponse:
case ESoapResponse:
upnpc_get_response(p);
case EReady:
case EProcessing:
break;
default:
return UPNPC_ERR_UNKNOWN_STATE;

View File

@ -1,6 +1,6 @@
/* $Id: miniupnpc-async.h,v 1.13 2014/11/07 11:25:52 nanard Exp $ */
/* miniupnpc-async
* Copyright (c) 2008-2014, Thomas BERNARD <miniupnp@free.fr>
* Copyright (c) 2008-2017, Thomas BERNARD <miniupnp@free.fr>
* http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
*
* Permission to use, copy, modify, and/or distribute this software for any
@ -30,12 +30,15 @@ extern "C" {
#define UPNPC_ERR_BIND_FAILED (-3)
#define UPNPC_ERR_UNKNOWN_STATE (-4)
typedef struct {
#define UPNPC_SSDP_READABLE 0x0001
#define UPNPC_SSDP_WRITEABLE 0x0100
#define UPNPC_HTTP_READABLE 0x0002
#define UPNPC_HTTP_WRITEABLE 0x0200
typedef struct upnpc_device {
struct upnpc_device * next;
enum {
EInit = 1,
ESendSSDP,
EReceiveSSDP,
/*EGetDesc,*/
EGetDescConnect,
EGetDescRequest,
EGetDescResponse,
@ -46,9 +49,11 @@ typedef struct {
EFinalized = 99,
EError = 1000
} state;
int ssdp_socket;
char * root_desc_location;
char * control_cif_url;
char * control_conn_url;
int http_socket;
int socket_flags; /* see UPNPC_*_READABLE, etc. */
char * http_request;
int http_request_len;
int http_request_sent;
@ -58,20 +63,34 @@ typedef struct {
int http_response_content_length;
int http_response_chunked;
int http_response_code;
char * control_cif_url;
char * control_conn_url;
struct NameValueParserData soap_response_data;
} upnpc_device_t;
typedef struct {
enum {
EInit = 1,
ESendSSDP,
EReceiveSSDP,
EGetDesc,
EReady,
EProcessing,
EFinalized = 99,
EError = 1000
} state;
int socket_flags; /* see UPNPC_*_READABLE, etc. */
int ssdp_socket;
upnpc_device_t * device_list;
} upnpc_t;
int upnpc_init(upnpc_t * p, const char * multicastif);
int upnpc_finalize(upnpc_t * p);
int upnpc_get_external_ip_address(upnpc_t * p);
int upnpc_get_external_ip_address(upnpc_device_t * p);
int upnpc_get_link_layer_max_rate(upnpc_t * p);
int upnpc_get_link_layer_max_rate(upnpc_device_t * p);
int upnpc_add_port_mapping(upnpc_t * p,
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,
@ -79,6 +98,7 @@ int upnpc_add_port_mapping(upnpc_t * p,
#ifdef UPNPC_USE_SELECT
int upnpc_select_fds(upnpc_t * p, int * nfds, fd_set * readfds, fd_set * writefds);
void upnpc_check_select_fds(upnpc_t * p, const fd_set * readfds, const fd_set * writefds);
#endif /* UPNPC_USE_SELECT */
int upnpc_process(upnpc_t * p);

View File

@ -1,6 +1,6 @@
/* $Id: testasync.c,v 1.14 2014/11/07 12:07:38 nanard Exp $ */
/* miniupnpc-async
* Copyright (c) 2008-2014, Thomas BERNARD <miniupnp@free.fr>
* Copyright (c) 2008-2017, Thomas BERNARD <miniupnp@free.fr>
* http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
*
* Permission to use, copy, modify, and/or distribute this software for any
@ -43,7 +43,7 @@ int main(int argc, char * * argv)
}
r = upnpc_process(&upnp);
printf("upnpc_process returned %d\n", r);
while((upnp.state != EReady) && (upnp.state != EError)) {
while(upnp.state != EError) {
int nfds;
fd_set readfds;
fd_set writefds;
@ -68,6 +68,7 @@ int main(int argc, char * * argv)
perror("select");
return 1;
}
upnpc_check_select_fds(&upnp, &readfds, &writefds);
r = upnpc_process(&upnp);
#if DEBUG
printf("upnpc_process returned %d\n", r);
@ -76,18 +77,18 @@ int main(int argc, char * * argv)
break;
if(upnp.state == EReady) {
char * p;
printf("Process UPnP IGD Method results : HTTP %d\n", upnp.http_response_code);
if(upnp.http_response_code == 200) {
printf("Process UPnP IGD Method results : HTTP %d\n", upnp.device_list->http_response_code); /* XXX */
if(upnp.device_list->http_response_code == 200) {
switch(last_method) {
case EGetExternalIP:
p = GetValueFromNameValueList(&upnp.soap_response_data, "NewExternalIPAddress");
p = GetValueFromNameValueList(&upnp.device_list->soap_response_data, "NewExternalIPAddress");
printf("ExternalIPAddress = %s\n", p);
/* p = GetValueFromNameValueList(&pdata, "errorCode");*/
break;
case EGetRates:
p = GetValueFromNameValueList(&upnp.soap_response_data, "NewLayer1DownstreamMaxBitRate");
p = GetValueFromNameValueList(&upnp.device_list->soap_response_data, "NewLayer1DownstreamMaxBitRate");
printf("DownStream MaxBitRate = %s\t", p);
p = GetValueFromNameValueList(&upnp.soap_response_data, "NewLayer1UpstreamMaxBitRate");
p = GetValueFromNameValueList(&upnp.device_list->soap_response_data, "NewLayer1UpstreamMaxBitRate");
printf("UpStream MaxBitRate = %s\n", p);
break;
case EAddPortMapping:
@ -98,10 +99,10 @@ int main(int argc, char * * argv)
}
} else {
printf("SOAP error :\n");
printf(" faultcode='%s'\n", GetValueFromNameValueList(&upnp.soap_response_data, "faultcode"));
printf(" faultstring='%s'\n", GetValueFromNameValueList(&upnp.soap_response_data, "faultstring"));
printf(" errorCode=%s\n", GetValueFromNameValueList(&upnp.soap_response_data, "errorCode"));
printf(" errorDescription='%s'\n", GetValueFromNameValueList(&upnp.soap_response_data, "errorDescription"));
printf(" faultcode='%s'\n", GetValueFromNameValueList(&upnp.device_list->soap_response_data, "faultcode"));
printf(" faultstring='%s'\n", GetValueFromNameValueList(&upnp.device_list->soap_response_data, "faultstring"));
printf(" errorCode=%s\n", GetValueFromNameValueList(&upnp.device_list->soap_response_data, "errorCode"));
printf(" errorDescription='%s'\n", GetValueFromNameValueList(&upnp.device_list->soap_response_data, "errorDescription"));
}
if(next_method_to_call == ENothing)
break;
@ -110,17 +111,17 @@ int main(int argc, char * * argv)
switch(next_method_to_call) {
case EGetExternalIP:
printf("GetExternalIPAddress\n");
upnpc_get_external_ip_address(&upnp);
upnpc_get_external_ip_address(upnp.device_list);
next_method_to_call = EGetRates;
break;
case EGetRates:
printf("GetCommonLinkProperties\n");
upnpc_get_link_layer_max_rate(&upnp);
upnpc_get_link_layer_max_rate(upnp.device_list);
next_method_to_call = EAddPortMapping;
break;
case EAddPortMapping:
printf("AddPortMapping\n");
upnpc_add_port_mapping(&upnp,
upnpc_add_port_mapping(upnp.device_list, /* XXX */
NULL /* remote_host */, 40002 /* ext_port */,
42042 /* int_port */, "192.168.1.202" /* int_client */,
"TCP" /* proto */, "this is a test" /* description */,