Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
InternetGatewayRegistrator.cs
1using System;
2using System.Threading;
3using System.Collections.Generic;
4using System.Net;
5using System.Net.NetworkInformation;
6using System.Net.Sockets;
7using System.Threading.Tasks;
8using Waher.Events;
12
14{
18 public class InternetGatewayRegistrator : IDisposable
19 {
20 internal readonly InternetGatewayRegistration[] ports;
21 internal IPAddress localAddress;
22 internal IPAddress externalAddress;
23 internal Exception exception = null;
24 private readonly ISniffer[] sniffers;
25 private Dictionary<IPAddress, bool> ipAddressesFound = new Dictionary<IPAddress, bool>();
26 private UPnPClient upnpClient = null;
27 private WANIPConnectionV1 serviceWANIPConnectionV1;
28 private PeerToPeerNetworkState state = PeerToPeerNetworkState.Created;
29 private ManualResetEvent ready = new ManualResetEvent(false);
30 private ManualResetEvent error = new ManualResetEvent(false);
31 private Timer searchTimer = null;
32 internal bool disposed = false;
33
40 {
41 this.ports = Ports;
42 this.sniffers = Sniffers;
43
44 NetworkChange.NetworkAddressChanged += this.NetworkChange_NetworkAddressChanged;
45 }
46
47 private async void NetworkChange_NetworkAddressChanged(object Sender, EventArgs e)
48 {
49 try
50 {
51 if (!this.disposed && this.State != PeerToPeerNetworkState.SearchingForGateway) // Multiple events might get fired one after the other. Just start one search.
52 {
53 await this.SetState(PeerToPeerNetworkState.Reinitializing);
54
55 this.ready?.Reset();
56 this.error?.Reset();
57
58 await this.Start();
59 }
60 }
61 catch (Exception ex)
62 {
63 Log.Exception(ex);
64 }
65 }
66
70 public virtual async Task Start()
71 {
72 if (this.OnPublicNetwork())
73 this.localAddress = this.externalAddress;
74 else
75 await this.SearchGateways();
76 }
77
82 public bool OnPublicNetwork()
83 {
84 foreach (NetworkInterface Interface in NetworkInterface.GetAllNetworkInterfaces())
85 {
86 if (Interface.OperationalStatus != OperationalStatus.Up)
87 continue;
88
89 IPInterfaceProperties Properties = Interface.GetIPProperties();
90
91 foreach (UnicastIPAddressInformation UnicastAddress in Properties.UnicastAddresses)
92 {
93 if (!IsPublicAddress(UnicastAddress.Address))
94 continue;
95
96 this.externalAddress = UnicastAddress.Address;
97 return true;
98 }
99 }
100
101 return false;
102 }
103
109 public static bool IsPublicAddress(IPAddress Address)
110 {
111 if (Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4)
112 {
113 byte[] Addr = Address.GetAddressBytes();
114
115 // https://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xhtml
116
117 switch (Addr[0])
118 {
119 case 0:
120 return false; // 000.X.X.X/8: Reserved for self-identification [RFC1122]
121
122 case 10:
123 return false; // 010.X.X.X/8: Reserved for Private-Use Networks [RFC1918]
124
125 case 100: // 100.64.X.X/10 reserved for Shared Address Space [RFC6598].
126 return (Addr[1] & 0xc0) != 64;
127
128 case 127: // 127.X.X.X/8 reserved for Loopback [RFC1122]
129 return false;
130
131 case 169: // 169.254.X.X/16 reserved for Link Local
132 return Addr[1] != 254;
133
134 case 172: // 172.16.0.0/12 reserved for Private-Use Networks
135 return (Addr[1] & 0xf0) != 16;
136
137 case 192:
138 switch (Addr[1])
139 {
140 case 0:
141 switch (Addr[2])
142 {
143 case 0: // 192.0.0.0/24 reserved for IANA IPv4 Special Purpose Address Registry
144 return false;
145
146 case 2: // 192.0.2.X/24 reserved for TEST-NET-1
147 return false;
148
149 default:
150 return true;
151 }
152
153 case 88:
154 return Addr[2] != 99; // 192.88.99.X/24 reserved for 6to4 Relay Anycast
155
156 case 168:
157 return false; // 192.168.0.0/16 reserved for Private-Use Networks
158 }
159 break;
160 }
161
162 return true;
163 }
164 else
165 return false;
166 }
167
171 public async Task SearchGateways()
172 {
173 if (this.disposed || this.ipAddressesFound is null)
174 return;
175
176 try
177 {
178 this.searchTimer?.Dispose();
179 this.searchTimer = null;
180
181 if (this.upnpClient is null)
182 {
183 this.upnpClient = new UPnPClient(this.sniffers);
184 this.upnpClient.OnDeviceFound += this.UpnpClient_OnDeviceFound;
185 }
186
187 lock (this.ipAddressesFound)
188 {
189 this.ipAddressesFound.Clear();
190 }
191
192 await this.SetState(PeerToPeerNetworkState.SearchingForGateway);
193
194 await this.upnpClient.StartSearch("urn:schemas-upnp-org:service:WANIPConnection:1", 1);
195 await this.upnpClient.StartSearch("urn:schemas-upnp-org:service:WANIPConnection:2", 1);
196
197 this.searchTimer = new Timer(this.SearchTimeout, null, 10000, Timeout.Infinite);
198 }
199 catch (Exception ex)
200 {
201 this.exception = ex;
202 await this.SetState(PeerToPeerNetworkState.Error);
203 }
204 }
205
206 private async void SearchTimeout(object State)
207 {
208 try
209 {
210 this.searchTimer?.Dispose();
211 this.searchTimer = null;
212
213 await this.SetState(PeerToPeerNetworkState.Error);
214 }
215 catch (Exception ex)
216 {
217 Log.Exception(ex);
218 }
219 }
220
221 private void Reinitialize(object State)
222 {
223 this.searchTimer?.Dispose();
224 this.searchTimer = null;
225
226 this.NetworkChange_NetworkAddressChanged(this, EventArgs.Empty);
227 }
228
229 private async Task UpnpClient_OnDeviceFound(object Sender, DeviceLocationEventArgs e)
230 {
231 try
232 {
233 lock (this.ipAddressesFound)
234 {
235 if (this.ipAddressesFound.ContainsKey(e.RemoteEndPoint.Address))
236 return;
237
238 this.ipAddressesFound[e.RemoteEndPoint.Address] = true;
239 }
240
242 if (!(Doc is null))
243 {
244 UPnPService Service = Doc.GetService("urn:schemas-upnp-org:service:WANIPConnection:1");
245 if (Service is null)
246 {
247 Service = Doc.GetService("urn:schemas-upnp-org:service:WANIPConnection:2");
248 if (Service is null)
249 return;
250 }
251
252 ServiceDescriptionDocument Scpd = await Service.GetServiceAsync();
253 await this.ServiceRetrieved(Scpd, e.LocalEndPoint);
254 }
255 }
256 catch (Exception ex)
257 {
258 this.exception = ex;
259 await this.SetState(PeerToPeerNetworkState.Error);
260 }
261 }
262
263 private async Task ServiceRetrieved(ServiceDescriptionDocument Scpd, IPEndPoint LocalEndPoint)
264 {
265 try
266 {
267 Dictionary<ushort, bool> TcpPortMapped = new Dictionary<ushort, bool>();
268 Dictionary<ushort, bool> UdpPortMapped = new Dictionary<ushort, bool>();
269 ushort PortMappingIndex;
270
271 this.serviceWANIPConnectionV1 = new WANIPConnectionV1(Scpd);
272 await this.SetState(PeerToPeerNetworkState.RegisteringApplicationInGateway);
273
274 this.serviceWANIPConnectionV1.GetExternalIPAddress(out string NewExternalIPAddress);
275 this.externalAddress = IPAddress.Parse(NewExternalIPAddress);
276
277 Log.Informational("External IP Address: " + NewExternalIPAddress);
278
279 if (!IsPublicAddress(this.externalAddress))
280 {
281 Log.Warning("External IP Address not a public IP address.");
282 return; // TODO: Handle multiple layers of gateways.
283 }
284
285 PortMappingIndex = 0;
286
287 try
288 {
289 string LocalAddress = LocalEndPoint.Address.ToString();
290
291 while (true)
292 {
293 this.serviceWANIPConnectionV1.GetGenericPortMappingEntry(PortMappingIndex, out string NewRemoteHost,
294 out ushort NewExternalPort, out string NewProtocol, out ushort NewInternalPort, out string NewInternalClient,
295 out bool NewEnabled, out string NewPortMappingDescription, out uint NewLeaseDuration);
296
297 if (NewInternalClient != LocalAddress)
298 {
299 PortMappingIndex++;
300 continue;
301 }
302
303 bool Found = false;
304
305 foreach (InternetGatewayRegistration Registration in this.ports)
306 {
307 if ((Registration.ExternalPort != 0 && NewExternalPort == Registration.ExternalPort) ||
308 (Registration.ExternalPort == 0 && NewPortMappingDescription == Registration.ApplicationName))
309 {
310 if (NewProtocol == "TCP")
311 {
312 Found = true;
313 Registration.TcpRegistered = true;
314 break;
315 }
316 else if (NewProtocol == "UDP")
317 {
318 Found = true;
319 Registration.UdpRegistered = true;
320 break;
321 }
322
323 Log.Notice("Deleting Internet Gateway port mapping.",
324 new KeyValuePair<string, object>("Host", NewRemoteHost),
325 new KeyValuePair<string, object>("External Port", NewExternalPort),
326 new KeyValuePair<string, object>("Protocol", NewProtocol),
327 new KeyValuePair<string, object>("Local Port", NewInternalPort),
328 new KeyValuePair<string, object>("Local Address", NewInternalClient),
329 new KeyValuePair<string, object>("Application", NewPortMappingDescription));
330
331 this.serviceWANIPConnectionV1.DeletePortMapping(NewRemoteHost, NewExternalPort, NewProtocol);
332 }
333 }
334
335 if (Found)
336 {
337 PortMappingIndex++;
338 continue;
339 }
340 else
341 {
342 switch (NewProtocol)
343 {
344 case "TCP":
345 TcpPortMapped[NewExternalPort] = true;
346 break;
347
348 case "UDP":
349 UdpPortMapped[NewExternalPort] = true;
350 break;
351 }
352
353 PortMappingIndex++;
354 }
355 }
356 }
357 catch (AggregateException ex)
358 {
359 if (!(ex.InnerException is UPnPException))
360 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
361 }
362 catch (UPnPException)
363 {
364 // No more entries.
365 }
366
367 this.localAddress = LocalEndPoint.Address;
368
369 foreach (InternetGatewayRegistration Registration in this.ports)
370 {
371 await this.BeforeRegistration(Registration, TcpPortMapped, UdpPortMapped);
372
373 if ((Registration.TcpRegistered || !Registration.Tcp) &&
374 (Registration.UdpRegistered || !Registration.Udp))
375 {
376 continue;
377 }
378
379 if (Registration.Tcp && !Registration.TcpRegistered)
380 {
381 Log.Notice("Adding Internet Gateway port mapping.",
382 new KeyValuePair<string, object>("Host", string.Empty),
383 new KeyValuePair<string, object>("External Port", Registration.ExternalPort),
384 new KeyValuePair<string, object>("Protocol", "TCP"),
385 new KeyValuePair<string, object>("Local Port", Registration.LocalPort),
386 new KeyValuePair<string, object>("Local Address", this.LocalAddress.ToString()),
387 new KeyValuePair<string, object>("Application", Registration.ApplicationName));
388
389 try
390 {
391 this.serviceWANIPConnectionV1.AddPortMapping(string.Empty, Registration.ExternalPort,
392 "TCP", Registration.LocalPort, this.LocalAddress.ToString(), true, Registration.ApplicationName, 0);
393
394 Registration.TcpRegistered = true;
395 }
396 catch (Exception ex)
397 {
398 Log.Error("Unable to register port in Internet Gateway: " + ex.Message,
399 new KeyValuePair<string, object>("External Port", Registration.ExternalPort),
400 new KeyValuePair<string, object>("Protocol", "TCP"),
401 new KeyValuePair<string, object>("Local Port", Registration.LocalPort),
402 new KeyValuePair<string, object>("Local Address", this.LocalAddress.ToString()),
403 new KeyValuePair<string, object>("Application", Registration.ApplicationName));
404 }
405 }
406
407 if (Registration.Udp && !Registration.UdpRegistered)
408 {
409 Log.Notice("Adding Internet Gateway port mapping.",
410 new KeyValuePair<string, object>("Host", string.Empty),
411 new KeyValuePair<string, object>("External Port", Registration.ExternalPort),
412 new KeyValuePair<string, object>("Protocol", "UDP"),
413 new KeyValuePair<string, object>("Local Port", Registration.LocalPort),
414 new KeyValuePair<string, object>("Local Address", this.LocalAddress.ToString()),
415 new KeyValuePair<string, object>("Application", Registration.ApplicationName));
416
417 try
418 {
419 this.serviceWANIPConnectionV1.AddPortMapping(string.Empty, Registration.ExternalPort,
420 "UDP", Registration.LocalPort, this.LocalAddress.ToString(), true, Registration.ApplicationName, 0);
421
422 Registration.UdpRegistered = true;
423 }
424 catch (Exception ex)
425 {
426 Log.Error("Unable to register port in Internet Gateway: " + ex.Message,
427 new KeyValuePair<string, object>("External Port", Registration.ExternalPort),
428 new KeyValuePair<string, object>("Protocol", "UDP"),
429 new KeyValuePair<string, object>("Local Port", Registration.LocalPort),
430 new KeyValuePair<string, object>("Local Address", this.LocalAddress.ToString()),
431 new KeyValuePair<string, object>("Application", Registration.ApplicationName));
432 }
433 }
434 }
435
436 await this.SetState(PeerToPeerNetworkState.Ready);
437 }
438 catch (Exception ex)
439 {
440 Log.Exception(ex);
441
442 this.exception = ex;
443 await this.SetState(PeerToPeerNetworkState.Error);
444 }
445 }
446
453 protected virtual Task BeforeRegistration(InternetGatewayRegistration Registration,
454 Dictionary<ushort, bool> TcpPortMapped, Dictionary<ushort, bool> UdpPortMapped)
455 {
456 return Task.CompletedTask; // Do nothing by default.
457 }
458
462 public PeerToPeerNetworkState State => this.state;
463
464 internal async Task SetState(PeerToPeerNetworkState NewState)
465 {
466 if (this.state != NewState)
467 {
468 this.state = NewState;
469
470 switch (NewState)
471 {
472 case PeerToPeerNetworkState.Ready:
473 this.searchTimer?.Dispose();
474 this.searchTimer = null;
475 this.ready?.Set();
476 break;
477
478 case PeerToPeerNetworkState.Error:
479 this.searchTimer?.Dispose();
480 this.searchTimer = null;
481 this.error?.Set();
482
483 this.searchTimer = new Timer(this.Reinitialize, null, 60000, Timeout.Infinite);
484 break;
485 }
486
487 await this.OnStateChange.Raise(this, NewState);
488 }
489 }
490
494 public event EventHandlerAsync<PeerToPeerNetworkState> OnStateChange = null;
495
499 public IPAddress ExternalAddress => this.externalAddress;
500
504 public IPAddress LocalAddress => this.localAddress;
505
509 public Exception Exception => this.exception;
510
515 public bool Wait()
516 {
517 return this.Wait(10000);
518 }
519
525 public bool Wait(int TimeoutMilliseconds)
526 {
527 switch (WaitHandle.WaitAny(new WaitHandle[] { this.ready, this.error }, TimeoutMilliseconds))
528 {
529 case 0:
530 return true;
531
532 case 1:
533 default:
534 return false;
535 }
536 }
537
541 [Obsolete("Use DisposeAsync() instead.")]
542 public void Dispose()
543 {
544 try
545 {
546 this.DisposeAsync().Wait();
547 }
548 catch (Exception ex)
549 {
550 Log.Exception(ex);
551 }
552 }
553
557 public virtual async Task DisposeAsync()
558 {
559 this.disposed = true;
560 await this.SetState(PeerToPeerNetworkState.Closed);
561
562 NetworkChange.NetworkAddressChanged -= this.NetworkChange_NetworkAddressChanged;
563
564 this.searchTimer?.Dispose();
565 this.searchTimer = null;
566
567 foreach (InternetGatewayRegistration Registration in this.ports)
568 {
569 if (Registration.TcpRegistered)
570 {
571 Registration.TcpRegistered = false;
572 try
573 {
574 Log.Notice("Deleting Internet Gateway port mapping.",
575 new KeyValuePair<string, object>("Host", string.Empty),
576 new KeyValuePair<string, object>("External Port", Registration.ExternalPort),
577 new KeyValuePair<string, object>("Protocol", "TCP"),
578 new KeyValuePair<string, object>("Local Port", Registration.LocalPort),
579 new KeyValuePair<string, object>("Application", Registration.ApplicationName));
580
581 this.serviceWANIPConnectionV1.DeletePortMapping(string.Empty, Registration.LocalPort, "TCP");
582 }
583 catch (Exception)
584 {
585 // Ignore
586 }
587 }
588
589 if (Registration.UdpRegistered)
590 {
591 Registration.UdpRegistered = false;
592 try
593 {
594 Log.Notice("Deleting Internet Gateway port mapping.",
595 new KeyValuePair<string, object>("Host", string.Empty),
596 new KeyValuePair<string, object>("External Port", Registration.ExternalPort),
597 new KeyValuePair<string, object>("Protocol", "UDP"),
598 new KeyValuePair<string, object>("Local Port", Registration.LocalPort),
599 new KeyValuePair<string, object>("Application", Registration.ApplicationName));
600
601 this.serviceWANIPConnectionV1.DeletePortMapping(string.Empty, Registration.LocalPort, "UDP");
602 }
603 catch (Exception)
604 {
605 // Ignore
606 }
607 }
608 }
609
610 this.serviceWANIPConnectionV1 = null;
611
612 this.upnpClient?.Dispose();
613 this.upnpClient = null;
614
615 this.ipAddressesFound?.Clear();
616 this.ipAddressesFound = null;
617
618 this.ready?.Dispose();
619 this.ready = null;
620
621 this.error?.Dispose();
622 this.error = null;
623 }
624
631 public IPEndPoint CheckLocalRemoteEndpoint(IPEndPoint RemoteEndPoint)
632 {
633 if (IPAddress.Equals(RemoteEndPoint.Address, this.externalAddress))
634 {
635 this.serviceWANIPConnectionV1.GetSpecificPortMappingEntry(string.Empty, (ushort)RemoteEndPoint.Port, "TCP",
636 out ushort InternalPort, out string InternalClient, out bool _, out string _, out uint _);
637
638 return new IPEndPoint(IPAddress.Parse(InternalClient), InternalPort);
639 }
640 else
641 return RemoteEndPoint;
642 }
643
644 }
645}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
static void Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
Definition: Log.cs:566
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:682
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:334
static void Notice(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a notice event.
Definition: Log.cs:450
Represents a registraing in an UPnP-compatible Internet Gateway.
string ApplicationName
Name of application to be registered.
Manages registration of TCP and UDP ports in an Internet Gateway
async Task SearchGateways()
Searches for Internet Gateways in the network.
virtual async Task Start()
Starts the registration.
Exception Exception
In case State=PeerToPeerNetworkState.Error, this exception object contains details about the error.
static bool IsPublicAddress(IPAddress Address)
Checks if an IPv4 address is public.
EventHandlerAsync< PeerToPeerNetworkState > OnStateChange
Event raised when the state of the peer-to-peer network changes.
IPEndPoint CheckLocalRemoteEndpoint(IPEndPoint RemoteEndPoint)
Checks if a remote endpoint resides in the internal network, and if so, replaces it with the correspo...
bool Wait(int TimeoutMilliseconds)
Waits for the peer-to-peer network object to be ready to receive connections.
bool Wait()
Waits for the peer-to-peer network object to be ready to receive connections.
bool OnPublicNetwork()
If the machine is on a public network.
InternetGatewayRegistrator(InternetGatewayRegistration[] Ports, params ISniffer[] Sniffers)
Manages registration of TCP and UDP ports in an Internet Gateway
PeerToPeerNetworkState State
Current state of the peer-to-peer network object.
virtual Task BeforeRegistration(InternetGatewayRegistration Registration, Dictionary< ushort, bool > TcpPortMapped, Dictionary< ushort, bool > UdpPortMapped)
is called before performing a registration.
Contains the information provided in a Device Description Document, downloaded from a device in the n...
UPnPService GetService(string ServiceType)
Gets a service, given its service type.
Event arguments for completion events when downloading device description documents.
DeviceLocation Location
Device Location information.
Task< DeviceDescriptionDocument > GetDeviceAsync()
Gets a Device Description Document from a device.
Contains the information provided in a Service Description Document, downloaded from a service in the...
void GetSpecificPortMappingEntry(string NewRemoteHost, ushort NewExternalPort, string NewProtocol, out ushort NewInternalPort, out string NewInternalClient, out bool NewEnabled, out string NewPortMappingDescription, out uint NewLeaseDuration)
Generated from SCPD
void AddPortMapping(string NewRemoteHost, ushort NewExternalPort, string NewProtocol, ushort NewInternalPort, string NewInternalClient, bool NewEnabled, string NewPortMappingDescription, uint NewLeaseDuration)
Generated from SCPD
void GetGenericPortMappingEntry(ushort NewPortMappingIndex, out string NewRemoteHost, out ushort NewExternalPort, out string NewProtocol, out ushort NewInternalPort, out string NewInternalClient, out bool NewEnabled, out string NewPortMappingDescription, out uint NewLeaseDuration)
Generated from SCPD
void GetExternalIPAddress(out string NewExternalIPAddress)
Generated from SCPD
void DeletePortMapping(string NewRemoteHost, ushort NewExternalPort, string NewProtocol)
Generated from SCPD
Implements support for the UPnP protocol, as described in: http://upnp.org/specs/arch/UPnP-arch-Devic...
Definition: UPnPClient.cs:21
Task StartSearch()
Starts a search for devices on the network.
Definition: UPnPClient.cs:276
void Dispose()
IDisposable.Dispose
Definition: UPnPClient.cs:386
Contains information about a service.
Definition: UPnPService.cs:12
Task< ServiceDescriptionDocument > GetServiceAsync()
Starts the retrieval of a Service Description Document.
Definition: UPnPService.cs:166
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:11
PeerToPeerNetworkState
State of Peer-to-peer network.