Windows」カテゴリーアーカイブ

Ubuntu 26.04 で Gnome Remote Desktop を使って Hyper-V の Enhanced Session Modeを動かす話

どうも、みむらです。

前回の記事が 2024年だそうです。今ほど 生成AI万歳な世の中ではなかったことを考えると、ここ1~2年で結構変わったなと思う今日この頃です。

Ubuntu もそのような時代の流れの中で X から Wayland に本格的に切り替わり、タイトルにあるような Enhanced Session Mode (拡張セッションモード) がうまく動かなくなりました。
※backend が xrdp であり、xorg + xrdp になっていたため。そして 2024 時点でも X は backports だったと思います。。

というわけで最新の環境でも使えるように Ubuntu 26.04 と Remote Desktop 機能と freerdp-proxy を組みあわせて良い感じに有効化できましたので紹介します。


シンプルに:何をしたのか

Hyper-V で Enhanced Session Mode の EnhancedSessionTransportType の設定を HvSocket にした上で、下記を行いました。

参考: EnhancedSessionTransportType の設定方法 (Windows 上で実施)

Get-VM -Name "Ubuntu" | Set-VM -EnhancedSessionTransportType HvSocket

Linux 上で行ったこと:

  1. Ubuntu の Remote Desktop 内の Remote Login を有効にする
  2. freerdp-proxy をインストール
  3. 下記の設定ファイルとともに freerdp-proxy を起動 (ユーザ名とパスワードはリモートログインのユーザ名・パスワードに合わせる)
[Server]
Host=vsock://-1
Port=3389

[Target]
FixedTarget=true
Host=127.0.0.1
Port=3389
User=(Username)
Password=(Password)
Domain=.

[Channels]
GFX=true
DisplayControl=true
Clipboard=true
AudioInput=true
AudioOutput=true
DeviceRedirection=true
VideoRedirection=true
CameraRedirection=true
PassthroughIsBlacklist=true
Passthrough=

[Input]
Keyboard=true
Mouse=true
Multitouch=true

[Security]
ServerTlsSecurity=false
ServerNlaSecurity=false
ServerRdpSecurity=true
ClientTlsSecurity=true
ClientNlaSecurity=true
ClientRdpSecurity=true
ClientAllowFallbackToTls=true

[Certificates]
CertificateFile = (Certificate File Path)
PrivateKeyFile = (Private Key File Path)

うまくいけば Hyper-V 側で Enhanced Session Mode に切り替えるかを聞いてきます。


くわしく:何をしたか

Ubuntu 26.04 のクリーンインストール状態を用いて紹介します。

1. 必要なパッケージをインストール

xfreerdp-proxy と winpr-makecert (winpr-utils) を準備します。

$ sudo apt install freerdp-proxy winpr-utils

2. リモートデスクトップを有効にする

「設定」を開き、「システム」「リモートデスクトップ」と遷移します。

表示された画面で「リモートログイン」を選択し「ロック解除」を選択します。
パスワードが聞かれますのでログイン中のユーザのパスワードを入力します。

「ユーザ名」を設定し、「新しいパスワードを生成」をクリックしパスワードを生成した後、パスワードを可視状態にして、メモをしておきます。

今回は下記の設定で以降設定を行っていきます。
(この ID とパスワードで接続可能になるため、設定の際は安全のため皆さんの環境で生成したパスワードを利用してください。)

3.設定ファイルを作る

今回の例ではホームディレクトリ下に hv-proxy というディレクトリを作り、その中で作業していきます。

$ mkdir hv-proxy
$ cd hv-proxy

3.1. 鍵を作る

まず、freerdp で使う鍵ファイルを作ります。

$ winpr-makecert -rdp -path . rdp
$ ls
rdp.crt rdp.key

rdp.crt および rdp.key が出来ていれば OK です。

3.2. 設定ファイルを作る

テンプレートファイルを作成し、そのファイルを編集しながら下記の内容の設定ファイルを作ります。

$ freerdp-proxy --dump-config proxy.ini

ディレクトリに proxy.ini が作成されます。これを編集して下記のように設定ファイルを書き換えます。

注:User と Password は上記で生成したサンプル例にしています。
注2:「#」で始まる行は実際の設定ファイルには不要です。

[Server]
Host=vsock://-1
Port=3389

[Target]
FixedTarget=true
Host=127.0.0.1
Port=3389
# 注: 以下は実際に設定したユーザ名にする
User=hyper-v-enhancedsession
# 注: 以下は実際に設定 or 生成したパスワードにする
Password=TweezersDucklingUpfrontLeggingsAzalea
Domain=.

[Channels]
GFX=true
DisplayControl=true
Clipboard=true
AudioInput=true
AudioOutput=true
DeviceRedirection=true
VideoRedirection=true
CameraRedirection=true
PassthroughIsBlacklist=true
Passthrough=

[Input]
Keyboard=true
Mouse=true
Multitouch=true

[Security]
ServerTlsSecurity=false
ServerNlaSecurity=false
ServerRdpSecurity=true
ClientTlsSecurity=true
ClientNlaSecurity=true
ClientRdpSecurity=true
ClientAllowFallbackToTls=true

[Certificates]
CertificateFile=./rdp.crt
PrivateKeyFile=./rdp.key

設定ファイルが出来たら実際に動かしてみます。

4.動かす

設定ファイルが出来たら、Ubuntu をシャットダウンします。

