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