/* $OpenBSD: transport.c,v 1.34 2005/10/27 08:19:59 hshoexer Exp $ */ /* $EOM: transport.c,v 1.43 2000/10/10 12:36:39 provos Exp $ */ /* * Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved. * Copyright (c) 2001, 2004 Håkan Olsson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * This code was written under funding by Ericsson Radio Systems. */ #include #include #include #include #include "conf.h" #include "exchange.h" #include "log.h" #include "message.h" #include "sa.h" #include "timer.h" #include "transport.h" #include "virtual.h" /* If no retransmit limit is given, use this as a default. */ #define RETRANSMIT_DEFAULT 10 LIST_HEAD(transport_method_list, transport_vtbl) transport_method_list; /* Call the reinit function of the various transports. */ void transport_reinit(void) { struct transport_vtbl *method; for (method = LIST_FIRST(&transport_method_list); method; method = LIST_NEXT(method, link)) if (method->reinit) method->reinit(); } /* Initialize the transport maintenance module. */ void transport_init(void) { LIST_INIT(&transport_list); LIST_INIT(&transport_method_list); } /* Register another transport T. */ void transport_setup(struct transport *t, int toplevel) { if (toplevel) { /* Only the toplevel (virtual) transport has sendqueues. */ LOG_DBG((LOG_TRANSPORT, 70, "transport_setup: virtual transport %p", t)); TAILQ_INIT(&t->sendq); TAILQ_INIT(&t->prio_sendq); t->refcnt = 0; } else { /* udp and udp_encap trp goes into the transport list. */ LOG_DBG((LOG_TRANSPORT, 70, "transport_setup: added %p to transport list", t)); LIST_INSERT_HEAD(&transport_list, t, link); t->refcnt = 1; } t->flags = 0; } /* Add a referer to transport T. */ void transport_reference(struct transport *t) { t->refcnt++; LOG_DBG((LOG_TRANSPORT, 95, "transport_reference: transport %p now has %d references", t, t->refcnt)); } /* * Remove a referer from transport T, removing all of T when no referers left. */ void transport_release(struct transport *t) { LOG_DBG((LOG_TRANSPORT, 95, "transport_release: transport %p had %d references", t, t->refcnt)); if (--t->refcnt) return; LOG_DBG((LOG_TRANSPORT, 70, "transport_release: freeing %p", t)); t->vtbl->remove(t); } void transport_report(void) { struct virtual_transport *v; struct transport *t; struct message *msg; for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) { LOG_DBG((LOG_REPORT, 0, "transport_report: transport %p flags %x refcnt %d", t, t->flags, t->refcnt)); /* XXX Report sth on the virtual transport? */ t->vtbl->report(t); /* * This is the reason message_dump_raw lives outside * message.c. */ v = (struct virtual_transport *)t->virtual; if ((v->encap_is_active && v->encap == t) || (!v->encap_is_active && v->main == t)) { for (msg = TAILQ_FIRST(&t->virtual->prio_sendq); msg; msg = TAILQ_NEXT(msg, link)) message_dump_raw("udp_report(prio)", msg, LOG_REPORT); for (msg = TAILQ_FIRST(&t->virtual->sendq); msg; msg = TAILQ_NEXT(msg, link)) message_dump_raw("udp_report", msg, LOG_REPORT); } } } int transport_prio_sendqs_empty(void) { struct transport *t; for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) if (TAILQ_FIRST(&t->virtual->prio_sendq)) return 0; return 1; } /* Register another transport method T. */ void transport_method_add(struct transport_vtbl *t) { LIST_INSERT_HEAD(&transport_method_list, t, link); } /* Apply a function FUNC on all registered (non-toplevel) transports. */ void transport_map(void (*func) (struct transport *)) { struct transport *t; for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) (*func) (t); } /* * Build up a file descriptor set FDS with all transport descriptors we want * to read from. Return the number of file descriptors select(2) needs to * check in order to cover the ones we setup in here. */ int transport_fd_set(fd_set * fds) { struct transport *t; int n; int max = -1; for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) if (t->virtual->flags & TRANSPORT_LISTEN) { n = t->vtbl->fd_set(t, fds, 1); if (n > max) max = n; LOG_DBG((LOG_TRANSPORT, 95, "transport_fd_set: " "transport %p (virtual %p) fd %d", t, t->virtual, n)); } return max + 1; } /* * Build up a file descriptor set FDS with all the descriptors belonging to * transport where messages are queued for transmittal. Return the number * of file descriptors select(2) needs to check in order to cover the ones * we setup in here. */ int transport_pending_wfd_set(fd_set * fds) { struct transport *t; int n; int max = -1; for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) { if (TAILQ_FIRST(&t->virtual->sendq) || TAILQ_FIRST(&t->virtual->prio_sendq)) { n = t->vtbl->fd_set(t, fds, 1); LOG_DBG((LOG_TRANSPORT, 95, "transport_pending_wfd_set: " "transport %p (virtual %p) fd %d pending", t, t->virtual, n)); if (n > max) max = n; } } return max + 1; } /* * For each transport with a file descriptor in FDS, try to get an * incoming message and start processing it. */ void transport_handle_messages(fd_set *fds) { struct transport *t; for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) { if ((t->flags & TRANSPORT_LISTEN) && (*t->vtbl->fd_isset)(t, fds)) { (*t->virtual->vtbl->handle_message)(t); (*t->vtbl->fd_set)(t, fds, 0); } } } /* * Send the first queued message on the transports found whose file * descriptor is in FDS and has messages queued. Remove the fd bit from * FDS as soon as one message has been sent on it so other transports * sharing the socket won't get service without an intervening select * call. Perhaps a fairness strategy should be implemented between * such transports. Now early transports in the list will potentially * be favoured to later ones sharing the file descriptor. */ void transport_send_messages(fd_set * fds) { struct transport *t, *next; struct message *msg; struct exchange *exchange; struct sockaddr *dst; struct timeval expiration; int expiry, ok_to_drop_message; char peer[NI_MAXHOST], peersv[NI_MAXSERV]; /* * Reference all transports first so noone will disappear while in * use. */ for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) transport_reference(t->virtual); for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) { if ((TAILQ_FIRST(&t->virtual->sendq) || TAILQ_FIRST(&t->virtual->prio_sendq)) && t->vtbl->fd_isset(t, fds)) { /* Remove fd bit. */ t->vtbl->fd_set(t, fds, 0); /* Prefer a message from the prioritized sendq. */ if (TAILQ_FIRST(&t->virtual->prio_sendq)) { msg = TAILQ_FIRST(&t->virtual->prio_sendq); TAILQ_REMOVE(&t->virtual->prio_sendq, msg, link); } else { msg = TAILQ_FIRST(&t->virtual->sendq); TAILQ_REMOVE(&t->virtual->sendq, msg, link); } msg->flags &= ~MSG_IN_TRANSIT; exchange = msg->exchange; exchange->in_transit = 0; /* * We disregard the potential error message here, * hoping that the retransmit will go better. * XXX Consider a retry/fatal error discriminator. */ t->virtual->vtbl->send_message(msg, 0); msg->xmits++; /* * This piece of code has been proven to be quite * delicate. Think twice for before altering. * Here's an outline: * * If this message is not the one which finishes an * exchange, check if we have reached the number of * retransmit before queuing it up for another. * * If it is a finishing message we still may have to * keep it around for an on-demand retransmit when * seeing a duplicate of our peer's previous message. */ if ((msg->flags & MSG_LAST) == 0) { if (msg->xmits > conf_get_num("General", "retransmits", RETRANSMIT_DEFAULT)) { t->virtual->vtbl->get_dst(t->virtual, &dst); if (getnameinfo(dst, SA_LEN(dst), peer, sizeof peer, peersv, sizeof peersv, NI_NUMERICHOST | NI_NUMERICSERV)) { strlcpy(peer, "", sizeof peer); strlcpy(peersv, "", sizeof peersv); } log_print("transport_send_messages: " "giving up on exchange %s, no " "response from peer %s:%s", exchange->name ? exchange->name : "", peer, peersv); exchange->last_sent = 0; #ifdef notyet exchange_free(exchange); exchange = 0; #endif } else { gettimeofday(&expiration, 0); /* * XXX Calculate from round trip * timings and a backoff func. */ expiry = msg->xmits * 2 + 5; expiration.tv_sec += expiry; LOG_DBG((LOG_TRANSPORT, 30, "transport_send_messages: " "message %p scheduled for " "retransmission %d in %d secs", msg, msg->xmits, expiry)); if (msg->retrans) timer_remove_event(msg->retrans); msg->retrans = timer_add_event("message_send_expire", (void (*) (void *)) message_send_expire, msg, &expiration); /* * If we cannot retransmit, we * cannot... */ exchange->last_sent = msg->retrans ? msg : 0; } } else exchange->last_sent = exchange->last_received ? msg : 0; /* * If this message is not referred to for later * retransmission it will be ok for us to drop it * after the post-send function. But as the post-send * function may remove the exchange, we need to * remember this fact here. */ ok_to_drop_message = exchange->last_sent == 0; /* * If this is not a retransmit call post-send * functions that allows parallel work to be done * while the network and peer does their share of * the job. Note that a post-send function may take * away the exchange we belong to, but only if no * retransmits are possible. */ if (msg->xmits == 1) message_post_send(msg); if (ok_to_drop_message) message_free(msg); } } for (t = LIST_FIRST(&transport_list); t; t = next) { next = LIST_NEXT(t, link); transport_release(t->virtual); } } /* * Textual search after the transport method denoted by NAME, then create * a transport connected to the peer with address ADDR, given in a transport- * specific string format. */ struct transport * transport_create(char *name, char *addr) { struct transport_vtbl *method; for (method = LIST_FIRST(&transport_method_list); method; method = LIST_NEXT(method, link)) if (strcmp(method->name, name) == 0) return (*method->create) (addr); return 0; }