Техника отладки
Это приложение содержит некоторые рекомендации и описание методов отладки сетевых приложений. Ни один из приведенных методов не является панацеей от всех возможных проблем, однако существует множество инструментальных средств, с которыми следует ознакомиться, чтобы в дальнейшем использовать подходящие для конкретной среды.
В.1. Трассировка системных вызовов
Многие версии Unix предоставляют возможность трассировки (отслеживания) системных вызовов. Зачастую это может оказаться полезным методом отладки.
Работая на этом уровне, необходимо различать системный вызов и функцию. Системный вызов является точкой входа в ядро, и именно это можно отследить с помощью инструментальных средств, описанных в данном разделе. Стандарт POSIX и большинство других стандартов используют термин функция, вкладывая в это понятие тот же смысл, что и пользователи, хотя на самом деле это может быть системный вызов. Например, в Беркли-ядрах
socket
— это системный вызов, хотя программист приложений может считать, что это обычная функция языка С. В системе SVR4, как будет показано далее, это функция из библиотеки сокетов, которая содержит вызовы putmsg
и getmsg
, в действительности являющиеся системными вызовами.В этом разделе мы рассмотрим системные вызовы, задействованные в работе клиента времени и даты (см. листинг 1.1).
Сокеты ядра BSD
Мы начнем с FreeBSD, операционной системы с Беркли-ядром, в котором все функции сокетов являются системными вызовами. Программа трассировки системных вызовов имеет название
ktrace
. Она выводит информацию о трассировке в файл (по умолчанию имя этого файла ktrace.out
), который можно вывести на экран с помощью kdump
. Клиент сокета запускается следующим образом:freebsd % ktrace daytimetcpcli 192.168.42.2
Tue Aug 19 23:35.10 2003
Затем запускаем
kdump
, чтобы направить трассировочную информацию в стандартный поток вывода.3211 daytimetcpcli CALL socket(0x2,0x1,0)
3211 daytimetcpcli RET socket 3
3211 daytimetcpcli CALL connect(0x3,0x7fdffffe820,0x10)
3211 daytimetcpcli RET connect 0
3211 daytimetcpcli CALL read(0x3,0x7fdffffe830,0x1000)
3211 daytimetcpcli GIO fd 3 read 26 bytes
"Tue Aug 19 23:35:10 2003
"
3211 daytimetcpcli RET read 26/0x1a
...
3211 daytimetcpcli CALL write(0x1,0x204000,0x1a)
3211 daytimetcpcli GIO fd 1 wrote 26 bytes
"Tue Aug 19 23:35:10 2003
"
3211 daytimetcpcli RET write 26/0x1a
3211 daytimetcpcli CALL read(0x3,0x7fdffffe830,0x1000)
3211 daytimetcpcli GIO fd 3 read 0 bytes
""
3211 daytimetcpcli RET read 0
3211 daytimetcpcli CALL exit(0)
Число 3211 является идентификатором процесса.
CALL
идентифицирует системный вызов, RET
обозначает возвращение управления, GIO
подразумевает общую операцию ввода-вывода. Мы видим системные вызовы socket
и connect
, за которыми следуют вызовы read
, возвращающие 26 байт. Наш клиент записывает эти байты в стандартный поток вывода, и при следующем вызове read
возвращает нулевое значение (конец файла).Сокеты ядра Solaris 9
Операционная система Solaris 2.x основывается на SVR4, и во всех версиях ранее 2.6 сокеты реализуются так, как показано на рис. 31.3. Однако во всех версиях SVR4 с подобными реализациями сокетов существует одна проблема: они редко обеспечивают полную совместимость с сокетами Беркли-ядер. Для обеспечения дополнительной совместимости в Solaris 2.6 способ реализации изменен за счет использования файловой системы
sockfs
. Такой подход обеспечивает поддержку сокетов ядра, что можно проверить с помощью программы truss
на нашем клиенте (использующем сокеты).solaris % truss -v connect daytimetcpcli 127.0.0.1
Mon Sep 8 12:16:42 2003
После обычного подключения библиотеки осуществляется первый системный вызов
so_socket
— системный вызов, инициированный нашим вызовом socket
.so_socket(PF_INET, SOCK_STREAM, IPPROTO_IP, 1) = 3
connect(3, 0xFFBFDEF0, 16, 1) = 0
AF_INET name = 127.0.0.1 port = 13
read(3, " M o n S e p 8 1", ... 4096) = 26
Mon Sep 8 12:48:06 2003
write(1, " M o n S e p 8 1", ... 26) = 26
read(3, 0xFFBFDF03, 4096) = 0
_exit(0)
Первые три аргумента системного вызова
so_socket
являются нашими аргументами socket
.Далее мы видим, что
connect
является системным вызовом, a truss
при вызове с флагом -v connect
выводит на экран содержимое структуры адреса сокета, на которую указывает второй аргумент (IP-адрес и номер порта). Мы не показываем системные вызовы, относящиеся к стандартным потокам ввода и вывода.В.2. Стандартные службы Интернета
Рекомендуем ознакомиться со стандартными службами Интернета, приведенными в табл. 2.1. Для тестирования наших клиентов мы много раз использовали службу, позволяющую определить дату и время. Служба, игнорирующая присылаемые данные, является удобным портом, на который можно отправлять данные. Эхо-служба аналогична эхо-серверу, неоднократно упоминаемому в этой книге.
ПРИМЕЧАНИЕВ настоящее время многие сайты перекрывают доступ к этим службам с помощью брандмауэров, так как некоторые атаки типа «отказ в обслуживании» (DoS), имевшие место в 1996 году, были направлены именно на эти службы (см. упражнение 13.3). Тем не менее можно успешно использовать эти службы внутри локальной сети.
В.3. Программа sock
Программа
sock
, написанная Уильямом Стивенсом, впервые появилась в книге [111], где широко использовалась для генерации специальных условий, большинство которых затем проверялось с помощью программы tcpdump
. Удобство этой программы заключается в том, что она генерирует такое множество различных сценариев, что нет необходимости писать специальные тестовые программы.В этой книге исходный код программы не приведен (более 2000 строк на языке С), но он находится в свободном доступе (см. предисловие).
Программа работает в одном из четырех режимов, и в каждом из них можно использовать либо протокол TCP, либо протокол UDP.
1. Клиент стандартного ввода и стандартного вывода (рис. В.1).
Рис. В.1. Клиент sock: стандартный ввод и стандартный вывод
В клиентском режиме все, что считывается из стандартного потока ввода, передается в сеть, а все, что получается из сети, записывается в стандартный поток вывода. Должны быть указаны IP-адрес сервера и номер порта, и в случае TCP выполняется активное открытие.
2. Сервер стандартного ввода и стандартного вывода. Этот режим аналогичен предыдущему, за исключением того, что программа связывает заранее известный порт со своим сокетом и в случае TCP осуществляется пассивное открытие.
3. Клиент-отправитель (рис. В.2).
Рис. В.2. Программа sock в качестве клиента-отправителя
Программа осуществляет фиксированное количество передач пакетов некоторого определенного размера в сеть.
4. Сервер-получатель (рис. В.3).
Рис. В.3. Программа sock в качестве сервера-получателя
Программа осуществляет фиксированное количество считываний из сети.
Эти четыре рабочих режима соответствуют следующим четырем командам:
sock [параметры] узел служба
sock [параметры] -s [узел] служба
sock [параметры] -i узел служба
sock [параметры] -is [узел] служба
где узел— это имя или IP-адрес узла, а служба — это имя или номер порта. В двух серверных режимах выполняется связывание с универсальным адресом, если не задан необязательный параметр узел.
Можно также определить около 40 параметров командной строки, запускающих дополнительные возможности программы. Здесь мы не будем подробно останавливаться на этих параметрах, отметим только, что можно использовать почти все параметры сокетов, упомянутые в главе 7. Запуск программы без аргументов выводит на экран краткое описание всех параметров:
-b n связывает n в качестве клиентского локального номера порта
-с конвертирует символ новой строки в CR/LF и наоборот
-f a.b.c.d.p удаленный IP-адрес = a.b.c.d, удаленный номер порта = р
-g a.b.c.d свободная маршрутизация
-h половинное закрытие TCP при получении EOF из стандартного потока ввода
-i отправка данных на сокет, прием данных с сокета (w/-s)
-j a.b.c.d присоединение к группе многоадресной передачи
-k осуществляет write или writev порциями
-l a.b.c.d.p клиентский локальный IP-адрес = a.b.c.d. локальный номер порта = р
-n n размер буфера для записи клиентом "рассылки" (по умолчанию 1024)
-о НЕ присоединять UDP-клиент
-р n время ожидания (в мс) перед каждым считыванием или записью (рассылка/прием)
-q n размер очереди на прослушиваемом сокете для сервера TCP
(по умолчанию 5)
-r n количество байтов за одну операцию считывания (read) для сервера "приема"
(по умолчанию 1024)
-s работает как сервер, а не как клиент
-u использовать UDP вместо TCP
-v подробный вывод
-w n количество байтов для каждой записи (write) клиента "рассылки"
(по умолчанию 1024)
-x n время (в ms) для SO_RCVTIMEO (получение тайм-аута)
-y n время (в ms) для SO_SNDTIMEO (отправка тайм-аута)
-A параметр SO_REUSEADDR
-B параметр SO_BROADCAST
-D параметр SO_DEBUG
-E параметр IP_RECVDSTADDR
-F порождение дочерних процессов (fork) после установления соединения
(параллельный TCP-сервер)
-G a.b.c.d жесткая маршрутизация
-H n параметр IP_TOS (16=min del, 8=max thru, 4=max rel, 2=min cost)
-I сигнал SIGIO
-J n параметр IP_TTL
-K параметр SO_KEEPALIVE
-L n параметр SO_LINGER, n = linger time
-N параметр TCP_NODELAY
-O n время (в мс) для ожидания после вызова listen, но перед первым приемом (accept)
-Р n время (в мс) перед первым считыванием или записью (рассылка/прием)
-Q n время (в мс) ожидания после получения FIN, но перед закрытием
-R n параметр SO_RCVBUF
-S n параметр SO_SNDBUF
-Т параметр SO_REUSEPORT
-U n войти в срочный режим, прежде чем записать число n (только для отправителя)
-V использовать writev() вместо write(): включает -k
-W игнорировать ошибки записи для клиента приема
-X n параметр TCP_MAXSEG (устанавливает MSS)
-Y параметр SO_DONTROUTE
-Z MSG_PEEK
-2 параметр IP_ONESBCAST (255.255.255.255) для широковещательной передачи
В.4. Небольшие тестовые программы
Другим полезным методом отладки, которым автор пользовался при написании книги, является создание небольших тестовых программ, позволяющих увидеть, как работает одно конкретное свойство в тщательно выстроенной тестовой ситуации. При написании небольших тестовых программ полезно иметь набор библиотечных функций-оберток и некоторых простых функций вывода сообщений об ошибках, наподобие тех, что использовались на протяжении всей книги. Такой подход уменьшает размер создаваемого кода и в то же время обеспечивает требуемую проверку ошибок.
В.5. Программа tcpdump
Бесценным средством отладки в сетевом программировании является такая программа, как
tcpdump
. Она считывает пакеты из сети и выводит на экран большое количество информации об этих пакетах. Эта программа также позволяет нам задать некоторые критерии отбора пакетов, в результате чего будут выводиться только пакеты, удовлетворяющие этим критериям. Например,% tcpdump '(udp and port daytime) or icmp'
выводит только UDP-дейтаграммы с номером порта отправителя или получателя, равным 13 (сервер времени и даты), или ICMP-пакеты. Следующая команда:
% tcpdump 'tcp and port 80 and tcp[13:1] & 2 != 0'
выводит только TCP-сегменты с номером порта отправителя или получателя, равным 80 (сервер HTTP), у которых установлен флаг SYN. Флаг SYN имеет значение 2 в 13-м байте от начала TCP-заголовка. Следующая команда:
% tcpdump 'tcp and tcp[0:2] > 7000 and tcp[0:2] <= 7005'
выводит только те TCP-сегменты, у которых номер порта отправителя лежит в интервале от 7001 до 7005. Номер порта отправителя занимает 2 байта в самом начале TCP-заголовка (нулевое смещение).
В приложении А книги [111] более подробно описано действие данной программы.
ПРИМЕЧАНИЕЭта программа доступна по адресу http://www.tcpdump.org/ и работает под множеством реализаций Unix. Она написана Ван Якобсоном (Van Jacobson), Крэгом Лересом (Craig Leres) и Стивеном МакКаном (Steven McCanne) из LBL, и в настоящее время сопровождается командой tcpdump.org.
Некоторые поставщики предлагают свои программы, обладающие теми же возможностями. Например, в Solaris 2.x есть программа snoop. Но программа tcpdump функционирует под множеством версий Unix, а возможность использования одного и того же средства в неоднородном окружении является большим преимуществом.
В.6. Программа netstat
В тексте книги много раз использовалась программа
netstat
. Эта программа служит для следующих целей.■ Она выводит статус точек доступа сети. Это было показано в разделе 5.6, когда мы прослеживали статус нашей точки доступа при запуске клиента и сервера.
■ Она показывает, к какой группе принадлежит каждый из интерфейсов узла. Обычно для этой цели используется флаг
-ia
, а в Solaris 2.x используется флаг -g
.■ С параметром
-s
эта программа сообщает статистику по каждому протоколу. Подобный пример был приведен в разделе 8.13, когда мы говорили о недостаточном управлении потоками в UDP.■ При использовании параметра
-r
программа выводит таблицу маршрутизации, а с параметром -i
— информацию об интерфейсе. Эта возможность была использована в разделе 1.9, когда с помощью программы netstat
мы выясняли топологию сети.Программа
netstat
обладает и другими возможностями, а многие поставщики добавляют свои собственные. Обратитесь к руководству по вашей системе.В.7. Программа lsof
Название
lsof
происходит от «list open files» (перечислить открытые файлы). Как и tcpdump
, эта программа является общедоступной и представляет собой удобное средство для отладки, которое было перенесено на множество версий Unix.Одним из общих способов применения программы
lsof
при работе в сети является выявление процесса, имеющего открытый сокет, по указанному IP-адресу или порту. Программа netstat
позволяет выяснить, какой IP-адрес или порт используется, а также узнать состояние TCP-соединения, но она не позволяет идентифицировать процесс. Например, чтобы определить, какой процесс запустил сервер времени и даты, выполним следующую команду:solaris % lsof -i TCP:daytime
COMMAND PID USER FD TYPE DEVICE SIZE/OFF INODE NAME
inetd 222 root 15u inet 0xf5a801f8 0t0 TCP *:daytime
В выводе приводятся следующие данные: команда (данный сервис обеспечивается сервером
inetd
), идентификатор процесса, владелец процесса, дескриптор (15 и u означает, что он открыт на чтение и на запись), тип сокета, адрес протокола блока управления, размер смещения файла (не имеет значения для сокета), тип протокола и имя.Еще один из традиционных случаев применения данной программы имеет место, когда мы запускаем сервер, который связывает свой заранее известный порт и получает ошибку, указывающую, что адрес уже используется. Тогда мы запускаем программу
lsof
, чтобы выяснить, каким процессом используется данный порт.Поскольку программа
lsof
сообщает об открытых файлах, она не может сообщать о точках доступа, не ассоциированных с открытым файлом, то есть точках доступа TCP в состоянии TIME_WAIT.ПРИМЕЧАНИЕПрограмма находится по адресу ftp://vic.cc.purdue.edu/pub/tools/unix/lsof. Она написана Виком Абелем (Vic Abell).
Некоторые поставщики предлагают свои программы с похожими возможностями. Например, в BSD/OS предлагается программа fstat. Однако программа lsof работает под множеством версий Unix, а использование одного инструмента в неоднородном окружении вместо подбора различных средств для каждой среды является большим преимуществом.
Приложение Г