blob: 33e6f14f8033fb2c98926dc3d1729d6661649f83 [file] [log] [blame]
#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;
}