フレッツ光回線でscpが遅かった話

この記事は、[Supershipグループ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/supership)の7日目の記事になります。

先日、sshを使用したファイル転送が回線速度と比べて異常に遅いという現象に遭遇したので、その際に行った調査を再現しつつ原因や対策について書いてみたいと思います。

# 要約

- OpenSSHはデフォルトでinteractiveなセッションに `af21` 、non-interactiveなセッションに `cs1` をDSCP値としてIPヘッダに設定する
- フレッツ網はIPヘッダのDSCP値を帯域優先サービスで使用しており、契約に応じて指定された優先度以外が設定されたパケットの転送は保証されない
- そのため、OpenSSHをデフォルト設定のままフレッツ網で使うと通信ができなかったり、速度低下などの悪影響を受ける可能性がある
- OpenSSHがDSCP値を設定しないようにするためには、`IPQoS none`を設定する
    - 可能であれば、ルーターでのDSCP値上書きも有効

# きっかけ

リモートワークも定着し、むしろ出社したら仕事できるのかが不安な今日この頃、その日も元気に自宅で仕事をしていました。

さて、ちょっと大きいファイルを手元のPCから遠方のサーバーへ送りたくなりました。
インターネット越しのファイル転送なので、安全・簡単にファイルを送れるscpコマンドを使います。
(ただしプロトコルとしてのSCPはdeprecatedなため[^scp]、`-s`オプションをつけて内部的にはSFTPプロトコルを使用します[^ssh-8.7])

