(webSocket) Не могу подключиться к серверу с телефона. C#

Есть консольное приложение для сервера. Сервер запущен на VPS. Есть два устройства на одном wifi. Первое - это компьютер. С него спокойно могу зайти. Через unity билд для андроида внутри юнити. Второе это мой андроид. С него не могу подключиться к серверу.

await webSocket.ConnectAsync(new Uri("ws://45.143.93.104:8080"), CancellationToken.None);

Я пришёл к выводу, что мне нужен сертификат SSL/TLS, чтобы андроид пропустил соединение. Но я понятия не имею как его сделать. К тому же на ip. У меня же не домен...

Может кто-то дать совет? Обязателен ли этот сертификат? Других путей нет? Обязательно через wss потом делать?


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

Автор решения: Uranus

Android 9+ (Pie, API 28) блокирует все незашифрованные (ws://, http://) соединения по умолчанию. Это можно обойти, добавив разрешение в AndroidManifest.xml:

<application android:usesCleartextTraffic="true" />

Но это временное решение, так как Google активно продвигает wss:// и в будущем возможны более жесткие ограничения (как с http://). Лучше сразу использовать wss://, чтобы избежать проблем в будущем.

Если все-таки нужен wss://, то можно прокинуть WebSocket через Nginx с Let's Encrypt - тогда получите настоящий wss:// без проблем. Nginx принимает wss://, а сервер продолжает работать на ws:// внутри VPS.

Let's Encrypt не выдает сертификаты для IP-адресов, поэтому необходимо зарегистрировать домен и настроить его на свой сервер. Если хочется обойтись без этого, можно использовать самоподписанный сертификат. Android не доверяет самоподписанным сертификатам, поэтому придется вручную добавить его в каждое устройство. Говорят, можно настроить Android приложение чтобы оно доверяло самоподписанному сертификату, тогда не придется его добавлять на каждое устройство вручную. Но я не нашел инструкции для Unity, только вот эту: Trust my own self-signed certificate in local network using Android Native, Android Studio and retrofit.


Установка Nginx

sudo apt update
sudo apt install nginx -y

Проверяем, что он работает:

systemctl status nginx

Если не запущен, запускаем:

sudo systemctl enable nginx
sudo systemctl start nginx

Предположим, что ваше C#-приложение работает на порту 8080 и слушает ws://123.123.123.123:8080. Проверим, что сервер работает локально:

curl -i ws://localhost:8080

(примечание) В этом примере curl используется как быстрая проверка доступности порта, а не полноценный WebSocket-клиент. curl не поддерживает WebSocket по-настоящему, так что вы получите ошибку вроде:

HTTP/1.1 400 Bad Request

Это нормально, если сервер вообще отвечает - значит, порт открыт.

Если WebSocket-сервер отвечает на ws://123.123.123.123:8080, но не доступен на ws://localhost:8080, значит сервер слушает только внешний интерфейс. Используйте 0.0.0.0, чтобы сервер слушал все подключения.

Теперь настроим бесплатный сертификат от Let's Encrypt (замените example.com на ваш домен):

sudo apt install certbot python3-certbot-nginx -y
sudo certbot certonly --nginx -d example.com

Если у вас только IP (без домена), то Let's Encrypt не выдаст сертификат. В этом случае вам придется использовать самоподписанный сертификат (я объясню в конце).

Сертификаты будут сохранены в:

  • /etc/letsencrypt/live/example.com/fullchain.pem
  • /etc/letsencrypt/live/example.com/privkey.pem

Проверяем, что сертификат установлен:

sudo certbot renew --dry-run

Настройка Nginx как реверс-прокси для WebSocket

Используем механизм переключения протокола, описанный здесь: WebSocket proxying.

Создадим конфиг Nginx, который будет перенаправлять wss:// на ws://:

sudo nano /etc/nginx/sites-available/websocket

Добавляем конфигурацию (не забудьте заменить example.com на свой домен):

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8080/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
    }
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

Активируем конфигурацию:

sudo ln -s /etc/nginx/sites-available/websocket /etc/nginx/sites-enabled/

Проверяем синтаксис:

sudo nginx -t

Перезапускаем Nginx:

sudo systemctl restart nginx

Теперь вы можете подключаться к wss://example.com вместо ws://123.123.123.123:8080.

Проверка через wscat (клиент WebSocket):

npm install -g wscat
wscat -c wss://example.com

Если все работает нормально, вы должны увидеть соединение.


Что делать, если нет домена?

Можно сгенерировать самоподписанный сертификат:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/nginx-selfsigned.key \
-out /etc/ssl/certs/nginx-selfsigned.crt

Затем заменить конфиг Nginx:

ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;

Минусы: такой сертификат необходимо вручную добавить в приложение.


Можно ли подключить сертификат в C#-сервер без Nginx?

Можно настроить свой WebSocket-сервер так, чтобы он работал с сертификатом Let's Encrypt напрямую.

Устанавливаем Certbot и запускаем команду:

certbot certonly --standalone -d example.com

Сертификаты сохраняются в /etc/letsencrypt/live/example.com/fullchain.pem и privkey.pem. Преобразуем файлы pem в PFX:

openssl pkcs12 -export -out certificate.pfx -inkey privkey.pem -in fullchain.pem

Теперь можно загрузить сертификат SslStream:

var certificate = new X509Certificate2("certificate.pfx", "your_password");

var listener = new TcpListener(IPAddress.Any, 443);
listener.Start();

while (true)
{
    var client = await listener.AcceptTcpClientAsync();
    var sslStream = new SslStream(client.GetStream());
    await sslStream.AuthenticateAsServerAsync(certificate);
}

Если же вы используете ASP.NET Core с Kestrel, обратитесь к разделу SSL/TLS Protocols в документации.

Let's Encrypt выдает сертификаты сроком на 90 дней, так что нужно автоматизировать обновление. По расписанию выполнять команду:

certbot renew --quiet

Можно добавить это в cron (crontab -e):

0 0 * * 1 certbot renew --quiet && systemctl restart myapp

Плюсы:

  • Нет сторонних прокси

Минусы:

  • Нужно обновлять сертификаты вручную (или автоматизировать)
  • Сервер должен быть доступен снаружи (Certbot требует порт 80 или 443).
→ Ссылка