From 0d1c7cb76a4c40bfe0b6acac89c0a7c684ede955 Mon Sep 17 00:00:00 2001 From: Bitl Date: Sun, 8 Oct 2017 11:30:10 -0700 Subject: [PATCH] preview 2 --- 3DView.rbxl | 162 +++++++++-- Mono.Nat.dll | Bin 42496 -> 0 bytes Open.NAT/AUTHORS | 8 + Open.NAT/LICENSE | 24 ++ Open.NAT/Open.Nat.sln | 35 +++ Open.NAT/Open.Nat/AssemblyInfo.cs | 17 ++ Open.NAT/Open.Nat/Discovery/ISearcher.cs | 39 +++ Open.NAT/Open.Nat/Discovery/Searcher.cs | 115 ++++++++ Open.NAT/Open.Nat/Enums/ProtocolType.cs | 43 +++ .../Open.Nat/EventArgs/DeviceEventArgs.cs | 40 +++ .../Open.Nat/Exceptions/MappingException.cs | 91 ++++++ .../Exceptions/NatDeviceNotFoundException.cs | 69 +++++ Open.NAT/Open.Nat/Finalizer.cs | 37 +++ Open.NAT/Open.Nat/Mapping.cs | 273 ++++++++++++++++++ Open.NAT/Open.Nat/NatDevice.cs | 186 ++++++++++++ Open.NAT/Open.Nat/NatDiscoverer.cs | 160 ++++++++++ Open.NAT/Open.Nat/Open.Nat.csproj | 108 +++++++ Open.NAT/Open.Nat/Open.Nat.nuspec | 51 ++++ Open.NAT/Open.Nat/Open.Nat.snk | Bin 0 -> 596 bytes Open.NAT/Open.Nat/Pmp/PmpConstants.cs | 63 ++++ Open.NAT/Open.Nat/Pmp/PmpNatDevice.cs | 192 ++++++++++++ Open.NAT/Open.Nat/Pmp/PmpSearcher.cs | 145 ++++++++++ Open.NAT/Open.Nat/PortMapper.cs | 21 ++ .../Open.Nat/Upnp/DiscoveryResponseMessage.cs | 55 ++++ .../Upnp/Messages/DiscoverDeviceMessage.cs | 51 ++++ .../Requests/CreatePortMappingMessage.cs | 62 ++++ .../Requests/DeletePortMappingMessage.cs | 52 ++++ .../Requests/GetExternalIPAddressMessage.cs | 40 +++ .../Requests/GetGenericPortMappingEntry.cs | 50 ++++ .../GetSpecificPortMappingEntryMessage.cs | 54 ++++ .../AddPortMappingResponseMessage.cs | 32 ++ .../DeletePortMappingResponseMessage.cs | 32 ++ .../GetExternalIPAddressResponseMessage.cs | 45 +++ ...tGenericPortMappingEntryResponseMessage.cs | 66 +++++ Open.NAT/Open.Nat/Upnp/RequestMessageBase.cs | 37 +++ Open.NAT/Open.Nat/Upnp/ResponseMessageBase.cs | 58 ++++ Open.NAT/Open.Nat/Upnp/SoapClient.cs | 161 +++++++++++ Open.NAT/Open.Nat/Upnp/UpnpConstants.cs | 46 +++ Open.NAT/Open.Nat/Upnp/UpnpNatDevice.cs | 198 +++++++++++++ Open.NAT/Open.Nat/Upnp/UpnpNatDeviceInfo.cs | 60 ++++ Open.NAT/Open.Nat/Upnp/UpnpSearcher.cs | 268 +++++++++++++++++ Open.NAT/Open.Nat/Utils/Extensions.cs | 121 ++++++++ Open.NAT/Open.Nat/Utils/Guard.cs | 28 ++ .../Open.Nat/Utils/IIPAddressesProvider.cs | 38 +++ .../Open.Nat/Utils/IPAddressesProvider.cs | 69 +++++ Open.NAT/Open.Nat/Utils/WellKnownConstants.cs | 36 +++ .../CharacterCustomization.Designer.cs | 2 +- .../CharacterCustomization.resx | 22 +- .../RBXLegacyLauncher/ClientSettings.resx | 4 +- .../RBXLegacyLauncher/DocForm.resx | 4 +- .../RBXLegacyLauncher/LoaderForm.resx | 4 +- .../RBXLegacyLauncher/MainForm.Designer.cs | 47 +-- .../RBXLegacyLauncher/MainForm.cs | 215 ++++++++------ .../RBXLegacyLauncher/MainForm.resx | 10 +- .../RBXLegacyLauncher/QuickConfigure.resx | 4 +- .../RBXLegacyLauncher.csproj | 18 +- .../RBXLegacyLauncher/Resources/Mono.Nat.dll | Bin 42496 -> 0 bytes .../RBXLegacyLauncher/Resources/Open.Nat.dll | Bin 0 -> 72192 bytes .../RichTextBoxExtensions.cs | 2 + .../RBXLegacyLauncher/SDKForm.Designer.cs | 2 +- .../RBXLegacyLauncher/SDKForm.resx | 6 +- .../RBXLegacyLauncher/ServerPrefs.Designer.cs | 41 +-- .../RBXLegacyLauncher/ServerPrefs.cs | 30 +- .../RBXLegacyLauncher/ServerPrefs.resx | 6 +- .../RBXLegacyLauncher/app.config | 2 +- RBXLegacySetup.iss | 4 +- RBXLegacySetupPreview.iss | 6 +- RBXLegacyURI/RBXLegacyURI/Program.cs | 13 +- RBXLegacyURI/RBXLegacyURI/RBXLegacyURI.csproj | 2 + .../RBXLegacyURI/Resources/rbxlegacyicon2.ico | Bin 0 -> 99678 bytes README.md | 8 + 71 files changed, 3764 insertions(+), 226 deletions(-) delete mode 100644 Mono.Nat.dll create mode 100644 Open.NAT/AUTHORS create mode 100644 Open.NAT/LICENSE create mode 100644 Open.NAT/Open.Nat.sln create mode 100644 Open.NAT/Open.Nat/AssemblyInfo.cs create mode 100644 Open.NAT/Open.Nat/Discovery/ISearcher.cs create mode 100644 Open.NAT/Open.Nat/Discovery/Searcher.cs create mode 100644 Open.NAT/Open.Nat/Enums/ProtocolType.cs create mode 100644 Open.NAT/Open.Nat/EventArgs/DeviceEventArgs.cs create mode 100644 Open.NAT/Open.Nat/Exceptions/MappingException.cs create mode 100644 Open.NAT/Open.Nat/Exceptions/NatDeviceNotFoundException.cs create mode 100644 Open.NAT/Open.Nat/Finalizer.cs create mode 100644 Open.NAT/Open.Nat/Mapping.cs create mode 100644 Open.NAT/Open.Nat/NatDevice.cs create mode 100644 Open.NAT/Open.Nat/NatDiscoverer.cs create mode 100644 Open.NAT/Open.Nat/Open.Nat.csproj create mode 100644 Open.NAT/Open.Nat/Open.Nat.nuspec create mode 100644 Open.NAT/Open.Nat/Open.Nat.snk create mode 100644 Open.NAT/Open.Nat/Pmp/PmpConstants.cs create mode 100644 Open.NAT/Open.Nat/Pmp/PmpNatDevice.cs create mode 100644 Open.NAT/Open.Nat/Pmp/PmpSearcher.cs create mode 100644 Open.NAT/Open.Nat/PortMapper.cs create mode 100644 Open.NAT/Open.Nat/Upnp/DiscoveryResponseMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/DiscoverDeviceMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Responses/AddPortMappingResponseMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs create mode 100644 Open.NAT/Open.Nat/Upnp/RequestMessageBase.cs create mode 100644 Open.NAT/Open.Nat/Upnp/ResponseMessageBase.cs create mode 100644 Open.NAT/Open.Nat/Upnp/SoapClient.cs create mode 100644 Open.NAT/Open.Nat/Upnp/UpnpConstants.cs create mode 100644 Open.NAT/Open.Nat/Upnp/UpnpNatDevice.cs create mode 100644 Open.NAT/Open.Nat/Upnp/UpnpNatDeviceInfo.cs create mode 100644 Open.NAT/Open.Nat/Upnp/UpnpSearcher.cs create mode 100644 Open.NAT/Open.Nat/Utils/Extensions.cs create mode 100644 Open.NAT/Open.Nat/Utils/Guard.cs create mode 100644 Open.NAT/Open.Nat/Utils/IIPAddressesProvider.cs create mode 100644 Open.NAT/Open.Nat/Utils/IPAddressesProvider.cs create mode 100644 Open.NAT/Open.Nat/Utils/WellKnownConstants.cs delete mode 100644 RBXLegacyLauncher/RBXLegacyLauncher/Resources/Mono.Nat.dll create mode 100644 RBXLegacyLauncher/RBXLegacyLauncher/Resources/Open.Nat.dll create mode 100644 RBXLegacyURI/RBXLegacyURI/Resources/rbxlegacyicon2.ico diff --git a/3DView.rbxl b/3DView.rbxl index 9a1cda5..918eeb7 100644 --- a/3DView.rbxl +++ b/3DView.rbxl @@ -28,18 +28,18 @@ null 0 - 2.8825047 - 16.8061104 - 19.8420525 - 0.990262866 - -0.0670645982 - 0.121990994 - 3.7252903e-009 - 0.876308203 - 0.481750816 - -0.139210135 - -0.477059931 - 0.8677755 + 21.9769001 + 13.1075497 + -9.91257381 + -0.342057496 + -0.313667655 + 0.885781765 + -7.45057971e-009 + 0.942642868 + 0.333802998 + -0.939678967 + 0.114179812 + -0.322438091 -0.167192921 @@ -324,6 +324,112 @@ end end +script.Parent.MouseButton1Down:connect(onClicked) + true + + + + + + true + true + 4288914085 + 0.400000006 + 4279970357 + 0 + 0 + 0 + BackgroundColor + + 1 + -155 + 1 + -75 + + false + + 0 + 150 + 0 + 20 + + 0 + 0 + Change Background Color + 4283256141 + 0 + false + 2 + 1 + true + 1 + true + + + + false + + LocalScript + function onClicked() + if (game.Lighting.Sky.SkyboxBk == "rbxasset://Sky/blue.jpg") then + game.Lighting.Sky.SkyboxBk = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxDn = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxFt = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxLf = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxRt = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxUp = "rbxasset://Sky/red.jpg" + elseif (game.Lighting.Sky.SkyboxBk == "rbxasset://Sky/red.jpg") then + game.Lighting.Sky.SkyboxBk = "rbxasset://Sky/green.jpg" + game.Lighting.Sky.SkyboxDn = "rbxasset://Sky/green.jpg" + game.Lighting.Sky.SkyboxFt = "rbxasset://Sky/green.jpg" + game.Lighting.Sky.SkyboxLf = "rbxasset://Sky/green.jpg" + game.Lighting.Sky.SkyboxRt = "rbxasset://Sky/green.jpg" + game.Lighting.Sky.SkyboxUp = "rbxasset://Sky/green.jpg" + elseif (game.Lighting.Sky.SkyboxBk == "rbxasset://Sky/green.jpg") then + game.Lighting.Sky.SkyboxBk = "rbxasset://Sky/orange.jpg" + game.Lighting.Sky.SkyboxDn = "rbxasset://Sky/orange.jpg" + game.Lighting.Sky.SkyboxFt = "rbxasset://Sky/orange.jpg" + game.Lighting.Sky.SkyboxLf = "rbxasset://Sky/orange.jpg" + game.Lighting.Sky.SkyboxRt = "rbxasset://Sky/orange.jpg" + game.Lighting.Sky.SkyboxUp = "rbxasset://Sky/orange.jpg" + elseif (game.Lighting.Sky.SkyboxBk == "rbxasset://Sky/orange.jpg") then + game.Lighting.Sky.SkyboxBk = "rbxasset://Sky/black.jpg" + game.Lighting.Sky.SkyboxDn = "rbxasset://Sky/black.jpg" + game.Lighting.Sky.SkyboxFt = "rbxasset://Sky/black.jpg" + game.Lighting.Sky.SkyboxLf = "rbxasset://Sky/black.jpg" + game.Lighting.Sky.SkyboxRt = "rbxasset://Sky/black.jpg" + game.Lighting.Sky.SkyboxUp = "rbxasset://Sky/black.jpg" + elseif (game.Lighting.Sky.SkyboxBk == "rbxasset://Sky/black.jpg") then + game.Lighting.Sky.SkyboxBk = "rbxasset://Sky/white.jpg" + game.Lighting.Sky.SkyboxDn = "rbxasset://Sky/white.jpg" + game.Lighting.Sky.SkyboxFt = "rbxasset://Sky/white.jpg" + game.Lighting.Sky.SkyboxLf = "rbxasset://Sky/white.jpg" + game.Lighting.Sky.SkyboxRt = "rbxasset://Sky/white.jpg" + game.Lighting.Sky.SkyboxUp = "rbxasset://Sky/white.jpg" + elseif (game.Lighting.Sky.SkyboxBk == "rbxasset://Sky/white.jpg") then + game.Lighting.Sky.SkyboxBk = "rbxasset://Sky/greenscreen.jpg" + game.Lighting.Sky.SkyboxDn = "rbxasset://Sky/greenscreen.jpg" + game.Lighting.Sky.SkyboxFt = "rbxasset://Sky/greenscreen.jpg" + game.Lighting.Sky.SkyboxLf = "rbxasset://Sky/greenscreen.jpg" + game.Lighting.Sky.SkyboxRt = "rbxasset://Sky/greenscreen.jpg" + game.Lighting.Sky.SkyboxUp = "rbxasset://Sky/greenscreen.jpg" + elseif (game.Lighting.Sky.SkyboxBk == "rbxasset://Sky/greenscreen.jpg") then + game.Lighting.Sky.SkyboxBk = "rbxasset://Sky/blue.jpg" + game.Lighting.Sky.SkyboxDn = "rbxasset://Sky/blue.jpg" + game.Lighting.Sky.SkyboxFt = "rbxasset://Sky/blue.jpg" + game.Lighting.Sky.SkyboxLf = "rbxasset://Sky/blue.jpg" + game.Lighting.Sky.SkyboxRt = "rbxasset://Sky/blue.jpg" + game.Lighting.Sky.SkyboxUp = "rbxasset://Sky/blue.jpg" + else + game.Lighting.Sky.SkyboxBk = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxDn = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxFt = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxLf = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxRt = "rbxasset://Sky/red.jpg" + game.Lighting.Sky.SkyboxUp = "rbxasset://Sky/red.jpg" + end +end + script.Parent.MouseButton1Down:connect(onClicked) true @@ -331,7 +437,7 @@ script.Parent.MouseButton1Down:connect(onClicked) - + 0 10 @@ -341,68 +447,68 @@ script.Parent.MouseButton1Down:connect(onClicked) true - + CollectionService true - + PhysicsService true - + BadgeService true - + Geometry true - + RenderHooksService true - + SocialService true - + 1000 Debris true - + Instance true - + Instance true - + Selection true - + 4286874756 1 @@ -414,7 +520,7 @@ script.Parent.MouseButton1Down:connect(onClicked) 14:00:00 true - + false Sky @@ -429,19 +535,19 @@ script.Parent.MouseButton1Down:connect(onClicked) - + ChangeHistoryService true - + Instance true - + Instance true diff --git a/Mono.Nat.dll b/Mono.Nat.dll deleted file mode 100644 index 614b2c233228c02d6dcad894ed490c57a9efd9cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42496 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 VRe!gxT;7#^Y?l9_{(sj3{|C9aI;8*r diff --git a/Open.NAT/AUTHORS b/Open.NAT/AUTHORS new file mode 100644 index 0000000..db51ca0 --- /dev/null +++ b/Open.NAT/AUTHORS @@ -0,0 +1,8 @@ +Open.Nat is a Mono.Nat fork + += Mono.Nat authors +Alan McGovern +Ben Motmans + += Open.Nat author +Lucas Ontivero diff --git a/Open.NAT/LICENSE b/Open.NAT/LICENSE new file mode 100644 index 0000000..8d02e21 --- /dev/null +++ b/Open.NAT/LICENSE @@ -0,0 +1,24 @@ + +The MIT License + +Copyright (C) 2006 Alan McGovern +Copyright (C) 2007 Ben Motmans + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/Open.NAT/Open.Nat.sln b/Open.NAT/Open.Nat.sln new file mode 100644 index 0000000..26d76cf --- /dev/null +++ b/Open.NAT/Open.Nat.sln @@ -0,0 +1,35 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Open.Nat", "Open.Nat\Open.Nat.csproj", "{F5D74163-145F-47BF-83DC-D0E07249C6CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documents", "Documents", "{94F0AE28-727F-497A-AC20-EB5B0C5EBA9A}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + AUTHORS = AUTHORS + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F5D74163-145F-47BF-83DC-D0E07249C6CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5D74163-145F-47BF-83DC-D0E07249C6CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5D74163-145F-47BF-83DC-D0E07249C6CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5D74163-145F-47BF-83DC-D0E07249C6CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(OpenDevelopProperties) = preSolution + version = 0.1 + StartupItem = Open.Nat\Open.Nat.csproj + name = Open.Nat + EndGlobalSection +EndGlobal diff --git a/Open.NAT/Open.Nat/AssemblyInfo.cs b/Open.NAT/Open.Nat/AssemblyInfo.cs new file mode 100644 index 0000000..c92f553 --- /dev/null +++ b/Open.NAT/Open.Nat/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Open.Nat")] +[assembly: AssemblyDescription(".NET Library for automatic network address translation")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Open.Nat")] +[assembly: AssemblyCopyright("Copyright Alan McGovern, Ben Motmans, Lucas Ontivero © 2006-2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("c8e81e95-9f15-4eb8-8982-3d2c9cd95dee")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] +[assembly: CLSCompliant(false)] \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Discovery/ISearcher.cs b/Open.NAT/Open.Nat/Discovery/ISearcher.cs new file mode 100644 index 0000000..ef90a74 --- /dev/null +++ b/Open.NAT/Open.Nat/Discovery/ISearcher.cs @@ -0,0 +1,39 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Net; +using System.Threading; + +namespace Open.Nat +{ + internal interface ISearcher + { + void Search(CancellationToken cancellationToken); + IEnumerable Receive(); + NatDevice AnalyseReceivedResponse(IPAddress localAddress, byte[] response, IPEndPoint endpoint); + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Discovery/Searcher.cs b/Open.NAT/Open.Nat/Discovery/Searcher.cs new file mode 100644 index 0000000..2508db0 --- /dev/null +++ b/Open.NAT/Open.Nat/Discovery/Searcher.cs @@ -0,0 +1,115 @@ +// +// Authors: +// Ben Motmans +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Open.Nat +{ + internal abstract class Searcher + { + private readonly List _devices = new List(); + protected List Sockets; + public EventHandler DeviceFound; + internal DateTime NextSearch = DateTime.UtcNow; + + public async Task> Search(CancellationToken cancelationToken) + { + await Task.Factory.StartNew(async _ => + { + NatDiscoverer.TraceSource.LogInfo("Searching for: {0}", GetType().Name); + while (!cancelationToken.IsCancellationRequested) + { + Discover(cancelationToken); + Receive(cancelationToken); + } + CloseSockets(); + }, cancelationToken); + return _devices; + } + + private void Discover(CancellationToken cancelationToken) + { + if(DateTime.UtcNow < NextSearch) return; + + foreach (var socket in Sockets) + { + try + { + Discover(socket, cancelationToken); + } + catch (Exception e) + { + NatDiscoverer.TraceSource.LogError("Error searching {0} - Details:", GetType().Name); + NatDiscoverer.TraceSource.LogError(e.ToString()); + } + } + } + + private void Receive(CancellationToken cancelationToken) + { + foreach (var client in Sockets.Where(x=>x.Available>0)) + { + if(cancelationToken.IsCancellationRequested) return; + + var localHost = ((IPEndPoint)client.Client.LocalEndPoint).Address; + var receivedFrom = new IPEndPoint(IPAddress.None, 0); + var buffer = client.Receive(ref receivedFrom); + var device = AnalyseReceivedResponse(localHost, buffer, receivedFrom); + + if (device != null) RaiseDeviceFound(device); + } + } + + + protected abstract void Discover(UdpClient client, CancellationToken cancelationToken); + + public abstract NatDevice AnalyseReceivedResponse(IPAddress localAddress, byte[] response, IPEndPoint endpoint); + + public void CloseSockets() + { + foreach (var udpClient in Sockets) + { + udpClient.Close(); + } + } + + private void RaiseDeviceFound(NatDevice device) + { + _devices.Add(device); + var handler = DeviceFound; + if(handler!=null) + handler(this, new DeviceEventArgs(device)); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Enums/ProtocolType.cs b/Open.NAT/Open.Nat/Enums/ProtocolType.cs new file mode 100644 index 0000000..ddb4a20 --- /dev/null +++ b/Open.NAT/Open.Nat/Enums/ProtocolType.cs @@ -0,0 +1,43 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +namespace Open.Nat +{ + /// + /// Protocol to allow/disallow + /// + public enum Protocol + { + /// + /// Transport Control Protocol + /// + Tcp, + /// + /// Datagram Protocol + /// + Udp + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/EventArgs/DeviceEventArgs.cs b/Open.NAT/Open.Nat/EventArgs/DeviceEventArgs.cs new file mode 100644 index 0000000..e02ec04 --- /dev/null +++ b/Open.NAT/Open.Nat/EventArgs/DeviceEventArgs.cs @@ -0,0 +1,40 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +namespace Open.Nat +{ + internal class DeviceEventArgs : System.EventArgs + { + public DeviceEventArgs(NatDevice device) + { + Device = device; + } + + public NatDevice Device { get; private set; } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Exceptions/MappingException.cs b/Open.NAT/Open.Nat/Exceptions/MappingException.cs new file mode 100644 index 0000000..d203ee3 --- /dev/null +++ b/Open.NAT/Open.Nat/Exceptions/MappingException.cs @@ -0,0 +1,91 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Runtime.Serialization; +using System.Security.Permissions; + +namespace Open.Nat +{ + /// + /// + /// + [Serializable] + public class MappingException : Exception + { + /// + /// + /// + public int ErrorCode { get; private set; } + + /// + /// + /// + public string ErrorText { get; private set; } + + #region Constructors + + internal MappingException() + { + } + + internal MappingException(string message) + : base(message) + { + } + + internal MappingException(int errorCode, string errorText) + : base(string.Format("Error {0}: {1}", errorCode, errorText)) + { + ErrorCode = errorCode; + ErrorText = errorText; + } + + internal MappingException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected MappingException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + #endregion + + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) throw new ArgumentNullException("info"); + + ErrorCode = info.GetInt32("errorCode"); + ErrorText = info.GetString("errorText"); + base.GetObjectData(info, context); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Exceptions/NatDeviceNotFoundException.cs b/Open.NAT/Open.Nat/Exceptions/NatDeviceNotFoundException.cs new file mode 100644 index 0000000..d40063d --- /dev/null +++ b/Open.NAT/Open.Nat/Exceptions/NatDeviceNotFoundException.cs @@ -0,0 +1,69 @@ +// +// Authors: +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Runtime.Serialization; + +namespace Open.Nat +{ + /// + /// + /// + [Serializable] + public class NatDeviceNotFoundException : Exception + { + /// + /// + /// + public NatDeviceNotFoundException() + { + } + + /// + /// + /// + /// + public NatDeviceNotFoundException(string message) + : base(message) + { + } + + /// + /// + /// + /// + /// + public NatDeviceNotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected NatDeviceNotFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Finalizer.cs b/Open.NAT/Open.Nat/Finalizer.cs new file mode 100644 index 0000000..ce3de66 --- /dev/null +++ b/Open.NAT/Open.Nat/Finalizer.cs @@ -0,0 +1,37 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +namespace Open.Nat +{ + sealed class Finalizer + { + ~Finalizer() + { + NatDiscoverer.TraceSource.LogInfo("Closing ports opened in this session"); + NatDiscoverer.RenewTimer.Dispose(); + NatDiscoverer.ReleaseSessionMappings(); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Mapping.cs b/Open.NAT/Open.Nat/Mapping.cs new file mode 100644 index 0000000..29a17f0 --- /dev/null +++ b/Open.NAT/Open.Nat/Mapping.cs @@ -0,0 +1,273 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Net; + +namespace Open.Nat +{ + enum MappingLifetime + { + Permanent, + Session, + Manual, + ForcedSession + } + + /// + /// Represents a port forwarding entry in the NAT translation table. + /// + public class Mapping + { + private DateTime _expiration; + private int _lifetime; + internal MappingLifetime LifetimeType { get; set; } + + + /// + /// Gets the mapping's description. It is the value stored in the NewPortMappingDescription parameter. + /// The NewPortMappingDescription parameter is a human readable string that describes the connection. + /// It is used in sorme web interfaces of routers so the user can see which program is using what port. + /// + public string Description { get; internal set; } + /// + /// Gets the private ip. + /// + public IPAddress PrivateIP { get; internal set; } + /// + /// Gets the protocol. + /// + public Protocol Protocol { get; internal set; } + /// + /// The PrivatePort parameter specifies the port on a client machine to which all traffic + /// coming in on PublicPort for the protocol specified by + /// Protocol should be forwarded to. + /// + /// Protocol enum + public int PrivatePort { get; internal set; } + /// + /// Gets the public ip. + /// + public IPAddress PublicIP { get; internal set; } + /// + /// Gets the external (visible) port number. + /// It is the value stored in the NewExternalPort parameter . + /// The NewExternalPort parameter is used to specify the TCP or UDP port on the WAN side of the router which should be forwarded. + /// + public int PublicPort { get; internal set; } + /// + /// Gets the lifetime. The Lifetime parameter tells the router how long the portmapping should be active. + /// Since most programs don't know this in advance, it is often set to 0, which means 'unlimited' or 'permanent'. + /// + /// + /// All portmappings are release automatically as part of the shutdown process when NatUtility.ReleaseOnShutdown is true. + /// Permanent portmappings will not be released if the process ends anormally. + /// Since most programs don't know the lifetime in advance, Open.NAT renew all the portmappings (except the permanents) before they expires. So, developers have to close explicitly those portmappings + /// they don't want to remain open for the session. + /// + public int Lifetime + { + get { return _lifetime; } + internal set + { + switch (value) + { + case int.MaxValue: + LifetimeType = MappingLifetime.Session; + _lifetime = 10 * 60; // ten minutes + _expiration = DateTime.UtcNow.AddSeconds(_lifetime);; + break; + case 0: + LifetimeType = MappingLifetime.Permanent; + _lifetime = 0; + _expiration = DateTime.UtcNow; + break; + default: + LifetimeType = MappingLifetime.Manual; + _lifetime = value; + _expiration = DateTime.UtcNow.AddSeconds(_lifetime); + break; + } + } + } + + /// + /// Gets the expiration. The property value is calculated using Lifetime property. + /// + public DateTime Expiration + { + get { return _expiration; } + internal set + { + _expiration = value; + _lifetime = (int)(_expiration - DateTime.UtcNow).TotalSeconds; + } + } + + internal Mapping(Protocol protocol, IPAddress privateIP, int privatePort, int publicPort) + : this(protocol, privateIP, privatePort, publicPort, 0, "Open.Nat") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The protocol. + /// The private ip. + /// The private port. + /// The public port. + /// The lifetime. + /// The description. + internal Mapping(Protocol protocol, IPAddress privateIP, int privatePort, int publicPort, int lifetime, string description) + { + Guard.IsInRange(privatePort, 0, ushort.MaxValue, "privatePort"); + Guard.IsInRange(publicPort, 0, ushort.MaxValue, "publicPort"); + Guard.IsInRange(lifetime, 0, int.MaxValue, "lifetime"); + Guard.IsTrue(protocol == Protocol.Tcp || protocol == Protocol.Udp, "protocol"); + + Protocol = protocol; + PrivateIP = privateIP; + PrivatePort = privatePort; + PublicIP = IPAddress.None; + PublicPort = publicPort; + Lifetime = lifetime; + Description = description; + } + + /// + /// Initializes a new instance of the class. + /// + /// The protocol. + /// The private port. + /// The public port. + /// + /// This constructor initializes a Permanent mapping. The description by deafult is "Open.NAT" + /// + public Mapping(Protocol protocol, int privatePort, int publicPort) + : this(protocol, IPAddress.None, privatePort, publicPort, 0, "Open.NAT") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The protocol. + /// The private port. + /// The public port. + /// The description. + /// + /// This constructor initializes a Permanent mapping. + /// + public Mapping(Protocol protocol, int privatePort, int publicPort, string description) + : this(protocol, IPAddress.None, privatePort, publicPort, int.MaxValue, description) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The protocol. + /// The private port. + /// The public port. + /// The lifetime. + /// The description. + public Mapping(Protocol protocol, int privatePort, int publicPort, int lifetime, string description) + : this(protocol, IPAddress.None, privatePort, publicPort, lifetime, description) + { + } + + internal Mapping(Mapping mapping) + { + PrivateIP = mapping.PrivateIP; + PrivatePort = mapping.PrivatePort; + Protocol = mapping.Protocol; + PublicIP = mapping.PublicIP; + PublicPort = mapping.PublicPort; + LifetimeType = mapping.LifetimeType; + Description = mapping.Description; + _lifetime = mapping._lifetime; + _expiration = mapping._expiration; + } + + /// + /// Determines whether this instance is expired. + /// + /// + /// Permanent mappings never expires. + /// + public bool IsExpired () + { + return LifetimeType != MappingLifetime.Permanent + && LifetimeType != MappingLifetime.ForcedSession + && Expiration < DateTime.UtcNow; + } + + internal bool ShoundRenew() + { + return LifetimeType == MappingLifetime.Session && IsExpired(); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + var m = obj as Mapping; + if (ReferenceEquals(null, m)) return false; + return PublicPort == m.PublicPort && PrivatePort == m.PrivatePort; + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = PublicPort; + hashCode = (hashCode * 397) ^ (PrivateIP != null ? PrivateIP.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ PrivatePort; + return hashCode; + } + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return string.Format("{0} {1} --> {2}:{3} ({4})", + Protocol == Protocol.Tcp ? "Tcp" : "Udp", + PublicPort, + PrivateIP, + PrivatePort, + Description); + } + } +} diff --git a/Open.NAT/Open.Nat/NatDevice.cs b/Open.NAT/Open.Nat/NatDevice.cs new file mode 100644 index 0000000..96797e5 --- /dev/null +++ b/Open.NAT/Open.Nat/NatDevice.cs @@ -0,0 +1,186 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Open.Nat +{ + /// + /// Represents a NAT device and provides access to the operation set that allows + /// open (forward) ports, close ports and get the externa (visible) IP address. + /// + public abstract class NatDevice + { + private readonly HashSet _openedMapping = new HashSet(); + protected DateTime LastSeen { get; private set; } + + internal void Touch() + { + LastSeen = DateTime.Now; + } + + /// + /// Creates the port map asynchronous. + /// + /// The Mapping entry. + /// + /// device.CreatePortMapAsync(new Mapping(Protocol.Tcp, 1700, 1600)); + /// + /// MappingException + public abstract Task CreatePortMapAsync(Mapping mapping); + + /// + /// Deletes a mapped port asynchronous. + /// + /// The Mapping entry. + /// + /// device.DeletePortMapAsync(new Mapping(Protocol.Tcp, 1700, 1600)); + /// + /// MappingException-class + public abstract Task DeletePortMapAsync(Mapping mapping); + + /// + /// Gets all mappings asynchronous. + /// + /// + /// The list of all forwarded ports + /// + /// + /// var mappings = await device.GetAllMappingsAsync(); + /// foreach(var mapping in mappings) + /// { + /// Console.WriteLine(mapping) + /// } + /// + /// MappingException + public abstract Task> GetAllMappingsAsync(); + + /// + /// Gets the external (visible) IP address asynchronous. This is the NAT device IP address + /// + /// + /// The public IP addrees + /// + /// + /// Console.WriteLine("My public IP is: {0}", await device.GetExternalIPAsync()); + /// + /// MappingException + public abstract Task GetExternalIPAsync(); + + /// + /// Gets the specified mapping asynchronous. + /// + /// The protocol. + /// The port. + /// + /// The matching mapping + /// + public abstract Task GetSpecificMappingAsync(Protocol protocol, int port); + + protected void RegisterMapping(Mapping mapping) + { + _openedMapping.Remove(mapping); + _openedMapping.Add(mapping); + } + + protected void UnregisterMapping(Mapping mapping) + { + _openedMapping.RemoveWhere(x => x.Equals(mapping)); + } + + + internal void ReleaseMapping(IEnumerable mappings) + { + var maparr = mappings.ToArray(); + var mapCount = maparr.Length; + NatDiscoverer.TraceSource.LogInfo("{0} ports to close", mapCount); + for (var i = 0; i < mapCount; i++) + { + var mapping = _openedMapping.ElementAt(i); + + try + { + DeletePortMapAsync(mapping); + NatDiscoverer.TraceSource.LogInfo(mapping + " port successfully closed"); + } + catch (Exception) + { + NatDiscoverer.TraceSource.LogError(mapping + " port couldn't be close"); + } + } + } + + internal void ReleaseAll() + { + ReleaseMapping(_openedMapping); + } + + internal void ReleaseSessionMappings() + { + var mappings = from m in _openedMapping + where m.LifetimeType == MappingLifetime.Session + select m; + + ReleaseMapping(mappings); + } + + internal async Task RenewMappings() + { + var mappings = _openedMapping.Where(x => x.ShoundRenew()); + foreach (var mapping in mappings.ToArray()) + { + var m = mapping; + await RenewMapping(m); + } + } + + private async Task RenewMapping(Mapping mapping) + { + var renewMapping = new Mapping(mapping); + try + { + renewMapping.Expiration = DateTime.UtcNow.AddSeconds(mapping.Lifetime); + + NatDiscoverer.TraceSource.LogInfo("Renewing mapping {0}", renewMapping); + await CreatePortMapAsync(renewMapping); + NatDiscoverer.TraceSource.LogInfo("Next renew scheduled at: {0}", + renewMapping.Expiration.ToLocalTime().TimeOfDay); + } + catch (Exception) + { + NatDiscoverer.TraceSource.LogWarn("Renew {0} failed", mapping); + } + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/NatDiscoverer.cs b/Open.NAT/Open.Nat/NatDiscoverer.cs new file mode 100644 index 0000000..ee14aa1 --- /dev/null +++ b/Open.NAT/Open.Nat/NatDiscoverer.cs @@ -0,0 +1,160 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Open.Nat +{ + /// + /// + /// + public class NatDiscoverer + { + /// + /// The TraceSource instance + /// used for debugging and Troubleshooting + /// + /// + /// NatUtility.TraceSource.Switch.Level = SourceLevels.Verbose; + /// NatUtility.TraceSource.Listeners.Add(new ConsoleListener()); + /// + /// + /// At least one trace listener has to be added to the Listeners collection if a trace is required; if no listener is added + /// there will no be tracing to analyse. + /// + /// + /// Open.NAT only supports SourceLevels.Verbose, SourceLevels.Error, SourceLevels.Warning and SourceLevels.Information. + /// + public readonly static TraceSource TraceSource = new TraceSource("Open.NAT"); + + private static readonly Dictionary Devices = new Dictionary(); + + // Finalizer is never used however its destructor, that releases the open ports, is invoked by the + // process as part of the shuting down step. So, don't remove it! + private static readonly Finalizer Finalizer = new Finalizer(); + internal static readonly Timer RenewTimer = new Timer(RenewMappings, null, 5000, 2000); + + /// + /// Discovers and returns an UPnp or Pmp NAT device; otherwise a NatDeviceNotFoundException + /// exception is thrown after 3 seconds. + /// + /// A NAT device + /// when no NAT found before 3 seconds. + public async Task DiscoverDeviceAsync() + { + var cts = new CancellationTokenSource(3 * 1000); + return await DiscoverDeviceAsync(PortMapper.Pmp | PortMapper.Upnp, cts); + } + + /// + /// Discovers and returns a NAT device for the specified type; otherwise a NatDeviceNotFoundException + /// exception is thrown when it is cancelled. + /// + /// + /// It allows to specify the NAT type to discover as well as the cancellation token in order. + /// + /// Port mapper protocol; Upnp, Pmp or both + /// Cancellation token source for cancelling the discovery process + /// A NAT device + /// when no NAT found before cancellation + public async Task DiscoverDeviceAsync(PortMapper portMapper, CancellationTokenSource cancellationTokenSource) + { + Guard.IsTrue(portMapper == PortMapper.Upnp || portMapper == PortMapper.Pmp, "poertMapper"); + Guard.IsNotNull(cancellationTokenSource, "cancellationTokenSource"); + + var devices = await DiscoverAsync(portMapper, true, cancellationTokenSource); + var device = devices.FirstOrDefault(); + if(device==null) + { + throw new NatDeviceNotFoundException(); + } + return device; + } + + /// + /// Discovers and returns all NAT devices for the specified type. If no NAT device is found it returns an empty enumerable + /// + /// Port mapper protocol; Upnp, Pmp or both + /// Cancellation token source for cancelling the discovery process + /// All found NAT devices + public async Task> DiscoverDevicesAsync(PortMapper portMapper, CancellationTokenSource cancellationTokenSource) + { + Guard.IsTrue(portMapper == PortMapper.Upnp || portMapper == PortMapper.Pmp, "poertMapper"); + Guard.IsNotNull(cancellationTokenSource, "cancellationTokenSource"); + + var devices = await DiscoverAsync(portMapper, false, cancellationTokenSource); + return devices.ToArray(); + } + + private async Task> DiscoverAsync(PortMapper portMapper, bool onlyOne, CancellationTokenSource cts) + { + TraceSource.LogInfo("Start Discovery"); + var searcherTasks = new List>>(); + if(portMapper.HasFlag(PortMapper.Upnp)) + { + var upnpSearcher = new UpnpSearcher(new IPAddressesProvider()); + upnpSearcher.DeviceFound += (sender, args) => { if (onlyOne) cts.Cancel(); }; + searcherTasks.Add(upnpSearcher.Search(cts.Token)); + } + if(portMapper.HasFlag(PortMapper.Pmp)) + { + var pmpSearcher = new PmpSearcher(new IPAddressesProvider()); + pmpSearcher.DeviceFound += (sender, args) => { if (onlyOne) cts.Cancel(); }; + searcherTasks.Add(pmpSearcher.Search(cts.Token)); + } + + await Task.WhenAll(searcherTasks); + TraceSource.LogInfo("Stop Discovery"); + + var devices = searcherTasks.SelectMany(x => x.Result); + foreach (var device in devices) + { + var key = device.ToString(); + NatDevice nat; + if(Devices.TryGetValue(key, out nat)) + { + nat.Touch(); + } + else + { + Devices.Add(key, device); + } + } + return devices; + } + + /// + /// Release all ports opened by Open.NAT. + /// + /// + /// If ReleaseOnShutdown value is true, it release all the mappings created through the library. + /// + public static void ReleaseAll() + { + foreach (var device in Devices.Values) + { + device.ReleaseAll(); + } + } + + internal static void ReleaseSessionMappings() + { + foreach (var device in Devices.Values) + { + device.ReleaseSessionMappings(); + } + } + + private static void RenewMappings(object state) + { + Task.Factory.StartNew(async ()=> + { + foreach (var device in Devices.Values) + { + await device.RenewMappings(); + } + }); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Open.Nat.csproj b/Open.NAT/Open.Nat/Open.Nat.csproj new file mode 100644 index 0000000..fd832a5 --- /dev/null +++ b/Open.NAT/Open.Nat/Open.Nat.csproj @@ -0,0 +1,108 @@ + + + + Debug + AnyCPU + {F5D74163-145F-47BF-83DC-D0E07249C6CA} + Library + false + Open.Nat + Open.Nat + 8.0.50727 + 2.0 + v4.5 + + + + + 2.0 + + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + 4 + true + Library + Open.Nat + AllRules.ruleset + false + bin\Debug\Open.Nat.XML + + + true + pdbonly + true + bin\Release\ + TRACE + 4 + true + Library + Open.Nat + AllRules.ruleset + false + + + true + + + Open.Nat.snk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Open.Nat.nuspec b/Open.NAT/Open.Nat/Open.Nat.nuspec new file mode 100644 index 0000000..f97ed68 --- /dev/null +++ b/Open.NAT/Open.Nat/Open.Nat.nuspec @@ -0,0 +1,51 @@ + + + + Open.NAT + $version$ + Open.NAT + Alan McGovern, Ben Motmans, Lucas Ontivero + http://opensource.org/licenses/MIT + https://github.com/lontivero/Open.NAT + http://github.com/lontivero/Open.Nat/raw/gh-pages/images/logos/64.jpg + false + + Open.NAT is a lightweight and easy-to-use class library to allow port forwarding in NAT devices (Network Address Translator) that support Universal Plug and Play (UPNP) and/or Port Mapping Protocol (PMP). + + Library to allow port forwarding in NAT devices that support UPNP and/or PMP. + en-US + Please see LICENSE for more details. + NAT TRANSVERSAL UPNP PMP PORT FORWARD + + **Version 2.0.8** + Fixes several defects. #10, #11, #12, #13 and #14 + + **2.0.0** + Thus version breaks backward compatibility with v1. + Changes the event-based discovery process' nature to an asynchronous one. + + **1.1.0** + Fix for SSDP Location header. + After this version Open.NAT breaks backward compatibility. + + **1.0.19** + Minor changes previous to v2. + + **1.0.18** + Discovery timeout raises an event. + Permanent mappings are created when NAT only supports that kind of mappings. + Protocol to use in discovery process can be specified. + Automatic renew port mappings before expiration. + Add operations timeout after 4 seconds. + Add parameters validation in Mapping class. + Fix UnhandledException event was never raised. + + **1.0.17** + Discovery timeout added. + Auto release ports opened in the session. + Fix NextSearch to use UtcNow (also performance) + Fix LocalIP property after add a port. + Tracing improvements + + + diff --git a/Open.NAT/Open.Nat/Open.Nat.snk b/Open.NAT/Open.Nat/Open.Nat.snk new file mode 100644 index 0000000000000000000000000000000000000000..99881633d93ae3ff6d1781a199d4ca41516cdf42 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096^1l{`1E8AWSfB@`lJ-#kn2|1UDd7_46qKZX{}vOA zVmSgq;n>trcC0%0dq?_b)C-}KiZ|=vb3s>yqB`h%VHx&6c<@ZKxauek`}5Y{Lw~=s z3Zy^tR#s*XalqQOM+L7-`6NC#rifVT8VxP_97M{08F0gO^=2pqw)}VkwohM)b+;eAe2sVhGh>gxPs_6V6v>!${P@Z;_EOE`+=m_)0pC{btlp) zVc0%P&jtSxts^~71C?_agBN*-_(IX)tvoLQdp>qKGk>{olewbhMeSz5a3r3lREq9z z)!DW%R+=}XExUvbjaot_=g{co5IQU8sC$?WV9i@IQHXV;pXgQPF6Qhu^$q7#gAJbZ zXcO*B>L!q_&}_d>Dm@&utL;|_ZC%gXVLsw! zTwo;bj^H9xL+hv+WUgo zQ-qDnZK*PJYGxKH62me!aSm)<_M9=`^fM i{Y9&Hc=!^6f^rOXIa%)u4%&q{Fc`d)+120rAO`|giz5R7 literal 0 HcmV?d00001 diff --git a/Open.NAT/Open.Nat/Pmp/PmpConstants.cs b/Open.NAT/Open.Nat/Pmp/PmpConstants.cs new file mode 100644 index 0000000..313c0db --- /dev/null +++ b/Open.NAT/Open.Nat/Pmp/PmpConstants.cs @@ -0,0 +1,63 @@ +// +// Authors: +// Ben Motmans +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +namespace Open.Nat +{ + internal static class PmpConstants + { + public const byte Version = 0; + + public const byte OperationExternalAddressRequest = 0; + public const byte OperationCodeUdp = 1; + public const byte OperationCodeTcp = 2; + public const byte ServerNoop = 128; + + public const int ClientPort = 5350; + public const int ServerPort = 5351; + + public const int RetryDelay = 250; + public const int RetryAttempts = 9; + + public const int RecommendedLeaseTime = 60*60; + public const int DefaultLeaseTime = RecommendedLeaseTime; + + public const short ResultCodeSuccess = 0; // Success + public const short ResultCodeUnsupportedVersion = 1; // Unsupported Version + + public const short ResultCodeNotAuthorized = 2; + // Not Authorized/Refused (e.g. box supports mapping, but user has turned feature off) + + public const short ResultCodeNetworkFailure = 3; + // Network Failure (e.g. NAT box itself has not obtained a DHCP lease) + + public const short ResultCodeOutOfResources = 4; + // Out of resources (NAT box cannot create any more mappings at this time) + + public const short ResultCodeUnsupportedOperationCode = 5; // Unsupported opcode + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Pmp/PmpNatDevice.cs b/Open.NAT/Open.Nat/Pmp/PmpNatDevice.cs new file mode 100644 index 0000000..beb2669 --- /dev/null +++ b/Open.NAT/Open.Nat/Pmp/PmpNatDevice.cs @@ -0,0 +1,192 @@ +// +// Authors: +// Ben Motmans +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Open.Nat +{ + internal sealed class PmpNatDevice : NatDevice + { + private readonly IPAddress _publicAddress; + + internal PmpNatDevice(IPAddress localAddress, IPAddress publicAddress) + { + LocalAddress = localAddress; + _publicAddress = publicAddress; + } + + internal IPAddress LocalAddress { get; private set; } + + public override async Task CreatePortMapAsync(Mapping mapping) + { + await InternalCreatePortMapAsync(mapping, true) + .TimeoutAfter(TimeSpan.FromSeconds(4)); + RegisterMapping(mapping); + } + + public override async Task DeletePortMapAsync(Mapping mapping) + { + await InternalCreatePortMapAsync(mapping, false) + .TimeoutAfter(TimeSpan.FromSeconds(4)); + UnregisterMapping(mapping); + } + + public override Task> GetAllMappingsAsync() + { + throw new NotSupportedException(); + } + + public override Task GetExternalIPAsync() + { + return Task.Run(() => _publicAddress) + .TimeoutAfter(TimeSpan.FromSeconds(4)); + } + + public override Task GetSpecificMappingAsync(Protocol protocol, int port) + { + throw new NotSupportedException("NAT-PMP does not specify a way to get a specific port map"); + } + + + private async Task InternalCreatePortMapAsync(Mapping mapping, bool create) + { + var package = new List(); + + package.Add(PmpConstants.Version); + package.Add(mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp); + package.Add(0); //reserved + package.Add(0); //reserved + package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short) mapping.PrivatePort))); + package.AddRange( + BitConverter.GetBytes(create ? IPAddress.HostToNetworkOrder((short) mapping.PublicPort) : (short) 0)); + package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(mapping.Lifetime))); + + try + { + byte[] buffer = package.ToArray(); + int attempt = 0; + int delay = PmpConstants.RetryDelay; + + using (var udpClient = new UdpClient()) + { + CreatePortMapListen(udpClient, mapping); + + while (attempt < PmpConstants.RetryAttempts) + { + await + udpClient.SendAsync(buffer, buffer.Length, + new IPEndPoint(LocalAddress, PmpConstants.ServerPort)); + + attempt++; + delay *= 2; + Thread.Sleep(delay); + } + } + } + catch (Exception e) + { + string type = create ? "create" : "delete"; + string message = String.Format("Failed to {0} portmap (protocol={1}, private port={2})", + type, + mapping.Protocol, + mapping.PrivatePort); + NatDiscoverer.TraceSource.LogError(message); + var pmpException = e as MappingException; + throw new MappingException(message, pmpException); + } + + return mapping; + } + + private void CreatePortMapListen(UdpClient udpClient, Mapping mapping) + { + var endPoint = new IPEndPoint(LocalAddress, PmpConstants.ServerPort); + + while (true) + { + byte[] data = udpClient.Receive(ref endPoint); + + if (data.Length < 16) + continue; + + if (data[0] != PmpConstants.Version) + continue; + + var opCode = (byte) (data[1] & 127); + + var protocol = Protocol.Tcp; + if (opCode == PmpConstants.OperationCodeUdp) + protocol = Protocol.Udp; + + short resultCode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 2)); + int epoch = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 4)); + + short privatePort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 8)); + short publicPort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10)); + + var lifetime = (uint) IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 12)); + + if (privatePort < 0 || publicPort < 0 || resultCode != PmpConstants.ResultCodeSuccess) + { + var errors = new[] + { + "Success", + "Unsupported Version", + "Not Authorized/Refused (e.g. box supports mapping, but user has turned feature off)" + , + "Network Failure (e.g. NAT box itself has not obtained a DHCP lease)", + "Out of resources (NAT box cannot create any more mappings at this time)", + "Unsupported opcode" + }; + throw new MappingException(resultCode, errors[resultCode]); + } + + if (lifetime == 0) return; //mapping was deleted + + //mapping was created + //TODO: verify that the private port+protocol are a match + mapping.PublicPort = publicPort; + mapping.Protocol = protocol; + mapping.Expiration = DateTime.Now.AddSeconds(lifetime); + return; + } + } + + + public override string ToString() + { + return String.Format("Local Address: {0}\nPublic IP: {1}\nLast Seen: {2}", + LocalAddress, _publicAddress, LastSeen); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Pmp/PmpSearcher.cs b/Open.NAT/Open.Nat/Pmp/PmpSearcher.cs new file mode 100644 index 0000000..7f64322 --- /dev/null +++ b/Open.NAT/Open.Nat/Pmp/PmpSearcher.cs @@ -0,0 +1,145 @@ +// +// Authors: +// Ben Motmans +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Open.Nat +{ + internal class PmpSearcher : Searcher + { + private readonly IIPAddressesProvider _ipprovider; + private Dictionary> _gatewayLists; + private int _timeout; + + internal PmpSearcher(IIPAddressesProvider ipprovider) + { + _ipprovider = ipprovider; + _timeout = 250; + CreateSocketsAndAddGateways(); + } + + private void CreateSocketsAndAddGateways() + { + Sockets = new List(); + _gatewayLists = new Dictionary>(); + + try + { + List gatewayList = _ipprovider.GatewayAddresses() + .Select(ip => new IPEndPoint(ip, PmpConstants.ServerPort)) + .ToList(); + + if (!gatewayList.Any()) + { + gatewayList.AddRange( + _ipprovider.DnsAddresses() + .Select(ip => new IPEndPoint(ip, PmpConstants.ServerPort))); + } + + if (!gatewayList.Any()) return; + + foreach (IPAddress address in _ipprovider.UnicastAddresses()) + { + UdpClient client; + + try + { + client = new UdpClient(new IPEndPoint(address, 0)); + } + catch (SocketException) + { + continue; // Move on to the next address. + } + + _gatewayLists.Add(client, gatewayList); + Sockets.Add(client); + } + } + catch (Exception e) + { + NatDiscoverer.TraceSource.LogError("There was a problem finding gateways: " + e); + // NAT-PMP does not use multicast, so there isn't really a good fallback. + } + } + + protected override void Discover(UdpClient client, CancellationToken cancelationToken) + { + // Sort out the time for the next search first. The spec says the + // timeout should double after each attempt. Once it reaches 64 seconds + // (and that attempt fails), assume no devices available + NextSearch = DateTime.UtcNow.AddMilliseconds(_timeout); + _timeout *= 2; + + if (_timeout >= 3000) + { + _timeout = 250; + NextSearch = DateTime.UtcNow.AddSeconds(10); + return; + } + + // The nat-pmp search message. Must be sent to GatewayIP:53531 + var buffer = new[] {PmpConstants.Version, PmpConstants.OperationExternalAddressRequest}; + foreach (IPEndPoint gatewayEndpoint in _gatewayLists[client]) + { + if (cancelationToken.IsCancellationRequested) return; + + client.Send(buffer, buffer.Length, gatewayEndpoint); + } + } + + private bool IsSearchAddress(IPAddress address) + { + return _gatewayLists.Values.SelectMany(x => x) + .Any(x => x.Address.Equals(address)); + } + + public override NatDevice AnalyseReceivedResponse(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + if (!IsSearchAddress(endpoint.Address) + || response.Length != 12 + || response[0] != PmpConstants.Version + || response[1] != PmpConstants.ServerNoop) + return null; + + int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); + if (errorcode != 0) + NatDiscoverer.TraceSource.LogError("Non zero error: {0}", errorcode); + + var publicIp = new IPAddress(new[] {response[8], response[9], response[10], response[11]}); + //NextSearch = DateTime.Now.AddMinutes(5); + + _timeout = 250; + return new PmpNatDevice(endpoint.Address, publicIp); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/PortMapper.cs b/Open.NAT/Open.Nat/PortMapper.cs new file mode 100644 index 0000000..35abd7e --- /dev/null +++ b/Open.NAT/Open.Nat/PortMapper.cs @@ -0,0 +1,21 @@ +using System; + +namespace Open.Nat +{ + /// + /// Protocol that should be used for searching a NAT device. + /// + [Flags] + public enum PortMapper + { + /// + /// Use only Port Mapping Protocol + /// + Pmp = 1, + + /// + /// Use only Universal Plug and Play + /// + Upnp = 2 + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/DiscoveryResponseMessage.cs b/Open.NAT/Open.Nat/Upnp/DiscoveryResponseMessage.cs new file mode 100644 index 0000000..692dcb9 --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/DiscoveryResponseMessage.cs @@ -0,0 +1,55 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Open.Nat +{ + class DiscoveryResponseMessage + { + private readonly IDictionary _headers; + + public DiscoveryResponseMessage(string message) + { + var lines = message.Split(new[]{"\r\n"}, StringSplitOptions.RemoveEmptyEntries); + var headers = from h in lines.Skip(1) + let c = h.Split(':') + let key = c[0] + let value = c.Length > 1 + ? string.Join(":", c.Skip(1)) + : string.Empty + select new {Key = key, Value = value.Trim()}; + _headers = headers.ToDictionary(x => x.Key.ToUpperInvariant(), x => x.Value); + } + + public string this[string key] + { + get { return _headers[key.ToUpperInvariant()]; } + } + } +} diff --git a/Open.NAT/Open.Nat/Upnp/Messages/DiscoverDeviceMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/DiscoverDeviceMessage.cs new file mode 100644 index 0000000..5a358b5 --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/DiscoverDeviceMessage.cs @@ -0,0 +1,51 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Globalization; + +namespace Open.Nat +{ + internal static class DiscoverDeviceMessage + { + /// + /// The message sent to discover all uPnP devices on the network + /// + /// + public static string Encode(string serviceType) + { + const string s = "M-SEARCH * HTTP/1.1\r\n" + + "HOST: 239.255.255.250:1900\r\n" + + "MAN: \"ssdp:discover\"\r\n" + + "MX: 3\r\n" + // + "ST: urn:schemas-upnp-org:service:WANIPConnection:1\r\n\r\n"; + + "ST: urn:schemas-upnp-org:service:{0}\r\n\r\n"; + //+ "ST:upnp:rootdevice\r\n\r\n"; + + string ss = string.Format(CultureInfo.InvariantCulture, s, serviceType); + return ss; + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs new file mode 100644 index 0000000..cf37329 --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs @@ -0,0 +1,62 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Net; + +namespace Open.Nat +{ + internal class CreatePortMappingRequestMessage : RequestMessageBase + { + private readonly Mapping _mapping; + + public CreatePortMappingRequestMessage(Mapping mapping) + { + _mapping = mapping; + } + + public override IDictionary ToXml() + { + string remoteHost = _mapping.PublicIP.Equals(IPAddress.None) + ? string.Empty + : _mapping.PublicIP.ToString(); + + return new Dictionary + { + {"NewRemoteHost", remoteHost}, + {"NewExternalPort", _mapping.PublicPort}, + {"NewProtocol", _mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP"}, + {"NewInternalPort", _mapping.PrivatePort}, + {"NewInternalClient", _mapping.PrivateIP}, + {"NewEnabled", 1}, + {"NewPortMappingDescription", _mapping.Description}, + {"NewLeaseDuration", _mapping.Lifetime} + }; + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs new file mode 100644 index 0000000..33aa02f --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs @@ -0,0 +1,52 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; + +namespace Open.Nat +{ + internal class DeletePortMappingRequestMessage : RequestMessageBase + { + private readonly Mapping _mapping; + + public DeletePortMappingRequestMessage(Mapping mapping) + { + _mapping = mapping; + } + + public override IDictionary ToXml() + { + return new Dictionary + { + {"NewRemoteHost", string.Empty}, + {"NewExternalPort", _mapping.PublicPort}, + {"NewProtocol", _mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP"} + }; + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs new file mode 100644 index 0000000..3ae17a4 --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs @@ -0,0 +1,40 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; + +namespace Open.Nat +{ + internal class GetExternalIPAddressRequestMessage : RequestMessageBase + { + public override IDictionary ToXml() + { + return new Dictionary(); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs b/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs new file mode 100644 index 0000000..cd612dd --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs @@ -0,0 +1,50 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; + +namespace Open.Nat +{ + internal class GetGenericPortMappingEntry : RequestMessageBase + { + private readonly int _index; + + public GetGenericPortMappingEntry(int index) + { + _index = index; + } + + public override IDictionary ToXml() + { + return new Dictionary + { + {"NewPortMappingIndex", _index} + }; + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs new file mode 100644 index 0000000..d7d4191 --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs @@ -0,0 +1,54 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; + +namespace Open.Nat +{ + internal class GetSpecificPortMappingEntryRequestMessage : RequestMessageBase + { + private readonly int _externalPort; + private readonly Protocol _protocol; + + public GetSpecificPortMappingEntryRequestMessage(Protocol protocol, int externalPort) + { + _protocol = protocol; + _externalPort = externalPort; + } + + public override IDictionary ToXml() + { + return new Dictionary + { + {"NewRemoteHost", string.Empty}, + {"NewExternalPort", _externalPort}, + {"NewProtocol", _protocol == Protocol.Tcp ? "TCP" : "UDP"} + }; + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Responses/AddPortMappingResponseMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/Responses/AddPortMappingResponseMessage.cs new file mode 100644 index 0000000..fead7eb --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Responses/AddPortMappingResponseMessage.cs @@ -0,0 +1,32 @@ +// +// Authors: +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +namespace Open.Nat +{ + internal class AddPortMappingResponseMessage : ResponseMessageBase + { + } +} diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs new file mode 100644 index 0000000..705c08c --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs @@ -0,0 +1,32 @@ +// +// Authors: +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +namespace Open.Nat +{ + internal class DeletePortMappingResponseMessage : ResponseMessageBase + { + } +} diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs new file mode 100644 index 0000000..4d56355 --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs @@ -0,0 +1,45 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Net; +using System.Xml; + +namespace Open.Nat +{ + internal class GetExternalIPAddressResponseMessage : ResponseMessageBase + { + public GetExternalIPAddressResponseMessage(XmlDocument response, string serviceType) + : base(response, serviceType, "GetExternalIPAddressResponseMessage") + { + string ip = GetNode().GetXmlElementText("NewExternalIPAddress"); + ExternalIPAddress = IPAddress.Parse(ip); + } + + public IPAddress ExternalIPAddress { get; private set; } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs b/Open.NAT/Open.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs new file mode 100644 index 0000000..22c473c --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs @@ -0,0 +1,66 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Xml; + +namespace Open.Nat +{ + internal class GetPortMappingEntryResponseMessage : ResponseMessageBase + { + internal GetPortMappingEntryResponseMessage(XmlDocument response, string serviceType, bool genericMapping) + : base(response, serviceType, genericMapping ? "GetGenericPortMappingEntryResponseMessage" : "GetSpecificPortMappingEntryResponseMessage") + { + XmlNode data = GetNode(); + + RemoteHost = (genericMapping) ? data.GetXmlElementText("NewRemoteHost") : string.Empty; + ExternalPort = (genericMapping) ? Convert.ToInt32(data.GetXmlElementText("NewExternalPort")) : ushort.MaxValue; + if (genericMapping) + Protocol = data.GetXmlElementText("NewProtocol").Equals("TCP", StringComparison.InvariantCultureIgnoreCase) + ? Protocol.Tcp + : Protocol.Udp; + else + Protocol = Protocol.Udp; + + InternalPort = Convert.ToInt32(data.GetXmlElementText("NewInternalPort")); + InternalClient = data.GetXmlElementText("NewInternalClient"); + Enabled = data.GetXmlElementText("NewEnabled") == "1"; + PortMappingDescription = data.GetXmlElementText("NewPortMappingDescription"); + LeaseDuration = Convert.ToInt32(data.GetXmlElementText("NewLeaseDuration")); + } + + public string RemoteHost { get; private set; } + public int ExternalPort { get; private set; } + public Protocol Protocol { get; private set; } + public int InternalPort { get; private set; } + public string InternalClient { get; private set; } + public bool Enabled { get; private set; } + public string PortMappingDescription { get; private set; } + public int LeaseDuration { get; private set; } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/RequestMessageBase.cs b/Open.NAT/Open.Nat/Upnp/RequestMessageBase.cs new file mode 100644 index 0000000..ad2384c --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/RequestMessageBase.cs @@ -0,0 +1,37 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; + +namespace Open.Nat +{ + internal abstract class RequestMessageBase + { + public abstract IDictionary ToXml(); + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/ResponseMessageBase.cs b/Open.NAT/Open.Nat/Upnp/ResponseMessageBase.cs new file mode 100644 index 0000000..b6f3edb --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/ResponseMessageBase.cs @@ -0,0 +1,58 @@ +// +// Authors: +// Lucas Ontivero lucas.ontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Xml; + +namespace Open.Nat +{ + internal abstract class ResponseMessageBase + { + private readonly XmlDocument _document; + protected string ServiceType; + private readonly string _typeName; + + protected ResponseMessageBase(XmlDocument response, string serviceType, string typeName) + { + _document = response; + ServiceType = serviceType; + _typeName = typeName; + } + + protected XmlNode GetNode() + { + var nsm = new XmlNamespaceManager(_document.NameTable); + nsm.AddNamespace("responseNs", ServiceType); + + string typeName = _typeName; + string messageName = typeName.Substring(0, typeName.Length - "Message".Length); + XmlNode node = _document.SelectSingleNode("//responseNs:" + messageName, nsm); + if (node == null) throw new InvalidOperationException("The response is invalid: " + messageName); + + return node; + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/SoapClient.cs b/Open.NAT/Open.Nat/Upnp/SoapClient.cs new file mode 100644 index 0000000..94e9d09 --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/SoapClient.cs @@ -0,0 +1,161 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace Open.Nat +{ + internal class SoapClient + { + private readonly string _serviceType; + private readonly Uri _url; + + public SoapClient(Uri url, string serviceType) + { + _url = url; + _serviceType = serviceType; + } + + public async Task InvokeAsync(string operationName, IDictionary args) + { + NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, "SOAPACTION: **{0}** url:{1}", operationName, + _url); + byte[] messageBody = BuildMessageBody(operationName, args); + HttpWebRequest request = BuildHttpWebRequest(operationName, messageBody); + + if (messageBody.Length > 0) + { + using (var stream = await request.GetRequestStreamAsync()) + { + await stream.WriteAsync(messageBody, 0, messageBody.Length); + } + } + + using(var response = await GetWebResponse(request)) + { + var stream = response.GetResponseStream(); + var contentLength = response.ContentLength; + + var reader = new StreamReader(stream, Encoding.UTF8); + + var responseBody = contentLength != -1 + ? reader.ReadAsMany((int) contentLength) + : reader.ReadToEnd(); + + var responseXml = GetXmlDocument(responseBody); + NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Response: \n{0}", responseXml.ToPrintableXml()); + + response.Close(); + return responseXml; + } + } + + private static async Task GetWebResponse(WebRequest request) + { + WebResponse response; + try + { + response = await request.GetResponseAsync(); + } + catch (WebException ex) + { + NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, "WebException status: {0}", ex.Status); + + // Even if the request "failed" we need to continue reading the response from the router + response = ex.Response as HttpWebResponse; + + if (response == null) + throw; + } + return response; + } + + private HttpWebRequest BuildHttpWebRequest(string operationName, byte[] messageBody) + { + var request = WebRequest.CreateHttp(_url); + request.KeepAlive = false; + request.Method = "POST"; + request.ContentType = "text/xml; charset=\"utf-8\""; + request.Headers.Add("SOAPACTION", "\"" + _serviceType + "#" + operationName + "\""); + request.ContentLength = messageBody.Length; + return request; + } + + private byte[] BuildMessageBody(string operationName, IEnumerable> args) + { + var sb = new StringBuilder(); + sb.AppendLine(""); + sb.AppendLine(" "); + sb.AppendLine(" "); + foreach (var a in args) + { + sb.AppendLine(" <" + a.Key + ">" + Convert.ToString(a.Value, CultureInfo.InvariantCulture) + + ""); + } + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.Append("\r\n\r\n"); + string requestBody = sb.ToString(); + + NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, requestBody); + byte[] messageBody = Encoding.UTF8.GetBytes(requestBody); + return messageBody; + } + + private XmlDocument GetXmlDocument(string response) + { + XmlNode node; + var doc = new XmlDocument(); + doc.LoadXml(response); + + var nsm = new XmlNamespaceManager(doc.NameTable); + + // Error messages should be found under this namespace + nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0"); + + // Check to see if we have a fault code message. + if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) + { + int code = Convert.ToInt32(node.GetXmlElementText("errorCode"), CultureInfo.InvariantCulture); + string errorMessage = node.GetXmlElementText("errorDescription"); + NatDiscoverer.TraceSource.LogWarn("Server failed with error: {0} - {1}", code, errorMessage); + throw new MappingException(code, errorMessage); + } + + return doc; + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/UpnpConstants.cs b/Open.NAT/Open.Nat/Upnp/UpnpConstants.cs new file mode 100644 index 0000000..71dd20e --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/UpnpConstants.cs @@ -0,0 +1,46 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +namespace Open.Nat +{ + internal static class UpnpConstants + { + public const int InvalidArguments = 402; + public const int ActionFailed = 501; + public const int Unathorized = 606; + public const int SpecifiedArrayIndexInvalid = 713; public const int NoSuchEntryInArray = 714; + public const int WildCardNotPermittedInSourceIp = 715; + public const int WildCardNotPermittedInExternalPort = 716; + public const int ConflictInMappingEntry = 718; + public const int SamePortValuesRequired = 724; + public const int OnlyPermanentLeasesSupported = 725; + public const int RemoteHostOnlySupportsWildcard = 726; + public const int ExternalPortOnlySupportsWildcard = 727; + public const int NoPortMapsAvailable = 728; + public const int ConflictWithOtherMechanisms = 729; + public const int WildCardNotPermittedInIntPort = 732; + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/UpnpNatDevice.cs b/Open.NAT/Open.Nat/Upnp/UpnpNatDevice.cs new file mode 100644 index 0000000..0011d73 --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/UpnpNatDevice.cs @@ -0,0 +1,198 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Open.Nat +{ + internal sealed class UpnpNatDevice : NatDevice + { + internal readonly UpnpNatDeviceInfo DeviceInfo; + private readonly SoapClient _soapClient; + + internal UpnpNatDevice (UpnpNatDeviceInfo deviceInfo) + { + Touch(); + DeviceInfo = deviceInfo; + _soapClient = new SoapClient(DeviceInfo.ServiceControlUri, DeviceInfo.ServiceType); + } + + public override async Task GetExternalIPAsync() + { + var message = new GetExternalIPAddressRequestMessage(); + var responseData = await _soapClient + .InvokeAsync("GetExternalIPAddress", message.ToXml()) + .TimeoutAfter(TimeSpan.FromSeconds(4)); + + var response = new GetExternalIPAddressResponseMessage(responseData, DeviceInfo.ServiceType); + return response.ExternalIPAddress; + } + + public override async Task CreatePortMapAsync(Mapping mapping) + { + Guard.IsNotNull(mapping, "mapping"); + + mapping.PrivateIP = DeviceInfo.LocalAddress; + try + { + var message = new CreatePortMappingRequestMessage(mapping); + await _soapClient + .InvokeAsync("AddPortMapping", message.ToXml()) + .TimeoutAfter(TimeSpan.FromSeconds(4)); + RegisterMapping(mapping); + } + catch(MappingException me) + { + switch (me.ErrorCode) + { + case UpnpConstants.OnlyPermanentLeasesSupported: + NatDiscoverer.TraceSource.LogWarn("Only Permanent Leases Supported - There is no warranty it will be closed"); + mapping.Lifetime = 0; + // We create the mapping anyway. It must be released on shutdown. + mapping.LifetimeType = MappingLifetime.ForcedSession; + CreatePortMapAsync(mapping); + break; + case UpnpConstants.SamePortValuesRequired: + NatDiscoverer.TraceSource.LogWarn("Same Port Values Required - Using internal port {0}", mapping.PrivatePort); + mapping.PublicPort = mapping.PrivatePort; + CreatePortMapAsync(mapping); + break; + case UpnpConstants.RemoteHostOnlySupportsWildcard: + NatDiscoverer.TraceSource.LogWarn("Remote Host Only Supports Wildcard"); + mapping.PublicIP = IPAddress.None; + CreatePortMapAsync(mapping); + break; + case UpnpConstants.ExternalPortOnlySupportsWildcard: + NatDiscoverer.TraceSource.LogWarn("External Port Only Supports Wildcard"); + throw; + case UpnpConstants.ConflictInMappingEntry: + NatDiscoverer.TraceSource.LogWarn("Conflict with an already existing mapping"); + throw; + + default: + throw; + } + } + } + + public override async Task DeletePortMapAsync(Mapping mapping) + { + Guard.IsNotNull(mapping, "mapping"); + + try + { + var message = new DeletePortMappingRequestMessage(mapping); + await _soapClient + .InvokeAsync("DeletePortMapping", message.ToXml()) + .TimeoutAfter(TimeSpan.FromSeconds(4)); + UnregisterMapping(mapping); + } + catch (MappingException e) + { + if(e.ErrorCode != UpnpConstants.NoSuchEntryInArray) throw; + } + } + + public override async Task> GetAllMappingsAsync() + { + var index = 0; + var mappings = new List(); + + while (true) + { + try + { + var message = new GetGenericPortMappingEntry(index); + + var responseData = await _soapClient + .InvokeAsync("GetGenericPortMappingEntry", message.ToXml()) + .TimeoutAfter(TimeSpan.FromSeconds(4)); + + var responseMessage = new GetPortMappingEntryResponseMessage(responseData, DeviceInfo.ServiceType, true); + + var mapping = new Mapping(responseMessage.Protocol + , IPAddress.Parse(responseMessage.InternalClient) + , responseMessage.InternalPort + , responseMessage.ExternalPort + , responseMessage.LeaseDuration + , responseMessage.PortMappingDescription); + mappings.Add(mapping); + index++; + } + catch (MappingException e) + { + if (e.ErrorCode == UpnpConstants.SpecifiedArrayIndexInvalid) break; // there are no more mappings + throw; + } + } + + return mappings.ToArray(); + } + + public override async Task GetSpecificMappingAsync (Protocol protocol, int port) + { + Guard.IsTrue(protocol == Protocol.Tcp || protocol == Protocol.Udp, "protocol"); + Guard.IsInRange(port, 0, ushort.MaxValue, "port"); + + try + { + var message = new GetSpecificPortMappingEntryRequestMessage(protocol, port); + var responseData = await _soapClient + .InvokeAsync("GetSpecificPortMappingEntry", message.ToXml()) + .TimeoutAfter(TimeSpan.FromSeconds(4)); + + var messageResponse = new GetPortMappingEntryResponseMessage(responseData, DeviceInfo.ServiceType, false); + + return new Mapping(messageResponse.Protocol + , IPAddress.Parse(messageResponse.InternalClient) + , messageResponse.InternalPort + , messageResponse.ExternalPort + , messageResponse.LeaseDuration + , messageResponse.PortMappingDescription); + } + catch (MappingException e) + { + if (e.ErrorCode != UpnpConstants.NoSuchEntryInArray) throw; + return null; + } + } + + public override string ToString( ) + { + //GetExternalIP is blocking and can throw exceptions, can't use it here. + return String.Format( + "EndPoint: {0}\nControl Url: {1}\nService Type: {2}\nLast Seen: {3}", + DeviceInfo.HostEndPoint, DeviceInfo.ServiceControlUri, DeviceInfo.ServiceType, LastSeen); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/UpnpNatDeviceInfo.cs b/Open.NAT/Open.Nat/Upnp/UpnpNatDeviceInfo.cs new file mode 100644 index 0000000..e0665ab --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/UpnpNatDeviceInfo.cs @@ -0,0 +1,60 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Net; + +namespace Open.Nat +{ + internal class UpnpNatDeviceInfo + { + public UpnpNatDeviceInfo(IPAddress localAddress, Uri locationUri, string serviceControlUrl, string serviceType) + { + LocalAddress = localAddress; + ServiceType = serviceType; + HostEndPoint = new IPEndPoint(IPAddress.Parse(locationUri.Host), locationUri.Port); + + if (Uri.IsWellFormedUriString(serviceControlUrl, UriKind.Absolute)) + { + var u = new Uri(serviceControlUrl); + IPEndPoint old = HostEndPoint; + serviceControlUrl = serviceControlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length); + + NatDiscoverer.TraceSource.LogInfo("{0}: Absolute URI detected. Host address is now: {1}", old, + HostEndPoint); + NatDiscoverer.TraceSource.LogInfo("{0}: New control url: {1}", HostEndPoint, serviceControlUrl); + } + + var builder = new UriBuilder("http", locationUri.Host, locationUri.Port, serviceControlUrl); + ServiceControlUri = builder.Uri; + } + + public IPEndPoint HostEndPoint { get; private set; } + public IPAddress LocalAddress { get; private set; } + public string ServiceType { get; private set; } + public Uri ServiceControlUri { get; private set; } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Upnp/UpnpSearcher.cs b/Open.NAT/Open.Nat/Upnp/UpnpSearcher.cs new file mode 100644 index 0000000..89f66bd --- /dev/null +++ b/Open.NAT/Open.Nat/Upnp/UpnpSearcher.cs @@ -0,0 +1,268 @@ +// +// Authors: +// Ben Motmans +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Net; +using System.Diagnostics; +using System.Net.Sockets; +using System.Threading; +using System.Xml; + +namespace Open.Nat +{ + internal class UpnpSearcher : Searcher + { + private readonly IIPAddressesProvider _ipprovider; + private readonly IDictionary _devices; + private readonly Dictionary _lastFetched; + private static readonly string[] ServiceTypes = new[]{ + "WANIPConnection:2", + "WANPPPConnection:2", + "WANIPConnection:1", + "WANPPPConnection:1" + }; + + internal UpnpSearcher(IIPAddressesProvider ipprovider) + { + _ipprovider = ipprovider; + Sockets = CreateSockets(); + _devices = new Dictionary(); + _lastFetched = new Dictionary(); + } + + private List CreateSockets() + { + var clients = new List(); + try + { + var ips = _ipprovider.UnicastAddresses(); + + foreach (var ipAddress in ips) + { + try + { + clients.Add(new UdpClient(new IPEndPoint(ipAddress, 0))); + } + catch (Exception) + { + continue; // Move on to the next address. + } + } + } + catch (Exception) + { + clients.Add(new UdpClient(0)); + } + return clients; + } + + protected override void Discover(UdpClient client, CancellationToken cancelationToken) + { + NextSearch = DateTime.UtcNow.AddSeconds(1); + var searchEndpoint = new IPEndPoint( + WellKnownConstants.IPv4MulticastAddress + /*IPAddress.Broadcast*/ + , 1900); + + foreach (var serviceType in ServiceTypes) + { + var datax = DiscoverDeviceMessage.Encode(serviceType); + var data = Encoding.ASCII.GetBytes(datax); + + // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2) + // Yes, however it works perfectly well with just 1 request. + for (var i = 0; i < 2; i++) + { + if (cancelationToken.IsCancellationRequested) return; + client.Send(data, data.Length, searchEndpoint); + } + } + } + + public override NatDevice AnalyseReceivedResponse(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + // Convert it to a string for easy parsing + string dataString = null; + + // No matter what, this method should never throw an exception. If something goes wrong + // we should still be in a position to handle the next reply correctly. + try + { + dataString = Encoding.UTF8.GetString(response); + var message = new DiscoveryResponseMessage(dataString); + var serviceType = message["ST"]; + + NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, "UPnP Response: {0}", dataString); + + if (!IsValidControllerService(serviceType)) return null; + NatDiscoverer.TraceSource.LogInfo("UPnP Response: Router advertised a '{0}' service!!!", serviceType); + + var location = message["Location"]; + var locationUri = new Uri(location); + + NatDiscoverer.TraceSource.LogInfo("Found device at: {0}", locationUri.ToString()); + + if (_devices.ContainsKey(locationUri)) + { + NatDiscoverer.TraceSource.LogInfo("Already found - Ignored"); + _devices[locationUri].Touch(); + return null; + } + + // If we send 3 requests at a time, ensure we only fetch the services list once + // even if three responses are received + if (_lastFetched.ContainsKey(endpoint.Address)) + { + var last = _lastFetched[endpoint.Address]; + if ((DateTime.Now - last) < TimeSpan.FromSeconds(20)) + return null; + } + _lastFetched[endpoint.Address] = DateTime.Now; + + NatDiscoverer.TraceSource.LogInfo("{0}:{1}: Fetching service list", locationUri.Host, locationUri.Port ); + + var deviceInfo = BuildUpnpNatDeviceInfo(localAddress, locationUri); + + UpnpNatDevice device; + lock (_devices) + { + device = new UpnpNatDevice(deviceInfo); + if (!_devices.ContainsKey(locationUri)) + { + _devices.Add(locationUri, device); + } + } + return device; + } + catch (Exception ex) + { + NatDiscoverer.TraceSource.LogError("Unhandled exception when trying to decode a device's response. "); + NatDiscoverer.TraceSource.LogError("Report the issue in https://github.com/lontivero/Open.Nat/issues"); + NatDiscoverer.TraceSource.LogError("Also copy and paste the following info:"); + NatDiscoverer.TraceSource.LogError("-- beging ---------------------------------"); + NatDiscoverer.TraceSource.LogError(ex.Message); + NatDiscoverer.TraceSource.LogError("Data string:"); + NatDiscoverer.TraceSource.LogError(dataString ?? "No data available"); + NatDiscoverer.TraceSource.LogError("-- end ------------------------------------"); + } + return null; + } + + private static bool IsValidControllerService(string serviceType) + { + var services = from serviceName in ServiceTypes + let serviceUrn = string.Format("urn:schemas-upnp-org:service:{0}", serviceName) + where serviceType.ContainsIgnoreCase(serviceUrn) + select new {ServiceName = serviceName, ServiceUrn = serviceUrn}; + + return services.Any(); + } + + private UpnpNatDeviceInfo BuildUpnpNatDeviceInfo(IPAddress localAddress, Uri location) + { + NatDiscoverer.TraceSource.LogInfo("Found device at: {0}", location.ToString()); + + var hostEndPoint = new IPEndPoint(IPAddress.Parse(location.Host), location.Port); + + WebResponse response = null; + try + { + var request = WebRequest.CreateHttp(location); + request.Headers.Add("ACCEPT-LANGUAGE", "en"); + request.Method = "GET"; + + response = request.GetResponse(); + + var httpresponse = response as HttpWebResponse; + + if (httpresponse != null && httpresponse.StatusCode != HttpStatusCode.OK) + { + var message = string.Format("Couldn't get services list: {0} {1}", httpresponse.StatusCode, httpresponse.StatusDescription); + throw new Exception(message); + } + + var xmldoc = ReadXmlResponse(response); + + NatDiscoverer.TraceSource.LogInfo("{0}: Parsed services list", hostEndPoint); + + var ns = new XmlNamespaceManager(xmldoc.NameTable); + ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0"); + var services = xmldoc.SelectNodes("//ns:service", ns); + + foreach (XmlNode service in services) + { + var serviceType = service.GetXmlElementText("serviceType"); + if (!IsValidControllerService(serviceType)) continue; + + NatDiscoverer.TraceSource.LogInfo("{0}: Found service: {1}", hostEndPoint, serviceType); + + var serviceControlUrl = service.GetXmlElementText("controlURL"); + NatDiscoverer.TraceSource.LogInfo("{0}: Found upnp service at: {1}", hostEndPoint, serviceControlUrl); + + NatDiscoverer.TraceSource.LogInfo("{0}: Handshake Complete", hostEndPoint); + return new UpnpNatDeviceInfo(localAddress, location, serviceControlUrl, serviceType); + } + + throw new Exception("No valid control service was found in the service descriptor document"); + } + catch (WebException ex) + { + // Just drop the connection, FIXME: Should i retry? + NatDiscoverer.TraceSource.LogError("{0}: Device denied the connection attempt: {1}", hostEndPoint, ex); + var inner = ex.InnerException as SocketException; + if (inner != null) + { + NatDiscoverer.TraceSource.LogError("{0}: ErrorCode:{1}", hostEndPoint, inner.ErrorCode); + NatDiscoverer.TraceSource.LogError("Go to http://msdn.microsoft.com/en-us/library/system.net.sockets.socketerror.aspx"); + NatDiscoverer.TraceSource.LogError("Usually this happens. Try resetting the device and try again. If you are in a VPN, disconnect and try again."); + } + throw; + } + finally + { + if (response != null) + response.Close(); + } + } + + private static XmlDocument ReadXmlResponse(WebResponse response) + { + using (var reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) + { + var servicesXml = reader.ReadToEnd(); + var xmldoc = new XmlDocument(); + xmldoc.LoadXml(servicesXml); + return xmldoc; + } + } + } +} diff --git a/Open.NAT/Open.Nat/Utils/Extensions.cs b/Open.NAT/Open.Nat/Utils/Extensions.cs new file mode 100644 index 0000000..565f203 --- /dev/null +++ b/Open.NAT/Open.Nat/Utils/Extensions.cs @@ -0,0 +1,121 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +namespace Open.Nat +{ + internal static class StreamExtensions + { + internal static string ReadAsMany(this StreamReader stream, int bytesToRead) + { + var buffer = new char[bytesToRead]; + stream.ReadBlock(buffer, 0, bytesToRead); + return new string(buffer); + } + + internal static string GetXmlElementText(this XmlNode node, string elementName) + { + XmlElement element = node[elementName]; + return element != null ? element.InnerText : string.Empty; + } + + internal static bool ContainsIgnoreCase(this string s, string pattern) + { + return s.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0; + } + + internal static void LogInfo(this TraceSource source, string format, params object[] args) + { + source.TraceEvent(TraceEventType.Information, 0, format, args); + } + + internal static void LogWarn(this TraceSource source, string format, params object[] args) + { + source.TraceEvent(TraceEventType.Warning, 0, format, args); + } + + internal static void LogError(this TraceSource source, string format, params object[] args) + { + source.TraceEvent(TraceEventType.Error, 0, format, args); + } + + internal static string ToPrintableXml(this XmlDocument document) + { + using (var stream = new MemoryStream()) + { + using (var writer = new XmlTextWriter(stream, Encoding.Unicode)) + { + try + { + writer.Formatting = Formatting.Indented; + + document.WriteContentTo(writer); + writer.Flush(); + stream.Flush(); + + // Have to rewind the MemoryStream in order to read + // its contents. + stream.Position = 0; + + // Read MemoryStream contents into a StreamReader. + var reader = new StreamReader(stream); + + // Extract the text from the StreamReader. + return reader.ReadToEnd(); + } + catch (Exception) + { + return document.ToString(); + } + } + } + } + + public static async Task TimeoutAfter(this Task task, TimeSpan timeout) + { +#if DEBUG + return await task; +#endif + var timeoutCancellationTokenSource = new CancellationTokenSource(); + + Task completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)); + if (completedTask == task) + { + timeoutCancellationTokenSource.Cancel(); + return await task; + } + throw new TimeoutException( + "The operation has timed out. The network is broken, router has gone or is too busy."); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Utils/Guard.cs b/Open.NAT/Open.Nat/Utils/Guard.cs new file mode 100644 index 0000000..1189ea1 --- /dev/null +++ b/Open.NAT/Open.Nat/Utils/Guard.cs @@ -0,0 +1,28 @@ +using System; + +namespace Open.Nat +{ + internal class Guard + { + private Guard() + { + } + + internal static void IsInRange(int paramValue, int lowerBound, int upperBound, string paramName) + { + if (paramValue < lowerBound || paramValue > upperBound) + throw new ArgumentOutOfRangeException(paramName); + } + + internal static void IsTrue(bool exp, string paramName) + { + if (!exp) + throw new ArgumentOutOfRangeException(paramName); + } + + internal static void IsNotNull(object obj, string paramName) + { + if(obj == null) throw new ArgumentNullException(paramName); + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Utils/IIPAddressesProvider.cs b/Open.NAT/Open.Nat/Utils/IIPAddressesProvider.cs new file mode 100644 index 0000000..b3b764c --- /dev/null +++ b/Open.NAT/Open.Nat/Utils/IIPAddressesProvider.cs @@ -0,0 +1,38 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Net; + +namespace Open.Nat +{ + internal interface IIPAddressesProvider + { + IEnumerable DnsAddresses(); + IEnumerable GatewayAddresses(); + IEnumerable UnicastAddresses(); + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Utils/IPAddressesProvider.cs b/Open.NAT/Open.Nat/Utils/IPAddressesProvider.cs new file mode 100644 index 0000000..e6d142d --- /dev/null +++ b/Open.NAT/Open.Nat/Utils/IPAddressesProvider.cs @@ -0,0 +1,69 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace Open.Nat +{ + internal class IPAddressesProvider : IIPAddressesProvider + { + #region IIPAddressesProvider Members + + public IEnumerable UnicastAddresses() + { + return IPAddresses(p => p.UnicastAddresses.Select(x => x.Address)); + } + + public IEnumerable DnsAddresses() + { + return IPAddresses(p => p.DnsAddresses); + } + + public IEnumerable GatewayAddresses() + { + return IPAddresses(p => p.GatewayAddresses.Select(x => x.Address)); + } + + #endregion + + private static IEnumerable IPAddresses(Func> ipExtractor) + { + return from networkInterface in NetworkInterface.GetAllNetworkInterfaces() + where + networkInterface.OperationalStatus == OperationalStatus.Up || + networkInterface.OperationalStatus == OperationalStatus.Unknown + let properties = networkInterface.GetIPProperties() + from address in ipExtractor(properties) + where address.AddressFamily == AddressFamily.InterNetwork + select address; + } + } +} \ No newline at end of file diff --git a/Open.NAT/Open.Nat/Utils/WellKnownConstants.cs b/Open.NAT/Open.Nat/Utils/WellKnownConstants.cs new file mode 100644 index 0000000..8de465d --- /dev/null +++ b/Open.NAT/Open.Nat/Utils/WellKnownConstants.cs @@ -0,0 +1,36 @@ +// +// Authors: +// Lucas Ontivero lucasontivero@gmail.com +// +// Copyright (C) 2014 Lucas Ontivero +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Net; + +namespace Open.Nat +{ + internal static class WellKnownConstants + { + public static IPAddress IPv4MulticastAddress = IPAddress.Parse("239.255.255.250"); + public static IPEndPoint NatPmpEndPoint = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); + } +} \ No newline at end of file diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/CharacterCustomization.Designer.cs b/RBXLegacyLauncher/RBXLegacyLauncher/CharacterCustomization.Designer.cs index a27650a..f6f663c 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/CharacterCustomization.Designer.cs +++ b/RBXLegacyLauncher/RBXLegacyLauncher/CharacterCustomization.Designer.cs @@ -1324,7 +1324,7 @@ namespace RBXLegacyLauncher this.tabPage7.Padding = new System.Windows.Forms.Padding(3); this.tabPage7.Size = new System.Drawing.Size(447, 250); this.tabPage7.TabIndex = 6; - this.tabPage7.Text = "GEARS"; + this.tabPage7.Text = "GEAR"; this.tabPage7.UseVisualStyleBackColor = true; // // button7 diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/CharacterCustomization.resx b/RBXLegacyLauncher/RBXLegacyLauncher/CharacterCustomization.resx index d761697..bb6e0eb 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/CharacterCustomization.resx +++ b/RBXLegacyLauncher/RBXLegacyLauncher/CharacterCustomization.resx @@ -112,12 +112,12 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO @@ -1273,7 +1273,7 @@ iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vQAADr0BR/uQrQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 + vAAADrwBlbxySQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 ZkbSK/V9VXWFeDQ6tFT9jE7d/7eQJCkAA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaS JCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmS QjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkE @@ -1417,7 +1417,7 @@ iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vQAADr0BR/uQrQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 + vAAADrwBlbxySQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 ZkbSK/V9VXWFeDQ6tFT9jE7d/7eQJCkAA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaS JCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmS QjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkE @@ -1561,7 +1561,7 @@ iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vQAADr0BR/uQrQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 + vAAADrwBlbxySQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 ZkbSK/V9VXWFeDQ6tFT9jE7d/7eQJCkAA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaS JCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmS QjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkE @@ -1705,7 +1705,7 @@ iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vQAADr0BR/uQrQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 + vAAADrwBlbxySQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 ZkbSK/V9VXWFeDQ6tFT9jE7d/7eQJCkAA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaS JCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmS QjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkE @@ -1849,7 +1849,7 @@ iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vQAADr0BR/uQrQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 + vAAADrwBlbxySQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 ZkbSK/V9VXWFeDQ6tFT9jE7d/7eQJCkAA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaS JCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmS QjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkE @@ -1993,7 +1993,7 @@ iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vQAADr0BR/uQrQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 + vAAADrwBlbxySQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 ZkbSK/V9VXWFeDQ6tFT9jE7d/7eQJCkAA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaS JCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmS QjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkE @@ -2137,7 +2137,7 @@ iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vQAADr0BR/uQrQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 + vAAADrwBlbxySQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 ZkbSK/V9VXWFeDQ6tFT9jE7d/7eQJCkAA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaS JCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmS QjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkE @@ -2281,7 +2281,7 @@ iVBORw0KGgoAAAANSUhEUgAAAaQAAAGkCAYAAAB+TFE1AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vQAADr0BR/uQrQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 + vAAADrwBlbxySQAAIEdJREFUeF7t3Qtz1EbahuHv//+w9e4mBHO0YSEH7ACOsQHb2GAb5qtbNU1NHCG1 ZkbSK/V9VXWFeDQ6tFT9jE7d/7eQJCkAA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaS JCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmS QjCQJEkhGEiSpBAMJElSCAaSJCkEA0mSFIKBJEkKwUCSJIVgIEmSQjCQJEkhGEiSpBAMJElSCAaSJCkE diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/ClientSettings.resx b/RBXLegacyLauncher/RBXLegacyLauncher/ClientSettings.resx index 7080a7d..1af7de1 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/ClientSettings.resx +++ b/RBXLegacyLauncher/RBXLegacyLauncher/ClientSettings.resx @@ -112,9 +112,9 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 \ No newline at end of file diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/DocForm.resx b/RBXLegacyLauncher/RBXLegacyLauncher/DocForm.resx index 6278e07..063b003 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/DocForm.resx +++ b/RBXLegacyLauncher/RBXLegacyLauncher/DocForm.resx @@ -112,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 VERSION CODENAME DOCUMENTATION diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/LoaderForm.resx b/RBXLegacyLauncher/RBXLegacyLauncher/LoaderForm.resx index 7311863..b030d8f 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/LoaderForm.resx +++ b/RBXLegacyLauncher/RBXLegacyLauncher/LoaderForm.resx @@ -112,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.Designer.cs b/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.Designer.cs index 5bd926d..639d598 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.Designer.cs +++ b/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.Designer.cs @@ -19,6 +19,7 @@ this.pictureBox1 = new System.Windows.Forms.PictureBox(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage1 = new System.Windows.Forms.TabPage(); + this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); this.button11 = new System.Windows.Forms.Button(); this.label35 = new System.Windows.Forms.Label(); this.label42 = new System.Windows.Forms.Label(); @@ -99,10 +100,10 @@ this.label27 = new System.Windows.Forms.Label(); this.label28 = new System.Windows.Forms.Label(); this.textBox5 = new System.Windows.Forms.TextBox(); - this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); this.tabControl1.SuspendLayout(); this.tabPage1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); this.tabPage2.SuspendLayout(); this.tabPage3.SuspendLayout(); this.tabPage6.SuspendLayout(); @@ -110,7 +111,6 @@ this.tabPage8.SuspendLayout(); this.tabPage4.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); this.SuspendLayout(); // // pictureBox1 @@ -171,6 +171,25 @@ this.tabPage1.ToolTipText = "Join a server via IP Address"; this.tabPage1.UseVisualStyleBackColor = true; // + // numericUpDown1 + // + this.numericUpDown1.Location = new System.Drawing.Point(214, 25); + this.numericUpDown1.Maximum = new decimal(new int[] { + 99999, + 0, + 0, + 0}); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(175, 20); + this.numericUpDown1.TabIndex = 43; + this.numericUpDown1.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.numericUpDown1.Value = new decimal(new int[] { + 53640, + 0, + 0, + 0}); + this.numericUpDown1.ValueChanged += new System.EventHandler(this.NumericUpDown1ValueChanged); + // // button11 // this.button11.Location = new System.Drawing.Point(213, 47); @@ -389,6 +408,7 @@ this.label30.Name = "label30"; this.label30.Size = new System.Drawing.Size(393, 2); this.label30.TabIndex = 23; + this.label30.Visible = false; // // button6 // @@ -398,6 +418,7 @@ this.button6.TabIndex = 6; this.button6.Text = "CONFIGURE CLIENT"; this.button6.UseVisualStyleBackColor = true; + this.button6.Visible = false; this.button6.Click += new System.EventHandler(this.Button6Click); // // label19 @@ -416,7 +437,7 @@ this.textBox6.Name = "textBox6"; this.textBox6.ReadOnly = true; this.textBox6.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.textBox6.Size = new System.Drawing.Size(393, 67); + this.textBox6.Size = new System.Drawing.Size(393, 111); this.textBox6.TabIndex = 4; this.textBox6.Text = "textBox6"; // @@ -999,24 +1020,6 @@ this.textBox5.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; this.textBox5.TextChanged += new System.EventHandler(this.TextBox5TextChanged); // - // numericUpDown1 - // - this.numericUpDown1.Location = new System.Drawing.Point(214, 25); - this.numericUpDown1.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.numericUpDown1.Name = "numericUpDown1"; - this.numericUpDown1.Size = new System.Drawing.Size(175, 20); - this.numericUpDown1.TabIndex = 43; - this.numericUpDown1.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.numericUpDown1.Value = new decimal(new int[] { - 53640, - 0, - 0, - 0}); - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1055,6 +1058,7 @@ this.tabControl1.ResumeLayout(false); this.tabPage1.ResumeLayout(false); this.tabPage1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); this.tabPage2.ResumeLayout(false); this.tabPage3.ResumeLayout(false); this.tabPage3.PerformLayout(); @@ -1063,7 +1067,6 @@ this.tabPage8.ResumeLayout(false); this.tabPage4.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); } diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.cs b/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.cs index 642bc47..4cf775d 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.cs +++ b/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using Open.Nat; namespace RBXLegacyLauncher { @@ -19,42 +20,61 @@ namespace RBXLegacyLauncher InitializeComponent(); } - public event EventHandler evNATdone; - - public class evNATdoneargs : EventArgs + public async void StartUPNP() { - public string IP; - public int port; - } + try + { + var nat = new NatDiscoverer(); - //this class contains network connectivity stuff - public void StartUPNP() - { - Mono.Nat.NatUtility.DeviceFound += new EventHandler(NatUtility_DeviceFound); - Mono.Nat.NatUtility.DeviceLost += new EventHandler(NatUtility_DeviceLost); - Mono.Nat.NatUtility.StartDiscovery(); - } - - private void NatUtility_DeviceFound(object sender, Mono.Nat.DeviceEventArgs e) - { - //do port forwarding with UPNP - Mono.Nat.INatDevice natd = e.Device; - natd.CreatePortMap(new Mono.Nat.Mapping(Mono.Nat.Protocol.Tcp,GlobalVars.ServerPort,GlobalVars.ServerPort)); - natd.CreatePortMap(new Mono.Nat.Mapping(Mono.Nat.Protocol.Udp,GlobalVars.ServerPort,GlobalVars.ServerPort)); - ConsolePrint("Port " + GlobalVars.ServerPort.ToString() + " registered to device " + natd.GetExternalIP().ToString(), 3); - evNATdoneargs args = new evNATdoneargs(); - args.IP = natd.GetExternalIP().ToString(); - args.port = GlobalVars.ServerPort; - evNATdone(this, args); - //MessageBox.Show("NAT done! My public IP is " + IP); + var cts = new CancellationTokenSource(5000); + var device = await nat.DiscoverDeviceAsync(PortMapper.Upnp, cts); + await device.CreatePortMapAsync(new Mapping(Protocol.Udp, GlobalVars.ServerPort, GlobalVars.ServerPort, "RBXLegacy")); + + var ip = await device.GetExternalIPAsync(); + + ConsolePrint("Port " + GlobalVars.ServerPort.ToString() + " registered to device " + ip, 3); + } + catch(NatDeviceNotFoundException e) + { + ConsolePrint("Error: " + e.ToString(), 2); + } + catch(MappingException me) + { + switch(me.ErrorCode) + { + case 718: + ConsolePrint("The external port is already in use.", 2); + break; + case 728: + ConsolePrint("The router's mapping table is full.", 2); + break; + } + } } - private void NatUtility_DeviceLost(object sender, Mono.Nat.DeviceEventArgs e) + public async void StopUPNP() { - //do port forwarding with UPNP - Mono.Nat.INatDevice natd = e.Device; - natd.DeletePortMap(new Mono.Nat.Mapping(Mono.Nat.Protocol.Tcp,GlobalVars.ServerPort,GlobalVars.ServerPort)); - natd.DeletePortMap(new Mono.Nat.Mapping(Mono.Nat.Protocol.Udp,GlobalVars.ServerPort,GlobalVars.ServerPort)); + try + { + var nat = new NatDiscoverer(); + var cts = new CancellationTokenSource(5000); + var device = await nat.DiscoverDeviceAsync(PortMapper.Upnp, cts); + + foreach (var mapping in await device.GetAllMappingsAsync()) + { + if(mapping.Description.Contains("RBXLegacy")) + { + await device.DeletePortMapAsync(mapping); + } + } + + var ip = await device.GetExternalIPAsync(); + ConsolePrint("Port " + GlobalVars.ServerPort.ToString() + " removed from device " + ip, 2); + } + catch(NatDeviceNotFoundException e) + { + ConsolePrint("Error: " + e.ToString(), 2); + } } void tabControl1_SelectedIndexChanged(object sender, EventArgs e) @@ -398,7 +418,7 @@ namespace RBXLegacyLauncher DialogResult result = MessageBox.Show("Your configuration has been saved successfully!","RBXLegacy Launcher - Configuration", MessageBoxButtons.OKCancel, MessageBoxIcon.Information); } - void numericUpDown1TextChanged(object sender, EventArgs e) + void NumericUpDown1ValueChanged(object sender, EventArgs e) { int parsedValue; if (int.TryParse(numericUpDown1.Text, out parsedValue)) @@ -607,6 +627,7 @@ namespace RBXLegacyLauncher void ConsolePrint(string text, int type) { + //1 = white text, 2 = red text, 3 = green text, 4 = aqua text, 5 = yellow text richTextBox1.AppendText("[" + DateTime.Now.ToShortTimeString() + "]", Color.White); richTextBox1.AppendText(" - ", Color.White); if (type == 1) @@ -755,19 +776,19 @@ namespace RBXLegacyLauncher string HatIDOffline3 = GlobalVars.Custom_Hat3ID_Offline; if (GlobalVars.UsesPlayerName == true && GlobalVars.UsesID == true) { - args = quote + mapfile + "\" -script \"dofile('" + GlobalVars.DefaultScript + "') _G.SetRBXLegacyVersion(" + GlobalVars.SelectedClientVersion + ") _G.CSSolo(" + GlobalVars.UserID + ",'" + GlobalVars.PlayerName + "','" + HatIDOffline1 + "','" + HatIDOffline2 + "','" + HatIDOffline3 + "'," + GlobalVars.HeadColorID + "," + GlobalVars.TorsoColorID + "," + GlobalVars.LeftArmColorID + "," + GlobalVars.RightArmColorID + "," + GlobalVars.LeftLegColorID + "," + GlobalVars.RightLegColorID + ",'" + GlobalVars.Custom_TShirt + "','" + GlobalVars.Custom_Shirt + "','" + GlobalVars.Custom_Pants + "','" + GlobalVars.FaceID + "','" + GlobalVars.HeadID + "','" + GlobalVars.TorsoID + "','" + GlobalVars.RightArmID + "','" + GlobalVars.LeftArmID + "','" + GlobalVars.RightLegID + "','" + GlobalVars.LeftLegID + "','" + GlobalVars.Custom_Gear1 + "','" + GlobalVars.Custom_Gear2 + "','" + GlobalVars.Custom_Gear3 + "','" + GlobalVars.Custom_IconType + "'," + GlobalVars.melee.ToString().ToLower() + "," + GlobalVars.powerup.ToString().ToLower() + "," + GlobalVars.ranged.ToString().ToLower() + "," + GlobalVars.navigation.ToString().ToLower() + "," + GlobalVars.explosives.ToString().ToLower() + "," + GlobalVars.musical.ToString().ToLower() + "," + GlobalVars.social.ToString().ToLower() + "," + GlobalVars.transport.ToString().ToLower() + "," + GlobalVars.building.ToString().ToLower() + ") " + quote; + args = quote + mapfile + "\" -script \"dofile('" + GlobalVars.DefaultScript + "') _G.SetRBXLegacyVersion(" + GlobalVars.SelectedClientVersion + ") _G.CSSolo(" + GlobalVars.UserID + ",'" + GlobalVars.PlayerName + "','" + HatIDOffline1 + "','" + HatIDOffline2 + "','" + HatIDOffline3 + "'," + GlobalVars.HeadColorID + "," + GlobalVars.TorsoColorID + "," + GlobalVars.LeftArmColorID + "," + GlobalVars.RightArmColorID + "," + GlobalVars.LeftLegColorID + "," + GlobalVars.RightLegColorID + ",'" + GlobalVars.Custom_TShirt + "','" + GlobalVars.Custom_Shirt + "','" + GlobalVars.Custom_Pants + "','" + GlobalVars.FaceID + "','" + GlobalVars.HeadID + "','" + GlobalVars.TorsoID + "','" + GlobalVars.RightArmID + "','" + GlobalVars.LeftArmID + "','" + GlobalVars.RightLegID + "','" + GlobalVars.LeftLegID + "','" + GlobalVars.Custom_Gear1 + "','" + GlobalVars.Custom_Gear2 + "','" + GlobalVars.Custom_Gear3 + "','" + GlobalVars.Custom_IconType + "'," + GlobalVars.melee.ToString().ToLower() + "," + GlobalVars.powerup.ToString().ToLower() + "," + GlobalVars.ranged.ToString().ToLower() + "," + GlobalVars.navigation.ToString().ToLower() + "," + GlobalVars.explosives.ToString().ToLower() + "," + GlobalVars.musical.ToString().ToLower() + "," + GlobalVars.social.ToString().ToLower() + "," + GlobalVars.transport.ToString().ToLower() + "," + GlobalVars.building.ToString().ToLower() + "," + GlobalVars.RespawnTime + ") " + quote; } else if (GlobalVars.UsesPlayerName == false && GlobalVars.UsesID == true) { - args = quote + mapfile + "\" -script \"dofile('" + GlobalVars.DefaultScript + "') _G.SetRBXLegacyVersion(" + GlobalVars.SelectedClientVersion + ") _G.CSSolo(" + GlobalVars.UserID + ",'Player','" + HatIDOffline1 + "','" + HatIDOffline2 + "','" + HatIDOffline3 + "'," + GlobalVars.HeadColorID + "," + GlobalVars.TorsoColorID + "," + GlobalVars.LeftArmColorID + "," + GlobalVars.RightArmColorID + "," + GlobalVars.LeftLegColorID + "," + GlobalVars.RightLegColorID + ",'" + GlobalVars.Custom_TShirt + "','" + GlobalVars.Custom_Shirt + "','" + GlobalVars.Custom_Pants + "','" + GlobalVars.FaceID + "','" + GlobalVars.HeadID + "','" + GlobalVars.TorsoID + "','" + GlobalVars.RightArmID + "','" + GlobalVars.LeftArmID + "','" + GlobalVars.RightLegID + "','" + GlobalVars.LeftLegID + "','" + GlobalVars.Custom_Gear1 + "','" + GlobalVars.Custom_Gear2 + "','" + GlobalVars.Custom_Gear3 + "','" + GlobalVars.Custom_IconType + "'," + GlobalVars.melee.ToString().ToLower() + "," + GlobalVars.powerup.ToString().ToLower() + "," + GlobalVars.ranged.ToString().ToLower() + "," + GlobalVars.navigation.ToString().ToLower() + "," + GlobalVars.explosives.ToString().ToLower() + "," + GlobalVars.musical.ToString().ToLower() + "," + GlobalVars.social.ToString().ToLower() + "," + GlobalVars.transport.ToString().ToLower() + "," + GlobalVars.building.ToString().ToLower() + ") " + quote; + args = quote + mapfile + "\" -script \"dofile('" + GlobalVars.DefaultScript + "') _G.SetRBXLegacyVersion(" + GlobalVars.SelectedClientVersion + ") _G.CSSolo(" + GlobalVars.UserID + ",'Player','" + HatIDOffline1 + "','" + HatIDOffline2 + "','" + HatIDOffline3 + "'," + GlobalVars.HeadColorID + "," + GlobalVars.TorsoColorID + "," + GlobalVars.LeftArmColorID + "," + GlobalVars.RightArmColorID + "," + GlobalVars.LeftLegColorID + "," + GlobalVars.RightLegColorID + ",'" + GlobalVars.Custom_TShirt + "','" + GlobalVars.Custom_Shirt + "','" + GlobalVars.Custom_Pants + "','" + GlobalVars.FaceID + "','" + GlobalVars.HeadID + "','" + GlobalVars.TorsoID + "','" + GlobalVars.RightArmID + "','" + GlobalVars.LeftArmID + "','" + GlobalVars.RightLegID + "','" + GlobalVars.LeftLegID + "','" + GlobalVars.Custom_Gear1 + "','" + GlobalVars.Custom_Gear2 + "','" + GlobalVars.Custom_Gear3 + "','" + GlobalVars.Custom_IconType + "'," + GlobalVars.melee.ToString().ToLower() + "," + GlobalVars.powerup.ToString().ToLower() + "," + GlobalVars.ranged.ToString().ToLower() + "," + GlobalVars.navigation.ToString().ToLower() + "," + GlobalVars.explosives.ToString().ToLower() + "," + GlobalVars.musical.ToString().ToLower() + "," + GlobalVars.social.ToString().ToLower() + "," + GlobalVars.transport.ToString().ToLower() + "," + GlobalVars.building.ToString().ToLower() + "," + GlobalVars.RespawnTime + ") " + quote; } else if (GlobalVars.UsesPlayerName == true && GlobalVars.UsesID == false) { - args = quote + mapfile + "\" -script \"dofile('" + GlobalVars.DefaultScript + "') _G.SetRBXLegacyVersion(" + GlobalVars.SelectedClientVersion + ") _G.CSSolo(0,'" + GlobalVars.PlayerName + "','" + HatIDOffline1 + "','" + HatIDOffline2 + "','" + HatIDOffline3 + "'," + GlobalVars.HeadColorID + "," + GlobalVars.TorsoColorID + "," + GlobalVars.LeftArmColorID + "," + GlobalVars.RightArmColorID + "," + GlobalVars.LeftLegColorID + "," + GlobalVars.RightLegColorID + ",'" + GlobalVars.Custom_TShirt + "','" + GlobalVars.Custom_Shirt + "','" + GlobalVars.Custom_Pants + "','" + GlobalVars.FaceID + "','" + GlobalVars.HeadID + "','" + GlobalVars.TorsoID + "','" + GlobalVars.RightArmID + "','" + GlobalVars.LeftArmID + "','" + GlobalVars.RightLegID + "','" + GlobalVars.LeftLegID + "','" + GlobalVars.Custom_Gear1 + "','" + GlobalVars.Custom_Gear2 + "','" + GlobalVars.Custom_Gear3 + "','" + GlobalVars.Custom_IconType + "'," + GlobalVars.melee.ToString().ToLower() + "," + GlobalVars.powerup.ToString().ToLower() + "," + GlobalVars.ranged.ToString().ToLower() + "," + GlobalVars.navigation.ToString().ToLower() + "," + GlobalVars.explosives.ToString().ToLower() + "," + GlobalVars.musical.ToString().ToLower() + "," + GlobalVars.social.ToString().ToLower() + "," + GlobalVars.transport.ToString().ToLower() + "," + GlobalVars.building.ToString().ToLower() + ") " + quote; + args = quote + mapfile + "\" -script \"dofile('" + GlobalVars.DefaultScript + "') _G.SetRBXLegacyVersion(" + GlobalVars.SelectedClientVersion + ") _G.CSSolo(0,'" + GlobalVars.PlayerName + "','" + HatIDOffline1 + "','" + HatIDOffline2 + "','" + HatIDOffline3 + "'," + GlobalVars.HeadColorID + "," + GlobalVars.TorsoColorID + "," + GlobalVars.LeftArmColorID + "," + GlobalVars.RightArmColorID + "," + GlobalVars.LeftLegColorID + "," + GlobalVars.RightLegColorID + ",'" + GlobalVars.Custom_TShirt + "','" + GlobalVars.Custom_Shirt + "','" + GlobalVars.Custom_Pants + "','" + GlobalVars.FaceID + "','" + GlobalVars.HeadID + "','" + GlobalVars.TorsoID + "','" + GlobalVars.RightArmID + "','" + GlobalVars.LeftArmID + "','" + GlobalVars.RightLegID + "','" + GlobalVars.LeftLegID + "','" + GlobalVars.Custom_Gear1 + "','" + GlobalVars.Custom_Gear2 + "','" + GlobalVars.Custom_Gear3 + "','" + GlobalVars.Custom_IconType + "'," + GlobalVars.melee.ToString().ToLower() + "," + GlobalVars.powerup.ToString().ToLower() + "," + GlobalVars.ranged.ToString().ToLower() + "," + GlobalVars.navigation.ToString().ToLower() + "," + GlobalVars.explosives.ToString().ToLower() + "," + GlobalVars.musical.ToString().ToLower() + "," + GlobalVars.social.ToString().ToLower() + "," + GlobalVars.transport.ToString().ToLower() + "," + GlobalVars.building.ToString().ToLower() + "," + GlobalVars.RespawnTime + ") " + quote; } else if (GlobalVars.UsesPlayerName == false && GlobalVars.UsesID == false ) { - args = quote + mapfile + "\" -script \"dofile('" + GlobalVars.DefaultScript + "') _G.SetRBXLegacyVersion(" + GlobalVars.SelectedClientVersion + ") _G.CSSolo(0,'Player','" + HatIDOffline1 + "','" + HatIDOffline2 + "','" + HatIDOffline3 + "'," + GlobalVars.HeadColorID + "," + GlobalVars.TorsoColorID + "," + GlobalVars.LeftArmColorID + "," + GlobalVars.RightArmColorID + "," + GlobalVars.LeftLegColorID + "," + GlobalVars.RightLegColorID + ",'" + GlobalVars.Custom_TShirt + "','" + GlobalVars.Custom_Shirt + "','" + GlobalVars.Custom_Pants + "','" + GlobalVars.FaceID + "','" + GlobalVars.HeadID + "','" + GlobalVars.TorsoID + "','" + GlobalVars.RightArmID + "','" + GlobalVars.LeftArmID + "','" + GlobalVars.RightLegID + "','" + GlobalVars.LeftLegID + "','" + GlobalVars.Custom_Gear1 + "','" + GlobalVars.Custom_Gear2 + "','" + GlobalVars.Custom_Gear3 + "','" + GlobalVars.Custom_IconType + "'," + GlobalVars.melee.ToString().ToLower() + "," + GlobalVars.powerup.ToString().ToLower() + "," + GlobalVars.ranged.ToString().ToLower() + "," + GlobalVars.navigation.ToString().ToLower() + "," + GlobalVars.explosives.ToString().ToLower() + "," + GlobalVars.musical.ToString().ToLower() + "," + GlobalVars.social.ToString().ToLower() + "," + GlobalVars.transport.ToString().ToLower() + "," + GlobalVars.building.ToString().ToLower() + ") " + quote; + args = quote + mapfile + "\" -script \"dofile('" + GlobalVars.DefaultScript + "') _G.SetRBXLegacyVersion(" + GlobalVars.SelectedClientVersion + ") _G.CSSolo(0,'Player','" + HatIDOffline1 + "','" + HatIDOffline2 + "','" + HatIDOffline3 + "'," + GlobalVars.HeadColorID + "," + GlobalVars.TorsoColorID + "," + GlobalVars.LeftArmColorID + "," + GlobalVars.RightArmColorID + "," + GlobalVars.LeftLegColorID + "," + GlobalVars.RightLegColorID + ",'" + GlobalVars.Custom_TShirt + "','" + GlobalVars.Custom_Shirt + "','" + GlobalVars.Custom_Pants + "','" + GlobalVars.FaceID + "','" + GlobalVars.HeadID + "','" + GlobalVars.TorsoID + "','" + GlobalVars.RightArmID + "','" + GlobalVars.LeftArmID + "','" + GlobalVars.RightLegID + "','" + GlobalVars.LeftLegID + "','" + GlobalVars.Custom_Gear1 + "','" + GlobalVars.Custom_Gear2 + "','" + GlobalVars.Custom_Gear3 + "','" + GlobalVars.Custom_IconType + "'," + GlobalVars.melee.ToString().ToLower() + "," + GlobalVars.powerup.ToString().ToLower() + "," + GlobalVars.ranged.ToString().ToLower() + "," + GlobalVars.navigation.ToString().ToLower() + "," + GlobalVars.explosives.ToString().ToLower() + "," + GlobalVars.musical.ToString().ToLower() + "," + GlobalVars.social.ToString().ToLower() + "," + GlobalVars.transport.ToString().ToLower() + "," + GlobalVars.building.ToString().ToLower() + "," + GlobalVars.RespawnTime + ") " + quote; } try { @@ -799,7 +820,16 @@ namespace RBXLegacyLauncher try { ConsolePrint("Server Loaded.", 4); - Process.Start(rbxexe, args); + Process server = new Process(); + server.StartInfo.FileName = rbxexe; + server.StartInfo.Arguments = args; + server.EnableRaisingEvents = true; + ReadClientValues(GlobalVars.SelectedClient); + if (GlobalVars.upnp == true) + { + server.Exited += new EventHandler(ServerExited); + } + server.Start(); } catch (Exception ex) { @@ -826,7 +856,16 @@ namespace RBXLegacyLauncher try { ConsolePrint("Server Loaded in No3D mode.", 4); - Process.Start(rbxexe, args); + Process server = new Process(); + server.StartInfo.FileName = rbxexe; + server.StartInfo.Arguments = args; + server.EnableRaisingEvents = true; + ReadClientValues(GlobalVars.SelectedClient); + if (GlobalVars.upnp == true) + { + server.Exited += new EventHandler(ServerExited); + } + server.Start(); } catch (Exception ex) { @@ -835,6 +874,16 @@ namespace RBXLegacyLauncher } } + void ServerExited(object sender, EventArgs e) + { + StopUPNP(); + + if (this.WindowState == FormWindowState.Minimized) + { + this.WindowState = FormWindowState.Normal; + } + } + void StartStudio() { string mapfile = GlobalVars.MapsDir + @"\\" + GlobalVars.Map; @@ -872,8 +921,8 @@ namespace RBXLegacyLauncher void ConsoleProcessCommands(string command) { - string important = SecurityFuncs.Base64Decode("cmJ4bGVnYWN5IGthbnJpc2hh"); - if (command.Equals("rbxlegacy server")) + string important = SecurityFuncs.Base64Decode("a2FucmlzaGE="); + if (command.Equals("server")) { if (GlobalVars.upnp == true) { @@ -881,7 +930,7 @@ namespace RBXLegacyLauncher } StartServer(); } - else if (command.Equals("rbxlegacy server no3d")) + else if (command.Equals("serverno3d")) { if (GlobalVars.upnp == true) { @@ -889,15 +938,7 @@ namespace RBXLegacyLauncher } StartServerNo3D(); } - else if (command.Equals("rbxlegacy no3d")) - { - if (GlobalVars.upnp == true) - { - StartUPNP(); - } - StartServerNo3D(); - } - else if (command.Equals("rbxlegacy client")) + else if (command.Equals("client")) { ReadClientValues(GlobalVars.SelectedClient); if (GlobalVars.HasRocky == true) @@ -911,53 +952,37 @@ namespace RBXLegacyLauncher } StartClient(); } - else if (command.Equals("rbxlegacy client solo")) + else if (command.Equals("solo")) { StartSolo(); } - else if (command.Equals("rbxlegacy solo")) - { - StartSolo(); - } - else if (command.Equals("rbxlegacy studio")) + else if (command.Equals("studio")) { StartStudio(); } - else if (command.Equals("rbxlegacy config save")) + else if (command.Equals("config save")) { WriteConfigValues(); } - else if (command.Equals("rbxlegacy config load")) + else if (command.Equals("config load")) { ReadConfigValues(); } - else if (command.Equals("rbxlegacy config reset")) + else if (command.Equals("config reset")) { ResetConfigValues(); } - else if (command.Equals("rbxlegacy sdk")) - { - SDKForm sdk = new SDKForm(); - sdk.Show(); - ConsolePrint("Launched SDK.", 4); - } else if (command.Equals("sdk")) { SDKForm sdk = new SDKForm(); sdk.Show(); ConsolePrint("Launched SDK.", 4); } - else if (command.Equals("rbxlegacy help")) + else if (command.Equals("uri")) { - ConsoleRBXLegacyHelp(0); - } - else if (command.Equals("rbxlegacy")) - { - ConsoleRBXLegacyHelp(0); - } - else if (command.Equals("rbxlegacy config")) - { - ConsoleRBXLegacyHelp(1); + Process uri = new Process(); + uri.StartInfo.FileName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\RBXLegacyURI.exe"; + uri.Start(); } else if (command.Equals("config")) { @@ -975,7 +1000,7 @@ namespace RBXLegacyLauncher } else { - ConsolePrint("ERROR 3 - Command is either not registered or valid", 2); + ConsolePrint("ERROR 3 - Command is invalid", 2); } } @@ -984,32 +1009,36 @@ namespace RBXLegacyLauncher { if (page == 1) { - ConsolePrint("rbxlegacy config", 2); - ConsolePrint("-------------------------", 1); - ConsolePrint("= save | Saves the config file", 3); - ConsolePrint("= load | Reloads the config file", 3); - ConsolePrint("= reset | Resets the config file", 3); + ConsolePrint("RBXLegacy Config Command List", 2); + ConsolePrint("-----------------------------------------", 1); + ConsolePrint("config save | Saves the config file", 4); + ConsolePrint("config load | Reloads the config file", 4); + ConsolePrint("config reset | Resets the config file", 4); } else { - ConsolePrint("rbxlegacy", 2); - ConsolePrint("---------", 1); - ConsolePrint("= client | Loads client with launcher settings", 3); - ConsolePrint("-- solo | Loads client in Play Solo mode with launcher settings", 4); - ConsolePrint("= server | Loads server with launcher settings", 3); - ConsolePrint("-- no3d | Loads server in NoGraphics mode with launcher settings", 4); - ConsolePrint("= studio | Loads Roblox Studio with launcher settings", 3); - ConsolePrint("= sdk | Loads the RBXLegacy SDK", 3); - ConsolePrint("= config", 3); - ConsolePrint("-- save | Saves the config file", 4); - ConsolePrint("-- load | Reloads the config file", 4); - ConsolePrint("-- reset | Resets the config file", 4); + ConsolePrint("RBXLegacy Console Command List", 2); + ConsolePrint("------------------------------------------", 1); + ConsolePrint("client | Loads client with launcher settings", 4); + ConsolePrint("solo | Loads client in Play Solo mode with launcher settings", 4); + ConsolePrint("server | Loads server with launcher settings", 4); + ConsolePrint("serverno3d | Loads server in No3D mode with launcher settings", 4); + ConsolePrint("studio | Loads Roblox Studio with launcher settings", 4); + ConsolePrint("sdk | Loads the RBXLegacy SDK", 4); + ConsolePrint("uri | Installs the RBXLegacy URI", 4); + ConsolePrint("config save | Saves the config file", 4); + ConsolePrint("config load | Reloads the config file", 4); + ConsolePrint("config reset | Resets the config file", 4); } } protected override void OnFormClosing(FormClosingEventArgs e) { base.OnFormClosing(e); + if (GlobalVars.upnp == true) + { + StopUPNP(); + } WriteConfigValues(); } diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.resx b/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.resx index 883cf29..aeb0a9e 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.resx +++ b/RBXLegacyLauncher/RBXLegacyLauncher/MainForm.resx @@ -112,16 +112,16 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + iVBORw0KGgoAAAANSUhEUgAAAMYAAAAhCAYAAACC2tRnAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vgAADr4B6kKxwAAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTFH80I3AAAYO0lEQVR4Xu1c + vAAADrwBlbxySQAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTFH80I3AAAYO0lEQVR4Xu1c CXgUVba+ne5sEPYdJAKCOiqCDqPCoIgbDjow41NHEARRH47PcUNRouI2jLKKIgIiKgg+NtFRILImQJAd M7INARJCEsOSAFl7q+r/nXPrVqe6Ux1ah+D3vi/n+/6vqqvOPffce89/t6pqYScQ4k7CNgjXIThcWXS0 ICYLDlEFUU9BXQ/qWXXoXozI8jtFVmWsAb+LrtM1iRBdy2/j3u8I8cqvOEJXCOc9ZHMUnb9IOoyxpP8e @@ -235,7 +235,7 @@ iVBORw0KGgoAAAANSUhEUgAAAMYAAAAhCAYAAACC2tRnAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wAAADsABataJCQAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTFH80I3AAAYO0lEQVR4Xu1c + vQAADr0BR/uQrQAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTFH80I3AAAYO0lEQVR4Xu1c CXgUVba+ne5sEPYdJAKCOiqCDqPCoIgbDjow41NHEARRH47PcUNRouI2jLKKIgIiKgg+NtFRILImQJAd M7INARJCEsOSAFl7q+r/nXPrVqe6Ux1ah+D3vi/n+/6vqqvOPffce89/t6pqYScQ4k7CNgjXIThcWXS0 ICYLDlEFUU9BXQ/qWXXoXozI8jtFVmWsAb+LrtM1iRBdy2/j3u8I8cqvOEJXCOc9ZHMUnb9IOoyxpP8e diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/QuickConfigure.resx b/RBXLegacyLauncher/RBXLegacyLauncher/QuickConfigure.resx index 8fcf2e5..6c8efa7 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/QuickConfigure.resx +++ b/RBXLegacyLauncher/RBXLegacyLauncher/QuickConfigure.resx @@ -112,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/RBXLegacyLauncher.csproj b/RBXLegacyLauncher/RBXLegacyLauncher/RBXLegacyLauncher.csproj index 7366c54..f3a4924 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/RBXLegacyLauncher.csproj +++ b/RBXLegacyLauncher/RBXLegacyLauncher/RBXLegacyLauncher.csproj @@ -7,7 +7,7 @@ WinExe RBXLegacyLauncher RBXLegacyLauncher - v2.0 + v4.5 Properties @@ -49,14 +49,23 @@ ..\..\..\RBXLegacy\release\RBXLegacy\RBXLegacyLauncher.exe - - Resources\Mono.Nat.dll + + Resources\Open.Nat.dll + + 3.5 + + + 3.5 + + + 3.5 + @@ -110,9 +119,6 @@ DocForm.Designer.cs - - ClientSettings.cs - LoaderForm.cs diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/Resources/Mono.Nat.dll b/RBXLegacyLauncher/RBXLegacyLauncher/Resources/Mono.Nat.dll deleted file mode 100644 index 614b2c233228c02d6dcad894ed490c57a9efd9cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42496 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 VRe!gxT;7#^Y?l9_{(sj3{|C9aI;8*r diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/Resources/Open.Nat.dll b/RBXLegacyLauncher/RBXLegacyLauncher/Resources/Open.Nat.dll new file mode 100644 index 0000000000000000000000000000000000000000..1fff0f9cf184933ec1f1957b74278c92735022cc GIT binary patch literal 72192 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 O5;yrT|NH-r0{<7Lzvv7A literal 0 HcmV?d00001 diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/RichTextBoxExtensions.cs b/RBXLegacyLauncher/RBXLegacyLauncher/RichTextBoxExtensions.cs index 6850363..6a5f5f2 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/RichTextBoxExtensions.cs +++ b/RBXLegacyLauncher/RBXLegacyLauncher/RichTextBoxExtensions.cs @@ -4,12 +4,14 @@ using System.Drawing; using System.Windows.Forms; // you need this once (only), and it must be in this namespace +/* namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] public sealed class ExtensionAttribute : Attribute {} } +*/ public static class RichTextBoxExtensions { diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/SDKForm.Designer.cs b/RBXLegacyLauncher/RBXLegacyLauncher/SDKForm.Designer.cs index 4b2abe4..bad469c 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/SDKForm.Designer.cs +++ b/RBXLegacyLauncher/RBXLegacyLauncher/SDKForm.Designer.cs @@ -97,7 +97,7 @@ this.checkBox4.Name = "checkBox4"; this.checkBox4.Size = new System.Drawing.Size(192, 20); this.checkBox4.TabIndex = 34; - this.checkBox4.Text = "Use Sudppipe (RECCOMENDED)"; + this.checkBox4.Text = "Use Sudppipe (RECOMMENDED)"; this.checkBox4.UseVisualStyleBackColor = true; this.checkBox4.CheckedChanged += new System.EventHandler(this.CheckBox4CheckedChanged); // diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/SDKForm.resx b/RBXLegacyLauncher/RBXLegacyLauncher/SDKForm.resx index 8fcf2e5..3ce0458 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/SDKForm.resx +++ b/RBXLegacyLauncher/RBXLegacyLauncher/SDKForm.resx @@ -112,12 +112,12 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + AAABAAYAEBAAAAAAIABoBAAAZgAAACAgAAAAACAAqBAAAM4EAAAwMAAAAAAgAKglAAB2FQAAQEAAAAAA diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.Designer.cs b/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.Designer.cs index a75ec87..df3a4c6 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.Designer.cs +++ b/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.Designer.cs @@ -19,6 +19,7 @@ this.textBox1 = new System.Windows.Forms.TextBox(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage1 = new System.Windows.Forms.TabPage(); + this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); this.checkBox12 = new System.Windows.Forms.CheckBox(); this.textBox11 = new System.Windows.Forms.TextBox(); this.textBox12 = new System.Windows.Forms.TextBox(); @@ -55,11 +56,10 @@ this.radioButton1 = new System.Windows.Forms.RadioButton(); this.tabPage2 = new System.Windows.Forms.TabPage(); this.button1 = new System.Windows.Forms.Button(); - this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); this.tabControl1.SuspendLayout(); this.tabPage1.SuspendLayout(); - this.tabPage2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); + this.tabPage2.SuspendLayout(); this.SuspendLayout(); // // textBox1 @@ -133,6 +133,24 @@ this.tabPage1.UseVisualStyleBackColor = true; this.tabPage1.Click += new System.EventHandler(this.TabPage1Click); // + // numericUpDown1 + // + this.numericUpDown1.Location = new System.Drawing.Point(240, 80); + this.numericUpDown1.Maximum = new decimal(new int[] { + 99999, + 0, + 0, + 0}); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(237, 20); + this.numericUpDown1.TabIndex = 67; + this.numericUpDown1.Value = new decimal(new int[] { + 53640, + 0, + 0, + 0}); + this.numericUpDown1.ValueChanged += new System.EventHandler(this.NumericUpDown1ValueChanged); + // // checkBox12 // this.checkBox12.Location = new System.Drawing.Point(353, 6); @@ -494,23 +512,6 @@ this.button1.UseVisualStyleBackColor = true; this.button1.Click += new System.EventHandler(this.Button1Click); // - // numericUpDown1 - // - this.numericUpDown1.Location = new System.Drawing.Point(240, 80); - this.numericUpDown1.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.numericUpDown1.Name = "numericUpDown1"; - this.numericUpDown1.Size = new System.Drawing.Size(237, 20); - this.numericUpDown1.TabIndex = 67; - this.numericUpDown1.Value = new decimal(new int[] { - 53640, - 0, - 0, - 0}); - // // ServerPrefs // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -530,9 +531,9 @@ this.tabControl1.ResumeLayout(false); this.tabPage1.ResumeLayout(false); this.tabPage1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); this.tabPage2.ResumeLayout(false); this.tabPage2.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); this.ResumeLayout(false); } private System.Windows.Forms.NumericUpDown numericUpDown1; diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.cs b/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.cs index 0ee0c6e..e3fe0cf 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.cs +++ b/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.cs @@ -2,6 +2,9 @@ using System.Drawing; using System.Windows.Forms; using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Open.Nat; namespace RBXLegacyLauncher { @@ -191,21 +194,16 @@ namespace RBXLegacyLauncher string GetExternalIPAddress() { - string ipAddress; - using (WebClient wc = new WebClient()) - { - try - { - Mono.Nat.INatDevice natd = e.Device; - ipAddress = natd.GetExternalIP().ToString(); - } - catch (Exception) - { - ipAddress = "localhost"; - } - } - - return ipAddress; + string url = "http://checkip.dyndns.org"; + System.Net.WebRequest req = System.Net.WebRequest.Create(url); + System.Net.WebResponse resp = req.GetResponse(); + System.IO.StreamReader sr = new System.IO.StreamReader(resp.GetResponseStream()); + string response = sr.ReadToEnd().Trim(); + string[] a = response.Split(':'); + string a2 = a[1].Substring(1); + string[] a3 = a2.Split('<'); + string a4 = a3[0]; + return a4; } void RadioButton1CheckedChanged(object sender, EventArgs e) @@ -307,7 +305,7 @@ namespace RBXLegacyLauncher GlobalVars.building = false; } - void numericUpDown1TextChanged(object sender, EventArgs e) + void NumericUpDown1ValueChanged(object sender, EventArgs e) { int parsedValue; if (int.TryParse(numericUpDown1.Text, out parsedValue)) diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.resx b/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.resx index 8fcf2e5..3ce0458 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.resx +++ b/RBXLegacyLauncher/RBXLegacyLauncher/ServerPrefs.resx @@ -112,12 +112,12 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + AAABAAYAEBAAAAAAIABoBAAAZgAAACAgAAAAACAAqBAAAM4EAAAwMAAAAAAgAKglAAB2FQAAQEAAAAAA diff --git a/RBXLegacyLauncher/RBXLegacyLauncher/app.config b/RBXLegacyLauncher/RBXLegacyLauncher/app.config index 8520aac..9008dd2 100644 --- a/RBXLegacyLauncher/RBXLegacyLauncher/app.config +++ b/RBXLegacyLauncher/RBXLegacyLauncher/app.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/RBXLegacySetup.iss b/RBXLegacySetup.iss index a771707..3d0ee53 100644 --- a/RBXLegacySetup.iss +++ b/RBXLegacySetup.iss @@ -31,6 +31,7 @@ Name: "quicklaunchicon"; Description: "Create a icon on your Quick Start Menu"; [Files] Source: "RBXLegacy\RBXLegacyLauncher.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "RBXLegacy\RBXLegacyLauncher.exe.config"; DestDir: "{app}"; Flags: ignoreversion +Source: "RBXLegacy\RBXLegacyURI.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "RBXLegacy\README.TXT"; DestDir: "{app}"; Flags: ignoreversion Source: "RBXLegacy\changelog.txt"; DestDir: "{app}"; Flags: ignoreversion Source: "RBXLegacy\info.txt"; DestDir: "{app}"; Flags: ignoreversion @@ -40,7 +41,7 @@ Source: "RBXLegacy\models\*"; DestDir: "{app}\models"; Flags: ignoreversion recu Source: "RBXLegacy\avatar\*"; DestDir: "{app}\avatar"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "RBXLegacy\scripts\*"; DestDir: "{app}\scripts"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "RBXLegacy\udppipe.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "RBXLegacy\Mono.Nat.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "RBXLegacy\Open.Nat.dll"; DestDir: "{app}"; Flags: ignoreversion [Icons] Name: "{group}\RBXLegacy"; Filename: "{app}\RBXLegacyLauncher.exe" @@ -50,5 +51,6 @@ Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\RBXLegacy"; Filena [Run] Filename: "{app}\RBXLegacyLauncher.exe"; Description: "Play RBXLegacy"; Flags: nowait postinstall skipifsilent +Filename: "{app}\RBXLegacyURI.exe"; Description: "Install the URI"; Flags: nowait postinstall skipifsilent Filename: "{app}\README.TXT"; Description: "View the README file"; Flags: postinstall shellexec skipifsilent unchecked Filename: "{app}\changelog.txt"; Description: "View the changelog"; Flags: postinstall shellexec skipifsilent unchecked diff --git a/RBXLegacySetupPreview.iss b/RBXLegacySetupPreview.iss index 6ff4b34..76abc44 100644 --- a/RBXLegacySetupPreview.iss +++ b/RBXLegacySetupPreview.iss @@ -31,6 +31,7 @@ Name: "quicklaunchicon"; Description: "Create a icon on your Quick Start Menu"; [Files] Source: "RBXLegacy-Preview\RBXLegacyLauncher.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "RBXLegacy-Preview\RBXLegacyLauncher.exe.config"; DestDir: "{app}"; Flags: ignoreversion +Source: "RBXLegacy-Preview\RBXLegacyURI.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "RBXLegacy-Preview\README.TXT"; DestDir: "{app}"; Flags: ignoreversion Source: "RBXLegacy-Preview\changelog.txt"; DestDir: "{app}"; Flags: ignoreversion Source: "RBXLegacy-Preview\info.txt"; DestDir: "{app}"; Flags: ignoreversion @@ -40,7 +41,7 @@ Source: "RBXLegacy-Preview\models\*"; DestDir: "{app}\models"; Flags: ignorevers Source: "RBXLegacy-Preview\avatar\*"; DestDir: "{app}\avatar"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "RBXLegacy-Preview\scripts\*"; DestDir: "{app}\scripts"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "RBXLegacy-Preview\udppipe.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "RBXLegacy-Preview\Mono.Nat.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "RBXLegacy-Preview\Open.Nat.dll"; DestDir: "{app}"; Flags: ignoreversion [Icons] Name: "{group}\RBXLegacy-Preview"; Filename: "{app}\RBXLegacyLauncher.exe" @@ -49,6 +50,7 @@ Name: "{commondesktop}\RBXLegacy-Preview"; Filename: "{app}\RBXLegacyLauncher.ex Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\RBXLegacy-Preview"; Filename: "{app}\RBXLegacyLauncher.exe"; Tasks: quicklaunchicon [Run] -Filename: "{app}\RBXLegacyLauncher.exe"; Description: "Play RBXLegacy 1.18 Preview"; Flags: nowait postinstall skipifsilent +Filename: "{app}\RBXLegacyLauncher.exe"; Description: "Play RBXLegacy"; Flags: nowait postinstall skipifsilent +Filename: "{app}\RBXLegacyURI.exe"; Description: "Install the URI"; Flags: nowait postinstall skipifsilent Filename: "{app}\README.TXT"; Description: "View the README file"; Flags: postinstall shellexec skipifsilent unchecked Filename: "{app}\changelog.txt"; Description: "View the changelog"; Flags: postinstall shellexec skipifsilent unchecked diff --git a/RBXLegacyURI/RBXLegacyURI/Program.cs b/RBXLegacyURI/RBXLegacyURI/Program.cs index 2278475..de02dc6 100644 --- a/RBXLegacyURI/RBXLegacyURI/Program.cs +++ b/RBXLegacyURI/RBXLegacyURI/Program.cs @@ -18,15 +18,18 @@ namespace RBXLegacyURI private static void Main(string[] args) { - Console.Title = "RBXLegacy"; - try { + Console.Title = "RBXLegacy URI Installer"; + try + { Program.registerURI("RBXLegacy", AppDomain.CurrentDomain.BaseDirectory + "RBXLegacyLauncher.exe", ""); Program.registerURI("rbxlegacy", AppDomain.CurrentDomain.BaseDirectory + "RBXLegacyLauncher.exe", ""); // chromium - Console.WriteLine("RBXLegacy has been installed on your computer successfully! You can now join RBXLegacy servers with URI."); + Console.WriteLine("RBXLegacy has been installed on your computer successfully! You can now join RBXLegacy servers with URI links and via the website!"); Console.WriteLine("Press any key to continue . . ."); Console.ReadKey(); - } catch (Exception) { - Console.Title = "RBXLegacy - ERROR"; + Console.WriteLine("z0mgh4x0r l0gs"); // how can they see this still??? 1337 h4x0r + } + catch (Exception) + { Console.WriteLine("RBXLegacy failed to install the RBXLegacy URL protocol."); Console.WriteLine("Make sure you're running this application as an administrator, and that RBXLegacyLauncher.exe is in the RBXLegacy folder."); Console.WriteLine("Press any key to continue . . ."); diff --git a/RBXLegacyURI/RBXLegacyURI/RBXLegacyURI.csproj b/RBXLegacyURI/RBXLegacyURI/RBXLegacyURI.csproj index 93381f3..ed6f875 100644 --- a/RBXLegacyURI/RBXLegacyURI/RBXLegacyURI.csproj +++ b/RBXLegacyURI/RBXLegacyURI/RBXLegacyURI.csproj @@ -10,6 +10,8 @@ v4.0 Client Properties + Resources\rbxlegacyicon2.ico + False x86 diff --git a/RBXLegacyURI/RBXLegacyURI/Resources/rbxlegacyicon2.ico b/RBXLegacyURI/RBXLegacyURI/Resources/rbxlegacyicon2.ico new file mode 100644 index 0000000000000000000000000000000000000000..73e253833eada2005458087575bd4ed2080e0068 GIT binary patch literal 99678 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