Я хочу поделиться опытом создания универсального прокси-сервера на C#, который поддерживает не только HTTP/HTTPS, но и FTP-протокол с автоматической подстановкой учетных данных. Зачем это нужно? Представьте ситуацию: вам нужно работать с FTP-сервером через корпоративный прокси, но стандартные средства не поддерживают автоматическую аутентификацию. Или вы хотите иметь полный контроль над трафиком между клиентом и FTP-сервером. Мой прокси-сервер решает эти задачи.

Почему именно C# и FTP?

Выбор C# обусловлен несколькими факторами:

  • Мощная работа с сетевыми потоками (NetworkStreamTcpClient)

  • Простота реализации многопоточности с async/await

  • Кроссплатформенность (.NET Core/.NET 5+)

  • НУ И ПОТОМУ ЧТО Я НА НЁМ ПИШУ :)

FTP же был выбран потому, что это один из немногих протоколов, где проксирование требует не просто туннелирования, а анализа и подмены команд.

FTP подключение брал с сервера 1gb ru одного моего заказчика.

Архитектура решения

ой прокси-сервер работает на локальном порту (например, 8888) и обрабатывает три типа запросов:

  1. CONNECT метод — для HTTPS трафика

  2. FTP запросы — с автоматической аутентификацией

  3. Обычные HTTP запросы — прямое туннелирование

public class FullProxyServer
{
    private readonly TcpListener _listener;
    private readonly ConcurrentDictionary<string, string> _ftpCredentials;
    
    public FullProxyServer(int proxyPort, string ftpServer, string ftpUser, string ftpPassword)
    {
        _listener = new TcpListener(IPAddress.Loopback, proxyPort);
        _ftpCredentials = new ConcurrentDictionary<string, string>();
        _ftpCredentials.TryAdd(ftpServer, $"{ftpUser}:{ftpPassword}");
    }
}

Основные компоненты

1. Определение типа запроса

Первый шаг — понять, с каким запросом мы имеем дело:

string request = Encoding.ASCII.GetString(buffer, 0, bytesRead);

if (request.StartsWith("CONNECT"))
{
    await HandleConnectRequest(clientStream, request, id);
}
else if (request.Contains("FTP") || request.Contains("ftp"))
{
    await HandleFtpRequest(clientStream, buffer, bytesRead, id);
}
else
{
    await TunnelConnection(clientStream, buffer, bytesRead, id);
}

2. Обработка HTTPS (CONNECT метод)

Для HTTPS мы просто устанавливаем туннель между клиентом и целевым сервером:

private async Task HandleConnectRequest(NetworkStream clientStream, string request, string id)
{
    var parts = connectLine.Split(' ');
    string host = targetParts[0];
    int port = int.Parse(targetParts[1]);
    
    var targetClient = new TcpClient();
    await targetClient.ConnectAsync(host, port);
    
    string response = "HTTP/1.1 200 Connection Established\r\n\r\n";
    await clientStream.WriteAsync(responseBytes);
    
    await TunnelBidirectional(clientStream, targetStream, id);
}

3. Самое интересное: FTP с подстановкой пароля

Сердце моего решения — автоматическая аутентификация на FTP-сервере:

private async Task FtpTunnelWithAuth(NetworkStream clientStream, NetworkStream ftpStream,
                                      string username, string password, string id)
{
    bool authenticated = false;
    
    // Перехватываем ответы FTP-сервера
    if (!authenticated && response.Contains("331")) // Запрос пароля
    {
        string passCmd = $"PASS {password}\r\n";
        await ftpStream.WriteAsync(passBytes);
    }
    
    // Перехватываем команды клиента
    if (!authenticated && command.ToUpper().StartsWith("USER"))
    {
        // Подменяем логин на наш
        string userCmd = $"USER {username}\r\n";
        await ftpStream.WriteAsync(userBytes);
    }
}

4. Двунаправленное туннелирование

Для эффективной передачи данных я использую асинхронное копирование потоков:

private async Task TunnelBidirectional(NetworkStream clientStream, NetworkStream targetStream, string id)
{
    var cts = new CancellationTokenSource();
    
    var clientToTarget = CopyDataAsync(clientStream, targetStream, id, "C->T", cts.Token);
    var targetToClient = CopyDataAsync(targetStream, clientStream, id, "T->C", cts.Token);
    
    await Task.WhenAny(clientToTarget, targetToClient);
    cts.Cancel();
}

Настройка и запуск

Вот как настроить и запустить сервер:

class Program
{
    static async Task Main(string[] args)
    {
        var proxy = new FullProxyServer(
            proxyPort: 8888,
            ftpServer: "ftp.example.com",
            ftpUser: "my_username",
            ftpPassword: "my_password"
        );
        
        await proxy.StartAsync();
    }
}

Настройка клиентских приложений

Для браузера:

  • HTTP прокси: 127.0.0.1:8888

  • HTTPS прокси: 127.0.0.1:8888

  • FTP прокси: 127.0.0.1:8888

Для FTP-клиента (например, FileZilla):

  • Тип прокси: Generic Proxy

  • Хост: 127.0.0.1

  • Порт: 8888

Как это работает на практике

Допустим, вы подключаетесь к FTP-серверу через обычный FTP-клиент:

  1. Клиент отправляет USER anonymous

  2. Прокси перехватывает команду и заменяет на USER my_username

  3. Сервер отвечает 331 Password required

  4. Прокси автоматически отправляет PASS my_password

  5. Сервер отвечает 230 Login successful

  6. Клиент даже не догадывается о подмене!

Отладка и мониторинг

Мой прокси выводит подробные логи всех операций:

[abc12345] FTP запрос
[abc12345] ->FTP: USER anonymous\r\n
[abc12345] FTP->: 331 User name okay, need password\r\n
[abc12345] Автоматическая отправка пароля
[abc12345] FTP->: 230 User logged in\r\n
[abc12345] Аутентификация успешна

Заключение

В итоге у меня получился легковесный, но мощный прокси-сервер на C#, который:

  • Поддерживает HTTP, HTTPS и FTP

  • Автоматически подставляет учетные данные для FTP

  • Легко расширяется под новые протоколы

  • Работает асинхронно и эффективно использует ресурсы

Весь код помещается в один файл, его легко модифицировать под свои нужды. Я использую этот прокси для обхода ограничений корпоративной сети и для автоматизации FTP-операций.

Плюсы решения:

  • Полный контроль над трафиком

  • Автоматическая аутентификация

  • Простота настройки

  • Кроссплатформенность

Кому нужно, вот проект на git