Pull to refresh

Comments 37

КДПВ честно? ВЛНС, ведь простая, ну хоть обновили статью старенькую…
UFO landed and left these words here
Как в старом анекдоте: «я знаю замечательную шутку про UDP, но боюсь, что она до вас не дойдет».
Я знаю отличную шутку про TCP, но если она до вас не дойдет, то я повторю.
Была еще одна шутка, про ARP. Кто-нибудь её знает? Расскажите!..
Стойте, стойте все! Я расскажу шутку про прерывания!
А теперь все слушайте анекдот про Broadcast.
А я могу про Multicast в соседний топик рассказать!
Кто-нибудь знает, где найти шутки про OSPF?
Ну и естественно, никакой уважающий себя сетевой стек не собирается принимать пакеты с совершенно незнакомого адреса, даже если порты стоят правильные.


Не понял, почему UDP сокет не должен принимать пакеты прилетевшие к нему? Какое ему дело с какого IP они прилетели? По какому признаку оно отличает свой/несвой?

У вас везде маска 24, шлюз между подсетью 3 и подсетью 1 есть? Это я на всякий случай спросил)
Это статья. Потом ее кто-то перевел. А потом спроксировали сюда. Автор статьи без адреса. Переводчик тоже. И все это вписывается в маску темы.
Одно непонятно, кто будет отвечать?
Какой вы оптимист, однако.
Перевод мой. Отвечать могу я (и за перевод, и по содержанию).
Насколько я понимаю, сокеты делятся на принимающие данные с любого удалённого адреса и те, которые ждут пакеты с конкретного адреса. Для первых ещё не определена ответная сторона. Они слушающие. Вторые полностью заданы. Они используются для передачи данных конкретному адресату. При получении пакета из слушающего сокета создаётся сокет для передачи данных. Это позволяет обслуживать несколько клиентов одновременно (или псевдопараллельно). Где в ядро происходит это копирование, я сейчас не готов рассказать (я просто плохо понимаю, что там происходит при приёме пакета).
Со стороны отправителя сетевой стек ждёт ответа с того адреса, куда он отправил данные. Например:
oxpa@oxpa-desktop:~$ netstat -unp
Proto Recv-Q Send-Q Local Address Foreign Address State       PID/Program name
udp        0      0 192.168.0.2:38940       8.8.8.8:53              ESTABLISHED 19597/operapluginwr

Видно, что задан как локальный адрес (192.168...), так и удалённый (8.8.8.8). Только эти два хоста могут обмениваться данными. Данные от третьей машины будут отброшены 192.168.0.2 как «шум».
А с другой стороны, вот ntp слушает сеть:
udp        0      0 127.0.0.1:123           0.0.0.0:*   

Ответная сторона не определена.
В этом случае, я получу данные от любой машины. Данные мне пожет послать кто угодно.
Обычно, клиентские приложения рассчитаны на ответ с того адерса, куда послан запрос. Такое что слушаем на одном порту, а отвечаем на другом — редкость. Примером может служить DHCP, где и получатель и отправитель на первых этапах обмена данными неизвестны.
Если получилось путано — переспросите, я поясню иначе. На другом примере ;)

Шлюз, естественно, есть между этими сетями.
Если не делать connect(2) для UDP сокета, то он будет получать пакеты от любого хоста, не фильтруя их, то есть:
    data->sock = socket ( PF_INET, SOCK_DGRAM, 0 );
    if ( data->sock == -1 ) {
        data->logger->log ( LOGGER_ERROR, "Can't allocate socket.\n" );
        return -1;
    }

    if ( bind ( data->sock, ( struct sockaddr* ) &data->si, sizeof ( data->si ) ) < 0 ) {
        close ( data->sock );
        data->sock = -1;
        data->logger->log ( LOGGER_ERROR, "Can't bind socket.\n" );
        return -1;
    }


а теперь я буду ловить все пакеты на приходящие на порт указанный при bind'е:

