[Dnsmasq-discuss] [PATCH] Add new extensible D-Bus signal

Victorien Molle victorien.molle at wifirst.fr
Thu Apr 2 10:41:32 BST 2020


For our usage, we need to have more informations sent over D-Bus such as the interface
name, the vendor class identifier and the lease expiration time.

To achieve this, we add a new D-Bus signal "DhcpLeaseNotification" which exports the
requested informations as a dictionnary.
It also has the advantage to be flexible if someone wants to add a new entry in the
future.

Note: in order to get leases extradata be populated, we enabled this feature if D-Bus
is enabled in configuration file (this was enabled in the past only if a script was
ran on leases updates).

Here is an example of the obtained result with a Python3 program:

''' Define our D-Bus callback '''
def cb(action, ipaddr, hwaddr, hostname, info):
    print(f'Action: {action}')
    print(f'IP: {ipaddr}')
    print(f'Hostname: {hostname}')
    for k in info:
        print(f'{k}: {info.get(k)}')

''' Connect to signal DhcpLeaseNotification on interface uk.org.thekelleys.dnsmasq '''
DNSMasq.listen('DhcpLeaseNotification', callback=cb)

''' Run GLib loop '''
GLib.MainLoop().run()

''' When DNSMasq receives a DHCP request, here is the result: '''

Action: DhcpLeaseAdded
IP: 192.168.1.100
Hostname: Nucleus.nucle.us
interface: br-mgmt
expiration: 43200
ipv6: 0
vendor_class: LaGrosseBiche
---
 dbus/DBus-interface |   9 ++
 src/dbus.c          | 341 +++++++++++++++++++++++++++++++++++++++++++-
 src/dnsmasq.h       |   4 +-
 src/lease.c         |   6 +-
 src/rfc2131.c       |  12 +-
 5 files changed, 359 insertions(+), 13 deletions(-)

diff --git a/dbus/DBus-interface b/dbus/DBus-interface
index 954c5b9..ed42551 100644
--- a/dbus/DBus-interface
+++ b/dbus/DBus-interface
@@ -273,14 +273,23 @@ DhcpLeaseAdded
 ---------------
 
 This signal is emitted when a DHCP lease for a given IP address is created.
+This will also trigger the DhcpLeaseNotification signal.
 
 DhcpLeaseDeleted
 ----------------
 
 This signal is emitted when a DHCP lease for a given IP address is deleted.
+This will also trigger the DhcpLeaseNotification signal.
 
 DhcpLeaseUpdated
 ----------------
 
 This signal is emitted when a DHCP lease for a given IP address is updated.
+This will also trigger the DhcpLeaseNotification signal.
+
+DhcpLeaseNotification
+---------------------
+
+This signal is emitted when a DHCP lease action is triggered. It exports,
+as a dictionnary, more informations than the other signals.
  
diff --git a/src/dbus.c b/src/dbus.c
index 4123391..e3c709a 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -55,6 +55,7 @@ const char* introspection_xml_template =
 "    <method name=\"SetBogusPrivOption\">\n"
 "      <arg name=\"boguspriv\" direction=\"in\" type=\"b\"/>\n"
 "    </method>\n"
+#ifdef HAVE_DHCP
 "    <signal name=\"DhcpLeaseAdded\">\n"
 "      <arg name=\"ipaddr\" type=\"s\"/>\n"
 "      <arg name=\"hwaddr\" type=\"s\"/>\n"
@@ -70,7 +71,13 @@ const char* introspection_xml_template =
 "      <arg name=\"hwaddr\" type=\"s\"/>\n"
 "      <arg name=\"hostname\" type=\"s\"/>\n"
 "    </signal>\n"
-#ifdef HAVE_DHCP
+"    <signal name=\"DhcpLeaseNotification\">\n"
+"      <arg name=\"notification_type\" type=\"s\"/>\n"
+"      <arg name=\"ipaddr\" type=\"s\"/>\n"
+"      <arg name=\"hwaddr\" type=\"s\"/>\n"
+"      <arg name=\"hostname\" type=\"s\"/>\n"
+"      <arg name=\"lease_info\" type=\"a{sv}\"/>\n"
+"    </signal>\n"
 "    <method name=\"AddDhcpLease\">\n"
 "       <arg name=\"ipaddr\" type=\"s\"/>\n"
 "       <arg name=\"hwaddr\" type=\"s\"/>\n"
