投稿者「みむら」のアーカイブ

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/

VPN 越しに DLNA 経由で NAS に触る (SSDP Proxy)

どうもみむらです。

VPN 越しに NAS のコンテンツに、しかも DLNA 対応アプリ経由で触りたいと思ったことはありませんか。

数ヶ月前に NAS を購入して、速攻で静音化の記事 ( ASUSTOR DataSync Center の HDD アクセスを静かにする ) を書いたりしていたのですが、DLNA 経由で音楽ファイルに触れるようにしてみた所これが大変使いやすく、ぜひ VPN 越しにもこれを享受したい・・と。。

DLNA は UPnP を用いており、その機器発見に SSDP (1900/udp) を用いているのですが、早い話が VPN 越しにブロードキャストが上手く動作しないので、これを改善してみようというそういう話です。


1.サーバから VPN 感を感じ取れないようにする

DLNA は同一ネットワーク上での動作を想定しているものになります。
他のネットワークからアクセスしようとすると上手く動いてくれないケースがいくつか見られましたので、下記のようにしてサーバからの 「VPN 感」を少し消してみます。

NAS 側には “192.168.1.0/24” のネットワークに属しているように見せ、VPN 用に “192.168.1.224/28” のネットワークを用意しておきます。

VPN サーバには Proxy ARP の設定を入れて、192.168.1.0/24 のネットワーク上からも 192.168.1.224/28 の端末の MAC アドレスの解決 (といっても VPN サーバが指し示されるだけ)が出来るようになります。

なお、Proxy ARP については、こちらのサイトが詳しいですので参照ください:
Proxy ARP(プロキシARP)とは / ネットワークエンジニアとして
https://www.infraexpert.com/study/gateway.htm


具体的な設定方法としては
“/proc/sys/net/ipv4/conf/<device>/proxy_arp” を 1 に書き換えるか
systemd-networkd の “[Network]” セクションに “IPv4ProxyARP=yes” とする方法が
簡単かなと思います。

設定する際は、“192.168.1.0/24” の方に ProxyARP の設定を適用する形になります。

systemd-networkd の設定例)

# cat /etc/systemd/network/eth0.network
[Match]
Name=eth0

[Network]
Address=192.168.1.240/24
Gateway=192.168.1.254
IPv4ProxyARP=yes

# cat /etc/systemd/network/eth1.network
[Match]
Name=eth1

[Network]
Address=192.168.1.238/28

[Link]
MTUBytes=1200

2. SSDP をプロキシする

探索プロトコルの通信を転送するようにしてみます。
転送しないと探索が VPN のネットワーク内で閉じてしまい、本来の機器まで届かなくなってしまいます。

SSDP の中身は概ね下記のような HTTP ベースの通信になっています。

# リクエストの例。(クライアントからサーバに対して送る)
# 239.255.255.250:1900/UDP に対して送出される
---
M-SEARCH * HTTP/1.1
MX: 5
ST: urn:schemas-upnp-org:device:MediaServer:1
MAN: "ssdp:discover"
User-Agent: UPnP/1.0
Host: 239.255.255.250:1900
Connection: close

---

# 応答例。
# サーバからリクエストを行ったクライアントのポートに対してユニキャストで応答が入る
---
HTTP/1.1 200 OK
Cache-Control: max-age=1800
EXT:
Location: http://192.168.1.22:55247/dms
Server: Linux 4.0.0 UPnP/1.0
ST: urn:schemas-upnp-org:device:MediaServer:1
USN: uuid:xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx::urn:schemas-upnp-org:device:MediaServer:1
Date: Sun, 16 May 2021 19:42:50 GMT

---

見ていただくとわかるように、応答の中にユニキャストアドレスが含まれているため、発見処理のみプロキシしてあげれば後は1対1での通信となります。

ということで下記のようなプログラムを書いてみました

#!/bin/python3

