diff --git a/src/electron/custom_install_steps.nsh b/src/electron/custom_install_steps.nsh index 4ca378065d..3f106efcd6 100755 --- a/src/electron/custom_install_steps.nsh +++ b/src/electron/custom_install_steps.nsh @@ -1,170 +1,170 @@ -; Copyright 2018 The Outline Authors -; -; Licensed under the Apache License, Version 2.0 (the "License"); -; you may not use this file except in compliance with the License. -; You may obtain a copy of the License at -; -; http://www.apache.org/licenses/LICENSE-2.0 -; -; Unless required by applicable law or agreed to in writing, software -; distributed under the License is distributed on an "AS IS" BASIS, -; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -; See the License for the specific language governing permissions and -; limitations under the License. - -!include StrFunc.nsh -!include WinVer.nsh -!include x64.nsh - -!include env.nsh - -; StrFunc weirdness; this fix suggested here: -; https://github.com/electron-userland/electron-builder/issues/888 -!ifndef BUILD_UNINSTALLER -${StrLoc} -${StrNSISToIO} -${StrRep} -!endif - -!macro customInstall - ; Normally, because we mark the installer binary as requiring administrator permissions, the - ; installer will be running with administrator permissions at this point. The exception is when - ; the system is running with the *lowest* (least safe) UAC setting in which case the installer - ; can progress to this point without administrator permissions. - ; - ; If that's the case, exit now so we don't waste time to trying to install the TAP device, etc. - ; Additionally, the client can detect their absence and prompt the user to reinstall. - ; - ; The returned value does *not* seem to be based on the user's current diaplay language. - UserInfo::GetAccountType - Pop $0 - StrCmp $0 "Admin" isadmin - MessageBox MB_OK "Sorry, Outline requires administrator permissions." - Quit - - isadmin: - - ; TAP device files. - SetOutPath "$INSTDIR\tap-windows6" - ${If} ${RunningX64} - File /r "${PROJECT_DIR}\third_party\tap-windows6\bin\amd64\*" - ${Else} - File /r "${PROJECT_DIR}\third_party\tap-windows6\bin\i386\*" - ${EndIf} - SetOutPath - - File "${PROJECT_DIR}\src\electron\add_tap_device.bat" - File "${PROJECT_DIR}\src\electron\find_tap_device_name.bat" - - ; OutlineService files, stopping the service first in case it's still running. - nsExec::Exec "$SYSDIR\net stop OutlineService" - File "${PROJECT_DIR}\tools\OutlineService\OutlineService\bin\OutlineService.exe" - File "${PROJECT_DIR}\tools\smartdnsblock\bin\smartdnsblock.exe" - File "${PROJECT_DIR}\third_party\newtonsoft\Newtonsoft.Json.dll" - File "${PROJECT_DIR}\src\electron\install_windows_service.bat" - - ; ExecToStack captures both stdout and stderr from the script, in the order output. - ; Set a (long) timeout in case the device never becomes visible to netsh. - ReadEnvStr $0 COMSPEC - nsExec::ExecToStack /timeout=180000 '$0 /c add_tap_device.bat' - - Pop $0 - Pop $1 - StrCmp $0 0 installservice - - ; The TAP device may have failed to install because the user did not want to - ; install the device driver. If so: - ; - tell the user that they need to install the driver - ; - skip the Sentry report - ; - quit - ; - ; When this happens, tapinstall.exe prints an error message like this: - ; UpdateDriverForPlugAndPlayDevices failed, GetLastError=-536870333 - ; - ; We can use the presence of that magic number to detect this case. - Var /GLOBAL DRIVER_FAILURE_MAGIC_NUMBER_INDEX - ${StrLoc} $DRIVER_FAILURE_MAGIC_NUMBER_INDEX $1 "536870333" ">" - - StrCmp $DRIVER_FAILURE_MAGIC_NUMBER_INDEX "" submitsentryreport - ; The term "device software" is the same as that used by the prompt, at least on Windows 7. - MessageBox MB_OK "Sorry, you must install the device software in order to use Outline. Please try \ - running the installer again." - Quit - - submitsentryreport: - MessageBox MB_OK "Sorry, we could not configure your system to connect to Outline. Please try \ - running the installer again. If you still cannot install Outline, please get in \ - touch with us and let us know that the TAP device failed to install with error code $0." - - ; Submit a Sentry error event. - ; - ; This will get bundled into an issue named "could not install TAP device" with the following - ; attributes: - ; - a single breadcrumb containing the output of add_tap_device.bat - ; - Windows version, as a tag named "os" with a value identical in most cases to what the - ; JavaScript Sentry client produces, e.g. "Windows 10.0.17134" - ; - client version - ; - ; Note: - ; - Sentry won't accept a breadcrumbs without a timestamp; fortunately, it accepts obviously - ; bogus values so we don't have to fetch the real time. - ; - Because nsExec::ExecToStack yields "NSIS strings" strings suitable for inclusion in, for - ; example, a MessageBox, e.g. "device not found$\ncommand failed", we must convert it to a - ; string that Sentry will like *and* can fit on one line, e.g. - ; "device not found\ncommand failed"; fortunately, StrFunc.nsh's StrNSISToIO does precisely - ; this. - ; - RELEASE and SENTRY_URL are defined in env.nsh which is generated at build time by - ; {package,release}_action.sh. - - ; TODO: Remove this once we figure out why/if breadcrumbs are being truncated. - Var /GLOBAL FAILURE_MESSAGE_LENGTH - StrLen $FAILURE_MESSAGE_LENGTH $1 - - ; http://nsis.sourceforge.net/Docs/StrFunc/StrFunc.txt - Var /GLOBAL FAILURE_MESSAGE - ${StrNSISToIO} $FAILURE_MESSAGE $1 - ${StrRep} $FAILURE_MESSAGE $FAILURE_MESSAGE '"' '\"' - - ${WinVerGetMajor} $R0 - ${WinVerGetMinor} $R1 - ${WinVerGetBuild} $R2 - - ; http://nsis.sourceforge.net/Inetc_plug-in#post - inetc::post '{\ - "message":"could not install TAP device ($0)",\ - "release":"${RELEASE}",\ - "tags":[\ - ["os", "Windows $R0.$R1.$R2"],\ - ["error_message_length", "$FAILURE_MESSAGE_LENGTH"]\ - ],\ - "breadcrumbs":[\ - {"timestamp":1, "message":"$FAILURE_MESSAGE"}\ - ]\ - }' /TOSTACK ${SENTRY_URL} /END - - Quit - - installservice: - - nsExec::Exec install_windows_service.bat - - nsExec::Exec "$SYSDIR\sc query OutlineService" - Pop $0 - StrCmp $0 0 success - ; TODO: Trigger a Sentry report for service installation failure, too, and revisit - ; the restart stuff in the TypeScript code. - MessageBox MB_OK "Sorry, we could not configure your system to connect to Outline. Please try \ - running the installer again. If you still cannot install Outline, please get in touch with us \ - and let us know that OutlineService failed to install." - Quit - - success: - -!macroend - -; TODO: Remove the TAP device on uninstall. This is impossible to implement safely -; with the bundled tapinstall.exe because it can only remove *all* devices -; having hwid tap0901 and these may include non-Outline devices. -!macro customUnInstall - nsExec::Exec "$SYSDIR\net stop OutlineService" - nsExec::Exec "$SYSDIR\sc delete OutlineService" -!macroend +; Copyright 2018 The Outline Authors +; +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; +; http://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. + +!include StrFunc.nsh +!include WinVer.nsh +!include x64.nsh + +!include env.nsh + +; StrFunc weirdness; this fix suggested here: +; https://github.com/electron-userland/electron-builder/issues/888 +!ifndef BUILD_UNINSTALLER +${StrLoc} +${StrNSISToIO} +${StrRep} +!endif + +!macro customInstall + ; Normally, because we mark the installer binary as requiring administrator permissions, the + ; installer will be running with administrator permissions at this point. The exception is when + ; the system is running with the *lowest* (least safe) UAC setting in which case the installer + ; can progress to this point without administrator permissions. + ; + ; If that's the case, exit now so we don't waste time to trying to install the TAP device, etc. + ; Additionally, the client can detect their absence and prompt the user to reinstall. + ; + ; The returned value does *not* seem to be based on the user's current diaplay language. + UserInfo::GetAccountType + Pop $0 + StrCmp $0 "Admin" isadmin + MessageBox MB_OK "Sorry, Outline requires administrator permissions." + Quit + + isadmin: + + ; TAP device files. + SetOutPath "$INSTDIR\tap-windows6" + ${If} ${RunningX64} + File /r "${PROJECT_DIR}\third_party\tap-windows6\bin\amd64\*" + ${Else} + File /r "${PROJECT_DIR}\third_party\tap-windows6\bin\i386\*" + ${EndIf} + SetOutPath - + File "${PROJECT_DIR}\src\electron\add_tap_device.bat" + File "${PROJECT_DIR}\src\electron\find_tap_device_name.bat" + + ; OutlineService files, stopping the service first in case it's still running. + nsExec::Exec "$SYSDIR\net stop OutlineService" + File "${PROJECT_DIR}\tools\OutlineService\OutlineService\bin\OutlineService.exe" + File "${PROJECT_DIR}\tools\smartdnsblock\bin\smartdnsblock.exe" + File "${PROJECT_DIR}\third_party\newtonsoft\Newtonsoft.Json.dll" + File "${PROJECT_DIR}\src\electron\install_windows_service.bat" + + ; ExecToStack captures both stdout and stderr from the script, in the order output. + ; Set a (long) timeout in case the device never becomes visible to netsh. + ReadEnvStr $0 COMSPEC + nsExec::ExecToStack /timeout=180000 '$0 /c add_tap_device.bat' + + Pop $0 + Pop $1 + StrCmp $0 0 installservice + + ; The TAP device may have failed to install because the user did not want to + ; install the device driver. If so: + ; - tell the user that they need to install the driver + ; - skip the Sentry report + ; - quit + ; + ; When this happens, tapinstall.exe prints an error message like this: + ; UpdateDriverForPlugAndPlayDevices failed, GetLastError=-536870333 + ; + ; We can use the presence of that magic number to detect this case. + Var /GLOBAL DRIVER_FAILURE_MAGIC_NUMBER_INDEX + ${StrLoc} $DRIVER_FAILURE_MAGIC_NUMBER_INDEX $1 "536870333" ">" + + StrCmp $DRIVER_FAILURE_MAGIC_NUMBER_INDEX "" submitsentryreport + ; The term "device software" is the same as that used by the prompt, at least on Windows 7. + MessageBox MB_OK "Sorry, you must install the device software in order to use Outline. Please try \ + running the installer again." + Quit + + submitsentryreport: + MessageBox MB_OK "Sorry, we could not configure your system to connect to Outline. Please try \ + running the installer again. If you still cannot install Outline, please get in \ + touch with us and let us know that the TAP device failed to install with error code $0." + + ; Submit a Sentry error event. + ; + ; This will get bundled into an issue named "could not install TAP device" with the following + ; attributes: + ; - a single breadcrumb containing the output of add_tap_device.bat + ; - Windows version, as a tag named "os" with a value identical in most cases to what the + ; JavaScript Sentry client produces, e.g. "Windows 10.0.17134" + ; - client version + ; + ; Note: + ; - Sentry won't accept a breadcrumbs without a timestamp; fortunately, it accepts obviously + ; bogus values so we don't have to fetch the real time. + ; - Because nsExec::ExecToStack yields "NSIS strings" strings suitable for inclusion in, for + ; example, a MessageBox, e.g. "device not found$\ncommand failed", we must convert it to a + ; string that Sentry will like *and* can fit on one line, e.g. + ; "device not found\ncommand failed"; fortunately, StrFunc.nsh's StrNSISToIO does precisely + ; this. + ; - RELEASE and SENTRY_URL are defined in env.nsh which is generated at build time by + ; {package,release}_action.sh. + + ; TODO: Remove this once we figure out why/if breadcrumbs are being truncated. + Var /GLOBAL FAILURE_MESSAGE_LENGTH + StrLen $FAILURE_MESSAGE_LENGTH $1 + + ; http://nsis.sourceforge.net/Docs/StrFunc/StrFunc.txt + Var /GLOBAL FAILURE_MESSAGE + ${StrNSISToIO} $FAILURE_MESSAGE $1 + ${StrRep} $FAILURE_MESSAGE $FAILURE_MESSAGE '"' '\"' + + ${WinVerGetMajor} $R0 + ${WinVerGetMinor} $R1 + ${WinVerGetBuild} $R2 + + ; http://nsis.sourceforge.net/Inetc_plug-in#post + inetc::post '{\ + "message":"could not install TAP device ($0)",\ + "release":"${RELEASE}",\ + "tags":[\ + ["os", "Windows $R0.$R1.$R2"],\ + ["error_message_length", "$FAILURE_MESSAGE_LENGTH"]\ + ],\ + "breadcrumbs":[\ + {"timestamp":1, "message":"$FAILURE_MESSAGE"}\ + ]\ + }' /TOSTACK ${SENTRY_URL} /END + + Quit + + installservice: + + nsExec::Exec install_windows_service.bat + + nsExec::Exec "$SYSDIR\sc query OutlineService" + Pop $0 + StrCmp $0 0 success + ; TODO: Trigger a Sentry report for service installation failure, too, and revisit + ; the restart stuff in the TypeScript code. + MessageBox MB_OK "Sorry, we could not configure your system to connect to Outline. Please try \ + running the installer again. If you still cannot install Outline, please get in touch with us \ + and let us know that OutlineService failed to install." + Quit + + success: + +!macroend + +; TODO: Remove the TAP device on uninstall. This is impossible to implement safely +; with the bundled tapinstall.exe because it can only remove *all* devices +; having hwid tap0901 and these may include non-Outline devices. +!macro customUnInstall + nsExec::Exec "$SYSDIR\net stop OutlineService" + nsExec::Exec "$SYSDIR\sc delete OutlineService" +!macroend diff --git a/third_party/newtonsoft/LICENSE b/third_party/newtonsoft/LICENSE index 05bc493e4d..ed11c37a1a 100755 --- a/third_party/newtonsoft/LICENSE +++ b/third_party/newtonsoft/LICENSE @@ -1,9 +1,9 @@ -The MIT License (MIT) - -Copyright (c) 2007 James Newton-King - -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 MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +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. \ No newline at end of file diff --git a/third_party/newtonsoft/METADATA b/third_party/newtonsoft/METADATA index cd5e964139..f43a7d6df0 100755 --- a/third_party/newtonsoft/METADATA +++ b/third_party/newtonsoft/METADATA @@ -1,16 +1,16 @@ -name: "Newtonsoft.Json" -description: - "Json.NET is a high-performance JSON framework for .NET." - -third_party { - url { - type: HOMEPAGE - value: "https://www.newtonsoft.com/json" - } - url { - type: GIT - value: "https://github.com/JamesNK/Newtonsoft.Json" - } - version: "11.0.0" - last_upgrade_date { year: 2018 month: 6 day: 26 } -} +name: "Newtonsoft.Json" +description: + "Json.NET is a high-performance JSON framework for .NET." + +third_party { + url { + type: HOMEPAGE + value: "https://www.newtonsoft.com/json" + } + url { + type: GIT + value: "https://github.com/JamesNK/Newtonsoft.Json" + } + version: "11.0.0" + last_upgrade_date { year: 2018 month: 6 day: 26 } +} diff --git a/third_party/tap-windows6/.gitignore b/third_party/tap-windows6/.gitignore index feb9211635..c84d745040 100644 --- a/third_party/tap-windows6/.gitignore +++ b/third_party/tap-windows6/.gitignore @@ -1,10 +1,10 @@ -dist/** -*.pyc -*.tar.gz -src/config.h -src/SOURCES -src/build*.log -src/obj* -src/i386 -src/amd64 -tap-windows-*.exe +dist/** +*.pyc +*.tar.gz +src/config.h +src/SOURCES +src/build*.log +src/obj* +src/i386 +src/amd64 +tap-windows-*.exe diff --git a/third_party/tap-windows6/bin/amd64/OemVista.inf b/third_party/tap-windows6/bin/amd64/OemVista.inf index 628a2158be..d92e2558b3 100644 --- a/third_party/tap-windows6/bin/amd64/OemVista.inf +++ b/third_party/tap-windows6/bin/amd64/OemVista.inf @@ -1,191 +1,191 @@ -; **************************************************************************** -; * Copyright (C) 2002-2014 OpenVPN Technologies, Inc. * -; * This program is free software; you can redistribute it and/or modify * -; * it under the terms of the GNU General Public License version 2 * -; * as published by the Free Software Foundation. * -; **************************************************************************** - -; SYNTAX CHECKER -; cd \WINDDK\3790\tools\chkinf -; chkinf c:\src\openvpn\tap-win32\i386\oemvista.inf -; OUTPUT -> file:///c:/WINDDK/3790/tools/chkinf/htm/c%23+src+openvpn+tap-win32+i386+__OemWin2k.htm - -; INSTALL/REMOVE DRIVER -; tapinstall install OemVista.inf tapoas -; tapinstall update OemVista.inf tapoas -; tapinstall remove tapoas - -;********************************************************* -; Note to Developers: -; -; If you are bundling the TAP-Windows driver with your app, -; you should try to rename it in such a way that it will -; not collide with other instances of TAP-Windows defined -; by other apps. Multiple versions of the TAP-Windows -; driver, each installed by different apps, can coexist -; on the same machine if you follow these guidelines. -; NOTE: these instructions assume you are editing the -; generated OemWin2k.inf file, not the source -; OemWin2k.inf.in file which is preprocessed by winconfig -; and uses macro definitions from settings.in. -; -; (1) Rename all tapXXXX instances in this file to -; something different (use at least 5 characters -; for this name!) -; (2) Change the "!define TAP" definition in openvpn.nsi -; to match what you changed tapXXXX to. -; (3) Change TARGETNAME in SOURCES to match what you -; changed tapXXXX to. -; (4) Change TAP_COMPONENT_ID in common.h to match what -; you changed tapXXXX to. -; (5) Change SZDEPENDENCIES in service.h to match what -; you changed tapXXXX to. -; (6) Change DeviceDescription and Provider strings. -; (7) Change PRODUCT_TAP_WIN_DEVICE_DESCRIPTION in constants.h to what you -; set DeviceDescription to. -; -;********************************************************* - -[Version] - Signature = "$Windows NT$" - CatalogFile = tap0901.cat - ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} - Provider = %Provider% - Class = Net - -; This version number should match the version -; number given in SOURCES. - DriverVer=04/21/2016,9.00.00.21 - -[Strings] - DeviceDescription = "TAP-Windows Adapter V9" - Provider = "TAP-Windows Provider V9" - -;---------------------------------------------------------------- -; Manufacturer + Product Section (Done) -;---------------------------------------------------------------- -[Manufacturer] - %Provider% = tap0901, NTamd64 - -[tap0901.NTamd64] - %DeviceDescription% = tap0901.ndi, root\tap0901 ; Root enumerated - %DeviceDescription% = tap0901.ndi, tap0901 ; Legacy - -;--------------------------------------------------------------- -; Driver Section (Done) -;--------------------------------------------------------------- - -;----------------- Characteristics ------------ -; NCF_PHYSICAL = 0x04 -; NCF_VIRTUAL = 0x01 -; NCF_SOFTWARE_ENUMERATED = 0x02 -; NCF_HIDDEN = 0x08 -; NCF_NO_SERVICE = 0x10 -; NCF_HAS_UI = 0x80 -;----------------- Characteristics ------------ - -[tap0901.ndi] - CopyFiles = tap0901.driver, tap0901.files - AddReg = tap0901.reg - AddReg = tap0901.params.reg - Characteristics = - *IfType = 0x6 ; IF_TYPE_ETHERNET_CSMACD - *MediaType = 0x0 ; NdisMedium802_3 - *PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 - -[tap0901.ndi.Services] - AddService = tap0901, 2, tap0901.service - -[tap0901.reg] - HKR, Ndi, Service, 0, "tap0901" - HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" - HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" - HKR, , Manufacturer, 0, "%Provider%" - HKR, , ProductName, 0, "%DeviceDescription%" - -[tap0901.params.reg] - HKR, Ndi\params\MTU, ParamDesc, 0, "MTU" - HKR, Ndi\params\MTU, Type, 0, "int" - HKR, Ndi\params\MTU, Default, 0, "1500" - HKR, Ndi\params\MTU, Optional, 0, "0" - HKR, Ndi\params\MTU, Min, 0, "100" - HKR, Ndi\params\MTU, Max, 0, "1500" - HKR, Ndi\params\MTU, Step, 0, "1" - HKR, Ndi\params\MediaStatus, ParamDesc, 0, "Media Status" - HKR, Ndi\params\MediaStatus, Type, 0, "enum" - HKR, Ndi\params\MediaStatus, Default, 0, "0" - HKR, Ndi\params\MediaStatus, Optional, 0, "0" - HKR, Ndi\params\MediaStatus\enum, "0", 0, "Application Controlled" - HKR, Ndi\params\MediaStatus\enum, "1", 0, "Always Connected" - HKR, Ndi\params\MAC, ParamDesc, 0, "MAC Address" - HKR, Ndi\params\MAC, Type, 0, "edit" - HKR, Ndi\params\MAC, Optional, 0, "1" - HKR, Ndi\params\AllowNonAdmin, ParamDesc, 0, "Non-Admin Access" - HKR, Ndi\params\AllowNonAdmin, Type, 0, "enum" - HKR, Ndi\params\AllowNonAdmin, Default, 0, "1" - HKR, Ndi\params\AllowNonAdmin, Optional, 0, "0" - HKR, Ndi\params\AllowNonAdmin\enum, "0", 0, "Not Allowed" - HKR, Ndi\params\AllowNonAdmin\enum, "1", 0, "Allowed" - -;---------------------------------------------------------------- -; Service Section -;---------------------------------------------------------------- - -;---------- Service Type ------------- -; SERVICE_KERNEL_DRIVER = 0x01 -; SERVICE_WIN32_OWN_PROCESS = 0x10 -;---------- Service Type ------------- - -;---------- Start Mode --------------- -; SERVICE_BOOT_START = 0x0 -; SERVICE_SYSTEM_START = 0x1 -; SERVICE_AUTO_START = 0x2 -; SERVICE_DEMAND_START = 0x3 -; SERVICE_DISABLED = 0x4 -;---------- Start Mode --------------- - -[tap0901.service] - DisplayName = %DeviceDescription% - ServiceType = 1 - StartType = 3 - ErrorControl = 1 - LoadOrderGroup = NDIS - ServiceBinary = %12%\tap0901.sys - -;----------------------------------------------------------------- -; File Installation -;----------------------------------------------------------------- - -;----------------- Copy Flags ------------ -; COPYFLG_NOSKIP = 0x02 -; COPYFLG_NOVERSIONCHECK = 0x04 -;----------------- Copy Flags ------------ - -; SourceDisksNames -; diskid = description[, [tagfile] [, , subdir]] -; 1 = "Intel Driver Disk 1",e100bex.sys,, - -[SourceDisksNames] - 1 = %DeviceDescription%, tap0901.sys - -; SourceDisksFiles -; filename_on_source = diskID[, [subdir][, size]] -; e100bex.sys = 1,, ; on distribution disk 1 - -[SourceDisksFiles] -tap0901.sys = 1 - -[DestinationDirs] - tap0901.files = 11 - tap0901.driver = 12 - -[tap0901.files] -; TapPanel.cpl,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK -; cipsrvr.exe,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK - -[tap0901.driver] - tap0901.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK - -;--------------------------------------------------------------- -; End -;--------------------------------------------------------------- +; **************************************************************************** +; * Copyright (C) 2002-2014 OpenVPN Technologies, Inc. * +; * This program is free software; you can redistribute it and/or modify * +; * it under the terms of the GNU General Public License version 2 * +; * as published by the Free Software Foundation. * +; **************************************************************************** + +; SYNTAX CHECKER +; cd \WINDDK\3790\tools\chkinf +; chkinf c:\src\openvpn\tap-win32\i386\oemvista.inf +; OUTPUT -> file:///c:/WINDDK/3790/tools/chkinf/htm/c%23+src+openvpn+tap-win32+i386+__OemWin2k.htm + +; INSTALL/REMOVE DRIVER +; tapinstall install OemVista.inf tapoas +; tapinstall update OemVista.inf tapoas +; tapinstall remove tapoas + +;********************************************************* +; Note to Developers: +; +; If you are bundling the TAP-Windows driver with your app, +; you should try to rename it in such a way that it will +; not collide with other instances of TAP-Windows defined +; by other apps. Multiple versions of the TAP-Windows +; driver, each installed by different apps, can coexist +; on the same machine if you follow these guidelines. +; NOTE: these instructions assume you are editing the +; generated OemWin2k.inf file, not the source +; OemWin2k.inf.in file which is preprocessed by winconfig +; and uses macro definitions from settings.in. +; +; (1) Rename all tapXXXX instances in this file to +; something different (use at least 5 characters +; for this name!) +; (2) Change the "!define TAP" definition in openvpn.nsi +; to match what you changed tapXXXX to. +; (3) Change TARGETNAME in SOURCES to match what you +; changed tapXXXX to. +; (4) Change TAP_COMPONENT_ID in common.h to match what +; you changed tapXXXX to. +; (5) Change SZDEPENDENCIES in service.h to match what +; you changed tapXXXX to. +; (6) Change DeviceDescription and Provider strings. +; (7) Change PRODUCT_TAP_WIN_DEVICE_DESCRIPTION in constants.h to what you +; set DeviceDescription to. +; +;********************************************************* + +[Version] + Signature = "$Windows NT$" + CatalogFile = tap0901.cat + ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} + Provider = %Provider% + Class = Net + +; This version number should match the version +; number given in SOURCES. + DriverVer=04/21/2016,9.00.00.21 + +[Strings] + DeviceDescription = "TAP-Windows Adapter V9" + Provider = "TAP-Windows Provider V9" + +;---------------------------------------------------------------- +; Manufacturer + Product Section (Done) +;---------------------------------------------------------------- +[Manufacturer] + %Provider% = tap0901, NTamd64 + +[tap0901.NTamd64] + %DeviceDescription% = tap0901.ndi, root\tap0901 ; Root enumerated + %DeviceDescription% = tap0901.ndi, tap0901 ; Legacy + +;--------------------------------------------------------------- +; Driver Section (Done) +;--------------------------------------------------------------- + +;----------------- Characteristics ------------ +; NCF_PHYSICAL = 0x04 +; NCF_VIRTUAL = 0x01 +; NCF_SOFTWARE_ENUMERATED = 0x02 +; NCF_HIDDEN = 0x08 +; NCF_NO_SERVICE = 0x10 +; NCF_HAS_UI = 0x80 +;----------------- Characteristics ------------ + +[tap0901.ndi] + CopyFiles = tap0901.driver, tap0901.files + AddReg = tap0901.reg + AddReg = tap0901.params.reg + Characteristics = + *IfType = 0x6 ; IF_TYPE_ETHERNET_CSMACD + *MediaType = 0x0 ; NdisMedium802_3 + *PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[tap0901.ndi.Services] + AddService = tap0901, 2, tap0901.service + +[tap0901.reg] + HKR, Ndi, Service, 0, "tap0901" + HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" + HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" + HKR, , Manufacturer, 0, "%Provider%" + HKR, , ProductName, 0, "%DeviceDescription%" + +[tap0901.params.reg] + HKR, Ndi\params\MTU, ParamDesc, 0, "MTU" + HKR, Ndi\params\MTU, Type, 0, "int" + HKR, Ndi\params\MTU, Default, 0, "1500" + HKR, Ndi\params\MTU, Optional, 0, "0" + HKR, Ndi\params\MTU, Min, 0, "100" + HKR, Ndi\params\MTU, Max, 0, "1500" + HKR, Ndi\params\MTU, Step, 0, "1" + HKR, Ndi\params\MediaStatus, ParamDesc, 0, "Media Status" + HKR, Ndi\params\MediaStatus, Type, 0, "enum" + HKR, Ndi\params\MediaStatus, Default, 0, "0" + HKR, Ndi\params\MediaStatus, Optional, 0, "0" + HKR, Ndi\params\MediaStatus\enum, "0", 0, "Application Controlled" + HKR, Ndi\params\MediaStatus\enum, "1", 0, "Always Connected" + HKR, Ndi\params\MAC, ParamDesc, 0, "MAC Address" + HKR, Ndi\params\MAC, Type, 0, "edit" + HKR, Ndi\params\MAC, Optional, 0, "1" + HKR, Ndi\params\AllowNonAdmin, ParamDesc, 0, "Non-Admin Access" + HKR, Ndi\params\AllowNonAdmin, Type, 0, "enum" + HKR, Ndi\params\AllowNonAdmin, Default, 0, "1" + HKR, Ndi\params\AllowNonAdmin, Optional, 0, "0" + HKR, Ndi\params\AllowNonAdmin\enum, "0", 0, "Not Allowed" + HKR, Ndi\params\AllowNonAdmin\enum, "1", 0, "Allowed" + +;---------------------------------------------------------------- +; Service Section +;---------------------------------------------------------------- + +;---------- Service Type ------------- +; SERVICE_KERNEL_DRIVER = 0x01 +; SERVICE_WIN32_OWN_PROCESS = 0x10 +;---------- Service Type ------------- + +;---------- Start Mode --------------- +; SERVICE_BOOT_START = 0x0 +; SERVICE_SYSTEM_START = 0x1 +; SERVICE_AUTO_START = 0x2 +; SERVICE_DEMAND_START = 0x3 +; SERVICE_DISABLED = 0x4 +;---------- Start Mode --------------- + +[tap0901.service] + DisplayName = %DeviceDescription% + ServiceType = 1 + StartType = 3 + ErrorControl = 1 + LoadOrderGroup = NDIS + ServiceBinary = %12%\tap0901.sys + +;----------------------------------------------------------------- +; File Installation +;----------------------------------------------------------------- + +;----------------- Copy Flags ------------ +; COPYFLG_NOSKIP = 0x02 +; COPYFLG_NOVERSIONCHECK = 0x04 +;----------------- Copy Flags ------------ + +; SourceDisksNames +; diskid = description[, [tagfile] [, , subdir]] +; 1 = "Intel Driver Disk 1",e100bex.sys,, + +[SourceDisksNames] + 1 = %DeviceDescription%, tap0901.sys + +; SourceDisksFiles +; filename_on_source = diskID[, [subdir][, size]] +; e100bex.sys = 1,, ; on distribution disk 1 + +[SourceDisksFiles] +tap0901.sys = 1 + +[DestinationDirs] + tap0901.files = 11 + tap0901.driver = 12 + +[tap0901.files] +; TapPanel.cpl,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK +; cipsrvr.exe,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK + +[tap0901.driver] + tap0901.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK + +;--------------------------------------------------------------- +; End +;--------------------------------------------------------------- diff --git a/third_party/tap-windows6/bin/i386/OemVista.inf b/third_party/tap-windows6/bin/i386/OemVista.inf index f71c2d3e94..6cd6791311 100644 --- a/third_party/tap-windows6/bin/i386/OemVista.inf +++ b/third_party/tap-windows6/bin/i386/OemVista.inf @@ -1,191 +1,191 @@ -; **************************************************************************** -; * Copyright (C) 2002-2014 OpenVPN Technologies, Inc. * -; * This program is free software; you can redistribute it and/or modify * -; * it under the terms of the GNU General Public License version 2 * -; * as published by the Free Software Foundation. * -; **************************************************************************** - -; SYNTAX CHECKER -; cd \WINDDK\3790\tools\chkinf -; chkinf c:\src\openvpn\tap-win32\i386\oemvista.inf -; OUTPUT -> file:///c:/WINDDK/3790/tools/chkinf/htm/c%23+src+openvpn+tap-win32+i386+__OemWin2k.htm - -; INSTALL/REMOVE DRIVER -; tapinstall install OemVista.inf tapoas -; tapinstall update OemVista.inf tapoas -; tapinstall remove tapoas - -;********************************************************* -; Note to Developers: -; -; If you are bundling the TAP-Windows driver with your app, -; you should try to rename it in such a way that it will -; not collide with other instances of TAP-Windows defined -; by other apps. Multiple versions of the TAP-Windows -; driver, each installed by different apps, can coexist -; on the same machine if you follow these guidelines. -; NOTE: these instructions assume you are editing the -; generated OemWin2k.inf file, not the source -; OemWin2k.inf.in file which is preprocessed by winconfig -; and uses macro definitions from settings.in. -; -; (1) Rename all tapXXXX instances in this file to -; something different (use at least 5 characters -; for this name!) -; (2) Change the "!define TAP" definition in openvpn.nsi -; to match what you changed tapXXXX to. -; (3) Change TARGETNAME in SOURCES to match what you -; changed tapXXXX to. -; (4) Change TAP_COMPONENT_ID in common.h to match what -; you changed tapXXXX to. -; (5) Change SZDEPENDENCIES in service.h to match what -; you changed tapXXXX to. -; (6) Change DeviceDescription and Provider strings. -; (7) Change PRODUCT_TAP_WIN_DEVICE_DESCRIPTION in constants.h to what you -; set DeviceDescription to. -; -;********************************************************* - -[Version] - Signature = "$Windows NT$" - CatalogFile = tap0901.cat - ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} - Provider = %Provider% - Class = Net - -; This version number should match the version -; number given in SOURCES. - DriverVer=04/21/2016,9.00.00.21 - -[Strings] - DeviceDescription = "TAP-Windows Adapter V9" - Provider = "TAP-Windows Provider V9" - -;---------------------------------------------------------------- -; Manufacturer + Product Section (Done) -;---------------------------------------------------------------- -[Manufacturer] - %Provider% = tap0901 - -[tap0901] - %DeviceDescription% = tap0901.ndi, root\tap0901 ; Root enumerated - %DeviceDescription% = tap0901.ndi, tap0901 ; Legacy - -;--------------------------------------------------------------- -; Driver Section (Done) -;--------------------------------------------------------------- - -;----------------- Characteristics ------------ -; NCF_PHYSICAL = 0x04 -; NCF_VIRTUAL = 0x01 -; NCF_SOFTWARE_ENUMERATED = 0x02 -; NCF_HIDDEN = 0x08 -; NCF_NO_SERVICE = 0x10 -; NCF_HAS_UI = 0x80 -;----------------- Characteristics ------------ - -[tap0901.ndi] - CopyFiles = tap0901.driver, tap0901.files - AddReg = tap0901.reg - AddReg = tap0901.params.reg - Characteristics = - *IfType = 0x6 ; IF_TYPE_ETHERNET_CSMACD - *MediaType = 0x0 ; NdisMedium802_3 - *PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 - -[tap0901.ndi.Services] - AddService = tap0901, 2, tap0901.service - -[tap0901.reg] - HKR, Ndi, Service, 0, "tap0901" - HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" - HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" - HKR, , Manufacturer, 0, "%Provider%" - HKR, , ProductName, 0, "%DeviceDescription%" - -[tap0901.params.reg] - HKR, Ndi\params\MTU, ParamDesc, 0, "MTU" - HKR, Ndi\params\MTU, Type, 0, "int" - HKR, Ndi\params\MTU, Default, 0, "1500" - HKR, Ndi\params\MTU, Optional, 0, "0" - HKR, Ndi\params\MTU, Min, 0, "100" - HKR, Ndi\params\MTU, Max, 0, "1500" - HKR, Ndi\params\MTU, Step, 0, "1" - HKR, Ndi\params\MediaStatus, ParamDesc, 0, "Media Status" - HKR, Ndi\params\MediaStatus, Type, 0, "enum" - HKR, Ndi\params\MediaStatus, Default, 0, "0" - HKR, Ndi\params\MediaStatus, Optional, 0, "0" - HKR, Ndi\params\MediaStatus\enum, "0", 0, "Application Controlled" - HKR, Ndi\params\MediaStatus\enum, "1", 0, "Always Connected" - HKR, Ndi\params\MAC, ParamDesc, 0, "MAC Address" - HKR, Ndi\params\MAC, Type, 0, "edit" - HKR, Ndi\params\MAC, Optional, 0, "1" - HKR, Ndi\params\AllowNonAdmin, ParamDesc, 0, "Non-Admin Access" - HKR, Ndi\params\AllowNonAdmin, Type, 0, "enum" - HKR, Ndi\params\AllowNonAdmin, Default, 0, "1" - HKR, Ndi\params\AllowNonAdmin, Optional, 0, "0" - HKR, Ndi\params\AllowNonAdmin\enum, "0", 0, "Not Allowed" - HKR, Ndi\params\AllowNonAdmin\enum, "1", 0, "Allowed" - -;---------------------------------------------------------------- -; Service Section -;---------------------------------------------------------------- - -;---------- Service Type ------------- -; SERVICE_KERNEL_DRIVER = 0x01 -; SERVICE_WIN32_OWN_PROCESS = 0x10 -;---------- Service Type ------------- - -;---------- Start Mode --------------- -; SERVICE_BOOT_START = 0x0 -; SERVICE_SYSTEM_START = 0x1 -; SERVICE_AUTO_START = 0x2 -; SERVICE_DEMAND_START = 0x3 -; SERVICE_DISABLED = 0x4 -;---------- Start Mode --------------- - -[tap0901.service] - DisplayName = %DeviceDescription% - ServiceType = 1 - StartType = 3 - ErrorControl = 1 - LoadOrderGroup = NDIS - ServiceBinary = %12%\tap0901.sys - -;----------------------------------------------------------------- -; File Installation -;----------------------------------------------------------------- - -;----------------- Copy Flags ------------ -; COPYFLG_NOSKIP = 0x02 -; COPYFLG_NOVERSIONCHECK = 0x04 -;----------------- Copy Flags ------------ - -; SourceDisksNames -; diskid = description[, [tagfile] [, , subdir]] -; 1 = "Intel Driver Disk 1",e100bex.sys,, - -[SourceDisksNames] - 1 = %DeviceDescription%, tap0901.sys - -; SourceDisksFiles -; filename_on_source = diskID[, [subdir][, size]] -; e100bex.sys = 1,, ; on distribution disk 1 - -[SourceDisksFiles] -tap0901.sys = 1 - -[DestinationDirs] - tap0901.files = 11 - tap0901.driver = 12 - -[tap0901.files] -; TapPanel.cpl,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK -; cipsrvr.exe,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK - -[tap0901.driver] - tap0901.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK - -;--------------------------------------------------------------- -; End -;--------------------------------------------------------------- +; **************************************************************************** +; * Copyright (C) 2002-2014 OpenVPN Technologies, Inc. * +; * This program is free software; you can redistribute it and/or modify * +; * it under the terms of the GNU General Public License version 2 * +; * as published by the Free Software Foundation. * +; **************************************************************************** + +; SYNTAX CHECKER +; cd \WINDDK\3790\tools\chkinf +; chkinf c:\src\openvpn\tap-win32\i386\oemvista.inf +; OUTPUT -> file:///c:/WINDDK/3790/tools/chkinf/htm/c%23+src+openvpn+tap-win32+i386+__OemWin2k.htm + +; INSTALL/REMOVE DRIVER +; tapinstall install OemVista.inf tapoas +; tapinstall update OemVista.inf tapoas +; tapinstall remove tapoas + +;********************************************************* +; Note to Developers: +; +; If you are bundling the TAP-Windows driver with your app, +; you should try to rename it in such a way that it will +; not collide with other instances of TAP-Windows defined +; by other apps. Multiple versions of the TAP-Windows +; driver, each installed by different apps, can coexist +; on the same machine if you follow these guidelines. +; NOTE: these instructions assume you are editing the +; generated OemWin2k.inf file, not the source +; OemWin2k.inf.in file which is preprocessed by winconfig +; and uses macro definitions from settings.in. +; +; (1) Rename all tapXXXX instances in this file to +; something different (use at least 5 characters +; for this name!) +; (2) Change the "!define TAP" definition in openvpn.nsi +; to match what you changed tapXXXX to. +; (3) Change TARGETNAME in SOURCES to match what you +; changed tapXXXX to. +; (4) Change TAP_COMPONENT_ID in common.h to match what +; you changed tapXXXX to. +; (5) Change SZDEPENDENCIES in service.h to match what +; you changed tapXXXX to. +; (6) Change DeviceDescription and Provider strings. +; (7) Change PRODUCT_TAP_WIN_DEVICE_DESCRIPTION in constants.h to what you +; set DeviceDescription to. +; +;********************************************************* + +[Version] + Signature = "$Windows NT$" + CatalogFile = tap0901.cat + ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} + Provider = %Provider% + Class = Net + +; This version number should match the version +; number given in SOURCES. + DriverVer=04/21/2016,9.00.00.21 + +[Strings] + DeviceDescription = "TAP-Windows Adapter V9" + Provider = "TAP-Windows Provider V9" + +;---------------------------------------------------------------- +; Manufacturer + Product Section (Done) +;---------------------------------------------------------------- +[Manufacturer] + %Provider% = tap0901 + +[tap0901] + %DeviceDescription% = tap0901.ndi, root\tap0901 ; Root enumerated + %DeviceDescription% = tap0901.ndi, tap0901 ; Legacy + +;--------------------------------------------------------------- +; Driver Section (Done) +;--------------------------------------------------------------- + +;----------------- Characteristics ------------ +; NCF_PHYSICAL = 0x04 +; NCF_VIRTUAL = 0x01 +; NCF_SOFTWARE_ENUMERATED = 0x02 +; NCF_HIDDEN = 0x08 +; NCF_NO_SERVICE = 0x10 +; NCF_HAS_UI = 0x80 +;----------------- Characteristics ------------ + +[tap0901.ndi] + CopyFiles = tap0901.driver, tap0901.files + AddReg = tap0901.reg + AddReg = tap0901.params.reg + Characteristics = + *IfType = 0x6 ; IF_TYPE_ETHERNET_CSMACD + *MediaType = 0x0 ; NdisMedium802_3 + *PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[tap0901.ndi.Services] + AddService = tap0901, 2, tap0901.service + +[tap0901.reg] + HKR, Ndi, Service, 0, "tap0901" + HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" + HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" + HKR, , Manufacturer, 0, "%Provider%" + HKR, , ProductName, 0, "%DeviceDescription%" + +[tap0901.params.reg] + HKR, Ndi\params\MTU, ParamDesc, 0, "MTU" + HKR, Ndi\params\MTU, Type, 0, "int" + HKR, Ndi\params\MTU, Default, 0, "1500" + HKR, Ndi\params\MTU, Optional, 0, "0" + HKR, Ndi\params\MTU, Min, 0, "100" + HKR, Ndi\params\MTU, Max, 0, "1500" + HKR, Ndi\params\MTU, Step, 0, "1" + HKR, Ndi\params\MediaStatus, ParamDesc, 0, "Media Status" + HKR, Ndi\params\MediaStatus, Type, 0, "enum" + HKR, Ndi\params\MediaStatus, Default, 0, "0" + HKR, Ndi\params\MediaStatus, Optional, 0, "0" + HKR, Ndi\params\MediaStatus\enum, "0", 0, "Application Controlled" + HKR, Ndi\params\MediaStatus\enum, "1", 0, "Always Connected" + HKR, Ndi\params\MAC, ParamDesc, 0, "MAC Address" + HKR, Ndi\params\MAC, Type, 0, "edit" + HKR, Ndi\params\MAC, Optional, 0, "1" + HKR, Ndi\params\AllowNonAdmin, ParamDesc, 0, "Non-Admin Access" + HKR, Ndi\params\AllowNonAdmin, Type, 0, "enum" + HKR, Ndi\params\AllowNonAdmin, Default, 0, "1" + HKR, Ndi\params\AllowNonAdmin, Optional, 0, "0" + HKR, Ndi\params\AllowNonAdmin\enum, "0", 0, "Not Allowed" + HKR, Ndi\params\AllowNonAdmin\enum, "1", 0, "Allowed" + +;---------------------------------------------------------------- +; Service Section +;---------------------------------------------------------------- + +;---------- Service Type ------------- +; SERVICE_KERNEL_DRIVER = 0x01 +; SERVICE_WIN32_OWN_PROCESS = 0x10 +;---------- Service Type ------------- + +;---------- Start Mode --------------- +; SERVICE_BOOT_START = 0x0 +; SERVICE_SYSTEM_START = 0x1 +; SERVICE_AUTO_START = 0x2 +; SERVICE_DEMAND_START = 0x3 +; SERVICE_DISABLED = 0x4 +;---------- Start Mode --------------- + +[tap0901.service] + DisplayName = %DeviceDescription% + ServiceType = 1 + StartType = 3 + ErrorControl = 1 + LoadOrderGroup = NDIS + ServiceBinary = %12%\tap0901.sys + +;----------------------------------------------------------------- +; File Installation +;----------------------------------------------------------------- + +;----------------- Copy Flags ------------ +; COPYFLG_NOSKIP = 0x02 +; COPYFLG_NOVERSIONCHECK = 0x04 +;----------------- Copy Flags ------------ + +; SourceDisksNames +; diskid = description[, [tagfile] [, , subdir]] +; 1 = "Intel Driver Disk 1",e100bex.sys,, + +[SourceDisksNames] + 1 = %DeviceDescription%, tap0901.sys + +; SourceDisksFiles +; filename_on_source = diskID[, [subdir][, size]] +; e100bex.sys = 1,, ; on distribution disk 1 + +[SourceDisksFiles] +tap0901.sys = 1 + +[DestinationDirs] + tap0901.files = 11 + tap0901.driver = 12 + +[tap0901.files] +; TapPanel.cpl,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK +; cipsrvr.exe,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK + +[tap0901.driver] + tap0901.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK + +;--------------------------------------------------------------- +; End +;--------------------------------------------------------------- diff --git a/third_party/tap-windows6/bin/include/tap-windows.h b/third_party/tap-windows6/bin/include/tap-windows.h index 25bdd82b41..d546a5b1cb 100644 --- a/third_party/tap-windows6/bin/include/tap-windows.h +++ b/third_party/tap-windows6/bin/include/tap-windows.h @@ -1,74 +1,74 @@ -/* - * TAP-Windows -- A kernel driver to provide virtual tap - * device functionality on Windows. - * - * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. - * - * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., - * and is released under the GPL version 2 (see below). - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program (see the file COPYING included with this - * distribution); if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifndef __TAP_WIN_H -#define __TAP_WIN_H - -/* - * ============= - * TAP IOCTLs - * ============= - */ - -#define TAP_WIN_CONTROL_CODE(request,method) \ - CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) - -/* Present in 8.1 */ - -#define TAP_WIN_IOCTL_GET_MAC TAP_WIN_CONTROL_CODE (1, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_GET_VERSION TAP_WIN_CONTROL_CODE (2, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_GET_MTU TAP_WIN_CONTROL_CODE (3, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_GET_INFO TAP_WIN_CONTROL_CODE (4, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT TAP_WIN_CONTROL_CODE (5, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_SET_MEDIA_STATUS TAP_WIN_CONTROL_CODE (6, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_CONFIG_DHCP_MASQ TAP_WIN_CONTROL_CODE (7, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_GET_LOG_LINE TAP_WIN_CONTROL_CODE (8, METHOD_BUFFERED) -#define TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT TAP_WIN_CONTROL_CODE (9, METHOD_BUFFERED) - -/* Added in 8.2 */ - -/* obsoletes TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT */ -#define TAP_WIN_IOCTL_CONFIG_TUN TAP_WIN_CONTROL_CODE (10, METHOD_BUFFERED) - -/* - * ================= - * Registry keys - * ================= - */ - -#define ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}" - -#define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" - -/* - * ====================== - * Filesystem prefixes - * ====================== - */ - -#define USERMODEDEVICEDIR "\\\\.\\Global\\" -#define SYSDEVICEDIR "\\Device\\" -#define USERDEVICEDIR "\\DosDevices\\Global\\" -#define TAP_WIN_SUFFIX ".tap" - -#endif // __TAP_WIN_H +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __TAP_WIN_H +#define __TAP_WIN_H + +/* + * ============= + * TAP IOCTLs + * ============= + */ + +#define TAP_WIN_CONTROL_CODE(request,method) \ + CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) + +/* Present in 8.1 */ + +#define TAP_WIN_IOCTL_GET_MAC TAP_WIN_CONTROL_CODE (1, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_VERSION TAP_WIN_CONTROL_CODE (2, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_MTU TAP_WIN_CONTROL_CODE (3, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_INFO TAP_WIN_CONTROL_CODE (4, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT TAP_WIN_CONTROL_CODE (5, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_SET_MEDIA_STATUS TAP_WIN_CONTROL_CODE (6, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_CONFIG_DHCP_MASQ TAP_WIN_CONTROL_CODE (7, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_LOG_LINE TAP_WIN_CONTROL_CODE (8, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT TAP_WIN_CONTROL_CODE (9, METHOD_BUFFERED) + +/* Added in 8.2 */ + +/* obsoletes TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT */ +#define TAP_WIN_IOCTL_CONFIG_TUN TAP_WIN_CONTROL_CODE (10, METHOD_BUFFERED) + +/* + * ================= + * Registry keys + * ================= + */ + +#define ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +#define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +/* + * ====================== + * Filesystem prefixes + * ====================== + */ + +#define USERMODEDEVICEDIR "\\\\.\\Global\\" +#define SYSDEVICEDIR "\\Device\\" +#define USERDEVICEDIR "\\DosDevices\\Global\\" +#define TAP_WIN_SUFFIX ".tap" + +#endif // __TAP_WIN_H diff --git a/tools/OutlineService/OutlineService.sln b/tools/OutlineService/OutlineService.sln index 9a40a06fab..0183a70546 100755 --- a/tools/OutlineService/OutlineService.sln +++ b/tools/OutlineService/OutlineService.sln @@ -1,31 +1,31 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2018 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OutlineService", "OutlineService\OutlineService.csproj", "{F39A4491-9868-4A71-9BE1-980FADE10AC9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Debug|Any CPU.ActiveCfg = Release|x86 - {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Debug|Any CPU.Build.0 = Release|x86 - {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Debug|x86.ActiveCfg = Release|x86 - {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Debug|x86.Build.0 = Release|x86 - {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Release|Any CPU.Build.0 = Release|Any CPU - {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Release|x86.ActiveCfg = Release|x86 - {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {55663288-4604-4D50-8D4A-7222B9A2B6BF} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2018 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OutlineService", "OutlineService\OutlineService.csproj", "{F39A4491-9868-4A71-9BE1-980FADE10AC9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Debug|Any CPU.ActiveCfg = Release|x86 + {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Debug|Any CPU.Build.0 = Release|x86 + {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Debug|x86.ActiveCfg = Release|x86 + {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Debug|x86.Build.0 = Release|x86 + {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Release|Any CPU.Build.0 = Release|Any CPU + {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Release|x86.ActiveCfg = Release|x86 + {F39A4491-9868-4A71-9BE1-980FADE10AC9}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {55663288-4604-4D50-8D4A-7222B9A2B6BF} + EndGlobalSection +EndGlobal diff --git a/tools/OutlineService/OutlineService/App.config b/tools/OutlineService/OutlineService/App.config index 321649d338..5534e28762 100755 --- a/tools/OutlineService/OutlineService/App.config +++ b/tools/OutlineService/OutlineService/App.config @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/tools/OutlineService/OutlineService/OutlineService.Designer.cs b/tools/OutlineService/OutlineService/OutlineService.Designer.cs index 1be3f894c8..2c933d862d 100755 --- a/tools/OutlineService/OutlineService/OutlineService.Designer.cs +++ b/tools/OutlineService/OutlineService/OutlineService.Designer.cs @@ -1,54 +1,54 @@ -// Copyright 2018 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace OutlineService -{ - partial class OutlineService - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - // - // OutlineService - // - this.ServiceName = "OutlineService"; - - } - - #endregion - } -} +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OutlineService +{ + partial class OutlineService + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + // + // OutlineService + // + this.ServiceName = "OutlineService"; + + } + + #endregion + } +} diff --git a/tools/OutlineService/OutlineService/OutlineService.cs b/tools/OutlineService/OutlineService/OutlineService.cs index 3f2ad5f571..71da846c61 100755 --- a/tools/OutlineService/OutlineService/OutlineService.cs +++ b/tools/OutlineService/OutlineService/OutlineService.cs @@ -1,1093 +1,1093 @@ -// Copyright 2018 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Pipes; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; -using System.Security.AccessControl; -using System.Security.Principal; -using System.ServiceProcess; -using System.Text; -using Newtonsoft.Json; - -/* - * Windows Service, part of the Outline Windows client, to configure routing. - * Modifying the system routes requires admin permissions, so this service must be installed - * and started as admin. - * - * The service listens on a named pipe and supports the following JSON API: - * - * Requests - * - * configureRouting: Modifies the system's routing table to route all traffic through the TAP device - * except that destined for proxyIp. Disables IPv6 traffic. - * { action: "configureRouting", parameters: {"proxyIp": , "isAutoConnect": "false" }} - * - * resetRouting: Restores the system's default routing. - * { action: "resetRouting"} - * - * Response - * - * { statusCode: , action: errorMessage?: } - * - * The service will send connection status updates if the pipe connection is kept - * open by the client. Such responses have the form: - * - * { statusCode: , action: "statusChanged", connectionStatus: } - * - * View logs with this PowerShell query: - * get-eventlog -logname Application -source OutlineService -newest 20 | format-table -property timegenerated,entrytype,message -autosize - */ -namespace OutlineService -{ - public partial class OutlineService : ServiceBase - { - private const string EVENT_LOG_SOURCE = "OutlineService"; - private const string EVENT_LOG_NAME = "Application"; - // Must be kept in sync with the Electron code. - private const string PIPE_NAME = "OutlineServicePipe"; - private const string TAP_DEVICE_NAME = "outline-tap0"; - private const string TAP_DEVICE_IP = "10.0.85.1"; - - private const string ACTION_CONFIGURE_ROUTING = "configureRouting"; - private const string ACTION_RESET_ROUTING = "resetRouting"; - private const string ACTION_STATUS_CHANGED = "statusChanged"; - private const string PARAM_PROXY_IP = "proxyIp"; - private const string PARAM_AUTO_CONNECT = "isAutoConnect"; - - private static string[] IPV4_SUBNETS = { "0.0.0.0/1", "128.0.0.0/1" }; - private static string[] IPV6_SUBNETS = { "fc00::/7", "2000::/4", "3000::/4" }; - private static string[] IPV4_RESERVED_SUBNETS = { - "0.0.0.0/8", - "10.0.0.0/8", - "100.64.0.0/10", - "169.254.0.0/16", - "172.16.0.0/12", - "192.0.0.0/24", - "192.0.2.0/24", - "192.31.196.0/24", - "192.52.193.0/24", - "192.88.99.0/24", - "192.168.0.0/16", - "192.175.48.0/24", - "198.18.0.0/15", - "198.51.100.0/24", - "203.0.113.0/24", - "240.0.0.0/4" - }; - private const string CMD_NETSH = "netsh"; - private const string CMD_ROUTE = "route"; - - private const uint BUFFER_SIZE_BYTES = 1024; - - private EventLog eventLog; - private NamedPipeServerStream pipe; - private string proxyIp; - private string gatewayIp; - private int gatewayInterfaceIndex; - - // Time, in ms, to wait until considering smartdnsblock.exe to have successfully launched. - private const int SMART_DNS_BLOCK_TIMEOUT_MS = 1000; - - // https://docs.microsoft.com/en-us/windows/desktop/api/ipmib/ns-ipmib-_mib_ipforwardrow - [StructLayout(LayoutKind.Sequential)] - internal class MIB_IPFORWARDROW - { - internal uint dwForwardDest; - internal uint dwForwardMask; - internal uint dwForwardPolicy; - internal uint dwForwardNextHop; - internal int dwForwardIfIndex; - internal uint dwForwardType; - internal uint dwForwardProto; - internal uint dwForwardAge; - internal uint dwForwardNextHopAS; - internal uint dwForwardMetric1; - internal uint dwForwardMetric2; - internal uint dwForwardMetric3; - internal uint dwForwardMetric4; - internal uint dwForwardMetric5; - } - - // https://docs.microsoft.com/en-us/windows/desktop/api/ipmib/ns-ipmib-_mib_ipforwardtable - // - // NOTE: Because of the variable-length array, Marshal.PtrToStructure - // will *not* populate the table field. Additionally, we have seen - // crashes following suspend/resume while trying to marshal this - // structure. See #GetSystemIpv4Gateway for more on this, as well - // as for how to traverse the table. - [StructLayout(LayoutKind.Sequential)] - internal class MIB_IPFORWARDTABLE - { - internal uint dwNumEntries; - internal MIB_IPFORWARDROW[] table; - }; - - // https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getipforwardtable - [DllImport("iphlpapi", CharSet = CharSet.Auto)] - private extern static int GetIpForwardTable(IntPtr pIpForwardTable, ref int pdwSize, bool bOrder); - - // https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--0-499- - private static int ERROR_INSUFFICIENT_BUFFER = 122; - - // Do as little as possible here because any error thrown will cause "net start" to fail - // without anything being added to the application log. - public OutlineService() - { - InitializeComponent(); - - eventLog = new EventLog(); - if (!EventLog.SourceExists(EVENT_LOG_SOURCE)) - { - EventLog.CreateEventSource(EVENT_LOG_SOURCE, EVENT_LOG_NAME); - } - eventLog.Source = EVENT_LOG_SOURCE; - eventLog.Log = EVENT_LOG_NAME; - } - - protected override void OnStart(string[] args) - { - eventLog.WriteEntry("OutlineService starting"); - NetworkChange.NetworkAddressChanged += - new NetworkAddressChangedEventHandler(NetworkAddressChanged); - CreatePipe(); - } - - protected override void OnStop() - { - eventLog.WriteEntry("OutlineService stopping"); - DestroyPipe(); - NetworkChange.NetworkAddressChanged -= NetworkAddressChanged; - } - - private void CreatePipe() - { - var pipeSecurity = new PipeSecurity(); - pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier( - WellKnownSidType.CreatorOwnerSid, null), - PipeAccessRights.FullControl, AccessControlType.Allow)); - pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier( - WellKnownSidType.AuthenticatedUserSid, null), - PipeAccessRights.ReadWrite, AccessControlType.Allow)); - - pipe = new NamedPipeServerStream(PIPE_NAME, PipeDirection.InOut, -1, PipeTransmissionMode.Message, - PipeOptions.Asynchronous, (int)BUFFER_SIZE_BYTES, (int)BUFFER_SIZE_BYTES, pipeSecurity); - pipe.BeginWaitForConnection(HandleConnection, null); - } - - private void DestroyPipe() - { - if (pipe == null) - { - return; - } - try - { - if (pipe.IsConnected) - { - pipe.Disconnect(); - } - pipe.Close(); - pipe = null; - } - catch (Exception e) - { - eventLog.WriteEntry($"Got an exception while destroying the pipe: {e.ToString()}", - EventLogEntryType.Warning); - } - } - - private void HandleConnection(IAsyncResult result) - { - eventLog.WriteEntry("Got incoming connection"); - - // Save the network config before we do anything. If the request fails - // it will be sent to the client for inclusion in Sentry reports. - var beforeNetworkInfo = GetNetworkInfo(); - - try - { - pipe.EndWaitForConnection(result); - // Keep the pipe connected to send connection status updates. - while (pipe.IsConnected) - { - ServiceResponse response = new ServiceResponse(); - var request = ReadRequest(); - if (request == null) - { - response.statusCode = (int)ErrorCode.GenericFailure; - } - else - { - response.action = request.action; - try - { - HandleRequest(request); - } - catch (Exception e) - { - response.statusCode = (int)ErrorCode.GenericFailure; - response.errorMessage = $"{e.Message} (network config: {beforeNetworkInfo})"; - eventLog.WriteEntry($"request failed: {e.Message}", EventLogEntryType.Error); - } - } - WriteResponse(response); - } - } - catch (Exception e) - { - eventLog.WriteEntry($"Failed to handle connection: {e.ToString()}", EventLogEntryType.Error); - } - finally - { - // Pipe streams are one-to-one connections. Recreate the pipe to handle subsequent requests. - DestroyPipe(); - CreatePipe(); - } - } - - private ServiceRequest ReadRequest() - { - var stringBuilder = new StringBuilder(); - var buffer = new byte[BUFFER_SIZE_BYTES]; - var memoryStream = new MemoryStream(); - do - { - var readBytes = pipe.Read(buffer, 0, buffer.Length); - memoryStream.Write(buffer, 0, readBytes); - } while (!pipe.IsMessageComplete); - var msg = Encoding.UTF8.GetString(buffer); - if (String.IsNullOrWhiteSpace(msg)) - { - eventLog.WriteEntry("Failed to read request", EventLogEntryType.Error); - return null; - } - eventLog.WriteEntry($"incoming message: {msg}"); - return ParseRequest(msg); - } - - private ServiceRequest ParseRequest(string jsonRequest) - { - try - { - return JsonConvert.DeserializeObject(jsonRequest); - } - catch (Exception e) - { - eventLog.WriteEntry($"Failed to parse request: {e.ToString()}"); - } - return null; - } - - private void WriteResponse(ServiceResponse response) - { - var jsonResponse = SerializeResponse(response); - if (jsonResponse == null) - { - eventLog.WriteEntry("Failed to serialize response.", EventLogEntryType.Error); - return; - } - eventLog.WriteEntry($"outgoing message: {jsonResponse}"); - var jsonResponseBytes = Encoding.UTF8.GetBytes(jsonResponse); - pipe.Write(jsonResponseBytes, 0, jsonResponseBytes.Length); - pipe.Flush(); - pipe.WaitForPipeDrain(); - } - - private string SerializeResponse(ServiceResponse response) - { - try - { - return JsonConvert.SerializeObject(response); - } - catch (Exception e) - { - eventLog.WriteEntry($"Failed to serialize response: {e.ToString()}"); - } - return null; - } - - private void HandleRequest(ServiceRequest request) - { - switch (request.action) - { - case ACTION_CONFIGURE_ROUTING: - ConfigureRouting(request.parameters[PARAM_PROXY_IP], Boolean.Parse(request.parameters[PARAM_AUTO_CONNECT])); - break; - case ACTION_RESET_ROUTING: - ResetRouting(proxyIp, gatewayInterfaceIndex); - break; - default: - eventLog.WriteEntry($"Received invalid request: {request.action}", EventLogEntryType.Error); - break; - } - } - - // Routes all traffic *except that destined for the proxy server* - // through the TAP device, creating the illusion of a system-wide VPN. - // - // The two key steps are: - // - Route all IPv4 traffic through the TAP device. - // - Find a gateway and route traffic *to the proxy server only* (a /32 - // mask) through it. - // - // Finding a gateway, in particular, is complex: for more on how it - // works, see #GetSystemIpv4Gateway. - // - // On top of this foundation, we take some steps to help prevent - // "leaking" traffic: - // - IPv6 traffic is "blocked", as Outline does not currently support - // servers on IPv6 addresses. - // - "Smart Multi-Homed Name Resolution" is disabled, as it can cause - // the system's "regular" - and potentially filtered - DNS servers to - // be used (particularly on Windows 10). - // - // Preventing leaks significantly complicates things. In particular, *if - // autostart is true and a gateway cannot be found then the IPv4 - // redirect and IPv6 block remain in place and no exception is thrown*. - // When a gateway re-appears following a network change (see - // #NetworkAddressChanged), we will reconnect. - // - // Lastly, a set of routes is added through the gateway *to non-routable - // ("LAN") addresses*. This allows common hardware such as Chromecast to - // function while Outline is active. - // - // Note: - // - Currently, this function does not "clean up" in the event of - // failure. Instead, we rely on the client to call ResetRouting - // following a connection failure. - // - There's limited protection against "nested connections", i.e. - // connecting to Outline while another VPN, e.g. OpenVPN, is already - // active. There's no simple API we can use to tell whether a VPN is - // already active and *given the difficulty in identifying this - // reliably* (multiple active network interfaces, for example, are - // very common, e.g. it happens - briefly - every time a user - // switches between a wired and wireless network) we err on the side - // of working. Since the user presumably knows whether another VPN is - // already active and *we make no persistent changes to the routing - // table* this seems reasonable. - // - // TODO: The client needs to handle certain autoconnect failures better, - // e.g. if IPv4 redirect fails then the client is not really in - // the reconnecting state; the system is leaking traffic. - public void ConfigureRouting(string proxyIp, bool isAutoConnect) - { - try - { - StartSmartDnsBlock(); - eventLog.WriteEntry($"started smartdnsblock"); - } - catch (Exception e) - { - throw new Exception($"could not start smartdnsblock: {e.Message}"); - } - - try - { - GetSystemIpv4Gateway(proxyIp); - - eventLog.WriteEntry($"connecting via gateway at {gatewayIp} on interface {gatewayInterfaceIndex}"); - - // Set the proxy escape route first to prevent a routing loop when capturing all IPv4 traffic. - try - { - AddOrUpdateProxyRoute(proxyIp, gatewayIp, gatewayInterfaceIndex); - eventLog.WriteEntry($"created route to proxy"); - } - catch (Exception e) - { - throw new Exception($"could not create route to proxy: {e.Message}"); - } - this.proxyIp = proxyIp; - - try - { - AddOrUpdateReservedSubnetBypass(gatewayIp, gatewayInterfaceIndex); - eventLog.WriteEntry($"created LAN bypass routes"); - } - catch (Exception e) - { - throw new Exception($"could not create LAN bypass routes: {e.Message}"); - } - } - catch (Exception e) when (isAutoConnect) - { - eventLog.WriteEntry($"could not reconnect during auto-connect: {e.Message}", EventLogEntryType.Warning); - } - - try - { - StopRoutingIpv6(); - eventLog.WriteEntry($"blocked IPv6 traffic"); - } - catch (Exception e) - { - throw new Exception($"could not block IPv6 traffic: {e.Message}"); - } - - try - { - AddIpv4TapRedirect(); - eventLog.WriteEntry($"redirected IPv4 traffic"); - } - catch (Exception e) - { - throw new Exception($"could not redirect IPv4 traffic: {e.Message}"); - } - } - - // Undoes and removes as many as possible of the routes and other - // changes to the system previously made by #ConfigureRouting. - // - // As per #ConfigureRouting, this function is largely idempodent: - // calling it multiple times in succession should be safe and result in - // the same system configuration. - // - // Notes: - // - *This function does not set any error code when any step fails*. - // It probably should, as the system may in some rare cases still be - // connected (or, worse but less likely, bricked). - // - If the service does not know the IP address of the proxy server - // then it *cannot remove that route*. This can happen if the service - // is restarted while Outline is connected *or* (more likely) if this - // function is called while Outline is not connected. This route is - // mostly harmless because it only affects traffic to the proxy and - // if/when the user reconnects to it the route will be updated. - public void ResetRouting(string proxyIp, int gatewayInterfaceIndex) - { - try - { - RemoveIpv4TapRedirect(); - eventLog.WriteEntry($"removed IPv4 redirect"); - } - catch (Exception e) - { - eventLog.WriteEntry($"failed to remove IPv4 redirect: {e.Message}", EventLogEntryType.Error); - } - - try - { - // This is only necessary when disconecting without network connectivity. - StartRoutingIpv4(); - } - catch (Exception) {} - - try - { - StartRoutingIpv6(); - eventLog.WriteEntry($"unblocked IPv6"); - } - catch (Exception e) - { - eventLog.WriteEntry($"failed to unblock IPv6: {e.Message}", EventLogEntryType.Error); - } - - if (proxyIp != null) - { - try - { - DeleteProxyRoute(proxyIp); - eventLog.WriteEntry($"deleted route to proxy"); - } - catch (Exception e) - { - eventLog.WriteEntry($"failed to delete route to proxy: {e.Message}", EventLogEntryType.Error); - } - this.proxyIp = null; - } - - try - { - RemoveReservedSubnetBypass(); - eventLog.WriteEntry($"deleted LAN bypass routes"); - } - catch (Exception e) - { - eventLog.WriteEntry($"failed to delete LAN bypass routes: {e.Message}", EventLogEntryType.Error); - } - this.gatewayIp = null; - - try - { - StopSmartDnsBlock(); - eventLog.WriteEntry($"stopped smartdnsblock"); - } - catch (Exception e) - { - eventLog.WriteEntry($"failed to stop smartdnsblock: {e.Message}", - EventLogEntryType.Warning); - } - } - - // Disable "Smart Multi-Homed Name Resolution", to ensure the system uses only the - // (non-filtered) DNS server(s) associated with the TAP device. - // - // Notes: - // - To show the current firewall rules: - // netsh wfp show filters - // - This website is an easy way to quickly verify there are no DNS leaks: - // https://ipleak.net/ - // - Because .Net provides *no way* to associate the new process with this one, the - // new process will continue to run even if this service is interrupted or crashes. - // Fortunately, since the changes it makes are *not* persistent, the system can, in - // the worst case, be fixed by rebooting. - private void StartSmartDnsBlock() - { - // smartdnsblock.exe must be a sibling of OutlineService.exe. - Process smartDnsBlock = new Process(); - smartDnsBlock.StartInfo.FileName = new DirectoryInfo(Process.GetCurrentProcess().MainModule.FileName).Parent.FullName + - Path.DirectorySeparatorChar + "smartdnsblock.exe"; - smartDnsBlock.StartInfo.UseShellExecute = false; - - smartDnsBlock.StartInfo.RedirectStandardError = true; - smartDnsBlock.StartInfo.RedirectStandardOutput = true; - - // This is for Windows 7: without it, the process exits immediately, presumably - // because stdin isn't connected to anything: - // https://github.com/Jigsaw-Code/outline-client/issues/415 - // - // This seems to make no difference on Windows 8 and 10. - smartDnsBlock.StartInfo.RedirectStandardInput = true; - - ArrayList stdout = new ArrayList(); - ArrayList stderr = new ArrayList(); - smartDnsBlock.OutputDataReceived += (object sender, DataReceivedEventArgs e) => - { - if (!String.IsNullOrEmpty(e.Data)) - { - stdout.Add(e.Data); - } - }; - smartDnsBlock.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => - { - if (!String.IsNullOrEmpty(e.Data)) - { - stderr.Add(e.Data); - } - }; - - try - { - smartDnsBlock.Start(); - smartDnsBlock.BeginOutputReadLine(); - smartDnsBlock.BeginErrorReadLine(); - } - catch (Exception e) - { - throw new Exception($"could not launch smartdnsblock at {smartDnsBlock.StartInfo.FileName}: { e.Message}"); - } - - // This does *not* throw if the process is still running after Nms. - smartDnsBlock.WaitForExit(SMART_DNS_BLOCK_TIMEOUT_MS); - if (smartDnsBlock.HasExited) - { - throw new Exception($"smartdnsblock failed " + $"(stdout: {String.Join(Environment.NewLine, stdout.ToArray())}, " + - $"(stderr: {String.Join(Environment.NewLine, stderr.ToArray())})"); - } - } - - private void StopSmartDnsBlock() - { - try - { - RunCommand("powershell", "stop-process -name smartdnsblock"); - } - catch (Exception e) - { - throw new Exception($"could not stop smartdnsblock: {e.Message}"); - } - } - - private void AddOrUpdateProxyRoute(string proxyIp, string gatewayIp, int gatewayInterfaceIndex) - { - // "netsh interface ipv4 set route" does *not* work for us here - // because it can only be used to change a route's *metric*. - try - { - RunCommand(CMD_ROUTE, $"change {proxyIp} {gatewayIp} if {gatewayInterfaceIndex}"); - } - catch (Exception) - { - RunCommand(CMD_NETSH, $"interface ipv4 add route {proxyIp}/32 nexthop={gatewayIp} interface=\"{gatewayInterfaceIndex}\" metric=0 store=active"); - } - } - - private void DeleteProxyRoute(string proxyIp) - { - // "route" doesn't need to know on which interface or through which - // gateway the route was created. - RunCommand(CMD_ROUTE, $"delete {proxyIp}"); - } - - // Route IPv4 traffic through the TAP device. Instead of deleting the - // default IPv4 gateway (0.0.0.0/0), we resort to creating two more - // specific routes (see IPV4_SUBNETS) that take precedence over the - // default gateway. This way, we need not worry about the default - // gateway being recreated with a lower metric upon device sleep. - // - // This 'hack' was inspired by OpenVPN; see: - // https://github.com/OpenVPN/openvpn3/commit/d08cc059e7132a3d3aee3dcd946fce4c35b1ced3#diff-1d76f0fd7ec04c6d1398288214a879c5R358 - // - // TODO: If these routes exist on a gateway that's not our TAP device, - // it might be a good signal that OpenVPN is active? - private void AddIpv4TapRedirect() - { - foreach (string subnet in IPV4_SUBNETS) - { - try - { - RunCommand(CMD_NETSH, $"interface ipv4 add route {subnet} nexthop={TAP_DEVICE_IP} interface={TAP_DEVICE_NAME} metric=0 store=active"); - } - catch (Exception) - { - RunCommand(CMD_NETSH, $"interface ipv4 set route {subnet} nexthop={TAP_DEVICE_IP} interface={TAP_DEVICE_NAME} metric=0 store=active"); - } - } - } - - private void RemoveIpv4TapRedirect() - { - foreach (string subnet in IPV4_SUBNETS) - { - RunCommand(CMD_NETSH, $"interface ipv4 delete route {subnet} interface={TAP_DEVICE_NAME}"); - } - } - - private void StartRoutingIpv4() - { - foreach (string subnet in IPV4_SUBNETS) - { - RunCommand(CMD_NETSH, $"interface ipv4 delete route {subnet} interface={NetworkInterface.LoopbackInterfaceIndex}"); - } - } - - private void StopRoutingIpv4() - { - foreach (string subnet in IPV4_SUBNETS) - { - try - { - RunCommand(CMD_NETSH, $"interface ipv4 add route {subnet} interface={NetworkInterface.LoopbackInterfaceIndex} metric=0 store=active"); - } - catch (Exception) - { - RunCommand(CMD_NETSH, $"interface ipv4 set route {subnet} interface={NetworkInterface.LoopbackInterfaceIndex} metric=0 store=active"); - } - } - } - - private void StartRoutingIpv6() - { - foreach (string subnet in IPV6_SUBNETS) - { - RunCommand(CMD_NETSH, $"interface ipv6 delete route {subnet} interface={NetworkInterface.IPv6LoopbackInterfaceIndex}"); - } - } - - // Outline does not currently support IPv6, so we resort to disabling it while the VPN is active to - // prevent leakage. Removing the default IPv6 gateway is not enough since it gets re-created - // through router advertisements and DHCP (disabling these or IPv6 routing altogether requires a - // system reboot). Thus, we resort to creating three IPv6 routes (see IPV6_SUBNETS) to the loopback - // interface that are more specific than the default route, causing IPv6 traffic to get dropped. - private void StopRoutingIpv6() - { - foreach (string subnet in IPV6_SUBNETS) - { - try - { - RunCommand(CMD_NETSH, $"interface ipv6 add route {subnet} interface={NetworkInterface.IPv6LoopbackInterfaceIndex} metric=0 store=active"); - } - catch (Exception) - { - RunCommand(CMD_NETSH, $"interface ipv6 set route {subnet} interface={NetworkInterface.IPv6LoopbackInterfaceIndex} metric=0 store=active"); - } - } - } - - // Routes reserved and private subnets through the default gateway so they bypass the VPN. - private void AddOrUpdateReservedSubnetBypass(string gatewayIp, int gatewayInterfaceIndex) - { - foreach (string subnet in IPV4_RESERVED_SUBNETS) - { - try - { - RunCommand(CMD_ROUTE, $"change {subnet} {gatewayIp} if {gatewayInterfaceIndex}"); - } - catch (Exception) - { - RunCommand(CMD_NETSH, $"interface ipv4 add route {subnet} nexthop={gatewayIp} interface=\"{gatewayInterfaceIndex}\" metric=0 store=active"); - } - } - } - - // Removes reserved subnet routes created to bypass the VPN. - private void RemoveReservedSubnetBypass() - { - foreach (string subnet in IPV4_RESERVED_SUBNETS) - { - RunCommand(CMD_ROUTE, $"delete {subnet}"); - } - } - - // Runs a shell command synchronously. - private void RunCommand(string cmd, string args) - { - Console.WriteLine($"running command: {cmd} {args}"); - - var startInfo = new ProcessStartInfo(cmd); - startInfo.Arguments = args; - startInfo.UseShellExecute = false; - startInfo.RedirectStandardError = true; - startInfo.RedirectStandardOutput = true; - startInfo.CreateNoWindow = true; - - Process p = new Process(); - var stdout = new StringBuilder(); - var stderr = new StringBuilder(); - p.OutputDataReceived += (object sender, DataReceivedEventArgs e) => - { - if (e == null || String.IsNullOrWhiteSpace(e.Data)) - { - return; - } - stdout.Append(e.Data); - }; - p.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => - { - if (e == null || String.IsNullOrWhiteSpace(e.Data)) - { - return; - } - stderr.Append(e.Data); - }; - p.StartInfo = startInfo; - p.Start(); - p.BeginOutputReadLine(); - p.BeginErrorReadLine(); - p.WaitForExit(); - - // "route" is weird and always exits with zero: we have to examine - // stderr to detect its errors. - if (p.ExitCode != 0 || stderr.ToString().Length > 0) - { - // NOTE: Do *not* add args to this error message because it's piped - // back to the client for inclusion in Sentry reports and - // effectively contain access keys. - throw new Exception($"command exited with {p.ExitCode} " + - $"(stdout: {stdout.ToString()}, stderr: {stderr.ToString()})"); - } - } - - // Searches the system's routing table for the best route to the - // specified IP address *that does not route through the TAP device*. - // - // That last requirement - ignoring the TAP device - is what prevents us - // from simply calling GetBestRoute: other than that, it does all the - // work of finding the lowest-weighted gateway to the destination IP. - // - // NOTE: This function does not *always* find the best gateway: it - // currently only considers "default" gateways (0.0.0.0) which may not - // work in some rare cases. Several re-implementations of the Windows - // API illustrate how we could more closely match GetBestRoute: - // - https://github.com/wine-mirror/wine/blob/master/dlls/iphlpapi/iphlpapi_main.c - // - https://github.com/reactos/reactos/blob/master/dll/win32/iphlpapi/iphlpapi_main.c - private void GetSystemIpv4Gateway(string proxyIp) - { - gatewayIp = null; - gatewayInterfaceIndex = -1; - - int tapInterfaceIndex; - try - { - tapInterfaceIndex = NetworkInterface.GetAllNetworkInterfaces() - .Where(i => i.Name == TAP_DEVICE_NAME) - .FirstOrDefault() - .GetIPProperties() - .GetIPv4Properties().Index; - } - catch (Exception) - { - throw new Exception("TAP device not found"); - } - - // Some marshalling craziness follows: we have to first ask - // GetIpForwardTable how much memory is required to hold the routing - // table before calling it again to actually return us the table; - // once we have the table, we have to iterate over the rows - // (thankfully, MIB_IPFORWARDROW marshalls easily). - int bufferSize = 0; - if (GetIpForwardTable(IntPtr.Zero, ref bufferSize, true) != ERROR_INSUFFICIENT_BUFFER) - { - throw new Exception("could not fetch routing table"); - } - var buffer = Marshal.AllocHGlobal(bufferSize); - if (GetIpForwardTable(buffer, ref bufferSize, true) != 0) - { - Marshal.FreeHGlobal(buffer); - throw new Exception("could not fetch routing table"); - } - - // NOTE: We deliberately *do not marshal the entire - // MIB_IPFORWARDTABLE* owing to unexplained crashes following - // suspend/resume. Fortunately, since that structure is - // logically just a DWORD followed by an array, this entails - // little extra work. - var numEntries = Marshal.ReadInt32(buffer); - MIB_IPFORWARDROW bestRow = null; - var rowPtr = buffer + Marshal.SizeOf(numEntries); - for (int i = 0; i < numEntries; i++) - { - MIB_IPFORWARDROW row = (MIB_IPFORWARDROW)Marshal.PtrToStructure(rowPtr, typeof(MIB_IPFORWARDROW)); - - // Must be a gateway (see note above on how we can improve this). - if (row.dwForwardDest != 0) - { - continue; - } - - // Must not be the TAP device. - if (row.dwForwardIfIndex == tapInterfaceIndex) - { - continue; - } - - if (bestRow == null || row.dwForwardMetric1 < bestRow.dwForwardMetric1) - { - bestRow = row; - } - - rowPtr += Marshal.SizeOf(typeof(MIB_IPFORWARDROW)); - } - - Marshal.FreeHGlobal(buffer); - - if (bestRow == null) - { - throw new Exception("no gateway found"); - } - - gatewayIp = new IPAddress(BitConverter.GetBytes(bestRow.dwForwardNextHop)).ToString(); - gatewayInterfaceIndex = bestRow.dwForwardIfIndex; - } - - // Updates, if Outline is connected, the routing table to reflect a new - // gateway. - // - // There's really just one thing to do when the gateway changes: update - // the (direct) route to the proxy server to route through the new - // gateway. If there is no gateway, e.g. because the system has lost - // network connectivity, notify the client and keep the IPv4 redirect - // and IPv6 block in place: this helps prevent leaking traffic. - // - // Notes: - // - *This function must not throw*. If it does, the handler is unset. - // - This function also updates two further sets of routes: the LAN - // bypass routes (which must route through the gateway) and the IPv4 - // redirect routes (which "fall back" to the system gateway once the - // TAP device temporarily disappears due to tun2socks' exit). - // - The NetworkChange.NetworkAddressChanged callback is *extremely - // noisy*. In particular, it seems to be called twice for every - // change to the routing table. There does not seem to be any useful - // information in the supplied EventArgs. This is partly why we don't - // touch the routing table unless the gateway has actually changed. - // - This function may be called while #ConfigureRouting is still - // running. This is partly why we exit early (at the top) if we don't - // think Outline is connected. - private void NetworkAddressChanged(object sender, EventArgs evt) - { - if (proxyIp == null) - { - eventLog.WriteEntry("network changed but Outline is not connected - doing nothing"); - return; - } - - var previousGatewayIp = gatewayIp; - var previousGatewayInterfaceIndex = gatewayInterfaceIndex; - - try - { - GetSystemIpv4Gateway(proxyIp); - } - catch (Exception e) - { - eventLog.WriteEntry($"network changed but no gateway found: {e.Message}"); - } - - if (previousGatewayIp == gatewayIp && previousGatewayInterfaceIndex == gatewayInterfaceIndex) - { - // Only send on actual change, to prevent duplicate notifications (mostly - // harmless but can make debugging harder). - eventLog.WriteEntry($"network changed but gateway and interface stayed the same"); - return; - } - else if (gatewayIp == null) - { - SendConnectionStatusChange(ConnectionStatus.Reconnecting); - - // Stop capturing IPv4 traffic in order to prevent a routing loop in the TAP device. - // Redirect IPv4 traffic to the loopback interface instead to avoid leaking traffic when - // the network becomes available. - try - { - StopRoutingIpv4(); - RemoveIpv4TapRedirect(); - eventLog.WriteEntry($"stopped routing IPv4 traffic"); - } - catch (Exception e) - { - eventLog.WriteEntry($"failed to stop routing IPv4: {e.Message}", EventLogEntryType.Error); - } - return; - } - - eventLog.WriteEntry($"network changed - gateway is now {gatewayIp} on interface {gatewayInterfaceIndex}"); - - // Add the proxy escape route before capturing IPv4 traffic to prevent a routing loop in the TAP device. - try - { - AddOrUpdateProxyRoute(proxyIp, gatewayIp, gatewayInterfaceIndex); - eventLog.WriteEntry($"updated route to proxy"); - } - catch (Exception e) - { - eventLog.WriteEntry($"could not update route to proxy: {e.Message}"); - return; - } - - try - { - AddIpv4TapRedirect(); - StartRoutingIpv4(); - eventLog.WriteEntry($"refreshed IPv4 redirect"); - } - catch (Exception e) - { - eventLog.WriteEntry($"could not refresh IPv4 redirect: {e.Message}"); - return; - } - - // Send the status update now that the full-system VPN is connected. - SendConnectionStatusChange(ConnectionStatus.Connected); - - try - { - AddOrUpdateReservedSubnetBypass(gatewayIp, gatewayInterfaceIndex); - eventLog.WriteEntry($"updated LAN bypass routes"); - } - catch (Exception e) - { - // TODO: This isn't quite right: because we successfully updated - // the route to the proxy, the client *is* connected; it's - // just not "fully" connected, in the way we like. We - // should distinguish between "cannot reconnect right now, - // because no internet" and "cannot reconnect because - // netsh commands are failing". - eventLog.WriteEntry($"could not update LAN bypass routes: {e.Message}"); - return; - } - } - - // Writes the connection status to the pipe, if it is connected. - private void SendConnectionStatusChange(ConnectionStatus status) - { - if (pipe == null || !pipe.IsConnected) - { - eventLog.WriteEntry("Cannot send connection status change, pipe not connected.", EventLogEntryType.Error); - return; - } - ServiceResponse response = new ServiceResponse(); - response.action = ACTION_STATUS_CHANGED; - response.statusCode = (int)ErrorCode.Success; - response.connectionStatus = (int)status; - try - { - WriteResponse(response); - } - catch (Exception e) - { - eventLog.WriteEntry($"Failed to send connection status change: {e.Message}"); - } - } - - public string GetNetworkInfo() - { - return String.Join(", ", NetworkInterface.GetAllNetworkInterfaces() - .Select(a => this.GetAdapterInfo(a))); - } - - private string GetAdapterInfo(NetworkInterface adapter) - { - var numIpv4Gateways = adapter.GetIPProperties().GatewayAddresses - .Select(g => g.Address) - .Where(a => a.AddressFamily == AddressFamily.InterNetwork) - .Count(); - var numIpv6Gateways = adapter.GetIPProperties().GatewayAddresses - .Select(g => g.Address) - .Where(a => a.AddressFamily == AddressFamily.InterNetworkV6) - .Count(); - - return $"{adapter.Name} ({adapter.OperationalStatus}): " + ( - adapter.Supports(NetworkInterfaceComponent.IPv4) ? - $"{numIpv4Gateways} x ipv4 gateways" : - "ipv4 disabled") + ", " + ( - adapter.Supports(NetworkInterfaceComponent.IPv6) ? - $"{numIpv6Gateways} x ipv6 gateways" : - "ipv6 disabled"); - } - } - - [DataContract] - internal class ServiceRequest - { - [DataMember] - internal string action; - [DataMember] - internal Dictionary parameters; - } - - [DataContract] - internal class ServiceResponse - { - [DataMember] - internal string action; - [DataMember] - internal int statusCode; - [DataMember] - internal string errorMessage; - [DataMember] - internal int connectionStatus; - } - - public enum ErrorCode - { - Success = 0, - GenericFailure = 1 - } - - public enum ConnectionStatus - { - Connected = 0, - Disconnected = 1, - Reconnecting = 2 - } -} +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Pipes; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security.AccessControl; +using System.Security.Principal; +using System.ServiceProcess; +using System.Text; +using Newtonsoft.Json; + +/* + * Windows Service, part of the Outline Windows client, to configure routing. + * Modifying the system routes requires admin permissions, so this service must be installed + * and started as admin. + * + * The service listens on a named pipe and supports the following JSON API: + * + * Requests + * + * configureRouting: Modifies the system's routing table to route all traffic through the TAP device + * except that destined for proxyIp. Disables IPv6 traffic. + * { action: "configureRouting", parameters: {"proxyIp": , "isAutoConnect": "false" }} + * + * resetRouting: Restores the system's default routing. + * { action: "resetRouting"} + * + * Response + * + * { statusCode: , action: errorMessage?: } + * + * The service will send connection status updates if the pipe connection is kept + * open by the client. Such responses have the form: + * + * { statusCode: , action: "statusChanged", connectionStatus: } + * + * View logs with this PowerShell query: + * get-eventlog -logname Application -source OutlineService -newest 20 | format-table -property timegenerated,entrytype,message -autosize + */ +namespace OutlineService +{ + public partial class OutlineService : ServiceBase + { + private const string EVENT_LOG_SOURCE = "OutlineService"; + private const string EVENT_LOG_NAME = "Application"; + // Must be kept in sync with the Electron code. + private const string PIPE_NAME = "OutlineServicePipe"; + private const string TAP_DEVICE_NAME = "outline-tap0"; + private const string TAP_DEVICE_IP = "10.0.85.1"; + + private const string ACTION_CONFIGURE_ROUTING = "configureRouting"; + private const string ACTION_RESET_ROUTING = "resetRouting"; + private const string ACTION_STATUS_CHANGED = "statusChanged"; + private const string PARAM_PROXY_IP = "proxyIp"; + private const string PARAM_AUTO_CONNECT = "isAutoConnect"; + + private static string[] IPV4_SUBNETS = { "0.0.0.0/1", "128.0.0.0/1" }; + private static string[] IPV6_SUBNETS = { "fc00::/7", "2000::/4", "3000::/4" }; + private static string[] IPV4_RESERVED_SUBNETS = { + "0.0.0.0/8", + "10.0.0.0/8", + "100.64.0.0/10", + "169.254.0.0/16", + "172.16.0.0/12", + "192.0.0.0/24", + "192.0.2.0/24", + "192.31.196.0/24", + "192.52.193.0/24", + "192.88.99.0/24", + "192.168.0.0/16", + "192.175.48.0/24", + "198.18.0.0/15", + "198.51.100.0/24", + "203.0.113.0/24", + "240.0.0.0/4" + }; + private const string CMD_NETSH = "netsh"; + private const string CMD_ROUTE = "route"; + + private const uint BUFFER_SIZE_BYTES = 1024; + + private EventLog eventLog; + private NamedPipeServerStream pipe; + private string proxyIp; + private string gatewayIp; + private int gatewayInterfaceIndex; + + // Time, in ms, to wait until considering smartdnsblock.exe to have successfully launched. + private const int SMART_DNS_BLOCK_TIMEOUT_MS = 1000; + + // https://docs.microsoft.com/en-us/windows/desktop/api/ipmib/ns-ipmib-_mib_ipforwardrow + [StructLayout(LayoutKind.Sequential)] + internal class MIB_IPFORWARDROW + { + internal uint dwForwardDest; + internal uint dwForwardMask; + internal uint dwForwardPolicy; + internal uint dwForwardNextHop; + internal int dwForwardIfIndex; + internal uint dwForwardType; + internal uint dwForwardProto; + internal uint dwForwardAge; + internal uint dwForwardNextHopAS; + internal uint dwForwardMetric1; + internal uint dwForwardMetric2; + internal uint dwForwardMetric3; + internal uint dwForwardMetric4; + internal uint dwForwardMetric5; + } + + // https://docs.microsoft.com/en-us/windows/desktop/api/ipmib/ns-ipmib-_mib_ipforwardtable + // + // NOTE: Because of the variable-length array, Marshal.PtrToStructure + // will *not* populate the table field. Additionally, we have seen + // crashes following suspend/resume while trying to marshal this + // structure. See #GetSystemIpv4Gateway for more on this, as well + // as for how to traverse the table. + [StructLayout(LayoutKind.Sequential)] + internal class MIB_IPFORWARDTABLE + { + internal uint dwNumEntries; + internal MIB_IPFORWARDROW[] table; + }; + + // https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getipforwardtable + [DllImport("iphlpapi", CharSet = CharSet.Auto)] + private extern static int GetIpForwardTable(IntPtr pIpForwardTable, ref int pdwSize, bool bOrder); + + // https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--0-499- + private static int ERROR_INSUFFICIENT_BUFFER = 122; + + // Do as little as possible here because any error thrown will cause "net start" to fail + // without anything being added to the application log. + public OutlineService() + { + InitializeComponent(); + + eventLog = new EventLog(); + if (!EventLog.SourceExists(EVENT_LOG_SOURCE)) + { + EventLog.CreateEventSource(EVENT_LOG_SOURCE, EVENT_LOG_NAME); + } + eventLog.Source = EVENT_LOG_SOURCE; + eventLog.Log = EVENT_LOG_NAME; + } + + protected override void OnStart(string[] args) + { + eventLog.WriteEntry("OutlineService starting"); + NetworkChange.NetworkAddressChanged += + new NetworkAddressChangedEventHandler(NetworkAddressChanged); + CreatePipe(); + } + + protected override void OnStop() + { + eventLog.WriteEntry("OutlineService stopping"); + DestroyPipe(); + NetworkChange.NetworkAddressChanged -= NetworkAddressChanged; + } + + private void CreatePipe() + { + var pipeSecurity = new PipeSecurity(); + pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier( + WellKnownSidType.CreatorOwnerSid, null), + PipeAccessRights.FullControl, AccessControlType.Allow)); + pipeSecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier( + WellKnownSidType.AuthenticatedUserSid, null), + PipeAccessRights.ReadWrite, AccessControlType.Allow)); + + pipe = new NamedPipeServerStream(PIPE_NAME, PipeDirection.InOut, -1, PipeTransmissionMode.Message, + PipeOptions.Asynchronous, (int)BUFFER_SIZE_BYTES, (int)BUFFER_SIZE_BYTES, pipeSecurity); + pipe.BeginWaitForConnection(HandleConnection, null); + } + + private void DestroyPipe() + { + if (pipe == null) + { + return; + } + try + { + if (pipe.IsConnected) + { + pipe.Disconnect(); + } + pipe.Close(); + pipe = null; + } + catch (Exception e) + { + eventLog.WriteEntry($"Got an exception while destroying the pipe: {e.ToString()}", + EventLogEntryType.Warning); + } + } + + private void HandleConnection(IAsyncResult result) + { + eventLog.WriteEntry("Got incoming connection"); + + // Save the network config before we do anything. If the request fails + // it will be sent to the client for inclusion in Sentry reports. + var beforeNetworkInfo = GetNetworkInfo(); + + try + { + pipe.EndWaitForConnection(result); + // Keep the pipe connected to send connection status updates. + while (pipe.IsConnected) + { + ServiceResponse response = new ServiceResponse(); + var request = ReadRequest(); + if (request == null) + { + response.statusCode = (int)ErrorCode.GenericFailure; + } + else + { + response.action = request.action; + try + { + HandleRequest(request); + } + catch (Exception e) + { + response.statusCode = (int)ErrorCode.GenericFailure; + response.errorMessage = $"{e.Message} (network config: {beforeNetworkInfo})"; + eventLog.WriteEntry($"request failed: {e.Message}", EventLogEntryType.Error); + } + } + WriteResponse(response); + } + } + catch (Exception e) + { + eventLog.WriteEntry($"Failed to handle connection: {e.ToString()}", EventLogEntryType.Error); + } + finally + { + // Pipe streams are one-to-one connections. Recreate the pipe to handle subsequent requests. + DestroyPipe(); + CreatePipe(); + } + } + + private ServiceRequest ReadRequest() + { + var stringBuilder = new StringBuilder(); + var buffer = new byte[BUFFER_SIZE_BYTES]; + var memoryStream = new MemoryStream(); + do + { + var readBytes = pipe.Read(buffer, 0, buffer.Length); + memoryStream.Write(buffer, 0, readBytes); + } while (!pipe.IsMessageComplete); + var msg = Encoding.UTF8.GetString(buffer); + if (String.IsNullOrWhiteSpace(msg)) + { + eventLog.WriteEntry("Failed to read request", EventLogEntryType.Error); + return null; + } + eventLog.WriteEntry($"incoming message: {msg}"); + return ParseRequest(msg); + } + + private ServiceRequest ParseRequest(string jsonRequest) + { + try + { + return JsonConvert.DeserializeObject(jsonRequest); + } + catch (Exception e) + { + eventLog.WriteEntry($"Failed to parse request: {e.ToString()}"); + } + return null; + } + + private void WriteResponse(ServiceResponse response) + { + var jsonResponse = SerializeResponse(response); + if (jsonResponse == null) + { + eventLog.WriteEntry("Failed to serialize response.", EventLogEntryType.Error); + return; + } + eventLog.WriteEntry($"outgoing message: {jsonResponse}"); + var jsonResponseBytes = Encoding.UTF8.GetBytes(jsonResponse); + pipe.Write(jsonResponseBytes, 0, jsonResponseBytes.Length); + pipe.Flush(); + pipe.WaitForPipeDrain(); + } + + private string SerializeResponse(ServiceResponse response) + { + try + { + return JsonConvert.SerializeObject(response); + } + catch (Exception e) + { + eventLog.WriteEntry($"Failed to serialize response: {e.ToString()}"); + } + return null; + } + + private void HandleRequest(ServiceRequest request) + { + switch (request.action) + { + case ACTION_CONFIGURE_ROUTING: + ConfigureRouting(request.parameters[PARAM_PROXY_IP], Boolean.Parse(request.parameters[PARAM_AUTO_CONNECT])); + break; + case ACTION_RESET_ROUTING: + ResetRouting(proxyIp, gatewayInterfaceIndex); + break; + default: + eventLog.WriteEntry($"Received invalid request: {request.action}", EventLogEntryType.Error); + break; + } + } + + // Routes all traffic *except that destined for the proxy server* + // through the TAP device, creating the illusion of a system-wide VPN. + // + // The two key steps are: + // - Route all IPv4 traffic through the TAP device. + // - Find a gateway and route traffic *to the proxy server only* (a /32 + // mask) through it. + // + // Finding a gateway, in particular, is complex: for more on how it + // works, see #GetSystemIpv4Gateway. + // + // On top of this foundation, we take some steps to help prevent + // "leaking" traffic: + // - IPv6 traffic is "blocked", as Outline does not currently support + // servers on IPv6 addresses. + // - "Smart Multi-Homed Name Resolution" is disabled, as it can cause + // the system's "regular" - and potentially filtered - DNS servers to + // be used (particularly on Windows 10). + // + // Preventing leaks significantly complicates things. In particular, *if + // autostart is true and a gateway cannot be found then the IPv4 + // redirect and IPv6 block remain in place and no exception is thrown*. + // When a gateway re-appears following a network change (see + // #NetworkAddressChanged), we will reconnect. + // + // Lastly, a set of routes is added through the gateway *to non-routable + // ("LAN") addresses*. This allows common hardware such as Chromecast to + // function while Outline is active. + // + // Note: + // - Currently, this function does not "clean up" in the event of + // failure. Instead, we rely on the client to call ResetRouting + // following a connection failure. + // - There's limited protection against "nested connections", i.e. + // connecting to Outline while another VPN, e.g. OpenVPN, is already + // active. There's no simple API we can use to tell whether a VPN is + // already active and *given the difficulty in identifying this + // reliably* (multiple active network interfaces, for example, are + // very common, e.g. it happens - briefly - every time a user + // switches between a wired and wireless network) we err on the side + // of working. Since the user presumably knows whether another VPN is + // already active and *we make no persistent changes to the routing + // table* this seems reasonable. + // + // TODO: The client needs to handle certain autoconnect failures better, + // e.g. if IPv4 redirect fails then the client is not really in + // the reconnecting state; the system is leaking traffic. + public void ConfigureRouting(string proxyIp, bool isAutoConnect) + { + try + { + StartSmartDnsBlock(); + eventLog.WriteEntry($"started smartdnsblock"); + } + catch (Exception e) + { + throw new Exception($"could not start smartdnsblock: {e.Message}"); + } + + try + { + GetSystemIpv4Gateway(proxyIp); + + eventLog.WriteEntry($"connecting via gateway at {gatewayIp} on interface {gatewayInterfaceIndex}"); + + // Set the proxy escape route first to prevent a routing loop when capturing all IPv4 traffic. + try + { + AddOrUpdateProxyRoute(proxyIp, gatewayIp, gatewayInterfaceIndex); + eventLog.WriteEntry($"created route to proxy"); + } + catch (Exception e) + { + throw new Exception($"could not create route to proxy: {e.Message}"); + } + this.proxyIp = proxyIp; + + try + { + AddOrUpdateReservedSubnetBypass(gatewayIp, gatewayInterfaceIndex); + eventLog.WriteEntry($"created LAN bypass routes"); + } + catch (Exception e) + { + throw new Exception($"could not create LAN bypass routes: {e.Message}"); + } + } + catch (Exception e) when (isAutoConnect) + { + eventLog.WriteEntry($"could not reconnect during auto-connect: {e.Message}", EventLogEntryType.Warning); + } + + try + { + StopRoutingIpv6(); + eventLog.WriteEntry($"blocked IPv6 traffic"); + } + catch (Exception e) + { + throw new Exception($"could not block IPv6 traffic: {e.Message}"); + } + + try + { + AddIpv4TapRedirect(); + eventLog.WriteEntry($"redirected IPv4 traffic"); + } + catch (Exception e) + { + throw new Exception($"could not redirect IPv4 traffic: {e.Message}"); + } + } + + // Undoes and removes as many as possible of the routes and other + // changes to the system previously made by #ConfigureRouting. + // + // As per #ConfigureRouting, this function is largely idempodent: + // calling it multiple times in succession should be safe and result in + // the same system configuration. + // + // Notes: + // - *This function does not set any error code when any step fails*. + // It probably should, as the system may in some rare cases still be + // connected (or, worse but less likely, bricked). + // - If the service does not know the IP address of the proxy server + // then it *cannot remove that route*. This can happen if the service + // is restarted while Outline is connected *or* (more likely) if this + // function is called while Outline is not connected. This route is + // mostly harmless because it only affects traffic to the proxy and + // if/when the user reconnects to it the route will be updated. + public void ResetRouting(string proxyIp, int gatewayInterfaceIndex) + { + try + { + RemoveIpv4TapRedirect(); + eventLog.WriteEntry($"removed IPv4 redirect"); + } + catch (Exception e) + { + eventLog.WriteEntry($"failed to remove IPv4 redirect: {e.Message}", EventLogEntryType.Error); + } + + try + { + // This is only necessary when disconecting without network connectivity. + StartRoutingIpv4(); + } + catch (Exception) {} + + try + { + StartRoutingIpv6(); + eventLog.WriteEntry($"unblocked IPv6"); + } + catch (Exception e) + { + eventLog.WriteEntry($"failed to unblock IPv6: {e.Message}", EventLogEntryType.Error); + } + + if (proxyIp != null) + { + try + { + DeleteProxyRoute(proxyIp); + eventLog.WriteEntry($"deleted route to proxy"); + } + catch (Exception e) + { + eventLog.WriteEntry($"failed to delete route to proxy: {e.Message}", EventLogEntryType.Error); + } + this.proxyIp = null; + } + + try + { + RemoveReservedSubnetBypass(); + eventLog.WriteEntry($"deleted LAN bypass routes"); + } + catch (Exception e) + { + eventLog.WriteEntry($"failed to delete LAN bypass routes: {e.Message}", EventLogEntryType.Error); + } + this.gatewayIp = null; + + try + { + StopSmartDnsBlock(); + eventLog.WriteEntry($"stopped smartdnsblock"); + } + catch (Exception e) + { + eventLog.WriteEntry($"failed to stop smartdnsblock: {e.Message}", + EventLogEntryType.Warning); + } + } + + // Disable "Smart Multi-Homed Name Resolution", to ensure the system uses only the + // (non-filtered) DNS server(s) associated with the TAP device. + // + // Notes: + // - To show the current firewall rules: + // netsh wfp show filters + // - This website is an easy way to quickly verify there are no DNS leaks: + // https://ipleak.net/ + // - Because .Net provides *no way* to associate the new process with this one, the + // new process will continue to run even if this service is interrupted or crashes. + // Fortunately, since the changes it makes are *not* persistent, the system can, in + // the worst case, be fixed by rebooting. + private void StartSmartDnsBlock() + { + // smartdnsblock.exe must be a sibling of OutlineService.exe. + Process smartDnsBlock = new Process(); + smartDnsBlock.StartInfo.FileName = new DirectoryInfo(Process.GetCurrentProcess().MainModule.FileName).Parent.FullName + + Path.DirectorySeparatorChar + "smartdnsblock.exe"; + smartDnsBlock.StartInfo.UseShellExecute = false; + + smartDnsBlock.StartInfo.RedirectStandardError = true; + smartDnsBlock.StartInfo.RedirectStandardOutput = true; + + // This is for Windows 7: without it, the process exits immediately, presumably + // because stdin isn't connected to anything: + // https://github.com/Jigsaw-Code/outline-client/issues/415 + // + // This seems to make no difference on Windows 8 and 10. + smartDnsBlock.StartInfo.RedirectStandardInput = true; + + ArrayList stdout = new ArrayList(); + ArrayList stderr = new ArrayList(); + smartDnsBlock.OutputDataReceived += (object sender, DataReceivedEventArgs e) => + { + if (!String.IsNullOrEmpty(e.Data)) + { + stdout.Add(e.Data); + } + }; + smartDnsBlock.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => + { + if (!String.IsNullOrEmpty(e.Data)) + { + stderr.Add(e.Data); + } + }; + + try + { + smartDnsBlock.Start(); + smartDnsBlock.BeginOutputReadLine(); + smartDnsBlock.BeginErrorReadLine(); + } + catch (Exception e) + { + throw new Exception($"could not launch smartdnsblock at {smartDnsBlock.StartInfo.FileName}: { e.Message}"); + } + + // This does *not* throw if the process is still running after Nms. + smartDnsBlock.WaitForExit(SMART_DNS_BLOCK_TIMEOUT_MS); + if (smartDnsBlock.HasExited) + { + throw new Exception($"smartdnsblock failed " + $"(stdout: {String.Join(Environment.NewLine, stdout.ToArray())}, " + + $"(stderr: {String.Join(Environment.NewLine, stderr.ToArray())})"); + } + } + + private void StopSmartDnsBlock() + { + try + { + RunCommand("powershell", "stop-process -name smartdnsblock"); + } + catch (Exception e) + { + throw new Exception($"could not stop smartdnsblock: {e.Message}"); + } + } + + private void AddOrUpdateProxyRoute(string proxyIp, string gatewayIp, int gatewayInterfaceIndex) + { + // "netsh interface ipv4 set route" does *not* work for us here + // because it can only be used to change a route's *metric*. + try + { + RunCommand(CMD_ROUTE, $"change {proxyIp} {gatewayIp} if {gatewayInterfaceIndex}"); + } + catch (Exception) + { + RunCommand(CMD_NETSH, $"interface ipv4 add route {proxyIp}/32 nexthop={gatewayIp} interface=\"{gatewayInterfaceIndex}\" metric=0 store=active"); + } + } + + private void DeleteProxyRoute(string proxyIp) + { + // "route" doesn't need to know on which interface or through which + // gateway the route was created. + RunCommand(CMD_ROUTE, $"delete {proxyIp}"); + } + + // Route IPv4 traffic through the TAP device. Instead of deleting the + // default IPv4 gateway (0.0.0.0/0), we resort to creating two more + // specific routes (see IPV4_SUBNETS) that take precedence over the + // default gateway. This way, we need not worry about the default + // gateway being recreated with a lower metric upon device sleep. + // + // This 'hack' was inspired by OpenVPN; see: + // https://github.com/OpenVPN/openvpn3/commit/d08cc059e7132a3d3aee3dcd946fce4c35b1ced3#diff-1d76f0fd7ec04c6d1398288214a879c5R358 + // + // TODO: If these routes exist on a gateway that's not our TAP device, + // it might be a good signal that OpenVPN is active? + private void AddIpv4TapRedirect() + { + foreach (string subnet in IPV4_SUBNETS) + { + try + { + RunCommand(CMD_NETSH, $"interface ipv4 add route {subnet} nexthop={TAP_DEVICE_IP} interface={TAP_DEVICE_NAME} metric=0 store=active"); + } + catch (Exception) + { + RunCommand(CMD_NETSH, $"interface ipv4 set route {subnet} nexthop={TAP_DEVICE_IP} interface={TAP_DEVICE_NAME} metric=0 store=active"); + } + } + } + + private void RemoveIpv4TapRedirect() + { + foreach (string subnet in IPV4_SUBNETS) + { + RunCommand(CMD_NETSH, $"interface ipv4 delete route {subnet} interface={TAP_DEVICE_NAME}"); + } + } + + private void StartRoutingIpv4() + { + foreach (string subnet in IPV4_SUBNETS) + { + RunCommand(CMD_NETSH, $"interface ipv4 delete route {subnet} interface={NetworkInterface.LoopbackInterfaceIndex}"); + } + } + + private void StopRoutingIpv4() + { + foreach (string subnet in IPV4_SUBNETS) + { + try + { + RunCommand(CMD_NETSH, $"interface ipv4 add route {subnet} interface={NetworkInterface.LoopbackInterfaceIndex} metric=0 store=active"); + } + catch (Exception) + { + RunCommand(CMD_NETSH, $"interface ipv4 set route {subnet} interface={NetworkInterface.LoopbackInterfaceIndex} metric=0 store=active"); + } + } + } + + private void StartRoutingIpv6() + { + foreach (string subnet in IPV6_SUBNETS) + { + RunCommand(CMD_NETSH, $"interface ipv6 delete route {subnet} interface={NetworkInterface.IPv6LoopbackInterfaceIndex}"); + } + } + + // Outline does not currently support IPv6, so we resort to disabling it while the VPN is active to + // prevent leakage. Removing the default IPv6 gateway is not enough since it gets re-created + // through router advertisements and DHCP (disabling these or IPv6 routing altogether requires a + // system reboot). Thus, we resort to creating three IPv6 routes (see IPV6_SUBNETS) to the loopback + // interface that are more specific than the default route, causing IPv6 traffic to get dropped. + private void StopRoutingIpv6() + { + foreach (string subnet in IPV6_SUBNETS) + { + try + { + RunCommand(CMD_NETSH, $"interface ipv6 add route {subnet} interface={NetworkInterface.IPv6LoopbackInterfaceIndex} metric=0 store=active"); + } + catch (Exception) + { + RunCommand(CMD_NETSH, $"interface ipv6 set route {subnet} interface={NetworkInterface.IPv6LoopbackInterfaceIndex} metric=0 store=active"); + } + } + } + + // Routes reserved and private subnets through the default gateway so they bypass the VPN. + private void AddOrUpdateReservedSubnetBypass(string gatewayIp, int gatewayInterfaceIndex) + { + foreach (string subnet in IPV4_RESERVED_SUBNETS) + { + try + { + RunCommand(CMD_ROUTE, $"change {subnet} {gatewayIp} if {gatewayInterfaceIndex}"); + } + catch (Exception) + { + RunCommand(CMD_NETSH, $"interface ipv4 add route {subnet} nexthop={gatewayIp} interface=\"{gatewayInterfaceIndex}\" metric=0 store=active"); + } + } + } + + // Removes reserved subnet routes created to bypass the VPN. + private void RemoveReservedSubnetBypass() + { + foreach (string subnet in IPV4_RESERVED_SUBNETS) + { + RunCommand(CMD_ROUTE, $"delete {subnet}"); + } + } + + // Runs a shell command synchronously. + private void RunCommand(string cmd, string args) + { + Console.WriteLine($"running command: {cmd} {args}"); + + var startInfo = new ProcessStartInfo(cmd); + startInfo.Arguments = args; + startInfo.UseShellExecute = false; + startInfo.RedirectStandardError = true; + startInfo.RedirectStandardOutput = true; + startInfo.CreateNoWindow = true; + + Process p = new Process(); + var stdout = new StringBuilder(); + var stderr = new StringBuilder(); + p.OutputDataReceived += (object sender, DataReceivedEventArgs e) => + { + if (e == null || String.IsNullOrWhiteSpace(e.Data)) + { + return; + } + stdout.Append(e.Data); + }; + p.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => + { + if (e == null || String.IsNullOrWhiteSpace(e.Data)) + { + return; + } + stderr.Append(e.Data); + }; + p.StartInfo = startInfo; + p.Start(); + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + p.WaitForExit(); + + // "route" is weird and always exits with zero: we have to examine + // stderr to detect its errors. + if (p.ExitCode != 0 || stderr.ToString().Length > 0) + { + // NOTE: Do *not* add args to this error message because it's piped + // back to the client for inclusion in Sentry reports and + // effectively contain access keys. + throw new Exception($"command exited with {p.ExitCode} " + + $"(stdout: {stdout.ToString()}, stderr: {stderr.ToString()})"); + } + } + + // Searches the system's routing table for the best route to the + // specified IP address *that does not route through the TAP device*. + // + // That last requirement - ignoring the TAP device - is what prevents us + // from simply calling GetBestRoute: other than that, it does all the + // work of finding the lowest-weighted gateway to the destination IP. + // + // NOTE: This function does not *always* find the best gateway: it + // currently only considers "default" gateways (0.0.0.0) which may not + // work in some rare cases. Several re-implementations of the Windows + // API illustrate how we could more closely match GetBestRoute: + // - https://github.com/wine-mirror/wine/blob/master/dlls/iphlpapi/iphlpapi_main.c + // - https://github.com/reactos/reactos/blob/master/dll/win32/iphlpapi/iphlpapi_main.c + private void GetSystemIpv4Gateway(string proxyIp) + { + gatewayIp = null; + gatewayInterfaceIndex = -1; + + int tapInterfaceIndex; + try + { + tapInterfaceIndex = NetworkInterface.GetAllNetworkInterfaces() + .Where(i => i.Name == TAP_DEVICE_NAME) + .FirstOrDefault() + .GetIPProperties() + .GetIPv4Properties().Index; + } + catch (Exception) + { + throw new Exception("TAP device not found"); + } + + // Some marshalling craziness follows: we have to first ask + // GetIpForwardTable how much memory is required to hold the routing + // table before calling it again to actually return us the table; + // once we have the table, we have to iterate over the rows + // (thankfully, MIB_IPFORWARDROW marshalls easily). + int bufferSize = 0; + if (GetIpForwardTable(IntPtr.Zero, ref bufferSize, true) != ERROR_INSUFFICIENT_BUFFER) + { + throw new Exception("could not fetch routing table"); + } + var buffer = Marshal.AllocHGlobal(bufferSize); + if (GetIpForwardTable(buffer, ref bufferSize, true) != 0) + { + Marshal.FreeHGlobal(buffer); + throw new Exception("could not fetch routing table"); + } + + // NOTE: We deliberately *do not marshal the entire + // MIB_IPFORWARDTABLE* owing to unexplained crashes following + // suspend/resume. Fortunately, since that structure is + // logically just a DWORD followed by an array, this entails + // little extra work. + var numEntries = Marshal.ReadInt32(buffer); + MIB_IPFORWARDROW bestRow = null; + var rowPtr = buffer + Marshal.SizeOf(numEntries); + for (int i = 0; i < numEntries; i++) + { + MIB_IPFORWARDROW row = (MIB_IPFORWARDROW)Marshal.PtrToStructure(rowPtr, typeof(MIB_IPFORWARDROW)); + + // Must be a gateway (see note above on how we can improve this). + if (row.dwForwardDest != 0) + { + continue; + } + + // Must not be the TAP device. + if (row.dwForwardIfIndex == tapInterfaceIndex) + { + continue; + } + + if (bestRow == null || row.dwForwardMetric1 < bestRow.dwForwardMetric1) + { + bestRow = row; + } + + rowPtr += Marshal.SizeOf(typeof(MIB_IPFORWARDROW)); + } + + Marshal.FreeHGlobal(buffer); + + if (bestRow == null) + { + throw new Exception("no gateway found"); + } + + gatewayIp = new IPAddress(BitConverter.GetBytes(bestRow.dwForwardNextHop)).ToString(); + gatewayInterfaceIndex = bestRow.dwForwardIfIndex; + } + + // Updates, if Outline is connected, the routing table to reflect a new + // gateway. + // + // There's really just one thing to do when the gateway changes: update + // the (direct) route to the proxy server to route through the new + // gateway. If there is no gateway, e.g. because the system has lost + // network connectivity, notify the client and keep the IPv4 redirect + // and IPv6 block in place: this helps prevent leaking traffic. + // + // Notes: + // - *This function must not throw*. If it does, the handler is unset. + // - This function also updates two further sets of routes: the LAN + // bypass routes (which must route through the gateway) and the IPv4 + // redirect routes (which "fall back" to the system gateway once the + // TAP device temporarily disappears due to tun2socks' exit). + // - The NetworkChange.NetworkAddressChanged callback is *extremely + // noisy*. In particular, it seems to be called twice for every + // change to the routing table. There does not seem to be any useful + // information in the supplied EventArgs. This is partly why we don't + // touch the routing table unless the gateway has actually changed. + // - This function may be called while #ConfigureRouting is still + // running. This is partly why we exit early (at the top) if we don't + // think Outline is connected. + private void NetworkAddressChanged(object sender, EventArgs evt) + { + if (proxyIp == null) + { + eventLog.WriteEntry("network changed but Outline is not connected - doing nothing"); + return; + } + + var previousGatewayIp = gatewayIp; + var previousGatewayInterfaceIndex = gatewayInterfaceIndex; + + try + { + GetSystemIpv4Gateway(proxyIp); + } + catch (Exception e) + { + eventLog.WriteEntry($"network changed but no gateway found: {e.Message}"); + } + + if (previousGatewayIp == gatewayIp && previousGatewayInterfaceIndex == gatewayInterfaceIndex) + { + // Only send on actual change, to prevent duplicate notifications (mostly + // harmless but can make debugging harder). + eventLog.WriteEntry($"network changed but gateway and interface stayed the same"); + return; + } + else if (gatewayIp == null) + { + SendConnectionStatusChange(ConnectionStatus.Reconnecting); + + // Stop capturing IPv4 traffic in order to prevent a routing loop in the TAP device. + // Redirect IPv4 traffic to the loopback interface instead to avoid leaking traffic when + // the network becomes available. + try + { + StopRoutingIpv4(); + RemoveIpv4TapRedirect(); + eventLog.WriteEntry($"stopped routing IPv4 traffic"); + } + catch (Exception e) + { + eventLog.WriteEntry($"failed to stop routing IPv4: {e.Message}", EventLogEntryType.Error); + } + return; + } + + eventLog.WriteEntry($"network changed - gateway is now {gatewayIp} on interface {gatewayInterfaceIndex}"); + + // Add the proxy escape route before capturing IPv4 traffic to prevent a routing loop in the TAP device. + try + { + AddOrUpdateProxyRoute(proxyIp, gatewayIp, gatewayInterfaceIndex); + eventLog.WriteEntry($"updated route to proxy"); + } + catch (Exception e) + { + eventLog.WriteEntry($"could not update route to proxy: {e.Message}"); + return; + } + + try + { + AddIpv4TapRedirect(); + StartRoutingIpv4(); + eventLog.WriteEntry($"refreshed IPv4 redirect"); + } + catch (Exception e) + { + eventLog.WriteEntry($"could not refresh IPv4 redirect: {e.Message}"); + return; + } + + // Send the status update now that the full-system VPN is connected. + SendConnectionStatusChange(ConnectionStatus.Connected); + + try + { + AddOrUpdateReservedSubnetBypass(gatewayIp, gatewayInterfaceIndex); + eventLog.WriteEntry($"updated LAN bypass routes"); + } + catch (Exception e) + { + // TODO: This isn't quite right: because we successfully updated + // the route to the proxy, the client *is* connected; it's + // just not "fully" connected, in the way we like. We + // should distinguish between "cannot reconnect right now, + // because no internet" and "cannot reconnect because + // netsh commands are failing". + eventLog.WriteEntry($"could not update LAN bypass routes: {e.Message}"); + return; + } + } + + // Writes the connection status to the pipe, if it is connected. + private void SendConnectionStatusChange(ConnectionStatus status) + { + if (pipe == null || !pipe.IsConnected) + { + eventLog.WriteEntry("Cannot send connection status change, pipe not connected.", EventLogEntryType.Error); + return; + } + ServiceResponse response = new ServiceResponse(); + response.action = ACTION_STATUS_CHANGED; + response.statusCode = (int)ErrorCode.Success; + response.connectionStatus = (int)status; + try + { + WriteResponse(response); + } + catch (Exception e) + { + eventLog.WriteEntry($"Failed to send connection status change: {e.Message}"); + } + } + + public string GetNetworkInfo() + { + return String.Join(", ", NetworkInterface.GetAllNetworkInterfaces() + .Select(a => this.GetAdapterInfo(a))); + } + + private string GetAdapterInfo(NetworkInterface adapter) + { + var numIpv4Gateways = adapter.GetIPProperties().GatewayAddresses + .Select(g => g.Address) + .Where(a => a.AddressFamily == AddressFamily.InterNetwork) + .Count(); + var numIpv6Gateways = adapter.GetIPProperties().GatewayAddresses + .Select(g => g.Address) + .Where(a => a.AddressFamily == AddressFamily.InterNetworkV6) + .Count(); + + return $"{adapter.Name} ({adapter.OperationalStatus}): " + ( + adapter.Supports(NetworkInterfaceComponent.IPv4) ? + $"{numIpv4Gateways} x ipv4 gateways" : + "ipv4 disabled") + ", " + ( + adapter.Supports(NetworkInterfaceComponent.IPv6) ? + $"{numIpv6Gateways} x ipv6 gateways" : + "ipv6 disabled"); + } + } + + [DataContract] + internal class ServiceRequest + { + [DataMember] + internal string action; + [DataMember] + internal Dictionary parameters; + } + + [DataContract] + internal class ServiceResponse + { + [DataMember] + internal string action; + [DataMember] + internal int statusCode; + [DataMember] + internal string errorMessage; + [DataMember] + internal int connectionStatus; + } + + public enum ErrorCode + { + Success = 0, + GenericFailure = 1 + } + + public enum ConnectionStatus + { + Connected = 0, + Disconnected = 1, + Reconnecting = 2 + } +} diff --git a/tools/OutlineService/OutlineService/OutlineService.csproj b/tools/OutlineService/OutlineService/OutlineService.csproj index 7a8947f723..4fac6eb2ea 100755 --- a/tools/OutlineService/OutlineService/OutlineService.csproj +++ b/tools/OutlineService/OutlineService/OutlineService.csproj @@ -1,92 +1,92 @@ - - - - - Debug - AnyCPU - {F39A4491-9868-4A71-9BE1-980FADE10AC9} - WinExe - OutlineService - OutlineService - v4.6.1 - 512 - true - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - - ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - Component - - - OutlineService.cs - - - - - - - - - - - OutlineService.cs - - - - - - + + + + + Debug + AnyCPU + {F39A4491-9868-4A71-9BE1-980FADE10AC9} + WinExe + OutlineService + OutlineService + v4.6.1 + 512 + true + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + + ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + Component + + + OutlineService.cs + + + + + + + + + + + OutlineService.cs + + + + + + \ No newline at end of file diff --git a/tools/OutlineService/OutlineService/OutlineService.resx b/tools/OutlineService/OutlineService/OutlineService.resx index 1a80cdd6e7..0129fa82a5 100755 --- a/tools/OutlineService/OutlineService/OutlineService.resx +++ b/tools/OutlineService/OutlineService/OutlineService.resx @@ -1,123 +1,123 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - False - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + \ No newline at end of file diff --git a/tools/OutlineService/OutlineService/Program.cs b/tools/OutlineService/OutlineService/Program.cs index bb9d7ee7dd..62d8c9b47d 100755 --- a/tools/OutlineService/OutlineService/Program.cs +++ b/tools/OutlineService/OutlineService/Program.cs @@ -1,70 +1,70 @@ -// Copyright 2018 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.ServiceProcess; -using System.Text; -using System.Threading.Tasks; - -namespace OutlineService -{ - static class Program - { - static void Main(string[] args) - { - if (!Environment.UserInteractive) - { - ServiceBase[] ServicesToRun; - ServicesToRun = new ServiceBase[] { new OutlineService() }; - ServiceBase.Run(ServicesToRun); - } - else - { - var service = new OutlineService(); - // To run as a service from the command-line: - // service.OnStart(null); - // System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); - if (args.Length < 2) - { - ShowUsageAndExit(); - } - switch (args[0]) - { - case "on": - service.ConfigureRouting(args[1], false); - break; - case "off": - if (args.Length < 3) - { - ShowUsageAndExit(); - } - service.ResetRouting(args[1], Int32.Parse(args[2])); - break; - default: - ShowUsageAndExit(); - break; - } - Console.WriteLine($"network config: {service.GetNetworkInfo()}"); - } - } - - static void ShowUsageAndExit() - { - Console.WriteLine("usage: on |off "); - Environment.Exit(1); - } - } -} // namespace OutlineService +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace OutlineService +{ + static class Program + { + static void Main(string[] args) + { + if (!Environment.UserInteractive) + { + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] { new OutlineService() }; + ServiceBase.Run(ServicesToRun); + } + else + { + var service = new OutlineService(); + // To run as a service from the command-line: + // service.OnStart(null); + // System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); + if (args.Length < 2) + { + ShowUsageAndExit(); + } + switch (args[0]) + { + case "on": + service.ConfigureRouting(args[1], false); + break; + case "off": + if (args.Length < 3) + { + ShowUsageAndExit(); + } + service.ResetRouting(args[1], Int32.Parse(args[2])); + break; + default: + ShowUsageAndExit(); + break; + } + Console.WriteLine($"network config: {service.GetNetworkInfo()}"); + } + } + + static void ShowUsageAndExit() + { + Console.WriteLine("usage: on |off "); + Environment.Exit(1); + } + } +} // namespace OutlineService diff --git a/tools/OutlineService/OutlineService/Properties/AssemblyInfo.cs b/tools/OutlineService/OutlineService/Properties/AssemblyInfo.cs index fc957bac77..20c3e92b6c 100755 --- a/tools/OutlineService/OutlineService/Properties/AssemblyInfo.cs +++ b/tools/OutlineService/OutlineService/Properties/AssemblyInfo.cs @@ -1,50 +1,50 @@ -// Copyright 2018 The Outline Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OutlineService")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("OutlineService")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f39a4491-9868-4a71-9be1-980fade10ac9")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OutlineService")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OutlineService")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f39a4491-9868-4a71-9be1-980fade10ac9")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tools/OutlineService/OutlineService/bin/Release/OutlineService.exe.config b/tools/OutlineService/OutlineService/bin/Release/OutlineService.exe.config index 6a19d29ee0..c37a182b77 100755 --- a/tools/OutlineService/OutlineService/bin/Release/OutlineService.exe.config +++ b/tools/OutlineService/OutlineService/bin/Release/OutlineService.exe.config @@ -1,21 +1,21 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/tools/OutlineService/OutlineService/packages.config b/tools/OutlineService/OutlineService/packages.config index 27e210f08a..5762754177 100755 --- a/tools/OutlineService/OutlineService/packages.config +++ b/tools/OutlineService/OutlineService/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file