Translate

Archives

Revisiting Systemd D-Bus Interfaces

In a May 2013 blog post I examined the then systemd D-Bus interface. At that time the systemd version string was 208. In this short blog I will discuss hostnamectl/hostnamed and timedatectl/timedatectl functionality as it relates to the systemd Dbus.

You can use the following dbus-send command to find out what’s available on the D-Bus system bus:

# dbus-send --system --print-reply --dest="org.freedesktop.DBus" \
/org/freedesktop/DBus org.freedesktop.DBus.ListActivatableNames

method return sender=org.freedesktop.DBus -> dest=:1.137 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string "org.freedesktop.login1"
      string "org.fedoraproject.Setroubleshootd"
      string "org.freedesktop.machine1"
      string "org.freedesktop.ColorManager"
      string "com.redhat.problems.configuration"
      string "org.freedesktop.systemd1"
      string "org.freedesktop.Avahi"
      string "org.freedesktop.PolicyKit1"
      string "org.freedesktop.ModemManager1"
      string "org.bluez"
      string "org.freedesktop.hostname1"
      string "org.freedesktop.NetworkManager"
      string "net.reactivated.Fprint"
      string "org.freedesktop.PackageKit"
      string "org.freedesktop.UPower"
      string "com.redhat.SubscriptionManager"
      string "org.freedesktop.UDisks2"
      string "org.freedesktop.problems"
      string "org.freedesktop.realmd"
      string "fi.epitest.hostap.WPASupplicant"
      string "org.gnome.GConf.Defaults"
      string "org.fedoraproject.SetroubleshootFixit"
      string "org.freedesktop.RealtimeKit1"
      string "fi.w1.wpa_supplicant1"
      string "org.freedesktop.timedate1"
      string "org.freedesktop.Accounts"
      string "org.freedesktop.nm_dispatcher"
      string "org.opensuse.CupsPkHelper.Mechanism"
      string "org.freedesktop.locale1"
   ]


Obviously org.freedesktop.systemd1 is the D-Bus bus that we are primarily interested in.

I am currently on up-to-date version of Fedora 20 and here is the version string returned via D-Bus:

# dbus-send --system --print-reply  --dest=org.freedesktop.systemd1 \
/org/freedesktop/systemd1 org.freedesktop.DBus.Properties.Get 

string:'org.freedesktop.systemd1.Manager' string:'Version'
method return sender=:1.3 -> dest=:1.94 reply_serial=2
   variant       string "systemd 208"


On RHEL version 7, the same version string is returned. As you can see, the systemd DBus version number has not changed in over a year. Stability in an interface as important as systemd is a good thing!

Here is what is outputted when I run hostnamectl on a RHEL7 VM running on VMware workstation:

# hostnamectl
   Static hostname: rhel7.example.com
         Icon name: computer
           Chassis: n/a
        Machine ID: 395414828aed4ec9b0690e168869e307
           Boot ID: f315f0fb59534f87882ded936a750342
    Virtualization: vmware
  Operating System: Red Hat Enterprise Linux Server 7.0 (Maipo)
       CPE OS Name: cpe:/o:redhat:enterprise_linux:7.0:GA:server
            Kernel: Linux 3.10.0-121.el7.x86_64
      Architecture: x86_64

For those who are unaware of it, systemd makes extensive use of the D-Bus infrastructure and utilities a number of D-Bus daemons including hostnamed. This daemon takes no arguments or options and is managed using hostnamectl via D-Bus. Whenever one of the hostnames or other metadata is changed via the daemon, clients that have subscribed to be notified of the changes are signaled. See here for more information about hostnamed.

Hostnamectl is a small utility that sends D-Bus messages to hostnamed and receives responses beck in the form of D-Bus messages which it then parses. For example, below is a section of source code from hostnamectl.c which sets the chassis id.

static int set_chassis(DBusConnection *bus, char **args, unsigned n) {
        _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
        dbus_bool_t interactive = true;

        assert(args);
        assert(n == 2);

        polkit_agent_open_if_enabled();

        return bus_method_call_with_reply(
                        bus,
                        "org.freedesktop.hostname1",
                        "/org/freedesktop/hostname1",
                        "org.freedesktop.hostname1",
                        "SetChassis",
                        &reply,
                        NULL,
                        DBUS_TYPE_STRING, &args[1],
                        DBUS_TYPE_BOOLEAN, &interactive,
                        DBUS_TYPE_INVALID);
}

Introspecting org.freedesktop.hostname1 reveals the methods, properties and signals available to us:

# dbus-send --system --print-reply --reply-timeout=2000 --type=method_call \ 
--dest=org.freedesktop.hostname1 /org/freedesktop/hostname1 \
org.freedesktop.DBus.Introspectable.Introspect 