#
# SSDP (DLNA の探索プロトコル) を吸って吐くプロキシ。
# Wireguard のように純粋にbridgeできないネットワーク間で使うと幸せになれる気がします。
# (使ったことでネットワークがダウン等しても保証は出来ませんので、自己責任でどうぞ。)
#
# Author : Satoshi Mimura (@mimura1133)
#

import socket
from contextlib import closing

def main():
        multicast_group = '239.255.255.250'
        src_adapter_ip = '' # DLNA クライアントがいるネットワークの IP アドレスを指定
        dst_adapter_ip = '' # DLNA サーバがいるネットワークの IP アドレスを指定
        port = 1900
        timeout = 5.0

        with closing(socket.socket(socket.AF_INET,socket.SOCK_DGRAM)) as src_sock:
            src_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
            src_sock.bind(('',port))
            src_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                                  socket.inet_aton(multicast_group) + socket.inet_aton(src_adapter_ip))

            while True:
                request_data, client_addr = src_sock.recvfrom(4096)
                with closing(socket.socket(socket.AF_INET,socket.SOCK_DGRAM)) as dst_sock:
                    dst_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(dst_adapter_ip))
                    dst_sock.settimeout(timeout)
                    dst_sock.sendto(request_data,(multicast_group,port))
                    print("\033[31m [FORWARDED:REQUEST]\033[0m {} -> {}".format(client_addr,(multicast_group,port)))
                    print(request_data)
                    while True:
                        try:
                            response_data, server_addr = dst_sock.recvfrom(4096)
                            src_sock.sendto(response_data,client_addr)
                            print("\033[32m [FORWARDED:RESPONSE]\033[0m {} -> {}".format(server_addr,client_addr))
                            print(response_data)

                        except Exception:
                            break
        return

if __name__ == '__main__':
    main()

動かしてみた結果は下図の通り。中々上手く動いてます。

今回の検証には iPhone 側に OPlayer Lite を入れて検証しています。

3.永続化する

毎回手動で起動するのは筋がよくありませんので、systemd のサービスにしてしまいましょう。

まずは、先ほどの Proxy をするプログラムからデバッグ用の print 文を消去したものを準備します。

#!/bin/python3

#
# SSDP (DLNA の探索プロトコル) を吸って吐くプロキシ。
# Wireguard のように純粋にbridgeできないネットワーク間で使うと幸せになれる気がします。
# (使ったことでネットワークがダウン等しても保証は出来ませんので、自己責任でどうぞ。)
#
# Author : Satoshi Mimura (@mimura1133)
#

import socket
from contextlib import closing

def main():
        multicast_group = '239.255.255.250'
        src_adapter_ip = '' # DLNA クライアントがいるネットワークの IP アドレスを指定
        dst_adapter_ip = '' # DLNA サーバがいるネットワークの IP アドレスを指定
        port = 1900
        timeout = 5.0

        with closing(socket.socket(socket.AF_INET,socket.SOCK_DGRAM)) as src_sock:
            src_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
            src_sock.bind(('',port))
            src_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                                  socket.inet_aton(multicast_group) + socket.inet_aton(src_adapter_ip))

            while True:
                request_data, client_addr = src_sock.recvfrom(4096)
                with closing(socket.socket(socket.AF_INET,socket.SOCK_DGRAM)) as dst_sock:
                    dst_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(dst_adapter_ip))
                    dst_sock.settimeout(timeout)
                    dst_sock.sendto(request_data,(multicast_group,port))
                    while True:
                        try:
                            response_data, server_addr = dst_sock.recvfrom(4096)
                            src_sock.sendto(response_data,client_addr)

                        except Exception:
                            break
        return

if __name__ == '__main__':
    main()

上記を /usr/local/sbin/proxy_dlna.py として保存しておきます。

保存が完了したら下記のように systemd のサービスを作成します。
今回は下記の内容を /etc/systemd/system/proxy_dlna.service として作成しました。

