vishwa | 5c912c8 | 2016-03-24 07:59:28 -0500 | [diff] [blame] | 1 | #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 */ |
| 11 | const char *bus_name = "org.openbmc.NetworkManager"; |
| 12 | const char *object_path = "/org/openbmc/NetworkManager/Interface"; |
| 13 | const char *intf_name = "org.openbmc.NetworkManager"; |
| 14 | |
| 15 | /* Used to tell Network Manager that Name Server listing is coming from DHCP */ |
| 16 | const 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 | */ |
| 27 | int 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 | |
| 107 | finish: |
| 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 | */ |
| 121 | int 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 | |
| 234 | void 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 | */ |
| 247 | int 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 | |
| 339 | int 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 | } |