1 module natop.natpmp; 2 3 import natop.network; 4 import natop.exceptions; 5 6 import std.bitmanip : nativeToBigEndian, bigEndianToNative; 7 import std.conv; 8 import std.exception; 9 import std.random; 10 import std.datetime; 11 12 import memutils.vector; 13 14 import vibe.core.core; 15 import vibe.core.net; 16 import vibe.core.log; 17 18 class NATPMP : Router { 19 private: 20 Vector!ubyte m_bytes; 21 UDPConnection m_udp; 22 bool m_hasDevice; 23 IPRoute m_iproute; 24 25 public: 26 @property string id() const { 27 return m_iproute.gateway.toAddressString(); 28 } 29 30 @property bool hasDevice() { 31 return m_hasDevice; 32 } 33 34 this(IPRoute iproute) { 35 m_iproute = iproute; 36 bool has_error; 37 int retries; 38 do { 39 40 has_error = false; 41 ushort port = uniform(2000,65000).to!ushort; 42 try { 43 m_udp = listenUDP(port, iproute.destination.toAddressString()); 44 } 45 catch (Exception e) { 46 logError("Could not bind to port [%d]: %s", port, e.msg); 47 has_error = true; 48 sleep(100.msecs); 49 } 50 enforce(++retries < 2); 51 } while (has_error); 52 m_udp.connect(iproute.gateway.toAddressString(), 5351); 53 } 54 55 ~this() { 56 try m_udp.close(); catch (Exception e) { 57 logError("Error in NAT-PMP Dtor: %s", e.msg); 58 } 59 } 60 61 void discover() { 62 int retries; 63 // send a request and watch for error 64 while (++retries < 2) { 65 ushort trial_port = cast(ushort) uniform(10000,63000); 66 ubyte[12] buf = buildRequest(trial_port, true); 67 m_udp.send(buf.ptr[0 .. 12]); 68 try readResponse(); 69 catch (NATPMPException e) { 70 logError("Error in NATPMP discover: %s", e.msg); 71 return; 72 } 73 catch (TimeoutException e) { 74 logError("Timeout in NATPMP discover: %s", e.msg); 75 continue; 76 } 77 catch (Exception e) { 78 logError("Generic Error in NATPMP discover: %s", e.msg); 79 return; 80 } 81 82 m_hasDevice = true; 83 84 // remove dummy port redirect 85 try { 86 buf = buildRequest(trial_port, true, false); 87 m_udp.send(buf.ptr[0 .. 12]); 88 readResponse(); 89 } catch (Exception e) { 90 logError("Failed to remove dummy port redirect: %s", e.msg); 91 } 92 return; 93 } 94 } 95 96 void createMapping(ushort local_port, ushort external_port, bool is_tcp = true) { 97 ubyte[12] buf = buildRequest(external_port, is_tcp); 98 m_udp.send(buf.ptr[0 .. 12]); 99 readResponse(); 100 } 101 102 void deleteMapping(ushort external_port, bool is_tcp = true) { 103 104 ubyte[12] buf = buildRequest(external_port, is_tcp, false); 105 m_udp.send(buf.ptr[0 .. 12]); 106 readResponse(); 107 } 108 private: 109 ubyte[12] buildRequest(ushort external_port, bool is_tcp, bool adding = true) { 110 ubyte[12] buf; 111 size_t pos; 112 113 buf[pos .. pos+1] = nativeToBigEndian!ubyte(0); ++pos; 114 buf[pos .. pos+1] = nativeToBigEndian!ubyte(is_tcp?2:1); ++pos; 115 buf[pos .. pos+2] = nativeToBigEndian!ushort(0); pos+=2; 116 buf[pos .. pos+2] = nativeToBigEndian!ushort(external_port); pos+=2; 117 buf[pos .. pos+2] = nativeToBigEndian!ushort(external_port); pos+=2; 118 buf[pos .. pos+4] = nativeToBigEndian!uint(adding?3600:0); 119 120 return buf; 121 } 122 123 void readResponse() { 124 size_t pos; 125 ubyte[16] data; 126 m_udp.recv(1.seconds, data.ptr[0 .. 16]); 127 ubyte version_ = bigEndianToNative!ubyte(*cast(ubyte[1]*)(data.ptr+pos)); ++pos; 128 ubyte cmd = bigEndianToNative!ubyte(*cast(ubyte[1]*)(data.ptr+pos)); ++pos; 129 ushort result = bigEndianToNative!ushort(*cast(ubyte[2]*)(data.ptr+pos)); pos+=2; 130 uint time = bigEndianToNative!uint(*cast(ubyte[4]*)(data.ptr+pos)); pos+=4; 131 ushort private_port = bigEndianToNative!ushort(*cast(ubyte[2]*)(data.ptr+pos)); pos+=2; 132 ushort public_port = bigEndianToNative!ushort(*cast(ubyte[2]*)(data.ptr+pos)); pos+=2; 133 uint lifetime = bigEndianToNative!uint(*cast(ubyte[4]*)(data.ptr+pos)); 134 135 bool is_tcp = (cmd - 128 == 1)?false:true; 136 137 static if (LOG) logInfo("Got version %d, cmd %d, result %d, time %d, private_port %d, public_port %d, lifetime %d, is_tcp %s", 138 version_, cmd, result, time, private_port, public_port, lifetime, is_tcp.to!string); 139 140 // validate 141 enforce(version_ == 0, "Invalid NAT-PMP version: " ~ version_.to!string); 142 143 if (result != 0) { 144 string[] errors = [ 145 "Unsupported protocol version", 146 "Not authorized to create port map (enable NAT-PMP on your router)", 147 "Network failure", 148 "Out of resources", 149 "Unsupported opcode" 150 ]; 151 throw new NATPMPException(errors[result-1]); 152 } 153 } 154 } 155