Как подключиться к приложению, установленному на хосте, из WSL2?

Как из WSL2 с установленной Ubuntu 18 подключиться к Windows-приложению, установленому хосте?

Когда из командной строки Windows я выполняю следующую команду:

telnet 127.0.0.1 9876

я получаю запись в логе, что установлено соединение.

Когда я делаю то же самое из терминала WSL Ubuntu, соединение не устанавливается.

Пробовал IP 127.0.1.1, но он тоже не работает. Как соединиться из WSL c хостом?


Ответы (1 шт):

Автор решения: Pak Uula

Может кому-то будет полезно.

UPDATE

По-умолчанию WSL2 поднимает сеть в режиме NAT, в этом режиме WSL2 функционирует в изолированной виртуальной машине, доступ в сеть через виртуальный vEthernet интерфейс.

Начиная с Windows 11 22H2 в WSL2 есть поддержка сетевого режима mirrored. В этом режиме все интерфейсы Windows транслированы внутрь WSL2, в том числе Localhost, а маршрут по-умолчанию строится через шлюз Windows.

Для этого создайте файл C:\Users\<user>\.wslconfig

[wsl2]
networkingMode = mirrored

и перезагрузите WSL2

wsl --shutdown

В режиме mirrored WSL2 loopback тождественнен Windows loopback.

  1. Я запустил echo server в Windows 127.0.0.1, из WSL2 делаю netcat. Лог сервера
Serving on ('127.0.0.1', 9876)
Connection from ('127.0.0.1', 45906)
Received: hello
Connection closed: ('127.0.0.1', 45906)

Видно, что соединение открывается с loopback интерфейса Windows.

  1. В обратную сторону тоже работает. В WSL2
python3 echo-server.py

В Windows

PS> Test-NetConnection -ComputerName 127.0.0.1 -Port 9876


ComputerName     : 127.0.0.1
RemoteAddress    : 127.0.0.1
RemotePort       : 9876
InterfaceAlias   : Loopback Pseudo-Interface 1
SourceAddress    : 127.0.0.1
TcpTestSucceeded : True

Лог сервера

Serving on ('127.0.0.1', 9876)
Connection from ('127.0.0.1', 51736)
Connection closed: ('127.0.0.1', 51736)

ИТОГО. В режиме mirrored сетевые соединения между WSL2 и Windows работают в обе стороны.

Первый ответ

  1. WSL2 выполняет Linux в виртуальной машине. Ядро, выполняемое в этой виртуалке, создает свой набор сетевых интерфейсов, не связанный с сетевыми интерфейсами в хосте Windows.

  2. Ядро WSL2 подпилено таким образом, что серверные порты, которые открыты в WSL2 localhost, автоматически форвардятся в Windows [1]. То есть, если вы в WSL2 запустите какой-то сервер на порту 9876, то к нему можно будет подключиться в Windows на localhost:9876

  3. НО! в обратную сторону это не работает. Серверные порты, открытые в Windows, не видны в WSL2.

Вам нужно каким-то образом транслировать искомый сервис 9876 на сетевой интерфейс WSL2, который виден в WSL2 по адресу

ip route show default | awk '{print $3}'

Вариант 1

Если это ваш сервис, измените его так, чтобы он был открыт на 0.0.0.0. Тогда он будет привязан ко всем интерфейсам в системе, в том числе к виртуальному интерфейсу WSL.

Вариант 2

Сразу признаюсь, у меня не заработало

$wslIp = (Get-NetIPAddress -InterfaceAlias "vEthernet (WSL (Hyper-V firewall))" -AddressFamily IPv4).IPAddress
netsh interface portproxy add v4tov4 listenaddress=$wslIp listenport=9876 connectaddress=127.0.0.1 connectport=9876

интернеты уверяют, что сервис iphlpsvc откроет прокси на $wslIp:9876 для 127.0.0.1:9876, но, повторюсь, у меня не заработало, и я не стал разбираться.

Вариант 3

Поднимите прокси, который будет обмениваться трафиком между $wslIp:9876 и 127.0.0.1:9876

Например, вот скрипт на Python

import psutil
import asyncio
import sys

