PY-59608: Support `wslproxy` when routing is broken by VPN.

fixup! PY-59608: Support ``wslproxy`` when routing is broken by VPN.

When user runs VPN with default gateway pointing to the VPN server and configures interface weight, we can't connect to WSL using its egress IP (traffic to ``172.0.0.0/8`` goes to VPN instead of WSL).

With ``wsl.proxy.connect.localhost`` enabled in registry we bind ``wslproxy`` to ``127.0.0.1`` which could be used in latest WSL2 to connect from Windows to Linux. It is a little bit slow, but still works.

fixup! PY-59608: Support ``wslproxy`` when routing is broken by VPN.
PY-59608: Support ``wslproxy`` when routing is broken by VPN.

When user runs VPN with default gateway pointing to the VPN server and configures interface weight, we can't connect to WSL using its egress IP (traffic to ``172.0.0.0/8`` goes to VPN instead of WSL).

With ``wsl.proxy.connect.localhost`` enabled in registry we bind ``wslproxy`` to ``127.0.0.1`` which could be used in latest WSL2 to connect from Windows to Linux. It is a little bit slow, but still works.


Co-authored-by: Vladimir Lagunov <vladimir.lagunov@jetbrains.com>

Merge-request: IJ-MR-105285
Merged-by: Ilya Kazakevich <ilya.kazakevich@jetbrains.com>

GitOrigin-RevId: 74b5ad4df9c5e95ca4ef53a465f07855a92aa324
This commit is contained in:
Ilya.Kazakevich
2023-03-24 19:15:50 +00:00
committed by intellij-monorepo-bot
parent 394380290d
commit eb7bca7328
5 changed files with 40 additions and 11 deletions

Binary file not shown.

View File

@@ -12,7 +12,8 @@
// See svg file and wslproxy_test_client.py
// When started, prints egress (eth0) IP addr as 4 bytes. Then, 2 bytes of ingress (loopback) port.
// When started, prints egress (eth0) IP addr as 4 bytes (or it might use 127.0.0.1 which is possible for WSL2, see args)
// Then, 2 bytes of ingress (loopback) port.
// App running on WSL connects to this port.
// Tool then opens egress (eth0) port and prints it as 2 bytes.
// Windows client connects to it and talks to WSL app connected to the ingress port.
@@ -20,6 +21,7 @@
// Threads are unbound, but it should not be a problem unless you create lots of connections
// To use 127.0.0.1 instead of eth0 address (for cases like VPN on Windows side) provide arg "--loopback"
// Will listen egress in this port
#define JB_EGRESS_INTERFACE "eth0"
@@ -186,9 +188,10 @@ _Noreturn static void *jb_listen_ingress(const int *p_ingress_srv_sock_fd) {
static int g_ingress_srv_sock_fd;
int main(void) {
g_egress_ip = jb_get_wsl_public_ip();
int main(int argc, char **argv) {
// '--loopback' means use 127.0.0.1 as egress IP
g_egress_ip = (argc > 1 && strcmp(&argv[1][0], "--loopback") == 0) ? htonl(INADDR_LOOPBACK)
: jb_get_wsl_public_ip();
// IP address
write(STDOUT_FILENO, &g_egress_ip, sizeof g_egress_ip);

View File

@@ -11,7 +11,7 @@ def read_stderr(stderr):
print(f"STDERR: {text}")
process = subprocess.Popen("./wslproxy", shell=False,
process = subprocess.Popen(["./wslproxy", "--loopback"], shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)

View File

@@ -153,6 +153,9 @@
<registryKey key="wsl.obtain.windows.host.ip.alternatively" os="windows" defaultValue="true" restartRequired="true"
description="Obtain Windows host machine IP alternatively (not using the recommended 'cat /etc/resolv.conf | grep nameserver')."/>
<registryKey key="wsl.proxy.connect.localhost" os="windows" defaultValue="false" restartRequired="false"
description="Connect to 127.0.0.1 on WSLProxy instead of public WSL IP which might be inaccessible due to routing issues"/>
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl" key="file" physical="true"/>
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.jar.JarFileSystemImpl" key="jar" physical="true"/>
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.ex.temp.TempFileSystem" key="temp" physical="true"/>

View File

@@ -3,10 +3,10 @@ package com.intellij.execution.wsl
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.util.registry.Registry
import io.ktor.network.selector.ActorSelectorManager
import io.ktor.network.sockets.aSocket
import io.ktor.network.sockets.openReadChannel
import io.ktor.network.sockets.openWriteChannel
import io.ktor.network.sockets.*
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.ByteWriteChannel
import io.ktor.utils.io.close
@@ -36,6 +36,22 @@ import kotlin.coroutines.coroutineContext
*/
class WslProxy(distro: AbstractWslDistribution, private val applicationPort: Int) : Disposable {
private companion object {
/**
* Server might not be opened yet. Since non-blocking Ktor API doesn't wait for it but throws exception instead, we retry
*/
private tailrec suspend fun TcpSocketBuilder.tryConnect(host: String, port: Int, attemptRemains: Int = 10): Socket {
try {
return connect(host, port)
}
catch (e: IOException) {
if (attemptRemains <= 0) throw e
thisLogger().warn("Can't connect to $host $port , will retry", e)
delay(100)
}
return tryConnect(host, port, attemptRemains - 1)
}
suspend fun connectChannels(source: ByteReadChannel, dest: ByteWriteChannel) {
val buffer = ByteBuffer.allocate(4096)
while (coroutineContext.isActive) {
@@ -97,7 +113,8 @@ class WslProxy(distro: AbstractWslDistribution, private val applicationPort: Int
private suspend fun readPortFromChannel(channel: ByteReadChannel): Int = readToBuffer(channel, 2).short.toUShort().toInt()
init {
val wslCommandLine = runBlocking { distro.getTool("wslproxy") }
val args = if (Registry.`is`("wsl.proxy.connect.localhost")) arrayOf("--loopback") else emptyArray()
val wslCommandLine = runBlocking { distro.getTool("wslproxy", *args) }
val process = Runtime.getRuntime().exec(wslCommandLine.commandLineString)
val log = Logger.getInstance(WslProxy::class.java)
@@ -136,10 +153,16 @@ class WslProxy(distro: AbstractWslDistribution, private val applicationPort: Int
private suspend fun clientConnected(linuxEgressPort: Int) {
val winToLin = scope.async {
aSocket(ActorSelectorManager(scope.coroutineContext)).tcp().connect(wslLinuxIp, linuxEgressPort)
thisLogger().info("Connecting to WSL: $wslLinuxIp:$linuxEgressPort")
val socket = aSocket(ActorSelectorManager(scope.coroutineContext)).tcp().tryConnect(wslLinuxIp, linuxEgressPort)
thisLogger().info("Connected to WSL")
socket
}
val winToWin = scope.async {
aSocket(ActorSelectorManager(scope.coroutineContext)).tcp().connect("127.0.0.1", applicationPort)
thisLogger().info("Connecting to app: $127.0.0.1:$applicationPort")
val socket = aSocket(ActorSelectorManager(scope.coroutineContext)).tcp().tryConnect("127.0.0.1", applicationPort)
thisLogger().info("Connected to app")
socket
}
scope.launch {
val winToLinSocket = winToLin.await()