1/* $Id: miniupnpcmodule.c,v 1.25 2014/09/06 08:08:25 nanard Exp $*/ 2/* Project : miniupnp 3 * Author : Thomas BERNARD 4 * website : http://miniupnp.tuxfamily.org/ 5 * copyright (c) 2007-2014 Thomas Bernard 6 * This software is subjet to the conditions detailed in the 7 * provided LICENCE file. */ 8#include <Python.h> 9#define MINIUPNP_STATICLIB 10#include "structmember.h" 11#include "miniupnpc.h" 12#include "upnpcommands.h" 13#include "upnperrors.h" 14 15/* for compatibility with Python < 2.4 */ 16#ifndef Py_RETURN_NONE 17#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None 18#endif 19 20#ifndef Py_RETURN_TRUE 21#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True 22#endif 23 24#ifndef Py_RETURN_FALSE 25#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False 26#endif 27 28/* for compatibility with Python < 3.0 */ 29#ifndef PyVarObject_HEAD_INIT 30#define PyVarObject_HEAD_INIT(type, size) \ 31 PyObject_HEAD_INIT(type) size, 32#endif 33 34#ifndef Py_TYPE 35#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) 36#endif 37 38typedef struct { 39 PyObject_HEAD 40 /* Type-specific fields go here. */ 41 struct UPNPDev * devlist; 42 struct UPNPUrls urls; 43 struct IGDdatas data; 44 unsigned int discoverdelay; /* value passed to upnpDiscover() */ 45 char lanaddr[40]; /* our ip address on the LAN */ 46 char * multicastif; 47 char * minissdpdsocket; 48} UPnPObject; 49 50static PyMemberDef UPnP_members[] = { 51 {"lanaddr", T_STRING_INPLACE, offsetof(UPnPObject, lanaddr), 52 READONLY, "ip address on the LAN" 53 }, 54 {"discoverdelay", T_UINT, offsetof(UPnPObject, discoverdelay), 55 0/*READWRITE*/, "value in ms used to wait for SSDP responses" 56 }, 57 /* T_STRING is allways readonly :( */ 58 {"multicastif", T_STRING, offsetof(UPnPObject, multicastif), 59 0, "IP of the network interface to be used for multicast operations" 60 }, 61 {"minissdpdsocket", T_STRING, offsetof(UPnPObject, multicastif), 62 0, "path of the MiniSSDPd unix socket" 63 }, 64 {NULL} 65}; 66 67static void 68UPnPObject_dealloc(UPnPObject *self) 69{ 70 freeUPNPDevlist(self->devlist); 71 FreeUPNPUrls(&self->urls); 72 Py_TYPE(self)->tp_free((PyObject*)self); 73} 74 75static PyObject * 76UPnP_discover(UPnPObject *self) 77{ 78 struct UPNPDev * dev; 79 int i; 80 PyObject *res = NULL; 81 if(self->devlist) 82 { 83 freeUPNPDevlist(self->devlist); 84 self->devlist = 0; 85 } 86 Py_BEGIN_ALLOW_THREADS 87 self->devlist = upnpDiscover((int)self->discoverdelay/*timeout in ms*/, 88 0/* multicast if*/, 89 0/*minissdpd socket*/, 90 0/*sameport flag*/, 91 0/*ip v6*/, 92 0/*error */); 93 Py_END_ALLOW_THREADS 94 /* Py_RETURN_NONE ??? */ 95 for(dev = self->devlist, i = 0; dev; dev = dev->pNext) 96 i++; 97 res = Py_BuildValue("i", i); 98 return res; 99} 100 101static PyObject * 102UPnP_selectigd(UPnPObject *self) 103{ 104 int r; 105Py_BEGIN_ALLOW_THREADS 106 r = UPNP_GetValidIGD(self->devlist, &self->urls, &self->data, 107 self->lanaddr, sizeof(self->lanaddr)); 108Py_END_ALLOW_THREADS 109 if(r) 110 { 111 return Py_BuildValue("s", self->urls.controlURL); 112 } 113 else 114 { 115 /* TODO: have our own exception type ! */ 116 PyErr_SetString(PyExc_Exception, "No UPnP device discovered"); 117 return NULL; 118 } 119} 120 121static PyObject * 122UPnP_totalbytesent(UPnPObject *self) 123{ 124 UNSIGNED_INTEGER i; 125Py_BEGIN_ALLOW_THREADS 126 i = UPNP_GetTotalBytesSent(self->urls.controlURL_CIF, 127 self->data.CIF.servicetype); 128Py_END_ALLOW_THREADS 129 return Py_BuildValue("I", i); 130} 131 132static PyObject * 133UPnP_totalbytereceived(UPnPObject *self) 134{ 135 UNSIGNED_INTEGER i; 136Py_BEGIN_ALLOW_THREADS 137 i = UPNP_GetTotalBytesReceived(self->urls.controlURL_CIF, 138 self->data.CIF.servicetype); 139Py_END_ALLOW_THREADS 140 return Py_BuildValue("I", i); 141} 142 143static PyObject * 144UPnP_totalpacketsent(UPnPObject *self) 145{ 146 UNSIGNED_INTEGER i; 147Py_BEGIN_ALLOW_THREADS 148 i = UPNP_GetTotalPacketsSent(self->urls.controlURL_CIF, 149 self->data.CIF.servicetype); 150Py_END_ALLOW_THREADS 151 return Py_BuildValue("I", i); 152} 153 154static PyObject * 155UPnP_totalpacketreceived(UPnPObject *self) 156{ 157 UNSIGNED_INTEGER i; 158Py_BEGIN_ALLOW_THREADS 159 i = UPNP_GetTotalPacketsReceived(self->urls.controlURL_CIF, 160 self->data.CIF.servicetype); 161Py_END_ALLOW_THREADS 162 return Py_BuildValue("I", i); 163} 164 165static PyObject * 166UPnP_statusinfo(UPnPObject *self) 167{ 168 char status[64]; 169 char lastconnerror[64]; 170 unsigned int uptime = 0; 171 int r; 172 status[0] = '\0'; 173 lastconnerror[0] = '\0'; 174Py_BEGIN_ALLOW_THREADS 175 r = UPNP_GetStatusInfo(self->urls.controlURL, self->data.first.servicetype, 176 status, &uptime, lastconnerror); 177Py_END_ALLOW_THREADS 178 if(r==UPNPCOMMAND_SUCCESS) { 179 return Py_BuildValue("(s,I,s)", status, uptime, lastconnerror); 180 } else { 181 /* TODO: have our own exception type ! */ 182 PyErr_SetString(PyExc_Exception, strupnperror(r)); 183 return NULL; 184 } 185} 186 187static PyObject * 188UPnP_connectiontype(UPnPObject *self) 189{ 190 char connectionType[64]; 191 int r; 192 connectionType[0] = '\0'; 193Py_BEGIN_ALLOW_THREADS 194 r = UPNP_GetConnectionTypeInfo(self->urls.controlURL, 195 self->data.first.servicetype, 196 connectionType); 197Py_END_ALLOW_THREADS 198 if(r==UPNPCOMMAND_SUCCESS) { 199 return Py_BuildValue("s", connectionType); 200 } else { 201 /* TODO: have our own exception type ! */ 202 PyErr_SetString(PyExc_Exception, strupnperror(r)); 203 return NULL; 204 } 205} 206 207static PyObject * 208UPnP_externalipaddress(UPnPObject *self) 209{ 210 char externalIPAddress[40]; 211 int r; 212 externalIPAddress[0] = '\0'; 213Py_BEGIN_ALLOW_THREADS 214 r = UPNP_GetExternalIPAddress(self->urls.controlURL, 215 self->data.first.servicetype, 216 externalIPAddress); 217Py_END_ALLOW_THREADS 218 if(r==UPNPCOMMAND_SUCCESS) { 219 return Py_BuildValue("s", externalIPAddress); 220 } else { 221 /* TODO: have our own exception type ! */ 222 PyErr_SetString(PyExc_Exception, strupnperror(r)); 223 return NULL; 224 } 225} 226 227/* AddPortMapping(externalPort, protocol, internalHost, internalPort, desc, 228 * remoteHost) 229 * protocol is 'UDP' or 'TCP' */ 230static PyObject * 231UPnP_addportmapping(UPnPObject *self, PyObject *args) 232{ 233 char extPort[6]; 234 unsigned short ePort; 235 char inPort[6]; 236 unsigned short iPort; 237 const char * proto; 238 const char * host; 239 const char * desc; 240 const char * remoteHost; 241 const char * leaseDuration = "0"; 242 int r; 243 if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto, 244 &host, &iPort, &desc, &remoteHost)) 245 return NULL; 246Py_BEGIN_ALLOW_THREADS 247 sprintf(extPort, "%hu", ePort); 248 sprintf(inPort, "%hu", iPort); 249 r = UPNP_AddPortMapping(self->urls.controlURL, self->data.first.servicetype, 250 extPort, inPort, host, desc, proto, 251 remoteHost, leaseDuration); 252Py_END_ALLOW_THREADS 253 if(r==UPNPCOMMAND_SUCCESS) 254 { 255 Py_RETURN_TRUE; 256 } 257 else 258 { 259 // TODO: RAISE an Exception. See upnpcommands.h for errors codes. 260 // upnperrors.c 261 //Py_RETURN_FALSE; 262 /* TODO: have our own exception type ! */ 263 PyErr_SetString(PyExc_Exception, strupnperror(r)); 264 return NULL; 265 } 266} 267 268/* AddAnyPortMapping(externalPort, protocol, internalHost, internalPort, desc, 269 * remoteHost) 270 * protocol is 'UDP' or 'TCP' */ 271static PyObject * 272UPnP_addanyportmapping(UPnPObject *self, PyObject *args) 273{ 274 char extPort[6]; 275 unsigned short ePort; 276 char inPort[6]; 277 unsigned short iPort; 278 char reservedPort[6]; 279 const char * proto; 280 const char * host; 281 const char * desc; 282 const char * remoteHost; 283 const char * leaseDuration = "0"; 284 int r; 285 if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto, &host, &iPort, &desc, &remoteHost)) 286 return NULL; 287Py_BEGIN_ALLOW_THREADS 288 sprintf(extPort, "%hu", ePort); 289 sprintf(inPort, "%hu", iPort); 290 r = UPNP_AddAnyPortMapping(self->urls.controlURL, self->data.first.servicetype, 291 extPort, inPort, host, desc, proto, 292 remoteHost, leaseDuration, reservedPort); 293Py_END_ALLOW_THREADS 294 if(r==UPNPCOMMAND_SUCCESS) { 295 return Py_BuildValue("i", atoi(reservedPort)); 296 } else { 297 /* TODO: have our own exception type ! */ 298 PyErr_SetString(PyExc_Exception, strupnperror(r)); 299 return NULL; 300 } 301} 302 303 304/* DeletePortMapping(extPort, proto, removeHost='') 305 * proto = 'UDP', 'TCP' */ 306static PyObject * 307UPnP_deleteportmapping(UPnPObject *self, PyObject *args) 308{ 309 char extPort[6]; 310 unsigned short ePort; 311 const char * proto; 312 const char * remoteHost = ""; 313 int r; 314 if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost)) 315 return NULL; 316Py_BEGIN_ALLOW_THREADS 317 sprintf(extPort, "%hu", ePort); 318 r = UPNP_DeletePortMapping(self->urls.controlURL, self->data.first.servicetype, 319 extPort, proto, remoteHost); 320Py_END_ALLOW_THREADS 321 if(r==UPNPCOMMAND_SUCCESS) { 322 Py_RETURN_TRUE; 323 } else { 324 /* TODO: have our own exception type ! */ 325 PyErr_SetString(PyExc_Exception, strupnperror(r)); 326 return NULL; 327 } 328} 329 330/* DeletePortMappingRange(extPort, proto, removeHost='') 331 * proto = 'UDP', 'TCP' */ 332static PyObject * 333UPnP_deleteportmappingrange(UPnPObject *self, PyObject *args) 334{ 335 char extPortStart[6]; 336 unsigned short ePortStart; 337 char extPortEnd[6]; 338 unsigned short ePortEnd; 339 const char * proto; 340 unsigned char manage; 341 char manageStr[1]; 342 int r; 343 if(!PyArg_ParseTuple(args, "HHsb", &ePortStart, &ePortEnd, &proto, &manage)) 344 return NULL; 345Py_BEGIN_ALLOW_THREADS 346 sprintf(extPortStart, "%hu", ePortStart); 347 sprintf(extPortEnd, "%hu", ePortEnd); 348 sprintf(manageStr, "%hhu", manage); 349 r = UPNP_DeletePortMappingRange(self->urls.controlURL, self->data.first.servicetype, 350 extPortStart, extPortEnd, proto, manageStr); 351Py_END_ALLOW_THREADS 352 if(r==UPNPCOMMAND_SUCCESS) { 353 Py_RETURN_TRUE; 354 } else { 355 /* TODO: have our own exception type ! */ 356 PyErr_SetString(PyExc_Exception, strupnperror(r)); 357 return NULL; 358 } 359} 360 361static PyObject * 362UPnP_getportmappingnumberofentries(UPnPObject *self) 363{ 364 unsigned int n = 0; 365 int r; 366Py_BEGIN_ALLOW_THREADS 367 r = UPNP_GetPortMappingNumberOfEntries(self->urls.controlURL, 368 self->data.first.servicetype, 369 &n); 370Py_END_ALLOW_THREADS 371 if(r==UPNPCOMMAND_SUCCESS) { 372 return Py_BuildValue("I", n); 373 } else { 374 /* TODO: have our own exception type ! */ 375 PyErr_SetString(PyExc_Exception, strupnperror(r)); 376 return NULL; 377 } 378} 379 380/* GetSpecificPortMapping(ePort, proto, remoteHost='') 381 * proto = 'UDP' or 'TCP' */ 382static PyObject * 383UPnP_getspecificportmapping(UPnPObject *self, PyObject *args) 384{ 385 char extPort[6]; 386 unsigned short ePort; 387 const char * proto; 388 const char * remoteHost = ""; 389 char intClient[40]; 390 char intPort[6]; 391 unsigned short iPort; 392 char desc[80]; 393 char enabled[4]; 394 char leaseDuration[16]; 395 if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost)) 396 return NULL; 397 extPort[0] = '\0'; intClient[0] = '\0'; intPort[0] = '\0'; 398 desc[0] = '\0'; enabled[0] = '\0'; leaseDuration[0] = '\0'; 399Py_BEGIN_ALLOW_THREADS 400 sprintf(extPort, "%hu", ePort); 401 UPNP_GetSpecificPortMappingEntry(self->urls.controlURL, 402 self->data.first.servicetype, 403 extPort, proto, remoteHost, 404 intClient, intPort, 405 desc, enabled, leaseDuration); 406Py_END_ALLOW_THREADS 407 if(intClient[0]) 408 { 409 iPort = (unsigned short)atoi(intPort); 410 return Py_BuildValue("(s,H,s,O,i)", 411 intClient, iPort, desc, 412 PyBool_FromLong(atoi(enabled)), 413 atoi(leaseDuration)); 414 } 415 else 416 { 417 Py_RETURN_NONE; 418 } 419} 420 421/* GetGenericPortMapping(index) */ 422static PyObject * 423UPnP_getgenericportmapping(UPnPObject *self, PyObject *args) 424{ 425 int i, r; 426 char index[8]; 427 char intClient[40]; 428 char intPort[6]; 429 unsigned short iPort; 430 char extPort[6]; 431 unsigned short ePort; 432 char protocol[4]; 433 char desc[80]; 434 char enabled[6]; 435 char rHost[64]; 436 char duration[16]; /* lease duration */ 437 unsigned int dur; 438 if(!PyArg_ParseTuple(args, "i", &i)) 439 return NULL; 440Py_BEGIN_ALLOW_THREADS 441 snprintf(index, sizeof(index), "%d", i); 442 rHost[0] = '\0'; enabled[0] = '\0'; 443 duration[0] = '\0'; desc[0] = '\0'; 444 extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0'; 445 r = UPNP_GetGenericPortMappingEntry(self->urls.controlURL, 446 self->data.first.servicetype, 447 index, 448 extPort, intClient, intPort, 449 protocol, desc, enabled, rHost, 450 duration); 451Py_END_ALLOW_THREADS 452 if(r==UPNPCOMMAND_SUCCESS) 453 { 454 ePort = (unsigned short)atoi(extPort); 455 iPort = (unsigned short)atoi(intPort); 456 dur = (unsigned int)strtoul(duration, 0, 0); 457 return Py_BuildValue("(H,s,(s,H),s,s,s,I)", 458 ePort, protocol, intClient, iPort, 459 desc, enabled, rHost, dur); 460 } 461 else 462 { 463 Py_RETURN_NONE; 464 } 465} 466 467/* miniupnpc.UPnP object Method Table */ 468static PyMethodDef UPnP_methods[] = { 469 {"discover", (PyCFunction)UPnP_discover, METH_NOARGS, 470 "discover UPnP IGD devices on the network" 471 }, 472 {"selectigd", (PyCFunction)UPnP_selectigd, METH_NOARGS, 473 "select a valid UPnP IGD among discovered devices" 474 }, 475 {"totalbytesent", (PyCFunction)UPnP_totalbytesent, METH_NOARGS, 476 "return the total number of bytes sent by UPnP IGD" 477 }, 478 {"totalbytereceived", (PyCFunction)UPnP_totalbytereceived, METH_NOARGS, 479 "return the total number of bytes received by UPnP IGD" 480 }, 481 {"totalpacketsent", (PyCFunction)UPnP_totalpacketsent, METH_NOARGS, 482 "return the total number of packets sent by UPnP IGD" 483 }, 484 {"totalpacketreceived", (PyCFunction)UPnP_totalpacketreceived, METH_NOARGS, 485 "return the total number of packets received by UPnP IGD" 486 }, 487 {"statusinfo", (PyCFunction)UPnP_statusinfo, METH_NOARGS, 488 "return status and uptime" 489 }, 490 {"connectiontype", (PyCFunction)UPnP_connectiontype, METH_NOARGS, 491 "return IGD WAN connection type" 492 }, 493 {"externalipaddress", (PyCFunction)UPnP_externalipaddress, METH_NOARGS, 494 "return external IP address" 495 }, 496 {"addportmapping", (PyCFunction)UPnP_addportmapping, METH_VARARGS, 497 "add a port mapping" 498 }, 499 {"addanyportmapping", (PyCFunction)UPnP_addanyportmapping, METH_VARARGS, 500 "add a port mapping, IGD to select alternative if necessary" 501 }, 502 {"deleteportmapping", (PyCFunction)UPnP_deleteportmapping, METH_VARARGS, 503 "delete a port mapping" 504 }, 505 {"deleteportmappingrange", (PyCFunction)UPnP_deleteportmappingrange, METH_VARARGS, 506 "delete a range of port mappings" 507 }, 508 {"getportmappingnumberofentries", (PyCFunction)UPnP_getportmappingnumberofentries, METH_NOARGS, 509 "-- non standard --" 510 }, 511 {"getspecificportmapping", (PyCFunction)UPnP_getspecificportmapping, METH_VARARGS, 512 "get details about a specific port mapping entry" 513 }, 514 {"getgenericportmapping", (PyCFunction)UPnP_getgenericportmapping, METH_VARARGS, 515 "get all details about the port mapping at index" 516 }, 517 {NULL} /* Sentinel */ 518}; 519 520static PyTypeObject UPnPType = { 521 PyVarObject_HEAD_INIT(NULL, 522 0) /*ob_size*/ 523 "miniupnpc.UPnP", /*tp_name*/ 524 sizeof(UPnPObject), /*tp_basicsize*/ 525 0, /*tp_itemsize*/ 526 (destructor)UPnPObject_dealloc,/*tp_dealloc*/ 527 0, /*tp_print*/ 528 0, /*tp_getattr*/ 529 0, /*tp_setattr*/ 530 0, /*tp_compare*/ 531 0, /*tp_repr*/ 532 0, /*tp_as_number*/ 533 0, /*tp_as_sequence*/ 534 0, /*tp_as_mapping*/ 535 0, /*tp_hash */ 536 0, /*tp_call*/ 537 0, /*tp_str*/ 538 0, /*tp_getattro*/ 539 0, /*tp_setattro*/ 540 0, /*tp_as_buffer*/ 541 Py_TPFLAGS_DEFAULT, /*tp_flags*/ 542 "UPnP objects", /* tp_doc */ 543 0, /* tp_traverse */ 544 0, /* tp_clear */ 545 0, /* tp_richcompare */ 546 0, /* tp_weaklistoffset */ 547 0, /* tp_iter */ 548 0, /* tp_iternext */ 549 UPnP_methods, /* tp_methods */ 550 UPnP_members, /* tp_members */ 551 0, /* tp_getset */ 552 0, /* tp_base */ 553 0, /* tp_dict */ 554 0, /* tp_descr_get */ 555 0, /* tp_descr_set */ 556 0, /* tp_dictoffset */ 557 0,/*(initproc)UPnP_init,*/ /* tp_init */ 558 0, /* tp_alloc */ 559#ifndef _WIN32 560 PyType_GenericNew,/*UPnP_new,*/ /* tp_new */ 561#else 562 0, 563#endif 564}; 565 566/* module methods */ 567static PyMethodDef miniupnpc_methods[] = { 568 {NULL} /* Sentinel */ 569}; 570 571#if PY_MAJOR_VERSION >= 3 572static struct PyModuleDef moduledef = { 573 PyModuleDef_HEAD_INIT, 574 "miniupnpc", /* m_name */ 575 "miniupnpc module.", /* m_doc */ 576 -1, /* m_size */ 577 miniupnpc_methods, /* m_methods */ 578 NULL, /* m_reload */ 579 NULL, /* m_traverse */ 580 NULL, /* m_clear */ 581 NULL, /* m_free */ 582}; 583#endif 584 585#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ 586#define PyMODINIT_FUNC void 587#endif 588 589PyMODINIT_FUNC 590#if PY_MAJOR_VERSION >= 3 591PyInit_miniupnpc(void) 592#else 593initminiupnpc(void) 594#endif 595{ 596 PyObject* m; 597 598#ifdef _WIN32 599 UPnPType.tp_new = PyType_GenericNew; 600#endif 601 if (PyType_Ready(&UPnPType) < 0) 602#if PY_MAJOR_VERSION >= 3 603 return 0; 604#else 605 return; 606#endif 607 608#if PY_MAJOR_VERSION >= 3 609 m = PyModule_Create(&moduledef); 610#else 611 m = Py_InitModule3("miniupnpc", miniupnpc_methods, 612 "miniupnpc module."); 613#endif 614 615 Py_INCREF(&UPnPType); 616 PyModule_AddObject(m, "UPnP", (PyObject *)&UPnPType); 617 618#if PY_MAJOR_VERSION >= 3 619 return m; 620#endif 621} 622 623