]> git.decadent.org.uk Git - nfs-utils.git/blob - support/nfs/rpc_socket.c
2f76542e8f1a6ad88ca119845aaa68b8ac0d716f
[nfs-utils.git] / support / nfs / rpc_socket.c
1 /*
2  * Generic RPC client socket-level APIs for nfs-utils
3  *
4  * Copyright (C) 2008 Oracle Corporation.  All rights reserved.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 021110-1307, USA.
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
32
33 #include <sys/socket.h>
34 #include <netinet/in.h>
35 #include <netdb.h>
36 #include <arpa/inet.h>
37
38 #include <rpc/rpc.h>
39 #include <rpc/pmap_prot.h>
40
41 #include "nfsrpc.h"
42
43 #ifdef HAVE_LIBTIRPC
44 #include <netconfig.h>
45 #include <rpc/rpcb_prot.h>
46 #endif  /* HAVE_LIBTIRPC */
47
48 /*
49  * If "-1" is specified in the tv_sec field, use these defaults instead.
50  */
51 #define NFSRPC_TIMEOUT_UDP      (3)
52 #define NFSRPC_TIMEOUT_TCP      (10)
53
54 /*
55  * Set up an RPC client for communicating via a AF_LOCAL socket.
56  *
57  * @timeout is initialized upon return
58  *
59  * Returns a pointer to a prepared RPC client if successful; caller
60  * must destroy a non-NULL returned RPC client.  Otherwise NULL, and
61  * rpc_createerr.cf_stat is set to reflect the error.
62  */
63 static CLIENT *nfs_get_localclient(const struct sockaddr *sap,
64                                    const socklen_t salen,
65                                    const rpcprog_t program,
66                                    const rpcvers_t version,
67                                    struct timeval *timeout)
68 {
69 #ifdef HAVE_LIBTIRPC
70         struct sockaddr_storage address;
71         const struct netbuf nbuf = {
72                 .maxlen         = sizeof(struct sockaddr_un),
73                 .len            = (size_t)salen,
74                 .buf            = &address,
75         };
76 #endif  /* HAVE_LIBTIRPC */
77         CLIENT *client;
78         int sock;
79
80         sock = socket(AF_LOCAL, SOCK_STREAM, 0);
81         if (sock == -1) {
82                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
83                 rpc_createerr.cf_error.re_errno = errno;
84                 return NULL;
85         }
86
87         if (timeout->tv_sec == -1)
88                 timeout->tv_sec = NFSRPC_TIMEOUT_TCP;
89
90 #ifdef HAVE_LIBTIRPC
91         memcpy(nbuf.buf, sap, (size_t)salen);
92         client = clnt_vc_create(sock, &nbuf, program, version, 0, 0);
93 #else   /* !HAVE_LIBTIRPC */
94         client = clntunix_create((struct sockaddr_un *)sap,
95                                         program, version, &sock, 0, 0);
96 #endif  /* !HAVE_LIBTIRPC */
97         if (client != NULL)
98                 CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
99         else
100                 (void)close(sock);
101
102         return client;
103 }
104
105 /*
106  * Bind a socket using an unused ephemeral source port.
107  *
108  * Returns zero on success, or returns -1 on error.  errno is
109  * set to reflect the nature of the error.
110  */
111 static int nfs_bind(const int sock, const sa_family_t family)
112 {
113         struct sockaddr_in sin = {
114                 .sin_family             = AF_INET,
115                 .sin_addr.s_addr        = htonl(INADDR_ANY),
116         };
117         struct sockaddr_in6 sin6 = {
118                 .sin6_family            = AF_INET6,
119                 .sin6_addr              = IN6ADDR_ANY_INIT,
120         };
121
122         switch (family) {
123         case AF_INET:
124                 return bind(sock, (struct sockaddr *)&sin,
125                                         (socklen_t)sizeof(sin));
126         case AF_INET6:
127                 return bind(sock, (struct sockaddr *)&sin6,
128                                         (socklen_t)sizeof(sin6));
129         }
130
131         errno = EAFNOSUPPORT;
132         return -1;
133 }
134
135 #ifdef HAVE_LIBTIRPC
136
137 /*
138  * Bind a socket using an unused privileged source port.
139  *
140  * Returns zero on success, or returns -1 on error.  errno is
141  * set to reflect the nature of the error.
142  */
143 static int nfs_bindresvport(const int sock, const sa_family_t family)
144 {
145         struct sockaddr_in sin = {
146                 .sin_family             = AF_INET,
147                 .sin_addr.s_addr        = htonl(INADDR_ANY),
148         };
149         struct sockaddr_in6 sin6 = {
150                 .sin6_family            = AF_INET6,
151                 .sin6_addr              = IN6ADDR_ANY_INIT,
152         };
153
154         switch (family) {
155         case AF_INET:
156                 return bindresvport_sa(sock, (struct sockaddr *)&sin);
157         case AF_INET6:
158                 return bindresvport_sa(sock, (struct sockaddr *)&sin6);
159         }
160
161         errno = EAFNOSUPPORT;
162         return -1;
163 }
164
165 #else   /* !HAVE_LIBTIRPC */
166
167 /*
168  * Bind a socket using an unused privileged source port.
169  *
170  * Returns zero on success, or returns -1 on error.  errno is
171  * set to reflect the nature of the error.
172  */
173 static int nfs_bindresvport(const int sock, const sa_family_t family)
174 {
175         if (family != AF_INET) {
176                 errno = EAFNOSUPPORT;
177                 return -1;
178         }
179
180         return bindresvport(sock, NULL);
181 }
182
183 #endif  /* !HAVE_LIBTIRPC */
184
185 /*
186  * Perform a non-blocking connect on the socket fd.
187  *
188  * @timeout is modified to contain the time remaining (i.e. time provided
189  * minus time elasped).
190  *
191  * Returns zero on success, or returns -1 on error.  errno is
192  * set to reflect the nature of the error.
193  */
194 static int nfs_connect_nb(const int fd, const struct sockaddr *sap,
195                           const socklen_t salen, struct timeval *timeout)
196 {
197         int flags, ret;
198         fd_set rset;
199
200         flags = fcntl(fd, F_GETFL, 0);
201         if (flags < 0)
202                 return -1;
203
204         ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
205         if (ret < 0)
206                 return -1;
207
208         /*
209          * From here on subsequent sys calls could change errno so
210          * we set ret = -errno to capture it in case we decide to
211          * use it later.
212          */
213         ret = connect(fd, sap, salen);
214         if (ret < 0 && errno != EINPROGRESS) {
215                 ret = -1;
216                 goto done;
217         }
218
219         if (ret == 0)
220                 goto done;
221
222         /* now wait */
223         FD_ZERO(&rset);
224         FD_SET(fd, &rset);
225
226         ret = select(fd + 1, NULL, &rset, NULL, timeout);
227         if (ret <= 0) {
228                 if (ret == 0)
229                         errno = ETIMEDOUT;
230                 ret = -1;
231                 goto done;
232         }
233
234         if (FD_ISSET(fd, &rset)) {
235                 int status;
236                 socklen_t len = (socklen_t)sizeof(ret);
237
238                 status = getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len);
239                 if (status < 0) {
240                         ret = -1;
241                         goto done;
242                 }
243
244                 /* Oops - something wrong with connect */
245                 if (ret != 0) {
246                         errno = ret;
247                         ret = -1;
248                 }
249         }
250
251 done:
252         (void)fcntl(fd, F_SETFL, flags);
253         return ret;
254 }
255
256 /*
257  * Set up an RPC client for communicating via a datagram socket.
258  * A connected UDP socket is used to detect a missing remote
259  * listener as quickly as possible.
260  *
261  * @timeout is initialized upon return
262  *
263  * Returns a pointer to a prepared RPC client if successful; caller
264  * must destroy a non-NULL returned RPC client.  Otherwise NULL, and
265  * rpc_createerr.cf_stat is set to reflect the error.
266  */
267 static CLIENT *nfs_get_udpclient(const struct sockaddr *sap,
268                                  const socklen_t salen,
269                                  const rpcprog_t program,
270                                  const rpcvers_t version,
271                                  struct timeval *timeout,
272                                  const int resvport)
273 {
274         CLIENT *client;
275         int ret, sock;
276 #ifdef HAVE_LIBTIRPC
277         struct sockaddr_storage address;
278         const struct netbuf nbuf = {
279                 .maxlen         = salen,
280                 .len            = salen,
281                 .buf            = &address,
282         };
283
284 #else   /* !HAVE_LIBTIRPC */
285
286         if (sap->sa_family != AF_INET) {
287                 rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
288                 return NULL;
289         }
290 #endif  /* !HAVE_LIBTIRPC */
291
292         sock = socket((int)sap->sa_family, SOCK_DGRAM, IPPROTO_UDP);
293         if (sock == -1) {
294                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
295                 rpc_createerr.cf_error.re_errno = errno;
296                 return NULL;
297         }
298
299         if (resvport)
300                 ret = nfs_bindresvport(sock, sap->sa_family);
301         else
302                 ret = nfs_bind(sock, sap->sa_family);
303         if (ret < 0) {
304                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
305                 rpc_createerr.cf_error.re_errno = errno;
306                 (void)close(sock);
307                 return NULL;
308         }
309
310         if (timeout->tv_sec == -1)
311                 timeout->tv_sec = NFSRPC_TIMEOUT_UDP;
312
313         ret = nfs_connect_nb(sock, sap, salen, timeout);
314         if (ret != 0) {
315                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
316                 rpc_createerr.cf_error.re_errno = errno;
317                 (void)close(sock);
318                 return NULL;
319         }
320
321 #ifdef HAVE_LIBTIRPC
322         memcpy(nbuf.buf, sap, (size_t)salen);
323         client = clnt_dg_create(sock, &nbuf, program, version, 0, 0);
324 #else   /* !HAVE_LIBTIRPC */
325         client = clntudp_create((struct sockaddr_in *)sap, program,
326                                         version, *timeout, &sock);
327 #endif  /* !HAVE_LIBTIRPC */
328         if (client != NULL) {
329                 CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT, (char *)timeout);
330                 CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
331         } else
332                 (void)close(sock);
333
334         return client;
335 }
336
337 /*
338  * Set up and connect an RPC client for communicating via a stream socket.
339  *
340  * @timeout is initialized upon return
341  *
342  * Returns a pointer to a prepared and connected RPC client if
343  * successful; caller must destroy a non-NULL returned RPC client.
344  * Otherwise NULL, and rpc_createerr.cf_stat is set to reflect the
345  * error.
346  */
347 static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap,
348                                  const socklen_t salen,
349                                  const rpcprog_t program,
350                                  const rpcvers_t version,
351                                  struct timeval *timeout,
352                                  const int resvport)
353 {
354         CLIENT *client;
355         int ret, sock;
356 #ifdef HAVE_LIBTIRPC
357         struct sockaddr_storage address;
358         const struct netbuf nbuf = {
359                 .maxlen         = salen,
360                 .len            = salen,
361                 .buf            = &address,
362         };
363
364 #else   /* !HAVE_LIBTIRPC */
365
366         if (sap->sa_family != AF_INET) {
367                 rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
368                 return NULL;
369         }
370 #endif  /* !HAVE_LIBTIRPC */
371
372         sock = socket((int)sap->sa_family, SOCK_STREAM, IPPROTO_TCP);
373         if (sock == -1) {
374                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
375                 rpc_createerr.cf_error.re_errno = errno;
376                 return NULL;
377         }
378
379         if (resvport)
380                 ret = nfs_bindresvport(sock, sap->sa_family);
381         else
382                 ret = nfs_bind(sock, sap->sa_family);
383         if (ret < 0) {
384                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
385                 rpc_createerr.cf_error.re_errno = errno;
386                 (void)close(sock);
387                 return NULL;
388         }
389
390         if (timeout->tv_sec == -1)
391                 timeout->tv_sec = NFSRPC_TIMEOUT_TCP;
392
393         ret = nfs_connect_nb(sock, sap, salen, timeout);
394         if (ret != 0) {
395                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
396                 rpc_createerr.cf_error.re_errno = errno;
397                 (void)close(sock);
398                 return NULL;
399         }
400
401 #ifdef HAVE_LIBTIRPC
402         memcpy(nbuf.buf, sap, (size_t)salen);
403         client = clnt_vc_create(sock, &nbuf, program, version, 0, 0);
404 #else   /* !HAVE_LIBTIRPC */
405         client = clnttcp_create((struct sockaddr_in *)sap,
406                                         program, version, &sock, 0, 0);
407 #endif  /* !HAVE_LIBTIRPC */
408         if (client != NULL)
409                 CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
410         else
411                 (void)close(sock);
412
413         return client;
414 }
415
416 /**
417  * nfs_get_rpcclient - acquire an RPC client
418  * @sap: pointer to socket address of RPC server
419  * @salen: length of socket address
420  * @transport: IPPROTO_ value of transport protocol to use
421  * @program: RPC program number
422  * @version: RPC version number
423  * @timeout: pointer to request timeout (must not be NULL)
424  *
425  * Set up an RPC client for communicating with an RPC program @program
426  * and @version on the server @sap over @transport.  An unprivileged
427  * source port is used.
428  *
429  * Returns a pointer to a prepared RPC client if successful, and
430  * @timeout is initialized; caller must destroy a non-NULL returned RPC
431  * client.  Otherwise returns NULL, and rpc_createerr.cf_stat is set to
432  * reflect the error.
433  */
434 CLIENT *nfs_get_rpcclient(const struct sockaddr *sap,
435                           const socklen_t salen,
436                           const unsigned short transport,
437                           const rpcprog_t program,
438                           const rpcvers_t version,
439                           struct timeval *timeout)
440 {
441         struct sockaddr_in *sin = (struct sockaddr_in *)sap;
442         struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
443
444         switch (sap->sa_family) {
445         case AF_LOCAL:
446                 return nfs_get_localclient(sap, salen, program,
447                                                 version, timeout);
448         case AF_INET:
449                 if (sin->sin_port == 0) {
450                         rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
451                         return NULL;
452                 }
453                 break;
454         case AF_INET6:
455                 if (sin6->sin6_port == 0) {
456                         rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
457                         return NULL;
458                 }
459                 break;
460         default:
461                 rpc_createerr.cf_stat = RPC_UNKNOWNHOST;
462                 return NULL;
463         }
464
465         switch (transport) {
466         case IPPROTO_TCP:
467                 return nfs_get_tcpclient(sap, salen, program, version,
468                                                 timeout, 0);
469         case 0:
470         case IPPROTO_UDP:
471                 return nfs_get_udpclient(sap, salen, program, version,
472                                                 timeout, 0);
473         }
474
475         rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
476         return NULL;
477 }
478
479 /**
480  * nfs_get_priv_rpcclient - acquire an RPC client
481  * @sap: pointer to socket address of RPC server
482  * @salen: length of socket address
483  * @transport: IPPROTO_ value of transport protocol to use
484  * @program: RPC program number
485  * @version: RPC version number
486  * @timeout: pointer to request timeout (must not be NULL)
487  *
488  * Set up an RPC client for communicating with an RPC program @program
489  * and @version on the server @sap over @transport.  A privileged
490  * source port is used.
491  *
492  * Returns a pointer to a prepared RPC client if successful, and
493  * @timeout is initialized; caller must destroy a non-NULL returned RPC
494  * client.  Otherwise returns NULL, and rpc_createerr.cf_stat is set to
495  * reflect the error.
496  */
497 CLIENT *nfs_get_priv_rpcclient(const struct sockaddr *sap,
498                                const socklen_t salen,
499                                const unsigned short transport,
500                                const rpcprog_t program,
501                                const rpcvers_t version,
502                                struct timeval *timeout)
503 {
504         struct sockaddr_in *sin = (struct sockaddr_in *)sap;
505         struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
506
507         switch (sap->sa_family) {
508         case AF_LOCAL:
509                 return nfs_get_localclient(sap, salen, program,
510                                                 version, timeout);
511         case AF_INET:
512                 if (sin->sin_port == 0) {
513                         rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
514                         return NULL;
515                 }
516                 break;
517         case AF_INET6:
518                 if (sin6->sin6_port == 0) {
519                         rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
520                         return NULL;
521                 }
522                 break;
523         default:
524                 rpc_createerr.cf_stat = RPC_UNKNOWNHOST;
525                 return NULL;
526         }
527
528         switch (transport) {
529         case IPPROTO_TCP:
530                 return nfs_get_tcpclient(sap, salen, program, version,
531                                                 timeout, 1);
532         case 0:
533         case IPPROTO_UDP:
534                 return nfs_get_udpclient(sap, salen, program, version,
535                                                 timeout, 1);
536         }
537
538         rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
539         return NULL;
540 }
541
542 /**
543  * nfs_getrpcbyname - convert an RPC program name to a rpcprog_t
544  * @program: default program number to use if names not found in db
545  * @table: pointer to table of 'char *' names to try to find
546  *
547  * Returns program number of first name to be successfully looked
548  * up, or the default program number if all lookups fail.
549  */
550 rpcprog_t nfs_getrpcbyname(const rpcprog_t program, const char *table[])
551 {
552 #ifdef HAVE_GETRPCBYNAME
553         struct rpcent *entry;
554         unsigned int i;
555
556         if (table != NULL)
557                 for (i = 0; table[i] != NULL; i++) {
558                         entry = getrpcbyname(table[i]);
559                         if (entry)
560                                 return (rpcprog_t)entry->r_number;
561                 }
562 #endif  /* HAVE_GETRPCBYNAME */
563
564         return program;
565 }