Перфорирование отверстий UDP. Говорить серверу с клиентом - программирование
Подтвердить что ты не робот

Перфорирование отверстий UDP. Говорить серверу с клиентом

Я много читал о том, как реализовать перфорирование отверстий UDP, но по какой-то причине я не могу заставить его работать.

Для тех, кто не знаком с тем, что здесь происходит с отверстием дыры udp, является мое собственное определение:

Цель состоит в том, чтобы иметь возможность передавать данные между двумя клиентами (Клиент А и клиент B) с помощью сервера. Таким образом, клиент A подключается к серверу и отправляет его информацию. Клиент B делает то же самое. Сервер имеет необходимую информацию, чтобы клиент A мог отправлять данные клиенту B и наоборот. Поэтому сервер предоставляет эту информацию обоим клиентам. Как только у обоих клиентов есть эта информация друг о друге, можно начать отправку и получение данных между этими клиентами без помощи сервера.

Моя цель состоит в том, чтобы иметь возможность делать то, что я только что описал (ударопробивание отверстий). Прежде чем это сделать, я думаю, что будет полезно подключиться с сервера к клиенту. Для этого я планирую отправить серверу информацию о клиенте. Как только сервер получит эту информацию, попытайтесь подключиться к клиенту с нуля. Как только я смогу выполнить это, у меня должно получиться все, что мне нужно, чтобы начать реалистичную перфорирование дыры.

Вот как я создал вещи:

enter image description here

Верхний маршрутизатор имеет сервер и нижний маршрутизатор, подключенные к портам локальной сети. Нижний маршрутизатор (NAT) подключается к верхнему маршрутизатору через порт WAN. И клиентский компьютер подключается к нижнему маршрутизатору к одному из его LAN-портов.

Таким образом, в этом случае клиент может видеть сервер, но сервер не может видеть клиента.

Итак, алгоритм, который я сделал в псевдокоде:

  • Клиент подключается к серверу.
  • Клиент отправляет на сервер некоторые пакеты UDP, чтобы открыть некоторые порты в NAT
  • Отправлять на сервер информацию о том, какие порты клиент прослушивает.
  • Как только сервер получит эту информацию, попытайтесь подключиться к клиенту с нуля.

Вот реализация в коде:

Сервер:

static void Main()
{     
    /* Part 1 receive data from client */
    UdpClient listener = new UdpClient(11000);
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
    string received_data;
    byte[] receive_byte_array = listener.Receive(ref groupEP);       
    received_data = Encoding.ASCII.GetString(receive_byte_array, 0, receive_byte_array.Length);

    // get info
    var ip = groupEP.Address.ToString();
    var port = groupEP.Port;

    /* Part 2 atempt to connect to client from scratch */
    // now atempt to send data to client from scratch once we have the info       
    Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    IPEndPoint endPointClient = new IPEndPoint(IPAddress.Parse(ip), port);
    sendSocket.SendTo(Encoding.ASCII.GetBytes("Hello"), endPointClient);
}

Клиент:

static void Main(string[] args)
{
    /* Part 1 send info to server */
    Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,  ProtocolType.Udp);
    IPAddress send_to_address = IPAddress.Parse("192.168.0.132");
    IPEndPoint sending_end_point = new IPEndPoint(send_to_address, 11000);
    sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);

    // get info
    var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];

    /* Part 2 receive data from server */
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
    byte[] buffer = new byte[1024];
    sending_socket.Receive(buffer);
} 

По какой-то причине он работал несколько раз! Он работает, когда клиент успешно получает данные на линии: sending_socket.Receive(buffer);

Примечания: Если на сервере во второй части я использовал переменную экземпляра listner вместо создания новой переменной sendSocket и отправил байты через эту переменную, клиент сможет принимать отправленные данные. Помните, что вторая часть сервера будет реализована вторым клиентом B, поэтому я снова инициализирую переменные с нуля...


Изменить:

Вот другой способ взглянуть на ту же проблему. Когда я инициализирую новый объект вместо того, чтобы использовать тот же объект, клиент не получает ответ.

