137 void
138 Listen(int fd, int backlog)
139 {
140 char *ptr;
141 /* может заменить второй аргумент на переменную окружения */
142 if ((ptr = getenv("LISTENQ")) != NULL)
143 backlog = atoi(ptr);
144 if (listen(fd, backlog) < 0)
145 err_sys("listen error");
146 }
■ Традиционно в руководствах и книгах утверждалось, что помещение фиксированного числа соединений в очередь позволяет обрабатывать случай загруженного серверного процесса между последовательными вызовами функции
accept
. При этом подразумевается, что из двух очередей больше записей будет содержаться, вероятнее всего, в очереди полностью установленных соединений. Но оказалось, что для действительно загруженных веб-серверов это не так. Причина задания большего значения
backlog
в том, что очередь не полностью установленных соединений растет по мере поступления сегментов SYN от клиентов; элементы очереди находятся в состоянии ожидания завершения трехэтапного рукопожатия.
■ Если очереди заполнены, когда приходит клиентский сегмент SYN, то TCP игнорирует приходящий сегмент SYN [128, с. 930–931] и не посылает RST. Это происходит потому, что состояние считается временным, и TCP клиента должен еще раз передать свой сегмент SYN, для которого в ближайшее время, вероятно, найдется место в очереди. Если бы TCP сервера послал RST, функция
connect
клиента сразу же возвратила бы ошибку, заставив приложение обработать это условие, вместо того чтобы позволить TCP выполнить повторную передачу. Кроме того, клиент не может увидеть разницу между сегментами RST в ответе на сегмент SYN, означающими, что на данном порте нет сервера либо на данном порте есть сервер, но его очереди заполнены.
ПРИМЕЧАНИЕ
Некоторые реализации отправляют сегмент RST в описанной выше ситуации, что некорректно по изложенным выше причинам. Если вы не пишете клиент специально для работы с подобным сервером, лучше всего игнорировать такую возможность. Ее учет при кодировании клиента снизит его устойчивость и увеличит нагрузку на сеть, если окажется, что порт действительно не прослушивается сервером.
■ Данные, которые приходят после завершения трехэтапного рукопожатия, но до того, как сервер вызывает функцию
accept
, должны помещаться в очередь TCP-сервера, пока не будет заполнен приемный буфер.
В табл. 4.6 показано действительное число установленных в очередь соединений для различных значений аргумента
backlog
в операционных системах, показанных на рис. 1.7. Семь операционных систем помещены в пять колонок, что иллюстрирует многообразие значений аргумента
backlog
.
Таблица 4.6. Действительное количество соединений в очереди для различных значений аргумента backlog
backlog | MacOS 10.2.6 AIX 5.1 | Linux 2.4.7 | HP-UX 11.11 | FreeBSD 4.8 FreeBSD 5.1 | Solaris 2.9 |
0 | 1 | 3 | 1 | 1 | 1 |
1 | 2 | 4 | 1 | 2 | 2 |
2 | 4 | 5 | 3 | 3 | 4 |
3 | 5 | 6 | 4 | 4 | 5 |
4 | 7 | 7 | 6 | 5 | 6 |
5 | 8 | 8 | 7 | 6 | 8 |
6 | 10 | 9 | 9 | 7 | 10 |
7 | И | 10 | 10 | 8 | 11 |
8 | 13 | 11 | 12 | 9 | 13 |
9 | 14 | 12 | 13 | 10 | 14 |
10 | 16 | 13 | 15 | 11 | 16 |
11 | 17 | 14 | 16 | 12 | 17 |
12 | 19 | 15 | 18 | 13 | 19 |
13 | 20 | 16 | 19 | 14 | 20 |
14 | 22 | 17 | 21 | 15 | 22 |
Системы AIX, BSD/ОХ и SunOS реализуют традиционный алгоритм Беркли, хотя последний не допускает значения аргумента backlog больше пяти. В системах HP-UX и Solaris 2.6 используется другой поправочный множитель к аргументу
backlog
. Системы Digital Unix, Linux и UnixWare воспринимают этот аргумент буквально, то есть не используют поправочный множитель, а в Solaris 2.5.1 к аргументу
backlog
просто добавляется единица.
ПРИМЕЧАНИЕ
Программа для измерения этих значений представлена в решении упражнения 15.4.
Как мы отмечали, традиционно аргумент backlog задавал максимальное значение для суммы обеих очередей. В 1996 году была предпринята новая атака через Интернет, названная SYN flooding (лавинная адресация сегмента SYN). Написанная хакером программа отправляет жертве сегменты SYN с высокой частотой, заполняя очередь не полностью установленных соединений для одного или нескольких портов TCP. (Хакером мы называем атакующего, как сказано в предисловии к [20].) Кроме того, IP-адрес отправителя каждого сегмента SYN задается случайным числом — формируются вымышленные IP-адреса (IP spoofing), что ведет к получению доступа обманным путем. Таким образом, сегмент сервера SYN/ACK уходит в никуда. Это не позволяет серверу узнать реальный IP-адрес хакера. Очередь не полностью установленных соединений заполняется ложными сегментами SYN, в результате чего для подлинных сегментов SYN в ней не хватает места — происходит отказ в обслуживании (denial of service) нормальных клиентов. Существует два типичных способа противостояния этим атакам [8]. Но самое интересное в этом примечании — это еще одно обращение к вопросу о том, что на самом деле означает аргумент backlog функции listen. Он должен задавать максимальное число установленных соединений для данного сокета, которые ядро помещает в очередь. Ограничение количества установленных соединений имеет целью приостановить получение ядром новых запросов на соединение для данного сокета, когда их не принимает приложение (по любой причине). Если система реализует именно такую интерпретацию, как, например, BSD/OS 3.0, то приложению не нужно задавать большие значения аргумента backlog только потому, что сервер обрабатывает множество клиентских запросов (например, занятый веб-сервер), или для защиты от «наводнения» SYN (лавинной адресации сегмента SYN). Ядро обрабатывает множество не полностью установленных соединений вне зависимости от того, являются ли они законными или приходят от хакера. Но даже в такой интерпретации мы видим (см. табл. 4.6), что значения 5 тут явно недостаточно.