diff --git a/api.md b/api.md index e9b25d1..62b22b0 100644 --- a/api.md +++ b/api.md @@ -26,6 +26,13 @@ type MappingProtocol* = enum Direct ## no mapping needed, local address is already public ``` +```nim +type ProtocolFilter* {.pure.} = enum + Any ## try all protocols (default) + PCP ## PCP/NAT-PMP only + UPnP ## UPnP only +``` + ```nim type PlumMapping* = object protocol*: PlumProtocol ## IP protocol (TCP/UDP) @@ -55,7 +62,8 @@ proc init*( logLevel: plum_log_level_t = PLUM_LOG_LEVEL_NONE, discoverTimeout: int = 0, mappingTimeout: int = 0, - recheckPeriod: int = 0 + recheckPeriod: int = 0, + protocol: ProtocolFilter = ProtocolFilter.Any ): Result[void, string] ``` @@ -65,6 +73,7 @@ Initializes the library and starts the internal thread. Must be called before an - `discoverTimeout`: how long to probe for a NAT device, in ms (default: 10000) - `mappingTimeout`: how long to wait for a mapping response, in ms (default: 10000) - `recheckPeriod`: interval between periodic mapping rechecks, in ms (default: 300000) +- `protocol`: restrict discovery to a single family (`ProtocolFilter.PCP` for PCP/NAT-PMP, `ProtocolFilter.UPnP` for UPnP); `ProtocolFilter.Any` tries all (default) ### cleanup diff --git a/libplum/libplum.nim b/libplum/libplum.nim index d0ce609..199b990 100644 --- a/libplum/libplum.nim +++ b/libplum/libplum.nim @@ -66,6 +66,13 @@ type PLUM_IP_PROTOCOL_TCP = 0 PLUM_IP_PROTOCOL_UDP = 1 + plum_protocol_t* {. + importc: "plum_protocol_t", header: "plum.h", size: sizeof(cint) + .} = enum + PLUM_PROTOCOL_ANY = 0 + PLUM_PROTOCOL_PCP = 1 + PLUM_PROTOCOL_UPNP = 2 + plum_state_t* {.importc: "plum_state_t", header: "plum.h", size: sizeof(cint).} = enum PLUM_STATE_DESTROYED = 0 PLUM_STATE_PENDING = 1 @@ -96,6 +103,8 @@ type # msecs, 0 means use default (10000) recheck_period* {.importc: "recheck_period".}: cint # msecs, 0 means use default (300000) + protocol* {.importc: "protocol".}: plum_protocol_t + # PLUM_PROTOCOL_ANY means try all # Define the mapping struct, passed by copy (usual for struct). # The user_ptr is a pointer to the MappingHandle in order to receive the result diff --git a/libplum/plum.nim b/libplum/plum.nim index 65b3aad..7c72e85 100644 --- a/libplum/plum.nim +++ b/libplum/plum.nim @@ -37,6 +37,11 @@ type TCP = PLUM_IP_PROTOCOL_TCP.int UDP = PLUM_IP_PROTOCOL_UDP.int + ProtocolFilter* {.pure.} = enum + Any = PLUM_PROTOCOL_ANY.int + PCP = PLUM_PROTOCOL_PCP.int + UPnP = PLUM_PROTOCOL_UPNP.int + PlumLogLevel* {.pure.} = enum Verbose = PLUM_LOG_LEVEL_VERBOSE.int Debug = PLUM_LOG_LEVEL_DEBUG.int @@ -161,8 +166,11 @@ proc init*( discoverTimeout: int32 = 0, mappingTimeout: int32 = 0, recheckPeriod: int32 = 0, + protocol: ProtocolFilter = ProtocolFilter.Any, ): Result[void, string] = ## init MUST be called to setup internal plum thread (plum_init). + ## protocol restricts discovery to a single family (PCP/NAT-PMP or UPnP); + ## ProtocolFilter.Any tries all. var config = plum_config_t( log_level: plum_log_level_t(logLevel.int), @@ -171,6 +179,7 @@ proc init*( discover_timeout: discoverTimeout.cint, mapping_timeout: mappingTimeout.cint, recheck_period: recheckPeriod.cint, + protocol: plum_protocol_t(protocol.int), ) let res = plum_init(addr config) diff --git a/tests/test_plum.nim b/tests/test_plum.nim index 7ab7f13..b2e2fea 100644 --- a/tests/test_plum.nim +++ b/tests/test_plum.nim @@ -210,3 +210,40 @@ when miniupnp_protocol != "": # no destroyMapping on purpose: cleanup must release everything check cleanup().isOk() check activeMappingCount() == 0 + + # miniupnpd in the pcp container also serves UPnP, so a UPnP filter must + # override the default PCP-first selection. + when miniupnp_protocol == "pcp": + test "protocol filter selects UPnP even when PCP is available": + require init( + discoverTimeout = discoverMs.int32, + logLevel = logLevel, + protocol = ProtocolFilter.UPnP, + ) + .isOk() + defer: + discard cleanup() + + let r = waitFor createMapping(TCP, 8111, timeout = mappingTimeout) + require r.isOk() + let res = r.value + defer: + destroyMapping(res.id) + + check res.mapping.mappingProtocol == UPnP + + # miniupnpd in the upnp container has PCP/NAT-PMP disabled, so a PCP filter + # must not fall back to UPnP: the mapping cannot succeed. + when miniupnp_protocol == "upnp": + test "protocol filter PCP does not fall back to UPnP": + require init( + discoverTimeout = discoverMs.int32, + logLevel = logLevel, + protocol = ProtocolFilter.PCP, + ) + .isOk() + defer: + discard cleanup() + + let r = waitFor createMapping(TCP, 8112, timeout = mappingTimeout) + check r.isErr()