[Unit]
Description=Launch DLNA Proxy (/usr/local/sbin/proxy_dlna.py)
# After=wg-quick@wg0.service  #WireGuard を使っている場合はこうしておくと WireGuard が起動した後に起動するようになります。

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/proxy_dlna.py

[Install]
WantedBy=multi-user.target

保存が完了しましたら systemctl enable proxy_dlna 等で有効にすれば完了です。


手順は以上となります。

ただ上記のようなプロキシで全て動作するかというとそうではなく、特に DTCP-IP 等を用いるシステムの場合は「TTL は 3ホップ以下」「RTT は 7ms 以下」という制約が存在するため実現するのが大変難しい状況になっています。

もしそのような場合については NTT の NGN 網の特殊性を用いて回避しているケースや、L2TPv3 などを上手く組み合わせて回避しているケースがネット上にいくつか転がっていますのでそちらを用いるのが良いかなと思われます。

とはいえ、当方環境ではこれで VPN ライフがかなり改善されましたので、もしよろしければ参考にしていただけたらと!

SARA の更新はじめます。

どうも、みむらです。

サポート掲示板でよく「擬似言語シミュレータ SARA」のお問い合わせを受けるのですが、
ソースコードの欠損やバージョンの差異などによりサポートが難しい状況でした。

今回、一部データの補足を加えて Visual Studio 2019 にてビルドが出来る状況になりましたので、リファクタリングと機能追加含めてはじめていこうと考えています。
(もう Windows Forms でなくて XAML で良いよね、とか、 .NET Framework 4.8 ベースとかにしても良いよね・・とか色々と。。)

にしても最初のリリースは10年前だったんですね。。。
https://xtech.nikkei.com/it/article/NEWS/20101004/352568/

ということで、ボチボチはじめていきますー。

ASUSTOR DataSync Center の HDD アクセスを静かにする

どうもみむらです。

時代の流れに沿って(?)最近 NAS を買ってみました。

録画データを置いておいたり、時々使う ISO ファイルの保存場所に使ってみたり DLNA で色んな機器から触れるようになって QoL があがったりと「NAS は良いものだ」なんて感じている今日この頃です。

DataSync Center というソフトを入れると、外部のクラウドサービスとも同期してくれるので、スマホで撮った画像をクラウド経由で NAS に入れて DLNA で触れるようになったり・・等々、とても QoL が上がっております。

ただそんな NAS と DataSync Center なのですが、このソフトが常時 HDD にアクセスに行くために夜中も音がしてしまい、これは精神衛生上よろしくないので静音化を図ることにしてみました。


内容について

紹介する内容を実施した後で不具合が発生しても、私自身は責任を負えないため自己責任でお願い致します。

下記の内容としては稼働中の datacenter のメタデータを tmpfs に配置して解決というものになります。後半に作成物がありますので、お急ぎの方はそちらを参照ください。


1. 原因を追う

1.1. entware のインストールと SSH アクセスの許可

DataSync Center が何にアクセスしているかをトレースするため
entware のインストールと、SSH アクセスの許可を行います。

App Central から entware-ng インストール

以上の手順を踏むと “/opt/bin/opkg” から entware のパッケージマネージャが使えるようになります。


1.2. strace で挙動を見る

sudo su 等で root に登った後、下記のコマンドでインストールします。

なお opkg のパッケージは基本的に /opt 以下で触れるように配置されます。
またそれらのデータ群は /usr/local/AppCentral/entware-ng/opt/ 以下にシンボリックリンクが張られるようになるため、App Central から entware を削除すると、同時に opkg で入れたファイル群も削除されるようです。

# /opt/bin/opkg update
Downloading http://bin.entware.net/armv7sf-k2.6/Packages.gz
Updated list of available packages in /opt/var/opkg-lists/entware

# /opt/bin/opkg install strace
Installing strace (5.10-1) to root...
Downloading http://bin.entware.net/armv7sf-k2.6/strace_5.10-1_armv7-2.6.ipk
Configuring strace.