@@ -98,6 +105,13 @@ struct watch {
   struct watch *next;
 };
 
+struct lease_info {
+  char *key;
+  char *fmt;
+  char dbus_type;
+  DBusBasicValue value;
+  struct lease_info *next;
+};
 
 static dbus_bool_t add_watch(DBusWatch *watch, void *data)
 {
@@ -137,6 +151,48 @@ static void remove_watch(DBusWatch *watch, void *data)
   w = data; /* no warning */
 }
 
+static struct lease_info* add_lease_info(struct lease_info *parent, char *key, char *fmt, char dbus_type, DBusBasicValue value)
+{
+  struct lease_info *info, *it;
+
+  /* Allocate new struct */
+  if (!(info = whine_malloc(sizeof(struct lease_info))))
+    return parent;
+
+  /* Populate struct */
+  info->key = key;
+  info->fmt = fmt;
+  info->dbus_type = dbus_type;
+  info->value = value;
+  info->next = NULL;
+
+  /*
+    Assign newly allocated struct to parent.
+    It implies this is the first lease info we want to add
+  */
+  if (!parent)
+    parent = info;
+  else
+  {
+    for (it = parent; it->next != NULL; it = it->next);
+    it->next = info;
+  }
+
+  return parent;
+}
+
+static void destroy_lease_infos(struct lease_info *infos)
+{
+  struct lease_info *it;
+
+  while (infos)
+  {
+    it = infos->next;
+    free(infos);
+    infos = it;
+  }
+}
+
 static void dbus_read_servers(DBusMessage *message)
 {
   DBusMessageIter iter;
@@ -828,7 +884,285 @@ void check_dbus_listeners()
 }
 
 #ifdef HAVE_DHCP