Microsoft のドキュメント上からは見つかりませんでしたが、
VMが稼働し始めて一定時間たった後だと listen してもつなぎに来ないように見えています。このため一度シャットダウン (= VM の停止) を行い、再度見に来るように仕向けます。

VMが停止したのち、再度起動します。

起動したら、CTRL+F3 を押下して仮想コンソールに切り替え、
その画面にてログイン後、ディレクトリを移動して freerdp-proxy を起動します。

$ cd hv-proxy
$ freerdp-proxy ./proxy.ini

うまくいくと下記のように、拡張セッションへの切り替えを聞いてきます。

あとは OK を押すと画面が表示されますので、ログインして普段通り利用可能になります。


自動で立ち上がるようにしてみる

毎回立ち上げるのも手間ですので、systemd のサービスで自動的に立ち上がるようにしてみます。

“/opt/hv-proxy” ディレクトリを作成して、その中に鍵ファイルやら設定ファイルを保存する構成想定です。

下記の内容を /etc/systemd/system/hv-proxy.service として保存して、有効にします。
(お行儀良くやる場合は /etc/systemd/user/ 以下に保存して有効にすると良いと思います)

[Unit]
Description=Hyper-V Enhanced Session Proxy
After=gdm.service
Wants=network-online.target

[Service]
Type=simple
DynamicUser=yes
ExecStart=/usr/bin/freerdp-proxy /opt/hv-proxy/proxy.ini
Restart=on-failure
Environment=HOME=/opt/hv-proxy/
ReadOnlyPaths=/opt/hv-proxy/
RestrictAddressFamilies=AF_INET AF_VSOCK AF_UNIX

[Install]
WantedBy=multi-user.target

また上記の環境をより自動的にやる場合は、grdctl を組み合わせて自動でリモート設定も動かすと良いと思います。(未検証)


というわけで、xrdp でやるよりもずっと良い感じに動く環境が作れました。

これから X から wayland への切り替えは進んでいくと思います。そのときにこの方法で Enhanced Session をスムーズに有効化できたら(そしてそのヒントとなれば)と思っています。

Hyper-V 上の Linux から GPU を利用する話

どうも、みむらです。

最近は在宅時は NACK5 さんを聞きながら仕事をしていることが増えているのですが、
特に「日常野郎 鬼ガイバー!」と「あまがみ神社」に特にウキウキする今日この頃です。

さてさて。

Hyper-V 上の Linux 上で GPU を使うというのがあります。
主に GPU で機械学習をしたり動画処理したり、私ですと職業柄 hashcat を回してみたり。

色々な記事はあったのですが Linux カーネルを新しめ (執筆時点では 6.8) のもので組むのには、少し手間が掛かってしまったのでここにまとめてみようかと。

なおこの記事は「GPU パーティション分割」機能を用いた構築例となります。
GPU を直接触るものではないため、使い勝手は少し異なりますが、ホスト側と共存して使えるようになるため、個人的にはオススメです。

詳細についてはこのあたりを参照ください:
https://learn.microsoft.com/ja-jp/windows-server/virtualization/hyper-v/gpu-partitioning

今回は、諸事情で Kali Linux (Debian) での紹介となりますが、素のDebian や Ubuntu はもちろん、他のディストリビューションでも転用出来ると思います。


1. ゲスト側の Linux で必要なファイルを WSL2 環境から取り出す

WSL のシステムディストリビューションを起動し、中から必要なファイルを取り出します。

WSL2 はユーザがインストールしたディストリビューション(ユーザディストリビューション)をコンテナとして動かす仕組みになっています。
システムディストリビューションは、それらのコンテナにむけて機能を提供するホストとなっており、WSL が提供する機能のドライバ類はここに入っています。

余談ですが、とくに手を入れていなければ CBL-Mariner が走っています。
https://github.com/microsoft/azurelinux
Azure に馴染みのある方であれば、 “Azure Linux” と呼ばれている軽量な Linux システムとして見たことがあるかたもいらっしゃると思います。

今回ここから取り出すのは、/usr/lib/wsl/lib 以下にあるファイル群になります。


Windows 環境側に作業用フォルダを作り、必要なファイルをコピーして tar を作ります。

なお下記のコマンド例では、作業用フォルダのパスは「 C:\Users\mimura1133\Desktop\work_wsl 」にあるものとします。

方法1:ディスク容量を気にしない場合

この場合はシンプルに
/usr/lib/wsl 以下を tar 固めなどして Windows 環境側に持ち出せばOKです。

ただし、不要なドライバが大量に含まれるため、かなり大きくなります。
(私の環境では 3GB ほどありました)

cd /usr/lib/wsl
bsdtar cf - lib | gzip > /mnt/(Windows 側作業フォルダまでのパス)/lib.tar.gz
bsdtar cf - drivers | gzip > /mnt/(Windows 側作業フォルダまでのパス)/drivers.tar.gz

# コマンド実行例:
# cd /usr/lib/wsl
# bsdtar cf - lib | gzip > /mnt/c/Users/mimura1133/Desktop/work_wsl/lib.tar.gz
# bsdtar cf - drivers | gzip > /mnt/c/Users/mimura1133/Desktop/work_wsl/drivers.tar.gz

以上の手順で作成された lib.tar.gz と drivers.tar.gz が後ほど必要になります。

方法2:必要最小限のデータのみを取り出す場合

下記の手順では必要最小限のデータのみを取り出します。
(この方式では、当方環境ですと 1GB 未満になりました)

1. /usr/lib/wsl/lib ディレクトリ以下を取り出す

