vmt(4) はホスト時刻同期の設定を無視する
VMware でホスト時刻同期を完全にオフにしたい
例えば 2038 年問題のテストしたいとか、ホスト時刻同期をオフにしたいケースは多々あると思う。
これが VMware-Tools(open-vm-tools) なら 公式ドキュメント の仰せの通り、適当なエディタで *.vmx を編集して
tools.syncTime = "FALSE"
time.synchronize.continue = "FALSE"
time.synchronize.restore = "FALSE"
time.synchronize.resume.disk = "FALSE"
time.synchronize.shrink = "FALSE"
time.synchronize.tools.startup = "FALSE"
を設定すればいい、ちなみに本体もツールも最新版を使ってるならば
- tools.syncTime … 定期的な時刻同期
- time.synchronize.tools.startup … 初回のみの時刻同期
の2つが設定されていれば他は不要なはずである。
ただし旧バージョンを使ってる場合、仕様がコロコロ変わってるので上記全て設定してもダメなケースもある。 よってちゃんと無効になってるかの確認はとてもだいじ、怖いですねいきあたりばったり仕様やらかすクソ実装は。
ところで現状 vmt(4) については O でも N でもこれらの設定を一切参照しないのだ。 そのため設定画面で変更しようが *.vmx ファイルを直接編集しようがなんら動作に影響を与えない。 なんてこったい!
O ではそもそも vmt(4) に時刻同期機能がない
まず O においての vmt(4) は sensor_attach(9) というフレームワークで実装されていて、あくまでホストの時刻情報を取得できるセンサーデバイスという位置づけなんですわ。
It also provides access to the host machine’s clock as a timedelta sensor.
なのでそもそもドライバに時刻同期の機能は無いのだ、やるならば
sensorsd(8)
からホスト時刻を取得して
time(1)で時刻合わせしろって感じ。
訂正、
ntpd(8)
すなわち
OpenNTPd
の時刻ソースとして vmt(4) を指定すればいいのか、次回改めて解説します。
まぁ最初っから完全オフの方がマシだと思う、仮想マシンの機能よりNTPでもGPSでもPTPでも使うべき。
N で追加された実装がとにかく雑ゥ!
そして N に移植された時に、センサーデバイスを扱う統一フレームワークなんて無いしぃ…で 唐突に ドライバ自身が時刻同期を行う という実装に書換えられてしまった、まぁいいけど…
しかもこの時点ではオフにする方法も無く強制的に時刻が同期されてしまう雑さである。
その後 sysctl(8) を使って時刻同期のインターバルを設定する 変更が入った時に、インターバル秒数に0以下を指定すれば無効になるようにはなった。
しかし注意深くコードを読んでいくと、時刻同期はデバイスアタッチ時にも問答無用で実行されるんだよね。
118 void
119 vmt_common_attach(struct vmt_softc *sc)
120 {
...
215 callout_schedule(&sc->sc_clock_sync_tick,
216 mstohz(sc->sc_clock_sync_period_seconds * 1000));
217
218 vmt_sync_guest_clock(sc);
しかも callout_schedule(9) で時刻同期用のタイマがまだ /
がマウントされる前から回りだしてるから
sysctl.conf(5)
に
machdep.vmt0.clock_sync.period=0
を設定しても無意味なのである、これはひどい。
さらにサスペンドからの復帰時にも問答無用で時刻合わせは行われる。
470 static void
471 vmt_do_resume(struct vmt_softc *sc)
472 {
473 device_printf(sc->sc_dev, "guest resuming from suspended state\n");
474
475 vmt_sync_guest_clock(sc);
つまりはだ、N でホスト時刻同期を無効にしたければ vmt(4) ごと無効にしないとダメってこと。 kernel を作り直してドライバ削るか boot.cfg(5) に
userconf=disable vmt*
とでも書いて無効化して、電源管理についてはもうあきらめるしかない。
N の実装の改善案
そもそもするならするで VMware-Tools の行儀作法に従おうよ。
じゃどうするのが正しいのかというと、VMwareで時刻同期の設定を変更すると
- Time_Synchronize
- Set_Option synctime
- Set_Option synctime.period
- Set_Option time.synchronize.tools.enable
- Set_Option time.synchronize.tools.startup
- Set_Option time.synchronize.tools.startup.backward
というイベントが TCLO 側の RPC でゲストに通知されることになってるのだ。 TCLOって何?は 過去回 で簡単に説明したので省略。
以下、それぞれのイベントハンドラ内の処理をクッソ雑に実装したものを貼る。
つーかイベントハンドラは if 文じゃなくてテーブルに書換えた方が、O はそう修正してるけど。
Time_Synchronize [Boolean] を実装する
引数が1(true)ならば時刻合わせを実行する。
--- vmt_subr.c.orig 2024-02-04 17:27:53.322098853 +0900
+++ vmt_subr.c 2024-02-04 17:28:25.794986070 +0900
@@ -687,6 +687,10 @@ vmt_tclo_tick(void *xarg)
sc->sc_rpc_error = 1;
}
}
+ } else if (strcmp(sc->sc_rpc_buf, "Time_Synchronize 1") == 0) {
+ vmt_sync_guest_clock(sc);
+ if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
+ sc->sc_rpc_error = 1;
} else {
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR) != 0) {
device_printf(sc->sc_dev, "error sending unknown command reply\n");
Set_Option synctime [Boolean] を実装する
synctime.period で指定された秒数をインターバルとして時刻同期を行う。
--- vmtvar.h.orig 2024-02-01 18:40:19.583944132 +0900
+++ vmtvar.h 2024-02-04 15:31:24.956456368 +0900
@@ -72,6 +72,8 @@ struct vmt_softc {
struct callout sc_tick;
struct callout sc_tclo_tick;
+#define VMT_CLOCK_SYNC 0x1
+ unsigned int sc_clock_sync_flags;
#define VMT_CLOCK_SYNC_PERIOD_SECONDS 60
int sc_clock_sync_period_seconds;
struct callout sc_clock_sync_tick;
--- vmt_subr.c.orig 2024-02-04 17:28:25.794986070 +0900
+++ vmt_subr.c 2024-02-04 17:30:46.425072766 +0900
@@ -164,6 +164,7 @@ vmt_common_attach(struct vmt_softc *sc)
callout_init(&sc->sc_tclo_tick, 0);
callout_init(&sc->sc_clock_sync_tick, 0);
+ sc->sc_clock_sync_flags = 0;
sc->sc_clock_sync_period_seconds = VMT_CLOCK_SYNC_PERIOD_SECONDS;
rv = vmt_sysctl_setup_root(self);
@@ -208,8 +209,6 @@ vmt_common_attach(struct vmt_softc *sc)
callout_schedule(&sc->sc_tclo_tick, hz);
callout_setfunc(&sc->sc_clock_sync_tick, vmt_clock_sync_tick, sc);
- callout_schedule(&sc->sc_clock_sync_tick,
- mstohz(sc->sc_clock_sync_period_seconds * 1000));
vmt_sync_guest_clock(sc);
@@ -241,7 +240,8 @@ vmt_common_detach(struct vmt_softc *sc)
callout_halt(&sc->sc_tclo_tick, NULL);
callout_destroy(&sc->sc_tclo_tick);
- callout_halt(&sc->sc_clock_sync_tick, NULL);
+ if (sc->sc_clock_sync_flags & VMT_CLOCK_SYNC)
+ callout_halt(&sc->sc_clock_sync_tick, NULL);
callout_destroy(&sc->sc_clock_sync_tick);
if (sc->sc_rpc_buf)
@@ -332,11 +332,17 @@ vmt_sysctl_update_clock_sync_period(SYSC
return error;
if (sc->sc_clock_sync_period_seconds != period) {
- callout_halt(&sc->sc_clock_sync_tick, NULL);
+ if (sc->sc_clock_sync_flags & VMT_CLOCK_SYNC) {
+ callout_halt(&sc->sc_clock_sync_tick, NULL);
+ sc->sc_clock_sync_flags &= ~VMT_CLOCK_SYNC;
+ }
sc->sc_clock_sync_period_seconds = period;
- if (sc->sc_clock_sync_period_seconds > 0)
+ if (sc->sc_clock_sync_period_seconds > 0) {
callout_schedule(&sc->sc_clock_sync_tick,
mstohz(sc->sc_clock_sync_period_seconds * 1000));
+ sc->sc_clock_sync_flags |= VMT_CLOCK_SYNC;
+ } else
+ sc->sc_clock_sync_period_seconds = VMT_CLOCK_SYNC_PERIOD_SECONDS;
}
return 0;
}
@@ -691,6 +697,23 @@ vmt_tclo_tick(void *xarg)
vmt_sync_guest_clock(sc);
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
sc->sc_rpc_error = 1;
+ } else if (strncmp(sc->sc_rpc_buf, "Set_Option synctime ", 20) == 0) {
+ if (sc->sc_rpc_buf[20] == '0') {
+ if (sc->sc_clock_sync_flags & VMT_CLOCK_SYNC) {
+ /* synctime on -> off */
+ callout_halt(&sc->sc_clock_sync_tick, NULL);
+ sc->sc_clock_sync_flags &= ~VMT_CLOCK_SYNC;
+ }
+ } else {
+ if ((sc->sc_clock_sync_flags & VMT_CLOCK_SYNC) == 0) {
+ /* synctime off -> on */
+ callout_schedule(&sc->sc_clock_sync_tick,
+ mstohz(sc->sc_clock_sync_period_seconds * 1000));
+ sc->sc_clock_sync_flags |= VMT_CLOCK_SYNC;
+ }
+ }
+ if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
+ sc->sc_rpc_error = 1;
} else {
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR) != 0) {
device_printf(sc->sc_dev, "error sending unknown command reply\n");
sysctl(8) まわりはこれまでの挙動との互換性を保ちたかったので安易に MIB 増やさなかった。 でも別に増やしてもいいんだけどね、どうせ使ってるやつなんて世界で3人も居ねえんだから。
Set_Option synctime.period [Integer] を実装する
引数を synctime での定期的な時刻同期のインターバルとして設定する。
--- vmt_subr.c.orig 2024-02-04 17:30:46.425072766 +0900
+++ vmt_subr.c 2024-02-04 17:33:04.888500711 +0900
@@ -523,7 +523,7 @@ vmt_tclo_tick(void *xarg)
struct vmt_softc *sc = xarg;
u_int32_t rlen;
u_int16_t ack;
- int delay;
+ int delay, period;
/* By default, poll every second for new messages */
delay = hz;
@@ -714,6 +714,21 @@ vmt_tclo_tick(void *xarg)
}
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
sc->sc_rpc_error = 1;
+ } else if (strncmp(sc->sc_rpc_buf, "Set_Option synctime.period ", 27) == 0) {
+ period = (int)strtoll(&sc->sc_rpc_buf[27], NULL, 10);
+ if (period <= 0)
+ period = VMT_CLOCK_SYNC_PERIOD_SECONDS;
+ if (sc->sc_clock_sync_period_seconds != period) {
+ if (sc->sc_clock_sync_flags & VMT_CLOCK_SYNC) {
+ callout_halt(&sc->sc_clock_sync_tick, NULL);
+ sc->sc_clock_sync_period_seconds = period;
+ callout_schedule(&sc->sc_clock_sync_tick,
+ mstohz(sc->sc_clock_sync_period_seconds * 1000));
+ } else
+ sc->sc_clock_sync_period_seconds = period;
+ }
+ if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
+ sc->sc_rpc_error = 1;
} else {
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR) != 0) {
device_printf(sc->sc_dev, "error sending unknown command reply\n");
Set_Option time.synchronize.tools.enable [Boolean]
引数が1(true)なら時刻合わせを実行する。
--- vmtvar.h.orig 2024-02-04 16:16:48.511301653 +0900
+++ vmtvar.h 2024-02-04 16:18:49.409033677 +0900
@@ -73,6 +73,7 @@ struct vmt_softc {
struct callout sc_tclo_tick;
#define VMT_CLOCK_SYNC 0x1
+#define VMT_CLOCK_SYNC_ENABLE 0x2
unsigned int sc_clock_sync_flags;
#define VMT_CLOCK_SYNC_PERIOD_SECONDS 60
int sc_clock_sync_period_seconds;
--- vmt_subr.c.orig 2024-02-04 17:33:04.888500711 +0900
+++ vmt_subr.c 2024-02-04 17:34:25.844694925 +0900
@@ -729,6 +729,21 @@ vmt_tclo_tick(void *xarg)
}
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
sc->sc_rpc_error = 1;
+ } else if (strncmp(sc->sc_rpc_buf, "Set_Option time.synchronize.tools.enable ", 41) == 0) {
+ if (sc->sc_rpc_buf[41] == '0') {
+ if (sc->sc_clock_sync_flags & VMT_CLOCK_SYNC_ENABLE) {
+ /* time.synchronize.tools.enable on -> off */
+ sc->sc_clock_sync_flags &= ~VMT_CLOCK_SYNC_ENABLE;
+ }
+ } else {
+ if ((sc->sc_clock_sync_flags & VMT_CLOCK_SYNC_ENABLE) == 0) {
+ /* time.synchronize.tools.enable off -> on */
+ vmt_sync_guest_clock(sc);
+ sc->sc_clock_sync_flags |= VMT_CLOCK_SYNC_ENABLE;
+ }
+ }
+ if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
+ sc->sc_rpc_error = 1;
} else {
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR) != 0) {
device_printf(sc->sc_dev, "error sending unknown command reply\n");
Set_Option time.synchronize.tools.startup [Boolean]
引数が1(true)かつ一度も時刻合わせを行ってなければ実行する。
--- vmtvar.h.orig 2024-02-04 16:23:50.073672598 +0900
+++ vmtvar.h 2024-02-04 16:24:57.282052134 +0900
@@ -74,6 +74,7 @@ struct vmt_softc {
#define VMT_CLOCK_SYNC 0x1
#define VMT_CLOCK_SYNC_ENABLE 0x2
+#define VMT_CLOCK_SYNC_STARTUP 0x4
unsigned int sc_clock_sync_flags;
#define VMT_CLOCK_SYNC_PERIOD_SECONDS 60
int sc_clock_sync_period_seconds;
--- vmt_subr.c.orig 2024-02-04 17:34:25.844694925 +0900
+++ vmt_subr.c 2024-02-04 17:35:43.952915960 +0900
@@ -744,6 +744,16 @@ vmt_tclo_tick(void *xarg)
}
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
sc->sc_rpc_error = 1;
+ } else if (strncmp(sc->sc_rpc_buf, "Set_Option time.synchronize.tools.startup ", 42) == 0) {
+ if (sc->sc_rpc_buf[42] != '0') {
+ if ((sc->sc_clock_sync_flags & VMT_CLOCK_SYNC_STARTUP) == 0) {
+ /* time.synchronize.tools.start off -> on */
+ vmt_sync_guest_clock(sc);
+ sc->sc_clock_sync_flags |= VMT_CLOCK_SYNC_STARTUP;
+ }
+ }
+ if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
+ sc->sc_rpc_error = 1;
} else {
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR) != 0) {
device_printf(sc->sc_dev, "error sending unknown command reply\n");
Set_Option time.synchronize.tools.startup.backward [Boolean]
ホスト時刻に同期する際にゲスト時刻より過去になるのを許容する。
--- vmtvar.h.orig 2024-02-04 16:32:20.605750158 +0900
+++ vmtvar.h 2024-02-04 16:33:05.152814049 +0900
@@ -75,6 +75,7 @@ struct vmt_softc {
#define VMT_CLOCK_SYNC 0x1
#define VMT_CLOCK_SYNC_ENABLE 0x2
#define VMT_CLOCK_SYNC_STARTUP 0x4
+#define VMT_CLOCK_SYNC_BACKWARD 0x8
unsigned int sc_clock_sync_flags;
#define VMT_CLOCK_SYNC_PERIOD_SECONDS 60
int sc_clock_sync_period_seconds;
--- vmt_subr.c.orig 2024-02-04 17:35:43.952915960 +0900
+++ vmt_subr.c 2024-02-04 17:36:51.839369643 +0900
@@ -754,6 +754,13 @@ vmt_tclo_tick(void *xarg)
}
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
sc->sc_rpc_error = 1;
+ } else if (strncmp(sc->sc_rpc_buf, "Set_Option time.synchronize.tools.backward ", 43) == 0) {
+ if (sc->sc_rpc_buf[43] == '0')
+ sc->sc_clock_sync_flags &= ~VMT_CLOCK_SYNC_BACKWARD;
+ else
+ sc->sc_clock_sync_flags |= VMT_CLOCK_SYNC_BACKWARD;
+ if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0)
+ sc->sc_rpc_error = 1;
} else {
if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR) != 0) {
device_printf(sc->sc_dev, "error sending unknown command reply\n");
実装はダミーである、そもそも現状の N HEAD の実装だと
408 static void
409 vmt_sync_guest_clock(struct vmt_softc *sc)
410 {
411 struct vm_backdoor frame;
412 struct timespec ts;
413
414 memset(&frame, 0, sizeof(frame));
415 frame.eax = VM_MAGIC;
416 frame.ecx = VM_CMD_GET_TIME_FULL;
417 frame.edx = VM_REG_CMD(0, VM_PORT_CMD);
418 vm_cmd(&frame);
419
420 if (__SHIFTOUT(frame.eax, VM_REG_WORD_MASK) != 0xffffffff) {
421 ts.tv_sec = ((uint64_t)(
422 __SHIFTOUT(frame.esi, VM_REG_WORD_MASK) << 32)) |
423 __SHIFTOUT(frame.edx, VM_REG_WORD_MASK);
424 ts.tv_nsec = __SHIFTOUT(frame.ebx, VM_REG_WORD_MASK) * 1000;
425 tc_setclock(&ts);
426 }
427 }
ちゅーかんじで、ドキュメントすらない tc_setclock()
という API で時刻が逆行しようがお構いなしに同期してるので、常時 time.synchronize.tools.startup.backward
は true
であるといえる。
この辺は次回改めて正しい実装はどうすればいいのか説明する予定。
ついでに不要な強制時刻同期にさよなら
デバイスアタッチ時とサスペンドからの復帰の強制同期は不要なので消す。 もし必要があったとしても
- reset イベントハンドラで
- synctime の値に従う
必要があるかと。
--- vmt_subr.c.back 2024-02-04 18:17:35.630901476 +0900
+++ vmt_subr.c 2024-02-04 18:18:33.477167272 +0900
@@ -210,8 +210,6 @@ vmt_common_attach(struct vmt_softc *sc)
callout_setfunc(&sc->sc_clock_sync_tick, vmt_clock_sync_tick, sc);
- vmt_sync_guest_clock(sc);
-
return;
free:
@@ -474,8 +472,6 @@ vmt_do_resume(struct vmt_softc *sc)
{
device_printf(sc->sc_dev, "guest resuming from suspended state\n");
- vmt_sync_guest_clock(sc);
-
/* force guest info update */
sc->sc_hostname[0] = '\0';
sc->sc_set_guest_os = 0;
次回
いちおうこれで時刻同期について VMware で設定したポリシーにしたがうようになったはずである(backward 以外は)。
ワイの筆力が足りると思えんけどディープな時刻合わせの話に突入する、 めんどくさくなって別の話してる可能性が99%くらいあるけど。