diff --git a/.gitmodules b/.gitmodules index d5737a8..f797a16 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "vendor/libplum"] - path = vendor/libplum - url = https://github.com/paullouisageneau/libplum.git - branch = master + path = vendor/libplum + url = https://github.com/2-towns/libplum.git + branch = feat/protocol-filter 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..65afcba 100644 --- a/libplum/libplum.nim +++ b/libplum/libplum.nim @@ -52,35 +52,40 @@ type plum_log_level_t* {. importc: "plum_log_level_t", header: "plum.h", size: sizeof(cint) .} = enum - PLUM_LOG_LEVEL_VERBOSE = 0 - PLUM_LOG_LEVEL_DEBUG = 1 - PLUM_LOG_LEVEL_INFO = 2 - PLUM_LOG_LEVEL_WARN = 3 - PLUM_LOG_LEVEL_ERROR = 4 - PLUM_LOG_LEVEL_FATAL = 5 - PLUM_LOG_LEVEL_NONE = 6 + PLUM_LOG_LEVEL_VERBOSE + PLUM_LOG_LEVEL_DEBUG + PLUM_LOG_LEVEL_INFO + PLUM_LOG_LEVEL_WARN + PLUM_LOG_LEVEL_ERROR + PLUM_LOG_LEVEL_FATAL + PLUM_LOG_LEVEL_NONE plum_ip_protocol_t* {. importc: "plum_ip_protocol_t", header: "plum.h", size: sizeof(cint) .} = enum - PLUM_IP_PROTOCOL_TCP = 0 - PLUM_IP_PROTOCOL_UDP = 1 + PLUM_IP_PROTOCOL_TCP + PLUM_IP_PROTOCOL_UDP + + plum_protocol_t* {.importc: "plum_protocol_t", header: "plum.h", size: sizeof(cint).} = enum + PLUM_PROTOCOL_ANY + PLUM_PROTOCOL_PCP + PLUM_PROTOCOL_UPNP plum_state_t* {.importc: "plum_state_t", header: "plum.h", size: sizeof(cint).} = enum - PLUM_STATE_DESTROYED = 0 - PLUM_STATE_PENDING = 1 - PLUM_STATE_SUCCESS = 2 - PLUM_STATE_FAILURE = 3 - PLUM_STATE_DESTROYING = 4 + PLUM_STATE_DESTROYED + PLUM_STATE_PENDING + PLUM_STATE_SUCCESS + PLUM_STATE_FAILURE + PLUM_STATE_DESTROYING plum_mapping_protocol_t* {. importc: "plum_mapping_protocol_t", header: "plum.h", size: sizeof(cint) .} = enum - PLUM_MAPPING_PROTOCOL_UNKNOWN = 0 - PLUM_MAPPING_PROTOCOL_PCP = 1 - PLUM_MAPPING_PROTOCOL_NATPMP = 2 - PLUM_MAPPING_PROTOCOL_UPNP = 3 - PLUM_MAPPING_PROTOCOL_DIRECT = 4 + PLUM_MAPPING_PROTOCOL_UNKNOWN + PLUM_MAPPING_PROTOCOL_PCP + PLUM_MAPPING_PROTOCOL_NATPMP + PLUM_MAPPING_PROTOCOL_UPNP + PLUM_MAPPING_PROTOCOL_DIRECT # Define the callback to receive the plum logs plum_log_callback_t* = proc(level: plum_log_level_t, message: cstring) {.cdecl.} @@ -96,6 +101,7 @@ 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..2abc1b5 100644 --- a/libplum/plum.nim +++ b/libplum/plum.nim @@ -34,31 +34,36 @@ export results type PlumProtocol* = enum - TCP = PLUM_IP_PROTOCOL_TCP.int - UDP = PLUM_IP_PROTOCOL_UDP.int + TCP = ord(PLUM_IP_PROTOCOL_TCP) + UDP = ord(PLUM_IP_PROTOCOL_UDP) + + ProtocolFilter* {.pure.} = enum + Any = ord(PLUM_PROTOCOL_ANY) + PCP = ord(PLUM_PROTOCOL_PCP) + UPnP = ord(PLUM_PROTOCOL_UPNP) PlumLogLevel* {.pure.} = enum - Verbose = PLUM_LOG_LEVEL_VERBOSE.int - Debug = PLUM_LOG_LEVEL_DEBUG.int - Info = PLUM_LOG_LEVEL_INFO.int - Warn = PLUM_LOG_LEVEL_WARN.int - Error = PLUM_LOG_LEVEL_ERROR.int - Fatal = PLUM_LOG_LEVEL_FATAL.int - None = PLUM_LOG_LEVEL_NONE.int + Verbose = ord(PLUM_LOG_LEVEL_VERBOSE) + Debug = ord(PLUM_LOG_LEVEL_DEBUG) + Info = ord(PLUM_LOG_LEVEL_INFO) + Warn = ord(PLUM_LOG_LEVEL_WARN) + Error = ord(PLUM_LOG_LEVEL_ERROR) + Fatal = ord(PLUM_LOG_LEVEL_FATAL) + None = ord(PLUM_LOG_LEVEL_NONE) PlumState* = enum - Destroyed = PLUM_STATE_DESTROYED.int - Pending = PLUM_STATE_PENDING.int - Success = PLUM_STATE_SUCCESS.int - Failure = PLUM_STATE_FAILURE.int - Destroying = PLUM_STATE_DESTROYING.int + Destroyed = ord(PLUM_STATE_DESTROYED) + Pending = ord(PLUM_STATE_PENDING) + Success = ord(PLUM_STATE_SUCCESS) + Failure = ord(PLUM_STATE_FAILURE) + Destroying = ord(PLUM_STATE_DESTROYING) MappingProtocol* = enum - Unknown = PLUM_MAPPING_PROTOCOL_UNKNOWN.int - PCP = PLUM_MAPPING_PROTOCOL_PCP.int - NatPmp = PLUM_MAPPING_PROTOCOL_NATPMP.int - UPnP = PLUM_MAPPING_PROTOCOL_UPNP.int - Direct = PLUM_MAPPING_PROTOCOL_DIRECT.int + Unknown = ord(PLUM_MAPPING_PROTOCOL_UNKNOWN) + PCP = ord(PLUM_MAPPING_PROTOCOL_PCP) + NatPmp = ord(PLUM_MAPPING_PROTOCOL_NATPMP) + UPnP = ord(PLUM_MAPPING_PROTOCOL_UPNP) + Direct = ord(PLUM_MAPPING_PROTOCOL_DIRECT) PlumMapping* = object protocol*: PlumProtocol @@ -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() diff --git a/vendor/libplum b/vendor/libplum index e4be508..3df6d2b 160000 --- a/vendor/libplum +++ b/vendor/libplum @@ -1 +1 @@ -Subproject commit e4be508aad4297b190498c7e26552e0f495a9aa1 +Subproject commit 3df6d2b2e37689c572b69af401a6922d9db67541