cd /usr/lib/wsl
bsdtar cf - lib | gzip > /mnt/(Windows 側作業フォルダまでのパス)/lib.tar.gz

# コマンド実行例:
# cd /usr/lib/wsl
# bsdtar cf - lib | gzip > /mnt/c/Users/mimura1133/Desktop/work_wsl/lib.tar.gz

2. 現在使用しているグラフィックドライバをコピーする

Windows 側の PowerShell で下記のコマンドを実行して、ドライバを特定します。

Get-CimInstance -ClassName Win32_VideoController -Property * | Format-Table InstalledDisplayDrivers -AutoSize -Wrap

実行例:

上記の例の場合は、 C:\Windows\System32\DriverStore\FileRepository\nv_dispsig.inf_amd64_e6cac7f31a92d62e 以下に必要なファイル群があることが分かります。

上記の結果を用いて、必要なドライバをフォルダごと作業フォルダの “drivers” 以下にコピーします。

# カレントディレクトリ移動
cd C:\Windows\System32\DriverStore\FileRepository
# ドライバをフォルダごとコピー
robocopy /mir (必要なドライバのフォルダ名) (作業フォルダまでのパス)\drivers\(必要なドライバのフォルダ名)
# ini ファイルをコピー
copy (必要なドライバのフォルダ名).ini (作業フォルダまでのパス)\drivers

# コマンド実行例:
# cd C:\Windows\System32\DriverStore\FileRepository
# robocopy /mir nv_dispsi.inf_amd64_2a40de5cdd82b074 C:\Users\mimura1133\Desktop\work_wsl\drivers\nv_dispsi.inf_amd64_2a40de5cdd82b074
# copy nv_dispig.inf_amd64_0afec3f2050014a0.ini C:\Users\mimura1133\Desktop\work_wsl\drivers

コピーが完了した後、drivers フォルダの中は下記のような状態になります。

3.ドライバを tar で固める

WSL2 が動く Windows であれば、最初から tar が使えるようになっています。
コマンドプロンプトを用いて下記のコマンドを実行します。

cd (作業フォルダまでのパス)
tar zcvf drivers.tar.gz drivers

実行例:

以上で drivers.tar.gz と lib.tar.gz が出来ました。以降でこれらのファイルが必要になります。


2. Kali Linux をインストールする

通常通りのインストールを実施します。

公式サイトからの VM イメージのダウンロード、iso を用いたインストール、Hyper-V のクイック作成等方法は問いません。

なお、iso からインストールを行う場合は「第2世代」で作成を行ってください。


3. 仮想マシン (VM) の設定を変更する

VMの設定を行います。

既にVM が起動している場合はシャットダウンをおこなった上で
VMの設定画面を開き、いくつか確認を行っていきます。

・セキュアブートが無効になっているか
 ON の場合、後段の dkms 周りで不具合が出る場合があります。

・動的メモリは無効になっているか
 仕組み上動的メモリを有効にすることは出来ないようです。

チェックポイント(スナップショット)は無効になっているか
 これも、本機能有効時には使えないようです。


上記までの設定が終わったら “OK” を押して画面を閉じます。

続けて Powershell を管理者権限で起動し、下記の設定を順に行います。

# VM の情報を取得
$vm = Get-VM -Name "<VM名, ワイルドカードも使えます>"

# GPU の追加
$vm | Add-VMGpuPartitionAdapter

# Memory Mapped I/O 領域の設定
$vm | Set-VM -GuestControlledCacheTypes $true -LowMemoryMappedIoSpace 1GB -HighMemoryMappedIoSpace 32GB

4. VM 内に必要なデータをコピーする

VM を起動し、1つめのステップで作成した lib.tar.gz と drivers.tar.gz を Linux VM 内にコピーします。

※ 2024.06.17 現在、Kali Linux ではこの段階では GUI (Xorg) の起動に失敗します。
後段でGUI 起動が出来るようになりますのでご安心ください。

ファイルのコピーには SCP や curl 等を用いることもできますが、
一番やりやすい方法としては “Copy-VMFile” を用いるのが一番やりやすいかと思います。

管理者権限で起動した PowerShell を用いて下記のように入力します。

# VM の情報を取得。
# (Step 3 で行っている場合は下記コマンドは不要)
$vm = Get-VM -Name "<VM名, ワイルドカードも使えます>"

# ファイルコピー
$vm | Copy-VMFile -FileSource Host -SourcePath .\lib.tar.gz -DestinationPath /
$vm | Copy-VMFile -FileSource Host -SourcePath .\drivers.tar.gz -DestinationPath /

上記手順が完了次第、Linux VM にログインし、
ルート直下に lib.tar.gz と drivers.tar.gz があることを確認します。

(Ctrl + Alt + F1 を押下してログイン画面を表示し、ログインします。)

実行例:


5. コピーした tar.gz ファイルを展開する

コピーしたファイルを VM 内の /usr/lib/wsl 以下に展開します。

# ディレクトリを作成
sudo mkdir /usr/lib/wsl

# lib を展開
sudo tar zxvf lib.tar.gz -C /usr/lib/wsl

# drivers を展開
sudo tar zxvf drivers.tar.gz -C /usr/lib/wsl

# 権限を設定
sudo chmod -R 0555 /usr/lib/wsl

コピー後、下記のようなディレクトリ構造になっていることを確認出来れば OK です。
( nv_dispsig.inf およびそれ以下の部分は、コピーした内容や環境の違いで変わります )


6. コピーしたファイルにパスを通す

下記コマンドを実行しパスを通します。

