summaryrefslogtreecommitdiffstats
path: root/netman_watch_dns.c
blob: 33e6f14f8033fb2c98926dc3d1729d6661649f83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#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;
}
OpenPOWER on IntegriCloud