-void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
+static unsigned char *grab_extradata_into_var(unsigned char *buf, unsigned char *end, DBusBasicValue *var)
+{
+  unsigned char *next = NULL;
+  char *val = NULL;
+
+  if (buf && (buf != end))
+    {
+      for (next = buf; ; next++)
+  if (next == end)
+    {
+      next = NULL;
+      break;
+    }
+  else if (*next == 0)
+    break;
+
+      if (next && (next != buf))
+  {
+    char *p;
+    /* No "=" in value */
+    if ((p = strchr((char *)buf, '=')))
+      *p = 0;
+    val = (char *)buf;
+  }
+    }
+  
+  /* Assign var value */
+  if (!val)
+    var->str = "";
+  else
+    var->str = val;
+   
+  return next ? next + 1 : NULL;
+}
+
+/*
+  As this function is called by emit_dbus_signal, we already have access to ipaddr, hwaddr and hostname attributes
+  NOTE: connection attribute is currently not NULL as it is verified by emit_dbus_signal
+*/
+void emit_dhcplease_notification(DBusConnection *connection, char *action, struct dhcp_lease *lease, char *ipaddr, char *hwaddr, char *hostname, time_t now)
+{
+  char sfmt[24], interface[IF_NAMESIZE], *domain = NULL, *p;
+  DBusMessageIter array, dict, iter, variant;
+  int fd = daemon->dhcpfd, i, is6;
+  struct lease_info *infos, *it;
+  DBusMessage* message = NULL;
+  unsigned char *buf, *end;
+  DBusBasicValue value;
+
+#ifdef HAVE_DHCP6
+  if (!daemon->dhcp)
+    fd = daemon->dhcp6fd;
+#endif
+
+  /* Get interface name */
+  if (!indextoname(fd, lease->last_interface, interface))
+    interface[0] = 0;
+
+  /* Check if working with IPv6 */
+  is6 = !!(lease->flags & (LEASE_TA | LEASE_NA));
+
+  /* Try to extract domain */
+  if (hostname)
+  {
+    char *dot;
+    
+    if ((dot = strchr(hostname, '.')))
+    {
+      domain = dot + 1;
+      *dot = 0;
+    }
+  }
+
+  /* Start building dictionary with interface */
+  value.str = interface;
+  infos = add_lease_info(NULL, "interface", "s", DBUS_TYPE_STRING, value);
+
+  /* Add domain if present */
+  if (domain)
+  {
+    value.str = domain;
+    infos = add_lease_info(NULL, "domain", "s", DBUS_TYPE_STRING, value);
+  }
+
+  /* Add expiration time */
+  if (lease->expires != 0)
+    value.u64 = (dbus_uint64_t)difftime(lease->expires, now);
+  else
+    value.u64 = 0;
+  infos = add_lease_info(infos, "expiration", "t", DBUS_TYPE_UINT64, value);
+
+  /* Add flag to know if we use IPv6 or not */
+  value.bool_val = is6 == 1 ? TRUE : FALSE;
+  infos = add_lease_info(infos, "ipv6", "b", DBUS_TYPE_BOOLEAN, value);
+
+  /* CLID into packet */
+  for (p = daemon->packet, i = 0; i < lease->clid_len; i++)
+  {
+    p += sprintf(p, "%.2x", lease->clid[i]);
+    if (i != lease->clid_len - 1)
+      p += sprintf(p, ":");
+  }
+
+  /* Add more data to D-Bus dictionary */
+#ifdef HAVE_DHCP6
+  if (is6)
+  {
+    sprintf(daemon->dhcp_buff3, "%s%u", lease->flags & LEASE_TA ? "T" : "", lease->iaid);
+    value.str = (char*)daemon->dhcp_buff3;
+    infos = add_lease_info(infos, "iaid", "s", DBUS_TYPE_STRING, value);
+
+    /* IAID and server DUID */
+    for (p = daemon->dhcp_packet.iov_base, i = 0; i < daemon->duid_len; i++)
+    {
+      p += sprintf(p, "%.2x", daemon->duid[i]);
+      if (i != daemon->duid_len - 1)
+        p += sprintf(p, ":");
+    }
+    value.str = (char*)daemon->dhcp_packet.iov_base;
+    infos = add_lease_info(infos, "server_duid", "s", DBUS_TYPE_STRING, value);
+
+    value.str = (char*)daemon->packet;
+    infos = add_lease_info(infos, "client_duid", "s", DBUS_TYPE_STRING, value);
+  }
+#endif
+  if (!is6 && lease->clid_len != 0)
+  {
+    value.str = (char*)daemon->packet;
+    infos = add_lease_info(infos, "client_id", "s", DBUS_TYPE_STRING, value);
+  }
+
+  buf = lease->extradata;
+  end = buf + lease->extradata_len;
+  if (buf && *buf != 0)
+    {
+      if (!is6)
+      {
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "vendor_class", "s", DBUS_TYPE_STRING, value);
+      }
+#ifdef HAVE_DHCP6
+      else
+      {
+        value.i32 = (dbus_int32_t)lease->vendorclass_count;
+        infos = add_lease_info(infos, "vendorclass_count", "i", DBUS_TYPE_INT32, value);
+        if (lease->vendorclass_count != 0)
+        {
+          /* Add VENDOR-ID string */
+          buf = grab_extradata_into_var(buf, end, &value);
+          infos = add_lease_info(infos, "vendor_class_id", "s", DBUS_TYPE_STRING, value);
+
+          /* Add vendor_class strings */
+          for (i = 0; i < lease->vendorclass_count; ++i)
+          {
+            snprintf(sfmt, sizeof(sfmt), "vendor_class%i", i);
+            buf = grab_extradata_into_var(buf, end, &value);
+            infos = add_lease_info(infos, sfmt, "s", DBUS_TYPE_STRING, value);
+          }
+        }
+      }
+#endif
+
+      buf = grab_extradata_into_var(buf, end, &value);
+      infos = add_lease_info(infos, "supplied_hostname", "s", DBUS_TYPE_STRING, value);
+
+      if (!is6)
+      {
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "cpewan_oui", "s", DBUS_TYPE_STRING, value);
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "cpewan_serial", "s", DBUS_TYPE_STRING, value);
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "cpewan_class", "s", DBUS_TYPE_STRING, value);
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "circuit_id", "s", DBUS_TYPE_STRING, value);
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "subscriber_id", "s", DBUS_TYPE_STRING, value);
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "remote_id", "s", DBUS_TYPE_STRING, value);
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "requested_options", "s", DBUS_TYPE_STRING, value);
+      }
+
+      buf = grab_extradata_into_var(buf, end, &value);
+      infos = add_lease_info(infos, "tags", "s", DBUS_TYPE_STRING, value);
+
+      if (is6)
+      {
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, "relay_address", "s", DBUS_TYPE_STRING, value);
+      }
+      else
+      {
+        value.str = lease->giaddr.s_addr != 0 ? inet_ntoa(lease->giaddr) : "";
+        infos = add_lease_info(infos, "relay_address", "s", DBUS_TYPE_STRING, value);
+      }
+
+      for (i = 0; buf; i++)
+      {
+        sprintf(sfmt, "user_class%i", i);
+        buf = grab_extradata_into_var(buf, end, &value);
+        infos = add_lease_info(infos, sfmt, "s", DBUS_TYPE_STRING, value);
+      }
+    }
+
+  /* Initialize D-Bus signal */
+  if (!(message = dbus_message_new_signal(DNSMASQ_PATH, daemon->dbus_name, "DhcpLeaseNotification")))
+    goto out;
+
+  /* Append dictionary content */
+  dbus_message_iter_init_append(message, &iter);
+  dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &action);
+  dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &ipaddr);
+  dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &hwaddr);
+  dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &hostname);
+  dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array);
+  for (it = infos; it; it = it->next) {
+    dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
+    dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &it->key);
+    dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, it->fmt, &variant);
+    switch (it->dbus_type)
+      {
+      case DBUS_TYPE_BOOLEAN:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.bool_val);
+        break;
+
+      case DBUS_TYPE_INT16:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.i16);
+        break;
+
+      case DBUS_TYPE_UINT16:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.u16);
+        break;
+
+      case DBUS_TYPE_INT32:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.i32);
+        break;
+
+      case DBUS_TYPE_UINT32:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.u32);
+        break;
+
+      case DBUS_TYPE_INT64:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.i64);
+        break;
+
+      case DBUS_TYPE_UINT64:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.u64);
+        break;
+
+      case DBUS_TYPE_DOUBLE:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.dbl);
+        break;
+
+      case DBUS_TYPE_STRING:
+        dbus_message_iter_append_basic(&variant, it->dbus_type, &it->value.str);
+        break;
+
+      default:
+        dbus_message_iter_abandon_container(&dict, &variant);
+        dbus_message_iter_abandon_container_if_open(&array, &dict);
+        dbus_message_iter_abandon_container_if_open(&iter, &array);
+        dbus_message_unref(message);
+        my_syslog(LOG_WARNING, _("Unknown D-Bus type specified for key '%s'. This will completely disable 'DhcpLeaseNotification' signal"),
+                                 it->key);
+        goto out;
+      }
+    dbus_message_iter_close_container(&dict, &variant);
+    dbus_message_iter_close_container(&array, &dict);
+  }
+  dbus_message_iter_close_container(&iter, &array);
+  dbus_connection_send(connection, message, NULL);
+  dbus_message_unref(message);
+
+out:
+  destroy_lease_infos(infos);
+}
+
+void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname, time_t now)
 {
   DBusConnection *connection = (DBusConnection *)daemon->dbus;
   DBusMessage* message = NULL;
@@ -878,6 +1212,9 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
     dbus_connection_send(connection, message, NULL);
   
   dbus_message_unref(message);
+
+  /* Now emit a notification with more informations */
+  emit_dhcplease_notification(connection, action_str, lease, daemon->addrbuff, mac, hostname, now);
 }
 #endif
 
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index b47ef74..f28374d 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -1422,7 +1422,7 @@ void lease_update_from_configs(void);
 int do_script_run(time_t now);
 void rerun_scripts(void);
 void lease_find_interfaces(time_t now);