echo "/usr/lib/wsl/lib" | sudo tee /etc/ld.so.conf.d/ld.wsl.conf
sudo ldconfig

# libcuda.so.1 に関するエラーが出ますが、無視してOKです。

7. カーネルドライバをインストールする

WSL2 に組み込まれているカーネルモードドライバを組み込みます。

この部分のコードは WSL2 のカーネルのソースコードにしかなく、通常、最新のカーネルコードを用いる場合Microsoft 社が公開しているコードでは手入れが必要です。

とても親切なかたがインターネット上にはいらっしゃって、
最新のカーネルで上手く動くようにカスタムして公開してくれています。
https://github.com/Nevuly/WSL2-Linux-Kernel-Rolling

今回はここのコードを用いて、今走っているカーネル向けのドライバを作って入れ込む戦略を採ります。入れ込む部分のコードについても、下記に公開されているものをお借りして進めます。

https://gist.github.com/krzys-h/e2def49966aa42bbd3316dfb794f4d6a


・必要なプログラム・ファイルのインストール

sudo apt install linux-headers-amd64 dkms git

・カーネルモードドライバの作成・インストール

先ほどのサイトからお借りして少し編集したものを下記に示します。
こちらを実行すれば必要なドライバがビルドされてインストールされます。

#!/bin/bash -e

#
# The original version is https://gist.github.com/krzys-h/e2def49966aa42bbd3316dfb794f4d6a
#

if [ "$EUID" -ne 0 ]; then
    echo "Swithing to root..."
    exec sudo $0 "$@"
fi

git clone --depth=1 https://github.com/Nevuly/WSL2-Linux-Kernel-Rolling
cd WSL2-Linux-Kernel-Rolling
VERSION=$(git rev-parse --short HEAD)

cp -r drivers/hv/dxgkrnl /usr/src/dxgkrnl-$VERSION
mkdir -p /usr/src/dxgkrnl-$VERSION/inc/{uapi/misc,linux}
cp include/uapi/misc/d3dkmthk.h /usr/src/dxgkrnl-$VERSION/inc/uapi/misc/d3dkmthk.h
cp include/linux/hyperv.h /usr/src/dxgkrnl-$VERSION/inc/linux/hyperv_dxgkrnl.h
sed -i 's/\$(CONFIG_DXGKRNL)/m/' /usr/src/dxgkrnl-$VERSION/Makefile
sed -i 's#linux/hyperv.h#linux/hyperv_dxgkrnl.h#' /usr/src/dxgkrnl-$VERSION/dxgmodule.c
echo "EXTRA_CFLAGS=-I\$(PWD)/inc" >> /usr/src/dxgkrnl-$VERSION/Makefile

cat > /usr/src/dxgkrnl-$VERSION/dkms.conf <<EOF
PACKAGE_NAME="dxgkrnl"
PACKAGE_VERSION="$VERSION"
BUILT_MODULE_NAME="dxgkrnl"
DEST_MODULE_LOCATION="/kernel/drivers/hv/dxgkrnl/"
AUTOINSTALL="yes"
EOF

dkms add dxgkrnl/$VERSION
dkms build dxgkrnl/$VERSION
dkms install dxgkrnl/$VERSION

上記の内容をダウンロードしやすいよう下記の gist にもアップロードしました。
よろしければご利用ください。

https://gist.github.com/mimura1133/895b2f5f79ca1de1fbd7b0acf10358d6


8. CUDA の環境をインストールする

nvidia-cuda-toolkit のインストールと、ライブラリのリンクの張り替えを行います。

# cuda toolkit のインストール
sudo apt install nvidia-cuda-toolkit

# 不要な追加パッケージの削除
sudo apt remove nvidia-kernel-dkms nvidia-modprobe nvidia-kernel-common

# libcuda.so が cuda toolkit のモノになっているので張り替える。
sudo rm /usr/lib/x86_64-linux-gnu/libcuda.so
sudo rm /usr/lib/x86_64-linux-gnu/libcuda.so.1
sudo ln -s /usr/lib/wsl/lib/libcuda.so /usr/lib/x86_64-linux-gnu/libcuda.so
sudo ln -s /usr/lib/wsl/lib/libcuda.so.1 /usr/lib/x86_64-linux-gnu/libcuda.so.1

ここまで来たら一度再起動を行います。


再起動完了後、nvidia-smi を実行すると下記のような表示となり、
GPU (CUDA) が利用可能な状態になります。

また CUDA を用いるアプリケーションにおいても正常に認識され、 Windows 側のタスクマネージャからも GPU が動作していることが分かります。

CUI で計算を行う範囲で十分であれば、ここで設定は完了となります。
以降は GUI を有効にする場合の手順となります。


(追加) 9. GUI を有効にする

GUI が立ち上がるように設定を入れ込みます。

modesetting ドライバで上手く処理が出来なくなっているのが原因であるため、
BusID とドライバの設定を行い動作するようにします。

内部に露出している PCI の BusID を調べる

一度 xorg を起動して失敗させ、その中のログから調べるのが手っ取り早いです。

# Xorg の起動を試みる。(失敗してエラーが表示されます)
sudo startx

# ログの中から PCI Bus の ID を見つける
grep "PCI" /var/log/Xorg.0.log

# --
# 実行例
# --
grep "PCI" /var/log/Xorg.0.log
[   3.668] (--) PCI:*(0@41715:0:0) 1414:008e:0000:0000 rev 0

上記の実行例の場合、必要になるのはアットマーク以降の “41715:0:0” になります。