rxed = recvfrom ( d->sock, rx_buff, sizeof ( rx_buff ), 0, ( struct sockaddr* ) &si_other, &slen );


Выдержка из man connect(2)

int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);



The connect() system call connects the socket referred to by the file descriptor sockfd to the address specified by addr. The addrlen argument specifies the size of addr. The format of the address in addr is determined by the address space of the socket sockfd; see socket(2) for further details.

If the socket sockfd is of type SOCK_DGRAM then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received.


Так что все зависит от разработчика, как он сделает так и будет, а не от того, уважает себя TCP/IP стек или нет :-)
вы не могли бы привести полный исходник. Я пока не очень понимаю вашу мысль. То есть всё что вы говорите — верно, но какое имеет отношение к описанному выше я пока не пойму.
Примеры echo клиента и сервера на C можно взять например здесь.

Я не в курсе реализации сокетов в скриптовых интерпретаторах, но в статье были фразы типа:

Ну и естественно, никакой уважающий себя сетевой стек не собирается принимать пакеты с совершенно незнакомого адреса, даже если порты стоят правильные.

Но на самом деле, это обычный дефект для протокола без установки сессии, такого как UDP.

Ещё раз: ядро не хранит никакой информации об отправителе или получателе, так как мы работаем без соединений.


Для UDP и имплементации TCP/IP в ядре Linux (да и для многих других имплементаций) эти утверждения не верные, так как нет проблем сделать connect(2), который заставит ядро думать о том, что соединение установлено с конкретным узлом и нет проблем сделать bind к конкретному локальному интерфейсу, вот собственно и все.
Поймите меня правильно: я примерно представляю себе как устроен echo сервер. Проблем с реализацией у меня не возникало ни разу. Вы же предлагаете изменение, суть которого я не очень понимаю. Потому я прошу вас привести конкретную реализацию: я соберу именно тот исходник, который вы предложите, и попытаюсь на нём показать в чём проблема.
Насколько я вас понимаю, хорошо бы ещё отказаться и от nc как клиента. Так что я готов принять от вас ссылку на реализацию клиента до кучи. (Хотя лучше просто в комментарий выложить исходник).
Я вам привел ссылку выше, там есть примеры на plain C, клиент там не делает connect, то есть, если сервер примет echo на один интерфейс, а ответит с другого, клиент ответ примет, но произойдет вот это:

    /* Check that client and server are using same socket */
    if (echoserver.sin_addr.s_addr != echoclient.sin_addr.s_addr) {
        Die("Received a packet from an unexpected server");
    }


Вот исходники клиента и сервера по ссылке выше.

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define BUFFSIZE 255
void Die(char *mess) {
    perror(mess);
    exit(1);
}

int main(int argc, char *argv[]) {
    int sock;
    struct sockaddr_in echoserver;
    struct sockaddr_in echoclient;
    char buffer[BUFFSIZE];
    unsigned int echolen, clientlen;
    int received = 0;

    if (argc != 4) {
        fprintf(stderr, "USAGE: %s <server_ip> <word> <port>\n", argv[0]);
        exit(1);
    }


    /* Create the UDP socket */
    if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
        Die("Failed to create socket");
    }
    /* Construct the server sockaddr_in structure */
    memset(&echoserver, 0, sizeof(echoserver));       /* Clear struct */
    echoserver.sin_family = AF_INET;                  /* Internet/IP */
    echoserver.sin_addr.s_addr = inet_addr(argv[1]);  /* IP address */
    echoserver.sin_port = htons(atoi(argv[3]));       /* server port */


    /* Send the word to the server */
    echolen = strlen(argv[2]);
    if (sendto(sock, argv[2], echolen, 0,
               (struct sockaddr *) &echoserver,
               sizeof(echoserver)) != echolen) {
        Die("Mismatch in number of sent bytes");
    }


    /* Receive the word back from the server */
    fprintf(stdout, "Received: ");
    clientlen = sizeof(echoclient);
    if ((received = recvfrom(sock, buffer, BUFFSIZE, 0,
                             (struct sockaddr *) &echoclient,
                             &clientlen)) != echolen) {
        Die("Mismatch in number of received bytes");
    }
    /* Check that client and server are using same socket */
    if (echoserver.sin_addr.s_addr != echoclient.sin_addr.s_addr) {
        Die("Received a packet from an unexpected server");
    }
    buffer[received] = '\0';        /* Assure null terminated string */
    fprintf(stdout, buffer);
    fprintf(stdout, "\n");
    close(sock);
    exit(0);
}



