macOS guest time becomes out of sync
guest time can desync from the host os. this issue is valid for a few versions back.
- UTM Version: 4.1.0
- macOS Version: host 12.5, guest 13.1
- Mac Chip (Intel, M1, ...): m1
Could you please add more information about the issue? Especially need the guest configuration file:
right-click the VM in UTM and choose "Show in Finder". Then right-click the selected file in Finder and choose "Show Package Contents". The config.plist file is now visible
How long after you turn on the VM does it take until the issue occurs? Are all of your macOS VMs affected or only a specific configuration? (please provide!)
I only have this VM. This happened to me a few times, and it seems to be fixing itself after a while. Definitely not an easy 1/1 repro, but it does happen and it was happening before 4.1.0.
Thanks. Config looks good to me. You could potentially test this further by turning off "Set date and time automatically" in the guest's System Settings. That would in theory prevent the guest time from fixing itself by using the internet time, and force it to rely on the time information it receives from the Apple virtualization backend.
Done. Monitoring the issue.
@conath since I wrote the above comment the time slipped 3 minutes today. So with the time sync set to off in the guest, it is continuously slipping.
Having the same issue here with macOS 13 host and guest. I've noticed the VM time slips when the host is suspended due to no activity (i.e. "falls asleep"). An easy way to force the issue is pausing the VM for a few minutes. When you resume, the time will be out of sync.
Seems like something we'll have to report to Apple: FB11815959
Using latest release 4.1.5, it's also happening while actively using a MacOS guest VM.
I regularly hit a similar issue, maybe the same issue. I'm a first-time UTM user as of a couple weeks ago on my new M2 laptop, using UTM 4.4.4 and MacOS Ventura 13.5.2. My guest VM's time constantly falls days behind when not in use despite the Date & Time settings being configured to set the time automatically from the Apple time server.
I can reset the clock to the exact time by opening Date & Time settings and toggling "Set time and date automatically" off and then back on. The time is correct for as long as I am actively using the VM, and then it falls behind again.
I observe the same issue, has been ongoing for about a year for me, currently with this config.
- UTM Version: Version 4.4.4 (92)
- macOS Version: 14.2.1 (23C71)
- Mac Chip: M2
Guest system is Ubuntu 23.04 (lunar).
Fixed for Qemu, no APIs from Apple for Virtualization.framework
Found AppleVirtualPlatformRTCPlugin in Console.app logs when switching on and off the syncing to time.apple.com in Date & Time settings :
I checked the binary /System//Library/PrivateFrameworks/CoreTime.framework/TimeSources/AppleVirtualPlatformRTCPlugin.bundle/Contents/MacOS/AppleVirtualPlatformRTCPlugin which only uses IOKit to get notified about the change
r0 = IONotificationPortCreate(*(int32_t *)*_kIOMainPortDefault);
[...]
r0 = IOServiceNameMatching("AppleVirtualPlatformRTC");
r0 = IOServiceGetMatchingService(r20, r0);
[...]
r0 = IOServiceAddInterestNotification(r0, r1, "IOGeneralInterest", 0x34bc, r19, r19 + 0x18);
[...]
with the 0x34bc method:
int sub_34bc(int arg0, int arg1, int arg2, int arg3) {
[...]
r0 = os_log_type_enabled(r20, 0x0);
if (arg2 == 0xe0000230) {
if (r0 != 0x0) {
_os_log_impl(0x0, r20, 0x0, "Avp rtc state notification received", &var_30, 0x2);
}
if (r8 != 0x0) {
if (r8 == 0x1) {
r20 = *(r19 + 0x10);
if (os_log_type_enabled(r20, 0x0) != 0x0) {
_os_log_impl(0x0, r20, 0x0, "Reset time", &var_40, 0x2);
}
sub_38c0();
}
}
else {
r20 = *(r19 + 0x10);
if (os_log_type_enabled(r20, 0x0) != 0x0) {
_os_log_impl(0x0, r20, 0x0, "Resync Time", &var_50, 0x2);
}
sub_38e0();
}
}
else {
if (r0 != 0x0) {
_os_log_impl(0x0, r20, 0x0, "Avp rtc unknown notification received", &var_60, 0x2);
}
}
r0 = [r19 release];
return r0;
}
No clue about how the host macOS send such an IOKit notification to the guest macOS
I do not know if my comment helps or not, but I noticed that the time drift certainly appears with Ubuntu guests (many versions tested) but does not seem to appear if the guest is openSUSE LEAP 15.5
maybe you are running the openSUSE guest with QEMU (which time sync is supported)
Finally found a way to solve this, a macOS LaunchAgent with the swift code like the fix for QEMU, but to make the VM aware of the drift => a Wake On LAN magic packet (reading the UTM config.plist)
A launchd service on the VM listens to the WOL packet and run systemctl restart networking & systemctl restart ntp
My VM has network in bridge mode, so I lose network connectivity when closing my laptop, moving home, opening it. Hence the WakeOnLAN packet ^^
Microsoft solves this issue on their Hypervisor with a RTC device /dev/ptp_hyperv, and configuring chrony to use it, see https://learn.microsoft.com/en-us/azure/virtual-machines/linux/time-sync#chrony
I'm seeing this with UTM 4.6.4 hosting Windows 11 on an M4 iMac (Sequoia 15.3.1). It's a pain. Is there a fix or workaround I may have missed?
Workaround, a script ~/scripts/wake-on-lan-send.sh :
#!/bin/bash
set -e
PATH=$PATH:/usr/local/bin:/opt/homebrew/bin/
if [ ! -x "$(command -v wakeonlan)" ] ; then echo "wakeonlan not installed (or not in the PATH), install it with brew install wakeonlan" ; exit 12 ; fi
if [ ! -x "/Applications/UTM.app/Contents/MacOS/utmctl" ] ; then echo "utmctl not found in /Application/UTM.app, please install or update UTM" ; exit 12 ; fi
BOOTED_UTM_VMS="$(/Applications/UTM.app/Contents/MacOS/utmctl list 2>/dev/null | grep started | tr -s ' ' | cut -d ' ' -f 3)"
if [ -z "$BOOTED_UTM_VMS" ] ; then
echo "No UTM VMs found"
exit 0
fi
echo $BOOTED_UTM_VMS | while IFS= read -r VM_NAME
do
echo "Sending wake on lan packet to: $VM_NAME"
UTM_VM_CONFIG_FILE_PATH="$HOME/Library/Containers/com.utmapp.UTM/Data/Documents/$VM_NAME.utm/config.plist"
UTM_VM_MAC_ADDR="$(/usr/libexec/PlistBuddy -c 'print Network:0:MacAddress' $UTM_VM_CONFIG_FILE_PATH)"
wakeonlan -p 7 $UTM_VM_MAC_ADDR
done
called by a swift program (mainly listening NSWorkspace.didWakeNotification), install as a launchd agent:
import Foundation
signal(SIGINT) { _ in
NSApp.terminate(nil)
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didWakeNotification, object: nil, queue: nil) { _ in
let process = Process()
// .app with automator to prevent sandboxing issue while accessing UTM config files
process.executableURL = URL(fileURLWithPath: NSHomeDirectory() + "/scripts/wake-on-lan-send.app/Contents/MacOS/Automator Application Stub")
let outputPipe = Pipe()
process.standardOutput = outputPipe
let errorPipe = Pipe()
process.standardError = errorPipe
NSLog("did wake notification received, re-syncing guest time \(Date())")
do {
try process.run()
process.waitUntilExit()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let outputString = String(data: outputData, encoding: .utf8)
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
let errorString = String(data: errorData, encoding: .utf8)
// Affichez les résultats
if let output = outputString {
NSLog("Output: \(output)")
}
if let error = errorString, !error.isEmpty {
NSLog("Error: \(error)")
}
} catch {
NSLog("An error occurred: \(error)")
}
NSLog("Ended re-syncing guest time")
}
}
}
NSLog("Starting wake notification listener")
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
app.run()
To install it:
#!/bin/bash
plistConfigFile="$HOME/Library/LaunchAgents/fr.openium.wake-notification-listener.plist"
if [ -f $plistConfigFile ] ; then
launchctl unload "$plistConfigFile"
fi
cat <<EOF > "$plistConfigFile"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>fr.openium.wake-notification-listener</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>-c</string>
<string>swift $HOME/scripts/dc-laptop-wake-notification-listener.swift</string>
</array>
<key>StandardOutPath</key>
<string>/tmp/fr.openium.wake-notification-listener.log</string>
<key>StandardErrorPath</key>
<string>/tmp/fr.openium.wake-notification-listener.log</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
EOF
launchctl load "$plistConfigFile"
Then, on guest side, you'll have to be able to listen to the wakeonlan packet
Exemple on a linux guest:
#!/bin/bash
# view output with: journalctl -fu wake-on-lan-listener
MY_MAC_ADDRESS="$(ip link show enp0s1 | grep 'link/ether' | awk '{print $2}')"
nc -dknl -p 7 -u | stdbuf -o0 xxd -c 6 -p | stdbuf -o0 uniq | stdbuf -o0 grep -v 'ffffffffffff' |
while read ; do
DEST_MAC_ADDRESS="${REPLY:0:2}:${REPLY:2:2}:${REPLY:4:2}:${REPLY:6:2}:${REPLY:8:2}:${REPLY:10:2}"
echo "Received WoL packet for : $DEST_MAC_ADDRESS (mine is $MY_MAC_ADDRESS)"
if [ "$DEST_MAC_ADDRESS" == "$MY_MAC_ADDRESS" ]; then
/root/scripts/wake-on-lan-received.sh
else
echo "Ignoring WoL packet"
fi
done
with wake-on-lan-receveived.sh doing a network restart (my case is that I can move back home/work, thus bridged networking differs) :
#!/bin/bash
set -e
# view logs with: journalctl -fu wake-on-lan-listener
sleep 10
echo "restarting networking service"
#time dhclient -v enp0s1
# instead of restarting network, only ask for a new dhcp address, cf https://askubuntu.com/a/1432450
pkill dhclient
/sbin/dhclient -pf /run/dhclient.enp0s1.pid -lf /var/lib/dhcp/dhclient.enp0s1.leases enp0s1
#systemctl restart networking.service
sleep 1
systemctl restart avahi-daemon.service
sleep 10
echo "restarting ntp service"
systemctl restart ntp.service
sleep 5
echo "end"
Microsoft solves this issue on their Hypervisor with a RTC device
/dev/ptp_hyperv, and configuring chrony to use it, see https://learn.microsoft.com/en-us/azure/virtual-machines/linux/time-sync#chrony
Yea and there's /dev/ptp_kvm under KVM. But problem is: neither HyperV nor KVM is a thing on Mac. The choice is HVF or AVF (Apple's virtualization framework), and both don't implement a virtual PTP clock.
There's /dev/rtc0 which looks like it's correctly synced to host time on wake ups. So you could do hwclock --hctosys in a loop inside VM to continuously sync kernel clock with it.
Next chrony's release (4.7) adds RTC driver, which is a more principled way of basically doing that. There's a prerelease build chrony 4.7~pre1-1 now in debian experimental - I've tested it today, it seems to work both under HVF and AVF with this config:
# poll /dev/rtc0 every 0.25s (2^-2)
refclock RTC /dev/rtc0:utc poll -2
# step the clock on >1s delta, unlimited (-1) number of times
makestep 1 -1