blob: 33e6f14f8033fb2c98926dc3d1729d6661649f83 [file] [log] [blame]
vishwa5c912c82016-03-24 07:59:28 -05001#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <sys/inotify.h>
5#include <limits.h>
6#include <errno.h>
7#include <unistd.h>
8#include <systemd/sd-bus.h>
9
10/* Dbus settings to get the DNS entries updated in resolv.conf */
11const char *bus_name = "org.openbmc.NetworkManager";
12const char *object_path = "/org/openbmc/NetworkManager/Interface";
13const char *intf_name = "org.openbmc.NetworkManager";
14
15/* Used to tell Network Manager that Name Server listing is coming from DHCP */
16const char *DHCP_MARKER = "DHCP_AUTO= ";
17
18#define DHCP_MARKER_LEN strlen(DHCP_MARKER)
19
20/*
21 * ----------------------------------------------
22 * Receives the buffer that has the IPs of DNS
23 * and then makes a dbus call to have these DNS
24 * entries updated in /etc/resolv.conf
25 * --------------------------------------------
26 */
27int update_resolv_conf(const char *dns_entry)
28{
29 /* To read the message sent by dbus handler */
30 const char *resp_msg = NULL;
31
32 /* Generic error handler */
33 int rc = 0;
34
35 if(dns_entry == NULL || !(strlen(dns_entry)))
36 {
37 fprintf(stderr,"Invalid DNS entry\n");
38 return -1;
39 }
40
41 /*
42 * Since 'state' file gets touched many a times during the network setting,
43 * it does not make sense to have the same DNS entry updated in
44 * resolv.conf. SO we can actually have a cache of what was previously
45 * updated and not update if the same DNS info is supplied again.
46 * Eventhough this approach is a gain performance wise, it will
47 * open up windows. Assume a case where DHCP has given 1.2.3.4 as IP and
48 * user goes and updates the DNS as 4.5.6.7 and then user restarts the
49 * network and thus getting the value of 1.2.3.4. If we maintain a cache of
50 * what was previously updated, since we get 1.2.3.4 again, we will not
51 * update with 1.2.3.4 eventhough that is needed since 4.5.6.7 would not
52 * make sense. So doing updates to resolv.conf each time is bit of a
53 * overkill but its error proof.
54 */
55
56 /* Encapsulated respose by dbus handler */
57 sd_bus_message *response = NULL;
58
59 /* Errors reported by dbus handler */
60 sd_bus_error bus_error = SD_BUS_ERROR_NULL;
61
62 /*
63 * Gets a hook onto SYSTEM bus. This API may get called multiple
64 * times so do not want to have so many instances of bus as it
65 * leads to system resource issues. Re use the one that is present.
66 */
67 static sd_bus *bus_type = NULL;
68 if(bus_type == NULL)
69 {
70 rc = sd_bus_open_system(&bus_type);
71 if(rc < 0)
72 {
73 fprintf(stderr,"Error:[%s] getting system bus\n",strerror(-rc));
74 return rc;
75 }
76 }
77
78 rc = sd_bus_call_method(
79 bus_type, /* In the System Bus */
80 bus_name, /* Service to contact */
81 object_path, /* Object path */
82 intf_name, /* Interface name */
83 "SetNameServers",/* Method to be called */
84 &bus_error, /* object to return error */
85 &response, /* Response buffer if any */
86 "s", /* Input as strings */
87 dns_entry, /* string of DNS IPs */
88 NULL); /* No return message expected */
89 if(rc < 0)
90 {
91 fprintf(stderr,"ERROR updating DNS entries:[%s]\n",bus_error.message);
92 goto finish;
93 }
94
95 /* Extract the encapsulated response message */
96 rc = sd_bus_message_read(response, "s", &resp_msg);
97 if (rc < 0)
98 {
99 fprintf(stderr,"Error:[%s] reading dns"
100 " updation status\n",strerror(-rc));
101 }
102 else
103 {
104 printf("%s\n",resp_msg);
105 }
106
107finish:
108 sd_bus_error_free(&bus_error);
109 response = sd_bus_message_unref(response);
110
111 return rc;
112}
113
114/*
115 * ----------------------------------------------
116 * Gets invoked by inotify handler whenever the
117 * netif/state file gets modified
118 * -or- when this binary first gets launched.
119 * --------------------------------------------
120 */
121int read_netif_state_file(const char *netif_dir, const char *state_file)
122{
123 FILE *fp;
124
125 /* Each line read from 'state' file */
126 char *line = NULL;
127
128 /* A list containing all the DNS IPs that are mentioned in 'state' file. */
129 char *dns_list = NULL;
130
131 /* length of each line read from 'state' file */
132 size_t len = 0;
133
134 /* Length of current and updated dns list */
135 size_t list_len = 0;
136 size_t new_list_len = 0;
137
138 /* Generic error reporter */
139 int rc = 0;
140
141 /* Extract the 'state' file */
142 char netif_state_file[strlen(netif_dir) + strlen(state_file) + 2];
143 sprintf(netif_state_file,"%s%s", netif_dir,state_file);
144
145 fp = fopen(netif_state_file,"r");
146 if(fp == NULL)
147 {
148 fprintf(stderr,"Error opening[%s]\n",netif_state_file);
149 return -1;
150 }
151
152 /*
153 * Read the file line by line and look for the one that starts with DNS
154 * If there is one, then what appears after DNS= are the IPs of the DNS
155 * Just checking for DNS here since any non standard IP is rejected by
156 * the dbus handler. This is to cater to cases where file may have DNS =
157 */
158 while ((getline(&line, &len, fp)) != -1)
159 {
160 if(!(strncmp(line,"DNS",3)))
161 {
162 /* Go all the way until the start of IPs */
163 char *dns_entry = strrchr(line, '=');
164
165 /* Advance to the first character after = */
166 dns_entry = &((char *)dns_entry)[1];
167
168 /* If we have never populated anything into the list */
169 if(dns_list == NULL)
170 {
171 /* The extra 2 characters to leave some gaps between DNS
172 * entries that would come from multiple lines since each line
173 * would start with DNS= and this overlaps with previous DNS IP.
174 * Although I don't see this as any reality to have DNS IPs
175 * spreading multiple lines.
176 */
177 list_len = strlen(dns_entry) + DHCP_MARKER_LEN + 2;
178 dns_list = (char *)malloc(list_len);
179
180 /*
181 * Populate DHCP_AUTO= along with the first line of DNS entries
182 * This will help in putting the appropriate comments in
183 * /etc/resolv.conf indicating the mode of DNS setting.
184 */
185 memset(dns_list, ' ', list_len);
186 memcpy(dns_list, DHCP_MARKER, DHCP_MARKER_LEN);
187 memcpy(&dns_list[DHCP_MARKER_LEN], dns_entry, strlen(dns_entry));
188 dns_list[list_len]='\0';
189 }
190 else
191 {
192 /* This would be the entries that are coming from second+ line */
193 new_list_len = strlen(dns_entry) + list_len + 2;
194 dns_list = (char *)realloc(dns_list, new_list_len);
195
196 memset(&dns_list[list_len], ' ', strlen(dns_entry) + 2);
197 memmove(&dns_list[list_len], dns_entry, strlen(dns_entry));
198
199 /* Starting offset for next line */
200 list_len = new_list_len;
201 dns_list[list_len] = '\0';
202 }
203 }
204
205 /* Memory is allocated by getline and user needs to free */
206 if(line)
207 {
208 free(line);
209 line = NULL;
210 }
211 }
212
213 /* If we have found some or more DNS entries */
214 if(dns_list)
215 {
216 /*
217 * Being extra cautious if string somehow is not null terminated in the loop
218 */
219 dns_list[list_len] = '\0';
220
221 rc = update_resolv_conf(dns_list);
222 if(rc < 0)
223 {
224 fprintf(stderr,"Error updating resolv.conf with:[%s]\n",dns_list);
225 }
226
227 free(dns_list);
228 dns_list = NULL;
229 }
230
231 return 0;
232}
233
234void usage(void)
235{
236 printf("Usage: netman_watch_dns <Absolute path of DHCP netif state file>\n"
237 "Example: netman_watch_dns /run/systemd/netif/state\n");
238 return;
239}
240
241/*
242 * ------------------------------------------------------
243 * Registers a inotify watch on the state file and calls
244 * into handling the state file whenever there is a change.
245 * ------------------------------------------------------
246 */
247int watch_for_dns_change(char *netif_dir, char *state_file)
248{
249 int inotify_fd, wd;
250
251 /* the aligned statement below is per the recommendation by inotify(7) */
252 char event_data[4096]
253 __attribute__ ((aligned(__alignof__(struct inotify_event))));
254
255 /* To check the number of bytes read from inotify event */
256 ssize_t bytes_read = 0;
257
258 /* To walk event by event when inotify returns */
259 char *ptr = NULL;
260
261 /* Variable to hold individual event notification */
262 const struct inotify_event *event = NULL;
263
264 /* Generic error handler */
265 int rc = 0;
266
267 /* Create inotify instance */
268 inotify_fd = inotify_init();
269 if(inotify_fd == -1)
270 {
271 fprintf(stderr,"Error:[%s] initializing Inotify",strerror(errno));
272 return -1;
273 }
274
275 /* Register to write actions on the netif directory */
276 wd = inotify_add_watch(inotify_fd, netif_dir, IN_MODIFY);
277 if(wd == -1)
278 {
279 fprintf(stderr,"Error:[%s] adding watch for:[%s]\n",
280 strerror(errno), netif_dir);
281 return -1;
282 }
283
284 /*
285 * When this is first launched, we need to go see
286 * if there is anything present in the state file.
287 * Doing it here to close any gaps between the file
288 * getting created before registering inotifier.
289 */
290 rc = read_netif_state_file(netif_dir, state_file);
291 if(rc < 0)
292 {
293 fprintf(stderr,"Error doing initial processing of state file\n");
294 }
295
296 /* Read events forever */
297 for (;;)
298 {
299 memset(event_data, 0x0, sizeof(event_data));
300 bytes_read = read(inotify_fd, event_data, sizeof(event_data));
301 if(bytes_read <= 0)
302 {
303 fprintf(stderr,"event_data read from inotify fd was Invalid\n");
304 continue;
305 }
306
307 /* Process all of the events in buffer returned by read() */
308 for(ptr = event_data; ptr < event_data + bytes_read;
309 ptr += sizeof(struct inotify_event) + event->len)
310 {
311 event = (struct inotify_event *)ptr;
312
313 /*
314 * We are not interested in anything other than updates to
315 * state file. Now when this code is being written, its in
316 * /run/systemd/netif/state.
317 */
318 if((event->len > 0) && (strstr(event->name, state_file)))
319 {
320 rc = read_netif_state_file(netif_dir, state_file);
321 if(rc < 0)
322 {
323 fprintf(stderr,"Error processing inotify event\n");
324 }
325 }
326 } /* Processing all inotify events. */
327 } /* Endless loop waiting for events. */
328
329 /*
330 * Technically, we should not reach here since the monitor function
331 * is not supposed to stop even on error. But for completeness.....
332 */
333 inotify_rm_watch(inotify_fd, wd);
334 close(inotify_fd);
335
336 return 0;
337}
338
339int main(int argc, char *argv[])
340{
341 /* Generic error handler */
342 int rc = 0;
343
344 /* Sanity checking */
345 if(argc != 2 || argv[1] == NULL)
346 {
347 usage();
348 return -1;
349 }
350
351 /*
352 * We now have the job of extracting the directory and
353 * the state file from the user supplied input.
354 */
355 char netif_dir[strlen(argv[1]) + 1];
356 memset(netif_dir, 0x0, sizeof(netif_dir));
357
358 /* File where the actual DNS= entry is found */
359 char *state_file = NULL;
360
361 /* Filter invalid inputs */
362 state_file = strrchr(argv[1], '/');
363 if(strlen(state_file) <= 1)
364 {
365 fprintf(stderr,"Invalid state file :[%s] specified\n",state_file);
366 return -1;
367 }
368 else
369 {
370 /* we have /state now and what we need is just the 'state' */
371 state_file = &((char *)state_file)[1];
372
373 /*
374 * Also extract the Absolute Path of the directory
375 * containing this state file
376 */
377 strncpy(netif_dir, argv[1], strlen(argv[1]) - strlen(state_file));
378 strcat(netif_dir,"\0");
379 }
380
381 printf("Watching for changes in DNS settings..\n");
382
383 /* Now that we have checked it once. rest is all notification bases. */
384 rc = watch_for_dns_change(netif_dir, state_file);
385 if(rc < 0)
386 {
387 fprintf(stderr,"Error watching for DNS changes\n");
388 }
389
390 return 0;
391}