調べた BusID を設定に入れる

下記の内容を /etc/X11/xorg.conf.d/hv-fbdev.conf として保存します。

Section "Device"
         Identifier "Card0"
         Driver     "fbdev"
         BusID      "PCI:(上記で見つけた ID)"
EndSection

記入例としては下記のような形になります:

#
# 設定・記入例
#

# sudo vim /etc/X11/xorg.conf.d/hv-fbdev.conf

Section "Device"
         Identifier "Card0"
         Driver     "fbdev"
         BusID      "PCI:41715:0:0"
EndSection

その後、再起動すると GUI が立ち上がってきます。


(追加)10. Kali Linux の Enhanced Mode を有効にする

Enhanced Mode を有効にすることで、クリップボードの共有やスムーズな描画を使えるようにします。

下記ページに従って設定を行った後、追加の設定を行います。
https://www.kali.org/docs/virtualization/install-hyper-v-guest-enhanced-session-mode


kali-tweaks を実行して、初期設定を行う

下記の順番で遷移して設定を行います。
Virtualization → Configure

設定後下記コマンドを入力し、xorg を xrdp を出力先として起動するように設定します。
(先述の 9 で設定した内容と排他になり、Enhanced Mode でのみ GUI が表示されるようになります。)

# Step 9 を実行している場合は、その設定を消去する
sudo rm /etc/X11/xorg.conf.d/hv-fbdev.conf

# xrdp を出力先として起動するように設定する
sudo cp /etc/X11/xrdp/xorg.conf /etc/X11/xorg.conf.d/

設定後 VM をシャットダウンします。

Enhanced Session の接続方式を HVSocket に切替える

管理者権限で起動した PowerShell を用いて、下記のコマンドを実行します。

# VM の情報を取得。
# (Step 3 や 4 で既に実行済みであれば実行不要)
$vm = Get-VM -Name "<VM名, ワイルドカードも使えます>"

$vm | Set-VM -EnhancedSessionTransportType HVSocket

上記が完了したら VM の電源を投入します。

上手く行けば、起動時に下記のようなダイアログが表示されます。

接続を行い、IDとパスワードを入力後、デスクトップが出てくれば完成です。


注意・留意事項等

・マイクロソフト社やその他関連する会社様などから記事の削除・非表示化の指示を受けた場合は予告なく記事を非表示にすることがあります。

・本手法については公式のものではありません。内容に関する質問についてマイクロソフト社やその他の窓口に問い合わせすることはおやめください。また予告なく動作しなくなる場合も考えられます。

・ホスト側のグラフィックドライバの更新などのVM 内のドライバも入れ替える必要がある可能性がある、とのことです(当方環境ではまだ未確認)

執筆に際して参考にした記事など

Hyper-VでGPU(GPU-PV)を利用する方法 (Ubuntu編)
https://qiita.com/Hyper-W/items/5ddfc93891f7b620da8a

Ubuntu 21.04 VM with GPU acceleration under Hyper-V…?
https://gist.github.com/krzys-h/e2def49966aa42bbd3316dfb794f4d6a

GitHub – Nevuly/WSL2-Linux-Kernel-Rolling: Rolling Release Stable Kernel for Windows Subsystem for Linux2 (WSL2)
https://github.com/Nevuly/WSL2-Linux-Kernel-Rolling


検証しながら書いていたら、日が沈んで夜になり、また新たな朝日が昇ってきてしまいました。でもとっても楽しかったのでヨシとします・・!

それではよき GPGPU ライフを VM 内でもお過ごしください!

Jumbo Frame + WinServer 2019 の Hyper-V の通信は妙に遅い。

どうもみむらです。
最近梅雨に入ったのか夏に入ったのか、色々パッとしなくて困る日々が続いております。

先日 Windows Server 2019 (もしくは Hyper-V Server 2019) を Jumbo Frame 環境下で用いると、通信が遅くなるということが分かりましたのでメモがてら。
同じような問題に遭遇した方の一助のとなればありがたいです。


事象:

Windows Server 2019 上の Hyper-V に展開された VM において
RSC (Receive Side Coalescing, Linux では Large Receive Offload で知られている) が有効
かつ VM が Jumbo Frame の環境において、VMから見て受信方向の通信が遅くなる。

注1:Windows Server 2019 では RSC は既定で有効になっています。
注2:MTU が 2850 bytes を上回る ( >2850 ) 場合に速度低下が発生するようです。

原因:

VMSwitch において Reassemble されたパケットが VMに届かなくなる。

回避方法:

RSC を無効にするか MTU を 1500 にする

方法1:ゲスト VM の MTU を 1500 にする。
 例) ip l set mtu 1500 dev eth0

方法2:ホスト側でRSC 機能を VMSwitch 単位で無効にする
 例)Set-VMSwitch (Switch名) -EnableSoftwareRsc $false

方法3:ゲストVM において ethtool 等を用いて “large-receive-offload” を off にする
 Linux) ethtool -K eth0 large-receive-offload off
 Windows) Set-NetAdapterAdvancedProperty “*” -DisplayName “Recv Segment Coalescing” -RegistryValue 0


パケットの気持ちになってみる

最近パケットの気持ちになる、が一部界隈で有名ですので「なってみよう」と思います。

ざっくりと構成は下記の通り。
Hyper-V サーバ内に立てられた VM-01 との通信について Hyper-V サーバの前段のミラー (CAP-01) と VMSwitch のミラー (CAP-02) で通信をキャプチャして挙動を確認しよう、という構成になっています。

