xref: /openbsd/lib/libc/rpc/clnt_udp.c (revision cb7760d1)
1 /*	$OpenBSD: clnt_udp.c,v 1.26 2010/09/01 14:43:34 millert Exp $ */
2 
3 /*
4  * Copyright (c) 2010, Oracle America, Inc.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  *     * Redistributions of source code must retain the above copyright
11  *       notice, this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above
13  *       copyright notice, this list of conditions and the following
14  *       disclaimer in the documentation and/or other materials
15  *       provided with the distribution.
16  *     * Neither the name of the "Oracle America, Inc." nor the names of its
17  *       contributors may be used to endorse or promote products derived
18  *       from this software without specific prior written permission.
19  *
20  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  *   FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  *   COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  *   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  *   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
27  *   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  *   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29  *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30  *   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 /*
35  * clnt_udp.c, Implements a UDP/IP based, client side RPC.
36  */
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <rpc/rpc.h>
43 #include <sys/socket.h>
44 #include <sys/ioctl.h>
45 #include <netdb.h>
46 #include <errno.h>
47 #include <rpc/pmap_clnt.h>
48 
49 /*
50  * UDP bases client side rpc operations
51  */
52 static enum clnt_stat	clntudp_call(CLIENT *, u_long, xdrproc_t, caddr_t,
53 			    xdrproc_t, caddr_t, struct timeval);
54 static void		clntudp_abort(CLIENT *);
55 static void		clntudp_geterr(CLIENT *, struct rpc_err *);
56 static bool_t		clntudp_freeres(CLIENT *, xdrproc_t, caddr_t);
57 static bool_t           clntudp_control(CLIENT *, u_int, void *);
58 static void		clntudp_destroy(CLIENT *);
59 
60 static struct clnt_ops udp_ops = {
61 	clntudp_call,
62 	clntudp_abort,
63 	clntudp_geterr,
64 	clntudp_freeres,
65 	clntudp_destroy,
66 	clntudp_control
67 };
68 
69 /*
70  * Private data kept per client handle
71  */
72 struct cu_data {
73 	int		   cu_sock;
74 	bool_t		   cu_closeit;
75 	struct sockaddr_in cu_raddr;
76 	int		   cu_rlen;
77 	struct timeval	   cu_wait;
78 	struct timeval     cu_total;
79 	struct rpc_err	   cu_error;
80 	XDR		   cu_outxdrs;
81 	u_int		   cu_xdrpos;
82 	u_int		   cu_sendsz;
83 	char		   *cu_outbuf;
84 	u_int		   cu_recvsz;
85 	char		   cu_inbuf[1];
86 };
87 
88 /*
89  * Create a UDP based client handle.
90  * If *sockp<0, *sockp is set to a newly created UPD socket.
91  * If raddr->sin_port is 0 a binder on the remote machine
92  * is consulted for the correct port number.
93  * NB: It is the clients responsibility to close *sockp.
94  * NB: The rpch->cl_auth is initialized to null authentication.
95  *     Caller may wish to set this something more useful.
96  *
97  * wait is the amount of time used between retransmitting a call if
98  * no response has been heard;  retransmission occurs until the actual
99  * rpc call times out.
100  *
101  * sendsz and recvsz are the maximum allowable packet sizes that can be
102  * sent and received.
103  */
104 CLIENT *
105 clntudp_bufcreate(struct sockaddr_in *raddr, u_long program, u_long version,
106     struct timeval wait, int *sockp, u_int sendsz, u_int recvsz)
107 {
108 	CLIENT *cl;
109 	struct cu_data *cu = NULL;
110 	struct timeval now;
111 	struct rpc_msg call_msg;
112 
113 	cl = (CLIENT *)mem_alloc(sizeof(CLIENT));
114 	if (cl == NULL) {
115 		(void) fprintf(stderr, "clntudp_create: out of memory\n");
116 		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
117 		rpc_createerr.cf_error.re_errno = errno;
118 		goto fooy;
119 	}
120 	sendsz = ((sendsz + 3) / 4) * 4;
121 	recvsz = ((recvsz + 3) / 4) * 4;
122 	cu = (struct cu_data *)mem_alloc(sizeof(*cu) + sendsz + recvsz);
123 	if (cu == NULL) {
124 		(void) fprintf(stderr, "clntudp_create: out of memory\n");
125 		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
126 		rpc_createerr.cf_error.re_errno = errno;
127 		goto fooy;
128 	}
129 	cu->cu_outbuf = &cu->cu_inbuf[recvsz];
130 
131 	(void)gettimeofday(&now, NULL);
132 	if (raddr->sin_port == 0) {
133 		u_short port;
134 		if ((port =
135 		    pmap_getport(raddr, program, version, IPPROTO_UDP)) == 0) {
136 			goto fooy;
137 		}
138 		raddr->sin_port = htons(port);
139 	}
140 	cl->cl_ops = &udp_ops;
141 	cl->cl_private = (caddr_t)cu;
142 	cu->cu_raddr = *raddr;
143 	cu->cu_rlen = sizeof (cu->cu_raddr);
144 	cu->cu_wait = wait;
145 	cu->cu_total.tv_sec = -1;
146 	cu->cu_total.tv_usec = -1;
147 	cu->cu_sendsz = sendsz;
148 	cu->cu_recvsz = recvsz;
149 	call_msg.rm_xid = arc4random();
150 	call_msg.rm_direction = CALL;
151 	call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
152 	call_msg.rm_call.cb_prog = program;
153 	call_msg.rm_call.cb_vers = version;
154 	xdrmem_create(&(cu->cu_outxdrs), cu->cu_outbuf,
155 	    sendsz, XDR_ENCODE);
156 	if (!xdr_callhdr(&(cu->cu_outxdrs), &call_msg)) {
157 		goto fooy;
158 	}
159 	cu->cu_xdrpos = XDR_GETPOS(&(cu->cu_outxdrs));
160 	if (*sockp < 0) {
161 		int dontblock = 1;
162 
163 		*sockp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
164 		if (*sockp < 0) {
165 			rpc_createerr.cf_stat = RPC_SYSTEMERROR;
166 			rpc_createerr.cf_error.re_errno = errno;
167 			goto fooy;
168 		}
169 		/* attempt to bind to priv port */
170 		(void)bindresvport(*sockp, NULL);
171 		/* the sockets rpc controls are non-blocking */
172 		(void)ioctl(*sockp, FIONBIO, (char *) &dontblock);
173 		cu->cu_closeit = TRUE;
174 	} else {
175 		cu->cu_closeit = FALSE;
176 	}
177 	cu->cu_sock = *sockp;
178 	cl->cl_auth = authnone_create();
179 	if (cl->cl_auth == NULL) {
180 		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
181 		rpc_createerr.cf_error.re_errno = errno;
182 		goto fooy;
183 	}
184 	return (cl);
185 fooy:
186 	if (cu)
187 		mem_free((caddr_t)cu, sizeof(*cu) + sendsz + recvsz);
188 	if (cl)
189 		mem_free((caddr_t)cl, sizeof(CLIENT));
190 	return (NULL);
191 }
192 
193 CLIENT *
194 clntudp_create(struct sockaddr_in *raddr, u_long program, u_long version,
195     struct timeval wait, int *sockp)
196 {
197 
198 	return(clntudp_bufcreate(raddr, program, version, wait, sockp,
199 	    UDPMSGSIZE, UDPMSGSIZE));
200 }
201 
202 static enum clnt_stat
203 clntudp_call(CLIENT *cl,	/* client handle */
204     u_long proc,		/* procedure number */
205     xdrproc_t xargs,		/* xdr routine for args */
206     caddr_t argsp,		/* pointer to args */
207     xdrproc_t xresults,		/* xdr routine for results */
208     caddr_t resultsp,		/* pointer to results */
209     struct timeval utimeout)	/* seconds to wait before giving up */
210 {
211 	struct cu_data *cu = (struct cu_data *)cl->cl_private;
212 	XDR *xdrs;
213 	int outlen;
214 	int inlen;
215 	socklen_t fromlen;
216 	struct pollfd pfd[1];
217 	struct sockaddr_in from;
218 	struct rpc_msg reply_msg;
219 	XDR reply_xdrs;
220 	struct timeval time_waited, start, after, tmp1, tmp2;
221 	bool_t ok;
222 	int nrefreshes = 2;	/* number of times to refresh cred */
223 	struct timeval timeout;
224 
225 	if (cu->cu_total.tv_usec == -1)
226 		timeout = utimeout;     /* use supplied timeout */
227 	else
228 		timeout = cu->cu_total; /* use default timeout */
229 
230 	pfd[0].fd = cu->cu_sock;
231 	pfd[0].events = POLLIN;
232 	timerclear(&time_waited);
233 call_again:
234 	xdrs = &(cu->cu_outxdrs);
235 	xdrs->x_op = XDR_ENCODE;
236 	XDR_SETPOS(xdrs, cu->cu_xdrpos);
237 	/*
238 	 * the transaction is the first thing in the out buffer
239 	 */
240 	(*(u_short *)(cu->cu_outbuf))++;
241 	if (!XDR_PUTLONG(xdrs, (long *)&proc) ||
242 	    !AUTH_MARSHALL(cl->cl_auth, xdrs) ||
243 	    !(*xargs)(xdrs, argsp)) {
244 		return (cu->cu_error.re_status = RPC_CANTENCODEARGS);
245 	}
246 	outlen = (int)XDR_GETPOS(xdrs);
247 
248 send_again:
249 	if (sendto(cu->cu_sock, cu->cu_outbuf, outlen, 0,
250 	    (struct sockaddr *)&(cu->cu_raddr), cu->cu_rlen) != outlen) {
251 		cu->cu_error.re_errno = errno;
252 		return (cu->cu_error.re_status = RPC_CANTSEND);
253 	}
254 
255 	/*
256 	 * Hack to provide rpc-based message passing
257 	 */
258 	if (!timerisset(&timeout))
259 		return (cu->cu_error.re_status = RPC_TIMEDOUT);
260 
261 	/*
262 	 * sub-optimal code appears here because we have
263 	 * some clock time to spare while the packets are in flight.
264 	 * (We assume that this is actually only executed once.)
265 	 */
266 	reply_msg.acpted_rply.ar_verf = _null_auth;
267 	reply_msg.acpted_rply.ar_results.where = resultsp;
268 	reply_msg.acpted_rply.ar_results.proc = xresults;
269 
270 	gettimeofday(&start, NULL);
271 	for (;;) {
272 		switch (poll(pfd, 1,
273 		    cu->cu_wait.tv_sec * 1000 + cu->cu_wait.tv_usec / 1000)) {
274 		case 0:
275 			timeradd(&time_waited, &cu->cu_wait, &tmp1);
276 			time_waited = tmp1;
277 			if (timercmp(&time_waited, &timeout, <))
278 				goto send_again;
279 			return (cu->cu_error.re_status = RPC_TIMEDOUT);
280 		case 1:
281 			if (pfd[0].revents & POLLNVAL)
282 				errno = EBADF;
283 			else if (pfd[0].revents & POLLERR)
284 				errno = EIO;
285 			else
286 				break;
287 			/* FALLTHROUGH */
288 		case -1:
289 			if (errno == EINTR) {
290 				gettimeofday(&after, NULL);
291 				timersub(&after, &start, &tmp1);
292 				timeradd(&time_waited, &tmp1, &tmp2);
293 				time_waited = tmp2;
294 				if (timercmp(&time_waited, &timeout, <))
295 					continue;
296 				return (cu->cu_error.re_status = RPC_TIMEDOUT);
297 			}
298 			cu->cu_error.re_errno = errno;
299 			return (cu->cu_error.re_status = RPC_CANTRECV);
300 		}
301 
302 		do {
303 			fromlen = sizeof(struct sockaddr);
304 			inlen = recvfrom(cu->cu_sock, cu->cu_inbuf,
305 			    (int) cu->cu_recvsz, 0,
306 			    (struct sockaddr *)&from, &fromlen);
307 		} while (inlen < 0 && errno == EINTR);
308 		if (inlen < 0) {
309 			if (errno == EWOULDBLOCK)
310 				continue;
311 			cu->cu_error.re_errno = errno;
312 			return (cu->cu_error.re_status = RPC_CANTRECV);
313 		}
314 		if (inlen < sizeof(u_int32_t))
315 			continue;
316 		/* see if reply transaction id matches sent id */
317 		if (((struct rpc_msg *)(cu->cu_inbuf))->rm_xid !=
318 		    ((struct rpc_msg *)(cu->cu_outbuf))->rm_xid)
319 			continue;
320 		/* we now assume we have the proper reply */
321 		break;
322 	}
323 
324 	/*
325 	 * now decode and validate the response
326 	 */
327 	xdrmem_create(&reply_xdrs, cu->cu_inbuf, (u_int)inlen, XDR_DECODE);
328 	ok = xdr_replymsg(&reply_xdrs, &reply_msg);
329 	/* XDR_DESTROY(&reply_xdrs);  save a few cycles on noop destroy */
330 	if (ok) {
331 #if 0
332 		/*
333 		 * XXX Would like to check these, but call_msg is not
334 		 * around.
335 		 */
336 		if (reply_msg.rm_call.cb_prog != call_msg.rm_call.cb_prog ||
337 		    reply_msg.rm_call.cb_vers != call_msg.rm_call.cb_vers ||
338 		    reply_msg.rm_call.cb_proc != call_msg.rm_call.cb_proc) {
339 			goto call_again;	/* XXX spin? */
340 		}
341 #endif
342 
343 		_seterr_reply(&reply_msg, &(cu->cu_error));
344 		if (cu->cu_error.re_status == RPC_SUCCESS) {
345 			if (!AUTH_VALIDATE(cl->cl_auth,
346 			    &reply_msg.acpted_rply.ar_verf)) {
347 				cu->cu_error.re_status = RPC_AUTHERROR;
348 				cu->cu_error.re_why = AUTH_INVALIDRESP;
349 			}
350 			if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) {
351 				xdrs->x_op = XDR_FREE;
352 				(void)xdr_opaque_auth(xdrs,
353 				    &(reply_msg.acpted_rply.ar_verf));
354 			}
355 		} else {
356 			/* maybe our credentials need to be refreshed ... */
357 			if (nrefreshes > 0 && AUTH_REFRESH(cl->cl_auth)) {
358 				nrefreshes--;
359 				goto call_again;
360 			}
361 		}
362 	} else {
363 		/* xdr_replymsg() may have left some things allocated */
364 		int op = reply_xdrs.x_op;
365 		reply_xdrs.x_op = XDR_FREE;
366 		xdr_replymsg(&reply_xdrs, &reply_msg);
367 		reply_xdrs.x_op = op;
368 		cu->cu_error.re_status = RPC_CANTDECODERES;
369 	}
370 
371 	return (cu->cu_error.re_status);
372 }
373 
374 static void
375 clntudp_geterr(CLIENT *cl, struct rpc_err *errp)
376 {
377 	struct cu_data *cu = (struct cu_data *)cl->cl_private;
378 
379 	*errp = cu->cu_error;
380 }
381 
382 
383 static bool_t
384 clntudp_freeres(CLIENT *cl, xdrproc_t xdr_res, caddr_t res_ptr)
385 {
386 	struct cu_data *cu = (struct cu_data *)cl->cl_private;
387 	XDR *xdrs = &(cu->cu_outxdrs);
388 
389 	xdrs->x_op = XDR_FREE;
390 	return ((*xdr_res)(xdrs, res_ptr));
391 }
392 
393 /*ARGSUSED*/
394 static void
395 clntudp_abort(CLIENT *clnt)
396 {
397 }
398 
399 static bool_t
400 clntudp_control(CLIENT *cl, u_int request, void *info)
401 {
402 	struct cu_data *cu = (struct cu_data *)cl->cl_private;
403 
404 	switch (request) {
405 	case CLSET_TIMEOUT:
406 		cu->cu_total = *(struct timeval *)info;
407 		break;
408 	case CLGET_TIMEOUT:
409 		*(struct timeval *)info = cu->cu_total;
410 		break;
411 	case CLSET_RETRY_TIMEOUT:
412 		cu->cu_wait = *(struct timeval *)info;
413 		break;
414 	case CLGET_RETRY_TIMEOUT:
415 		*(struct timeval *)info = cu->cu_wait;
416 		break;
417 	case CLGET_SERVER_ADDR:
418 		*(struct sockaddr_in *)info = cu->cu_raddr;
419 		break;
420 	default:
421 		return (FALSE);
422 	}
423 	return (TRUE);
424 }
425 
426 static void
427 clntudp_destroy(CLIENT *cl)
428 {
429 	struct cu_data *cu = (struct cu_data *)cl->cl_private;
430 
431 	if (cu->cu_closeit) {
432 		(void)close(cu->cu_sock);
433 	}
434 	XDR_DESTROY(&(cu->cu_outxdrs));
435 	mem_free((caddr_t)cu, (sizeof(*cu) + cu->cu_sendsz + cu->cu_recvsz));
436 	mem_free((caddr_t)cl, sizeof(CLIENT));
437 }
438