"netstat -s" の出力に "Udp: packet receive errors" が多数表示される
Environment
- Red Hat Enterprise Linux
- UDP ネットワーク通信
Issue
netstat -s
出力には、Udp: packet receive errors
が多数表示されます。- 多数の UDP パケットが削除されたり、行方不明になったりします。
- SNMP トラップの問題:- RHEL サーバー上で SNMP トラップが変動しているようです。
Resolution
Udp: packet receive errors
は、以下の理由で増加します。
- ソケットバッファースペースの不足
- UDP チェックサムの失敗
- UDP の長さの不一致
- IPSec セキュリティーポリシーの失敗
これらの点について、以下で説明します。
ソケットバッファースペースの不足
注記: アプリケーションが setsockopt (SO_RCVBUF)
を使用して、独自の最大受信バッファーサイズを設定する場合は、アプリケーションのドキュメントやソースコードを確認するか、アプリケーションプロバイダーに問い合わせて、アプリケーションのバッファーサイズを rmem_max 設定に合わせて増やしてください。 このテストは、最初に開発環境で、またはメンテナンス期間中に行うことを推奨します。
以下は、Udp: packet receive errors
の最も一般的な原因です。
RHEL7、8、9 システムで "netstat -us" が 'receive buffer errors' を表示する場合、これが原因となっています。
RHEL6 でこれを確認するには、以下の 診断手順 セクションの ソケットバッファーのチェック 手順に従います。
この問題は、UDP ソケットバッファーサイズを増やすと解決できます。
以下のコマンドは、デフォルトおよび最大の UDP バッファーサイズを 8 MiB に設定します。
# sysctl -w net.core.rmem_max=8388608
# sysctl -w net.core.rmem_default=8388608
この値の変更後、UDP アプリケーションを停止および起動して変更を有効にする必要があります。
再起動後もこの変更を永続化にするには、/etc/sysctl.conf
を編集して以下の行を追加します。
net.core.rmem_max = 8388608
net.core.rmem_default = 8388608
引き続きドロップが発生する場合は、これをさらに増やすことができます。16MiB は 16777216
、32MiB は 33554432
です。
注記: 大きなデフォルトバッファーは、TCP 以外のすべてに適用されるため、理想的ではありません。最適な状況は、最大値を大きくしつつ妥当な (デフォルトまたは 256KiB、262144
) デフォルト値を設定し、UDP アプリケーションで大きなバッファーサイズを設定することです。これは、大きなバッファーサイズを設定するアプリケーションによって異なります。この件のサポートについては、アプリケーションベンダーにお問い合わせください。
UDP チェックサムの失敗
Udp: packet receive errors
が増加している間に、UDP トラフィックのパケットキャプチャーを実行します。
# tcpdump -n -s0 -i ethX -w /tmp/tcpdump.pcap
Wireshark などのパケットキャプチャー分析ツールを使用してトラフィックを表示し、環境設定で UDP チェックサムの計算が有効になっていることを確認して、チェックサムエラーのあるパケットを探します。Wireshark は、Expert Info 分析で不正なチェックサムを持つパケットに自動的にフラグを付けます。
NIC 受信オフロードにより、すべて の UDP トラフィックのチェックサムが破損しているように見える場合があることに注意してください。この場合、以下のようなコマンドを使用して、NIC オフロードを (トラブルシューティングの目的のみで一時的に) 無効にすることができます。
# ethtool -K ethX rx off
UDP の長さの不一致
Udp: packet receive errors
が増加している間に、UDP トラフィックのパケットキャプチャーを実行します。
# tcpdump -n -s0 -i ethX -w /tmp/tcpdump.pcap
Wireshark などのパケットキャプチャー分析ツールを使用してトラフィックを表示し、UDP ヘッダーで定義された長さと、UDP ヘッダーおよび UDP ペイロードの実際の長さが一致しないパケットを探します。
Wireshark は、Expert Info 分析で不正なチェックサムを持つパケットに自動的にフラグを付けます。
Root Cause
UDP: packet receive errors
は、さまざまな理由により増加します。- エラーカウンターの増加理由が何かを判断するには、トラブルシューティングが必要です。
- UDP エラーの原因が判明したら、適切な解決策を適用できます。
Diagnostic Steps
統計の収集
netstat -nsu
コマンドを実行し、Udp:
セクションを確認します。
# netstat -su
Udp:
559933412 packets received
71 packets to unknown port received.
33861296 packet receive errors <---- HERE
7516291 packets sent
ソケットバッファーのチェック
現在のシステム全体のデフォルトのソケットバッファーサイズは、以下のコマンドで確認できます。
# sysctl net.core.rmem_max
# sysctl net.core.rmem_default
これは、ss -nump
を定期的に実行し、packet receive errors
が増加している間のソケット統計を監視することで確認できます。次に例を示します。
# while true; do ss -nump; sleep 1; done
以下のような出力が生成されます。
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.0.2:4500 192.168.0.1:4500
users:(("processname",pid=1234,fd=3))
skmem:(r0,rb212992,t0,tb212992,f4096,w0,o0,bl0)
Recv-Q
統計がシステム全体の rmem_max
に近づくなど、定期的に大きくなる場合は、ソケットバッファーサイズを増やします。
これは、アプリケーションがバッファーから十分な速度で受信していないことを意味することに注意してください。パフォーマンスを向上させるには、アプリケーションを再設定または再設計する必要があるかもしれません。
カーネルソース分析
統計 Udp: packet receive errors
は、UDP_MIB_INERRORS
と呼ばれる SNMP MIB を報告しています。
この MIB は、RHEL 6.6 の kernel-2.6.32-504.el6
で表示されるカーネルソースの以下の場所で増加します。
これはチェックサムチェックです。
Line 926 net/ipv4/udp.c
UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS,
908 /**
909 * first_packet_length - return length of first packet in receive queue
910 * @sk: socket
911 *
912 * Drops all bad checksum frames, until a valid one is found.
913 * Returns the length of found skb, or 0 if none is found.
914 */
915 static unsigned int first_packet_length(struct sock *sk)
916 {
917 struct sk_buff_head list_kill, *rcvq = &sk->sk_receive_queue;
918 struct sk_buff *skb;
919 unsigned int res;
920
921 __skb_queue_head_init(&list_kill);
922
923 spin_lock_bh(&rcvq->lock);
924 while ((skb = skb_peek(rcvq)) != NULL &&
925 udp_lib_checksum_complete(skb)) {
926 UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS,
927 IS_UDPLITE(sk));
928 __skb_unlink(skb, rcvq);
929 __skb_queue_tail(&list_kill, skb);
930 }
931 res = skb ? skb->len : 0;
932 spin_unlock_bh(&rcvq->lock);
933
934 if (!skb_queue_empty(&list_kill)) {
935 lock_sock(sk);
936 __skb_queue_purge(&list_kill);
937 sk_mem_reclaim_partial(sk);
938 release_sock(sk);
939 }
940 return res;
941 }
これもチェックサムチェックです。
Line 1067 net/ipv4/udp.c
UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
980 /*
981 * This should be easy, if there is something there we
982 * return it, otherwise we block.
983 */
984
985 int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
986 size_t len, int noblock, int flags, int *addr_len)
987 {
....
1064 csum_copy_err:
1065 lock_sock(sk);
1066 if (!skb_kill_datagram(sk, skb, flags))
1067 UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
1068 release_sock(sk);
1069
1070 if (noblock)
1071 return -EAGAIN;
1072 goto try_again;
1073 }
これはソケットキューイングの失敗です。sock_queue_rcv_skb()
はバッファーメモリーチェックです。
(注記: これにより、netstat -s
の RcvbufErrors
である UDP_MIB_RCVBUFERRORS
も増加します)
Line 1132 net/ipv4/udp.c
UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
1117 static int __udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
1118 {
1119 int rc;
1120
1121 if (inet_sk(sk)->daddr)
1122 sock_rps_save_rxhash(sk, skb->rxhash);
1123
1124 rc = sock_queue_rcv_skb(sk, skb);
1125 if (rc < 0) {
1126 int is_udplite = IS_UDPLITE(sk);
1127
1128 /* Note that an ENOMEM error is charged twice */
1129 if (rc == -ENOMEM)
1130 UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_RCVBUFERRORS,
1131 is_udplite);
1132 UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
1133 kfree_skb(skb);
1134 trace_udp_fail_queue_rcv_skb(rc, sk);
1135 return -1;
1136 }
1137
1138 return 0;
1139 }
以下は、IPSec セキュリティーポリシーチェック、UDP チェックサム、および完全なソケットと完全なソケットバックログのチェックです。
Line 1250 net/ipv4/udp.c
UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
1141 /* returns:
1142 * -1: error
1143 * 0: success
1144 * >0: "udp encap" protocol resubmission
1145 *
1146 * Note that in the success and error cases, the skb is assumed to
1147 * have either been requeued or freed.
1148 */
1149 int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
1150 {
...
1155 /*
1156 * Charge it to the socket, dropping if the queue is full.
1157 */
1158 if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
1159 goto drop;
1160 nf_reset(skb);
...
1227 if (sk->sk_filter) {
1228 if (udp_lib_checksum_complete(skb))
1229 goto drop;
1230 }
1231
1232
1233 if (sk_rcvqueues_full(sk, skb, sk->sk_rcvbuf))
1234 goto drop;
1235
1236 rc = 0;
1237
1238 bh_lock_sock(sk);
1239 if (!sock_owned_by_user(sk))
1240 rc = __udp_queue_rcv_skb(sk, skb);
1241 else if (sk_add_backlog(sk, skb, sk->sk_rcvbuf)) {
1242 bh_unlock_sock(sk);
1243 goto drop;
1244 }
1245 bh_unlock_sock(sk);
1246
1247 return rc;
1248
1249 drop:
1250 UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
1251 kfree_skb(skb);
1252 return -1;
1253 }
上記の関数は、"ソケット受信バックログ" という概念を使用します。これは、通常のソケットバッファーの外側にあるソケットバッファースペースです。
受信バックログは、カーネルがソケットバッファーにデータを入れようとしたが、ソケットバッファーがすでにユーザー空間からアクセスされている場合に使用されます。ユーザー空間がソケットをロックおよびロック解除すると、ユーザー空間のロックコードはバックログキューを処理し、ソケットバックログデータをユーザー空間に提供します。
バックログチェックは、次のインライン関数で説明されています。
681 /* The per-socket spinlock must be held here. */
682 static inline __must_check int sk_add_backlog(struct sock *sk, struct sk_buff *skb,
683 unsigned int limit)
684 {
685 if (sk_rcvqueues_full(sk, skb, limit))
686 return -ENOBUFS;
687
688 __sk_add_backlog(sk, skb);
689 sk_extended(sk)->sk_backlog.len += skb->truesize;
690 return 0;
691 }
667 /*
668 * Take into account size of receive queue and backlog queue
669 * Do not take into account this skb truesize,
670 * to allow even a single big packet to come.
671 */
672 static inline bool sk_rcvqueues_full(const struct sock *sk, const struct sk_buff *skb,
673 unsigned int limit)
674 {
675 unsigned int qsize = sk_extended(sk)->sk_backlog.len +
676 atomic_read(&sk->sk_rmem_alloc);
677
678 return qsize > limit;
679 }
ソケット受信バックログは、バックログによって消費されるスペースの量がユーザー空間に公開されないため、トラブルシューティングが特に困難です。
以下は、メモリー領域、短いパケットの長さ、UDP チェックサム、およびソケットが実在するかをチェックします。
Line 1440 net/ipv4/udp.c
UDP_INC_STATS_BH(net, UDP_MIB_INERRORS, proto == IPPROTO_UDPLITE);
1339 /*
1340 * All we need to do is get the socket, and then do a checksum.
1341 */
1342
1343 int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
1344 int proto)
1345 {
...
1353 /*
1354 * Validate the packet.
1355 */
1356 if (!pskb_may_pull(skb, sizeof(struct udphdr)))
1357 goto drop; /* No space for header. */
...
1364 if (ulen > skb->len)
1365 goto short_packet;
...
1367 if (proto == IPPROTO_UDP) {
1368 /* UDP validates ulen. */
1369 if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))
1370 goto short_packet;
1371 uh = udp_hdr(skb);
1372 }
1373
1374 if (udp4_csum_init(skb, uh, proto))
1375 goto csum_error;
...
1383 if (sk != NULL) {
1384 int ret;
1385
1386 sk_mark_napi_id(sk, skb);
1387 ret = udp_queue_rcv_skb(sk, skb);
1388 sock_put(sk);
1389
1390 /* a return value > 0 means to resubmit the input, but
1391 * it wants the return to be -protocol, or 0
1392 */
1393 if (ret > 0)
1394 return -ret;
1395 return 0;
1396 }
1397
1398 if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
1399 goto drop;
1400 nf_reset(skb);
1401
1402 /* No socket. Drop packet silently, if checksum is wrong */
1403 if (udp_lib_checksum_complete(skb))
1404 goto csum_error;
1405
1406 UDP_INC_STATS_BH(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
1407 icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
1408
1409 /*
1410 * Hmm. We got an UDP packet to a port to which we
1411 * don't wanna listen. Ignore it.
1412 */
1413 kfree_skb(skb);
1414 return 0;
1415
1416 short_packet:
1417 LIMIT_NETDEBUG(KERN_DEBUG "UDP%s: short packet: From %pI4:%u %d/%d to %pI4:%u\n",
1418 proto == IPPROTO_UDPLITE ? "-Lite" : "",
1419 &saddr,
1420 ntohs(uh->source),
1421 ulen,
1422 skb->len,
1423 &daddr,
1424 ntohs(uh->dest));
1425 goto drop;
1426
1427 csum_error:
1428 /*
1429 * RFC1122: OK. Discards the bad packet silently (as far as
1430 * the network is concerned, anyway) as per 4.1.3.4 (MUST).
1431 */
1432 LIMIT_NETDEBUG(KERN_DEBUG "UDP%s: bad checksum. From %pI4:%u to %pI4:%u ulen %d\n",
1433 proto == IPPROTO_UDPLITE ? "-Lite" : "",
1434 &saddr,
1435 ntohs(uh->source),
1436 &daddr,
1437 ntohs(uh->dest),
1438 ulen);
1439 drop:
1440 UDP_INC_STATS_BH(net, UDP_MIB_INERRORS, proto == IPPROTO_UDPLITE);
1441 kfree_skb(skb);
1442 return 0;
1443 }
以下は、データが待機中の UDP RPC トランスポートソケットへの RPC コールバックからのものです。チェックサムの失敗後に増加します。
Line 1037 net/sunrpc/xprtsock.c
UDPX_INC_STATS_BH(sk, UDP_MIB_INERRORS);
IPv6 UDP には UDP_MIB_INERRORS
が増加する場所が他にもありますが、現時点ではこのナレッジベースソリューションではカバーされていません。
This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.
Comments