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