1 module natop.opener; 2 3 import natop.network; 4 import natop.upnp; 5 import natop.natpmp; 6 import natop.exceptions; 7 import vibe.core.core; 8 import vibe.core.log; 9 import vibe.http.server; 10 11 import std.datetime; 12 import std.string : format; 13 import std.stdio: writeln; 14 import std.algorithm : remove; 15 import std.array : array; 16 import std.conv : to; 17 // returns false on error 18 private Router[string] g_routers; 19 private Mapping[][string] g_registeredMappings; 20 private Mapping[] g_mappings; 21 22 23 struct Mapping { 24 ushort external_port; 25 bool is_tcp; 26 } 27 28 // ports must be closed before application exit 29 void open(ushort port, bool is_tcp = true) { 30 foreach (mapping; g_mappings) 31 if (mapping.external_port == port && mapping.is_tcp == is_tcp) 32 throw new NATOPException(format("Mapping already created: %d, %s", port, is_tcp?"TCP":"UDP")); 33 static if (LOG) logInfo("Opening port: %d", port); 34 foreach (string id, Router router; g_routers) { 35 int retries; 36 do { 37 try { 38 router.createMapping(port, port, is_tcp); 39 g_registeredMappings[id] ~= Mapping(port, is_tcp); 40 } 41 catch (Exception e) { 42 logError("Could not create mapping in router: %s", e.msg); 43 } 44 } while (++retries < 3); 45 } 46 47 g_mappings ~= Mapping(port, is_tcp); 48 } 49 50 void close(ushort port, bool is_tcp = true) { 51 // foreach device, delete mapping 52 53 size_t to_delete; 54 bool deleted; 55 { 56 foreach (i, Mapping mapping; g_mappings) { 57 if (mapping.external_port == port && mapping.is_tcp == is_tcp) 58 { 59 to_delete = i; 60 deleted = true; 61 break; 62 } 63 } 64 if (deleted) { 65 g_mappings = g_mappings.remove(to_delete).array.to!(Mapping[]); 66 } 67 } 68 foreach (string router_id, Mapping[] mappings; g_registeredMappings) { 69 deleted = false; 70 to_delete = 0; 71 foreach (i, mapping; mappings) { 72 if (mapping.external_port == port && mapping.is_tcp == is_tcp) 73 { 74 if (router_id !in g_routers) continue; 75 try { 76 static if (LOG) logInfo("Deleting mapping in close for port %d", port); 77 g_routers[router_id].deleteMapping(mapping.external_port, mapping.is_tcp); 78 to_delete = i; 79 deleted = true; 80 } 81 catch (Exception e) logError("Could not delete mapping: %s", e.msg); 82 break; 83 } 84 } 85 if (deleted) 86 g_registeredMappings[router_id] = mappings.remove(to_delete).array.to!(Mapping[]); 87 88 } 89 static if (LOG) logInfo("Now have registered mappings: %s", g_registeredMappings.to!string); 90 static if (LOG) logInfo("Mappings: %s", g_mappings.to!string); 91 } 92 93 void discover() { 94 Router[string] new_routers; 95 Appender!(Task[]) tasks; 96 IPRoute[] devices = getDeviceListing(); 97 98 logInfo("Discovering %d devices", devices.length); 99 foreach (iproute; devices) { 100 logInfo("Discovering %s", iproute.gateway.toAddressString()); 101 logInfo("Destination %s", iproute.destination.toAddressString()); 102 103 // interrupt the other when one completes a successful port redirect 104 auto mtx = new InterruptibleTaskMutex(); 105 Task upnp_task; 106 Task natpmp_task; 107 scope(success) { tasks ~= upnp_task; tasks ~= natpmp_task; } 108 bool mapping_exists; 109 void createMappings(Router router) { 110 // remember this device to open ports on it when necessary 111 new_routers[router.id] = router; 112 static if (LOG) logInfo("Done discovery"); 113 if (router.id !in g_registeredMappings) { 114 // open the ports on this router 115 foreach (mapping; g_mappings) { 116 router.createMapping(mapping.external_port, mapping.external_port, mapping.is_tcp); 117 g_registeredMappings[router.id] ~= mapping; 118 } 119 } 120 else mapping_exists = true; 121 } 122 123 upnp_task = runTask({ 124 auto upnp = new UPNP(iproute); 125 scope(failure) upnp.destroy(); 126 try upnp.discover(); 127 catch (Exception e) { 128 logError("Error with UPNP in gateway %s: %s", iproute.gateway.toString(), e.msg); 129 //static if (LOG) logInfo("%s", e.toString()); 130 } 131 if (upnp.hasDevice()) { 132 synchronized(mtx) { 133 if (!mapping_exists) { 134 int retry; 135 do { 136 try createMappings(upnp); 137 catch (Exception e) { logError("Error creating mapping: %s", e.msg); } 138 139 } while (++retry < 3); 140 // interrupt the other one 141 if (natpmp_task != Task()) 142 natpmp_task.interrupt(); 143 } 144 } 145 } 146 upnp_task = Task(); 147 }); 148 149 natpmp_task = runTask({ 150 auto natpmp = new NATPMP(iproute); 151 scope(failure) natpmp.destroy(); 152 try natpmp.discover(); 153 catch (Exception e) { 154 logError("Error with NATPMP in gateway %s: %s", iproute.gateway.toString(), e.msg); 155 static if (LOG) logInfo("%s", e.toString()); 156 } 157 if (natpmp.hasDevice) { 158 synchronized(mtx) { 159 if (!mapping_exists) { 160 createMappings(natpmp); 161 // interrupt the other one 162 if (upnp_task != Task()) 163 upnp_task.interrupt(); 164 } 165 } 166 } 167 static if (LOG) logInfo("NATPMP has device: %s", natpmp.hasDevice.to!string); 168 natpmp_task = Task(); 169 }); 170 171 } 172 foreach (t; tasks.data) t.join(); 173 foreach (string id, Router router; g_routers) { 174 if (id !in new_routers) { 175 g_registeredMappings.remove(id); 176 } 177 } 178 g_routers = new_routers; 179 } 180 181 shared static ~this() { 182 foreach (string router_id, Mapping[] mappings; g_registeredMappings) { 183 foreach (mapping; mappings) { 184 g_routers[router_id].deleteMapping(mapping.external_port, mapping.is_tcp); 185 } 186 } 187 } 188 189 shared static this() { 190 //setTimer(10.seconds, { discover(); }, true); 191 } 192 /* 193 void main() { 194 // setLogLevel(LogLevel.trace); 195 // static if (LOG) logInfo("Found devices: %s", getDeviceListing().to!string); 196 197 open(8081, true); 198 HTTPServerSettings settings = new HTTPServerSettings(); 199 settings.port = 8081; 200 settings.bindAddresses = ["0.0.0.0"]; 201 202 listenHTTP(settings, (scope req, scope res) { 203 res.writeBody("OK"); 204 }); 205 runTask( { 206 discover(); 207 }); 208 runEventLoop(); 209 210 close(8081, true); 211 } 212 */