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 */