VM Guest as a NTPD Time Source

blue analog clockIf all I wanted to do was fix the clock on a VM guest, I would have stopped running ntp on the guest and left it running on the underlying physical host. Simple.

But what if you want to use a VM guest as a ntpd time source?! Reasons might be because you’re migrating from a physical server to a VM and don’t have access to the guests to redirect them to another host for whatever reason.

I know this problem is caused in different ways for each virtualization platform. And while my specific problem was using KVM, this should avoid the problem on all of them.

If your host system uses Time Stamp Counter (TSC)

# grep -m1 constant_tsc /proc/cpuinfo
flags  : fpu vme de pse tsc blah blah blah...
constant_tsc
# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc

and Virtual Machine guests use kvm-clock

# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
kvm-clock
# dmesg | grep clock
...
[    0.056001] kvm-clock: cpu 11, msr 0:11975701, secondary cpu clock
[    0.388036] Switching to clocksource kvm-clock
[    0.637349] rtc_cmos 00:01: setting system clock to 2012-01-21 19:18:21 UTC

Do not use ntp on the guest!

But what if you must use a guest as a time source? If the physical host synchronizes to a good Internet time source and the VM guest uses itself (127.0.0.1), what would happen? Well it would still be fighting the clock. So that’s not going to work. I was just hoping to avoid spike messages showing up in client ntp logs, but the clock can skew drastically in either direction.

iptables MASQUERADE to the rescue!

Stop running ntpd on the guest and forward all ntp requests that come in to a physical host serving NTP. You need two rules minimum:

iptables -t nat -A PREROUTING -i eth0 -p udp -m udp --dport 123 -j DNAT --to-destination $HOST
iptables -t nat -A POSTROUTING -o eth0 -p udp -m udp --dport 123 -j MASQUERADE

If you have two interfaces, you can forward the traffic from one network to the other this way too. Just change the -i eth0 to match the other network interface and then allow forwarding:

sysctl net.ipv4.conf.eth0.forwarding=1
sysctl net.ipv4.conf.eth1.forwarding=1

Even if you only have one interface and the ntp server is on the same network, the masquerade should still work.

You should really limit forwarding to ntp for your source and destination too. Default policies of ACCEPT for iptables are bad if you don’t have an explicit rule to drop everything not handled by a higher rule.

# forwarding for ntp requests to your ntp server
iptables -A FORWARD -p udp -d 10.9.8.7/32 --dport 123 -j ACCEPT
# forwarding for responses from your ntp server
iptables -A FORWARD -p udp -d 192.168.1.0/24 --dport 123 -j ACCEPT