UTM icon indicating copy to clipboard operation
UTM copied to clipboard

macOS guest time becomes out of sync

Open ghost opened this issue 3 years ago • 21 comments

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

ghost avatar Nov 10 '22 13:11 ghost

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!)

conath avatar Nov 13 '22 20:11 conath

config.plist.zip

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.

ghost avatar Nov 13 '22 22:11 ghost

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.

conath avatar Nov 13 '22 22:11 conath

Done. Monitoring the issue.

ghost avatar Nov 14 '22 09:11 ghost

@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.

ghost avatar Nov 14 '22 11:11 ghost

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.

jtran19 avatar Nov 14 '22 14:11 jtran19

Seems like something we'll have to report to Apple: FB11815959

osy avatar Nov 23 '22 00:11 osy

Using latest release 4.1.5, it's also happening while actively using a MacOS guest VM.

krypu avatar Jan 16 '23 13:01 krypu

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.

tenedor avatar Nov 01 '23 14:11 tenedor

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).

antonkratz avatar Dec 26 '23 02:12 antonkratz

Fixed for Qemu, no APIs from Apple for Virtualization.framework

kenji21 avatar Jun 19 '24 12:06 kenji21

Found AppleVirtualPlatformRTCPlugin in Console.app logs when switching on and off the syncing to time.apple.com in Date & Time settings :

Screenshot 36

kenji21 avatar Jun 21 '24 14:06 kenji21

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;
}

kenji21 avatar Jul 18 '24 12:07 kenji21

No clue about how the host macOS send such an IOKit notification to the guest macOS

kenji21 avatar Jul 18 '24 12:07 kenji21

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

antonkratz avatar Jul 18 '24 13:07 antonkratz

maybe you are running the openSUSE guest with QEMU (which time sync is supported)

kenji21 avatar Jul 24 '24 07:07 kenji21

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 ^^

kenji21 avatar Jul 24 '24 08:07 kenji21

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

kenji21 avatar Jul 24 '24 09:07 kenji21

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?

osxrox avatar Feb 26 '25 20:02 osxrox

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"

kenji21 avatar Feb 27 '25 08:02 kenji21

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

ivankra avatar May 22 '25 11:05 ivankra