def get_wsl_address():
    """
    Returns IPv4 address of the WSL interface
    """
    for iface_name, addrs in psutil.net_if_addrs().items():
        if "WSL" in iface_name:
            for addr in addrs:
                if addr.family == 2: # AF_INET
                    return addr.address
    return None

async def handle_proxy(client_reader, client_writer, port):
    """
    Handles a proxy connection between the client (WSL) and the server (localhost).

    Exchanges traffic between WSL endpoint and `localhost:<port>`
    
    Parameters:

      - client_reader: The reader for the client connection (from WSL).
      - client_writer: The writer for the client connection (to WSL).
      - port: The port to connect to on localhost.
    """
    peer_name = client_writer.get_extra_info('peername')
    print(f"New connection from {peer_name}")
    try:
        # Connect to localhost
        server_reader, server_writer = await asyncio.open_connection('127.0.0.1', port)

        async def forward(reader, writer):
            try:
                while True:
                    data = await reader.read(4096)
                    if not data:
                        break
                    writer.write(data)
                    await writer.drain()
            except Exception:
                pass
            finally:
                writer.close()
                await writer.wait_closed()

        # Bidirectional forwarding
        await asyncio.gather(
            forward(client_reader, server_writer),
            forward(server_reader, client_writer)
        )
    except Exception:
        pass
    finally:
        print(f"Closing connection from {peer_name}")
        server_writer.close()
        await server_writer.wait_closed()
        client_writer.close()
        await client_writer.wait_closed()

async def start_proxy(port):
    """
    Starts Windows-localhost-to-WSL proxying.

    Opens a server socket at WSL vEthernet interface and forwards traffic to `localhost:<port>`.
    The same port is used at WSL vEthernet interface.
    
    Parameter:
      - port: The port to connect to on localhost.
    """
    wsl_addr = get_wsl_address()
    if not wsl_addr:
        print("WSL address not found")
        return

    server = await asyncio.start_server(
        lambda r, w: handle_proxy(r, w, port),
        wsl_addr, port
    )
    async with server:
        await server.serve_forever()

if __name__ == "__main__":
    port = int(sys.argv[1]) if len(sys.argv) > 1 else None
    if port is not None:
        asyncio.run(start_proxy(port))
    else:
        print("Usage: python proxy.py <port>")
        sys.exit(1)

Пример

В Windows я запустил простой echo server

import asyncio

async def handle_client(reader, writer):
    addr = writer.get_extra_info('peername')
    print(f'Client connected: {addr}')
    try:
        while True:
            data = await reader.readline()
            if not data:
                break
            message = data.decode().rstrip()
            print(f'Message from client {addr}: {message}')
            writer.write(data)
            await writer.drain()
    except Exception as e:
        print(f'Error with client {addr}: {e}')
    finally:
        print(f'Client disconnected: {addr}')
        writer.close()
        await writer.wait_closed()

async def main():
    server = await asyncio.start_server(handle_client, '127.0.0.1', 9876)
    addr = server.sockets[0].getsockname()
    print(f'Server started on {addr}')
    async with server:
        await server.serve_forever()

if __name__ == '__main__':
    asyncio.run(main())
python .\echo-server.py

Затем запустил прокси

python .\proxy.py 9876

В WSL запустил netcat

echo "Hello!" | nc -N $(ip route show default | awk '{print $3}') 9876

Логи:

  • netcat -- ничего (опция -N сразу завершает, не печатает ответ сервера)
  • proxy
    New connection from ('172.27.157.123', 47952)
    Closing connection from ('172.27.157.123', 47952)
    
    Адрес 172.27.157.123 - это адрес моей убунты в WSL
  • эхо-сервер
    Server started on ('127.0.0.1', 9876)
    Client connected: ('127.0.0.1', 59614)
    Message from client ('127.0.0.1', 59614): Hello!
    Client disconnected: ('127.0.0.1', 59614)
    
    здесь порт - это клиентский порт прокси, а сообщение Hello! переслано от WSL.

Работает.


[1]: при условии, что в .wslconfig установлена опция localhostForwarding (по-умолчанию установлена)

→ Ссылка