method return sender=:1.105 -> dest=:1.112 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
 <interface name="org.freedesktop.hostname1">
  <property name="Hostname" type="s" access="read"/>
  <property name="StaticHostname" type="s" access="read"/>
  <property name="PrettyHostname" type="s" access="read"/>
  <property name="IconName" type="s" access="read"/>
  <property name="Chassis" type="s" access="read"/>
  <method name="SetHostname">
   <arg name="name" type="s" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
  <method name="SetStaticHostname">
   <arg name="name" type="s" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
  <method name="SetPrettyHostname">
   <arg name="name" type="s" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
  <method name="SetIconName">
   <arg name="name" type="s" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
  <method name="SetChassis">
   <arg name="name" type="s" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
 </interface>
 <interface name="org.freedesktop.DBus.Properties">
  <method name="Get">
   <arg name="interface" direction="in" type="s"/>
   <arg name="property" direction="in" type="s"/>
   <arg name="value" direction="out" type="v"/>
  </method>
  <method name="GetAll">
   <arg name="interface" direction="in" type="s"/>
   <arg name="properties" direction="out" type="a{sv}"/>
  </method>
  <method name="Set">
   <arg name="interface" direction="in" type="s"/>
   <arg name="property" direction="in" type="s"/>
   <arg name="value" direction="in" type="v"/>
  </method>
  <signal name="PropertiesChanged">
   <arg type="s" name="interface"/>
   <arg type="a{sv}" name="changed_properties"/>
   <arg type="as" name="invalidated_properties"/>
  </signal>
 </interface>
 <interface name="org.freedesktop.DBus.Introspectable">
  <method name="Introspect">
   <arg name="data" type="s" direction="out"/>
  </method>
 </interface>
<interface name="org.freedesktop.DBus.Peer">
 <method name="Ping"/>
 <method name="GetMachineId">
  <arg type="s" name="machine_uuid" direction="out"/>
 </method>
</interface>
</node>
"

Based on what was revealed by introspecting org.freedesktop.hostname1, here is the equivalent command to hostnamectl using dbus-send:

# dbus-send --system --print-reply --reply-timeout=2000 --type=method_call \
 --dest=org.freedesktop.hostname1 /org/freedesktop/hostname1 \ 
org.freedesktop.DBus.Properties.GetAll string:"org.freedesktop.hostname1"

method return sender=:1.105 -> dest=:1.132 reply_serial=2
   array [
      dict entry(
         string "Hostname"
         variant             string "rhel7.example.com"
      )
      dict entry(
         string "StaticHostname"
         variant             string "rhel7.example.com"
      )
      dict entry(
         string "PrettyHostname"
         variant             string ""
      )
      dict entry(
         string "IconName"
         variant             string "computer"
      )
      dict entry(
         string "Chassis"
         variant             string ""
      )
   ]


As an aside, I still have difficulty wrapping my head around the fact that the systemd developer gods seem to think that an operating systems needs a static hostname, a pretty hostname, an icon name as well as the good old-fashioned hostname which served the Unix-like community perfectly well for the last 30 plus years.

The static hostname is stored in /etc/hostname whereas the pretty hostname and icon name are stored in /etc/machine-info. Interestingly, the documentation for hostnamectl mentions three different hostnames: static, pretty and transient.

From the hostnamectl man page:

This tool distinguishes three different hostnames: the high-level “pretty” hostname which might include all kinds of special characters (e.g. “Lennart’s Laptop”), the static hostname which is used to initialize the kernel hostname at boot (e.g. “lennarts-laptop”), and the transient hostname which might be assigned temporarily due to network configuration and might revert back to the static hostname if network connectivity is lost and is only temporarily written to the kernel hostname (e.g. “dhcp-47-11”).

Looking at the source code for hostnamed, I conclude that hostname is the transient hostname as far as D-Bus is concerned.

The service unit for hostnamed is at /usr/lib/systemd/system

cat systemd-hostnamed.service 
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Hostname Service
Documentation=man:systemd-hostnamed.service(8) man:hostname(5) man:machine-info(5)
Documentation=http://www.freedesktop.org/wiki/Software/systemd/hostnamed

[Service]
ExecStart=/usr/lib/systemd/systemd-hostnamed
BusName=org.freedesktop.hostname1
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE

Looking at the output of hostnamectl, you will notice that a property called virtualization is listed. Until recently, I was normally running on bare metal so never particularly noticed this property. It seems that the Linux kernel can now determine whether it is running on bare metal or in a hypervisor and even determine which particular hypervisor it is running under.

Here is what is returned when the Virtualization property is queried using D-Bus when running RHEL7 as a VM using VMware Workstation:

