]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/statd/sm-notify.c
sm-notify: Support IPv6 DNS lookups in smn_lookup
[nfs-utils.git] / utils / statd / sm-notify.c
1 /*
2  * Send NSM notify calls to all hosts listed in /var/lib/sm
3  *
4  * Copyright (C) 2004-2006 Olaf Kirch <okir@suse.de>
5  */
6
7 #ifdef HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10
11 #include <err.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <sys/stat.h>
15 #include <sys/poll.h>
16 #include <sys/param.h>
17 #include <sys/syslog.h>
18 #include <arpa/inet.h>
19 #include <dirent.h>
20 #include <time.h>
21 #include <stdio.h>
22 #include <getopt.h>
23 #include <stdlib.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <stdarg.h>
28 #include <netdb.h>
29 #include <errno.h>
30 #include <grp.h>
31
32 #include "xlog.h"
33 #include "nsm.h"
34 #include "nfsrpc.h"
35
36 #ifndef HAVE_DECL_AI_ADDRCONFIG
37 #define AI_ADDRCONFIG   0
38 #endif
39
40 #define NSM_TIMEOUT     2
41 #define NSM_MAX_TIMEOUT 120     /* don't make this too big */
42
43 struct nsm_host {
44         struct nsm_host *       next;
45         char *                  name;
46         struct addrinfo         *ai;
47         time_t                  last_used;
48         time_t                  send_next;
49         unsigned int            timeout;
50         unsigned int            retries;
51         uint32_t                xid;
52 };
53
54 static char             nsm_hostname[256];
55 static int              nsm_state;
56 static int              nsm_family = AF_INET;
57 static int              opt_debug = 0;
58 static _Bool            opt_update_state = true;
59 static unsigned int     opt_max_retry = 15 * 60;
60 static char *           opt_srcaddr = NULL;
61 static char *           opt_srcport = NULL;
62
63 static void             notify(const int sock);
64 static int              notify_host(int, struct nsm_host *);
65 static void             recv_reply(int);
66 static void             insert_host(struct nsm_host *);
67 static struct nsm_host *find_host(uint32_t);
68 static int              record_pid(void);
69
70 static struct nsm_host *        hosts = NULL;
71
72 __attribute_malloc__
73 static struct addrinfo *
74 smn_lookup(const char *name)
75 {
76         struct addrinfo *ai = NULL;
77         struct addrinfo hint = {
78                 .ai_flags       = AI_ADDRCONFIG,
79                 .ai_family      = (nsm_family == AF_INET ? AF_INET: AF_UNSPEC),
80                 .ai_protocol    = (int)IPPROTO_UDP,
81         };
82         int error;
83
84         error = getaddrinfo(name, NULL, &hint, &ai);
85         if (error != 0) {
86                 xlog(D_GENERAL, "getaddrinfo(3): %s", gai_strerror(error));
87                 return NULL;
88         }
89
90         return ai;
91 }
92
93 __attribute_malloc__
94 static struct nsm_host *
95 smn_alloc_host(const char *hostname, const time_t timestamp)
96 {
97         struct nsm_host *host;
98
99         host = calloc(1, sizeof(*host));
100         if (host == NULL)
101                 goto out_nomem;
102
103         host->name = strdup(hostname);
104         if (host->name == NULL) {
105                 free(host);
106                 goto out_nomem;
107         }
108
109         host->last_used = timestamp;
110         host->timeout = NSM_TIMEOUT;
111         host->retries = 100;            /* force address retry */
112
113         return host;
114
115 out_nomem:
116         xlog_warn("Unable to allocate memory");
117         return NULL;
118 }
119
120 static void smn_forget_host(struct nsm_host *host)
121 {
122         xlog(D_CALL, "Removing %s from notify list", host->name);
123
124         nsm_delete_notified_host(host->name);
125
126         free(host->name);
127         if (host->ai)
128                 freeaddrinfo(host->ai);
129
130         free(host);
131 }
132
133 static unsigned int
134 smn_get_host(const char *hostname,
135                 __attribute__ ((unused)) const struct sockaddr *sap,
136                 __attribute__ ((unused)) const struct mon *m,
137                 const time_t timestamp)
138 {
139         struct nsm_host *host;
140
141         host = smn_alloc_host(hostname, timestamp);
142         if (host == NULL)
143                 return 0;
144
145         insert_host(host);
146         xlog(D_GENERAL, "Added host %s to notify list", hostname);
147         return 1;
148 }
149
150 #ifdef IPV6_SUPPORTED
151 static int smn_socket(void)
152 {
153         int sock;
154
155         /*
156          * Use an AF_INET socket if IPv6 is disabled on the
157          * local system.
158          */
159         sock = socket(AF_INET6, SOCK_DGRAM, 0);
160         if (sock == -1) {
161                 if (errno != EAFNOSUPPORT) {
162                         xlog(L_ERROR, "Failed to create RPC socket: %m");
163                         return -1;
164                 }
165                 sock = socket(AF_INET, SOCK_DGRAM, 0);
166                 if (sock < 0) {
167                         xlog(L_ERROR, "Failed to create RPC socket: %m");
168                         return -1;
169                 }
170         } else
171                 nsm_family = AF_INET6;
172
173         if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
174                 xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m");
175                 goto out_close;
176         }
177
178         /*
179          * TI-RPC over IPv6 (udp6/tcp6) does not handle IPv4.  However,
180          * since sm-notify open-codes all of its RPC support, it can
181          * use a single socket and let the local network stack provide
182          * the correct mapping between address families automatically.
183          * This is the same thing that is done in the kernel.
184          */
185         if (nsm_family == AF_INET6) {
186                 const int zero = 0;
187                 socklen_t zerolen = (socklen_t)sizeof(zero);
188
189                 if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY,
190                                         (char *)&zero, zerolen) == -1) {
191                         xlog(L_ERROR, "setsockopt(3) on RPC socket failed: %m");
192                         goto out_close;
193                 }
194         }
195
196         return sock;
197
198 out_close:
199         (void)close(sock);
200         return -1;
201 }
202 #else   /* !IPV6_SUPPORTED */
203 static int smn_socket(void)
204 {
205         int sock;
206
207         sock = socket(AF_INET, SOCK_DGRAM, 0);
208         if (sock == -1) {
209                 xlog(L_ERROR, "Failed to create RPC socket: %m");
210                 return -1;
211         }
212
213         if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
214                 xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m");
215                 (void)close(sock);
216                 return -1;
217         }
218
219         return sock;
220 }
221 #endif  /* !IPV6_SUPPORTED */
222
223 /*
224  * If admin specified a source address or srcport, then convert those
225  * to a sockaddr and return it.   Otherwise, return an ANYADDR address.
226  */
227 __attribute_malloc__
228 static struct addrinfo *
229 smn_bind_address(const char *srcaddr, const char *srcport)
230 {
231         struct addrinfo *ai = NULL;
232         struct addrinfo hint = {
233                 .ai_flags       = AI_NUMERICSERV,
234                 .ai_family      = nsm_family,
235                 .ai_protocol    = (int)IPPROTO_UDP,
236         };
237         int error;
238
239         if (srcaddr == NULL)
240                 hint.ai_flags |= AI_PASSIVE;
241
242         if (srcport == NULL)
243                 error = getaddrinfo(srcaddr, "", &hint, &ai);
244         else
245                 error = getaddrinfo(srcaddr, srcport, &hint, &ai);
246         if (error != 0) {
247                 xlog(L_ERROR,
248                         "Invalid bind address or port for RPC socket: %s",
249                                 gai_strerror(error));
250                 return NULL;
251         }
252
253         return ai;
254 }
255
256 #ifdef HAVE_LIBTIRPC
257 static int
258 smn_bindresvport(int sock, struct sockaddr *sap)
259 {
260         return bindresvport_sa(sock, sap);
261 }
262
263 #else   /* !HAVE_LIBTIRPC */
264 static int
265 smn_bindresvport(int sock, struct sockaddr *sap)
266 {
267         if (sap->sa_family != AF_INET) {
268                 errno = EAFNOSUPPORT;
269                 return -1;
270         }
271
272         return bindresvport(sock, (struct sockaddr_in *)(char *)sap);
273 }
274 #endif  /* !HAVE_LIBTIRPC */
275
276 /*
277  * Prepare a socket for sending RPC requests
278  *
279  * Returns a bound datagram socket file descriptor, or -1 if
280  * an error occurs.
281  */
282 static int
283 smn_create_socket(const char *srcaddr, const char *srcport)
284 {
285         int sock, retry_cnt = 0;
286         struct addrinfo *ai;
287
288 retry:
289         sock = smn_socket();
290         if (sock == -1)
291                 return -1;
292
293         ai = smn_bind_address(srcaddr, srcport);
294         if (ai == NULL) {
295                 (void)close(sock);
296                 return -1;
297         }
298
299         /* Use source port if provided on the command line,
300          * otherwise use bindresvport */
301         if (srcport) {
302                 if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
303                         xlog(L_ERROR, "Failed to bind RPC socket: %m");
304                         freeaddrinfo(ai);
305                         (void)close(sock);
306                         return -1;
307                 }
308         } else {
309                 struct servent *se;
310
311                 if (smn_bindresvport(sock, ai->ai_addr) == -1) {
312                         xlog(L_ERROR,
313                                 "bindresvport on RPC socket failed: %m");
314                         freeaddrinfo(ai);
315                         (void)close(sock);
316                         return -1;
317                 }
318
319                 /* try to avoid known ports */
320                 se = getservbyport((int)nfs_get_port(ai->ai_addr), "udp");
321                 if (se != NULL && retry_cnt < 100) {
322                         retry_cnt++;
323                         freeaddrinfo(ai);
324                         (void)close(sock);
325                         goto retry;
326                 }
327         }
328
329         freeaddrinfo(ai);
330         return sock;
331 }
332
333 int
334 main(int argc, char **argv)
335 {
336         int     c, sock, force = 0;
337         char *  progname;
338
339         progname = strrchr(argv[0], '/');
340         if (progname != NULL)
341                 progname++;
342         else
343                 progname = argv[0];
344
345         while ((c = getopt(argc, argv, "dm:np:v:P:f")) != -1) {
346                 switch (c) {
347                 case 'f':
348                         force = 1;
349                         break;
350                 case 'd':
351                         opt_debug++;
352                         break;
353                 case 'm':
354                         opt_max_retry = atoi(optarg) * 60;
355                         break;
356                 case 'n':
357                         opt_update_state = false;
358                         break;
359                 case 'p':
360                         opt_srcport = optarg;
361                         break;
362                 case 'v':
363                         opt_srcaddr = optarg;
364                         break;
365                 case 'P':
366                         if (!nsm_setup_pathnames(argv[0], optarg))
367                                 exit(1);
368                         break;
369
370                 default:
371                         goto usage;
372                 }
373         }
374
375         if (optind < argc) {
376 usage:          fprintf(stderr,
377                         "Usage: %s -notify [-dfq] [-m max-retry-minutes] [-p srcport]\n"
378                         "            [-P /path/to/state/directory] [-v my_host_name]\n",
379                         progname);
380                 exit(1);
381         }
382
383         xlog_syslog(1);
384         if (opt_debug) {
385                 xlog_stderr(1);
386                 xlog_config(D_ALL, 1);
387         } else
388                 xlog_stderr(0);
389
390         xlog_open(progname);
391         xlog(L_NOTICE, "Version " VERSION " starting");
392
393         if (nsm_is_default_parentdir()) {
394                 if (record_pid() == 0 && force == 0 && opt_update_state) {
395                         /* already run, don't try again */
396                         xlog(L_NOTICE, "Already notifying clients; Exiting!");
397                         exit(0);
398                 }
399         }
400
401         if (opt_srcaddr) {
402                 strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname)-1);
403         } else
404         if (gethostname(nsm_hostname, sizeof(nsm_hostname)) < 0) {
405                 xlog(L_ERROR, "Failed to obtain name of local host: %m");
406                 exit(1);
407         }
408
409         (void)nsm_retire_monitored_hosts();
410         if (nsm_load_notify_list(smn_get_host) == 0) {
411                 xlog(D_GENERAL, "No hosts to notify; exiting");
412                 return 0;
413         }
414
415         nsm_state = nsm_get_state(opt_update_state);
416         if (nsm_state == 0)
417                 exit(1);
418         nsm_update_kernel_state(nsm_state);
419
420         if (!opt_debug) {
421                 xlog(L_NOTICE, "Backgrounding to notify hosts...\n");
422
423                 if (daemon(0, 0) < 0) {
424                         xlog(L_ERROR, "unable to background: %m");
425                         exit(1);
426                 }
427
428                 close(0);
429                 close(1);
430                 close(2);
431         }
432
433         sock = smn_create_socket(opt_srcaddr, opt_srcport);
434         if (sock == -1)
435                 exit(1);
436
437         if (!nsm_drop_privileges(-1))
438                 exit(1);
439
440         notify(sock);
441
442         if (hosts) {
443                 struct nsm_host *hp;
444
445                 while ((hp = hosts) != 0) {
446                         hosts = hp->next;
447                         xlog(L_NOTICE, "Unable to notify %s, giving up",
448                                 hp->name);
449                 }
450                 exit(1);
451         }
452
453         exit(0);
454 }
455
456 /*
457  * Notify hosts
458  */
459 static void
460 notify(const int sock)
461 {
462         time_t  failtime = 0;
463
464         if (opt_max_retry)
465                 failtime = time(NULL) + opt_max_retry;
466
467         while (hosts) {
468                 struct pollfd   pfd;
469                 time_t          now = time(NULL);
470                 unsigned int    sent = 0;
471                 struct nsm_host *hp;
472                 long            wait;
473
474                 if (failtime && now >= failtime)
475                         break;
476
477                 while (hosts && ((wait = hosts->send_next - now) <= 0)) {
478                         /* Never send more than 10 packets at once */
479                         if (sent++ >= 10)
480                                 break;
481
482                         /* Remove queue head */
483                         hp = hosts;
484                         hosts = hp->next;
485
486                         if (notify_host(sock, hp))
487                                 continue;
488
489                         /* Set the timeout for this call, using an
490                            exponential timeout strategy */
491                         wait = hp->timeout;
492                         if ((hp->timeout <<= 1) > NSM_MAX_TIMEOUT)
493                                 hp->timeout = NSM_MAX_TIMEOUT;
494                         hp->send_next = now + wait;
495                         hp->retries++;
496
497                         insert_host(hp);
498                 }
499                 if (hosts == NULL)
500                         return;
501
502                 xlog(D_GENERAL, "Host %s due in %ld seconds",
503                                 hosts->name, wait);
504
505                 pfd.fd = sock;
506                 pfd.events = POLLIN;
507
508                 wait *= 1000;
509                 if (wait < 100)
510                         wait = 100;
511                 if (poll(&pfd, 1, wait) != 1)
512                         continue;
513
514                 recv_reply(sock);
515         }
516 }
517
518 /*
519  * Send notification to a single host
520  */
521 static int
522 notify_host(int sock, struct nsm_host *host)
523 {
524         struct sockaddr *sap;
525         socklen_t salen;
526
527         if (host->ai == NULL) {
528                 host->ai = smn_lookup(host->name);
529                 if (host->ai == NULL) {
530                         xlog_warn("DNS resolution of %s failed; "
531                                 "retrying later", host->name);
532                         return 0;
533                 }
534         }
535
536         /* If we retransmitted 4 times, reset the port to force
537          * a new portmap lookup (in case statd was restarted).
538          * We also rotate through multiple IP addresses at this
539          * point.
540          */
541         if (host->retries >= 4) {
542                 /* don't rotate if there is only one addrinfo */
543                 if (host->ai->ai_next != NULL) {
544                         struct addrinfo *first = host->ai;
545                         struct addrinfo **next = &host->ai;
546
547                         /* remove the first entry from the list */
548                         host->ai = first->ai_next;
549                         first->ai_next = NULL;
550                         /* find the end of the list */
551                         next = &first->ai_next;
552                         while ( *next )
553                                 next = & (*next)->ai_next;
554                         /* put first entry at end */
555                         *next = first;
556                 }
557
558                 nfs_set_port(host->ai->ai_addr, 0);
559                 host->retries = 0;
560         }
561
562         sap = host->ai->ai_addr;
563         salen = host->ai->ai_addrlen;
564
565         if (nfs_get_port(sap) == 0)
566                 host->xid = nsm_xmit_rpcbind(sock, sap, SM_PROG, SM_VERS);
567         else
568                 host->xid = nsm_xmit_notify(sock, sap, salen,
569                                 SM_PROG, nsm_hostname, nsm_state);
570         
571         return 0;
572 }
573
574 /*
575  * Extract the returned port number and set up the SM_NOTIFY call.
576  */
577 static void
578 recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr)
579 {
580         uint16_t port = nsm_recv_rpcbind(sap->sa_family, xdr);
581
582         host->send_next = time(NULL);
583         host->xid = 0;
584
585         if (port == 0) {
586                 /* No binding for statd... */
587                 xlog(D_GENERAL, "No statd on host %s", host->name);
588                 host->timeout = NSM_MAX_TIMEOUT;
589                 host->send_next += NSM_MAX_TIMEOUT;
590         } else {
591                 nfs_set_port(sap, port);
592                 if (host->timeout >= NSM_MAX_TIMEOUT / 4)
593                         host->timeout = NSM_MAX_TIMEOUT / 4;
594         }
595
596         insert_host(host);
597 }
598
599 /*
600  * Successful NOTIFY call. Server returns void, so nothing
601  * we need to do here.
602  */
603 static void
604 recv_notify_reply(struct nsm_host *host)
605 {
606         xlog(D_GENERAL, "Host %s notified successfully", host->name);
607
608         smn_forget_host(host);
609 }
610
611 /*
612  * Receive reply from remote host
613  */
614 static void
615 recv_reply(int sock)
616 {
617         struct nsm_host *hp;
618         struct sockaddr *sap;
619         char msgbuf[NSM_MAXMSGSIZE];
620         uint32_t        xid;
621         ssize_t         msglen;
622         XDR             xdr;
623
624         memset(msgbuf, 0 , sizeof(msgbuf));
625         msglen = recv(sock, msgbuf, sizeof(msgbuf), 0);
626         if (msglen < 0)
627                 return;
628
629         xlog(D_GENERAL, "Received packet...");
630
631         memset(&xdr, 0, sizeof(xdr));
632         xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE);
633         xid = nsm_parse_reply(&xdr);
634         if (xid == 0)
635                 goto out;
636
637         /* Before we look at the data, find the host struct for
638            this reply */
639         if ((hp = find_host(xid)) == NULL)
640                 goto out;
641
642         sap = hp->ai->ai_addr;
643         if (nfs_get_port(sap) == 0)
644                 recv_rpcbind_reply(sap, hp, &xdr);
645         else
646                 recv_notify_reply(hp);
647
648 out:
649         xdr_destroy(&xdr);
650 }
651
652 /*
653  * Insert host into sorted list
654  */
655 static void
656 insert_host(struct nsm_host *host)
657 {
658         struct nsm_host **where, *p;
659
660         where = &hosts;
661         while ((p = *where) != 0) {
662                 /* Sort in ascending order of timeout */
663                 if (host->send_next < p->send_next)
664                         break;
665                 /* If we have the same timeout, put the
666                  * most recently used host first.
667                  * This makes sure that "recent" hosts
668                  * get notified first.
669                  */
670                 if (host->send_next == p->send_next
671                  && host->last_used > p->last_used)
672                         break;
673                 where = &p->next;
674         }
675
676         host->next = *where;
677         *where = host;
678 }
679
680 /*
681  * Find host given the XID
682  */
683 static struct nsm_host *
684 find_host(uint32_t xid)
685 {
686         struct nsm_host **where, *p;
687
688         where = &hosts;
689         while ((p = *where) != 0) {
690                 if (p->xid == xid) {
691                         *where = p->next;
692                         return p;
693                 }
694                 where = &p->next;
695         }
696         return NULL;
697 }
698
699 /*
700  * Record pid in /var/run/sm-notify.pid
701  * This file should remain until a reboot, even if the
702  * program exits.
703  * If file already exists, fail.
704  */
705 static int record_pid(void)
706 {
707         char pid[20];
708         ssize_t len;
709         int fd;
710
711         (void)snprintf(pid, sizeof(pid), "%d\n", (int)getpid());
712         fd = open("/var/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600);
713         if (fd < 0)
714                 return 0;
715
716         len = write(fd, pid, strlen(pid));
717         if ((len < 0) || ((size_t)len != strlen(pid))) {
718                 xlog_warn("Writing to pid file failed: errno %d (%m)",
719                                 errno);
720         }
721
722         (void)close(fd);
723         return 1;
724 }