nat-traversal
If you find that this library lacks some feature you need, or you have a suggestion for improving it, please don’t hesitate to get in touch with me!
1 Introduction
Using this library, you can discover the external IP address of your home router, and can manage port mappings from the public internet to internal TCP and UDP ports.
The library implements
NAT-PMP, the Apple/IETF protocol for opening TCP and UDP ports on home routers, and
UPnP, in particular the "WANIPConnection" service, the Microsoft et al. protocol for doing the same, plus a whole lot more.
It provides both a low-level interface to each of the two protocols as well as a high-level interface that abstracts away from the details of the particular NAT traversal techniques available.
2 How to use the library
(require nat-traversal) | package: nat-traversal |
2.1 The High-Level Interface
The high-level interface to the library lets you automatically manage NAT port mappings by simply changing calls to udp-bind! and tcp-listen to udp-bind!/public and tcp-listen/public, respectively.
Each socket managed by the library is associated with a mapping-change-listener, a background thread that tracks changes to the NAT configuration, keeping a set of "port assignments" up-to-date. A user-supplied callback (on-mapping-change) is called every time the port assignment set changes.
Each port assignment in a set is an address at which the corresponding socket is reachable. A set of port assignments includes both local (internal to the NAT) and public (external to the NAT) addresses.
procedure
(udp-bind!/public udp-socket hostname-string port-no [ #:on-mapping-change on-mapping-change]) → mapping-change-listener? udp-socket : udp? hostname-string : (or/c string? #f)
port-no :
(and/c exact-nonnegative-integer? (integer-in 0 65535))
on-mapping-change :
(-> (set/c port-assignment?) any/c) = void
procedure
(tcp-listen/public port-no [ max-allow-wait reuse? hostname #:on-mapping-change on-mapping-change])
→
tcp-listener? mapping-change-listener?
port-no :
(and/c exact-nonnegative-integer? (integer-in 0 65535)) max-allow-wait : exact-nonnegative-integer? = 4 reuse? : boolean? = #f hostname : (or/c string? #f) = #f
on-mapping-change :
(-> (set/c port-assignment?) any/c) = void
procedure
(mapping-change-listener protocol initial-local-address local-port on-mapping-change) → mapping-change-listener? protocol : (or/c 'tcp 'udp) initial-local-address : string? local-port : (integer-in 0 65535) on-mapping-change : (-> (set/c port-assignment?) any/c)
struct
(struct mapping-change-listener (thread) #:prefab) thread : thread?
struct
(struct port-assignment ( protocol address port nat-traversal-technique) #:prefab) protocol : (or/c 'udp 'tcp) address : string? port : exact-nonnegative-integer? nat-traversal-technique : (or/c 'nat-pmp 'upnp #f)
procedure
→ (set/c port-assignment?) mcl : mapping-change-listener?
procedure
(mapping-change-listener-stop! mcl) → void?
mcl : mapping-change-listener?
2.2 Getting information on local gateways and interfaces
This library provides utilities for discovering and classifying interface IP addresses, and for discovering the local default gateway IP.
procedure
procedure
procedure
the first non-private, non-local IP address found is considered best;
the first private, non-local IP address found is considered second-best;
any other address found is considered third-best;
and if no addresses were found at all, "127.0.0.1" is returned.
procedure
(wildcard-ip-address? addr) → boolean?
addr : string?
procedure
(localhost-ip-address? addr) → boolean?
addr : string?
procedure
(private-ip-address? addr) → boolean?
addr : string?
2.3 Low-level interfaces
2.3.1 Records of established mappings
struct
(struct mapping ( protocol internal-address internal-port external-port lifetime) #:prefab) protocol : (or/c 'udp 'tcp) internal-address : (or/c string? #f) internal-port : integer? external-port : integer? lifetime : integer?
2.3.2 NAT-PMP
(require nat-traversal/nat-pmp)
NAT-PMP depends on being able to learn the IP address of the current default gateway. It does so by calling gateway-ip-address.
Requests made to the gateway using NAT-PMP will eventually time out if the gateway does not support NAT-PMP. When this happens, an exception is raised by the routine making the request.
procedure
(nat-pmp-external-ip-address) → string?
procedure
(nat-pmp-make-persistent-mapping protocol local-port requested-port [ #:refresh-interval refresh-interval #:on-mapping on-mapping]) → persistent-mapping? protocol : (or/c 'udp 'tcp) local-port : integer? requested-port : integer? refresh-interval : integer? = 3600
on-mapping :
(-> (or/c string? #f) (or/c mapping? #f) any/c) = void
Every time the externally mapped port changes (including when the mapping is first established!) the #:on-mapping callback is called with the updated mapping information. Note that the callback is invoked directly from the mapping’s thread - if it raises an exception, it will kill the persistent mapping.
If you can’t or don’t want to use persistent mapping, the following routines let you explicitly manage mappings with the gateway.
procedure
(nat-pmp-map-port! protocol local-port requested-port lifetime-seconds) → mapping? protocol : (or/c 'udp 'tcp) local-port : integer? requested-port : integer? lifetime-seconds : integer?
procedure
(nat-pmp-delete-mapping! mapping) → void?
mapping : mapping?
2.3.3 UPnP
(require nat-traversal/upnp-ip-gateway)
TODO
2.3.4 Managing low-level persistent mappings
procedure
(stop-persistent-mapping! p) → void?
p : persistent-mapping?
procedure
(refresh-persistent-mapping! p) → void?
p : persistent-mapping?
2.3.5 Calling other UPnP services
(require nat-traversal/upnp)
Routines for discovering UPnP services and calling service actions.
parameter
(default-scan-time) → exact-nonnegative-integer?
(default-scan-time seconds) → void? seconds : exact-nonnegative-integer?
struct
(struct exn:fail:upnp exn:fail (code description) #:transparent) code : (or/c exact-nonnegative-integer? #f) description : (or/c exact-nonnegative-integer? #f)
procedure
code : exact-nonnegative-integer?
struct
(struct upnp-service (type control-url event-url scpd-url) #:prefab) type : string? control-url : url? event-url : url? scpd-url : url?
struct
(struct upnp-service-action (name args results) #:prefab) name : string? args : (listof string?) results : (listof string?)
procedure
→
(sequenceof (case-> (-> 'descriptor upnp-service?) (-> 'actions (hash/c string? upnp-service-action?)) (-> string? #:rest (listof string?) (hash/c string? string?)))) scan-time : exact-nonnegative-integer? = (default-scan-time)
Each of the yielded dispatchers is a procedure taking a variable number of arguments:
If the first argument is 'descriptor, the underlying upnp-service is returned.
If the first argument is 'actions, a hashtable mapping action name strings to upnp-service-action instances is returned.
Otherwise, the first argument must be a string naming an action supported by the service being dispatched to. The number of additional arguments must be equal to the number of arguments expected by the named action, and they must all be strings. The result will be a hashtable mapping result name to string result value.
3 References
NAT-PMP is currently defined in an Internet-Draft.
UPnP is a vast expanse of entangled specification.
The core discovery mechanism is SSDP.
Everything else in UPnP is done with SOAP (!) over HTTP.
You can download the 70MB (!) specification zip file from the UPnP forum. Fair warning, it’s not an easy read.