Add Protocol filter

This commit is contained in:
Arnaud 2026-07-08 12:32:08 +04:00
parent de15c06943
commit dc1fe8cb39
No known key found for this signature in database
GPG Key ID: A6C7C781817146FA
4 changed files with 65 additions and 1 deletions

11
api.md
View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()