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