1/* $OpenBSD: transport.c,v 1.39 2021/01/28 01:18:44 mortimer Exp $	 */
2/* $EOM: transport.c,v 1.43 2000/10/10 12:36:39 provos Exp $	 */
3
4/*
5 * Copyright (c) 1998, 1999 Niklas Hallqvist.  All rights reserved.
6 * Copyright (c) 2001, 2004 H�kan Olsson.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/*
30 * This code was written under funding by Ericsson Radio Systems.
31 */
32
33#include <sys/queue.h>
34#include <netdb.h>
35#include <string.h>
36
37#include "conf.h"
38#include "exchange.h"
39#include "log.h"
40#include "message.h"
41#include "sa.h"
42#include "timer.h"
43#include "transport.h"
44#include "virtual.h"
45
46/* If no retransmit limit is given, use this as a default.  */
47#define RETRANSMIT_DEFAULT 10
48
49LIST_HEAD(transport_method_list, transport_vtbl) transport_method_list;
50
51struct transport_list transport_list;
52
53/* Call the reinit function of the various transports.  */
54void
55transport_reinit(void)
56{
57	struct transport_vtbl *method;
58
59	for (method = LIST_FIRST(&transport_method_list); method;
60	    method = LIST_NEXT(method, link))
61		if (method->reinit)
62			method->reinit();
63}
64
65/* Initialize the transport maintenance module.  */
66void
67transport_init(void)
68{
69	LIST_INIT(&transport_list);
70	LIST_INIT(&transport_method_list);
71}
72
73/* Register another transport T.  */
74void
75transport_setup(struct transport *t, int toplevel)
76{
77	if (toplevel) {
78		/* Only the toplevel (virtual) transport has sendqueues.  */
79		LOG_DBG((LOG_TRANSPORT, 70,
80		    "transport_setup: virtual transport %p", t));
81		TAILQ_INIT(&t->sendq);
82		TAILQ_INIT(&t->prio_sendq);
83		t->refcnt = 0;
84	} else {
85		/* udp and udp_encap trp goes into the transport list.  */
86		LOG_DBG((LOG_TRANSPORT, 70,
87		    "transport_setup: added %p to transport list", t));
88		LIST_INSERT_HEAD(&transport_list, t, link);
89		t->refcnt = 1;
90	}
91	t->flags = 0;
92}
93
94/* Add a referer to transport T.  */
95void
96transport_reference(struct transport *t)
97{
98	t->refcnt++;
99	LOG_DBG((LOG_TRANSPORT, 95,
100	    "transport_reference: transport %p now has %d references", t,
101	    t->refcnt));
102}
103
104/*
105 * Remove a referer from transport T, removing all of T when no referers left.
106 */
107void
108transport_release(struct transport *t)
109{
110	LOG_DBG((LOG_TRANSPORT, 95,
111	    "transport_release: transport %p had %d references", t,
112	    t->refcnt));
113	if (--t->refcnt)
114		return;
115
116	LOG_DBG((LOG_TRANSPORT, 70, "transport_release: freeing %p", t));
117	t->vtbl->remove(t);
118}
119
120void
121transport_report(void)
122{
123	struct virtual_transport *v;
124	struct transport *t;
125	struct message *msg;
126
127	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) {
128		LOG_DBG((LOG_REPORT, 0,
129		    "transport_report: transport %p flags %x refcnt %d", t,
130		    t->flags, t->refcnt));
131
132		/* XXX Report sth on the virtual transport?  */
133		t->vtbl->report(t);
134
135		/*
136		 * This is the reason message_dump_raw lives outside
137		 * message.c.
138		 */
139		v = (struct virtual_transport *)t->virtual;
140		if ((v->encap_is_active && v->encap == t) ||
141		    (!v->encap_is_active && v->main == t)) {
142			for (msg = TAILQ_FIRST(&t->virtual->prio_sendq); msg;
143			    msg = TAILQ_NEXT(msg, link))
144				message_dump_raw("udp_report(prio)", msg,
145				    LOG_REPORT);
146
147			for (msg = TAILQ_FIRST(&t->virtual->sendq); msg;
148			    msg = TAILQ_NEXT(msg, link))
149				message_dump_raw("udp_report", msg,
150				    LOG_REPORT);
151		}
152	}
153}
154
155int
156transport_prio_sendqs_empty(void)
157{
158	struct transport *t;
159
160	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link))
161		if (TAILQ_FIRST(&t->virtual->prio_sendq))
162			return 0;
163	return 1;
164}
165
166/* Register another transport method T.  */
167void
168transport_method_add(struct transport_vtbl *t)
169{
170	LIST_INSERT_HEAD(&transport_method_list, t, link);
171}
172
173/*
174 * Build up a file descriptor set FDS with all transport descriptors we want
175 * to read from.  Return the number of file descriptors select(2) needs to
176 * check in order to cover the ones we setup in here.
177 */
178int
179transport_fd_set(fd_set * fds)
180{
181	struct transport *t;
182	int	n;
183	int	max = -1;
184
185	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link))
186		if (t->virtual->flags & TRANSPORT_LISTEN) {
187			n = t->vtbl->fd_set(t, fds, 1);
188			if (n > max)
189				max = n;
190
191			LOG_DBG((LOG_TRANSPORT, 95, "transport_fd_set: "
192			    "transport %p (virtual %p) fd %d", t,
193			    t->virtual, n));
194		}
195	return max + 1;
196}
197
198/*
199 * Build up a file descriptor set FDS with all the descriptors belonging to
200 * transport where messages are queued for transmittal.  Return the number
201 * of file descriptors select(2) needs to check in order to cover the ones
202 * we setup in here.
203 */
204int
205transport_pending_wfd_set(fd_set * fds)
206{
207	struct transport *t;
208	int	n;
209	int	max = -1;
210
211	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) {
212		if (TAILQ_FIRST(&t->virtual->sendq) ||
213		    TAILQ_FIRST(&t->virtual->prio_sendq)) {
214			n = t->vtbl->fd_set(t, fds, 1);
215			LOG_DBG((LOG_TRANSPORT, 95,
216			    "transport_pending_wfd_set: "
217			    "transport %p (virtual %p) fd %d pending", t,
218			    t->virtual, n));
219			if (n > max)
220				max = n;
221		}
222	}
223	return max + 1;
224}
225
226/*
227 * For each transport with a file descriptor in FDS, try to get an
228 * incoming message and start processing it.
229 */
230void
231transport_handle_messages(fd_set *fds)
232{
233	struct transport *t;
234
235	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) {
236		if ((t->flags & TRANSPORT_LISTEN) &&
237		    (*t->vtbl->fd_isset)(t, fds)) {
238			(*t->virtual->vtbl->handle_message)(t);
239			(*t->vtbl->fd_set)(t, fds, 0);
240		}
241	}
242}
243
244/*
245 * Send the first queued message on the transports found whose file
246 * descriptor is in FDS and has messages queued.  Remove the fd bit from
247 * FDS as soon as one message has been sent on it so other transports
248 * sharing the socket won't get service without an intervening select
249 * call.  Perhaps a fairness strategy should be implemented between
250 * such transports.  Now early transports in the list will potentially
251 * be favoured to later ones sharing the file descriptor.
252 */
253void
254transport_send_messages(fd_set * fds)
255{
256	struct transport *t, *next;
257	struct message *msg;
258	struct exchange *exchange;
259	struct sockaddr *dst;
260	struct timespec expiration;
261	int             expiry, ok_to_drop_message;
262	char peer[NI_MAXHOST], peersv[NI_MAXSERV];
263
264	/*
265	 * Reference all transports first so noone will disappear while in
266	 * use.
267	 */
268	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link))
269		transport_reference(t->virtual);
270
271	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) {
272		if ((TAILQ_FIRST(&t->virtual->sendq) ||
273		    TAILQ_FIRST(&t->virtual->prio_sendq)) &&
274		    t->vtbl->fd_isset(t, fds)) {
275			/* Remove fd bit.  */
276			t->vtbl->fd_set(t, fds, 0);
277
278			/* Prefer a message from the prioritized sendq.  */
279			if (TAILQ_FIRST(&t->virtual->prio_sendq)) {
280				msg = TAILQ_FIRST(&t->virtual->prio_sendq);
281				TAILQ_REMOVE(&t->virtual->prio_sendq, msg,
282				    link);
283			} else {
284				msg = TAILQ_FIRST(&t->virtual->sendq);
285				TAILQ_REMOVE(&t->virtual->sendq, msg, link);
286			}
287
288			msg->flags &= ~MSG_IN_TRANSIT;
289			exchange = msg->exchange;
290			exchange->in_transit = 0;
291
292			/*
293			 * We disregard the potential error message here,
294			 * hoping that the retransmit will go better.
295			 * XXX Consider a retry/fatal error discriminator.
296			 */
297			t->virtual->vtbl->send_message(msg, 0);
298			msg->xmits++;
299
300			/*
301			 * This piece of code has been proven to be quite
302			 * delicate. Think twice for before altering.
303			 * Here's an outline:
304			 *
305			 * If this message is not the one which finishes an
306			 * exchange, check if we have reached the number of
307			 * retransmit before queuing it up for another.
308			 *
309			 * If it is a finishing message we still may have to
310			 * keep it around for an on-demand retransmit when
311			 * seeing a duplicate of our peer's previous message.
312			 */
313			if ((msg->flags & MSG_LAST) == 0) {
314				if (msg->flags & MSG_DONTRETRANSMIT)
315					exchange->last_sent = 0;
316				else if (msg->xmits > conf_get_num("General",
317				    "retransmits", RETRANSMIT_DEFAULT)) {
318					t->virtual->vtbl->get_dst(t->virtual, &dst);
319					if (getnameinfo(dst, SA_LEN(dst), peer,
320					    sizeof peer, peersv, sizeof peersv,
321					    NI_NUMERICHOST | NI_NUMERICSERV)) {
322						strlcpy(peer, "<unknown>", sizeof peer);
323						strlcpy(peersv, "<?>", sizeof peersv);
324					}
325					log_print("transport_send_messages: "
326					    "giving up on exchange %s, no "
327					    "response from peer %s:%s",
328					    exchange->name ? exchange->name :
329					    "<unnamed>", peer, peersv);
330
331					exchange->last_sent = 0;
332#ifdef notyet
333					exchange_free(exchange);
334					exchange = 0;
335#endif
336				} else {
337					clock_gettime(CLOCK_MONOTONIC,
338					    &expiration);
339
340					/*
341					 * XXX Calculate from round trip
342					 * timings and a backoff func.
343					 */
344					expiry = msg->xmits * 2 + 5;
345					expiration.tv_sec += expiry;
346					LOG_DBG((LOG_TRANSPORT, 30,
347					    "transport_send_messages: "
348					    "message %p scheduled for "
349					    "retransmission %d in %d secs",
350					    msg, msg->xmits, expiry));
351					if (msg->retrans)
352						timer_remove_event(msg->retrans);
353					msg->retrans
354					    = timer_add_event("message_send_expire",
355						(void (*) (void *)) message_send_expire,
356						msg, &expiration);
357					/*
358					 * If we cannot retransmit, we
359					 * cannot...
360					 */
361					exchange->last_sent =
362					    msg->retrans ? msg : 0;
363				}
364			} else
365				exchange->last_sent =
366				    exchange->last_received ? msg : 0;
367
368			/*
369			 * If this message is not referred to for later
370			 * retransmission it will be ok for us to drop it
371			 * after the post-send function. But as the post-send
372			 * function may remove the exchange, we need to
373			 * remember this fact here.
374			 */
375			ok_to_drop_message = exchange->last_sent == 0;
376
377			/*
378			 * If this is not a retransmit call post-send
379			 * functions that allows parallel work to be done
380			 * while the network and peer does their share of
381			 * the job.  Note that a post-send function may take
382			 * away the exchange we belong to, but only if no
383			 * retransmits are possible.
384			 */
385			if (msg->xmits == 1)
386				message_post_send(msg);
387
388			if (ok_to_drop_message)
389				message_free(msg);
390		}
391	}
392
393	for (t = LIST_FIRST(&transport_list); t; t = next) {
394		next = LIST_NEXT(t, link);
395		transport_release(t->virtual);
396	}
397}
398
399/*
400 * Textual search after the transport method denoted by NAME, then create
401 * a transport connected to the peer with address ADDR, given in a transport-
402 * specific string format.
403 */
404struct transport *
405transport_create(char *name, char *addr)
406{
407	struct transport_vtbl *method;
408
409	for (method = LIST_FIRST(&transport_method_list); method;
410	    method = LIST_NEXT(method, link))
411		if (strcmp(method->name, name) == 0)
412			return (*method->create) (addr);
413	return 0;
414}
415