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