なお、各キャプチャは同タイミングでのものではありません。ご了承ください


VM-01 での curl での速度比較

事象を確認するためにまずは curl で適当な通信を発生させて速度を見てみます。

MTU 1500 の場合は Average Speed が 10.7M となっているのに対し
MTU 9000 の場合は Average Speed が 44024 となっています。


MTU 9000 の時の VM-01 のキャプチャ

上記のようになります。

No. 12 において Seq=141, Ack=1409 をサーバに対して返答していますが
その次にやってきたパケット (No.13) は Seq=16897 になっています。
(通常は直前の Ack と同じ番号の Seq が返ってきます)

そのため No.14 において Ack=1409 を再度送信され、
サーバ側からは No.16 において Seq=1409 の返答(再送)が起きてしまっています。


MTU9000 の時の CAP-02 のキャプチャ

上記のようになります。
No. 13 までは先ほどの VM-01 と同じような流れになっています。

ですが No.14 付近から 1474 bytes ではないパケットが流れはじめます。
またそれを境にして、 Ack と Seq の関係が壊れはじめるのも確認が出来ます。

たとえば No.14 は Seq=1409 として 2882 bytes の通信が行われていますが
その後の ACK (No.16) では Ack=1409 として返答が行われていることが分かり、
通信が正常に行われていないことがここから読み取れます。

またこの 1475 bytes でないパケットについては先述の VM-01 のキャプチャにおいては確認出来ず、その後発生した No.25 の Ack に対する No.26 の 1474 bytes の通信が行われて初めて VM-01 側にパケットが到達しているように見受けられます。


MTU9000 の時の CAP-01 のキャプチャ

上記のようになります。

インターネットを介して No.10 ~ No.19 に掛けて勢いよく通信が行われていますが
その後 No.20 において Ack=1 が返され、No.23 において再送が行われています。

この間 Seq は 12673 まで増えており、この No.19 に当該するパケットは先述の CAP-02 においても No.21 として観測できているように見受けられます。


以上の事象、また CAP-02 においてパケットをとり続けると、 reassemble されたパケットが送信された後に再送要求が発生していること、そして RSC を Disable にするとこれらの事象が解決することから RSC (LRO) が原因と判断しました。

また繰り返しではありますが、
Windows Server 2022 (Preview) や Windows 10 (21H1) では発生しないことを確認していますので Windows Server 2019 特有の問題 ( 1809 ベースの Hyper-V 特有? ) と判断しています。


番外:ドライバベースで治せないかやってみる

Linux のドライバは自由に修正したりして実験できますので、
これで何か出来ないかやってみます。

1.そもそも機能を切る:

netvsc の 603 行目に下記のような記述があります
https://github.com/torvalds/linux/blob/master/drivers/net/hyperv/netvsc.c#L603

/* Negotiate NVSP protocol version */
static int negotiate_nvsp_ver(struct hv_device *device,
			      struct netvsc_device *net_device,
			      struct nvsp_message *init_packet,
			      u32 nvsp_ver)
{

/// 省略 ///

	if (nvsp_ver >= NVSP_PROTOCOL_VERSION_61)
		init_packet->msg.v2_msg.send_ndis_config.capability.rsc = 1;

/// 省略 ///

	return ret;
}

この nvsp_ver で Windows Server 2019 とそれ以降の区別を試みましたが
共に NVSP_PROTOCOL_VERSION_61 (0x60001) が返るため、区別は出来ませんでした。

もちろんですが、”init_packet->msg.v2_msg.send_ndis_config.capability.rsc = 0;” とすると RSC の機能が恒久的に無効になります。


2.別パラメータから値を推測する等で値を修正する:

こちらですが、そもそも VMBus 経由での VMQ の割込が来ないため
修正は難しいという形になりました。

Hyper-V のネットワーク通信は下記のようなアーキテクチャになっています。

vmq コンポーネント

引用元 : https://docs.microsoft.com/ja-jp/windows-hardware/drivers/network/vmq-components

親(ホスト)が持つ NetVSP (VMSwitch) に対して VMBus 経由で接続するアーキテクチャになっており、Linux の netvsc ドライバにおいても 1665 行目付近でその接続が行われていることが伺えます。

struct netvsc_device *netvsc_device_add(struct hv_device *device,
				const struct netvsc_device_info *device_info)
{

/// 省略 ///

	/* Enable NAPI handler before init callbacks */
	netif_napi_add(ndev, &net_device->chan_table[0].napi,
		       netvsc_poll, NAPI_POLL_WEIGHT);

	/* Open the channel */
	device->channel->rqstor_size = netvsc_rqstor_size(netvsc_ring_bytes);
	ret = vmbus_open(device->channel, netvsc_ring_bytes,
			 netvsc_ring_bytes,  NULL, 0,
			 netvsc_channel_cb, net_device->chan_table);

	if (ret != 0) {
		netdev_err(ndev, "unable to open channel: %d\n", ret);
		goto cleanup;
	}

	/* Channel is opened */
	netdev_dbg(ndev, "hv_netvsc channel opened successfully\n");

	napi_enable(&net_device->chan_table[0].napi);

	/* Connect with the NetVsp */
	ret = netvsc_connect_vsp(device, net_device, device_info);
	if (ret != 0) {
		netdev_err(ndev,
			"unable to connect to NetVSP - %d\n", ret);
		goto close;
	}

/// 省略 ///

}