-#ifdef HAVE_SCRIPT
+#if defined(HAVE_SCRIPT) || defined(HAVE_DBUS)
 void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, 
 			 unsigned int len, int delim);
 #endif
@@ -1472,7 +1472,7 @@ char *dbus_init(void);
 void check_dbus_listeners(void);
 void set_dbus_listeners(void);
 #  ifdef HAVE_DHCP
-void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname);
+void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname, time_t now);
 #  endif
 #endif
 
diff --git a/src/lease.c b/src/lease.c
index 23e6fe0..f3cefb1 100644
--- a/src/lease.c
+++ b/src/lease.c
@@ -1109,7 +1109,7 @@ int do_script_run(time_t now)
 	  queue_script(ACTION_DEL, lease, lease->old_hostname, now);
 #endif
 #ifdef HAVE_DBUS
-	  emit_dbus_signal(ACTION_DEL, lease, lease->old_hostname);
+	  emit_dbus_signal(ACTION_DEL, lease, lease->old_hostname, now);
 #endif
 	  old_leases = lease->next;
 	  
@@ -1145,7 +1145,7 @@ int do_script_run(time_t now)
 #endif
 #ifdef HAVE_DBUS
 	emit_dbus_signal((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease,
-			 lease->fqdn ? lease->fqdn : lease->hostname);
+			 lease->fqdn ? lease->fqdn : lease->hostname, now);
 #endif
 	lease->flags &= ~(LEASE_NEW | LEASE_CHANGED | LEASE_AUX_CHANGED | LEASE_EXP_CHANGED);
 	
