Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

2254 lines
76 KiB
C

/*H********************************************************************************/
/*!
\File protoupnp.c
\Description
Implements a simple UPnP client, designed specifically to talk to a UPnP
router and open up a firewall port for peer-peer communication with a
remote client.
\Notes
ProtoUpnp implementation was based on the following documents:
References:
[1] UPnP Device Architecture: http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf
[2] UPnP Resources: http://upnp.org/sdcps-and-certification/resources/
[3] Intel Software for UPnP Technology: http://software.intel.com/en-us/articles/intel-software-for-upnp-technology-download-tools/
[4] Internet Gateway Device v1.0: http://upnp.org/specs/gw/igd1/
[5] SOAP 1.2: http://www.w3.org/TR/2003/REC-soap12-part0-20030624/
[6] UPnP compatibility list (old): http://ccgi.mgillespie.plus.com/upnp/test.php
In addition, many thanks to the Burnout3 team, whose UPnP implementation
was used as reference.
UPnP Optional Features:
[a] - Finite Lease Duration
[b] - Wildcard in Source IP (Remote Host)
[c] - Wildcard in External Port
[d] - Non-matching Internal and External Ports
[e] - Specific Remote Host
[f] - Specific External Port
Tested against the following routers:
<1> Linksys WRT54G 3.03.06 [b,c,d,e,f]
<2> Linksys BEFSR41 [b,c?,d,e,f] [c? because GetSpecificPortMapping with wc port fails)
<3> Netgear WGR614v6 1.0.8 [b,d,f]
Common UPnP SOAP errors we have seen in usage:
402: Invalid Args
403: Undefined
501: Action Failed
713: SpecifiedArrayIndexInvalid (GetGenericPortMapping return code, but we don't use that method?)
714: NoSuchEntryInArray (GetSpecificPortMapping/DeletePortMapping return code, should not be fatal)
716: WildCardNotPermittedInExtPort (AddPortMapping return code, but we don't ask for a wildcard external port?)
718: ConflictInMappingEntry (AddPortMapping return code)
\Copyright
Copyright (c) 2005 Electronic Arts Inc.
\Version 03/23/2005 (jbrookes) First Version
*/
/********************************************************************************H*/
/*** Include files ****************************************************************/
#include <string.h>
#include "DirtySDK/dirtysock.h"
#include "DirtySDK/dirtysock/dirtymem.h"
#include "DirtySDK/dirtysock/netconn.h"
#include "DirtySDK/proto/protohttp.h"
#include "DirtySDK/xml/xmlparse.h"
#include "DirtySDK/proto/protoupnp.h"
/*** Defines **********************************************************************/
//! define this to allow reuse of a preexisting mapping that matches what we want
#define PROTOUPNP_REUSEMAPPING (FALSE)
//! define this to use WAN IP address as the source address in the port map if a remote host is unspecified
#define PROTOMANGLE_USEWANIPSRCADDR (FALSE)
//! define this to use verbose variable descriptions in requests
#define PROTOUPNP_FULLVARDESC (FALSE)
//! addr to send discovery requests to (239.255.255.250)
#define PROTOUPNP_DISCOVERYADDR (0xEFFFFFFA)
//! port to send discovery requests to
#define PROTOUPNP_DISCOVERYPORT (1900)
//! interval at which we send out discovery request broadcasts
#define PROTOUPNP_DISCOVERYINTERVAL (15000)
//! maximum number of services parsed
#define PROTOUPNP_MAXSERVICES (5)
//! default port to map
#define PROTOUPNP_DEFAULTPORTMAP (3658)
/*** Macros ***********************************************************************/
//! add a soap item of type string
#define _ProtoUpnpSoapRequestAddStr(_pUpnp, _pName, _pData) _ProtoUpnpSoapRequestAdd(_pUpnp, _pName, "string", _pData)
//! add a soap item of type ui2
#define _ProtoUpnpSoapRequestAddUI2(_pUpnp, _pName, _uData) _ProtoUpnpSoapRequestAdd(_pUpnp, _pName, "ui2", _ProtoUpnpIntToStr(_uData))
//! add a soap item of type ui4
#define _ProtoUpnpSoapRequestAddUI4(_pUpnp, _pName, _uData) _ProtoUpnpSoapRequestAdd(_pUpnp, _pName, "ui4", _ProtoUpnpIntToStr(_uData))
//! add a soap item of type boolean
#define _ProtoUpnpSoapRequestAddBln(_pUpnp, _pName, _uData) _ProtoUpnpSoapRequestAdd(_pUpnp, _pName, "boolean", _ProtoUpnpIntToStr(_uData))
/*** Type Definitions *************************************************************/
//! port mapping description
typedef struct ProtoUpnpPortMapT
{
int32_t iAddr;
int32_t iPort;
int32_t iLeaseDuration;
char strDesc[31];
uint8_t bEnabled;
} ProtoUpnpPortMapT;
//! upnp service info
typedef struct ProtoUpnpServiceT
{
char strServiceType[64]; //!< service type description
char strDescriptionUrl[128]; //!< service description URL
char strControlUrl[256]; //!< service control url
} ProtoUpnpServiceT;
//! upnp device info
typedef struct ProtoUpnpDeviceT
{
char strUrl[128]; //!< string holding device description url
char strUrlBase[64]; //!< base url, with addr/port info
char strUdn[64]; //!< device universal device name
char strModel[127]; //!< device manufacturer and model
uint8_t bDiscovered; //!< TRUE if we've discovered a UPnP device
uint32_t uExternalAddress; //!< external address of device
ProtoUpnpPortMapT CurPortMap; //!< current port mapping
int32_t iNumServices; //!< number of services device supports
ProtoUpnpServiceT Services[PROTOUPNP_MAXSERVICES]; //!< list of services device supports
} ProtoUpnpDeviceT;
//! module state
struct ProtoUpnpRefT
{
int32_t iRefCount; //!< module ref count
// module memory group
int32_t iMemGroup; //!< module mem group id
void *pMemGroupUserData; //!< user data associated with mem group
SocketT *pUdp; //!< udp socket, used for discovery process
ProtoHttpRefT *pProtoHttp; //!< http module, used for device communication
int32_t iClientAddr; //!< local address
int32_t iDiscoveryTimer; //!< timer used to time discovery sends
struct sockaddr DiscoveryAddr; //!< address to send discovery to
int32_t iStatus; //!< current status (PROTOUPNP_STATUS_*)
int32_t iService; //!< current service index
ProtoUpnpDeviceT Device;
enum
{
ST_IDLE, //!< idle state
ST_DISCOVERY, //!< UPnP discovery
ST_DESCRIPTION, //!< UPnP description
ST_SERVICEDESC, //!< service description
ST_GETSTATEVAR, //!< QueryStateVariable request
ST_GETEXTADDR, //!< GetExternalAddress request
ST_GETPORTMAPPING, //!< GetSpecificPortMapping request
ST_DELPORTMAPPING, //!< DeletePortMapping request
ST_ADDPORTMAPPING, //!< AddPortMapping request
ST_LAST, //!< last state
} eState;
int32_t iSendSize; //!< size of current send buffer
int32_t iSentBytes; //!< amount of data that we have sent
int32_t iHttpError; //!< most recent ProtoHttpRecv() error, if any
int32_t iSoapError; //!< SOAP error
int32_t iRemoteHost; //!< address of remote host
int32_t iPortToMapExt; //!< external port we are trying to map
int32_t iPortToMapInt; //!< internal port we are trying to map
int32_t iLeaseDuration; //!< lease duration to try and set
const ProtoUpnpMacroT *pCommandList; //!< pointer to current command list, if any
uint8_t bRequestInProgress; //!< TRUE if a device request is in progress
uint8_t bEnableMapping; //!< TRUE to enable mapping, else FALSE
uint8_t bPortIsMapped; //!< TRUE if port is already mapped
uint8_t bVerbose; //!< TRUE to set verbose mode, else FALSE
#if DIRTYCODE_DEBUG
uint8_t bFakeResponse; //!< TRUE if faking a response, else FALSE
#endif
char strRequestName[64]; //!< name of current request
char strSendBuf[2048]; //!< buffer to construct posts in
char strRecvBuf[16*1024]; //!< buffer to receive into
NetCritT Crit; //!< critical section
};
/*** Variables ********************************************************************/
/*! protoupnp description, sent in soap requests. note that this string is deliberately
short because some implementations (e.g. BEFSR41) only store a small number of
characters (e.g. 11) for the mapping description */
static const char _ProtoUpnp_strDescription[] = "EA Tunnel";
//! protoupnp macro to check for and get the upnp information
static const ProtoUpnpMacroT _ProtoUpnp_cmd_dscg[] =
{
{ 'disc', 0, 0, NULL }, // discovery
{ 'desc', 0, 0, NULL }, // get router description
{ 'gadr', 0, 0, NULL }, // GetExternalIPAddress
{ 0, 0, 0, NULL }
};
//! protoupnp macro to add a port mapping
static const ProtoUpnpMacroT _ProtoUpnp_cmd_addp[] =
{
{ 'gprt', 0, 0, NULL }, // GetSpecificPortMapping
{ 'aprt', 0, 0, NULL }, // AddPortMapping
{ 0, 0, 0, NULL }
};
//! protoupnp macro to discover, describe, and check port mapping
static const ProtoUpnpMacroT _ProtoUpnp_cmd_dscp[] =
{
{ 'disc', 0, 0, NULL }, // discovery
{ 'desc', 0, 0, NULL }, // get router description
{ 'gadr', 0, 0, NULL }, // GetExternalIPAddress
{ 'gprt', 0, 0, NULL }, // GetSpecificPortMapping
{ 0, 0, 0, NULL }
};
//! protoupnp macro to discover, describe, and add port map
static const ProtoUpnpMacroT _ProtoUpnp_cmd_upnp[] =
{
{ 'disc', 0, 0, NULL }, // discovery
{ 'desc', 0, 0, NULL }, // get router description
{ 'gadr', 0, 0, NULL }, // GetExternalIPAddress
{ 'gprt', 0, 0, NULL }, // GetSpecificPortMapping
{ 'aprt', 0, 0, NULL }, // AddPortMapping
{ 0, 0, 0, NULL }
};
//! protoupnp macro to fully test router
static const ProtoUpnpMacroT _ProtoUpnp_cmd_test[] =
{
// initial test is basically the 'addp' macro plus service description
{ 'disc', 0, 0, NULL },
{ 'desc', 0, 0, NULL },
{ 'sdsc', 0, 0, NULL },
{ 'gadr', 0, 0, NULL },
{ 'gprt', 0, 0, NULL },
{ 'aprt', 0, 0, NULL },
{ 'dprt', 0, 0, NULL },
// test Wildcard in Source IP optional feature
{ 'host', 0, 0, NULL }, // set host to use wildcard
{ 'aprt', 0, 0, NULL },
{ 'gprt', 0, 0, NULL },
{ 'dprt', 0, 0, NULL },
{ 'host', -1, 0, NULL }, // restore host
// test Wildcard in External Port optional feature
{ 'extp', 0, 0, NULL },
{ 'aprt', 0, 0, NULL },
{ 'gprt', 0, 0, NULL },
{ 'dprt', 0, 0, NULL },
// test Non-matching Internal and External Ports optional feature
{ 'extp', 3000, 0, NULL },
{ 'aprt', 0, 0, NULL },
{ 'gprt', 0, 0, NULL },
{ 'dprt', 0, 0, NULL },
// done
{ 0, 0, 0, NULL }
};
//! map states to control request idents
static uint32_t _ProtoUpnp_aStateMap[] =
{
'idle', // ST_IDLE
'disc', // ST_DISCOVERY
'desc', // ST_DESCRIPTION
'sdsc', // ST_SERVICEDESC
'gvar', // ST_GETSTATEVAR
'gadr', // ST_GETEXTADDR
'gprt', // ST_GETPORTMAPPING
'dprt', // ST_DELPORTMAPPING
'aprt', // ST_ADDPORTMAPPING
};
//! upnp module ref
static ProtoUpnpRefT *_ProtoUpnp_pRef = NULL;
/*** Private Functions ************************************************************/
/*F********************************************************************************/
/*!
\Function _ProtoUpnpIntToStr
\Description
Convert an integer to a string, and return a pointer to the string.
\Input iVal - value to convert to string
\Output
const char *- pointer to string
\Version 03/30/2005 (jbrookes)
*/
/********************************************************************************F*/
static const char *_ProtoUpnpIntToStr(int32_t iVal)
{
static char _strVal[16];
ds_snzprintf(_strVal, sizeof(_strVal), "%d", iVal);
return(_strVal);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpFindUrl
\Description
Find the url following the http://host:port/ portion of a url.
\Input *pUrl - pointer to source url to parse
\Output
const char * - pointer to url or NULL
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
static const char *_ProtoUpnpFindUrl(const char *pUrl)
{
// make sure it's http (or https)
if ((pUrl = ds_stristr(pUrl, "http")) == NULL)
{
return(NULL);
}
// skip past double slash
if ((pUrl = ds_stristr(pUrl, "//")) == NULL)
{
return(NULL);
}
pUrl += 2;
// next slash is the start of the url
return(strchr(pUrl, '/'));
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpMakeFullUrl
\Description
Create a full url for the given device.
\Input *pDevice - pointer to device
\Input *pBuffer - [out] storage for full url
\Input iBufSize - size of output buffer
\Input *pUrl - pointer to input url (which may be relative or absolute)
\Version 11/07/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpMakeFullUrl(ProtoUpnpDeviceT *pDevice, char *pBuffer, int iBufSize, const char *pUrl)
{
// clear output url buffer
ds_memclr(pBuffer, iBufSize);
// prepend url base if we weren't given an absolute url
if (ds_strnicmp(pUrl, "http", 4))
{
ds_strnzcpy(pBuffer, pDevice->strUrlBase, iBufSize);
// if relative url does not start with a slash, add one here
if (*pUrl != '/')
{
NetPrintf(("protoupnp: relative url does not start with a forward slash; adding one\n"));
ds_strnzcat(pBuffer, "/", iBufSize);
}
}
ds_strnzcat(pBuffer, pUrl, iBufSize);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpReset
\Description
Reset module state
\Input *pProtoUpnp - pointer to module state
\Version 07/14/2009 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpReset(ProtoUpnpRefT *pProtoUpnp)
{
// init device structure
ds_memclr(&pProtoUpnp->Device, sizeof(pProtoUpnp->Device));
// force immediate discovery broadcast
pProtoUpnp->iDiscoveryTimer = NetTick() - PROTOUPNP_DISCOVERYINTERVAL;
// clear status
pProtoUpnp->iStatus = 0;
// reset service index
pProtoUpnp->iService = 0;
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpError
\Description
Handle error condition
\Input *pProtoUpnp - pointer to module state
\Input *pReason - reason for error
\Version 10/07/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpError(ProtoUpnpRefT *pProtoUpnp, const char *pReason)
{
// abort current command list?
//pProtoUpnp->pCommandList = NULL;
// reset to idle state
pProtoUpnp->eState = ST_IDLE;
// output reason to debug output
NetPrintf(("protoupnp: error -- %s\n", pReason));
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpSendDiscoveryRequest
\Description
Send a discovery request
\Input *pProtoUpnp - pointer to module state
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpSendDiscoveryRequest(ProtoUpnpRefT *pProtoUpnp)
{
/*! HTTP-formatted packet to send in UDP discovery broadcast
[1] 1.2.2 Discovery: Search: Request with M-SEARCH */
static const char _strDiscoveryPacket[] =
"M-SEARCH * HTTP/1.1\r\n" \
"Host:239.255.255.250:1900\r\n" \
"ST:urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" \
"Man:\"ssdp:discover\"\r\n" \
"MX:3\r\n" \
"\r\n";
// send discovery multicast
#if DIRTYCODE_LOGGING
if (pProtoUpnp->bVerbose)
{
NetPrintf(("protoupnp: multicasting discovery request\n"));
}
#endif
SocketSendto(pProtoUpnp->pUdp, _strDiscoveryPacket, sizeof(_strDiscoveryPacket), 0,
&pProtoUpnp->DiscoveryAddr, sizeof(pProtoUpnp->DiscoveryAddr));
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpParseDiscoveryResponse
\Description
Parse a discovery response
\Input *pProtoUpnp - pointer to module state
\Input *pResponse - pointer to response
\Output
int32_t - zero=failure, else success
\Notes
The discovery response is an HTTP response in a UDP packet. Since
ProtoHttp does not support UDP, we do the parsing inline here.
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpParseDiscoveryResponse(ProtoUpnpRefT *pProtoUpnp, const char *pResponse)
{
const char strLocation[] = "Location:";
const char *pLocation, *pUrl;
int32_t iStrLen;
// echo response to debug output
#if DIRTYCODE_LOGGING
if (pProtoUpnp->bVerbose)
{
NetPrintf(("protoupnp: received discovery response:\n"));
NetPrintWrap(pProtoUpnp->strRecvBuf, 80);
}
#endif
// make sure we have a valid header
if (strncmp(pResponse, "HTTP", 4))
{
NetPrintf(("protoupnp: ignoring non-http response\n"));
return(0);
}
/*! check for substring that must be included in a valid discovery response
[1] 1.2.3 Discovery: Search: Response */
if (!ds_stristr(pResponse, "urn:schemas-upnp-org:device:wanconnectiondevice"))
{
NetPrintf(("protoupnp: ignoring non-discovery response\n"));
return(0);
}
// find and skip location header
if ((pLocation = ds_stristr(pResponse, strLocation)) == NULL)
{
NetPrintf(("protoupnp: response did not include a location header\n"));
return(0);
}
pLocation += sizeof(strLocation) - 1;
// skip to location body
while((*pLocation != '\0') && (*pLocation <= ' '))
{
pLocation += 1;
}
// copy url, leaving room for null termination
for (iStrLen = 0; iStrLen < (signed)(sizeof(pProtoUpnp->Device.strUrl) - 1); iStrLen++)
{
if ((*pLocation == '\0') || (*pLocation == '\r') || (*pLocation == '\n'))
{
break;
}
pProtoUpnp->Device.strUrl[iStrLen] = *pLocation++;
}
// null terminate url
pProtoUpnp->Device.strUrl[iStrLen] = '\0';
NetPrintf(("protoupnp: found upnp device '%s'\n", pProtoUpnp->Device.strUrl));
// extract address/port
if ((pUrl = _ProtoUpnpFindUrl(pProtoUpnp->Device.strUrl)) != NULL)
{
ds_strsubzcpy(pProtoUpnp->Device.strUrlBase, sizeof(pProtoUpnp->Device.strUrlBase), pProtoUpnp->Device.strUrl, (int32_t)(pUrl-pProtoUpnp->Device.strUrl));
NetPrintf(("protoupnp: parsed base url '%s'\n", pProtoUpnp->Device.strUrlBase));
}
// mark device as valid and set status
pProtoUpnp->Device.bDiscovered = TRUE;
// reset to idle state
pProtoUpnp->eState = ST_IDLE;
return(1);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpGetRemoteHost
\Description
Get string-formatted address for remote host used in UPnP service requests.
\Input *pProtoUpnp - pointer to module state
\Input *pBuffer - buffer
\Input iBufSize - buffer size
\Version 11/07/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpGetRemoteHost(ProtoUpnpRefT *pProtoUpnp, char *pBuffer, int32_t iBufSize)
{
uint32_t uRemoteHost;
// get remote host address
#if PROTOMANGLE_USEWANIPSRCADDR
uRemoteHost = (pProtoUpnp->iRemoteHost == -1) ? pProtoUpnp->Device.uExternalAddress : (unsigned)pProtoUpnp->iRemoteHost;
#else
uRemoteHost = (pProtoUpnp->iRemoteHost == -1) ? 0 : (unsigned)pProtoUpnp->iRemoteHost;
#endif
// return formatted host string
if (uRemoteHost != 0)
{
SocketInAddrGetText(uRemoteHost, pBuffer, iBufSize);
}
else
{
*pBuffer = '\0';
}
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpHttpReset
\Description
Reset HTTP-tracking variables prior to initiating an HTTP transaction.
\Input *pProtoUpnp - pointer to module state
\Version 10/10/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpHttpReset(ProtoUpnpRefT *pProtoUpnp)
{
pProtoUpnp->iHttpError = 0;
pProtoUpnp->iSoapError = 0;
pProtoUpnp->bRequestInProgress = TRUE;
ds_memclr(pProtoUpnp->strRecvBuf, sizeof(pProtoUpnp->strRecvBuf));
if (pProtoUpnp->pProtoHttp != NULL)
{
ProtoHttpControl(pProtoUpnp->pProtoHttp, 'keep', 0, 0, NULL);
}
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpHttpWaitResponse
\Description
Wait for an HTTP response.
\Input *pProtoUpnp - pointer to module state
\Output
int32_t - negative=failure, zero=in progress, positve=success
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpHttpWaitResponse(ProtoUpnpRefT *pProtoUpnp)
{
int32_t iRecvResult;
// if faking a response, data is already in buffer waiting for us
#if DIRTYCODE_DEBUG
if (pProtoUpnp->bFakeResponse)
{
pProtoUpnp->bFakeResponse = FALSE;
pProtoUpnp->bRequestInProgress = FALSE;
pProtoUpnp->eState = ST_IDLE;
return(2);
}
#endif
// give some time to ProtoHttp
ProtoHttpUpdate(pProtoUpnp->pProtoHttp);
// send any data that is still needed to be sent
if (pProtoUpnp->iSentBytes < pProtoUpnp->iSendSize)
{
int32_t iResult;
if ((iResult = ProtoHttpSend(pProtoUpnp->pProtoHttp, pProtoUpnp->strSendBuf+pProtoUpnp->iSentBytes, pProtoUpnp->iSendSize-pProtoUpnp->iSentBytes)) >= 0)
{
pProtoUpnp->iSentBytes += iResult;
}
else
{
NetPrintf(("protoupnp: %s request send failed for service %s with result %d\n", pProtoUpnp->strRequestName,
pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType, iResult));
pProtoUpnp->iHttpError = iResult;
pProtoUpnp->bRequestInProgress = FALSE;
pProtoUpnp->eState = ST_IDLE;
pProtoUpnp->iSentBytes = 0;
return(-1);
}
}
// read data
if ((iRecvResult = ProtoHttpRecvAll(pProtoUpnp->pProtoHttp, pProtoUpnp->strRecvBuf, sizeof(pProtoUpnp->strRecvBuf))) >= 0)
{
// echo response to debug output
#if DIRTYCODE_LOGGING
if (pProtoUpnp->bVerbose)
{
NetPrintf(("\n"));
NetPrintWrap(pProtoUpnp->strRecvBuf, 80);
}
#endif
// reset transaction state
pProtoUpnp->bRequestInProgress = FALSE;
pProtoUpnp->eState = ST_IDLE;
pProtoUpnp->iSentBytes = 0;
// check for zero-length response (has been shown to happen in some Windows ICS error responses)
if (iRecvResult == 0)
{
return(-1);
}
// success
return(1);
}
else if ((iRecvResult < 0) && (iRecvResult != PROTOHTTP_RECVWAIT))
{
NetPrintf(("protoupnp: %s request receive failed for service %s with result %d\n", pProtoUpnp->strRequestName,
pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType, iRecvResult));
pProtoUpnp->iHttpError = iRecvResult;
pProtoUpnp->bRequestInProgress = FALSE;
pProtoUpnp->eState = ST_IDLE;
pProtoUpnp->iSentBytes = 0;
return(-1);
}
// return pending result
return(0);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpSoapFormatRequestHeader
\Description
Format SOAP header for request given by pRequestName, using the currently
active service.
\Input *pProtoUpnp - pointer to module state
\Input *pRequestName - pointer to request name
\Version 03/27/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpSoapFormatRequestHeader(ProtoUpnpRefT *pProtoUpnp, const char *pRequestName)
{
char strHdrExtra[256];
// set up extended header fields
ds_snzprintf(strHdrExtra, sizeof(strHdrExtra),
"Content-Type: text/xml; charset=\"utf-8\"\r\n"
"SOAPAction: \"%s#%s\"\r\n"
"Cache-Control: no-cache\r\n",
pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType,
pRequestName);
// tell ProtoHttp to use this for the extended header info
ProtoHttpControl(pProtoUpnp->pProtoHttp, 'apnd', 0, 0, strHdrExtra);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpSoapRequestOpen
\Description
Format a SOAP request based on pRequestName and the given variable-argument
list that constructs the SOAP request body, for the currently active
service.
\Input *pProtoUpnp - pointer to module state
\Input *pRequestName - pointer to request name
\Version 03/30/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpSoapRequestOpen(ProtoUpnpRefT *pProtoUpnp, const char *pRequestName)
{
char *pBuf = pProtoUpnp->strSendBuf;
int32_t iBufSize = sizeof(pProtoUpnp->strSendBuf);
// set up the header
_ProtoUpnpSoapFormatRequestHeader(pProtoUpnp, pRequestName);
// set up soap envelope start
pProtoUpnp->iSendSize = ds_snzprintf(pBuf, iBufSize,
"<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n"
" <s:Body>\r\n"
" <u:%s xmlns:u=\"%s\">\r\n",
pRequestName,
pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType);
// save request name
ds_strnzcpy(pProtoUpnp->strRequestName, pRequestName, sizeof(pProtoUpnp->strRequestName));
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpSoapRequestAdd
\Description
Add a value to a soap request. This function should not be used directly,
the _ProtoUpnpSoapRequestAddStr, AddUI2, AddUI4, and AddBln macros should be
used instead.
\Input *pProtoUpnp - pointer to module state
\Input *pName - pointer to item name
\Input *pType - pointer to item type
\Input *pData - pointer to item data
\Version 03/30/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpSoapRequestAdd(ProtoUpnpRefT *pProtoUpnp, const char *pName, const char *pType, const char *pData)
{
char *pBuf = pProtoUpnp->strSendBuf + pProtoUpnp->iSendSize;
int32_t iBufSize = sizeof(pProtoUpnp->strSendBuf) - pProtoUpnp->iSendSize;
#if PROTOUPNP_FULLVARDESC
pProtoUpnp->iSendSize += ds_snzprintf(pBuf, iBufSize,
" <%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>\r\n",
pName, pType, pData, pName);
#else
pProtoUpnp->iSendSize += ds_snzprintf(pBuf, iBufSize, " <%s>%s</%s>\r\n", pName, pData, pName);
#endif
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpSoapRequestClose
\Description
Close the soap request.
\Input *pProtoUpnp - pointer to module state
\Version 03/30/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpSoapRequestClose(ProtoUpnpRefT *pProtoUpnp)
{
char *pBuf = pProtoUpnp->strSendBuf + pProtoUpnp->iSendSize;
int32_t iBufSize = sizeof(pProtoUpnp->strSendBuf) - pProtoUpnp->iSendSize;
// append soap envelope end
pProtoUpnp->iSendSize += ds_snzprintf(pBuf, iBufSize,
" </u:%s>\r\n"
" </s:Body>\r\n"
"</s:Envelope>\r\n\r\n",
pProtoUpnp->strRequestName);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpSoapRequestPost
\Description
Post a formatted SOAP request to gateway device.
\Input *pProtoUpnp - pointer to module state
\Version 03/27/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpSoapRequestPost(ProtoUpnpRefT *pProtoUpnp)
{
int32_t iResult;
if (pProtoUpnp->bRequestInProgress == TRUE)
{
_ProtoUpnpError(pProtoUpnp, "a soap request is already in progress");
return;
}
// print request to debug output
NetPrintf(("protoupnp: initiating %s request for service %s\n", pProtoUpnp->strRequestName,
pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType));
// initiate external address fetch operation
_ProtoUpnpHttpReset(pProtoUpnp);
if ((iResult = ProtoHttpPost(pProtoUpnp->pProtoHttp,
pProtoUpnp->Device.Services[pProtoUpnp->iService].strControlUrl,
pProtoUpnp->strSendBuf, pProtoUpnp->iSendSize,
FALSE)) >= 0)
{
pProtoUpnp->iSentBytes = iResult;
}
else
{
NetPrintf(("protoupnp: %s request failed for service %s with result %d\n", pProtoUpnp->strRequestName,
pProtoUpnp->Device.Services[pProtoUpnp->iService].strServiceType, iResult));
pProtoUpnp->iHttpError = iResult;
pProtoUpnp->bRequestInProgress = FALSE;
pProtoUpnp->eState = ST_IDLE;
return;
}
// verbose output?
#if DIRTYCODE_LOGGING
if (pProtoUpnp->bVerbose)
{
NetPrintWrap(pProtoUpnp->strSendBuf, 80);
}
#endif
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpSoapWaitResponse
\Description
Poll for a soap response. If there is an error response, this function
parses it and stores the value internally.
\Input *pProtoUpnp - pointer to module state
\Version 03/30/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpSoapWaitResponse(ProtoUpnpRefT *pProtoUpnp)
{
ProtoHttpResponseE eResponse;
int32_t iResult;
// wait until http transaction completes
if ((iResult = _ProtoUpnpHttpWaitResponse(pProtoUpnp)) == 0)
{
return(iResult);
}
#if DIRTYCODE_DEBUG
else if (iResult == 2)
{
return(iResult);
}
#endif
// check for error response
eResponse = ProtoHttpStatus(pProtoUpnp->pProtoHttp, 'code', NULL, 0);
// if an error
if (eResponse != PROTOHTTP_RESPONSE_SUCCESSFUL)
{
// if it is a soap error, parse the response
if (eResponse == PROTOHTTP_RESPONSE_SERVERERROR)
{
const char *pXml, *pXml2;
// find UPnPError
if ((pXml = XmlFind(pProtoUpnp->strRecvBuf, "%*:Envelope.%*:Body.%*:Fault.detail.UPnPError")) != NULL)
{
// get error code
if ((pXml2 = XmlFind(pXml, ".errorCode")) != NULL)
{
pProtoUpnp->iSoapError = XmlContentGetInteger(pXml2, 0);
}
// get error description
#if DIRTYCODE_LOGGING
{
char strErrorText[96] = "unknown";
if ((pXml2 = XmlFind(pXml, ".errorDescription")) != NULL)
{
XmlContentGetString(pXml2, strErrorText, sizeof(strErrorText), "");
}
NetPrintf(("protoupnp: soap error %d (%s) in response to %s request\n",
pProtoUpnp->iSoapError, strErrorText, pProtoUpnp->strRequestName));
}
#endif
}
}
iResult = -1;
}
// return result to caller
return(iResult);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpXmlGetAddress
\Description
Get an address from XML field pName.
\Input *pXml - pointer to current location in xml file
\Input *pName - name of field to get
\Input *pAddress - [out] output buffer to store address
\Output
int32_t - negative=not found, else found
\Version 10/10/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpXmlGetAddress(const char *pXml, const char *pName, int32_t *pAddress)
{
if ((pXml = XmlFind(pXml, pName)) != NULL)
{
*pAddress = XmlContentGetAddress(pXml, 0);
return(0);
}
return(-1);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpXmlGetBoolean
\Description
Get a boolean from XML field pName.
\Input *pXml - pointer to current location in xml file
\Input *pName - name of field to get
\Input *pBoolean - [out] output buffer to store boolean
\Output
int32_t - negative=not found, else found
\Version 10/10/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpXmlGetBoolean(const char *pXml, const char *pName, uint8_t *pBoolean)
{
if ((pXml = XmlFind(pXml, pName)) != NULL)
{
*pBoolean = (uint8_t)XmlContentGetInteger(pXml, 0);
return(0);
}
return(-1);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpXmlGetInteger
\Description
Get an integer from XML field pName.
\Input *pXml - pointer to current location in xml file
\Input *pName - name of field to get
\Input *pInteger - [out] output buffer to store integer
\Output
int32_t - negative=not found, else found
\Version 10/10/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpXmlGetInteger(const char *pXml, const char *pName, int32_t *pInteger)
{
if ((pXml = XmlFind(pXml, pName)) != NULL)
{
*pInteger = XmlContentGetInteger(pXml, 0);
return(0);
}
return(-1);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpXmlGetString
\Description
Get a string from XML field pName.
\Input *pXml - pointer to current location in xml file
\Input *pName - name of field to get
\Input *pBuffer - [out] output buffer to store string
\Input iBufSize - size of output buffer
\Output
int32_t - negative=not found, else found
\Version 10/10/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpXmlGetString(const char *pXml, const char *pName, char *pBuffer, int32_t iBufSize)
{
ds_memclr(pBuffer, iBufSize);
if ((pXml = XmlFind(pXml, pName)) != NULL)
{
return(XmlContentGetString(pXml, pBuffer, iBufSize, ""));
}
return(-1);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpXmlParseDescription
\Description
Parse a device description response
\Input *pProtoUpnp - pointer to module state
\Output
int32_t - negative=failure, else success
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpXmlParseDescription(ProtoUpnpRefT *pProtoUpnp)
{
char strContentA[256], strContentB[256], strContentC[256];
ProtoUpnpDeviceT *pDevice = &pProtoUpnp->Device;
const char *pXml, *pXml2;
// parse optional URLBase field if present
if ((pXml = XmlFind(pProtoUpnp->strRecvBuf, "root.URLBase")) != NULL)
{
// get urlbase string
int32_t iUrlBaseLen = XmlContentGetString(pXml, pProtoUpnp->Device.strUrlBase, sizeof(pProtoUpnp->Device.strUrlBase), "");
if (iUrlBaseLen > 0)
{
// if urlbase ends in a trailing slash, trim it
if (pProtoUpnp->Device.strUrlBase[iUrlBaseLen-1] == '/')
{
NetPrintf(("protoupnp: trimming trailing slash from parsed URLBase string\n"));
pProtoUpnp->Device.strUrlBase[iUrlBaseLen-1] = '\0';
}
NetPrintf(("protoupnp: parsed URLBase=%s\n", pProtoUpnp->Device.strUrlBase));
}
else
{
NetPrintf(("protoupnp: unable to parse URLBase field\n"));
}
}
// find a WANConnectionDevice
NetPrintf(("protoupnp: parsing xml response for a WANConnectionDevice:\n"));
for (pXml = XmlFind(pProtoUpnp->strRecvBuf, "root.device"); pXml != NULL; )
{
// get deviceType
pXml2 = XmlFind(pXml, ".deviceType");
// get deviceType contents
XmlContentGetString(pXml2, strContentA, sizeof(strContentA), "");
NetPrintf(("protoupnp: deviceType=%s\n", strContentA));
// is this the deviceType we are looking for?
if (ds_stristr(strContentA, "WANConnectionDevice"))
{
// get UDN
_ProtoUpnpXmlGetString(pXml2, "UDN", pDevice->strUdn, sizeof(pDevice->strUdn));
// get manufacturer name
_ProtoUpnpXmlGetString(pXml2, "manufacturer", strContentA, sizeof(strContentA));
// get model name
_ProtoUpnpXmlGetString(pXml2, "modelName", strContentB, sizeof(strContentB));
// get model number (firmware rev)
_ProtoUpnpXmlGetString(pXml2, "modelNumber", strContentC, sizeof(strContentC));
// format model name
ds_snzprintf(pProtoUpnp->Device.strModel, sizeof(pProtoUpnp->Device.strModel), "%s %s %s",
strContentA, strContentB, strContentC);
NetPrintf(("protoupnp: device=%s UDN=%s\n", pProtoUpnp->Device.strModel, pDevice->strUdn));
break;
}
// first try to advance through recursion
if ((pXml2 = XmlFind(pXml, ".deviceList.device")) == NULL)
{
// if no child device lists, skip to next element
pXml2 = XmlSkip(pXml);
}
pXml = pXml2;
}
// find any services associated with this device
NetPrintf(("protoupnp: parsing xml response for WANConnection Services:\n"));
for (pXml = XmlFind(pXml, ".serviceList.service"); pXml != NULL; pXml = XmlNext(pXml))
{
// get serviceType header
if (_ProtoUpnpXmlGetString(pXml, ".serviceType", strContentB, sizeof(strContentB)) >= 0)
{
// get serviceType contents
NetPrintf(("protoupnp: serviceType=%s\n", strContentB));
// if it's not a serviceType we want, ignore it
if (!ds_stristr(strContentB, "connection"))
{
continue;
}
// if we are out of space to store it, continue
if (pDevice->iNumServices >= PROTOUPNP_MAXSERVICES)
{
NetPrintf(("protoupnp: service list full, ignoring this entry\n"));
continue;
}
// if this a serviceType we want, find the controlUrl
if (_ProtoUpnpXmlGetString(pXml, ".controlURL", strContentA, sizeof(strContentA)) >= 0)
{
ProtoUpnpServiceT *pService = &pDevice->Services[pDevice->iNumServices];
// create controlURL
_ProtoUpnpMakeFullUrl(&pProtoUpnp->Device, pService->strControlUrl, sizeof(pService->strControlUrl), strContentA);
NetPrintf(("protoupnp: controlURL=%s\n", pService->strControlUrl));
// create serviceURL
_ProtoUpnpXmlGetString(pXml, ".SCPDURL", strContentA, sizeof(strContentA));
_ProtoUpnpMakeFullUrl(&pProtoUpnp->Device, pService->strDescriptionUrl, sizeof(pService->strDescriptionUrl), strContentA);
NetPrintf(("protoupnp: SCPDURL=%s\n", pService->strDescriptionUrl));
// save serviceType
ds_strnzcpy(pService->strServiceType, strContentB, sizeof(pService->strServiceType));
NetPrintf(("protoupnp: serviceType=%s\n", pService->strServiceType));
pDevice->iNumServices += 1;
}
}
}
return((pDevice->iNumServices > 0) ? 0 : -1);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpXmlParseGetExtAddr
\Description
Parse a service GetExternalIpAddress response
\Input *pProtoUpnp - pointer to module state
\Output
int32_t - negative=failure, else success
\Version 03/25/2005 (jbrookes)
*/
/********************************************************************************F*/
static int32_t _ProtoUpnpXmlParseGetExtAddr(ProtoUpnpRefT *pProtoUpnp)
{
const char *pXml;
// parse response and look for external address
NetPrintf(("protoupnp: parsing xml response to GetExternalIPAddress request:\n"));
if ((pXml = XmlFind(pProtoUpnp->strRecvBuf, "%*:Envelope.%*:Body.%*:GetExternalIPAddressResponse")) != NULL)
{
// first, try to get contents ($$note - try this with a Netgear as it is different from the Linksys)
if ((pProtoUpnp->Device.uExternalAddress = XmlContentGetAddress(pXml, 0)) == 0)
{
// if that didn't work, try NewExternalIPAddress
_ProtoUpnpXmlGetAddress(pXml, ".NewExternalIPAddress", (int32_t *)&pProtoUpnp->Device.uExternalAddress);
}
// display parsed address
NetPrintf(("protoupnp: IPAddress=%a\n", pProtoUpnp->Device.uExternalAddress));
}
return((pProtoUpnp->Device.uExternalAddress != 0) ? 0 : -1);
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpXmlParseGetPortMapping
\Description
Parse a service GetSpecificPortMappingEntry response
\Input *pProtoUpnp - pointer to module state
\Version 10/07/2005 (jbrookes)
*/
/********************************************************************************F*/
static void _ProtoUpnpXmlParseGetPortMapping(ProtoUpnpRefT *pProtoUpnp)
{
const char *pXml;
// parse response and look for external address
NetPrintf(("protoupnp: parsing xml response to GetSpecificPortMappingEntry request:\n"));
if ((pXml = XmlFind(pProtoUpnp->strRecvBuf, "%*:Envelope.%*:Body.%*:GetSpecificPortMappingEntryResponse")) != NULL)
{
// parse addr
_ProtoUpnpXmlGetAddress(pXml, ".NewInternalClient", &pProtoUpnp->Device.CurPortMap.iAddr);
// parse port
_ProtoUpnpXmlGetInteger(pXml, ".NewInternalPort", &pProtoUpnp->Device.CurPortMap.iPort);
// parse enabled
_ProtoUpnpXmlGetBoolean(pXml, ".NewEnabled", &pProtoUpnp->Device.CurPortMap.bEnabled);
// parse description
_ProtoUpnpXmlGetString(pXml, ".NewPortMappingDescription", pProtoUpnp->Device.CurPortMap.strDesc,
sizeof(pProtoUpnp->Device.CurPortMap.strDesc));
// debug output
NetPrintf(("protoupnp: found port mapping ->%a:%d enabled=%s (%s)\n",
pProtoUpnp->Device.CurPortMap.iAddr, pProtoUpnp->Device.CurPortMap.iPort,
pProtoUpnp->Device.CurPortMap.bEnabled ? "true" : "false",
pProtoUpnp->Device.CurPortMap.strDesc));
}
}
/*F********************************************************************************/
/*!
\Function _ProtoUpnpIdle
\Description
NetConn idle function to update the ProtoUpnp module.
\Input *pData - pointer to module state
\Input uTick - current tick count
\Notes
This function is installed as a NetConn Idle function. NetConnIdle()
must be regularly polled for this function to be called.
\Version 1.0 02/01/2008 (cadam) First Version
*/
/********************************************************************************F*/
static void _ProtoUpnpIdle(void *pData, uint32_t uTick)
{
ProtoUpnpRefT *pRef = _ProtoUpnp_pRef;
ProtoUpnpUpdate(pRef);
}
/*** Public functions *************************************************************/
/*F********************************************************************************/
/*!
\Function ProtoUpnpCreate
\Description
Create the ProtoUpnp module state.
\Output
ProtoUpnpRefT * - pointer to module state, or NULL
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
ProtoUpnpRefT *ProtoUpnpCreate(void)
{
ProtoUpnpRefT *pProtoUpnp;
int32_t iMemGroup, iResult;
void *pMemGroupUserData;
struct sockaddr BindAddr;
// Query current mem group data
DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData);
// if already created, just ref it
if (_ProtoUpnp_pRef != NULL)
{
_ProtoUpnp_pRef->iRefCount += 1;
return(_ProtoUpnp_pRef);
}
// allocate and initialize module state
if ((pProtoUpnp = DirtyMemAlloc(sizeof(*pProtoUpnp), PROTOUPNP_MEMID, iMemGroup, pMemGroupUserData)) == NULL)
{
return(NULL);
}
ds_memclr(pProtoUpnp, sizeof(*pProtoUpnp));
pProtoUpnp->iMemGroup = iMemGroup;
pProtoUpnp->pMemGroupUserData = pMemGroupUserData;
// open udp socket
if ((pProtoUpnp->pUdp = SocketOpen(AF_INET, SOCK_DGRAM, 0)) == NULL)
{
NetPrintf(("protoupnp: unable to open udp socket\n"));
DirtyMemFree(pProtoUpnp, PROTOUPNP_MEMID, pProtoUpnp->iMemGroup, pProtoUpnp->pMemGroupUserData);
return(NULL);
}
// bind udp socket
SockaddrInit(&BindAddr, AF_INET);
if ((iResult = SocketBind(pProtoUpnp->pUdp, &BindAddr, sizeof(BindAddr))) == SOCKERR_NONE)
{
#if DIRTYCODE_LOGGING
SocketInfo(pProtoUpnp->pUdp, 'bind', 0, &BindAddr, sizeof(BindAddr));
NetPrintf(("protoupnp: bound discovery socket to port %d\n", SockaddrInGetPort(&BindAddr)));
#endif
}
else
{
NetPrintf(("protoupnp: error %d binding discovery socket\n", iResult));
SocketClose(pProtoUpnp->pUdp);
DirtyMemFree(pProtoUpnp, PROTOUPNP_MEMID, pProtoUpnp->iMemGroup, pProtoUpnp->pMemGroupUserData);
return(NULL);
}
// allocate protohttp module
if ((pProtoUpnp->pProtoHttp = ProtoHttpCreate(1024)) == NULL)
{
NetPrintf(("protoupnp: unable to create protohttp module\n"));
SocketClose(pProtoUpnp->pUdp);
DirtyMemFree(pProtoUpnp, PROTOUPNP_MEMID, pProtoUpnp->iMemGroup, pProtoUpnp->pMemGroupUserData);
return(NULL);
}
// setup critical section
NetCritInit(&pProtoUpnp->Crit, "protoupnp");
// set to quiet mode
ProtoHttpControl(pProtoUpnp->pProtoHttp, 'spam', FALSE, 0, NULL);
// set up discovery sendto address
SockaddrInit(&pProtoUpnp->DiscoveryAddr, AF_INET);
SockaddrInSetAddr(&pProtoUpnp->DiscoveryAddr, PROTOUPNP_DISCOVERYADDR);
SockaddrInSetPort(&pProtoUpnp->DiscoveryAddr, PROTOUPNP_DISCOVERYPORT);
// set initial state
pProtoUpnp->eState = ST_IDLE;
pProtoUpnp->iLeaseDuration = 4*60*60; // four hours
pProtoUpnp->iPortToMapExt = pProtoUpnp->iPortToMapInt = PROTOUPNP_DEFAULTPORTMAP;
pProtoUpnp->bEnableMapping = TRUE;
pProtoUpnp->iRemoteHost = -1;
// add protoupnp task handle
NetConnIdleAdd(_ProtoUpnpIdle, pProtoUpnp);
// save ref and return module pointer to caller
pProtoUpnp->iRefCount = 1;
_ProtoUpnp_pRef = pProtoUpnp;
return(pProtoUpnp);
}
/*F********************************************************************************/
/*!
\Function ProtoUpnpGetRef
\Description
Get ProtoUpnp reference
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
ProtoUpnpRefT *ProtoUpnpGetRef(void)
{
return(_ProtoUpnp_pRef);
}
/*F********************************************************************************/
/*!
\Function ProtoUpnpDestroy
\Description
Destroy the ProtoUpnp module.
\Input *pProtoUpnp - pointer to module state
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
void ProtoUpnpDestroy(ProtoUpnpRefT *pProtoUpnp)
{
// if we aren't the last, just decrement the refcount and return
if (--pProtoUpnp->iRefCount > 0)
{
return;
}
// destroy ProtoHttp module
ProtoHttpDestroy(pProtoUpnp->pProtoHttp);
// close udp socket
SocketClose(pProtoUpnp->pUdp);
// destroy critical section
NetCritKill(&pProtoUpnp->Crit);
// del protoupnp task handle
NetConnIdleDel(_ProtoUpnpIdle, pProtoUpnp);
// release module memory
DirtyMemFree(pProtoUpnp, PROTOUPNP_MEMID, pProtoUpnp->iMemGroup, pProtoUpnp->pMemGroupUserData);
// clear ref
_ProtoUpnp_pRef = NULL;
}
/*F********************************************************************************/
/*!
\Function ProtoUpnpStatus
\Description
Get information from the ProtoUpnp module.
\Input *pProtoUpnp - pointer to module state
\Input iSelect - info selector
\Input *pBuf - [out] pointer to output buffer
\Input iBufSize - size of output buffer
\Output
int32_t - selector specific
\Notes
iSelect can be one of the following:
\verbatim
'body' - get most recent http response body
'ctrl' - return control ident for current operation
'disc' - TRUE if a UPnP device has been discovered, else FALSE
'dnam' - device name ("manufacturer modelName modelNumber")
'done' - returns TRUE if in IDLE state and no macro is currently being processed
'durn' - device URN
'extn' - device external (WAN) IP address
'extp' - returns external port used in mapping operations
'idle' - return if module is idle or not
'intp' - returns internal port used in mapping operations
'lerr' - most recent error result (http or soap)
'macr' - return current macro being executed, or zero if none, + name copied to pBuf
'rbdy' - get most recent htto request body
'stat' - module status (PROTOUPNP_STATUS_*)
\endverbatim
\Version 10/10/2005 (jbrookes)
*/
/********************************************************************************F*/
int32_t ProtoUpnpStatus(ProtoUpnpRefT *pProtoUpnp, int32_t iSelect, void *pBuf, int32_t iBufSize)
{
// return most recent http body
if (iSelect == 'body')
{
ds_strnzcpy(pBuf, pProtoUpnp->strRecvBuf, iBufSize);
return(0);
}
// return control ident for current operation
if (iSelect == 'ctrl')
{
return(_ProtoUpnp_aStateMap[pProtoUpnp->eState]);
}
// have we discovered a device?
if (iSelect == 'disc')
{
return(pProtoUpnp->Device.bDiscovered);
}
// return device name
if (iSelect == 'dnam')
{
ds_strnzcpy(pBuf, pProtoUpnp->Device.strModel, iBufSize);
return(0);
}
if (iSelect == 'done')
{
return((pProtoUpnp->pCommandList == NULL) && (pProtoUpnp->eState == ST_IDLE));
}
// return device urn
if (iSelect == 'durn')
{
ds_strnzcpy(pBuf, pProtoUpnp->Device.strUdn, iBufSize);
return(0);
}
// return device external (WAN) IP address
if (iSelect == 'extn')
{
return(pProtoUpnp->Device.uExternalAddress);
}
// return external port used in map operations
if (iSelect == 'extp')
{
return(pProtoUpnp->iPortToMapExt);
}
// return if idle or not
if (iSelect == 'idle')
{
return(pProtoUpnp->eState == ST_IDLE);
}
// set source port
if (iSelect == 'intp')
{
return(pProtoUpnp->iPortToMapInt);
}
// last error
if (iSelect == 'lerr')
{
if (pProtoUpnp->iSoapError != 0)
{
return(pProtoUpnp->iSoapError);
}
else if (pProtoUpnp->iHttpError != -1)
{
return(pProtoUpnp->iHttpError);
}
else
{
return(0);
}
}
// return current macro being executed
if (iSelect == 'macr')
{
int32_t iMacro = (pProtoUpnp->pCommandList != NULL) ? pProtoUpnp->pCommandList->iControl : 0;
if (pBuf != NULL)
{
ds_strnzcpy(pBuf, pProtoUpnp->strRequestName, iBufSize);
}
return(iMacro);
}
// most recent request body
if (iSelect == 'rbdy')
{
ds_strnzcpy(pBuf, pProtoUpnp->strSendBuf, iBufSize);
return(0);
}
// return module status
if (iSelect == 'stat')
{
return(pProtoUpnp->iStatus);
}
// try passing it to ProtoHttp
return(ProtoHttpStatus(pProtoUpnp->pProtoHttp, iSelect, pBuf, iBufSize));
}
/*F********************************************************************************/
/*!
\Function ProtoUpnpControl
\Description
Control ProtoUpnp behavior
\Input *pProtoUpnp - pointer to module state
\Input iControl - control selector
\Input iValue - selector specifc
\Input iValue2 - selector specific
\Input *pValue - selector specific
\Output
int32_t - selector specific
\Notes
iControl can be one of the following:
\verbatim
'abrt' - abort current operation
'aprt' - start AddPortMapping request
'desc' - initiate description
'disc' - initiate discovery
'dprt' - start DeletePortMapping request
'extp' - set external port
'gadr' - start GetExternalIPAddress request
'gprt' - start GetSpecificPortMappingEntry request
'host' - host address to allow traffic through firewall from
'intp' - set internal port
'ldur' - set lease duration
'macr' - execute a macro. iValue=macro id, or pValue=pointer to user macro
'addp' - macro to add a new port mapping
'port' - set internal and external ports
'spam' - enable/disable verbose output
\endverbatim
\Version 05/24/2005 (jbrookes)
*/
/********************************************************************************F*/
int32_t ProtoUpnpControl(ProtoUpnpRefT *pProtoUpnp, int32_t iControl, int32_t iValue, int32_t iValue2, const void *pValue)
{
char strAddrText[20];
// abort
if (iControl == 'abrt')
{
NetCritEnter(&pProtoUpnp->Crit);
pProtoUpnp->eState = ST_IDLE;
pProtoUpnp->bRequestInProgress = FALSE;
NetCritLeave(&pProtoUpnp->Crit);
return(0);
}
// set dest port
if (iControl == 'extp')
{
NetPrintf(("protoupnp: set external port to %d\n", iValue));
pProtoUpnp->iPortToMapExt = iValue;
return(0);
}
// fake a command
#if DIRTYCODE_DEBUG
if (iControl == 'fake')
{
const int32_t _StateMap[] = { 'idle', 'disc', 'desc', 'sdsc', 'gvar', 'gext', 'gprt', 'dprt', 'aprt' };
int32_t iState;
NetCritEnter(&pProtoUpnp->Crit);
// copy input into receive buffer
ds_strnzcpy(pProtoUpnp->strRecvBuf, pValue, sizeof(pProtoUpnp->strRecvBuf));
// fake the command
if (iValue == 'disc')
{
// parse the response & update status
NetPrintf(("protoupnp: faking 'disc' command\n"));
_ProtoUpnpParseDiscoveryResponse(pProtoUpnp, pProtoUpnp->strRecvBuf);
pProtoUpnp->iStatus |= PROTOUPNP_STATUS_DISCOVERED;
}
else
{
for (iState = 0; iState < ST_LAST; iState++)
{
if (_StateMap[iState] == iValue)
{
NetPrintf(("protoupnp: faking '%c%c%c%c' command\n",
(iValue>>24)&0xff, (iValue>>16)&0xff, (iValue>>8)&0xff,(iValue>>0)&0xff));
pProtoUpnp->bFakeResponse = TRUE;
pProtoUpnp->eState = iState;
break;
}
}
}
NetCritLeave(&pProtoUpnp->Crit);
return(0);
}
#endif
// set remote host
if (iControl == 'host')
{
NetPrintf(("protoupnp: set remote host to %a\n", iValue));
pProtoUpnp->iRemoteHost = iValue;
return(0);
}
// set source port
if (iControl == 'intp')
{
NetPrintf(("protoupnp: set internal port to %d\n", iValue));
pProtoUpnp->iPortToMapInt = iValue;
return(0);
}
// set lease duration
if (iControl == 'ldur')
{
NetPrintf(("protoupnp: set lease duration to %d\n", iValue));
pProtoUpnp->iLeaseDuration = iValue;
return(0);
}
// set both internal and external ports
if (iControl == 'port')
{
NetPrintf(("protoupnp: setting internal and external port to %d\n", iValue));
pProtoUpnp->iPortToMapInt = pProtoUpnp->iPortToMapExt = iValue;
return(0);
}
// set verbose
if (iControl == 'spam')
{
ProtoHttpControl(pProtoUpnp->pProtoHttp, iControl, iValue, 0, NULL);
pProtoUpnp->bVerbose = iValue;
return(0);
}
/*
The following control functions can only be initiated in IDLE state
*/
if ((pProtoUpnp->eState != ST_IDLE) || (pProtoUpnp->bRequestInProgress))
{
NetPrintf(("protoupnp: '%c%c%c%c' only valid in IDLE state\n",
(iControl>>24)&0xff, (iControl>>16)&0xff, (iControl>>8)&0xff, iControl&0xff));
return(-1);
}
// execute a macro
if (iControl == 'macr')
{
const ProtoUpnpMacroT *pMacro;
// is it a built-in, set up command list to execute
if (iValue == 'dscg')
{
pProtoUpnp->pCommandList = _ProtoUpnp_cmd_dscg;
}
else if (iValue == 'dscp')
{
pProtoUpnp->pCommandList = _ProtoUpnp_cmd_dscp;
}
else if (iValue == 'addp')
{
pProtoUpnp->pCommandList = _ProtoUpnp_cmd_addp;
}
else if (iValue == 'upnp')
{
pProtoUpnp->pCommandList = _ProtoUpnp_cmd_upnp;
}
else if (iValue == 'test')
{
pProtoUpnp->pCommandList = _ProtoUpnp_cmd_test;
}
else // user-specified macro
{
pProtoUpnp->pCommandList = pValue;
}
// start macro
pMacro = pProtoUpnp->pCommandList;
NetPrintf(("protoupnp: command list start -> %c%c%c%c\n",
(pMacro->iControl>>24)&0xff, (pMacro->iControl>>16)&0xff,
(pMacro->iControl>>8)&0xff, pMacro->iControl&0xff));
ProtoUpnpControl(pProtoUpnp, pMacro->iControl, pMacro->iValue, pMacro->iValue2, pMacro->pValue);
return(0);
}
// start discovery
if (iControl == 'disc')
{
// reset module
_ProtoUpnpReset(pProtoUpnp);
NetPrintf(("protoupnp: searching for upnp devices\n"));
// set to discovery state
pProtoUpnp->eState = ST_DISCOVERY;
return(0);
}
// only allow subsequent operations if we've discovered a device
if (pProtoUpnp->Device.bDiscovered != TRUE)
{
NetPrintf(("protoupnp: '%c%c%c%c' only valid if a device has been discovered\n",
(iControl>>24)&0xff, (iControl>>16)&0xff, (iControl>>8)&0xff, iControl&0xff));
return(-1);
}
// start description
if (iControl == 'desc')
{
// initiate description fetch operation
NetPrintf(("protoupnp: sending description request\n"));
ds_strnzcpy(pProtoUpnp->strRequestName, "Description", sizeof(pProtoUpnp->strRequestName));
_ProtoUpnpHttpReset(pProtoUpnp);
ProtoHttpGet(pProtoUpnp->pProtoHttp, pProtoUpnp->Device.strUrl, FALSE);
// set to description state
pProtoUpnp->eState = ST_DESCRIPTION;
return(0);
}
// only allow subsequent operations if we've described a device
if (pProtoUpnp->Device.iNumServices == 0)
{
NetPrintf(("protoupnp: '%c%c%c%c' only valid if a device has been described\n",
(iControl>>24)&0xff, (iControl>>16)&0xff, (iControl>>8)&0xff, iControl&0xff));
return(-1);
}
// start addportmapping request
if (iControl == 'aprt')
{
// if we haven't already, acquire client address
if (pProtoUpnp->iClientAddr == 0)
{
// get local address
pProtoUpnp->iClientAddr = NetConnStatus('addr', 0, NULL, 0);
}
// if a mapping doesn't exist, go ahead and map it
if (!pProtoUpnp->bPortIsMapped)
{
// get remote host address
_ProtoUpnpGetRemoteHost(pProtoUpnp, strAddrText, sizeof(strAddrText));
// post AddPortMapping request
NetPrintf(("protoupnp: addportmap %s:%d->%a:%d duration=%d enabled=%s\n", strAddrText, pProtoUpnp->iPortToMapExt, pProtoUpnp->iClientAddr,
pProtoUpnp->iPortToMapInt, pProtoUpnp->iLeaseDuration, pProtoUpnp->bEnableMapping ? "true" : "false"));
// format the request
_ProtoUpnpSoapRequestOpen(pProtoUpnp, "AddPortMapping");
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewRemoteHost", strAddrText);
_ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewExternalPort", pProtoUpnp->iPortToMapExt);
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewProtocol", "UDP");
_ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewInternalPort", pProtoUpnp->iPortToMapInt);
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewInternalClient", SocketInAddrGetText(pProtoUpnp->iClientAddr, strAddrText, sizeof(strAddrText)));
_ProtoUpnpSoapRequestAddBln(pProtoUpnp, "NewEnabled", pProtoUpnp->bEnableMapping);
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewPortMappingDescription", _ProtoUpnp_strDescription);
_ProtoUpnpSoapRequestAddUI4(pProtoUpnp, "NewLeaseDuration", pProtoUpnp->iLeaseDuration);
_ProtoUpnpSoapRequestClose(pProtoUpnp);
_ProtoUpnpSoapRequestPost(pProtoUpnp);
// set to poll for response
pProtoUpnp->eState = ST_ADDPORTMAPPING;
}
else
{
#if PROTOUPNP_REUSEMAPPING
// do we already have the mapping we want?
if ((pProtoUpnp->Device.CurPortMap.iAddr == pProtoUpnp->iClientAddr) &&
(pProtoUpnp->Device.CurPortMap.iPort == pProtoUpnp->iPortToMapInt) &&
(pProtoUpnp->Device.CurPortMap.bEnabled == pProtoUpnp->bEnableMapping) &&
(!strcmp(pProtoUpnp->Device.CurPortMap.strDesc, _ProtoUpnp_strDescription)))
{
NetPrintf(("protoupnp: reusing already existing port mapping\n"));
}
else
#endif
{
// we have to delete the current mapping before we can add a new one
ProtoUpnpControl(pProtoUpnp, 'dprt', 0, 0, NULL);
// if this is a command-list command, don't consume it
if (pProtoUpnp->pCommandList != NULL)
{
pProtoUpnp->pCommandList -= 1;
}
}
}
return(0);
}
// start delportmapping request
if (iControl == 'dprt')
{
// get remote host address
_ProtoUpnpGetRemoteHost(pProtoUpnp, strAddrText, sizeof(strAddrText));
// post DeletePortMapping request
_ProtoUpnpSoapRequestOpen(pProtoUpnp, "DeletePortMapping");
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewRemoteHost", strAddrText);
_ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewExternalPort", pProtoUpnp->iPortToMapExt);
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewProtocol", "UDP");
_ProtoUpnpSoapRequestClose(pProtoUpnp);
_ProtoUpnpSoapRequestPost(pProtoUpnp);
// set to poll for response
pProtoUpnp->eState = ST_DELPORTMAPPING;
return(0);
}
// start getexternalipaddress request
if (iControl == 'gadr')
{
// post GetExternalIPAddress request
_ProtoUpnpSoapRequestOpen(pProtoUpnp, "GetExternalIPAddress");
_ProtoUpnpSoapRequestClose(pProtoUpnp);
_ProtoUpnpSoapRequestPost(pProtoUpnp);
// set to getextaddr state
pProtoUpnp->eState = ST_GETEXTADDR;
return(0);
}
// start getgenericportmapping request
if (iControl == 'ggpt')
{
// post GetSpecificPortMappingEntry request
_ProtoUpnpSoapRequestOpen(pProtoUpnp, "GetGenericPortMappingEntry");
_ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewPortMappingIndex", iValue);
_ProtoUpnpSoapRequestClose(pProtoUpnp);
_ProtoUpnpSoapRequestPost(pProtoUpnp);
// set to poll for response
pProtoUpnp->eState = ST_GETPORTMAPPING;
return(0);
}
// start getspecificportmapping request
if (iControl == 'gprt')
{
// get remote host address
_ProtoUpnpGetRemoteHost(pProtoUpnp, strAddrText, sizeof(strAddrText));
// post GetSpecificPortMappingEntry request
_ProtoUpnpSoapRequestOpen(pProtoUpnp, "GetSpecificPortMappingEntry");
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewRemoteHost", strAddrText);
_ProtoUpnpSoapRequestAddUI2(pProtoUpnp, "NewExternalPort", pProtoUpnp->iPortToMapExt);
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "NewProtocol", "UDP");
_ProtoUpnpSoapRequestClose(pProtoUpnp);
_ProtoUpnpSoapRequestPost(pProtoUpnp);
// set to poll for response
pProtoUpnp->eState = ST_GETPORTMAPPING;
return(0);
}
// start QueryStateVariable request
if (iControl == 'gvar')
{
// post QueryStateVariable request
_ProtoUpnpSoapRequestOpen(pProtoUpnp, "QueryStateVariable");
_ProtoUpnpSoapRequestAddStr(pProtoUpnp, "varName", pValue);
_ProtoUpnpSoapRequestClose(pProtoUpnp);
_ProtoUpnpSoapRequestPost(pProtoUpnp);
// set to poll for response
pProtoUpnp->eState = ST_GETSTATEVAR;
return(0);
}
// get service description
if (iControl == 'sdsc')
{
// initiate description fetch operation
NetPrintf(("protoupnp: sending service description request\n"));
ds_strnzcpy(pProtoUpnp->strRequestName, "ServiceDesc", sizeof(pProtoUpnp->strRequestName));
_ProtoUpnpHttpReset(pProtoUpnp);
ProtoHttpGet(pProtoUpnp->pProtoHttp, pProtoUpnp->Device.Services[pProtoUpnp->iService].strDescriptionUrl, FALSE);
// set to description state
pProtoUpnp->eState = ST_SERVICEDESC;
return(0);
}
// unhandled
return(-1);
}
/*F********************************************************************************/
/*!
\Function ProtoUpnpUpdate
\Description
Update the ProtoUpnp module.
\Input *pProtoUpnp - pointer to module state
\Version 03/23/2005 (jbrookes)
*/
/********************************************************************************F*/
void ProtoUpnpUpdate(ProtoUpnpRefT *pProtoUpnp)
{
int32_t iCurTick = NetTick();
int32_t iResult;
NetCritEnter(&pProtoUpnp->Crit);
// update in idle state
if (pProtoUpnp->eState == ST_IDLE)
{
// update command list
if (pProtoUpnp->pCommandList != NULL)
{
// get next command
const ProtoUpnpMacroT *pMacro = ++pProtoUpnp->pCommandList;
// process command
if (pMacro->iControl != 0)
{
NetPrintf(("protoupnp: command list -> %c%c%c%c\n",
(pMacro->iControl>>24)&0xff, (pMacro->iControl>>16)&0xff,
(pMacro->iControl>>8)&0xff, pMacro->iControl&0xff));
ProtoUpnpControl(pProtoUpnp, pMacro->iControl, pMacro->iValue, pMacro->iValue2, pMacro->pValue);
}
else
{
NetPrintf(("protoupnp: command list complete\n"));
pProtoUpnp->pCommandList = NULL;
}
}
}
// update during discovery process
if (pProtoUpnp->eState == ST_DISCOVERY)
{
struct sockaddr RemoteAddr;
int32_t iResponseSize, iAddrLen = sizeof(RemoteAddr);
// send out a discovery broadcast?
if (NetTickDiff(iCurTick, pProtoUpnp->iDiscoveryTimer) >= PROTOUPNP_DISCOVERYINTERVAL)
{
// send discovery request
_ProtoUpnpSendDiscoveryRequest(pProtoUpnp);
// set timer for next broadcast
pProtoUpnp->iDiscoveryTimer = iCurTick;
}
// poll for discovery response
if ((iResponseSize = SocketRecvfrom(pProtoUpnp->pUdp, pProtoUpnp->strRecvBuf, SOCKET_MAXUDPRECV, 0, &RemoteAddr, &iAddrLen)) > 0)
{
// add null-termination
pProtoUpnp->strRecvBuf[iResponseSize] = '\0';
// parse the response
_ProtoUpnpParseDiscoveryResponse(pProtoUpnp, pProtoUpnp->strRecvBuf);
// update status
pProtoUpnp->iStatus |= PROTOUPNP_STATUS_DISCOVERED;
}
}
// update during description process
if (pProtoUpnp->eState == ST_DESCRIPTION)
{
// check for response
if ((iResult = _ProtoUpnpHttpWaitResponse(pProtoUpnp)) > 0)
{
// parse the response
if (_ProtoUpnpXmlParseDescription(pProtoUpnp) >= 0)
{
pProtoUpnp->iStatus |= PROTOUPNP_STATUS_DESCRIBED;
}
else
{
_ProtoUpnpError(pProtoUpnp, "parsing description");
}
}
else if (iResult < 0)
{
_ProtoUpnpError(pProtoUpnp, "getting description");
}
}
// update during service description process
if (pProtoUpnp->eState == ST_SERVICEDESC)
{
// check for response
if ((iResult = _ProtoUpnpHttpWaitResponse(pProtoUpnp)) > 0)
{
// $$ todo - handle service description response
}
else if (iResult < 0)
{
_ProtoUpnpError(pProtoUpnp, "getting service description");
}
}
// update during QueryStateVariable request
if (pProtoUpnp->eState == ST_GETSTATEVAR)
{
// check for response
if ((iResult = _ProtoUpnpHttpWaitResponse(pProtoUpnp)) > 0)
{
// $$ todo - handle querystatevariable response
}
else if (iResult < 0)
{
_ProtoUpnpError(pProtoUpnp, "querying state variable");
}
}
// update during GetExternalIPAddress request
if (pProtoUpnp->eState == ST_GETEXTADDR)
{
// check for response
if ((iResult = _ProtoUpnpSoapWaitResponse(pProtoUpnp)) > 0)
{
// parse the response
if (_ProtoUpnpXmlParseGetExtAddr(pProtoUpnp) >= 0)
{
pProtoUpnp->iStatus |= PROTOUPNP_STATUS_GOTEXTADDR;
}
else
{
// didn't find it, so check the next service
if (pProtoUpnp->iService < (pProtoUpnp->Device.iNumServices-1))
{
pProtoUpnp->iService += 1;
ProtoUpnpControl(pProtoUpnp, 'gadr', 0, 0, NULL);
}
}
}
else if (iResult < 0)
{
_ProtoUpnpError(pProtoUpnp, "getting external address");
}
}
// update during GetSpecificPortMappingEntry request
if (pProtoUpnp->eState == ST_GETPORTMAPPING)
{
// check for response
if ((iResult = _ProtoUpnpSoapWaitResponse(pProtoUpnp)) != 0)
{
// if we got a success response, that means a mapping already exists
if (iResult > 0)
{
NetPrintf(("protoupnp: mapping already exists\n"));
_ProtoUpnpXmlParseGetPortMapping(pProtoUpnp);
pProtoUpnp->iStatus |= PROTOUPNP_STATUS_FNDPORTMAP;
pProtoUpnp->bPortIsMapped = TRUE;
}
else if (pProtoUpnp->iSoapError == 714)
{
NetPrintf(("protoupnp: mapping does not exist\n"));
pProtoUpnp->bPortIsMapped = FALSE;
}
else if (pProtoUpnp->iSoapError == 501)
{
// not supported? try continuing on anyway (BEFSR41)
NetPrintf(("protoupnp: unable to query mapping\n"));
pProtoUpnp->bPortIsMapped = FALSE;
}
else // some other error, so bail
{
_ProtoUpnpError(pProtoUpnp, "getting portmapping");
}
}
}
// update during DeletePortMapping request
if (pProtoUpnp->eState == ST_DELPORTMAPPING)
{
// check for response
if ((iResult = _ProtoUpnpSoapWaitResponse(pProtoUpnp)) != 0)
{
// if we got a success response, that means we've successfully deleted the mapping
if (iResult > 0)
{
NetPrintf(("protoupnp: deleted port mapping\n"));
pProtoUpnp->iStatus |= PROTOUPNP_STATUS_DELPORTMAP;
pProtoUpnp->bPortIsMapped = FALSE;
}
else if (pProtoUpnp->iSoapError == 714)
{
NetPrintf(("protoupnp: mapping does not exist\n"));
pProtoUpnp->bPortIsMapped = FALSE;
}
else // some other error, so bail
{
_ProtoUpnpError(pProtoUpnp, "deleting port mapping");
}
}
}
// update during AddPortMapping request
if (pProtoUpnp->eState == ST_ADDPORTMAPPING)
{
// check for response
if ((iResult = _ProtoUpnpSoapWaitResponse(pProtoUpnp)) != 0)
{
// if we got a success response, that means the mapping succeeded
if (iResult > 0)
{
pProtoUpnp->iStatus |= PROTOUPNP_STATUS_ADDPORTMAP;
NetPrintf(("protoupnp: port mapping added\n"));
}
else
{
uint8_t bRetry = TRUE;
if (pProtoUpnp->iSoapError == 716)
{
NetPrintf(("protoupnp: wildcard not permitted in ext port\n"));
bRetry = FALSE;
}
else if (pProtoUpnp->iSoapError == 725)
{
NetPrintf(("protoupnp: finite lease duration not supported -- trying again with infinite\n"));
pProtoUpnp->iLeaseDuration = 0;
}
else if (pProtoUpnp->iSoapError == 726)
{
NetPrintf(("protoupnp: specific remote host not supported -- trying again with wildcard\n"));
pProtoUpnp->iRemoteHost = 0;
}
else if (pProtoUpnp->iLeaseDuration != 0)
{
NetPrintf(("protoupnp: unknown error adding port mapping -- trying again with infinite lease duration\n"));
pProtoUpnp->iLeaseDuration = 0;
}
else if (pProtoUpnp->iRemoteHost != 0)
{
NetPrintf(("protoupnp: unknown error adding port mapping -- trying again with wildcard source IP\n"));
pProtoUpnp->iRemoteHost = 0;
}
else
{
// nothing else to try, so just fail
bRetry = FALSE;
}
// retry or fail?
if (bRetry)
{
ProtoUpnpControl(pProtoUpnp, 'aprt', 0, 0, NULL);
}
else
{
_ProtoUpnpError(pProtoUpnp, "adding port mapping");
}
}
}
}
NetCritLeave(&pProtoUpnp->Crit);
}