# dbus-send --system --print-reply  --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1 \ 
 org.freedesktop.DBus.Properties.Get string:'org.freedesktop.systemd1.Manager' string:'Virtualization'
method return sender=:1.3 -> dest=:1.94 reply_serial=2

   variant       string "vmware"


It returns an empty string when not virtualized.

I was actually quite surprised to see that Linux can now surface the fact that it is running in a hypervisor and the actual hypervisor vendor. I wondered if this sort of information is also surfaced if Fedora 20 were running in a Linux Container and found that, yes, it detected that Fedora 20 was running in a container.

The utility lscpu also discloses the fact that the OS is virtualized.

# lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                1
On-line CPU(s) list:   0
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 58
Model name:            Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
Stepping:              9
CPU MHz:               2494.439
BogoMIPS:              4988.87
Hypervisor vendor:     VMware
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              3072K
NUMA node0 CPU(s):     0


Also there is a specific systemd command called systemd-detect-virt which can be used to surface information about Linux Containers and much more. See the man page for system-detect-virt.

# systemd-detect-virt
vmware

Hostnamectl and hostnamed make use of a function called detect_virtualization (see virt.c) to detect whether the OS is running on bare metal or is in some way virtualized. The real work is done by detect_hypervisor_vendor in the Linux kernel file …/arch/x86/kernel/cpu/hypervisor.c, a portion of which is shown below:

#include <linux/module.h>
#include <asm/processor.h>
#include <asm/hypervisor.h>

static const __initconst struct hypervisor_x86 * const hypervisors[] =
{
#ifdef CONFIG_XEN_PVHVM
	&x86_hyper_xen_hvm,
#endif
	&x86_hyper_vmware,
	&x86_hyper_ms_hyperv,
#ifdef CONFIG_KVM_GUEST
	&x86_hyper_kvm,
#endif
};

const struct hypervisor_x86 *x86_hyper;
EXPORT_SYMBOL(x86_hyper);

static inline void __init
detect_hypervisor_vendor(void)
{
	const struct hypervisor_x86 *h, * const *p;
	uint32_t pri, max_pri = 0;

	for (p = hypervisors; p < hypervisors + ARRAY_SIZE(hypervisors); p++) {
		h = *p;
		pri = h->detect();
		if (pri != 0 && pri > max_pri) {
			max_pri = pri;
			x86_hyper = h;
		}
	}

	if (max_pri)
		printk(KERN_INFO "Hypervisor detected: %s\n", x86_hyper->name);
}

Putting on my security hat, I think this is a very bad feature because it makes the examination of malware more difficult. Modern malware often changes it’s behavior depending on the environment it is run in. One possible solution would be to add a kernel command line switch to turn off this feature as required.

Turning now to date and time functionality in systemd. Here is sample output from timedatectl:

# timedatectl
      Local time: Tue 2014-10-07 12:59:30 EDT
  Universal time: Tue 2014-10-07 16:59:30 UTC
        RTC time: Tue 2014-10-07 16:59:31
        Timezone: America/New_York (EDT, -0400)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: no
      DST active: yes
 Last DST change: DST began at
                  Sun 2014-03-09 01:59:59 EST
                  Sun 2014-03-09 03:00:00 EDT
 Next DST change: DST ends (the clock jumps one hour backwards) at
                  Sun 2014-11-02 01:59:59 EDT
                  Sun 2014-11-02 01:00:00 
#

Here is what is returned when we introspect org.freedesktop.timedate1:

# dbus-send --system --print-reply --reply-timeout=2000 --type=method_call --dest=org.freedesktop.timedate1 /org/freedesktop/timedate1 org.freedesktop.DBus.Introspectable.Introspect 
method return sender=:1.204 -> dest=:1.203 reply_serial=2
   string "< !DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
 <interface name="org.freedesktop.timedate1">
  <property name="Timezone" type="s" access="read"/>
  <property name="LocalRTC" type="b" access="read"/>
  <property name="CanNTP" type="b" access="read"/>
  <property name="NTP" type="b" access="read"/>
  <method name="SetTime">
   <arg name="usec_utc" type="x" direction="in"/>
   <arg name="relative" type="b" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
  <method name="SetTimezone">
   <arg name="timezone" type="s" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
  <method name="SetLocalRTC">
   <arg name="local_rtc" type="b" direction="in"/>
   <arg name="fix_system" type="b" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
  <method name="SetNTP">
   <arg name="use_ntp" type="b" direction="in"/>
   <arg name="user_interaction" type="b" direction="in"/>
  </method>
 </interface>
 <interface name="org.freedesktop.DBus.Properties">
  <method name="Get">
   <arg name="interface" direction="in" type="s"/>
   <arg name="property" direction="in" type="s"/>
   <arg name="value" direction="out" type="v"/>
  </method>
  <method name="GetAll">
   <arg name="interface" direction="in" type="s"/>
   <arg name="properties" direction="out" type="a{sv}"/>
  </method>
  <method name="Set">
   <arg name="interface" direction="in" type="s"/>
   <arg name="property" direction="in" type="s"/>
   <arg name="value" direction="in" type="v"/>
  </method>
  <signal name="PropertiesChanged">
   <arg type="s" name="interface"/>
   <arg type="a{sv}" name="changed_properties"/>
   <arg type="as" name="invalidated_properties"/>
  </signal>
 </interface>
 <interface name="org.freedesktop.DBus.Introspectable">
  <method name="Introspect">
   <arg name="data" type="s" direction="out"/>
  </method>
 </interface>