[^scp]: [OpenSSH 8.0 Release note](https://www.openssh.com/txt/release-8.0)より、The scp protocol is outdated, inflexible and not readily fixed. We recommend the use of more modern protocols like sftp and rsync for file transfer instead.

[^ssh-8.7]: [OpenSSH 8.7 Release note](https://www.openssh.com/txt/release-8.7)より、SFTP support may be enabled via a temporary scp -s flag. It is intended for SFTP to become the default transfer mode in the near future, at which time the -s flag will be removed.
```
tango@pc % scp -s big_data.bin server:
big_data.bin                                     1%   20MB   1.2MB/s   14:25 ETA
```

お、遅い...

# 調査

自宅に引いたネット回線やPCのスペックに少々自信のある私としてはショッキングなスピードです。
俺のscpがこんなに遅いわけがない!ということで原因を調べます。

## 検証環境

調査を始める前に私が使用した環境についてまとめておきます。

- OS: PC・サーバー共にLinux
- 回線: フレッツ光ネクスト ギガタイプ (に相当する光コラボ回線) + [JPNE v6プラス 固定IPサービス](https://www.jpne.co.jp/service/v6plus-static/)
- ルーター: Yamaha RTX830

また、本記事中に出てくる通信速度の計測値については1回だけ測定したものとなります。

## 自宅回線の速度

まずは念の為、手元のPCのインターネットとの接続速度を確認します。
Googleで"speedtest"を検索し、検索結果画面で測定した結果は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2323453/7969a42d-94de-d98e-c9a5-0ddef66e4c2c.png)

非常に高速です!

## PC → サーバー間の通信速度

こちら側の回線が速くても、サーバー側の回線や間のネットワークが遅い場合はそこがボトルネックとなってしまいます。
そこで、PC→サーバーの転送速度をiperf3コマンドで計測してみましょう。

まず、PC・サーバー両方にiperf3を導入し、サーバー側ではTCPの5201番ポートをファイヤウォールで開放しておきます。
サーバー側で `iperf3 -s` と実行してiperfをサーバーとして起動した後に、PC側で `iperf3 -c <server addr>` と実行することで PC → サーバーの転送速度を測ることができます。

結果は...

```
tango@pc % iperf3 -c server
Connecting to host server, port 5201
[  5] local X.X.X.X port 59012 connected to Y.Y.Y.Y port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   103 MBytes   867 Mbits/sec  1843   1.20 MBytes
[  5]   1.00-2.00   sec   102 MBytes   860 Mbits/sec  2018   1.21 MBytes
[  5]   2.00-3.00   sec   105 MBytes   881 Mbits/sec  1903   1.17 MBytes
[  5]   3.00-4.00   sec   105 MBytes   881 Mbits/sec  1915   1.18 MBytes
[  5]   4.00-5.00   sec   105 MBytes   881 Mbits/sec  1930   1.18 MBytes
[  5]   5.00-6.00   sec   104 MBytes   870 Mbits/sec  1892   1.19 MBytes
[  5]   6.00-7.00   sec   105 MBytes   881 Mbits/sec  1921   1.39 MBytes
[  5]   7.00-8.00   sec   105 MBytes   881 Mbits/sec  1933   1.16 MBytes
[  5]   8.00-9.00   sec   104 MBytes   870 Mbits/sec  1995   1.21 MBytes
[  5]   9.00-10.00  sec   101 MBytes   849 Mbits/sec  2322   1.20 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  1.02 GBytes   872 Mbits/sec  19672             sender
[  5]   0.00-10.01  sec  1.01 GBytes   869 Mbits/sec                  receiver

iperf Done.
```

scpよりずっと早い!

## CPU負荷

さて、回線は十分速いことが確認できました。
そうすると、問題はscp特有のものだと思われます。

まず考えられるのは、sshによる暗号化の負荷が高くて転送速度が上がらないという可能性です。
(PC・サーバーともに十分な性能のものを使用しているはずなので、そうであって欲しくはないですが)
そこで、転送中にPC・サーバー双方にかかっている負荷をtopコマンドで測定してみます。

```:サーバー側top
top - 09:08:44 up  1:00,  1 user,  load average: 0.07, 0.03, 0.00
Tasks:  79 total,   1 running,  78 sleeping,   0 stopped,   0 zombie
%Cpu0  :   0.7/2.4     3[|||                                                                         ]
%Cpu1  :   0.0/0.3     0[                                                                            ]
MiB Mem :  8.3/3930.6   [||||||                                                                      ]
MiB Swap:  0.0/0.0      [                                                                            ]

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   :
   :
   4420 tango     20   0   14356   5760   4544 S   2.7   0.1   0:06.44          `- sshd
   4421 tango     20   0    6172   4620   4148 S   0.3   0.1   0:00.83              `- scp
   :
   :
```

```:PC側top
top - 18:09:19 up 20:00,  1 user,  load average: 0.00, 0.00, 0.00
Tasks: 154 total,   1 running, 153 sleeping,   0 stopped,   0 zombie
%Cpu0  :   0.3/0.0     0[                                                                            ]
%Cpu1  :   0.3/5.5     6[||||                                                                        ]
%Cpu2  :   0.0/0.0     0[                                                                            ]
%Cpu3  :   0.0/0.0     0[                                                                            ]
%Cpu4  :   0.0/0.0     0[                                                                            ]
%Cpu5  :   0.0/0.0     0[                                                                            ]
%Cpu6  :   0.0/0.0     0[                                                                            ]
%Cpu7  :   0.0/0.7     1[|                                                                           ]
MiB Mem :  9.8/23703.3  [|||||||                                                                     ]
MiB Swap:  0.0/11851.0  [                                                                            ]

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
:
:
13614 tango     20   0   10032   6408   4200 S   0.0   0.0   0:00.13      `- zsh
13781 tango     20   0    8740   4052   3656 S   0.3   0.0   0:01.02          `- scp
13782 tango     20   0   10896   6844   5188 S   1.0   0.0   0:03.18              `- ssh
:
:
```

予想はしていましたが、随分と暇そうです...

## scpコマンド以外の転送手段

こうなると、scpコマンドの実装やプロトコルに問題がある可能性があります。
scp以外の転送方法を試してみましょう。
(流石にデータを暗号化せずにインターネット上に流すわけにはいかないので、まずはsshベースの方法から確認します)

### sftp

scpコマンドの時点でSFTPプロトコルを使用しているので同じ結果になると思われますが、念の為確認します。

```
tango@pc % sftp server
Connected to server.
sftp> put big_data.bin
Uploading big_data.bin to /home/tango/big_data.bin
big_data.bin                                                           1%   18MB   1.2MB/s   14:13 ETA
```

予想通り変わらず

### pipe over ssh

シェルのパイプでファイルをsshに流し、サーバー側でddコマンドをにて書き込む速度をPC側のpvコマンドで計測します。

```
tango@pc % < big_data.bin pv | ssh server dd of=big_data.bin
13.2MiB 0:00:10 [1.10MiB/s] [>                                                        ]  1% ETA 0:12:43
```

変わらず

### iperf3 over ssh tunnel

ファイル転送特有の問題があるのでしょうか?
sshトンネル越しにiperf3を実行し、sshそのものの転送速度を計測してみます。



```:トンネル作成・iperf3サーバー起動
tango@pc % ssh -L5201:localhost:5201 server
tango@server:~$ iperf3 -s
:
:
```

```:速度計測
tango@pc % iperf3 -c localhost
Connecting to host localhost, port 5201
[  5] local ::1 port 50744 connected to ::1 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   110 MBytes   923 Mbits/sec    0    895 KBytes
[  5]   1.00-2.00   sec   102 MBytes   860 Mbits/sec    3    895 KBytes
[  5]   2.00-3.00   sec   101 MBytes   849 Mbits/sec    1    895 KBytes
[  5]   3.00-4.00   sec   101 MBytes   849 Mbits/sec    2    895 KBytes
[  5]   4.00-5.00   sec   102 MBytes   860 Mbits/sec    2    895 KBytes
[  5]   5.00-6.00   sec   101 MBytes   849 Mbits/sec    2    895 KBytes
[  5]   6.00-7.00   sec   102 MBytes   860 Mbits/sec    1    895 KBytes
[  5]   7.00-8.00   sec   100 MBytes   839 Mbits/sec    1    895 KBytes
[  5]   8.00-9.00   sec   101 MBytes   849 Mbits/sec    0    895 KBytes
[  5]   9.00-10.00  sec   102 MBytes   860 Mbits/sec    2    895 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  1.00 GBytes   860 Mbits/sec   14             sender
[  5]   0.00-10.02  sec  1016 MBytes   850 Mbits/sec                  receiver

iperf Done.
```

おや、ほぼ生iperf3と同じ速度です。

### nc over ssh tunnel

sshのトンネルだと速いことが分かったので、ncコマンドを利用してトンネルを通してファイルを転送してみましょう。

```:サーバー側
tango@server:~$ nc -l 5201 > big_data.bin
```



```:PC側(転送完了後にCtrl-Cで終了する)
tango@pc % < big_data.bin pv | nc localhost 5201
1.00GiB 0:00:10 [ 102MiB/s] [=======================================================>] 100%
```

はい、無事に高速に転送できました。
めでたしめでたし...

### scp over ssh tunnel

scpでの転送がssh tunnelでの転送より大幅に遅いことに納得できないので、本来無意味ですがsshトンネルを通してscpでファイルを転送してみます。

```:トンネル作成
tango@pc % ssh -L8000:localhost:22 server
```

```:転送
tango@pc % scp -s -P 8000 big_data.bin localhost:
big_data.bin                                                         100% 1024MB  96.9MB/s   00:10
```

速い!?
無駄に2重に暗号・復号処理が発生しているはずなのに速いです!

# 原因

## ssh v.s. ssh tunnel

ここまで確認できた現象をまとめてみます。

- ssh/scp/sftp でファイルを転送した場合、1.1MiB/s 程度
- iperf3/iperf3 over ssh tunnel で帯域を計測すると 850Mbps (≒ 101MiB/s) 以上
- nc over ssh tunnel/scp over ssh tunnel でファイルを転送した場合は 100MiB/s 程度

以上より、

- ssh tunnel では中に通すプロトコルによらず、PC → サーバー間の帯域とほぼ同等の速度で転送できる
- ssh/scp/sftp を直接使うと 1.1MiB/s 程度の速度しか出ない

ということが分かりました。

ssh tunnelとそれ以外のssh系ファイル転送手段の違いは何でしょうか?

## QoS

ネットで調べてみると、フレッツ光回線でscpができないという話がヒットします。
曰く、sshはデフォルトでIPヘッダにQoSのためのビットを設定するが、この設定をされたパケットはフレッツ網の制約によりドロップされるため通信不能になるとのことです。
ssh_config(5)manを確認すると、QoS設定を行うための設定項目 `IPQoS` について、以下のように説明されていました。

https://man.openbsd.org/ssh_config#IPQoS

> IPQoS
Specifies the IPv4 type-of-service or DSCP class for connections. Accepted values are af11, af12, af13, af21, af22, af23, af31, af32, af33, af41, af42, af43, cs0, cs1, cs2, cs3, cs4, cs5, cs6, cs7, ef, le, lowdelay, throughput, reliability, a numeric value, or none to use the operating system default. This option may take one or two arguments, separated by whitespace. If one argument is specified, it is used as the packet class unconditionally. If two values are specified, the first is automatically selected for interactive sessions and the second for non-interactive sessions. The default is af21 (Low-Latency Data) for interactive sessions and cs1 (Lower Effort) for non-interactive sessions.

どうやらデフォルトで、interactiveなセッションとnon-interactiveなセッションで異なる設定が使用されるようです。
scpやsshにパイプを組み合わせた転送はnon-interactiveなセッション、ssh tunnelはinteractiveなセッションに該当しそうなので、こいつが怪しいです。
試しに`IPQoS`を`none`に設定してscpを動かしてみます。

```
tango@pc % scp -s -o IPQoS=none big_data.bin server:
big_data.bin                                       100% 1024MB  72.2MB/s   00:14
```

速い!
オマエノシワザダタノカ......

## DSCP

`IPQoS` で指定できる値は基本的にDSCPで定められたクラスの値です。
non-interactiveセッションのデフォルト、 `cs1` は2進数で `001000`、10進数で `8` となります。

次にフレッツ網の仕様を確認してみます。
NTT東日本の[技術参考資料のページ](https://www.ntt-east.co.jp/gisanshi/)より、[IP通信網サービスのインタ フェース 第三分冊](https://flets.com/pdf/ip-int-3.pdf)に以下のように記載されています。

> 2.4.4 帯域優先に関する仕様
契約帯域の範囲で優先クラスを利用したIPv6(IPoE)通信が可能となるサービスにて、当該サービス契約者の端末機器からIP通信網に送信されるIPv6パケットにおいて、RFC2474に規定される優先度の指定が可能です。当該サービス契約者の端末機器からIP通信網に送信されるIPv6パケット(IPoE方式）のトラヒッククラスフィールドの先頭6ビットに、DSCP値として8(001000)を指定することで、優先トラヒックとして転送します。
尚、指定された優先度以外が設定されたパケットの転送は保証しません。

「契約帯域の範囲で優先クラスを利用したIPv6(IPoE)通信が可能となるサービス」というのはおそらく[フレッツ 光ネクスト プライオ](https://business.ntt-east.co.jp/service/prio/)などの法人向けサービスのことかと思われ、家庭用回線である私の家では当然そのようなサービスを契約しておりません。
そのような端末からDSCP値`8`のパケットを送出したため、転送が保証されない = パケットドロップなどによる異常な速度低下が発生したと推測できます。
(あくまで推測で、実際のところ何が起こっているのかは公開されている資料からは読み取れませんでした。詳しい人いたら教えてください)

検索すると出てくるscpが全く使えないという事象と異なり、異常な速度低下という現象が発生した理由もよくわかりませんが、以下の辺りが原因かなと思っています。

- 最近フレッツ網の仕様が変わった
- v6プラス 固定IPサービスを使用していることによる影響
- ひかり電話契約の有無やHGWの有無による違い (当環境はひかり電話無し、HGW無し)

なお、sshはIPv4で使用しているのになぜフレッツ網へ送るIPv6パケットにDSCP値が設定されるのかについては、おそらくルーター(Yamaha RTX)がipipトンネルへ送るためにIPv4パケットをカプセルする際に、IPv4ヘッダーのDSCP値を外側のIPv6ヘッダーへコピーしているためと思われます。
(こちらも推測で、実際にルータから出ているパケットをキャプチャして調べてはいません)

# 対処

アプリケーション・クライアント毎に設定するのは面倒なため、ルーター側でフレッツ網へ送るパケットのDSCP値を書き換えるような処理を行うのが理想です。
私が使用しているRTX830は、IPv4パケットについては[IP パケットの TOS フィールドの書き換え](http://www.rtpro.yamaha.co.jp/RT/manual/rt-common/ip/ip_tos_supersede.html)機能により、以下のような設定でDSCP値を書き換えることができます。

```config
ip tos supersede 10000 normal precedence=0 999000
# 10000は任意の識別番号、999000はこの機能を適用する静的フィルタの番号
```

実際に上記設定を行った後、`IPQoS`設定を削除しても転送が遅くならないことを確認しました。

ただし、IPv6パケットについてはTraffic classフィールドを書き換える機能が見当たらず、現状RTXシリーズでは対応できなさそうです。

**(2023年1月7日 追記)**
RTX830については2022年12月26日にリリース(ただし現時点で公開停止中)されたファームウェア Rev.15.02.27[^yamaha] にて、[IPパケットのDSCP値書き換え機能](http://www.rtpro.yamaha.co.jp/RT/manual/rt-common/ip/ip_dscp_supersede.html)及び[IPv6パケットのDSCP値書き換え機能](http://www.rtpro.yamaha.co.jp/RT/manual/rt-common/ipv6/ipv6_dscp_supersede.html)が追加されたようです。
現在RTX830を所有していないため未検証ですが、参考情報として記載します。

```config
ip dscp supersede 10000 default 999000
ipv6 dscp supersede 10000 default 999000
# 10000は任意の識別番号、999000はこの機能を適用する静的フィルタの番号
```

[^yamaha]: http://www.rtpro.yamaha.co.jp/RT/docs/relnote/Rev.15.02/relnote_15_02_27.html

**(追記ここまで)**

そこで、sshの設定ファイルでも対処することにします。

```:.ssh/config
Host *
	IPQoS none
```

システムグローバルで適用するのであれば、`/etc/ssh/ssh_config` での設定も可能です。

```:/etc/ssh/ssh_config
IPQoS none
```

これで、sshはDSCP値を設定しないようになり、フレッツ網でも問題なく使えるようになりました。

## サーバー側設定について

今回はクライアント側がフレッツ光回線である場合の設定ですが、サーバー側がフレッツ光回線である場合には`/etc/ssh/sshd_config`での`IPQoS`設定(もしくはルーターでの対応)が必要となります。
これは、ssh接続時に認証が完了した後、サーバー側から開始されるセッション(シェルなど)にはサーバー側の`IPQoS`設定が使用されるためです。
(sshの挙動について理解し切れていませんが、パケットキャプチャで実際にそのような挙動を確認しています)

設定自体はssh_configと同様です。

```:/etc/ssh/sshd_config
IPQoS none
```

# おわりに

本記事では最近遭遇した不思議な現象について、Advent Calendarに乗じてまとめてみました。
フレッツでsshが繋がらないという情報は調べてみるとちらほら見つかるのですが、遅くなるという情報はあまりなく、IPQoSへ辿り着くまでに時間がかかりました。
同じ現象に遭遇している人がこの記事を見つけて役に立ててもらえたら嬉しいです。

# 宣伝

リモートワークを推奨しているSupershipグループではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。
[Supershipグループ 採用サイト](https://supership-hd.jp/recruit/)
是非ともよろしくお願いします。