github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/nat/service_ics.go (about) 1 /* 2 * Copyright (C) 2019 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package nat 19 20 import ( 21 "net" 22 "strconv" 23 "strings" 24 "sync" 25 26 "github.com/mysteriumnetwork/node/utils" 27 "github.com/pkg/errors" 28 ) 29 30 const ( 31 enablePublicSharing = "$config.EnableSharing(0)" 32 enablePrivateSharing = "$config.EnableSharing(1)" 33 disableSharing = "$config.DisableSharing()" 34 propsForPublic = `$props.IsIcsPublic = 1;$props.IsIcsPrivate = 0;` 35 propsForPrivate = `$props.IsIcsPublic = 0;$props.IsIcsPrivate = 1;` 36 propsForDisable = `$props.IsIcsPublic = 0;$props.IsIcsPrivate = 0;` 37 ) 38 39 func getSharingScript(ifaceName, props, action string) string { 40 return `regsvr32 /s hnetcfg.dll; 41 $netShare = New-Object -ComObject HNetCfg.HNetShare; 42 $c = $netShare.EnumEveryConnection |? { $netshare.NetConnectionProps.Invoke($_).Name -eq "` + ifaceName + `" }; 43 $config = $netShare.INetSharingConfigurationForINetConnection.Invoke($c); 44 Try { 45 ` + action + ` 46 } Catch { 47 $guid = $netShare.NetConnectionProps.Invoke($c).Guid; 48 $props = Get-WmiObject -Class HNet_ConnectionProperties -Namespace "ROOT\microsoft\homenet" -Filter "__PATH like '%$guid%'";` + props + `$props.Put(); 49 ` + action + ` 50 }` 51 } 52 53 func getPublicSharingScript(ifaceName string) string { 54 return getSharingScript(ifaceName, propsForPublic, enablePublicSharing) 55 } 56 57 func getPrivateSharingScript(ifaceName string) string { 58 return getSharingScript(ifaceName, propsForPrivate, enablePrivateSharing) 59 } 60 61 func getDisableSharingScript(ifaceName string) string { 62 return getSharingScript(ifaceName, propsForDisable, disableSharing) 63 } 64 65 type serviceICS struct { 66 mu sync.Mutex 67 activeInternalIface string 68 remoteAccessStatus string 69 powerShell func(cmd string) ([]byte, error) 70 setICSAddresses func(config map[string]string) (map[string]string, error) 71 oldICSConfig map[string]string 72 } 73 74 func (ics *serviceICS) disableICSAllInterfaces() error { 75 // Filter out invalid media (where NETCON_MEDIATYPE = NCM_NONE) because it is not an instance of INetConnection 76 // and will throw a cast error when attempting to `INetSharingConfigurationForINetConnection($conn)` 77 // Enum: https://docs.microsoft.com/en-us/windows/desktop/api/netcon/ne-netcon-tagnetcon_mediatype 78 _, err := ics.powerShell(`regsvr32 /s hnetcfg.dll; 79 $mgr = New-Object -ComObject HnetCfg.HNetShare; 80 filter IsValidMediaType { if ($mgr.NetConnectionProps($_).MediaType -gt 0) { $_ } }; 81 $connections = $mgr.EnumEveryConnection | IsValidMediaType; 82 foreach ($conn in $connections) { 83 $mgr.INetSharingConfigurationForINetConnection($conn).DisableSharing() 84 }; 85 `) 86 return err 87 } 88 89 // Enable enables internet connection sharing for the public interface. 90 func (ics *serviceICS) Enable() error { 91 // We have to clean up ICS configuration for all interfaces to apply our configuration. 92 // It is possible to have ICS configured only for single pair of interfaces. 93 if err := ics.disableICSAllInterfaces(); err != nil { 94 return errors.Wrap(err, "failed to cleanup ICS before Enabling") 95 } 96 97 if err := ics.enableRemoteAccessService(); err != nil { 98 return errors.Wrap(err, "failed to Enable RemoteAccess service") 99 } 100 101 ifaceName, err := ics.getPublicInterfaceName() 102 if err != nil { 103 return errors.Wrap(err, "failed to get public interface name") 104 } 105 106 _, err = ics.powerShell(getPublicSharingScript(ifaceName)) 107 return errors.Wrap(err, "failed to enable internet connection sharing") 108 } 109 110 func (ics *serviceICS) enableRemoteAccessService() error { 111 status, err := ics.powerShell(`(Get-WmiObject Win32_Service -filter "Name='RemoteAccess'").StartMode`) 112 if err != nil { 113 return errors.Wrap(err, "failed to get RemoteAccess service startup type") 114 } 115 116 statusStringified := strings.ToLower(strings.TrimSpace(string(status))) 117 if statusStringified == "auto" { 118 ics.remoteAccessStatus = "automatic" 119 } else { 120 ics.remoteAccessStatus = statusStringified 121 } 122 123 if _, err := ics.powerShell("Set-Service -Name RemoteAccess -StartupType automatic"); err != nil { 124 return errors.Wrap(err, "failed to set RemoteAccess service startup type to automatic") 125 } 126 127 _, err = ics.powerShell("Start-Service -Name RemoteAccess") 128 return errors.Wrap(err, "failed to start RemoteAccess service") 129 } 130 131 // Setup enables internet connection sharing for the local interface. 132 func (ics *serviceICS) Setup(opts Options) (rules []interface{}, err error) { 133 ics.mu.Lock() 134 defer ics.mu.Unlock() 135 136 ip := incrementIP(opts.VPNNetwork.IP) 137 ics.oldICSConfig, err = ics.setICSAddresses(map[string]string{ 138 "ScopeAddress": ip.String(), 139 "ScopeAddressBackup": ip.String(), 140 "StandaloneDhcpAddress": ip.String()}) 141 if err != nil { 142 return nil, errors.Wrap(err, "failed to set ICS IP-address range") 143 } 144 145 internalInterface, err := ics.getInternalInterfaceName() 146 if err != nil { 147 return nil, errors.Wrap(err, "failed to find suitable interface") 148 } 149 150 _, err = ics.powerShell(getPrivateSharingScript(internalInterface)) 151 if err != nil { 152 return nil, errors.Wrap(err, "failed to enable internet connection sharing for internal interface") 153 } 154 155 ics.activeInternalIface = internalInterface 156 return nil, nil 157 } 158 159 // Del disables internet connection sharing for the local interface. 160 func (ics *serviceICS) Del([]interface{}) error { 161 ics.mu.Lock() 162 defer ics.mu.Unlock() 163 164 ifaceName, err := ics.getInternalInterfaceName() 165 if err != nil { 166 return errors.Wrap(err, "failed to find suitable interface") 167 } 168 169 _, err = ics.powerShell(getDisableSharingScript(ifaceName)) 170 if err != nil { 171 return errors.Wrap(err, "failed to disable internet connection sharing for internal interface") 172 } 173 174 ics.activeInternalIface = "" 175 return nil 176 } 177 178 // Disable disables internet connection sharing for the public interface. 179 func (ics *serviceICS) Disable() error { 180 result := utils.ErrorCollection{} 181 182 if _, err := ics.setICSAddresses(ics.oldICSConfig); err != nil { 183 return errors.Wrap(err, "failed to revert ICS IP-address range") 184 } 185 186 if ics.activeInternalIface != "" { 187 if err := ics.Del(nil); err != nil { 188 result.Add(errors.Wrap(err, "Failed to cleanup internet connection sharing for internal interface")) 189 } 190 } 191 192 _, err := ics.powerShell("Set-Service -Name RemoteAccess -StartupType " + ics.remoteAccessStatus) 193 if err != nil { 194 result.Add(errors.Wrap(err, "failed to revert RemoteAccess service startup type")) 195 } 196 197 ifaceName, err := ics.getPublicInterfaceName() 198 if err != nil { 199 result.Add(errors.Wrap(err, "failed to get public interface name")) 200 } 201 202 _, err = ics.powerShell(getDisableSharingScript(ifaceName)) 203 if err != nil { 204 result.Add(errors.Wrap(err, "failed to disable internet connection sharing")) 205 } 206 207 err = ics.disableICSAllInterfaces() 208 if err != nil { 209 result.Add(errors.Wrap(err, "failed to cleanup ICS before Enabling")) 210 } 211 212 return result.Error() 213 } 214 215 func (ics *serviceICS) getPublicInterfaceName() (string, error) { 216 out, err := ics.powerShell(`Get-WmiObject -Class Win32_IP4RouteTable | where { $_.destination -eq '0.0.0.0' -and $_.mask -eq '0.0.0.0'} | foreach { $_.InterfaceIndex }`) 217 if err != nil { 218 return "", errors.Wrap(err, "failed to get interface from the default route") 219 } 220 ifaceID, err := strconv.Atoi(strings.TrimSpace(string(out))) 221 if err != nil { 222 return "", errors.Wrap(err, "failed to parse interface ID") 223 } 224 225 ifaces, err := net.Interfaces() 226 if err != nil { 227 return "", errors.Wrap(err, "failed to get a list of network interfaces") 228 } 229 230 for _, iface := range ifaces { 231 if iface.Index == ifaceID { 232 return iface.Name, nil 233 } 234 } 235 236 return "", errors.New("interface not found") 237 } 238 239 func (ics *serviceICS) getInternalInterfaceName() (string, error) { 240 out, err := ics.powerShell(`Get-WmiObject Win32_NetworkAdapter | Where-Object {$_.ServiceName -eq "tap0901"} | foreach { $_.NetConnectionID }`) 241 if err != nil { 242 return "", errors.Wrap(err, "failed to detect internal interface name") 243 } 244 245 ifaceName := strings.TrimSpace(string(out)) 246 if len(ifaceName) == 0 { 247 return "", errors.New("interface not found") 248 } 249 250 return ifaceName, nil 251 } 252 253 func incrementIP(ip net.IP) net.IP { 254 dup := make(net.IP, len(ip)) 255 copy(dup, ip) 256 for j := len(dup) - 1; j >= 0; j-- { 257 dup[j]++ 258 if dup[j] > 0 { 259 break 260 } 261 } 262 return dup 263 }