Картинка взята из телнет-видео «Звёздных войн»: telnet towel.blinkenlights.nl
Недавно был пост о том, нужны ли сетевики. До тех пор, пока проверка доступности tcp/ip порта кажется чем-то сложным даже для администраторов БД и AD, сетевики несомненно нужны. Они особенно полезны в тех случаях, когда необходимо понять почему так плохо работает некое клиент-серверное приложение ценой в пароход.
Иногда мало знать ping и traceroute для того, чтобы понять и устранить проблему в сети. Необходимо понимать как работают все звенья в цепи, а сделать это может лишь сетевик. Рассмотрим несколько таких примеров.
Чем Netcat лучше telnet?
Практически всегда, если речь идет о проверке TCP/UDP порта стараются сделать это через telnet и приходится всякий раз просить поставить Netcat. Есть несколько причин, по которым telnet сильно проигрывает. Вот основные причины.
- telnet не умеет различать причины недоступности порта. Все, или ничего — connect, либо fail. Netcat может подсказать, в чем проблема. В этом случае все просто, такого порта в состоянии LISTENING нет.
- Не умеет в UDP, SCTP и прочие, ничего кроме TCP.
- Не имеет опций тайм-аута соединения -w и проверки без подключения -z
Теперь не говорите, что вы не знали и закопайте поглубже telnet.
(1:1004)$ ncat -v -w3 -z some.server 1111
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Connection refused.
Если этот хост включен и пингуется, то дело скорее всего в файрволе, а telnet этого не покажет. На самом деле все немного сложнее и зависит от настроек файрвола, чаще всего заблокированные пакеты просто отбрасываются, но при желании можно применить другие политики.
(1:1005)$ ncat -v -w3 -z another.server 1521
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: No route to host.
Бытует мнение, что на Windows нет альтернативы telnet, однако это неверно. Существует штатная утилита Test-NetConnection, которую можно запустить из PowerShell.
Test-NetConnection -ComputerName "www.contoso.com" -Port 80
Не умеет в UDP, SCTP и прочие, ничего кроме TCP.
Не имеет опций тайм-аута соединения -w и проверки без подключения -z.
Теперь не говорите, что вы не знали и закопайте поглубже telnet.
Linux MIB в помощь
Если ваш сервер вдруг перестал реагировать на внешние раздражители и клиентские запросы стали провисать, не всегда причина в выдернутом сетевом кабеле, или зависших сервисах JBoss/Tomcat. Прежде, чем заводить кейс в тех-поддержку проверьте Linux MIB. Есть множество программ, выдающих статистику ядра Linux по внутренним счетчикам SNMP. Самая распространенная из них все еще netstat.
(1:1006)$ netstat -s
(1:1007)$ nstat -s
Первая утилита более дружелюбна к пользователю, однако показывает не все переменные. Вторая же показывает весь список, однако не содержит никаких подсказок. Подробнее про отличия здесь. SNMP подсистема Linux находится в несколько обветшалом состоянии, из-за чего нужно приложить некоторые усилия для того, чтобы понять смысл и назначение тех, или иных счетчиков. Для сетевика интерес может представлять buffer overrun.
(1:1008)$ netstat -s |grep prune
873 packets pruned from receive queue because of socket buffer overrun
(1:1009)$ nstat -s |grep Prune
TcpExtPruneCalled 873 0.0
Если сервер не справляется с сетевой нагрузкой и буфер сокета перманентно переполнен, тогда ядро будет просто сбрасывать все новые пакеты и счетчик будет расти на глазах. Чтобы исправить эту ситуацию, можно добавить некоторые настраиваемые параметры в файле sysctl.conf. Текущие значения можно увидеть из файловой системы /proc.
(1:1010)$ cat /proc/sys/net/ipv4/tcp_rmem
4096 16384 4194304
(1:1011)$ cat /proc/sys/net/ipv4/wcp_rmem
4096 16384 4194304
(1:1012)$ grep -e tcp_rmem -e tcp_wmem /etc/sysctl.conf
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
Три значения параметра tcp_rmem/tcp_wmem обозначает соответственно минимальное, пороговое и максимальное значение. По умолчанию величины выставлены довольно разумно, поэтому менять их без веских оснований не стоит. Чаще всего такая ситуация может возникнуть на сети с пропускной способностью 10 GiB/s. После сохранения изменений в файле sysctl.conf следует выполнить systcl -p. Эта команда запускает системный вызов setsockopt (SO_RCVBUF).
Следует отметить, что буфер setsockopt(SO_RCVBUF), установленный в приложении, будет ограничен значениями, установленными в net.core.rmem_default и net.core.rmem_max и если приложение имеет буфер размером 1 мб, но net.core.rmem_max составляет всего 256 кб, то буфер сокета приложения будет ограничен этим значением.
Диагностика сети с помощью системных вызовов
Для любого сетевика WireShark абсолютно необходимый инструмент отладки, иногда — главный. Не раз и не два только благодаря WireShark удавалось спасти проект в очень сложной ситуации. Но бывают случаи, когда только в системных вызовах ядра можно увидеть сетевую проблему.
В частности отброшенные пакеты нельзя будет увидеть в tcpdump, или WireShark. Зато можно их увидеть в режиме реального времени с помощью tcpdrop из сета bcc-tools. У команды нет никаких опций, просто выполните tcpdrop. Вывод состоит из сокета, статусе соединения и флагах TCP. Далее идет трассировка стека ядра, которая привела к этому сбросу пакета.
(1:1013)$ /usr/share/bcc/tools/tcpdrop
TIME PID IP SADDR:SPORT > DADDR:DPORT STATE (FLAGS)
16:31:07 3103 4 93.184.216.34:443 > 192.168.11.32:36222 ESTABLISHED (PSH|ACK)
b'tcp_drop+0x1'
b'tcp_data_queue+0x1e7'
b'tcp_rcv_established+0x1df'
b'tcp_v4_do_rcv+0x131'
b'tcp_v4_rcv+0xad3'
b'ip_protocol_deliver_rcu+0x2b'
b'ip_local_deliver_finish+0x55'
b'ip_local_deliver+0xe8'
b'ip_rcv+0xcf'
b'__netif_receive_skb_core+0x429'
b'__netif_receive_skb_list_core+0x10a'
b'netif_receive_skb_list_internal+0x1bf'
b'netif_receive_skb_list+0x26'
b'iwl_pcie_rx_handle+0x447'
b'iwl_pcie_irq_handler+0x4d5'
b'irq_thread_fn+0x20'
b'irq_thread+0xdc'
b'kthread+0x113'
b'ret_from_fork+0x35'
В bcc-tools имеется еще одна очень полезная утилита tcplife, которая показывает параметры TCP соединения, в том числе, длительность сеанса. У этой команды есть некоторые опции.
(1:1014)$ /usr/share/bcc/tools/tcplife -h
...
examples:
./tcplife # trace all TCP connect()s
./tcplife -T # include time column (HH:MM:SS)
./tcplife -w # wider columns (fit IPv6)
./tcplife -stT # csv output, with times & timestamps
./tcplife -p 181 # only trace PID 181
./tcplife -L 80 # only trace local port 80
./tcplife -L 80,81 # only trace local ports 80 and 81
./tcplife -D 80 # only trace remote port 80
Просмотр сессий выглядит так.
# ./tcplife -D 80
PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS
27448 curl 100.66.11.247 54146 54.154.224.174 80 0 1 263.85
27450 curl 100.66.11.247 20618 54.154.164.22 80 0 1 243.62
27452 curl 100.66.11.247 11480 54.154.43.103 80 0 1 231.16
27454 curl 100.66.11.247 31382 54.154.15.7 80 0 1 249.95
Может так случится, что между клиентом и сервером расположен межсетевой экран, либо IPS/IDS с набором правил по обрыву длительности TCP сессии по прошествии определенного времени, или достижении некоего объёма трафика. В этом случае диагностика подобного кейса может затянутся неопределенно долго, так как находится в слепой зоне, как со стороны клиентского, так и со стороны серверного приложения. Единственный способ понять в чему тут дело, это использовать инструменты bcc-tools
.
Недооцененный резолвинг
Еще одним слепым пятном во многих проектах по внедрению, интеграции различных комплексов автоматизации и повышения производительности, является система разрешения имен ОС. Большинство приложений могут работать с ошибками если не соблюдено следующее условие.
getnameinfo(getaddrinfo(HOSTNAME)) = HOSTMANE
ункции getnameinfo и getaddrinfo являются заменой устаревших gethostbyname и gethostbyaddr соответственно.
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
char *host, socklen_t hostlen,
char *serv, socklen_t servlen, int flags);
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res);
Положительный пример — lib.ru
(1:1015)$ host lib.ru
lib.ru has address 81.176.66.163
...
(1:1016)$ host 81.176.66.163
163.66.176.81.in-addr.arpa domain name pointer lib.ru.
Отрицательный пример - example.com.
(1:1017)$ host example.com
example.com has address 93.184.216.34
(1:1018)$ host 93.184.216.34
Host 34.216.184.93.in-addr.arpa. not found: 3(NXDOMAIN)
Если бы IP 93.184.216.34 указывал не на example.com, а на иной хост, то и это было бы ошибкой. Только тождество результат двустороннего разрешения имен гарантирует отсутствие ошибок настройки.
Вообще-то используя утилиту host следует помнить, что она так же, как dig и nslookup используют лишь DNS для разрешения имён хостов и игнорируют записи в файле /etc/hosts. По этой причине целесообразнее использовать команду getent.
(1:1019)$ strace -f -e trace=openat host my.router 2>&1 |grep -e "^openat" -e address
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/libcrypto.so.1.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/libuv.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/libidn2.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/libunistring.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/proc/self/task/7164/comm", O_RDWR) = 3
(1:1020)$ strace -f -e trace=openat getent ahosts my.router
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/gconv/gconv-modules.cache", O_RDONLY) = 3
openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/host.conf", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/libidn2.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/libunistring.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib64/charset.alias", O_RDONLY|O_NOFOLLOW) = -1 ENOENT (No such file or directory)
192.168.10.10 STREAM my.router
192.168.10.10 DGRAM
192.168.10.10 RAW
+++ exited with 0 +++
Из вывода видно, что host не обращается к файлу /etc/hosts и по этой причине не в состоянии определить IP адрес маршрутизатора. Напротив, getent ищет в этом файле и находит соответствующую запись.
Использованные материалы
- How can I tune the TCP Socket Buffers?
- Resolve IP адресов в Linux: понятное и детальное описание
- Brendan Gregg's Blog
Автор: oldadmin