Картинка взята из телнет-видео «Звёздных войн»: 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


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 ищет в этом файле и находит соответствующую запись.

Использованные материалы