#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define BUFFSIZE 255
void Die(char *mess) {
    perror(mess);
    exit(1);
}


int main(int argc, char *argv[]) {
    int sock;
    struct sockaddr_in echoserver;
    struct sockaddr_in echoclient;
    char buffer[BUFFSIZE];
    unsigned int echolen, clientlen, serverlen;
    int received = 0;

    if (argc != 2) {
        fprintf(stderr, "USAGE: %s <port>\n", argv[0]);
        exit(1);
    }

    /* Create the UDP socket */
    if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
        Die("Failed to create socket");
    }
    /* Construct the server sockaddr_in structure */
    memset(&echoserver, 0, sizeof(echoserver));       /* Clear struct */
    echoserver.sin_family = AF_INET;                  /* Internet/IP */
    echoserver.sin_addr.s_addr = htonl(INADDR_ANY);   /* Any IP address */
    echoserver.sin_port = htons(atoi(argv[1]));       /* server port */

    /* Bind the socket */
    serverlen = sizeof(echoserver);
    if (bind(sock, (struct sockaddr *) &echoserver, serverlen) < 0) {
        Die("Failed to bind server socket");
    }

    /* Run until cancelled */
    while (1) {
        /* Receive a message from the client */
        clientlen = sizeof(echoclient);
        if ((received = recvfrom(sock, buffer, BUFFSIZE, 0,
                                 (struct sockaddr *) &echoclient,
                                 &clientlen)) < 0) {
            Die("Failed to receive message");
        }
        fprintf(stderr,
                "Client connected: %s\n", inet_ntoa(echoclient.sin_addr));
        /* Send the message back to client */
        if (sendto(sock, buffer, received, 0,
                   (struct sockaddr *) &echoclient,
                   sizeof(echoclient)) != received) {
            Die("Mismatch in number of echo'd bytes");
        }
    }
}
Да, всё так, как вы описали. Вот так выглядит клиентский сокет.
udp        0      0 0.0.0.0:45721               0.0.0.0:*                               21444/./client.udp  

Он действительно не устанавливал соединение ни с кем, так что примет не то что ответ, просто пакет откуда угодно.
Но особых плюсов это не даёт, имхо. Способов избежать «Received a packet from an unexpected server» я не вижу.
Вообще, почему я стал писать этот перевод: есть софт типа openvpn или asterisk, который использует udp, но всё же коннектится к удалённому хосту. И при использовании алиасов на интерфейсах, возникают различные фокусы, как в описанном выше варианте. Переписывать этот софт не получится, однако проблемы можно избежать сконфигурив сервис по другому. Перевод в основном для этого сделан ;)
Про отправку пакетов без connect'а — любопытно. Как-то я в таком виде клиентов не реализовывал…
Тема кажется до удивления знакомой.
Сдаётся мне автор наступил на граблю, которую можно кратко проиллюстрировать следующим примером:

-- запускаем netcat слушать

1$ nc -u -l 0.0.0.0 10000

-- смотрим что он открыл

2$ netstat -anpu | grep 10000
udp        0      0 0.0.0.0:10000               0.0.0.0:*                               26242/nc
2$ lsof -p 26242 | grep UDP
nc      26242 jcmvbkbc    3u  IPv4 388760      0t0      UDP *:ndmp

-- посылаем ему пакет

3$ echo Hello | nc 127.0.0.1 10000
^C

-- смотрим что с ним стало

