1 /* $OpenBSD: transport.c,v 1.36 2013/03/21 04:30:14 deraadt 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 timeval 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->xmits > conf_get_num("General", 313 "retransmits", RETRANSMIT_DEFAULT)) { 314 t->virtual->vtbl->get_dst(t->virtual, &dst); 315 if (getnameinfo(dst, SA_LEN(dst), peer, 316 sizeof peer, peersv, sizeof peersv, 317 NI_NUMERICHOST | NI_NUMERICSERV)) { 318 strlcpy(peer, "<unknown>", sizeof peer); 319 strlcpy(peersv, "<?>", sizeof peersv); 320 } 321 log_print("transport_send_messages: " 322 "giving up on exchange %s, no " 323 "response from peer %s:%s", 324 exchange->name ? exchange->name : 325 "<unnamed>", peer, peersv); 326 327 exchange->last_sent = 0; 328 #ifdef notyet 329 exchange_free(exchange); 330 exchange = 0; 331 #endif 332 } else { 333 gettimeofday(&expiration, 0); 334 335 /* 336 * XXX Calculate from round trip 337 * timings and a backoff func. 338 */ 339 expiry = msg->xmits * 2 + 5; 340 expiration.tv_sec += expiry; 341 LOG_DBG((LOG_TRANSPORT, 30, 342 "transport_send_messages: " 343 "message %p scheduled for " 344 "retransmission %d in %d secs", 345 msg, msg->xmits, expiry)); 346 if (msg->retrans) 347 timer_remove_event(msg->retrans); 348 msg->retrans 349 = timer_add_event("message_send_expire", 350 (void (*) (void *)) message_send_expire, 351 msg, &expiration); 352 /* 353 * If we cannot retransmit, we 354 * cannot... 355 */ 356 exchange->last_sent = 357 msg->retrans ? msg : 0; 358 } 359 } else 360 exchange->last_sent = 361 exchange->last_received ? msg : 0; 362 363 /* 364 * If this message is not referred to for later 365 * retransmission it will be ok for us to drop it 366 * after the post-send function. But as the post-send 367 * function may remove the exchange, we need to 368 * remember this fact here. 369 */ 370 ok_to_drop_message = exchange->last_sent == 0; 371 372 /* 373 * If this is not a retransmit call post-send 374 * functions that allows parallel work to be done 375 * while the network and peer does their share of 376 * the job. Note that a post-send function may take 377 * away the exchange we belong to, but only if no 378 * retransmits are possible. 379 */ 380 if (msg->xmits == 1) 381 message_post_send(msg); 382 383 if (ok_to_drop_message) 384 message_free(msg); 385 } 386 } 387 388 for (t = LIST_FIRST(&transport_list); t; t = next) { 389 next = LIST_NEXT(t, link); 390 transport_release(t->virtual); 391 } 392 } 393 394 /* 395 * Textual search after the transport method denoted by NAME, then create 396 * a transport connected to the peer with address ADDR, given in a transport- 397 * specific string format. 398 */ 399 struct transport * 400 transport_create(char *name, char *addr) 401 { 402 struct transport_vtbl *method; 403 404 for (method = LIST_FIRST(&transport_method_list); method; 405 method = LIST_NEXT(method, link)) 406 if (strcmp(method->name, name) == 0) 407 return (*method->create) (addr); 408 return 0; 409 } 410