UDPの受信エラーが発生した際の対応について

chestnut_eared_bunting02 linux

UDP受信エラーの対応時にいろいろ調べたので備忘録的にまとめてみました。
対象は Rocky8 です。

※ここでは実際に設定、動作したものを掲載していますが、内容について保証するものではありません。流用される場合は各自の責任でお願いします。

対象の環境情報

# cat /etc/redhat-release
Rocky Linux release 8.10 (Green Obsidian)

# uname -sr
Linux 4.18.0-372.9.1.el8.x86_64

受信処理

パケットの受信の流れ

(パケット)
  ↓
ネットワークアダプタ自身のバッファ
  ↓
リングバッファ
  ↓
バックログキュー
  ↓
ソケットバッファ
  ↓
アプリケーション

ネットワークアダプタの処理

パケットをフィルタリングし、自分宛てのパケットをネットワークアダプタのバッファキューに格納。
ハードウェア割り込みを発生させる。

ネットワークドライバの処理

ドライバの割り込みハンドラが起動し、DMAでパケットをリングバッファ(RX)へ移動したのち、netif_rx()を呼び出す。

カーネルおよびアプリの処理

netif_rx()はパケットをバックログ(CPU毎のキュー。遅延キューともいう?)に積み、ソフトウェア割り込みをスケジュール。
ソフトウェア割り込み処理はパケットをバックログからソケットバッファに渡す。

※ Kernel2.6 以降はデバイスドライバに対してNAPI(New API)提供されるようになり、ハードウェア割り込みを発生させず(バックログを経由せず)、ソフトウェア割り込み処理のポーリングでリングバッファからパケットを受け取るらしい。ハードウェア割り込みの負荷は減るが、ポーリングのためレイテンシが発生する可能性がある。らしい。

UDP受信エラーの考察

パケット受信時のDROPは以下の理由により発生すると考えられる。

  • バッファに空きが無い
  • パケットのフォーマットにエラーがある
  • 知らないタイプのパケット
  • システムの負荷が高く、パケット処理が追いつかない

バッファの空きがなくなる理由は以下のように想定される。(引き渡し元を下位、引き渡し先を上位として記載)

  • 上位のバッファがいっぱいでパケットを移動できず、下位バッファがパンク。
  • 上位のバッファに空きがあっても、システム高負荷(およびそれに伴うパケット処理遅延)で、下位バッファのパケットがはけずにパンク。

チューニングの基本はバッファサイズの拡張。カーネルやアプリの遅延は、別途メモリやCPUの使用率を確認し調整する。

状況確認と対応

リングバッファ

リングバッファの状況確認

リングバッファのDROPを確認。
パケット受信の前後でコマンドを実行し差分でカウントアップを確認する。

  • ifconfig で確認する場合
