Comments 37
КДПВ честно? ВЛНС, ведь простая, ну хоть обновили статью старенькую…
Как в старом анекдоте: «я знаю замечательную шутку про UDP, но боюсь, что она до вас не дойдет».
А вы слышали шутку про ICMP?
Я знаю отличную шутку про TCP, но если она до вас не дойдет, то я повторю.
Про MTU тоже есть кла
Вонни?
Ну и естественно, никакой уважающий себя сетевой стек не собирается принимать пакеты с совершенно незнакомого адреса, даже если порты стоят правильные.
Не понял, почему UDP сокет не должен принимать пакеты прилетевшие к нему? Какое ему дело с какого IP они прилетели? По какому признаку оно отличает свой/несвой?
У вас везде маска 24, шлюз между подсетью 3 и подсетью 1 есть?
Это статья. Потом ее кто-то перевел. А потом спроксировали сюда. Автор статьи без адреса. Переводчик тоже. И все это вписывается в маску темы.
Одно непонятно, кто будет отвечать?
Одно непонятно, кто будет отвечать?
Насколько я понимаю, сокеты делятся на принимающие данные с любого удалённого адреса и те, которые ждут пакеты с конкретного адреса. Для первых ещё не определена ответная сторона. Они слушающие. Вторые полностью заданы. Они используются для передачи данных конкретному адресату. При получении пакета из слушающего сокета создаётся сокет для передачи данных. Это позволяет обслуживать несколько клиентов одновременно (или псевдопараллельно). Где в ядро происходит это копирование, я сейчас не готов рассказать (я просто плохо понимаю, что там происходит при приёме пакета).
Со стороны отправителя сетевой стек ждёт ответа с того адреса, куда он отправил данные. Например:
Видно, что задан как локальный адрес (192.168...), так и удалённый (8.8.8.8). Только эти два хоста могут обмениваться данными. Данные от третьей машины будут отброшены 192.168.0.2 как «шум».
А с другой стороны, вот ntp слушает сеть:
Ответная сторона не определена.
В этом случае, я получу данные от любой машины. Данные мне пожет послать кто угодно.
Обычно, клиентские приложения рассчитаны на ответ с того адерса, куда послан запрос. Такое что слушаем на одном порту, а отвечаем на другом — редкость. Примером может служить DHCP, где и получатель и отправитель на первых этапах обмена данными неизвестны.
Если получилось путано — переспросите, я поясню иначе. На другом примере ;)
Шлюз, естественно, есть между этими сетями.
Со стороны отправителя сетевой стек ждёт ответа с того адреса, куда он отправил данные. Например:
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 сокета, то он будет получать пакеты от любого хоста, не фильтруя их, то есть:
а теперь я буду ловить все пакеты на приходящие на порт указанный при bind'е:
Выдержка из man connect(2)
Так что все зависит от разработчика, как он сделает так и будет, а не от того, уважает себя TCP/IP стек или нет :-)
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 и имплементации TCP/IP в ядре Linux (да и для многих других имплементаций) эти утверждения не верные, так как нет проблем сделать connect(2), который заставит ядро думать о том, что соединение установлено с конкретным узлом и нет проблем сделать bind к конкретному локальному интерфейсу, вот собственно и все.
Я не в курсе реализации сокетов в скриптовых интерпретаторах, но в статье были фразы типа:
Ну и естественно, никакой уважающий себя сетевой стек не собирается принимать пакеты с совершенно незнакомого адреса, даже если порты стоят правильные.
Но на самом деле, это обычный дефект для протокола без установки сессии, такого как UDP.
Ещё раз: ядро не хранит никакой информации об отправителе или получателе, так как мы работаем без соединений.
Для UDP и имплементации TCP/IP в ядре Linux (да и для многих других имплементаций) эти утверждения не верные, так как нет проблем сделать connect(2), который заставит ядро думать о том, что соединение установлено с конкретным узлом и нет проблем сделать bind к конкретному локальному интерфейсу, вот собственно и все.
Поймите меня правильно: я примерно представляю себе как устроен echo сервер. Проблем с реализацией у меня не возникало ни разу. Вы же предлагаете изменение, суть которого я не очень понимаю. Потому я прошу вас привести конкретную реализацию: я соберу именно тот исходник, который вы предложите, и попытаюсь на нём показать в чём проблема.
Насколько я вас понимаю, хорошо бы ещё отказаться и от nc как клиента. Так что я готов принять от вас ссылку на реализацию клиента до кучи. (Хотя лучше просто в комментарий выложить исходник).
Насколько я вас понимаю, хорошо бы ещё отказаться и от 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");
}
}
}
Да, всё так, как вы описали. Вот так выглядит клиентский сокет.
Он действительно не устанавливал соединение ни с кем, так что примет не то что ответ, просто пакет откуда угодно.
Но особых плюсов это не даёт, имхо. Способов избежать «Received a packet from an unexpected server» я не вижу.
Вообще, почему я стал писать этот перевод: есть софт типа openvpn или asterisk, который использует udp, но всё же коннектится к удалённому хосту. И при использовании алиасов на интерфейсах, возникают различные фокусы, как в описанном выше варианте. Переписывать этот софт не получится, однако проблемы можно избежать сконфигурив сервис по другому. Перевод в основном для этого сделан ;)
Про отправку пакетов без connect'а — любопытно. Как-то я в таком виде клиентов не реализовывал…
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'а — любопытно. Как-то я в таком виде клиентов не реализовывал…
Тема кажется до удивления знакомой.
Сдаётся мне автор наступил на граблю, которую можно кратко проиллюстрировать следующим примером:
Грабля в том, что сокет открытый nc после приёма первой UDP датаграммы переходит в состояние «соединение установлено», как ни смешно это прозвучит, т.е. связывает локальный адрес с адресом подходящего интерфейса, а удалённые адрес и порт — с адресом отправителя.
ИМХО это глюк, в данном случае — глюк nc.
Если вместо nc использовать нижеприведённый пример, то всё работает:
А именно, получение UDP-датаграммы не вызывает связывания принимающего сокета.
Сдаётся мне автор наступил на граблю, которую можно кратко проиллюстрировать следующим примером:
-- запускаем 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 (я выше описал) я не поленился, глянул в его исходники:
Функция:
для всех type'ов сокетов делает 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» в своём исходнике (у меня не компилилось, по крайней мере, пока не поправил).
Вы не могли бы привести пример исходника, который при этом шлёт ответ клиенту? Проблема не на приёме, проблема в доставке ответа клиенту.
Вы не могли бы привести пример исходника, который при этом шлёт ответ клиенту? Проблема не на приёме, проблема в доставке ответа клиенту.
Например так (ничего неожиданного):
#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;
}
Дело, очевидно, в nc. А я бы вообще посоветовал использовать разные сокеты для приёма и передачи udp пакетов. Ваша завязка на цикл “послал–принял” практически лишает смысла использование stateless протокола udp.
Sign up to leave a comment.
UDP и проблема доставки ответа