Translated message

A translation of this page exists in English.

Warning message

This translation is outdated. For the most up-to-date information, please refer to the English version.

"netstat -s" の出力に "Udp: packet receive errors" が多数表示される

Solution Verified - Updated -

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 -sRcvbufErrors である 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