@@ -1159,7 +1159,7 @@ int do_script_run(time_t now)
   return 0; /* nothing to do */
 }
 
-#ifdef HAVE_SCRIPT
+#if defined(HAVE_SCRIPT) || defined(HAVE_DBUS)
 /* delim == -1 -> delim = 0, but embedded 0s, creating extra records, are OK. */
 void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, unsigned int len, int delim)
 {
diff --git a/src/rfc2131.c b/src/rfc2131.c
index fc54aab..7fd059c 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -21,7 +21,7 @@
 #define option_len(opt) ((int)(((unsigned char *)(opt))[1]))
 #define option_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2u+(unsigned int)(i)]))
 
-#ifdef HAVE_SCRIPT
+#if defined(HAVE_SCRIPT) || defined(HAVE_DBUS)
 static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt);
 #endif
 
@@ -97,7 +97,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
   struct dhcp_opt *o;
   unsigned char pxe_uuid[17];
   unsigned char *oui = NULL, *serial = NULL;
-#ifdef HAVE_SCRIPT
+#if defined(HAVE_SCRIPT) || defined(HAVE_DBUS)
   unsigned char *class = NULL;
 #endif
 
@@ -163,7 +163,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 		  unsigned char *y = option_ptr(opt, offset + elen + 5);
 		  oui = option_find1(x, y, 1, 1);
 		  serial = option_find1(x, y, 2, 1);
-#ifdef HAVE_SCRIPT
+#if defined(HAVE_SCRIPT) || defined(HAVE_DBUS)
 		  class = option_find1(x, y, 3, 1);		  
 #endif
 		  /* If TR069-id is present set the tag "cpewan-id" to facilitate echoing 
@@ -1363,8 +1363,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
 	      /* pick up INIT-REBOOT events. */
 	      lease->flags |= LEASE_CHANGED;
 
-#ifdef HAVE_SCRIPT
-	      if (daemon->lease_change_command)
+#if defined(HAVE_SCRIPT) || defined(HAVE_DBUS)
+	      if (daemon->lease_change_command || daemon->dbus)
 		{
 		  struct dhcp_netid *n;
 		  
@@ -1659,7 +1659,7 @@ static int sanitise(unsigned char *opt, char *buf)
   return 1;
 }
 
-#ifdef HAVE_SCRIPT
+#if defined(HAVE_SCRIPT) || defined(HAVE_DBUS)
 static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt)
 {
   if (!opt)
-- 
2.24.0




More information about the Dnsmasq-discuss mailing list