# ifconfig ens33
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.15.197  netmask 255.255.255.0  broadcast 192.168.15.255
        inet6 fe80::20c:29ff:fe88:f10e  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:88:f1:0e  txqueuelen 1000  (Ethernet)
        RX packets 70889788  bytes 7157952584 (6.6 GiB)
        RX errors 0  dropped 10  overruns 0  frame 0   <<ココ
        TX packets 67858701  bytes 6637676732 (6.1 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  • netstat で確認する場合
# netstat --interfaces=ens33
Kernel Interface table
Iface             MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
ens33            1500 78892766      0     10 0      75656999      0      0      0 BMRU

・RX-ERR : 受信時にエラーとなったパケット数
・RX-DRP : 受信時に破棄したパケット数
・RX-OVR : 受信時にオーバーロードとなったパケット数

(オプションについて補足)
-i : 全インターフェースを対象とした出力。
-c : 毎秒出力。
  • /proc/net/dev で確認する場合
# cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo: 15112305   37398    0    0    0     0          0         0 15112305   37398    0    0    0     0       0          0
 ens33: 7881436763 78892841    0   10    0     0          0         0 7359018619 75657101    0    0    0     0       0          0
  • ip で確認する場合
# ip -s addr show dev ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:88:f1:0e brd ff:ff:ff:ff:ff:ff
    inet 192.168.15.197/24 brd 192.168.15.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe88:f10e/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
    RX:  bytes  packets errors dropped  missed   mcast
    7158087885 70891012      0      10       0       0   <<ココ
    TX:  bytes  packets errors dropped carrier collsns
    6637771419 67859709      0       0       0       0

# ip -s link show dev ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:88:f1:0e brd ff:ff:ff:ff:ff:ff
    RX:  bytes  packets errors dropped  missed   mcast
    7158088849 70891024      0      10       0       0   <<ココ
    TX:  bytes  packets errors dropped carrier collsns
    6637773113 67859718      0       0       0       0

リングバッファの拡大

ドライバーが許可する最大値までキューのバッファー数を増やせる。

1. 現在の設定を確認(インターフェース(例ではens33)は環境に合わせて指定)

# ethtool -g ens33
Ring parameters for ens33:
Pre-set maximums:
RX:             4096  << 最大値
RX Mini:        n/a
RX Jumbo:       n/a
TX:             4096
Current hardware settings:
RX:             256  << 現在値
RX Mini:        n/a
RX Jumbo:       n/a
TX:             256

2. バッファー数を増加(暫定対応。OS再起動で揮発する)

# ethtool -G ens33 rx 512

3. 増加後の確認

# ethtool -g ens33
Ring parameters for ens33:
Pre-set maximums:
RX:             4096
RX Mini:        n/a
RX Jumbo:       n/a
TX:             4096
Current hardware settings:
RX:             512  << 増加後の値
RX Mini:        n/a
RX Jumbo:       n/a
TX:             256

バックログ

バックログの状況確認

バックログキューのチューニングが必要かどうかを判断。
パケット受信の前後でコマンドを実行し、差分でカウントアップを確認する。
下記コマンドは awk で /proc/net/softnet_stat のデータを 16進から 10進に変換している。
2番目の列の値が時間の経過とともに増加する場合は、バックログキューのサイズを増やす。

# awk '{for (i=1; i<=NF; i++) printf strtonum("0x" $i) (i==NF?"\n":" ")}' /proc/net/softnet_stat | column -t
9482      0  0    0  0  0  0  0  0  0  0  0  0
10636     0  0    0  0  0  0  0  0  0  0  0  1
11490     0  0    0  0  0  0  0  0  0  0  0  2
70512579  0  896  0  0  0  0  0  0  0  0  0  3

・最初の列   : 受信フレームの総数
・2 番目の列 : バックログキューがいっぱいであるためにドロップされたフレームの数
・最後の列   : CPU コア番号

バックログの拡大

1. 現在の設定を確認

# sysctl net.core.netdev_max_backlog
net.core.netdev_max_backlog = 1000

2. キューサイズを増加(暫定対応。OS再起動で揮発する)

# sysctl -w net.core.netdev_max_backlog=2000
net.core.netdev_max_backlog = 2000

ソケットバッファ

ソケットバッファの状況確認①

UDPパケットの受信エラーを確認する。
パケット受信の前後でコマンドを実行し、差分でカウントアップを確認する。

packet receive errors がカウントアップされている場合、パケットの受信エラーが発生している。
同時に receive buffer errors もカウントアップされていれば、バッファサイズが不足している可能性がある。
その他の原因としては、フォーマットエラー、IPSecのエラーがあるらしい。

  • netstat で確認する場合
# netstat -su
IcmpMsg:
    InType0: 9
    InType3: 60
    OutType3: 1013
    OutType8: 24
Udp:
    75387877 packets received
    1995978 packets to unknown port received
    710187 packet receive errors  <<ココ
    75426038 packets sent
    710184 receive buffer errors  <<ココ
    51386 send buffer errors
    InCsumErrors: 3
    IgnoredMulti: 4
UdpLite:
IpExt:
    InBcastPkts: 4
    InOctets: 6821027571
    OutOctets: 6315511910
    InBcastOctets: 2304
    InNoECTPkts: 78965908
MPTcpExt:
  • /proc/net/snmp で確認する場合(/proc/net/snmp は、snmp エージェントが監視、管理のために使用するファイル。)
# cat /proc/net/snmp | grep Udp:
Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti
Udp: 75387877 1995978 710187 75426038 710184 51386 3 4

ソケットバッファの状況確認②

下記コマンドで ESTAB のエントリが表示される場合、アプリケーションがバッファーから十分な速度で受信できていない可能性がある。
Recv-Q 統計がシステム全体の rmem_max(この値の確認と変更については次節を参照) に近づくなど、定期的に大きくなる場合は、ソケットバッファサイズを増やす。
※ UDP の State 表示は、送受信が行われていれば ESTAB、行われていなければ UNCONN。下記のコマンドで出力されるのはESTABの通信のみ。オプションに”a”を加えれば、UNCONNも含めて出力される。
※ Recv-Q は、受信パケットのうち、まだユーザプログラム(つまり-pで表示されるプロセス)に引き渡されていないパケットの総バイト数。

# while true; do ss -nump; sleep 1; done
Recv-Q         Send-Q         Local Address:Port       Peer Address:Port       Process

ソケットバッファの拡大

1. ソケットバッファに関する現在の設定値を確認

# sysctl -a | grep rmem
net.core.rmem_default = 212992
net.core.rmem_max = 212992
net.ipv4.tcp_rmem = 4096        87380   6291456
net.ipv4.udp_rmem_min = 4096

2. ソケットバッファのサイズ拡大(暫定対応。OS再起動で揮発する)

※ net.core.rmem_default を拡大すると、大きなサイズのソケットバッファを必要としない通信でも、最初からこのサイズを確保してしまうため、特に理由が無ければ拡大しない方がよい。

# sysctl -w net.core.rmem_max=26214400
net.core.rmem_max = 26214400

3. サイズ拡大後の確認

# sysctl -a | grep rmem
net.core.rmem_default = 212992
net.core.rmem_max = 26214400
net.ipv4.tcp_rmem = 4096        87380   6291456
net.ipv4.udp_rmem_min = 4096

パケットのフォーマットエラーの確認

パケット受信の前後でコマンドを実行し、差分でカウントアップを確認する。

# ethtool -S ens33 | grep rx_ | grep errors
     rx_errors: 0
     rx_length_errors: 0
     rx_over_errors: 0
     rx_crc_errors: 0
     rx_frame_errors: 0
     rx_missed_errors: 0
     rx_long_length_errors: 0
     rx_short_length_errors: 0
     rx_align_errors: 0
     rx_csum_offload_errors: 0

チェックサムエラーは netstat でも確認できるらしい。

# netstat -su | grep InCsumErrors
    InCsumErrors: 1

上記以外の状況確認について

dropwatch でパケットをドロップしている関数を確認

dropwatch でどの関数が何件ドロップしているかを確認でき、受信エラーの発生個所・原因の切り分けとして利用できる。

dropwatch のインストール

DROPが発生するサーバに dropwatch をインストール

# dnf install dropwatch -y
...

インストール済み:
  dropwatch-1.5-1.el8.x86_64

完了しました!

dropwatch を起動すると対話モードになる。
start コマンドでモニター開始後、DROP対象のパケットをサーバに送信すると、DROP件数とDROPした関数が表示される。
チェックサムエラーのパケットを受信したときの dropwatch によるモニターの例を以下に示す。

# dropwatch -l kas
Initalizing kallsyms db
dropwatch> start                                    << 1. モニター開始
Enabling monitoring...
Kernel monitoring activated.
Issue Ctrl-C to stop monitoring                     << 2. 左記のメッセージ表示後、別ノードからDROP対象のパケットを送信
1 drops at __udp4_lib_rcv+207 (0xffffffffb14b0ee7)  << 3. DROP件数とDROPした関数が表示される
^CGot a stop message                                << 4. Ctrl+C でモニター終了
dropwatch> exit                                     << 5. dropwatch を終了
Shutting down ...

perf でパケットをドロップしている関数を確認

perf でもDROP関数を確認できる。

perf のインストール

DROPが発生するサーバに perf をインストール

# dnf install perf -y
...

インストール済み:
  bzip2-1.0.6-28.el8_10.x86_64                                 libbabeltrace-1.5.4-4.el8.x86_64
  libtraceevent-1.5.3-1.el8.x86_64                             perf-4.18.0-553.37.1.el8_10.x86_64
  perl-Carp-1.42-396.el8.noarch                                perl-Data-Dumper-2.167-399.el8.x86_64
  perl-Digest-1.17-395.el8.noarch                              perl-Digest-MD5-2.55-396.el8.x86_64
  perl-Encode-4:2.97-3.el8.x86_64                              perl-Errno-1.28-422.el8.x86_64
  perl-Exporter-5.72-396.el8.noarch                            perl-File-Path-2.15-2.el8.noarch
  perl-File-Temp-0.230.600-1.el8.noarch                        perl-Getopt-Long-1:2.50-4.el8.noarch
  perl-HTTP-Tiny-0.074-3.el8.noarch                            perl-IO-1.38-422.el8.x86_64
  perl-IO-Socket-IP-0.39-5.el8.noarch                          perl-IO-Socket-SSL-2.066-4.module+el8.9.0+1517+e71a7a62.noarch
  perl-MIME-Base64-3.15-396.el8.x86_64                         perl-Mozilla-CA-20160104-7.module+el8.9.0+1521+0101edce.noarch
  perl-Net-SSLeay-1.88-2.module+el8.9.0+1517+e71a7a62.x86_64   perl-PathTools-3.74-1.el8.x86_64
  perl-Pod-Escapes-1:1.07-395.el8.noarch                       perl-Pod-Perldoc-3.28-396.el8.noarch
  perl-Pod-Simple-1:3.35-395.el8.noarch                        perl-Pod-Usage-4:1.69-395.el8.noarch
  perl-Scalar-List-Utils-3:1.49-2.el8.x86_64                   perl-Socket-4:2.027-3.el8.x86_64
  perl-Storable-1:3.11-3.el8.x86_64                            perl-Term-ANSIColor-4.06-396.el8.noarch
  perl-Term-Cap-1.17-395.el8.noarch                            perl-Text-ParseWords-3.30-395.el8.noarch
  perl-Text-Tabs+Wrap-2013.0523-395.el8.noarch                 perl-Time-Local-1:1.280-1.el8.noarch
  perl-URI-1.73-3.el8.noarch                                   perl-Unicode-Normalize-1.25-396.el8.x86_64
  perl-constant-1.33-396.el8.noarch                            perl-interpreter-4:5.26.3-422.el8.x86_64
  perl-libnet-3.11-3.el8.noarch                                perl-libs-4:5.26.3-422.el8.x86_64
  perl-macros-4:5.26.3-422.el8.x86_64                          perl-parent-1:0.237-1.el8.noarch
  perl-podlators-4.11-1.el8.noarch                             perl-threads-1:2.21-2.el8.x86_64
  perl-threads-shared-1.58-2.el8.x86_64

完了しました!

使い方としては、perf record でキャプチャし、perf report、perf script などで分析する、という流れ。

perf のオプションについてはヘルプを参照。
(ヘルプ抜粋)
# perf -h
 usage: perf [--version] [--help] [OPTIONS] COMMAND [ARGS]

   list            List all symbolic event types
   record          Run a command and record its profile into perf.data
   report          Read perf.data (created by perf record) and display the profile
   sched           Tool to trace/measure scheduler properties (latencies)
   script          Read perf.data (created by perf record) and display trace output
   stat            Run a command and gather performance counter statistics

# perf record -h
 Usage: perf record [<options>] [<command>]
    or: perf record [<options>] -- <command> [<options>]

    -a, --all-cpus        system-wide collection from all CPUs
    -e, --event <event>   event selector. use 'perf list' to list available events
    -g                    enables call-graph recording

1. パケットDROP時のイベント(skb:kfree_skb)を指定し、キャプチャーする。

※ skb もしくは sk_buffer は着信パケットの構造体。kfree_skb はパケットをDROPする関数もしくはイベント。

# perf record -a -g -e skb:kfree_skb sleep 10
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.163 MB perf.data (1 samples) ]