У меня есть объект типа UdpClient. Я могу отправить данные с этим объектом другому партнеру. Если я создаю другой объект того же типа с теми же свойствами и попытаюсь отправить данные, это не сработает! Мне может быть недостает инициализации некоторых переменных. Я могу установить частные переменные с отражением, поэтому у меня не должно быть проблем. в любом случае здесь находится код сервера:

public static void Main()
{
    // wait for client to send data
    UdpClient listener = new UdpClient(11000);
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);        
    byte[] receive_byte_array = listener.Receive(ref groupEP);

    // connect so that we are able to send data back
    listener.Connect(groupEP);

    byte[] dataToSend = new byte[] { 1, 2, 3, 4, 5 };

    // now let atempt to reply back

    // this part does not work!
    UdpClient newClient = CopyUdpClient(listener, groupEP);
    newClient.Send(dataToSend, dataToSend.Length);

    // this part works!
    listener.Send(dataToSend, dataToSend.Length);
}

static UdpClient CopyUdpClient(UdpClient client, IPEndPoint groupEP)
{
    var ip = groupEP.Address.ToString();
    var port = groupEP.Port;
    var newUdpClient = new UdpClient(ip, port);
    return newUdpClient;
}

клиентский код в основном отправляет данные на сервер, а затем ждет ответа:

    string ipOfServer = "192.168.0.132";
    int portServerIsListeningOn = 11000;

    // send data to server
    Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    IPAddress send_to_address = IPAddress.Parse(ipOfServer);
    IPEndPoint sending_end_point = new IPEndPoint(send_to_address, portServerIsListeningOn);
    sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);

    // get info
    var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];

    // now wait for server to send data back
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
    byte[] buffer = new byte[1024];
    sending_socket.Receive(buffer); // <----- keeps waiting in here :(

обратите внимание, что клиент находится за маршрутизатором (NAT), иначе у меня не будет этой проблемы. Причина, по которой я хочу скопировать udpClient, заключается в том, что я могу отправить эту переменную на другой компьютер, другой компьютер для отправки данных клиенту.

Итак, мой вопрос, почему исходный объект listener способен отправлять данные, но newClient не может? Клиент продолжает ждать в строке sending_socket.Receive(buffer); даже после того, как сервер выполнит строку: newClient.Send(dataToSend, dataToSend.Length);. клиент успешно получает данные, когда слушатель отправляет данные, но не newClient. Почему это происходит, если обе переменные имеют одинаковый IP-адрес и порт назначения? как отличаются переменные?

Примечание: Если сервер и клиент находятся в одной сети, то копия работает, а переменная newClient может отправлять данные клиенту. Чтобы имитировать эту проблему, клиент должен находиться за NAT (маршрутизатором). Пример такой сети может состоять из двух маршрутизаторов. позвольте называть их маршрутизатором X и маршрутизатором Y. Вам также нужен серверный вызов, который S. и клиент C., поэтому S может быть подключен к одному из LAN-портов X. C может быть подключен к одному из LAN-портов Y. Наконец, подключите WAN-порт Y к одному из портов LAN X.

4b9b3361

Ответ 1

Наконец нашел ответ! Вот воплощение только с клиентом и сервером. Моя следующая попытка будет состоять в том, чтобы использовать 3 компьютера. в любом случае надеюсь, что это поможет:

Код сервера:

class Program
{
    static byte[] dataToSend = new byte[] { 1, 2, 3, 4, 5 };

    // get the ip and port number where the client will be listening on
    static IPEndPoint GetClientInfo()
    {
        // wait for client to send data
        using (UdpClient listener = new UdpClient(11000))
        {
            IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
            byte[] receive_byte_array = listener.Receive(ref groupEP);

            return groupEP;
        }
    }

