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