xref: /openbsd/lib/libcrypto/bio/bss_dgram.c (revision acf64401)
1 /* $OpenBSD: bss_dgram.c,v 1.45 2023/07/05 21:23:37 beck Exp $ */
2 /*
3  * DTLS implementation written by Nagendra Modadugu
4  * (nagendra@cs.stanford.edu) for the OpenSSL project 2005.
5  */
6 /* ====================================================================
7  * Copyright (c) 1999-2005 The OpenSSL Project.  All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in
18  *    the documentation and/or other materials provided with the
19  *    distribution.
20  *
21  * 3. All advertising materials mentioning features or use of this
22  *    software must display the following acknowledgment:
23  *    "This product includes software developed by the OpenSSL Project
24  *    for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
25  *
26  * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
27  *    endorse or promote products derived from this software without
28  *    prior written permission. For written permission, please contact
29  *    openssl-core@OpenSSL.org.
30  *
31  * 5. Products derived from this software may not be called "OpenSSL"
32  *    nor may "OpenSSL" appear in their names without prior written
33  *    permission of the OpenSSL Project.
34  *
35  * 6. Redistributions of any form whatsoever must retain the following
36  *    acknowledgment:
37  *    "This product includes software developed by the OpenSSL Project
38  *    for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
39  *
40  * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
41  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
42  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
43  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
44  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
45  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
46  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
47  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
48  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
49  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
50  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
51  * OF THE POSSIBILITY OF SUCH DAMAGE.
52  * ====================================================================
53  *
54  * This product includes cryptographic software written by Eric Young
55  * (eay@cryptsoft.com).  This product includes software written by Tim
56  * Hudson (tjh@cryptsoft.com).
57  *
58  */
59 
60 #include <sys/socket.h>
61 #include <sys/time.h>
62 
63 #include <netinet/in.h>
64 
65 #include <errno.h>
66 #include <netdb.h>
67 #include <stdio.h>
68 #include <string.h>
69 #include <unistd.h>
70 
71 #include <openssl/opensslconf.h>
72 
73 #include <openssl/bio.h>
74 
75 #include "bio_local.h"
76 
77 #ifndef OPENSSL_NO_DGRAM
78 
79 
80 static int dgram_write(BIO *h, const char *buf, int num);
81 static int dgram_read(BIO *h, char *buf, int size);
82 static int dgram_puts(BIO *h, const char *str);
83 static long dgram_ctrl(BIO *h, int cmd, long arg1, void *arg2);
84 static int dgram_new(BIO *h);
85 static int dgram_free(BIO *data);
86 static int dgram_clear(BIO *bio);
87 
88 
89 static int BIO_dgram_should_retry(int s);
90 
91 static const BIO_METHOD methods_dgramp = {
92 	.type = BIO_TYPE_DGRAM,
93 	.name = "datagram socket",
94 	.bwrite = dgram_write,
95 	.bread = dgram_read,
96 	.bputs = dgram_puts,
97 	.ctrl = dgram_ctrl,
98 	.create = dgram_new,
99 	.destroy = dgram_free
100 };
101 
102 
103 typedef struct bio_dgram_data_st {
104 	union {
105 		struct sockaddr sa;
106 		struct sockaddr_in sa_in;
107 		struct sockaddr_in6 sa_in6;
108 	} peer;
109 	unsigned int connected;
110 	unsigned int _errno;
111 	unsigned int mtu;
112 	struct timeval next_timeout;
113 	struct timeval socket_timeout;
114 } bio_dgram_data;
115 
116 
117 const BIO_METHOD *
BIO_s_datagram(void)118 BIO_s_datagram(void)
119 {
120 	return (&methods_dgramp);
121 }
122 LCRYPTO_ALIAS(BIO_s_datagram);
123 
124 BIO *
BIO_new_dgram(int fd,int close_flag)125 BIO_new_dgram(int fd, int close_flag)
126 {
127 	BIO *ret;
128 
129 	ret = BIO_new(BIO_s_datagram());
130 	if (ret == NULL)
131 		return (NULL);
132 	BIO_set_fd(ret, fd, close_flag);
133 	return (ret);
134 }
135 LCRYPTO_ALIAS(BIO_new_dgram);
136 
137 static int
dgram_new(BIO * bi)138 dgram_new(BIO *bi)
139 {
140 	bio_dgram_data *data = NULL;
141 
142 	bi->init = 0;
143 	bi->num = 0;
144 	data = calloc(1, sizeof(bio_dgram_data));
145 	if (data == NULL)
146 		return 0;
147 	bi->ptr = data;
148 
149 	bi->flags = 0;
150 	return (1);
151 }
152 
153 static int
dgram_free(BIO * a)154 dgram_free(BIO *a)
155 {
156 	bio_dgram_data *data;
157 
158 	if (a == NULL)
159 		return (0);
160 	if (!dgram_clear(a))
161 		return 0;
162 
163 	data = (bio_dgram_data *)a->ptr;
164 	free(data);
165 
166 	return (1);
167 }
168 
169 static int
dgram_clear(BIO * a)170 dgram_clear(BIO *a)
171 {
172 	if (a == NULL)
173 		return (0);
174 	if (a->shutdown) {
175 		if (a->init) {
176 			shutdown(a->num, SHUT_RDWR);
177 			close(a->num);
178 		}
179 		a->init = 0;
180 		a->flags = 0;
181 	}
182 	return (1);
183 }
184 
185 static void
dgram_adjust_rcv_timeout(BIO * b)186 dgram_adjust_rcv_timeout(BIO *b)
187 {
188 #if defined(SO_RCVTIMEO)
189 	bio_dgram_data *data = (bio_dgram_data *)b->ptr;
190 
191 	/* Is a timer active? */
192 	if (data->next_timeout.tv_sec > 0 || data->next_timeout.tv_usec > 0) {
193 		struct timeval timenow, timeleft;
194 
195 		/* Read current socket timeout */
196 		socklen_t sz = sizeof(data->socket_timeout);
197 		if (getsockopt(b->num, SOL_SOCKET, SO_RCVTIMEO,
198 		    &(data->socket_timeout), &sz) < 0) {
199 			perror("getsockopt");
200 		}
201 
202 		/* Get current time */
203 		gettimeofday(&timenow, NULL);
204 
205 		/* Calculate time left until timer expires */
206 		memcpy(&timeleft, &(data->next_timeout), sizeof(struct timeval));
207 		timeleft.tv_sec -= timenow.tv_sec;
208 		timeleft.tv_usec -= timenow.tv_usec;
209 		if (timeleft.tv_usec < 0) {
210 			timeleft.tv_sec--;
211 			timeleft.tv_usec += 1000000;
212 		}
213 
214 		if (timeleft.tv_sec < 0) {
215 			timeleft.tv_sec = 0;
216 			timeleft.tv_usec = 1;
217 		}
218 
219 		/* Adjust socket timeout if next handshake message timer
220 		 * will expire earlier.
221 		 */
222 		if ((data->socket_timeout.tv_sec == 0 &&
223 		    data->socket_timeout.tv_usec == 0) ||
224 		    (data->socket_timeout.tv_sec > timeleft.tv_sec) ||
225 		    (data->socket_timeout.tv_sec == timeleft.tv_sec &&
226 		    data->socket_timeout.tv_usec >= timeleft.tv_usec)) {
227 			if (setsockopt(b->num, SOL_SOCKET, SO_RCVTIMEO,
228 			    &timeleft, sizeof(struct timeval)) < 0) {
229 				perror("setsockopt");
230 			}
231 		}
232 	}
233 #endif
234 }
235 
236 static void
dgram_reset_rcv_timeout(BIO * b)237 dgram_reset_rcv_timeout(BIO *b)
238 {
239 #if defined(SO_RCVTIMEO)
240 	bio_dgram_data *data = (bio_dgram_data *)b->ptr;
241 
242 	/* Is a timer active? */
243 	if (data->next_timeout.tv_sec > 0 || data->next_timeout.tv_usec > 0) {
244 		if (setsockopt(b->num, SOL_SOCKET, SO_RCVTIMEO,
245 		    &(data->socket_timeout), sizeof(struct timeval)) < 0) {
246 			perror("setsockopt");
247 		}
248 	}
249 #endif
250 }
251 
252 static int
dgram_read(BIO * b,char * out,int outl)253 dgram_read(BIO *b, char *out, int outl)
254 {
255 	int ret = 0;
256 	bio_dgram_data *data = (bio_dgram_data *)b->ptr;
257 
258 	struct	{
259 		socklen_t len;
260 		union	{
261 			struct sockaddr sa;
262 			struct sockaddr_in sa_in;
263 			struct sockaddr_in6 sa_in6;
264 		} peer;
265 	} sa;
266 
267 	sa.len = sizeof(sa.peer);
268 
269 	if (out != NULL) {
270 		errno = 0;
271 		memset(&sa.peer, 0, sizeof(sa.peer));
272 		dgram_adjust_rcv_timeout(b);
273 		ret = recvfrom(b->num, out, outl, 0, &sa.peer.sa, &sa.len);
274 
275 		if (! data->connected  && ret >= 0)
276 			BIO_ctrl(b, BIO_CTRL_DGRAM_SET_PEER, 0, &sa.peer);
277 
278 		BIO_clear_retry_flags(b);
279 		if (ret < 0) {
280 			if (BIO_dgram_should_retry(ret)) {
281 				BIO_set_retry_read(b);
282 				data->_errno = errno;
283 			}
284 		}
285 
286 		dgram_reset_rcv_timeout(b);
287 	}
288 	return (ret);
289 }
290 
291 static int
dgram_write(BIO * b,const char * in,int inl)292 dgram_write(BIO *b, const char *in, int inl)
293 {
294 	int ret;
295 	bio_dgram_data *data = (bio_dgram_data *)b->ptr;
296 	errno = 0;
297 
298 	if (data->connected)
299 		ret = write(b->num, in, inl);
300 	else {
301 		int peerlen = sizeof(data->peer);
302 
303 		if (data->peer.sa.sa_family == AF_INET)
304 			peerlen = sizeof(data->peer.sa_in);
305 		else if (data->peer.sa.sa_family == AF_INET6)
306 			peerlen = sizeof(data->peer.sa_in6);
307 		ret = sendto(b->num, in, inl, 0, &data->peer.sa, peerlen);
308 	}
309 
310 	BIO_clear_retry_flags(b);
311 	if (ret <= 0) {
312 		if (BIO_dgram_should_retry(ret)) {
313 			BIO_set_retry_write(b);
314 
315 			data->_errno = errno;
316 			/*
317 			 * higher layers are responsible for querying MTU,
318 			 * if necessary
319 			 */
320 		}
321 	}
322 	return (ret);
323 }
324 
325 static long
dgram_ctrl(BIO * b,int cmd,long num,void * ptr)326 dgram_ctrl(BIO *b, int cmd, long num, void *ptr)
327 {
328 	long ret = 1;
329 	int *ip;
330 	struct sockaddr *to = NULL;
331 	bio_dgram_data *data = NULL;
332 #if (defined(IP_MTU_DISCOVER) || defined(IP_MTU))
333 	int sockopt_val = 0;
334 	socklen_t sockopt_len;	/* assume that system supporting IP_MTU is
335 				 * modern enough to define socklen_t */
336 	socklen_t addr_len;
337 	union	{
338 		struct sockaddr	sa;
339 		struct sockaddr_in s4;
340 		struct sockaddr_in6 s6;
341 	} addr;
342 #endif
343 
344 	data = (bio_dgram_data *)b->ptr;
345 
346 	switch (cmd) {
347 	case BIO_CTRL_RESET:
348 		num = 0;
349 	case BIO_C_FILE_SEEK:
350 		ret = 0;
351 		break;
352 	case BIO_C_FILE_TELL:
353 	case BIO_CTRL_INFO:
354 		ret = 0;
355 		break;
356 	case BIO_C_SET_FD:
357 		dgram_clear(b);
358 		b->num= *((int *)ptr);
359 		b->shutdown = (int)num;
360 		b->init = 1;
361 		break;
362 	case BIO_C_GET_FD:
363 		if (b->init) {
364 			ip = (int *)ptr;
365 			if (ip != NULL)
366 				*ip = b->num;
367 			ret = b->num;
368 		} else
369 			ret = -1;
370 		break;
371 	case BIO_CTRL_GET_CLOSE:
372 		ret = b->shutdown;
373 		break;
374 	case BIO_CTRL_SET_CLOSE:
375 		b->shutdown = (int)num;
376 		break;
377 	case BIO_CTRL_PENDING:
378 	case BIO_CTRL_WPENDING:
379 		ret = 0;
380 		break;
381 	case BIO_CTRL_DUP:
382 	case BIO_CTRL_FLUSH:
383 		ret = 1;
384 		break;
385 	case BIO_CTRL_DGRAM_CONNECT:
386 		to = (struct sockaddr *)ptr;
387 		switch (to->sa_family) {
388 		case AF_INET:
389 			memcpy(&data->peer, to, sizeof(data->peer.sa_in));
390 			break;
391 		case AF_INET6:
392 			memcpy(&data->peer, to, sizeof(data->peer.sa_in6));
393 			break;
394 		default:
395 			memcpy(&data->peer, to, sizeof(data->peer.sa));
396 			break;
397 		}
398 		break;
399 		/* (Linux)kernel sets DF bit on outgoing IP packets */
400 	case BIO_CTRL_DGRAM_MTU_DISCOVER:
401 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
402 		addr_len = (socklen_t)sizeof(addr);
403 		memset((void *)&addr, 0, sizeof(addr));
404 		if (getsockname(b->num, &addr.sa, &addr_len) < 0) {
405 			ret = 0;
406 			break;
407 		}
408 		switch (addr.sa.sa_family) {
409 		case AF_INET:
410 			sockopt_val = IP_PMTUDISC_DO;
411 			ret = setsockopt(b->num, IPPROTO_IP, IP_MTU_DISCOVER,
412 			    &sockopt_val, sizeof(sockopt_val));
413 			if (ret < 0)
414 				perror("setsockopt");
415 			break;
416 #if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO)
417 		case AF_INET6:
418 			sockopt_val = IPV6_PMTUDISC_DO;
419 			ret = setsockopt(b->num, IPPROTO_IPV6,
420 			    IPV6_MTU_DISCOVER, &sockopt_val,
421 			    sizeof(sockopt_val));
422 			if (ret < 0)
423 				perror("setsockopt");
424 			break;
425 #endif
426 		default:
427 			ret = -1;
428 			break;
429 		}
430 #else
431 		ret = -1;
432 #endif
433 		break;
434 	case BIO_CTRL_DGRAM_QUERY_MTU:
435 #if defined(IP_MTU)
436 		addr_len = (socklen_t)sizeof(addr);
437 		memset((void *)&addr, 0, sizeof(addr));
438 		if (getsockname(b->num, &addr.sa, &addr_len) < 0) {
439 			ret = 0;
440 			break;
441 		}
442 		sockopt_len = sizeof(sockopt_val);
443 		switch (addr.sa.sa_family) {
444 		case AF_INET:
445 			ret = getsockopt(b->num, IPPROTO_IP, IP_MTU,
446 			    &sockopt_val, &sockopt_len);
447 			if (ret < 0 || sockopt_val < 0) {
448 				ret = 0;
449 			} else {
450 				/* we assume that the transport protocol is UDP and no
451 				 * IP options are used.
452 				 */
453 				data->mtu = sockopt_val - 8 - 20;
454 				ret = data->mtu;
455 			}
456 			break;
457 #if defined(IPV6_MTU)
458 		case AF_INET6:
459 			ret = getsockopt(b->num, IPPROTO_IPV6, IPV6_MTU,
460 			    &sockopt_val, &sockopt_len);
461 			if (ret < 0 || sockopt_val < 0) {
462 				ret = 0;
463 			} else {
464 				/* we assume that the transport protocol is UDP and no
465 				 * IPV6 options are used.
466 				 */
467 				data->mtu = sockopt_val - 8 - 40;
468 				ret = data->mtu;
469 			}
470 			break;
471 #endif
472 default:
473 			ret = 0;
474 			break;
475 		}
476 #else
477 		ret = 0;
478 #endif
479 		break;
480 	case BIO_CTRL_DGRAM_GET_FALLBACK_MTU:
481 		switch (data->peer.sa.sa_family) {
482 		case AF_INET:
483 			ret = 576 - 20 - 8;
484 			break;
485 		case AF_INET6:
486 #ifdef IN6_IS_ADDR_V4MAPPED
487 			if (IN6_IS_ADDR_V4MAPPED(&data->peer.sa_in6.sin6_addr))
488 				ret = 576 - 20 - 8;
489 			else
490 #endif
491 				ret = 1280 - 40 - 8;
492 			break;
493 		default:
494 			ret = 576 - 20 - 8;
495 			break;
496 		}
497 		break;
498 	case BIO_CTRL_DGRAM_GET_MTU:
499 		return data->mtu;
500 		break;
501 	case BIO_CTRL_DGRAM_SET_MTU:
502 		data->mtu = num;
503 		ret = num;
504 		break;
505 	case BIO_CTRL_DGRAM_SET_CONNECTED:
506 		to = (struct sockaddr *)ptr;
507 
508 		if (to != NULL) {
509 			data->connected = 1;
510 			switch (to->sa_family) {
511 			case AF_INET:
512 				memcpy(&data->peer, to, sizeof(data->peer.sa_in));
513 				break;
514 			case AF_INET6:
515 				memcpy(&data->peer, to, sizeof(data->peer.sa_in6));
516 				break;
517 			default:
518 				memcpy(&data->peer, to, sizeof(data->peer.sa));
519 				break;
520 			}
521 		} else {
522 			data->connected = 0;
523 			memset(&(data->peer), 0, sizeof(data->peer));
524 		}
525 		break;
526 	case BIO_CTRL_DGRAM_GET_PEER:
527 		switch (data->peer.sa.sa_family) {
528 		case AF_INET:
529 			ret = sizeof(data->peer.sa_in);
530 			break;
531 		case AF_INET6:
532 			ret = sizeof(data->peer.sa_in6);
533 			break;
534 		default:
535 			ret = sizeof(data->peer.sa);
536 			break;
537 		}
538 		if (num == 0 || num > ret)
539 			num = ret;
540 		memcpy(ptr, &data->peer, (ret = num));
541 		break;
542 	case BIO_CTRL_DGRAM_SET_PEER:
543 		to = (struct sockaddr *) ptr;
544 		switch (to->sa_family) {
545 		case AF_INET:
546 			memcpy(&data->peer, to, sizeof(data->peer.sa_in));
547 			break;
548 		case AF_INET6:
549 			memcpy(&data->peer, to, sizeof(data->peer.sa_in6));
550 			break;
551 		default:
552 			memcpy(&data->peer, to, sizeof(data->peer.sa));
553 			break;
554 		}
555 		break;
556 	case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT:
557 		memcpy(&(data->next_timeout), ptr, sizeof(struct timeval));
558 		break;
559 #if defined(SO_RCVTIMEO)
560 	case BIO_CTRL_DGRAM_SET_RECV_TIMEOUT:
561 		if (setsockopt(b->num, SOL_SOCKET, SO_RCVTIMEO, ptr,
562 		    sizeof(struct timeval)) < 0) {
563 			perror("setsockopt");
564 			ret = -1;
565 		}
566 		break;
567 	case BIO_CTRL_DGRAM_GET_RECV_TIMEOUT:
568 		{
569 			socklen_t sz = sizeof(struct timeval);
570 			if (getsockopt(b->num, SOL_SOCKET, SO_RCVTIMEO,
571 			    ptr, &sz) < 0) {
572 				perror("getsockopt");
573 				ret = -1;
574 			} else
575 				ret = sz;
576 		}
577 		break;
578 #endif
579 #if defined(SO_SNDTIMEO)
580 	case BIO_CTRL_DGRAM_SET_SEND_TIMEOUT:
581 		if (setsockopt(b->num, SOL_SOCKET, SO_SNDTIMEO, ptr,
582 		    sizeof(struct timeval)) < 0) {
583 			perror("setsockopt");
584 			ret = -1;
585 		}
586 		break;
587 	case BIO_CTRL_DGRAM_GET_SEND_TIMEOUT:
588 		{
589 			socklen_t sz = sizeof(struct timeval);
590 			if (getsockopt(b->num, SOL_SOCKET, SO_SNDTIMEO,
591 			    ptr, &sz) < 0) {
592 				perror("getsockopt");
593 				ret = -1;
594 			} else
595 				ret = sz;
596 		}
597 		break;
598 #endif
599 	case BIO_CTRL_DGRAM_GET_SEND_TIMER_EXP:
600 		/* fall-through */
601 	case BIO_CTRL_DGRAM_GET_RECV_TIMER_EXP:
602 		if (data->_errno == EAGAIN) {
603 			ret = 1;
604 			data->_errno = 0;
605 		} else
606 			ret = 0;
607 		break;
608 #ifdef EMSGSIZE
609 	case BIO_CTRL_DGRAM_MTU_EXCEEDED:
610 		if (data->_errno == EMSGSIZE) {
611 			ret = 1;
612 			data->_errno = 0;
613 		} else
614 			ret = 0;
615 		break;
616 #endif
617 	default:
618 		ret = 0;
619 		break;
620 	}
621 	return (ret);
622 }
623 
624 static int
dgram_puts(BIO * bp,const char * str)625 dgram_puts(BIO *bp, const char *str)
626 {
627 	int n, ret;
628 
629 	n = strlen(str);
630 	ret = dgram_write(bp, str, n);
631 	return (ret);
632 }
633 
634 
635 static int
BIO_dgram_should_retry(int i)636 BIO_dgram_should_retry(int i)
637 {
638 	int err;
639 
640 	if ((i == 0) || (i == -1)) {
641 		err = errno;
642 		return (BIO_dgram_non_fatal_error(err));
643 	}
644 	return (0);
645 }
646 
647 int
BIO_dgram_non_fatal_error(int err)648 BIO_dgram_non_fatal_error(int err)
649 {
650 	switch (err) {
651 	case EINTR:
652 	case EAGAIN:
653 	case EINPROGRESS:
654 	case EALREADY:
655 		return (1);
656 	default:
657 		break;
658 	}
659 	return (0);
660 }
661 LCRYPTO_ALIAS(BIO_dgram_non_fatal_error);
662 
663 #endif
664