2$ netstat -anpu | grep 10000
udp        0      0 127.0.0.1:10000             127.0.0.1:51359             ESTABLISHED 26242/nc
2$ lsof -p 26242 | grep UDP
nc      26242 jcmvbkbc    3u  IPv4 388760      0t0      UDP localhost:ndmp->localhost:51359

-- смотрим что он напечатал

1$ nc -u -l 0.0.0.0 10000
Hello

-- шлём ему что-нибудь ещё, с другого порта... не шлётся

3$ echo Hello | nc 127.0.0.1 10000
nc: Write error: Connection refused
^C



Грабля в том, что сокет открытый nc после приёма первой UDP датаграммы переходит в состояние «соединение установлено», как ни смешно это прозвучит, т.е. связывает локальный адрес с адресом подходящего интерфейса, а удалённые адрес и порт — с адресом отправителя.

ИМХО это глюк, в данном случае — глюк nc.

Если вместо nc использовать нижеприведённый пример, то всё работает:

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>

int main(int argc,char *argv[])
{
  int s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  int port = 10000;

  sockaddr_in sa = {};
  sa.sin_family = AF_INET;
  sa.sin_port = htons(port);
  bind(s, (sockaddr*)&sa, sizeof(sa));
  for (;;) {
      char buf[100000];
      ssize_t sz;

      sz = read(s, buf, sizeof(buf));
      write(STDOUT_FILENO, buf, sz);
  }
  return 0;
}


А именно, получение UDP-датаграммы не вызывает связывания принимающего сокета.
Все верно, nc делает connect (я выше описал) я не поленился, глянул в его исходники:

Функция:

int netcat_socket_new_connect(int domain, int type, const struct in_addr *addr,
		in_port_t port, const struct in_addr *local_addr,
		in_port_t local_port)

для всех type'ов сокетов делает connect:

  /* now launch the real connection.  Since we are in non-blocking mode, this
     call will return -1 in MOST cases (on some systems, a connect() to a local
     address may immediately return successfully) */
  ret = connect(sock, (struct sockaddr *)&rem_addr, sizeof(rem_addr));
  if ((ret < 0) && (errno != EINPROGRESS)) {
    ret = -5;
    goto err;
  }
вы пропустили два «struct» в своём исходнике (у меня не компилилось, по крайней мере, пока не поправил).
Вы не могли бы привести пример исходника, который при этом шлёт ответ клиенту? Проблема не на приёме, проблема в доставке ответа клиенту.
Блин, не туда ответил сначала ответ написал

Это исходники nc фаил network.c
по поводу nc я понял. nc не используется как сервер. Только как клиент. Кроме того, в некоторых версиях nc это поведение можно обойти. Клюс -k заставляет nc слушать последующие соединения после завершения текущего. Как этот ключ сочетаетс с udp я не очень представляю.
Например так (ничего неожиданного):

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>

int main(int argc,char *argv[])
{
  int s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  int port = 10000;

  struct sockaddr_in sa = {};
  sa.sin_family = AF_INET;
  sa.sin_port = htons(port);
  bind(s, (struct sockaddr*)&sa, sizeof(sa));
  for (;;) {
      char buf[100000];
      struct sockaddr client_sa;
      socklen_t client_sa_sz = sizeof(client_sa);
      ssize_t sz;

      sz = recvfrom(s, buf, sizeof(buf), 0, &client_sa, &client_sa_sz);
      write(STDOUT_FILENO, buf, sz);
      sendto(s, buf, sz, 0, &client_sa, client_sa_sz);
  }
  return 0;
}
с Quasar_ru разобрались: дело в общем-то не в сервере, а в клиенте. В чём именно — тоже разобрались. Спасибо.
Дело, очевидно, в nc. А я бы вообще посоветовал использовать разные сокеты для приёма и передачи udp пакетов. Ваша завязка на цикл “послал–принял” практически лишает смысла использование stateless протокола udp.
Sign up to leave a comment.

Articles