<interface name="org.freedesktop.DBus.Peer">
 <method name="Ping"/>
 <method name="GetMachineId">
  <arg type="s" name="machine_uuid" direction="out"/>
 </method>
</interface>
</node>
"
#


Clients can subscribe to PropertyChanged signals to be notified of changes to the timezone and local_rtc values.

If you prefer, you can use gdbus to introspect org.freedesktop.timedate1. The output from gdbus is often easier to understand than that of dbus-send.

gdbus introspect --system  --dest=org.freedesktop.timedate1 --object-path /org/freedesktop/timedate1
node /org/freedesktop/timedate1 {
  interface org.freedesktop.timedate1 {
    methods:
      SetTime(in  x usec_utc,
              in  b relative,
              in  b user_interaction);
      SetTimezone(in  s timezone,
                  in  b user_interaction);
      SetLocalRTC(in  b local_rtc,
                  in  b fix_system,
                  in  b user_interaction);
      SetNTP(in  b use_ntp,
             in  b user_interaction);
    signals:
    properties:
      readonly s Timezone = 'America/New_York';
      readonly b LocalRTC = false;
      readonly b CanNTP = true;
      readonly b NTP = true;
  };
  interface org.freedesktop.DBus.Properties {
    methods:
      Get(in  s interface,
          in  s property,
          out v value);
      GetAll(in  s interface,
             out a{sv} properties);
      Set(in  s interface,
          in  s property,
          in  v value);
    signals:
      PropertiesChanged(s interface,
                        a{sv} changed_properties,
                        as invalidated_properties);
    properties:
  };
  interface org.freedesktop.DBus.Introspectable {
    methods:
      Introspect(out s data);
    signals:
    properties:
  };
  interface org.freedesktop.DBus.Peer {
    methods:
      Ping();
      GetMachineId(out s machine_uuid);
    signals:
    properties:
  };
};
# 


As with a lot of systemd stuff, you need to read the your log file using journalctl after many of the operations to ensure the operation completed as expected.

For example, the following code from timedated shows that you will normally get back the realtime clock time. However, in some circumstances (read the code), you will “silently” get back the time value returned by timegm which is not what you are expecting.

static int property_get_rtc_time(
                sd_bus *bus,
                const char *path,
                const char *interface,
                const char *property,
                sd_bus_message *reply,
                void *userdata,
                sd_bus_error *error) {

        struct tm tm;
        usec_t t;
        int r;

        zero(tm);
        r = clock_get_hwclock(&tm);
        if (r == -EBUSY) {
                log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
                t = 0;
        } else if (r == -ENOENT) {
                log_debug("/dev/rtc not found.");
                t = 0; /* no RTC found */
        } else if (r < 0)
                return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
        else
                t = (usec_t) timegm(&tm) * USEC_PER_SEC;

        return sd_bus_message_append(reply, "t", t);
}

The service unit for timedated is at /usr/lib/systemd/system

# cat systemd-timedated.service 
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Time & Date Service
Documentation=man:systemd-timedated.service(8) man:localtime(5)
Documentation=http://www.freedesktop.org/wiki/Software/systemd/timedated

[Service]
ExecStart=/usr/lib/systemd/systemd-timedated
BusName=org.freedesktop.timedate1
CapabilityBoundingSet=CAP_SYS_TIME
# 

Note the both hostnamed and timedated are examples of static systemd units (AKA tasks).

# systemctl list-unit-files | grep -E "hostname|timedate"
dbus-org.freedesktop.hostname1.service      static  
dbus-org.freedesktop.timedate1.service      static  
systemd-hostnamed.service                   static  
systemd-timedated.service                   static  
# 


Static units do not have a systemd[Install] section nor can they be introspected using the generic org.freedesktop.systemd1.Unit interface.

Systemd is, unfortunately, probably here to stay so it is worth system administrators time and effort to try and understand how it is constructed, it’s underpinnings and it’s dependencies. It is not a simple monolithic piece of code but rather a large hairball of code which will probably get bigger in time as more standalone components are subsumed into it.

Comments are closed.