Enabling updates to /etc/resolve.conf either manually or using DHCP supplied DNS
IP
diff --git a/netman_watch_dns.c b/netman_watch_dns.c
new file mode 100644
index 0000000..33e6f14
--- /dev/null
+++ b/netman_watch_dns.c
@@ -0,0 +1,391 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <limits.h>
+#include <errno.h>
+#include <unistd.h>
+#include <systemd/sd-bus.h>
+
+/* Dbus settings to get the DNS entries updated in resolv.conf */
+const char *bus_name = "org.openbmc.NetworkManager";
+const char *object_path = "/org/openbmc/NetworkManager/Interface";
+const char *intf_name = "org.openbmc.NetworkManager";
+
+/* Used to tell Network Manager that Name Server listing is coming from DHCP */
+const char *DHCP_MARKER = "DHCP_AUTO= ";
+
+#define DHCP_MARKER_LEN strlen(DHCP_MARKER)
+
+/*
+ * ----------------------------------------------
+ * Receives the buffer that has the IPs of DNS
+ * and then makes a dbus call to have these DNS
+ * entries updated in /etc/resolv.conf
+ * --------------------------------------------
+ */
+int update_resolv_conf(const char *dns_entry)
+{
+ /* To read the message sent by dbus handler */
+ const char *resp_msg = NULL;
+
+ /* Generic error handler */
+ int rc = 0;
+
+ if(dns_entry == NULL || !(strlen(dns_entry)))
+ {
+ fprintf(stderr,"Invalid DNS entry\n");
+ return -1;
+ }
+
+ /*
+ * Since 'state' file gets touched many a times during the network setting,
+ * it does not make sense to have the same DNS entry updated in
+ * resolv.conf. SO we can actually have a cache of what was previously
+ * updated and not update if the same DNS info is supplied again.
+ * Eventhough this approach is a gain performance wise, it will
+ * open up windows. Assume a case where DHCP has given 1.2.3.4 as IP and
+ * user goes and updates the DNS as 4.5.6.7 and then user restarts the
+ * network and thus getting the value of 1.2.3.4. If we maintain a cache of
+ * what was previously updated, since we get 1.2.3.4 again, we will not
+ * update with 1.2.3.4 eventhough that is needed since 4.5.6.7 would not
+ * make sense. So doing updates to resolv.conf each time is bit of a
+ * overkill but its error proof.
+ */
+
+ /* Encapsulated respose by dbus handler */
+ sd_bus_message *response = NULL;
+
+ /* Errors reported by dbus handler */
+ sd_bus_error bus_error = SD_BUS_ERROR_NULL;
+
+ /*
+ * Gets a hook onto SYSTEM bus. This API may get called multiple
+ * times so do not want to have so many instances of bus as it
+ * leads to system resource issues. Re use the one that is present.
+ */
+ static sd_bus *bus_type = NULL;
+ if(bus_type == NULL)
+ {
+ rc = sd_bus_open_system(&bus_type);
+ if(rc < 0)
+ {
+ fprintf(stderr,"Error:[%s] getting system bus\n",strerror(-rc));
+ return rc;
+ }
+ }
+
+ rc = sd_bus_call_method(
+ bus_type, /* In the System Bus */
+ bus_name, /* Service to contact */
+ object_path, /* Object path */
+ intf_name, /* Interface name */
+ "SetNameServers",/* Method to be called */
+ &bus_error, /* object to return error */
+ &response, /* Response buffer if any */
+ "s", /* Input as strings */
+ dns_entry, /* string of DNS IPs */
+ NULL); /* No return message expected */
+ if(rc < 0)
+ {
+ fprintf(stderr,"ERROR updating DNS entries:[%s]\n",bus_error.message);
+ goto finish;
+ }
+
+ /* Extract the encapsulated response message */
+ rc = sd_bus_message_read(response, "s", &resp_msg);
+ if (rc < 0)
+ {
+ fprintf(stderr,"Error:[%s] reading dns"
+ " updation status\n",strerror(-rc));
+ }
+ else
+ {
+ printf("%s\n",resp_msg);
+ }
+
+finish:
+ sd_bus_error_free(&bus_error);
+ response = sd_bus_message_unref(response);
+
+ return rc;
+}
+
+/*
+ * ----------------------------------------------
+ * Gets invoked by inotify handler whenever the
+ * netif/state file gets modified
+ * -or- when this binary first gets launched.
+ * --------------------------------------------
+ */
+int read_netif_state_file(const char *netif_dir, const char *state_file)
+{
+ FILE *fp;
+
+ /* Each line read from 'state' file */
+ char *line = NULL;
+
+ /* A list containing all the DNS IPs that are mentioned in 'state' file. */
+ char *dns_list = NULL;
+
+ /* length of each line read from 'state' file */
+ size_t len = 0;
+
+ /* Length of current and updated dns list */
+ size_t list_len = 0;
+ size_t new_list_len = 0;
+
+ /* Generic error reporter */
+ int rc = 0;
+
+ /* Extract the 'state' file */
+ char netif_state_file[strlen(netif_dir) + strlen(state_file) + 2];
+ sprintf(netif_state_file,"%s%s", netif_dir,state_file);
+
+ fp = fopen(netif_state_file,"r");
+ if(fp == NULL)
+ {
+ fprintf(stderr,"Error opening[%s]\n",netif_state_file);
+ return -1;
+ }
+
+ /*
+ * Read the file line by line and look for the one that starts with DNS
+ * If there is one, then what appears after DNS= are the IPs of the DNS
+ * Just checking for DNS here since any non standard IP is rejected by
+ * the dbus handler. This is to cater to cases where file may have DNS =
+ */
+ while ((getline(&line, &len, fp)) != -1)
+ {
+ if(!(strncmp(line,"DNS",3)))
+ {
+ /* Go all the way until the start of IPs */
+ char *dns_entry = strrchr(line, '=');
+
+ /* Advance to the first character after = */
+ dns_entry = &((char *)dns_entry)[1];
+
+ /* If we have never populated anything into the list */
+ if(dns_list == NULL)
+ {
+ /* The extra 2 characters to leave some gaps between DNS
+ * entries that would come from multiple lines since each line
+ * would start with DNS= and this overlaps with previous DNS IP.
+ * Although I don't see this as any reality to have DNS IPs
+ * spreading multiple lines.
+ */
+ list_len = strlen(dns_entry) + DHCP_MARKER_LEN + 2;
+ dns_list = (char *)malloc(list_len);
+
+ /*
+ * Populate DHCP_AUTO= along with the first line of DNS entries
+ * This will help in putting the appropriate comments in
+ * /etc/resolv.conf indicating the mode of DNS setting.
+ */
+ memset(dns_list, ' ', list_len);
+ memcpy(dns_list, DHCP_MARKER, DHCP_MARKER_LEN);
+ memcpy(&dns_list[DHCP_MARKER_LEN], dns_entry, strlen(dns_entry));
+ dns_list[list_len]='\0';
+ }
+ else
+ {
+ /* This would be the entries that are coming from second+ line */
+ new_list_len = strlen(dns_entry) + list_len + 2;
+ dns_list = (char *)realloc(dns_list, new_list_len);
+
+ memset(&dns_list[list_len], ' ', strlen(dns_entry) + 2);
+ memmove(&dns_list[list_len], dns_entry, strlen(dns_entry));
+
+ /* Starting offset for next line */
+ list_len = new_list_len;
+ dns_list[list_len] = '\0';
+ }
+ }
+
+ /* Memory is allocated by getline and user needs to free */
+ if(line)
+ {
+ free(line);
+ line = NULL;
+ }
+ }
+
+ /* If we have found some or more DNS entries */
+ if(dns_list)
+ {
+ /*
+ * Being extra cautious if string somehow is not null terminated in the loop
+ */
+ dns_list[list_len] = '\0';
+
+ rc = update_resolv_conf(dns_list);
+ if(rc < 0)
+ {
+ fprintf(stderr,"Error updating resolv.conf with:[%s]\n",dns_list);
+ }
+
+ free(dns_list);
+ dns_list = NULL;
+ }
+
+ return 0;
+}
+
+void usage(void)
+{
+ printf("Usage: netman_watch_dns <Absolute path of DHCP netif state file>\n"
+ "Example: netman_watch_dns /run/systemd/netif/state\n");
+ return;
+}
+
+/*
+ * ------------------------------------------------------
+ * Registers a inotify watch on the state file and calls
+ * into handling the state file whenever there is a change.
+ * ------------------------------------------------------
+ */
+int watch_for_dns_change(char *netif_dir, char *state_file)
+{
+ int inotify_fd, wd;
+
+ /* the aligned statement below is per the recommendation by inotify(7) */
+ char event_data[4096]
+ __attribute__ ((aligned(__alignof__(struct inotify_event))));
+
+ /* To check the number of bytes read from inotify event */
+ ssize_t bytes_read = 0;
+
+ /* To walk event by event when inotify returns */
+ char *ptr = NULL;
+
+ /* Variable to hold individual event notification */
+ const struct inotify_event *event = NULL;
+
+ /* Generic error handler */
+ int rc = 0;
+
+ /* Create inotify instance */
+ inotify_fd = inotify_init();
+ if(inotify_fd == -1)
+ {
+ fprintf(stderr,"Error:[%s] initializing Inotify",strerror(errno));
+ return -1;
+ }
+
+ /* Register to write actions on the netif directory */
+ wd = inotify_add_watch(inotify_fd, netif_dir, IN_MODIFY);
+ if(wd == -1)
+ {
+ fprintf(stderr,"Error:[%s] adding watch for:[%s]\n",
+ strerror(errno), netif_dir);
+ return -1;
+ }
+
+ /*
+ * When this is first launched, we need to go see
+ * if there is anything present in the state file.
+ * Doing it here to close any gaps between the file
+ * getting created before registering inotifier.
+ */
+ rc = read_netif_state_file(netif_dir, state_file);
+ if(rc < 0)
+ {
+ fprintf(stderr,"Error doing initial processing of state file\n");
+ }
+
+ /* Read events forever */
+ for (;;)
+ {
+ memset(event_data, 0x0, sizeof(event_data));
+ bytes_read = read(inotify_fd, event_data, sizeof(event_data));
+ if(bytes_read <= 0)
+ {
+ fprintf(stderr,"event_data read from inotify fd was Invalid\n");
+ continue;
+ }
+
+ /* Process all of the events in buffer returned by read() */
+ for(ptr = event_data; ptr < event_data + bytes_read;
+ ptr += sizeof(struct inotify_event) + event->len)
+ {
+ event = (struct inotify_event *)ptr;
+
+ /*
+ * We are not interested in anything other than updates to
+ * state file. Now when this code is being written, its in
+ * /run/systemd/netif/state.
+ */
+ if((event->len > 0) && (strstr(event->name, state_file)))
+ {
+ rc = read_netif_state_file(netif_dir, state_file);
+ if(rc < 0)
+ {
+ fprintf(stderr,"Error processing inotify event\n");
+ }
+ }
+ } /* Processing all inotify events. */
+ } /* Endless loop waiting for events. */
+
+ /*
+ * Technically, we should not reach here since the monitor function
+ * is not supposed to stop even on error. But for completeness.....
+ */
+ inotify_rm_watch(inotify_fd, wd);
+ close(inotify_fd);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ /* Generic error handler */
+ int rc = 0;
+
+ /* Sanity checking */
+ if(argc != 2 || argv[1] == NULL)
+ {
+ usage();
+ return -1;
+ }
+
+ /*
+ * We now have the job of extracting the directory and
+ * the state file from the user supplied input.
+ */
+ char netif_dir[strlen(argv[1]) + 1];
+ memset(netif_dir, 0x0, sizeof(netif_dir));
+
+ /* File where the actual DNS= entry is found */
+ char *state_file = NULL;
+
+ /* Filter invalid inputs */
+ state_file = strrchr(argv[1], '/');
+ if(strlen(state_file) <= 1)
+ {
+ fprintf(stderr,"Invalid state file :[%s] specified\n",state_file);
+ return -1;
+ }
+ else
+ {
+ /* we have /state now and what we need is just the 'state' */
+ state_file = &((char *)state_file)[1];
+
+ /*
+ * Also extract the Absolute Path of the directory
+ * containing this state file
+ */
+ strncpy(netif_dir, argv[1], strlen(argv[1]) - strlen(state_file));
+ strcat(netif_dir,"\0");
+ }
+
+ printf("Watching for changes in DNS settings..\n");
+
+ /* Now that we have checked it once. rest is all notification bases. */
+ rc = watch_for_dns_change(netif_dir, state_file);
+ if(rc < 0)
+ {
+ fprintf(stderr,"Error watching for DNS changes\n");
+ }
+
+ return 0;
+}