#include #include #include #include #include #include #include #include /* 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 \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; }