2. キャプチャされたイベント(skb:kfree_skb)を確認。

キャプチャ時 -g オプションを指定しているため、call-graph(呼び出しのスタック?)も出力されている。
内容的には、__udp4_lib_rcv() から kfree_skb() が呼び出され、パケットがDROPされている模様。

# perf script
swapper     0 [003] 782616.764116: skb:kfree_skb: skbaddr=0xffff9af4b91c7600 protocol=2048 location=0xffffffffb14b0ee7
        ffffffffb13c4803 kfree_skb+0x73 ([kernel.kallsyms])
        ffffffffb13c4803 kfree_skb+0x73 ([kernel.kallsyms])
        ffffffffb14b0ee7 __udp4_lib_rcv+0x207 ([kernel.kallsyms])
        ffffffffb1476d0e ip_protocol_deliver_rcu+0xfe ([kernel.kallsyms])
        ffffffffb1476e2d ip_local_deliver_finish+0x4d ([kernel.kallsyms])
        ffffffffb1476f20 ip_local_deliver+0xe0 ([kernel.kallsyms])
        ffffffffb14771ab ip_rcv+0x27b ([kernel.kallsyms])
        ffffffffb13e0610 __netif_receive_skb_core+0xba0 ([kernel.kallsyms])
        ffffffffb13e07bd netif_receive_skb_internal+0x3d ([kernel.kallsyms])
        ffffffffb13e1268 napi_gro_receive+0x108 ([kernel.kallsyms])
        ffffffffc011061e e1000_clean_rx_irq+0x18e ([kernel.kallsyms])
        ffffffffc010f0d7 e1000_clean+0x287 ([kernel.kallsyms])
        ffffffffb13e1b5d __napi_poll+0x2d ([kernel.kallsyms])
        ffffffffb13e2033 net_rx_action+0x253 ([kernel.kallsyms])
        ffffffffb18000d7 __softirqentry_text_start+0xd7 ([kernel.kallsyms])
        ffffffffb0cf3f3b irq_exit_rcu+0xcb ([kernel.kallsyms])
        ffffffffb0cf3f4a irq_exit+0xa ([kernel.kallsyms])
        ffffffffb1601e7f do_IRQ+0x7f ([kernel.kallsyms])
        ffffffffb1600a8f ret_from_intr+0x0 ([kernel.kallsyms])
        ffffffffb15a5ede native_safe_halt+0xe ([kernel.kallsyms])
        ffffffffb15a6286 acpi_idle_do_entry+0x46 ([kernel.kallsyms])
        ffffffffb117c95a acpi_idle_enter+0x5a ([kernel.kallsyms])
        ffffffffb135e2e6 cpuidle_enter_state+0x86 ([kernel.kallsyms])
        ffffffffb135e67c cpuidle_enter+0x2c ([kernel.kallsyms])
        ffffffffb0d25734 do_idle+0x264 ([kernel.kallsyms])
        ffffffffb0d2595f cpu_startup_entry+0x6f ([kernel.kallsyms])
        ffffffffb0c59a46 start_secondary+0x1a6 ([kernel.kallsyms])
        ffffffffb0c00107 secondary_startup_64_no_verify+0xc2 ([kernel.kallsyms])

タイトルとURLをコピーしました