https://github.com/torvalds/linux/blob/9d31d2338950293ec19d9b095fbaa9030899dcb4/drivers/net/hyperv/netvsc.c#L1648


試しに netvsc_receive 関数を下記のように編集してみると下記のような出力が得られました。

static int netvsc_receive(struct net_device *ndev,
			  struct netvsc_device *net_device,
			  struct netvsc_channel *nvchan,
			  const struct vmpacket_descriptor *desc)
{
	struct net_device_context *net_device_ctx = netdev_priv(ndev);
	struct vmbus_channel *channel = nvchan->channel;
	const struct vmtransfer_page_packet_header *vmxferpage_packet
		= container_of(desc, const struct vmtransfer_page_packet_header, d);
	const struct nvsp_message *nvsp = hv_pkt_data(desc);
	u32 msglen = hv_pkt_datalen(desc);
	u16 q_idx = channel->offermsg.offer.sub_channel_index;
	char *recv_buf = net_device->recv_buf;
	u32 status = NVSP_STAT_SUCCESS;
	int i;
	int count = 0;

  // 下記行を追記
	netif_info(netdevice_ctx, rx_err, ndev, "BUF-SIZE : %u, SEC-SIZE : %u, RSC-PKTLEN %u\n",
    net_device->recv_buf_size, net_device->recv_section_size,
    nvchan->rsc.pktlen);

	/* Ensure packet is big enough to read header fields */
	if (msglen < sizeof(struct nvsp_message_header)) {
		netif_err(net_device_ctx, rx_err, ndev,
			  "invalid nvsp header, length too small: %u\n",
			  msglen);
		return 0;
	}

/// 以下省略 ///

BUF-SIZE が Window Size にちょっと足したもの、SEC-SIZE が MTU にちょっと足したものの値になり、 RSC-PKTLEN がフレームサイズと同じ値を指し示すようです。

RSC-PKTLEN の値は 1474 を示しており、冒頭のパケットキャプチャと同じような感じになっていることが読み取れます。

なお、同じ VM を同じ設定で Windows 10 の上に構築した場合は下記のようになります。

RSC-PKTLEN が十分に大きな値になっており、 RSC にて reassemble されたパケットが受信出来ていることが分かります。

ドライバを追いかけてみたのですが、正しい値が別パラメータに入っていることなどはなく、また NetVSP の割込が来ないため修正は難しいと考えられました。


まとめ

Windows Server 2019 の Hyper-V を使用して VM を作成する場合は

・MTU を 1500 以下に設定して RSO が正常に機能するようにして使う
・Jumbo Frame を有効にしたい場合は RSO を Disable にする

のどちらかで利用しないと、落とし穴があるという話です。

執筆時点の最新版である “10.0.17763.1999” でもこの事象は発生していますので、
お気をつけくださいませ。


P.S.

割と海外のフォーラムだと “RSO を無効にしたら良くなった!” 的なのはちらほら報告されているみたいですね。。修正されたらいいなとぼんやり思ってます。。

https://social.technet.microsoft.com/Forums/en-US/8aa6a88c-ffc8-4ede-abfc-42e746ff5996/windows-server-2019-hyperv-guest-on-windows-server-2019-hyperv-host?forum=winserverhyperv

https://www.doitfixit.com/blog/2020/01/15/slow-network-speed-with-hyper-v-virtual-machines-on-windows-server-server-2019/

コンソールで Optane Memory を有効にする

あけましておめでとうございます。
今年も宜しくお願い致します。

自宅内で Windows Server 2019 が走っているのですが、
大きなデータを保存しておく場所として HDD が搭載されており
これの高速化として “Optane Memory” を使おうとしてみました。

入れてみて、さて有効にしようと画面を開こうとしたところ・・

The Intel Optane memory application ran into a problem と表示される

ということで、起動が出来ません。でも Optane Memory での高速化はしたい。

ただコンソールで Optane Memory の有効化操作を行ったところ
上手く有効になりましたので、今後のメモとしてここに記録します。


記事にあたり:

インテル公式としては、 Windows Server での Optane Memory の動作は保証していないようです。
本番環境に導入するなどして障害が起きてもこちらではカバー出来ませんので、あくまでも実験用やデータが飛んでも問題ない環境でお試しください。

インテル® Optane™メモリー: 購入する前に、主な要件https://www.intel.co.jp/content/www/jp/ja/support/articles/000023994/memory-and-storage/intel-optane-memory.html


1.ドライバを入れる

セットアッププログラムを使用したり、
pnputil コマンド ( 例 : pnputil /add-driver iaStorAC.inf /install ) などでドライバをインストールします。

18.x 系のドライバが必要なため、マザーボードベンダなどで提供されているドライバが古い場合は最新版のドライバを入れておけば大丈夫だと思います。

インテル® Optane™・メモリーを備えたインテル®ラピッド・ストレージ・テクノロジードライバー・インストール・ソフトウェア
https://downloadcenter.intel.com/ja/download/29978

また、ドライバが上手く当たらない場合 (古いバージョンのまま、等) は
devcon.exe を Windows Driver Kit (WDK) から持ってきて更新することも可能です。

DevCon Update – Windows drivers | Microsoft Docs
https://docs.microsoft.com/ja-jp/windows-hardware/drivers/devtest/devcon-update


2. RSTCliPro を用意する

下記のサイトから RSTCliPro.exe をダウンロードし、
当該端末 (今回の私の場合は Windows Server 2019) にコピーしておきます。

インテル® Optane™メモリー向けインテル® RSTCLI Pro
https://downloadcenter.intel.com/ja/download/29986/-Optane-RSTCLI-Pro


3. Optane Memory と 高速化したい HDD の ID を確認する

下記のコマンドを実行します

RstCliPro.exe -I

実行すると下記のようにデバイス一覧が出てきます

そのうち、高速化したいデバイスの ID と Optane Memory の ID を探して控えます。
今回の場合は “0-0-5-0” が高速化したいデバイスで、 “0-1-0-0” が Optane Memory の ID になります。


4. Optane Memory を有効化する

先ほどメモをした ID を元に次のようなコマンドを実行します。

RstCliPro --OptaneMemory --enable --fast-drive <Optane MemoryのID> --drive-to-accel <高速化したいドライブ>

私の場合は、
“RstCliPro.exe –OptaneMemory –enable –fast-drive 0-1-0-0 –drive-to-accel 0-0-5-0”
というコマンドになります。

実行して “Enable completed” と出れば OK です。


以上で設定は完了です。

上記が完了した後 “RstCliPro.exe –OptaneMemory –info” を実行することで設定内容を確認することも可能です。

というわけで、素敵な Optane Memory ライフを!

PrimoCache を使って Intel の最新チップセットでも HDD を SSD を使って高速化する

どうもみむらです。
先日 PC が故障しまして第9世代の Intel CPU (i7-9700) を用いて組み直していました。

故障する数日前からビープ音(長音)の連続で起動に失敗したりしていたので
なんか気持ち悪いなとは思っていたのですが。。

テレワークが推奨されており、お仕事に甚大な影響が出ることもありましたので
ツクモ電機さんのレジの横にあるリーダーにクレジットカードをシャコシャコッと何度も差し入れしてきました。。来月の引き落としに今から震えています。

ただ今回買い換えを行ってみて
Intel Smart Response Technology (iSRT, HDD を SSD キャッシュで高速化するやつ) が
Intel Optane Memory との組み合わせ専用かつ、RAID 不可に変更
されており
iSRT を使って RAID-5 を高速化していた身としてはかなりショックでした。。

では iSRT の代替としていいものは無いのかと探していたところ
PrimoCacheという良さそうなものを見つけたので試してみました。


PrimoCacheとは

HDD を SSD でキャッシュして高速化するソフトです。
まさしく Intel の Smart Response Technology で実現していたものを実現してくれます。

ほかにも FuzeDrive (AMD 環境では StoreMI という名前で提供されています) というソフトなどもありますが、いくつか試してみたところ PrimoCache がよさそうな感じでした。

PrimoCache は Romex Software 社の製品で、1つ当たり $29.95 の製品になっています。
https://www.romexsoftware.com/en-us/primo-cache/

なお私自身はメーカーさんから
今回の記事に関して何か支援を受けてはいませんのであしからず。。

PrimoCache の設定をする

左上のボタンを押してドライブを追加します。

図3 : ドライブの追加ボタン

次の画面で「高速化したいドライブ」を選択します。

図4: 高速化したいドライブを設定。今回は Intel RAID 1 ドライブ。

次にキャッシュの方法についての設定です。
Level-1 Cache が “DRAM 上” のキャッシュ、 Level-2 Cache がストレージとなります。

またキャッシュについては文字通り Level1, Level2 の階層構造になっています。

今回の場合は SSD キャッシュのみを有効にするため、
Level-1 Cache は 0MB に設定します。

図5: Level-1 Cache を 0MBに設定する

次に Level-2 Cache を設定するために、画面中央の小さなボタンを押下します。

図6: Level-2 Cache の設定ボタン

ボタンを押下すると次のような画面が表示され
キャッシュ用のドライブを選択する画面になります。

今回は Intel Optane Memory を使うため
INTEL MEMPEK.. で始まるドライブを選択します。

注:もちろん Intel Optane Memory を使わずに通常の SSD でも利用可能です。
注2:認識されている容量が小さい場合は何らかのパーティションを作ってから再試行してみてください。

図7: キャッシュ用ドライブを設定している図¥

設定後、 “Size” を “MAX” に変更し、その横にあるボタンを押下します。

図8: キャッシュ設定を行う

特に何もなければ
“Individual Read/Write Cache Space” のチェックを無効にします。

これが ON の時はそれぞれのキャッシュが設定した割合で行われますが
無効の場合は割合を気にせずに Read / Write キャッシュを行うようになります。

図9: Read/Write Cache の割振設定を無効にする。

最後にお好みで Defer-Write (遅延書き込み) の設定を行います。

遅延書き込みを有効にすると応答速度が速くなりますが、
ディスクへの書き込み(キャッシュ内容の反映)が遅くなります。

図10: 設定がほぼ完了した図

設定が完了したら “Start” を押下します。

画面UI について

稼働を始めると下記のようにキャッシュ率などが表示されます。

図11: 稼働が開始した画面

ある程度動きはじめたら、画面を閉じて仕事に戻りましょう。


冒頭のとおり PC の故障で買い替えたところ
Intel RAID 構成に対する SSD キャッシュが使えなくなって呆然としていたところ
こういうソフトを見つけることができてよかったです。。!

また前回の構成では Intel RAID の RAID-5 だったのですが
iSRT が有効な環境だと最新のマザーボードや mdadm ではマウントできず焦りましたが
dmraid を使うことでマウントができました。

PrimoCache の場合は Intel RAID と SSD Cache が別の機構なため
同じようにマウントできなくなることは(きっと)ないと思いますし安心です・・!

ではでは、素敵なテレワークライフを!