# Copyright (c) 2019 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) # at your option. # This file may not be copied, modified, or distributed except according to # those terms. ################################ # headers and library location # ################################ import ./utils when defined(miniupnpcUseSystemLibs): {.passC: staticExec("pkg-config --cflags miniupnpc").} {.passL: staticExec("pkg-config --libs miniupnpc").} else: import os const includePath = currentSourcePath.parentDir().parentDir() / "vendor" / "miniupnp" / "miniupnpc" {.passC: "-I" & includePath.} # We can't use the {.link.} pragma in here, because it would place the static # library archive as the first object to be linked, which would lead to all # its exported symbols being ignored. We move it into the last position with {.passL.}. {.passL: includePath / "libminiupnpc.a".} when defined(windows): import nativesockets # for that wsaStartup() call at the end {.passC: "-DMINIUPNP_STATICLIB".} {.passL: "-lws2_32 -liphlpapi".} ################ # upnperrors.h # ################ ## strupnperror() ## Return a string description of the UPnP error code ## or NULL for undefinded errors proc upnpError*(err: cint): cstring {.importc: "strupnperror", header: "upnperrors.h".} ###################### # portlistingparse.h # ###################### type portMappingElt* {.size: sizeof(cint).} = enum PortMappingEltNone, PortMappingEntry, NewRemoteHost, NewExternalPort, NewProtocol, NewInternalPort, NewInternalClient, NewEnabled, NewDescription, NewLeaseTime PortMapping* {.importc: "struct PortMapping", header: "portlistingparse.h", bycopy.} = object l_next* {.importc: "l_next".}: ptr PortMapping ## list next element leaseTime* {.importc: "leaseTime".}: culonglong ## assume the used C version is at lead C99 (see miniupnpctypes.h for the definition of UNSIGNED_INTEGER) externalPort* {.importc: "externalPort".}: cushort internalPort* {.importc: "internalPort".}: cushort remoteHost* {.importc: "remoteHost".}: array[64, char] internalClient* {.importc: "internalClient".}: array[64, char] description* {.importc: "description".}: array[64, char] protocol* {.importc: "protocol".}: array[4, char] enabled* {.importc: "enabled".}: cuchar PortMappingParserData* {.importc: "struct PortMappingParserData", header: "portlistingparse.h", bycopy.} = object l_head* {.importc: "l_head".}: ptr PortMapping ## list head curelt* {.importc: "curelt".}: portMappingElt ################## # upnpcommands.h # ################## ## MiniUPnPc return codes : const UPNPCOMMAND_SUCCESS* = cint(0) UPNPCOMMAND_UNKNOWN_ERROR* = cint(-1) UPNPCOMMAND_INVALID_ARGS* = cint(-2) UPNPCOMMAND_HTTP_ERROR* = cint(-3) UPNPCOMMAND_INVALID_RESPONSE* = cint(-4) UPNPCOMMAND_MEM_ALLOC_ERROR* = cint(-5) proc UPNP_GetTotalBytesSent*(controlURL: cstring; servicetype: cstring): culonglong {. importc: "UPNP_GetTotalBytesSent", header: "upnpcommands.h".} proc UPNP_GetTotalBytesReceived*(controlURL: cstring; servicetype: cstring): culonglong {. importc: "UPNP_GetTotalBytesReceived", header: "upnpcommands.h".} proc UPNP_GetTotalPacketsSent*(controlURL: cstring; servicetype: cstring): culonglong {. importc: "UPNP_GetTotalPacketsSent", header: "upnpcommands.h".} proc UPNP_GetTotalPacketsReceived*(controlURL: cstring; servicetype: cstring): culonglong {. importc: "UPNP_GetTotalPacketsReceived", header: "upnpcommands.h".} ## UPNP_GetStatusInfo() ## status and lastconnerror are 64 byte buffers ## Return values : ## UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR ## or a UPnP Error code proc UPNP_GetStatusInfo*(controlURL: cstring; servicetype: cstring; status: cstring; uptime: ptr cuint; lastconnerror: cstring): cint {. importc: "UPNP_GetStatusInfo", header: "upnpcommands.h".} ## UPNP_GetConnectionTypeInfo() ## argument connectionType is a 64 character buffer ## Return Values : ## UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR ## or a UPnP Error code proc UPNP_GetConnectionTypeInfo*(controlURL: cstring; servicetype: cstring; connectionType: cstring): cint {. importc: "UPNP_GetConnectionTypeInfo", header: "upnpcommands.h".} ## UPNP_GetExternalIPAddress() call the corresponding UPNP method. ## if the third arg is not null the value is copied to it. ## at least 16 bytes must be available ## ## Return values : ## 0 : SUCCESS ## NON ZERO : ERROR Either an UPnP error code or an unknown error. ## ## possible UPnP Errors : ## 402 Invalid Args - See UPnP Device Architecture section on Control. ## 501 Action Failed - See UPnP Device Architecture section on Control. proc UPNP_GetExternalIPAddress*(controlURL: cstring; servicetype: cstring; extIpAdd: cstring): cint {. importc: "UPNP_GetExternalIPAddress", header: "upnpcommands.h".} ## UPNP_GetLinkLayerMaxBitRates() ## call WANCommonInterfaceConfig:1#GetCommonLinkProperties ## ## return values : ## UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR ## or a UPnP Error Code. proc UPNP_GetLinkLayerMaxBitRates*(controlURL: cstring; servicetype: cstring; bitrateDown: ptr cuint; bitrateUp: ptr cuint): cint {. importc: "UPNP_GetLinkLayerMaxBitRates", header: "upnpcommands.h".} ## UPNP_AddPortMapping() ## if desc is NULL, it will be defaulted to "libminiupnpc" ## remoteHost is usually NULL because IGD don't support it. ## ## Return values : ## 0 : SUCCESS ## NON ZERO : ERROR. Either an UPnP error code or an unknown error. ## ## List of possible UPnP errors for AddPortMapping : ## errorCode errorDescription (short) - Description (long) ## 402 Invalid Args - See UPnP Device Architecture section on Control. ## 501 Action Failed - See UPnP Device Architecture section on Control. ## 606 Action not authorized - The action requested REQUIRES authorization and ## the sender was not authorized. ## 715 WildCardNotPermittedInSrcIP - The source IP address cannot be ## wild-carded ## 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded ## 718 ConflictInMappingEntry - The port mapping entry specified conflicts ## with a mapping assigned previously to another client ## 724 SamePortValuesRequired - Internal and External port values ## must be the same ## 725 OnlyPermanentLeasesSupported - The NAT implementation only supports ## permanent lease times on port mappings ## 726 RemoteHostOnlySupportsWildcard - RemoteHost must be a wildcard ## and cannot be a specific IP address or DNS name ## 727 ExternalPortOnlySupportsWildcard - ExternalPort must be a wildcard and ## cannot be a specific port value ## 728 NoPortMapsAvailable - There are not enough free ports available to ## complete port mapping. ## 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed ## due to conflict with other mechanisms. ## 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded ## proc UPNP_AddPortMapping*(controlURL: cstring; servicetype: cstring; extPort: cstring; inPort: cstring; inClient: cstring; desc: cstring; proto: cstring; remoteHost: cstring; leaseDuration: cstring): cint {. importc: "UPNP_AddPortMapping", header: "upnpcommands.h".} ## UPNP_AddAnyPortMapping() ## if desc is NULL, it will be defaulted to "libminiupnpc" ## remoteHost is usually NULL because IGD don't support it. ## ## Return values : ## 0 : SUCCESS ## NON ZERO : ERROR. Either an UPnP error code or an unknown error. ## ## List of possible UPnP errors for AddPortMapping : ## errorCode errorDescription (short) - Description (long) ## 402 Invalid Args - See UPnP Device Architecture section on Control. ## 501 Action Failed - See UPnP Device Architecture section on Control. ## 606 Action not authorized - The action requested REQUIRES authorization and ## the sender was not authorized. ## 715 WildCardNotPermittedInSrcIP - The source IP address cannot be ## wild-carded ## 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded ## 728 NoPortMapsAvailable - There are not enough free ports available to ## complete port mapping. ## 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed ## due to conflict with other mechanisms. ## 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded ## proc UPNP_AddAnyPortMapping*(controlURL: cstring; servicetype: cstring; extPort: cstring; inPort: cstring; inClient: cstring; desc: cstring; proto: cstring; remoteHost: cstring; leaseDuration: cstring; reservedPort: cstring): cint {. importc: "UPNP_AddAnyPortMapping", header: "upnpcommands.h".} ## UPNP_DeletePortMapping() ## Use same argument values as what was used for AddPortMapping(). ## remoteHost is usually NULL because IGD don't support it. ## Return Values : ## 0 : SUCCESS ## NON ZERO : error. Either an UPnP error code or an undefined error. ## ## List of possible UPnP errors for DeletePortMapping : ## 402 Invalid Args - See UPnP Device Architecture section on Control. ## 606 Action not authorized - The action requested REQUIRES authorization ## and the sender was not authorized. ## 714 NoSuchEntryInArray - The specified value does not exist in the array proc UPNP_DeletePortMapping*(controlURL: cstring; servicetype: cstring; extPort: cstring; proto: cstring; remoteHost: cstring): cint {. importc: "UPNP_DeletePortMapping", header: "upnpcommands.h".} ## UPNP_DeletePortRangeMapping() ## Use same argument values as what was used for AddPortMapping(). ## remoteHost is usually NULL because IGD don't support it. ## Return Values : ## 0 : SUCCESS ## NON ZERO : error. Either an UPnP error code or an undefined error. ## ## List of possible UPnP errors for DeletePortMapping : ## 606 Action not authorized - The action requested REQUIRES authorization ## and the sender was not authorized. ## 730 PortMappingNotFound - This error message is returned if no port ## mapping is found in the specified range. ## 733 InconsistentParameters - NewStartPort and NewEndPort values are not consistent. proc UPNP_DeletePortMappingRange*(controlURL: cstring; servicetype: cstring; extPortStart: cstring; extPortEnd: cstring; proto: cstring; manage: cstring): cint {. importc: "UPNP_DeletePortMappingRange", header: "upnpcommands.h".} ## UPNP_GetPortMappingNumberOfEntries() ## not supported by all routers proc UPNP_GetPortMappingNumberOfEntries*(controlURL: cstring; servicetype: cstring; numEntries: ptr cuint): cint {. importc: "UPNP_GetPortMappingNumberOfEntries", header: "upnpcommands.h".} ## UPNP_GetSpecificPortMappingEntry() ## retrieves an existing port mapping ## params : ## in extPort ## in proto ## in remoteHost ## out intClient (16 bytes) ## out intPort (6 bytes) ## out desc (80 bytes) ## out enabled (4 bytes) ## out leaseDuration (16 bytes) ## ## return value : ## UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR ## or a UPnP Error Code. ## ## List of possible UPnP errors for _GetSpecificPortMappingEntry : ## 402 Invalid Args - See UPnP Device Architecture section on Control. ## 501 Action Failed - See UPnP Device Architecture section on Control. ## 606 Action not authorized - The action requested REQUIRES authorization ## and the sender was not authorized. ## 714 NoSuchEntryInArray - The specified value does not exist in the array. ## proc UPNP_GetSpecificPortMappingEntry*(controlURL: cstring; servicetype: cstring; extPort: cstring; proto: cstring; remoteHost: cstring; intClient: cstring; intPort: cstring; desc: cstring; enabled: cstring; leaseDuration: cstring): cint {. importc: "UPNP_GetSpecificPortMappingEntry", header: "upnpcommands.h".} ## UPNP_GetGenericPortMappingEntry() ## params : ## in index ## out extPort (6 bytes) ## out intClient (16 bytes) ## out intPort (6 bytes) ## out protocol (4 bytes) ## out desc (80 bytes) ## out enabled (4 bytes) ## out rHost (64 bytes) ## out duration (16 bytes) ## ## return value : ## UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR ## or a UPnP Error Code. ## ## Possible UPNP Error codes : ## 402 Invalid Args - See UPnP Device Architecture section on Control. ## 606 Action not authorized - The action requested REQUIRES authorization ## and the sender was not authorized. ## 713 SpecifiedArrayIndexInvalid - The specified array index is out of bounds ## proc UPNP_GetGenericPortMappingEntry*(controlURL: cstring; servicetype: cstring; index: cstring; extPort: cstring; intClient: cstring; intPort: cstring; protocol: cstring; desc: cstring; enabled: cstring; rHost: cstring; duration: cstring): cint {. importc: "UPNP_GetGenericPortMappingEntry", header: "upnpcommands.h".} ## UPNP_GetListOfPortMappings() Available in IGD v2 ## ## ## Possible UPNP Error codes : ## 606 Action not Authorized ## 730 PortMappingNotFound - no port mapping is found in the specified range. ## 733 InconsistantParameters - NewStartPort and NewEndPort values are not ## consistent. ## proc UPNP_GetListOfPortMappings*(controlURL: cstring; servicetype: cstring; startPort: cstring; endPort: cstring; protocol: cstring; numberOfPorts: cstring; data: ptr PortMappingParserData): cint {. importc: "UPNP_GetListOfPortMappings", header: "upnpcommands.h".} ## IGD:2, functions for service WANIPv6FirewallControl:1 proc UPNP_GetFirewallStatus*(controlURL: cstring; servicetype: cstring; firewallEnabled: ptr cint; inboundPinholeAllowed: ptr cint): cint {. importc: "UPNP_GetFirewallStatus", header: "upnpcommands.h".} proc UPNP_GetOutboundPinholeTimeout*(controlURL: cstring; servicetype: cstring; remoteHost: cstring; remotePort: cstring; intClient: cstring; intPort: cstring; proto: cstring; opTimeout: ptr cint): cint {. importc: "UPNP_GetOutboundPinholeTimeout", header: "upnpcommands.h".} proc UPNP_AddPinhole*(controlURL: cstring; servicetype: cstring; remoteHost: cstring; remotePort: cstring; intClient: cstring; intPort: cstring; proto: cstring; leaseTime: cstring; uniqueID: cstring): cint {. importc: "UPNP_AddPinhole", header: "upnpcommands.h".} proc UPNP_UpdatePinhole*(controlURL: cstring; servicetype: cstring; uniqueID: cstring; leaseTime: cstring): cint {. importc: "UPNP_UpdatePinhole", header: "upnpcommands.h".} proc UPNP_DeletePinhole*(controlURL: cstring; servicetype: cstring; uniqueID: cstring): cint {. importc: "UPNP_DeletePinhole", header: "upnpcommands.h".} proc UPNP_CheckPinholeWorking*(controlURL: cstring; servicetype: cstring; uniqueID: cstring; isWorking: ptr cint): cint {. importc: "UPNP_CheckPinholeWorking", header: "upnpcommands.h".} proc UPNP_GetPinholePackets*(controlURL: cstring; servicetype: cstring; uniqueID: cstring; packets: ptr cint): cint {. importc: "UPNP_GetPinholePackets", header: "upnpcommands.h".} #################### # igd_desc_parse.h # #################### ## Structure to store the result of the parsing of UPnP ## descriptions of Internet Gateway Devices const MINIUPNPC_URL_MAXSIZE* = (128) type IGDdatas_service* {.importc: "struct IGDdatas_service", header: "igd_desc_parse.h", bycopy.} = object controlurl* {.importc: "controlurl".}: array[MINIUPNPC_URL_MAXSIZE, char] eventsuburl* {.importc: "eventsuburl".}: array[MINIUPNPC_URL_MAXSIZE, char] scpdurl* {.importc: "scpdurl".}: array[MINIUPNPC_URL_MAXSIZE, char] servicetype* {.importc: "servicetype".}: array[MINIUPNPC_URL_MAXSIZE, char] ## char devicetype[MINIUPNPC_URL_MAXSIZE]; IGDdatas* {.importc: "struct IGDdatas", header: "igd_desc_parse.h", bycopy.} = object cureltname* {.importc: "cureltname".}: array[MINIUPNPC_URL_MAXSIZE, char] urlbase* {.importc: "urlbase".}: array[MINIUPNPC_URL_MAXSIZE, char] presentationurl* {.importc: "presentationurl".}: array[MINIUPNPC_URL_MAXSIZE, char] level* {.importc: "level".}: cint ## int state; ## "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" CIF* {.importc: "CIF".}: IGDdatas_service ## "urn:schemas-upnp-org:service:WANIPConnection:1" ## "urn:schemas-upnp-org:service:WANPPPConnection:1" first* {.importc: "first".}: IGDdatas_service ## if both WANIPConnection and WANPPPConnection are present second* {.importc: "second".}: IGDdatas_service ## "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1" IPv6FC* {.importc: "IPv6FC".}: IGDdatas_service ## tmp tmp* {.importc: "tmp".}: IGDdatas_service ############# # upnpdev.h # ############# type UPNPDev* {.importc: "struct UPNPDev", header: "upnpdev.h", bycopy.} = object pNext* {.importc: "pNext".}: ptr UPNPDev descURL* {.importc: "descURL".}: cstring st* {.importc: "st".}: cstring usn* {.importc: "usn".}: cstring scope_id* {.importc: "scope_id".}: cuint buffer* {.importc: "buffer".}: array[3, char] ## freeUPNPDevlist() ## free list returned by upnpDiscover() proc freeUPNPDevlist*(devlist: ptr UPNPDev) {.importc: "freeUPNPDevlist", header: "upnpdev.h".} ############### # miniupnpc.h # ############### ## error codes : const UPNPDISCOVER_SUCCESS* = cint(0) const UPNPDISCOVER_UNKNOWN_ERROR* = cint(-1) const UPNPDISCOVER_SOCKET_ERROR* = cint(-101) const UPNPDISCOVER_MEMORY_ERROR* = cint(-102) ## versions : # We use importConst here because when a system header is used, we want the # number from the actual header file. importConst(MINIUPNPC_VERSION, "miniupnpc.h", cstring) importConst(MINIUPNPC_API_VERSION, "miniupnpc.h", cint) ## Source port: ## Using "1" as an alias for 1900 for backwards compatibility ## (presuming one would have used that for the "sameport" parameter) const UPNP_LOCAL_PORT_ANY* = cint(0) const UPNP_LOCAL_PORT_SAME* = cint(1) ## Structures definitions : type UPNParg* {.importc: "struct UPNParg", header: "miniupnpc.h", bycopy.} = object elt* {.importc: "elt".}: cstring val* {.importc: "val".}: cstring proc simpleUPnPcommand*(a1: cint; a2: cstring; a3: cstring; a4: cstring; a5: ptr UPNParg; a6: ptr cint): cstring {.importc: "simpleUPnPcommand", header: "miniupnpc.h".} ## upnpDiscover() ## discover UPnP devices on the network. ## The discovered devices are returned as a chained list. ## It is up to the caller to free the list with freeUPNPDevlist(). ## delay (in millisecond) is the maximum time for waiting any device ## response. ## If available, device list will be obtained from MiniSSDPd. ## Default path for minissdpd socket will be used if minissdpdsock argument ## is NULL. ## If multicastif is not NULL, it will be used instead of the default ## multicast interface for sending SSDP discover packets. ## If localport is set to UPNP_LOCAL_PORT_SAME(1) SSDP packets will be sent ## from the source port 1900 (same as destination port), if set to ## UPNP_LOCAL_PORT_ANY(0) system assign a source port, any other value will ## be attempted as the source port. ## "searchalltypes" parameter is useful when searching several types, ## if 0, the discovery will stop with the first type returning results. ## TTL should default to 2. proc upnpDiscover*(delay: cint; multicastif: cstring; minissdpdsock: cstring; localport: cint; ipv6: cint; ttl: cuchar; error: ptr cint): ptr UPNPDev {. importc: "upnpDiscover", header: "miniupnpc.h".} proc upnpDiscoverAll*(delay: cint; multicastif: cstring; minissdpdsock: cstring; localport: cint; ipv6: cint; ttl: cuchar; error: ptr cint): ptr UPNPDev {. importc: "upnpDiscoverAll", header: "miniupnpc.h".} proc upnpDiscoverDevice*(device: cstring; delay: cint; multicastif: cstring; minissdpdsock: cstring; localport: cint; ipv6: cint; ttl: cuchar; error: ptr cint): ptr UPNPDev {. importc: "upnpDiscoverDevice", header: "miniupnpc.h".} proc upnpDiscoverDevices*(deviceTypes: ptr cstring; delay: cint; multicastif: cstring; minissdpdsock: cstring; localport: cint; ipv6: cint; ttl: cuchar; error: ptr cint; searchalltypes: cint): ptr UPNPDev {. importc: "upnpDiscoverDevices", header: "miniupnpc.h".} ## structure used to get fast access to urls ## controlURL: controlURL of the WANIPConnection ## ipcondescURL: url of the description of the WANIPConnection ## controlURL_CIF: controlURL of the WANCommonInterfaceConfig ## controlURL_6FC: controlURL of the WANIPv6FirewallControl ## type UPNPUrls* {.importc: "struct UPNPUrls", header: "miniupnpc.h", bycopy.} = object controlURL* {.importc: "controlURL".}: cstring ipcondescURL* {.importc: "ipcondescURL".}: cstring controlURL_CIF* {.importc: "controlURL_CIF".}: cstring controlURL_6FC* {.importc: "controlURL_6FC".}: cstring rootdescURL* {.importc: "rootdescURL".}: cstring ## UPNP_GetValidIGD() : ## return values : ## 0 = NO IGD found ## 1 = A valid connected IGD has been found ## 2 = A valid IGD has been found but it reported as ## not connected ## 3 = an UPnP device has been found but was not recognized as an IGD ## ## In any non-zero return case, the urls and data structures ## passed as parameters are set. Don't forget to call freeUPNPUrls(urls) to ## free allocated memory. ## proc UPNP_GetValidIGD*(devlist: ptr UPNPDev; urls: ptr UPNPUrls; data: ptr IGDdatas; lanaddr: cstring; lanaddrlen: cint): cint {. importc: "UPNP_GetValidIGD", header: "miniupnpc.h".} ## UPNP_GetIGDFromUrl() ## Used when skipping the discovery process. ## When succeding, urls, data, and lanaddr arguments are set. ## return value : ## 0 - Not ok ## 1 - OK proc UPNP_GetIGDFromUrl*(rootdescurl: cstring; urls: ptr UPNPUrls; data: ptr IGDdatas; lanaddr: cstring; lanaddrlen: cint): cint {. importc: "UPNP_GetIGDFromUrl", header: "miniupnpc.h".} proc freeUPNPUrls*(a1: ptr UPNPUrls) {.importc: "FreeUPNPUrls", header: "miniupnpc.h".} ## return 0 or 1 proc UPNPIGD_IsConnected*(a1: ptr UPNPUrls; a2: ptr IGDdatas): cint {. importc: "UPNPIGD_IsConnected", header: "miniupnpc.h".} ################### # custom wrappers # ################### import stew/result, strutils type Miniupnp* = ref object devList*: ptr UPNPDev urls*: UPNPUrls data*: IGDdatas discoverDelay*: cint # in ms, the delay defaults to 1000ms if this is left 0 multicastIF*: string miniSsdpdSocket*: string localPort*: cint ipv6*: cint ttl*: cuchar error*: cint lanAddr*: string proc miniupnpFinalizer(x: Miniupnp) = freeUPNPDevlist(x.devList) x.devList = nil freeUPNPUrls(addr(x.urls)) proc newMiniupnp*(): Miniupnp = new(result, miniupnpFinalizer) result.ttl = 2.cuchar proc `=deepCopy`*(x: Miniupnp): Miniupnp = doAssert(false, "not implemented") # trim a Nim string to the length of the internal cstring proc trimString(s: var string) = s.setLen(len(s.cstring)) ## returns the number of devices discovered or an error string proc discover*(self: Miniupnp): Result[int, cstring] = if self.devList != nil: freeUPNPDevlist(self.devList) self.error = 0 var multicastIF = if self.multicastIF.len > 0: self.multicastIF.cstring else: nil miniSsdpdSocket = if self.miniSsdpdSocket.len > 0: self.miniSsdpdSocket.cstring else: nil self.devList = upnpDiscover(self.discoverDelay, multicastIF, miniSsdpdSocket, self.localPort, self.ipv6, self.ttl, addr(self.error)) var dev = self.devList i = 0 while dev != nil: inc i dev = dev.pNext if self.error == 0: result.ok(i) else: result.err(upnpError(self.error)) type SelectIGDResult* = enum IGDNotFound = 0 IGDFound = 1 IGDNotConnected = 2 NotAnIGD = 3 proc selectIGD*(self: Miniupnp): SelectIGDResult = let lanaddrlen = 40.cint self.lanAddr.setLen(40) result = UPNP_GetValidIGD(self.devList, addr(self.urls), addr(self.data), self.lanAddr.cstring, lanaddrlen).SelectIGDResult trimString(self.lanAddr) type SentReceivedResult = Result[culonglong, cstring] proc totalBytesSent*(self: Miniupnp): SentReceivedResult = let res = UPNP_GetTotalBytesSent(self.urls.controlURL_CIF, addr(self.data.CIF.servicetype)) if res == cast[culonglong](UPNPCOMMAND_HTTP_ERROR): result.err(upnpError(res.cint)) else: result.ok(res) proc totalBytesReceived*(self: Miniupnp): SentReceivedResult = let res = UPNP_GetTotalBytesReceived(self.urls.controlURL_CIF, addr(self.data.CIF.servicetype)) if res == cast[culonglong](UPNPCOMMAND_HTTP_ERROR): result.err(upnpError(res.cint)) else: result.ok(res) proc totalPacketsSent*(self: Miniupnp): SentReceivedResult = let res = UPNP_GetTotalPacketsSent(self.urls.controlURL_CIF, addr(self.data.CIF.servicetype)) if res == cast[culonglong](UPNPCOMMAND_HTTP_ERROR): result.err(upnpError(res.cint)) else: result.ok(res) proc totalPacketsReceived*(self: Miniupnp): SentReceivedResult = let res = UPNP_GetTotalPacketsReceived(self.urls.controlURL_CIF, addr(self.data.CIF.servicetype)) if res == cast[culonglong](UPNPCOMMAND_HTTP_ERROR): result.err(upnpError(res.cint)) else: result.ok(res) type StatusInfo* = object status*: string uptime*: cuint lastconnerror*: string proc statusInfo*(self: Miniupnp): Result[StatusInfo, cstring] = var si: StatusInfo si.status.setLen(64) si.lastconnerror.setLen(64) let res = UPNP_GetStatusInfo(self.urls.controlURL, addr(self.data.first.servicetype), si.status.cstring, addr(si.uptime), si.lastconnerror.cstring) if res == UPNPCOMMAND_SUCCESS: trimString(si.status) trimString(si.lastconnerror) result.ok(si) else: result.err(upnpError(res)) proc connectionType*(self: Miniupnp): Result[string, cstring] = var connType = newString(64) let res = UPNP_GetConnectionTypeInfo(self.urls.controlURL, addr(self.data.first.servicetype), connType.cstring) if res == UPNPCOMMAND_SUCCESS: trimString(connType) result.ok(connType) else: result.err(upnpError(res)) proc externalIPAddress*(self: Miniupnp): Result[string, cstring] = var externalIP = newString(40) let res = UPNP_GetExternalIPAddress(self.urls.controlURL, addr(self.data.first.servicetype), externalIP.cstring) if res == UPNPCOMMAND_SUCCESS: trimString(externalIP) result.ok(externalIP) else: result.err(upnpError(res)) type UPNPProtocol* = enum TCP = "TCP" UDP = "UDP" proc addPortMapping*(self: Miniupnp, externalPort: string, protocol: UPNPProtocol, internalHost: string, internalPort: string, desc = "miniupnpc", leaseDuration = 0, externalIP = ""): Result[bool, cstring] = var extIP = externalIP.cstring if externalIP == "": # Some IGDs can't handle explicit external IPs here (they fail with "RemoteHostOnlySupportsWildcard"). # That's why we default to an empty address, which gets converted into a # NULL pointer for the wrapped library. extIP = nil let res = UPNP_AddPortMapping(self.urls.controlURL, addr(self.data.first.servicetype), externalPort.cstring, internalPort.cstring, internalHost.cstring, desc.cstring, cstring($protocol), extIP, cstring($leaseDuration)) if res == UPNPCOMMAND_SUCCESS: result.ok(true) else: result.err(upnpError(res)) ## (IGD:2 only) ## Returns the actual external port (that may differ from the requested one), or ## an error string. proc addAnyPortMapping*(self: Miniupnp, externalPort: string, protocol: UPNPProtocol, internalHost: string, internalPort: string, desc = "miniupnpc", leaseDuration = 0, externalIP = ""): Result[string, cstring] = var extIP = externalIP.cstring if externalIP == "": extIP = nil var reservedPort = newString(6) let res = UPNP_AddAnyPortMapping(self.urls.controlURL, addr(self.data.first.servicetype), externalPort.cstring, internalPort.cstring, internalHost.cstring, desc.cstring, cstring($protocol), extIP, cstring($leaseDuration), reservedPort.cstring) if res == UPNPCOMMAND_SUCCESS: trimString(reservedPort) result.ok(reservedPort) else: result.err(upnpError(res)) proc deletePortMapping*(self: Miniupnp, externalPort: string, protocol: UPNPProtocol, remoteHost = ""): Result[bool, cstring] = var remHost = remoteHost.cstring if remoteHost == "": remHost = nil let res = UPNP_DeletePortMapping(self.urls.controlURL, addr(self.data.first.servicetype), externalPort.cstring, cstring($protocol), remHost) if res == UPNPCOMMAND_SUCCESS: result.ok(true) else: result.err(upnpError(res)) proc deletePortMappingRange*(self: Miniupnp, externalPortStart: string, externalPortEnd: string, protocol: UPNPProtocol, manage = false): Result[bool, cstring] = let res = UPNP_DeletePortMappingRange(self.urls.controlURL, addr(self.data.first.servicetype), externalPortStart.cstring, externalPortEnd.cstring, cstring($protocol), cstring($(manage.int))) if res == UPNPCOMMAND_SUCCESS: result.ok(true) else: result.err(upnpError(res)) ## not supported by all routers proc getPortMappingNumberOfEntries*(self: Miniupnp): Result[cuint, cstring] = var numEntries: cuint let res = UPNP_GetPortMappingNumberOfEntries(self.urls.controlURL, addr(self.data.first.servicetype), addr(numEntries)) if res == UPNPCOMMAND_SUCCESS: result.ok(numEntries) else: result.err(upnpError(res)) type PortMappingRes* = object externalPort*: string internalClient*: string internalPort*: string protocol*: UPNPProtocol description*: string enabled*: bool remoteHost*: string leaseDuration*: uint64 proc getSpecificPortMapping*(self: Miniupnp, externalPort: string, protocol: UPNPProtocol, remoteHost = ""): Result[PortMappingRes, cstring] = var portMapping = PortMappingRes(externalPort: externalPort, protocol: protocol, remoteHost: remoteHost) enabledStr = newString(4) leaseDurationStr = newString(16) portMapping.internalClient.setLen(40) portMapping.internalPort.setLen(6) portMapping.description.setLen(80) var remHost = remoteHost.cstring if remoteHost == "": remHost = nil let res = UPNP_GetSpecificPortMappingEntry(self.urls.controlURL, addr(self.data.first.servicetype), externalPort.cstring, cstring($protocol), remHost, portMapping.internalClient.cstring, portMapping.internalPort.cstring, portMapping.description.cstring, enabledStr.cstring, leaseDurationStr.cstring) if res == UPNPCOMMAND_SUCCESS: trimString(portMapping.internalClient) trimString(portMapping.internalPort) trimString(portMapping.description) trimString(enabledStr) portMapping.enabled = bool(parseInt(enabledStr)) trimString(leaseDurationStr) portMapping.leaseDuration = parseBiggestUInt(leaseDurationStr) result.ok(portMapping) else: result.err(upnpError(res)) proc getGenericPortMapping*(self: Miniupnp, index: int): Result[PortMappingRes, cstring] = var portMapping: PortMappingRes protocolStr = newString(4) enabledStr = newString(4) leaseDurationStr = newString(16) portMapping.externalPort.setLen(6) portMapping.internalClient.setLen(40) portMapping.internalPort.setLen(6) portMapping.description.setLen(80) portMapping.remoteHost.setLen(64) let res = UPNP_GetGenericPortMappingEntry(self.urls.controlURL, addr(self.data.first.servicetype), cstring($index), portMapping.externalPort.cstring, portMapping.internalClient.cstring, portMapping.internalPort.cstring, protocolStr.cstring, portMapping.description.cstring, enabledStr.cstring, portMapping.remoteHost.cstring, leaseDurationStr.cstring) if res == UPNPCOMMAND_SUCCESS: trimString(portMapping.externalPort) trimString(portMapping.internalClient) trimString(portMapping.internalPort) trimString(protocolStr) portMapping.protocol = parseEnum[UPNPProtocol](protocolStr) trimString(portMapping.description) trimString(enabledStr) portMapping.enabled = bool(parseInt(enabledStr)) trimString(portMapping.remoteHost) trimString(leaseDurationStr) portMapping.leaseDuration = parseBiggestUInt(leaseDurationStr) result.ok(portMapping) else: result.err(upnpError(res))