    static void Main(string[] args)
    {
        var info = GetClientInfo(); // get client info

        /* NOW THAT WE HAVE THE INFO FROM THE CLIENT WE ARE GONG TO SEND
           DATA TO IT FROM SCRATCH!. NOTE THE CLIENT IS BEHIND A NAT AND
           WE WILL STILL BE ABLE TO SEND PACKAGES TO IT
        */

        // create a new client. this client will be created on a 
        // different computer when I do readl udp punch holing
        UdpClient newClient = ConstructUdpClient(info);

        // send data
        newClient.Send(dataToSend, dataToSend.Length);            
    }

    // Construct a socket with the info received from the client
    static UdpClient ConstructUdpClient(IPEndPoint clientInfo)
    {          
        var ip = clientInfo.Address.ToString();
        var port = clientInfo.Port;

        // this is the part I was missing!!!!
        // the local end point must match. this should be the ip this computer is listening on
        // and also the port            
        UdpClient client = new UdpClient(new IPEndPoint( IPAddress.Any, 11000));

        // lastly we are missing to set the end points. (ip and port client is listening on)

        // the connect method sets the remote endpoints
        client.Connect(ip, port);

        return client;
    }
}

клиентский код:

string ipOfServer = "192.168.0.139";
int portServerIsListeningOn = 11000;

// send data to server
Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress send_to_address = IPAddress.Parse(ipOfServer);
IPEndPoint sending_end_point = new IPEndPoint(send_to_address, portServerIsListeningOn);
sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);

// get info
var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];

// now wait for server to send data back
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
byte[] buffer = new byte[1024];
sending_socket.Receive(buffer); // <----- we can receive data now!!!!!

Ответ 2

Хм, я думаю, вы путаете несколько вещей здесь. Во-первых, это действительно называется штампование отверстий UDP. Позвольте мне попытаться объяснить, как это должно работать.

NAT-маршрутизаторы обычно сопоставление портов при пересылке пакетов из внутренней частной сети в внешний интернет.

Предположим, вы создали сокет UDP на машине за NAT и отправили дейтаграмму на внешний IP-порт. Когда IP-пакет, переносящий эту датаграмму, покидает отправляющую машину, его IP-заголовок имеет поле исходного адреса, установленное на локальный немаршрутизируемый частный IP-адрес (например, 192.168.1.15), а заголовок UDP имеет поле исходного порта, установленное на любой порт был назначен сокету (либо явно через привязку, либо неявно выбранный ОС из эфемерных портов). Я назову этот номер порта источника P1.

Затем, когда NAT-маршрутизатор отправляет этот пакет во внешней сети, он перезаписывает исходный IP-адрес на свой внешний IP-адрес (иначе нет способа перенаправлять пакеты) и часто перезаписывает исходный порт UDP на другой (возможно, потому, что какой-то другой хост в частной сети использует один и тот же исходный порт, что создает неоднозначность). Отображение между исходным исходным портом и новым номером порта (пусть обозначают его P2) сохраняется в маршрутизаторе для соответствия возвращаемым пакетам. Это сопоставление также может быть специфичным для целевого IP-адреса и целевого UDP-порта.

Итак, теперь у вас "пробито отверстие" в маршрутизаторе - UDP-пакеты, отправленные обратно маршрутизатору на порт P2, перенаправляются на внутреннюю машину на порт UDP P1. Опять же, в зависимости от реализации NAT это может быть ограничено только пакетами от исходного целевого IP-адреса и целевого UDP-порта.

Для взаимодействия клиент-клиент вам нужно будет указать внешний IP-порт одного на другой через сервер, надеясь, что NAT-маршрутизатор сопоставляет одни и те же внутренние исходные порты с теми же портами внешнего источника. Затем клиенты будут отправлять пакеты друг другу, используя их.

Надеюсь, что это поможет.

Ответ 3

Рассматривали ли вы использование UPnP на клиенте для настройки обхода NAT для разрешения входящих пакетов на конкретном порту? Затем клиенту будет необходимо только сообщать входящий IP-адрес и порт на сервер и ждать, пока сервер будет отправлять пакеты.  http://en.wikipedia.org/wiki/Universal_Plug_and_Play