]> git.decadent.org.uk Git - nfs-utils.git/blob - support/nfs/rpc_socket.c
nfs-utils: Include legacy or TI-RPC headers, not both
[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 /*
136  * Perform a non-blocking connect on the socket fd.
137  *
138  * @timeout is modified to contain the time remaining (i.e. time provided
139  * minus time elasped).
140  *
141  * Returns zero on success, or returns -1 on error.  errno is
142  * set to reflect the nature of the error.
143  */
144 static int nfs_connect_nb(const int fd, const struct sockaddr *sap,
145                           const socklen_t salen, struct timeval *timeout)
146 {
147         int flags, ret;
148         fd_set rset;
149
150         flags = fcntl(fd, F_GETFL, 0);
151         if (flags < 0)
152                 return -1;
153
154         ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
155         if (ret < 0)
156                 return -1;
157
158         /*
159          * From here on subsequent sys calls could change errno so
160          * we set ret = -errno to capture it in case we decide to
161          * use it later.
162          */
163         ret = connect(fd, sap, salen);
164         if (ret < 0 && errno != EINPROGRESS) {
165                 ret = -1;
166                 goto done;
167         }
168
169         if (ret == 0)
170                 goto done;
171
172         /* now wait */
173         FD_ZERO(&rset);
174         FD_SET(fd, &rset);
175
176         ret = select(fd + 1, NULL, &rset, NULL, timeout);
177         if (ret <= 0) {
178                 if (ret == 0)
179                         errno = ETIMEDOUT;
180                 ret = -1;
181                 goto done;
182         }
183
184         if (FD_ISSET(fd, &rset)) {
185                 int status;
186                 socklen_t len = (socklen_t)sizeof(ret);
187
188                 status = getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len);
189                 if (status < 0) {
190                         ret = -1;
191                         goto done;
192                 }
193
194                 /* Oops - something wrong with connect */
195                 if (ret != 0) {
196                         errno = ret;
197                         ret = -1;
198                 }
199         }
200
201 done:
202         (void)fcntl(fd, F_SETFL, flags);
203         return ret;
204 }
205
206 /*
207  * Set up an RPC client for communicating via a datagram socket.
208  * A connected UDP socket is used to detect a missing remote
209  * listener as quickly as possible.
210  *
211  * @timeout is initialized upon return
212  *
213  * Returns a pointer to a prepared RPC client if successful; caller
214  * must destroy a non-NULL returned RPC client.  Otherwise NULL, and
215  * rpc_createerr.cf_stat is set to reflect the error.
216  */
217 static CLIENT *nfs_get_udpclient(const struct sockaddr *sap,
218                                  const socklen_t salen,
219                                  const rpcprog_t program,
220                                  const rpcvers_t version,
221                                  struct timeval *timeout)
222 {
223         CLIENT *client;
224         int ret, sock;
225 #ifdef HAVE_LIBTIRPC
226         struct sockaddr_storage address;
227         const struct netbuf nbuf = {
228                 .maxlen         = salen,
229                 .len            = salen,
230                 .buf            = &address,
231         };
232
233 #else   /* !HAVE_LIBTIRPC */
234
235         if (sap->sa_family != AF_INET) {
236                 rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
237                 return NULL;
238         }
239 #endif  /* !HAVE_LIBTIRPC */
240
241         sock = socket((int)sap->sa_family, SOCK_DGRAM, IPPROTO_UDP);
242         if (sock == -1) {
243                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
244                 rpc_createerr.cf_error.re_errno = errno;
245                 return NULL;
246         }
247
248         ret = nfs_bind(sock, sap->sa_family);
249         if (ret < 0) {
250                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
251                 rpc_createerr.cf_error.re_errno = errno;
252                 (void)close(sock);
253                 return NULL;
254         }
255
256         if (timeout->tv_sec == -1)
257                 timeout->tv_sec = NFSRPC_TIMEOUT_UDP;
258
259         ret = nfs_connect_nb(sock, sap, salen, timeout);
260         if (ret != 0) {
261                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
262                 rpc_createerr.cf_error.re_errno = errno;
263                 (void)close(sock);
264                 return NULL;
265         }
266
267 #ifdef HAVE_LIBTIRPC
268         memcpy(nbuf.buf, sap, (size_t)salen);
269         client = clnt_dg_create(sock, &nbuf, program, version, 0, 0);
270 #else   /* !HAVE_LIBTIRPC */
271         client = clntudp_create((struct sockaddr_in *)sap, program,
272                                         version, *timeout, &sock);
273 #endif  /* !HAVE_LIBTIRPC */
274         if (client != NULL) {
275                 CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT, (char *)timeout);
276                 CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
277         } else
278                 (void)close(sock);
279
280         return client;
281 }
282
283 /*
284  * Set up and connect an RPC client for communicating via a stream socket.
285  *
286  * @timeout is initialized upon return
287  *
288  * Returns a pointer to a prepared and connected RPC client if
289  * successful; caller must destroy a non-NULL returned RPC client.
290  * Otherwise NULL, and rpc_createerr.cf_stat is set to reflect the
291  * error.
292  */
293 static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap,
294                                  const socklen_t salen,
295                                  const rpcprog_t program,
296                                  const rpcvers_t version,
297                                  struct timeval *timeout)
298 {
299         CLIENT *client;
300         int ret, sock;
301 #ifdef HAVE_LIBTIRPC
302         struct sockaddr_storage address;
303         const struct netbuf nbuf = {
304                 .maxlen         = salen,
305                 .len            = salen,
306                 .buf            = &address,
307         };
308
309 #else   /* !HAVE_LIBTIRPC */
310
311         if (sap->sa_family != AF_INET) {
312                 rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
313                 return NULL;
314         }
315 #endif  /* !HAVE_LIBTIRPC */
316
317         sock = socket((int)sap->sa_family, SOCK_STREAM, IPPROTO_TCP);
318         if (sock == -1) {
319                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
320                 rpc_createerr.cf_error.re_errno = errno;
321                 return NULL;
322         }
323
324         ret = nfs_bind(sock, sap->sa_family);
325         if (ret < 0) {
326                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
327                 rpc_createerr.cf_error.re_errno = errno;
328                 (void)close(sock);
329                 return NULL;
330         }
331
332         if (timeout->tv_sec == -1)
333                 timeout->tv_sec = NFSRPC_TIMEOUT_TCP;
334
335         ret = nfs_connect_nb(sock, sap, salen, timeout);
336         if (ret != 0) {
337                 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
338                 rpc_createerr.cf_error.re_errno = errno;
339                 (void)close(sock);
340                 return NULL;
341         }
342
343 #ifdef HAVE_LIBTIRPC
344         memcpy(nbuf.buf, sap, (size_t)salen);
345         client = clnt_vc_create(sock, &nbuf, program, version, 0, 0);
346 #else   /* !HAVE_LIBTIRPC */
347         client = clnttcp_create((struct sockaddr_in *)sap,
348                                         program, version, &sock, 0, 0);
349 #endif  /* !HAVE_LIBTIRPC */
350         if (client != NULL)
351                 CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
352         else
353                 (void)close(sock);
354
355         return client;
356 }
357
358 /**
359  * nfs_get_rpcclient - acquire an RPC client
360  * @sap: pointer to socket address of RPC server
361  * @salen: length of socket address
362  * @transport: IPPROTO_ value of transport protocol to use
363  * @program: RPC program number
364  * @version: RPC version number
365  * @timeout: pointer to request timeout (must not be NULL)
366  *
367  * Set up an RPC client for communicating with an RPC program @program
368  * and @version on the server @sap over @transport.
369  *
370  * Returns a pointer to a prepared RPC client if successful, and
371  * @timeout is initialized; caller must destroy a non-NULL returned RPC
372  * client.  Otherwise returns NULL, and rpc_createerr.cf_stat is set to
373  * reflect the error.
374  */
375 CLIENT *nfs_get_rpcclient(const struct sockaddr *sap,
376                           const socklen_t salen,
377                           const unsigned short transport,
378                           const rpcprog_t program,
379                           const rpcvers_t version,
380                           struct timeval *timeout)
381 {
382         struct sockaddr_in *sin = (struct sockaddr_in *)sap;
383         struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
384
385         switch (sap->sa_family) {
386         case AF_LOCAL:
387                 return nfs_get_localclient(sap, salen, program,
388                                                 version, timeout);
389         case AF_INET:
390                 if (sin->sin_port == 0) {
391                         rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
392                         return NULL;
393                 }
394                 break;
395         case AF_INET6:
396                 if (sin6->sin6_port == 0) {
397                         rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
398                         return NULL;
399                 }
400                 break;
401         default:
402                 rpc_createerr.cf_stat = RPC_UNKNOWNHOST;
403                 return NULL;
404         }
405
406         switch (transport) {
407         case IPPROTO_TCP:
408                 return nfs_get_tcpclient(sap, salen, program, version, timeout);
409         case 0:
410         case IPPROTO_UDP:
411                 return nfs_get_udpclient(sap, salen, program, version, timeout);
412         }
413
414         rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
415         return NULL;
416 }
417
418 /**
419  * nfs_getrpcbyname - convert an RPC program name to a rpcprog_t
420  * @program: default program number to use if names not found in db
421  * @table: pointer to table of 'char *' names to try to find
422  *
423  * Returns program number of first name to be successfully looked
424  * up, or the default program number if all lookups fail.
425  */
426 rpcprog_t nfs_getrpcbyname(const rpcprog_t program, const char *table[])
427 {
428 #ifdef HAVE_GETRPCBYNAME
429         struct rpcent *entry;
430         unsigned int i;
431
432         if (table != NULL)
433                 for (i = 0; table[i] != NULL; i++) {
434                         entry = getrpcbyname(table[i]);
435                         if (entry)
436                                 return (rpcprog_t)entry->r_number;
437                 }
438 #endif  /* HAVE_GETRPCBYNAME */
439
440         return program;
441 }