xref: /openbsd/lib/libc/rpc/svc_udp.c (revision 8d4335cb)
1 /*	$OpenBSD: svc_udp.c,v 1.27 2022/02/14 03:38:59 guenther 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  * svc_udp.c,
36  * Server side for UDP/IP based RPC.  (Does some caching in the hopes of
37  * achieving execute-at-most-once semantics.)
38  */
39 
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <rpc/rpc.h>
44 #include <sys/socket.h>
45 #include <stdint.h>
46 #include <errno.h>
47 #include <unistd.h>
48 
49 
50 #define rpc_buffer(xprt) ((xprt)->xp_p1)
51 #define MAX(a, b)     ((a > b) ? a : b)
52 
53 static bool_t		svcudp_recv(SVCXPRT *, struct rpc_msg *);
54 static enum xprt_stat	svcudp_stat(SVCXPRT *);
55 static bool_t		svcudp_getargs(SVCXPRT *, xdrproc_t, caddr_t);
56 static bool_t		svcudp_reply(SVCXPRT *, struct rpc_msg *);
57 static bool_t		svcudp_freeargs(SVCXPRT *, xdrproc_t, caddr_t);
58 static void		svcudp_destroy(SVCXPRT *);
59 static void		cache_set(SVCXPRT *, u_long);
60 static int		cache_get(SVCXPRT *, struct rpc_msg *, char **,
61 			    u_long *);
62 
63 static const struct xp_ops svcudp_op = {
64 	svcudp_recv,
65 	svcudp_stat,
66 	svcudp_getargs,
67 	svcudp_reply,
68 	svcudp_freeargs,
69 	svcudp_destroy
70 };
71 
72 /*
73  * kept in xprt->xp_p2
74  */
75 struct svcudp_data {
76 	u_int   su_iosz;	/* byte size of send.recv buffer */
77 	u_long	su_xid;		/* transaction id */
78 	XDR	su_xdrs;	/* XDR handle */
79 	char	su_verfbody[MAX_AUTH_BYTES];	/* verifier body */
80 	char * 	su_cache;	/* cached data, NULL if no cache */
81 };
82 #define	su_data(xprt)	((struct svcudp_data *)(xprt->xp_p2))
83 
84 /*
85  * Usage:
86  *	xprt = svcudp_create(sock);
87  *
88  * If sock<0 then a socket is created, else sock is used.
89  * If the socket, sock is not bound to a port then svcudp_create
90  * binds it to an arbitrary port.  In any (successful) case,
91  * xprt->xp_sock is the registered socket number and xprt->xp_port is the
92  * associated port number.
93  * Once *xprt is initialized, it is registered as a transporter;
94  * see (svc.h, xprt_register).
95  * The routines returns NULL if a problem occurred.
96  */
97 SVCXPRT *
svcudp_bufcreate(int sock,u_int sendsz,u_int recvsz)98 svcudp_bufcreate(int sock, u_int sendsz, u_int recvsz)
99 {
100 	bool_t madesock = FALSE;
101 	SVCXPRT *xprt;
102 	struct svcudp_data *su;
103 	struct sockaddr_in addr;
104 	socklen_t len = sizeof(struct sockaddr_in);
105 
106 	if (sock == RPC_ANYSOCK) {
107 		if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
108 			return (NULL);
109 		madesock = TRUE;
110 	}
111 	memset(&addr, 0, sizeof (addr));
112 	addr.sin_len = sizeof(struct sockaddr_in);
113 	addr.sin_family = AF_INET;
114 	if (bindresvport(sock, &addr)) {
115 		addr.sin_port = 0;
116 		(void)bind(sock, (struct sockaddr *)&addr, len);
117 	}
118 	if (getsockname(sock, (struct sockaddr *)&addr, &len) != 0) {
119 		if (madesock)
120 			(void)close(sock);
121 		return (NULL);
122 	}
123 	xprt = malloc(sizeof(SVCXPRT));
124 	if (xprt == NULL) {
125 		if (madesock)
126 			(void)close(sock);
127 		return (NULL);
128 	}
129 	su = malloc(sizeof(*su));
130 	if (su == NULL) {
131 		if (madesock)
132 			(void)close(sock);
133 		free(xprt);
134 		return (NULL);
135 	}
136 	su->su_iosz = ((MAX(sendsz, recvsz) + 3) / 4) * 4;
137 	if ((rpc_buffer(xprt) = malloc(su->su_iosz)) == NULL) {
138 		if (madesock)
139 			(void)close(sock);
140 		free(xprt);
141 		free(su);
142 		return (NULL);
143 	}
144 	xdrmem_create(&(su->su_xdrs), rpc_buffer(xprt), su->su_iosz,
145 	    XDR_DECODE);
146 	su->su_cache = NULL;
147 	xprt->xp_p2 = (caddr_t)su;
148 	xprt->xp_verf.oa_base = su->su_verfbody;
149 	xprt->xp_ops = &svcudp_op;
150 	xprt->xp_port = ntohs(addr.sin_port);
151 	xprt->xp_sock = sock;
152 	if (__xprt_register(xprt) == 0) {
153 		if (madesock)
154 			(void)close(sock);
155 		free(rpc_buffer(xprt));
156 		free(xprt);
157 		free(su);
158 		return (NULL);
159 	}
160 	return (xprt);
161 }
162 DEF_WEAK(svcudp_bufcreate);
163 
164 SVCXPRT *
svcudp_create(int sock)165 svcudp_create(int sock)
166 {
167 
168 	return(svcudp_bufcreate(sock, UDPMSGSIZE, UDPMSGSIZE));
169 }
170 DEF_WEAK(svcudp_create);
171 
172 static enum xprt_stat
svcudp_stat(SVCXPRT * xprt)173 svcudp_stat(SVCXPRT *xprt)
174 {
175 
176 	return (XPRT_IDLE);
177 }
178 
179 static bool_t
svcudp_recv(SVCXPRT * xprt,struct rpc_msg * msg)180 svcudp_recv(SVCXPRT *xprt, struct rpc_msg *msg)
181 {
182 	struct svcudp_data *su = su_data(xprt);
183 	XDR *xdrs = &(su->su_xdrs);
184 	int rlen;
185 	char *reply;
186 	u_long replylen;
187 
188     again:
189 	xprt->xp_addrlen = sizeof(struct sockaddr_in);
190 	rlen = recvfrom(xprt->xp_sock, rpc_buffer(xprt), (int) su->su_iosz,
191 	    0, (struct sockaddr *)&(xprt->xp_raddr), &(xprt->xp_addrlen));
192 	if (rlen == -1 && errno == EINTR)
193 		goto again;
194 	if (rlen == -1 || rlen < 4*sizeof(u_int32_t))
195 		return (FALSE);
196 	xdrs->x_op = XDR_DECODE;
197 	XDR_SETPOS(xdrs, 0);
198 	if (! xdr_callmsg(xdrs, msg))
199 		return (FALSE);
200 	su->su_xid = msg->rm_xid;
201 	if (su->su_cache != NULL) {
202 		if (cache_get(xprt, msg, &reply, &replylen)) {
203 			(void) sendto(xprt->xp_sock, reply, (int) replylen, 0,
204 			    (struct sockaddr *) &xprt->xp_raddr,
205 			    xprt->xp_addrlen);
206 			return (TRUE);
207 		}
208 	}
209 	return (TRUE);
210 }
211 
212 static bool_t
svcudp_reply(SVCXPRT * xprt,struct rpc_msg * msg)213 svcudp_reply(SVCXPRT *xprt, struct rpc_msg *msg)
214 {
215 	struct svcudp_data *su = su_data(xprt);
216 	XDR *xdrs = &(su->su_xdrs);
217 	int slen;
218 	bool_t stat = FALSE;
219 
220 	xdrs->x_op = XDR_ENCODE;
221 	XDR_SETPOS(xdrs, 0);
222 	msg->rm_xid = su->su_xid;
223 	if (xdr_replymsg(xdrs, msg)) {
224 		slen = (int)XDR_GETPOS(xdrs);
225 		if (sendto(xprt->xp_sock, rpc_buffer(xprt), slen, 0,
226 		    (struct sockaddr *)&(xprt->xp_raddr), xprt->xp_addrlen)
227 		    == slen) {
228 			stat = TRUE;
229 			if (su->su_cache && slen >= 0) {
230 				cache_set(xprt, (u_long) slen);
231 			}
232 		}
233 	}
234 	return (stat);
235 }
236 
237 static bool_t
svcudp_getargs(SVCXPRT * xprt,xdrproc_t xdr_args,caddr_t args_ptr)238 svcudp_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr)
239 {
240 
241 	return ((*xdr_args)(&(su_data(xprt)->su_xdrs), args_ptr));
242 }
243 
244 static bool_t
svcudp_freeargs(SVCXPRT * xprt,xdrproc_t xdr_args,caddr_t args_ptr)245 svcudp_freeargs(SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr)
246 {
247 	XDR *xdrs = &(su_data(xprt)->su_xdrs);
248 
249 	xdrs->x_op = XDR_FREE;
250 	return ((*xdr_args)(xdrs, args_ptr));
251 }
252 
253 static void
svcudp_destroy(SVCXPRT * xprt)254 svcudp_destroy(SVCXPRT *xprt)
255 {
256 	struct svcudp_data *su = su_data(xprt);
257 
258 	xprt_unregister(xprt);
259 	if (xprt->xp_sock != -1)
260 		(void)close(xprt->xp_sock);
261 	xprt->xp_sock = -1;
262 	XDR_DESTROY(&(su->su_xdrs));
263 	mem_free(rpc_buffer(xprt), su->su_iosz);
264 	mem_free((caddr_t)su, sizeof(struct svcudp_data));
265 	mem_free((caddr_t)xprt, sizeof(SVCXPRT));
266 }
267 
268 /*
269  * Fifo cache for udp server
270  * Copies pointers to reply buffers into fifo cache
271  * Buffers are sent again if retransmissions are detected.
272  */
273 
274 #define SPARSENESS 4	/* 75% sparse */
275 
276 /*
277  * An entry in the cache
278  */
279 typedef struct cache_node *cache_ptr;
280 struct cache_node {
281 	/*
282 	 * Index into cache is xid, proc, vers, prog and address
283 	 */
284 	u_long cache_xid;
285 	u_long cache_proc;
286 	u_long cache_vers;
287 	u_long cache_prog;
288 	struct sockaddr_in cache_addr;
289 	/*
290 	 * The cached reply and length
291 	 */
292 	char * cache_reply;
293 	u_long cache_replylen;
294 	/*
295  	 * Next node on the list, if there is a collision
296 	 */
297 	cache_ptr cache_next;
298 };
299 
300 /*
301  * The entire cache
302  */
303 struct udp_cache {
304 	u_long uc_size;		/* size of cache */
305 	cache_ptr *uc_entries;	/* hash table of entries in cache */
306 	cache_ptr *uc_fifo;	/* fifo list of entries in cache */
307 	u_long uc_nextvictim;	/* points to next victim in fifo list */
308 	u_long uc_prog;		/* saved program number */
309 	u_long uc_vers;		/* saved version number */
310 	u_long uc_proc;		/* saved procedure number */
311 	struct sockaddr_in uc_addr; /* saved caller's address */
312 };
313 
314 
315 /*
316  * the hashing function
317  */
318 #define CACHE_LOC(transp, xid)	\
319  (xid % (SPARSENESS*((struct udp_cache *) su_data(transp)->su_cache)->uc_size))
320 
321 
322 /*
323  * Enable use of the cache.
324  * Note: there is no disable.
325  */
326 int
svcudp_enablecache(SVCXPRT * transp,u_long size)327 svcudp_enablecache(SVCXPRT *transp, u_long size)
328 {
329 	struct svcudp_data *su = su_data(transp);
330 	struct udp_cache *uc;
331 
332 	if (su->su_cache != NULL)
333 		return(0);
334 	uc = malloc(sizeof(*uc));
335 	if (uc == NULL)
336 		return(0);
337 	uc->uc_size = size;
338 	uc->uc_nextvictim = 0;
339 	if (size > SIZE_MAX / (sizeof(cache_ptr) * SPARSENESS) ||
340 	    (uc->uc_entries = calloc(size, sizeof(cache_ptr) * SPARSENESS)) == NULL) {
341 		free(uc);
342 		return(0);
343 	}
344 	uc->uc_fifo = calloc(sizeof(cache_ptr), size);
345 	if (uc->uc_fifo == NULL) {
346 		free(uc->uc_entries);
347 		free(uc);
348 		return(0);
349 	}
350 	su->su_cache = (char *) uc;
351 	return(1);
352 }
353 
354 
355 /*
356  * Set an entry in the cache
357  */
358 static void
cache_set(SVCXPRT * xprt,u_long replylen)359 cache_set(SVCXPRT *xprt, u_long replylen)
360 {
361 	cache_ptr victim;
362 	cache_ptr *vicp;
363 	struct svcudp_data *su = su_data(xprt);
364 	struct udp_cache *uc = (struct udp_cache *) su->su_cache;
365 	u_int loc;
366 	char *newbuf;
367 
368 	/*
369  	 * Find space for the new entry, either by
370 	 * reusing an old entry, or by mallocing a new one
371 	 */
372 	victim = uc->uc_fifo[uc->uc_nextvictim];
373 	if (victim != NULL) {
374 		loc = CACHE_LOC(xprt, victim->cache_xid);
375 		for (vicp = &uc->uc_entries[loc];
376 		  *vicp != NULL && *vicp != victim;
377 		  vicp = &(*vicp)->cache_next)
378 				;
379 		if (*vicp == NULL) {
380 			return;
381 		}
382 		*vicp = victim->cache_next;	/* remote from cache */
383 		newbuf = victim->cache_reply;
384 	} else {
385 		victim = malloc(sizeof(struct cache_node));
386 		if (victim == NULL) {
387 			return;
388 		}
389 		newbuf = malloc(su->su_iosz);
390 		if (newbuf == NULL) {
391 			free(victim);
392 			return;
393 		}
394 	}
395 
396 	/*
397 	 * Store it away
398 	 */
399 	victim->cache_replylen = replylen;
400 	victim->cache_reply = rpc_buffer(xprt);
401 	rpc_buffer(xprt) = newbuf;
402 	xdrmem_create(&(su->su_xdrs), rpc_buffer(xprt), su->su_iosz, XDR_ENCODE);
403 	victim->cache_xid = su->su_xid;
404 	victim->cache_proc = uc->uc_proc;
405 	victim->cache_vers = uc->uc_vers;
406 	victim->cache_prog = uc->uc_prog;
407 	victim->cache_addr = uc->uc_addr;
408 	loc = CACHE_LOC(xprt, victim->cache_xid);
409 	victim->cache_next = uc->uc_entries[loc];
410 	uc->uc_entries[loc] = victim;
411 	uc->uc_fifo[uc->uc_nextvictim++] = victim;
412 	uc->uc_nextvictim %= uc->uc_size;
413 }
414 
415 /*
416  * Try to get an entry from the cache
417  * return 1 if found, 0 if not found
418  */
419 static int
cache_get(SVCXPRT * xprt,struct rpc_msg * msg,char ** replyp,u_long * replylenp)420 cache_get(SVCXPRT *xprt, struct rpc_msg *msg, char **replyp, u_long *replylenp)
421 {
422 	u_int loc;
423 	cache_ptr ent;
424 	struct svcudp_data *su = su_data(xprt);
425 	struct udp_cache *uc = (struct udp_cache *) su->su_cache;
426 
427 #	define EQADDR(a1, a2)	(memcmp(&a1, &a2, sizeof(a1)) == 0)
428 
429 	loc = CACHE_LOC(xprt, su->su_xid);
430 	for (ent = uc->uc_entries[loc]; ent != NULL; ent = ent->cache_next) {
431 		if (ent->cache_xid == su->su_xid &&
432 		  ent->cache_proc == uc->uc_proc &&
433 		  ent->cache_vers == uc->uc_vers &&
434 		  ent->cache_prog == uc->uc_prog &&
435 		  EQADDR(ent->cache_addr, uc->uc_addr)) {
436 			*replyp = ent->cache_reply;
437 			*replylenp = ent->cache_replylen;
438 			return(1);
439 		}
440 	}
441 	/*
442 	 * Failed to find entry
443 	 * Remember a few things so we can do a set later
444 	 */
445 	uc->uc_proc = msg->rm_call.cb_proc;
446 	uc->uc_vers = msg->rm_call.cb_vers;
447 	uc->uc_prog = msg->rm_call.cb_prog;
448 	uc->uc_addr = xprt->xp_raddr;
449 	return(0);
450 }
451 
452