次に ps コマンドでプロセスを探します

# ps | grep datasync
 3442 root       0:00 grep datasync
 5145 ***** 1295:0 /usr/local/AppCentral/datasync-center/bin/datasync_center 1

上記の例では PID は 5145 になっていることが分かります。
では strace でアタッチして挙動を観察しましょう。

# /opt/bin/strace -p (PID)
(上記例の場合は /opt/bin/strace -p 5145 と入力する)

gettimeofday({tv_sec=1619546282, tv_usec=544859}, NULL) = 0
gettimeofday({tv_sec=1619546282, tv_usec=544976}, NULL) = 0
gettimeofday({tv_sec=1619546282, tv_usec=545057}, NULL) = 0
stat64("/usr/builtin/etc/ldapclient/asldapclient.conf", 0xbead4188) = -1 EACCES (Permission denied)
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/nas.conf", {st_mode=S_IFREG|0644, st_size=3257, ...}) = 0
open("/etc/nas.conf", O_RDONLY|O_LARGEFILE) = 12
read(12, "[Basic]\nModel = AS1002T\nVersion "..., 3257) = 3257
close(12)                               = 0
lstat64("/share", {st_mode=S_IFDIR|0755, st_size=1024, ...}) = 0
lstat64("/share/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/share/home/*******", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/share/home/*******/.datasync-center/connections/1/user_info.json.lock", O_RDWR|O_CREAT|O_LARGEFILE|O_CLOEXEC, 0777) = 12
flock(12, LOCK_EX)                      = 0
open("/share/home/*******/.datasync-center/connections/1/user_info.json_5145.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0644) = 15
write(15, "{\"quota\":1131723882496,\"normal\":"..., 109) = 109
close(15)                               = 0
rename("/share/home/*******/.datasync-center/connections/1/user_info.json_5145.tmp", "/share/home/*******/.datasync-center/connections/1/user_info.json") = 0
open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 15
_llseek(15, 0, [0], SEEK_CUR)           = 0
fstat64(15, {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
mmap2(NULL, 461, PROT_READ, MAP_SHARED, 15, 0) = 0xb6faa000
_llseek(15, 461, [461], SEEK_SET)       = 0
munmap(0xb6faa000, 461)                 = 0
close(15)                               = 0
geteuid32()                             = 1000
chmod("/share/home/*******/.datasync-center/connections/1/user_info.json", 0700) = 0
flock(12, LOCK_UN)                      = 0
close(12)                               = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({tv_sec=1, tv_nsec=0}, 

定期的な read は良いとしても 「user_info.json_5145.tmp という名前でファイルを書いて、 user_info.json を消して、user_info.json に置換する」というのを定期的に行われるのは、やはりあんまり心臓にもよろしくないですね。。

ということで、これらを tmpfs に配置するようにしてみます。
(ディスクの書き込み先を RAM 上に作成したドライブに変更して、ディスクへの書き込みを行わせない)


2. データを tmpfs に配置するようにする。

2.1. 現在のデータをコピーして取っておく

方針としては、起動時・シャットダウン時にメタデータをコピーしておき
稼働中は tmpfs に対してずっと書かせるようにしてみます。

/share/home/(USERNAME)/.datasync-center/connections 以下のデータがずっと読み書きされていますので、connections 以下を tmpfs にするように丸っとコピーを取ります。

# /volume1/.@plugins/etc/init.d/K50datasync-center start
(DataSync Center を停止する)

# cd /share/home/(USERNAME)/.datasync-center/
( ユーザ名が hogeo であれば cd /share/home/hogeo/.datasync-center/ になる.)

# ln -ln
(UID,GID を調べておく. 下記の例では UID=1000, GID=100)
drwxrwxrwt    3 1000     100             60 Apr 23 11:55 connections

# cp -R connections original_connections
(コピーを取る)

# cd original_connections
# find -name "*lock" | xargs rm
# find -name "pid" | xargs rm
# find -name "sock" | xargs rm
(最後3つは誤作動の原因になりそうなものの削除です)

2.2. 起動時・シャットダウン時の挙動をサービスとして記述する

下記のサイトを参考にすると “/volume1/.@plugins/etc/init.d/ ” あたりにスクリプトを配置すると幸せになれそうですので、ここにスクリプトを記述していきます。

Digging in to the Linux install on a NAS
https://ljones.dev/blog/digging-into-nas/

基本的には下記2つのスクリプトを作成するだけです。


/volume1/.@plugins/etc/init.d/S01prepare-datasync-center

このスクリプトでは既存の connections ディレクトリを削除して tmpfs (かつ UID, GID はユーザに合わせる) をそこにマウントさせた後、コピーしておいたものを書き戻す処理になります。

下記スクリプトを利用される際は (USERNAME) 部分および (UID) 部分をご自身の環境に合わせて書き換えてください。

#!/bin/sh -e

rm -rf /share/home/(USERNAME)/.datasync-center/connections
mkdir /share/home/(USERNAME)/.datasync-center/connections
mount tmpfs -t tmpfs /share/home/(USERNAME)/.datasync-center/connections -o uid=(UID),gid=100

cp -R /share/home/(USERNAME)/.datasync-center/original_connections/* /share/home/(USERNAME)/.datasync-center/connections/
chown -R (UID):100 /share/home/(USERNAME)/.datasync-center/connections/

/volume1/.@plugins/etc/init.d/K99store-datasync-center

このスクリプトでは既存の connections ディレクトリの中身を待避させ、かつ誤作動の原因になりそうなものを削除する内容になります。

上記同様、下記スクリプトを利用される際は (USERNAME) 部分および (UID) 部分をご自身の環境に合わせて書き換えてください。

#!/bin/sh -e

cp -R /share/home/(USERNAME)/.datasync-center/connections/* /share/home/(USERNAME)/.datasync-center/original_connections/
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "*lock" | xargs rm
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "pid" | xargs rm
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "sock" | xargs rm

上記二つのスクリプトを書き上げた後は chmod で実行権限を与えることを忘れずに。

# vi /volume1/.@plugins/etc/init.d/S01prepare-datasync-center
( スクリプトを書く。書き上がったら :wq で抜ける )
# vi /volume1/.@plugins/etc/init.d/K99store-datasync-center
( スクリプトを書く。書き上がったら :wq で抜ける )
# chmod 755 /volume1/.@plugins/etc/init.d/S01prepare-datasync-center
# chmod 755 /volume1/.@plugins/etc/init.d/K99store-datasync-center

2.3. NAS を再起動して動作することを確認する

再起動後に root で mount コマンドを実行して tmpfs がマウントされていることを確認します。

# mount
rootfs on / type rootfs (rw)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /tmp type tmpfs (rw,relatime)
/dev/md0 on /volume0 type ext4 (rw,relatime,data=ordered)
/dev/loop0 on /share type ext4 (rw,relatime)
/dev/md1 on /volume1 type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/home type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Web type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Music type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Photos type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Pictures type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Movie type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/MSDN type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Media type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
tmpfs on /share/home/******/.datasync-center/connections type tmpfs (rw,relatime,uid=****,gid=100)

また DataSync Center を開き、正常に動作していることを併せて確認します。


3. 後片付け

App Central から entware-ng の削除 (インストールした場合) と、

SSH の無効化を忘れずに行っておきましょう。


ということで・・。

ここまで完了すると、HDD への DataSync Center による常時アクセスが無くなるため、 “Disk Hibernation” の設定に従って、未使用時間は眠ってくれるようになります。

快適な睡眠も得られて、めでたしめでたし・・です!
(書いてる時間的に寝られているのか、というのは一端脇に置いておきましょう)

素敵な NAS ライフをー!

コンソールで 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 ライフを!