summaryrefslogtreecommitdiff
path: root/sbin/isakmpd/virtual.c
diff options
context:
space:
mode:
authorHakan Olsson <ho@cvs.openbsd.org>2004-06-20 15:24:06 +0000
committerHakan Olsson <ho@cvs.openbsd.org>2004-06-20 15:24:06 +0000
commit296e43726af7240ad61013d007264b8effec9333 (patch)
treeb4f391ee607488f132ec2354d64361bf9b4e3735 /sbin/isakmpd/virtual.c
parentdce4168d7b1745fda1db84e6b445d6e2141503cf (diff)
NAT-Traversal for isakmpd. Work in progress...
hshoexer@ ok.
Diffstat (limited to 'sbin/isakmpd/virtual.c')
-rw-r--r--sbin/isakmpd/virtual.c706
1 files changed, 706 insertions, 0 deletions
diff --git a/sbin/isakmpd/virtual.c b/sbin/isakmpd/virtual.c
new file mode 100644
index 00000000000..5bcb2d76c06
--- /dev/null
+++ b/sbin/isakmpd/virtual.c
@@ -0,0 +1,706 @@
+/* $OpenBSD: virtual.c,v 1.1 2004/06/20 15:24:05 ho Exp $ */
+
+/*
+ * Copyright (c) 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.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#ifndef linux
+#include <sys/sockio.h>
+#endif
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <err.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "conf.h"
+#include "if.h"
+#include "exchange.h"
+#include "log.h"
+#include "transport.h"
+#include "virtual.h"
+#include "udp.h"
+#include "util.h"
+
+#if defined (USE_NAT_TRAVERSAL)
+#include "udp_encap.h"
+#endif
+
+static struct transport *virtual_bind(const struct sockaddr *);
+static struct transport *virtual_bind_ADDR_ANY(sa_family_t);
+static int virtual_bind_if(char *, struct sockaddr *, void *);
+static struct transport *virtual_clone(struct transport *, struct sockaddr *);
+static struct transport *virtual_create(char *);
+static char *virtual_decode_ids (struct transport *);
+static int virtual_fd_set(struct transport *, fd_set *, int);
+static int virtual_fd_isset(struct transport *, fd_set *);
+static void virtual_get_dst(struct transport *,
+ struct sockaddr **);
+static struct msg_head *virtual_get_queue(struct message *);
+static void virtual_get_src(struct transport *,
+ struct sockaddr **);
+static void virtual_handle_message(struct transport *);
+static void virtual_reinit(void);
+static void virtual_remove(struct transport *);
+static void virtual_report(struct transport *);
+static int virtual_send_message(struct message *,
+ struct transport *);
+
+static struct transport_vtbl virtual_transport_vtbl = {
+ { 0 }, "udp",
+ virtual_create,
+ virtual_reinit,
+ virtual_remove,
+ virtual_report,
+ virtual_fd_set,
+ virtual_fd_isset,
+ virtual_handle_message,
+ virtual_send_message,
+ virtual_get_dst,
+ virtual_get_src,
+ virtual_decode_ids,
+ virtual_clone,
+ virtual_get_queue
+};
+
+static LIST_HEAD (virtual_listen_list, virtual_transport) virtual_listen_list;
+static struct transport *default_transport, *default_transport6;
+
+void
+virtual_init(void)
+{
+ struct conf_list *listen_on;
+
+ LIST_INIT(&virtual_listen_list);
+
+ transport_method_add(&virtual_transport_vtbl);
+
+ /* Bind the ISAKMP port(s) on all network interfaces we have. */
+ if (if_map(virtual_bind_if, 0) == -1)
+ log_fatal("virtual_init: "
+ "could not bind the ISAKMP port(s) on all interfaces");
+
+ /* Only listen to the specified address if Listen-on is configured */
+ listen_on = conf_get_list("General", "Listen-on");
+ if (listen_on) {
+ LOG_DBG((LOG_TRANSPORT, 50,
+ "virtual_init: not binding ISAKMP port(s) to ADDR_ANY"));
+ conf_free_list(listen_on);
+ return;
+ }
+
+ /*
+ * Bind to INADDR_ANY in case of new addresses popping up.
+ * Packet reception on this transport is taken as a hint to reprobe the
+ * interface list.
+ */
+ if (!bind_family || (bind_family & BIND_FAMILY_INET4)) {
+ default_transport = virtual_bind_ADDR_ANY(AF_INET);
+ if (!default_transport)
+ return;
+ LIST_INSERT_HEAD(&virtual_listen_list,
+ (struct virtual_transport *)default_transport, link);
+ }
+
+ if (!bind_family || (bind_family & BIND_FAMILY_INET6)) {
+ default_transport6 = virtual_bind_ADDR_ANY(AF_INET6);
+ if (!default_transport6)
+ return;
+ LIST_INSERT_HEAD(&virtual_listen_list,
+ (struct virtual_transport *)default_transport6, link);
+ }
+}
+
+struct virtual_transport *
+virtual_get_default(sa_family_t af)
+{
+ switch (af) {
+ case AF_INET:
+ return (struct virtual_transport *)default_transport;
+ case AF_INET6:
+ return (struct virtual_transport *)default_transport6;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Probe the interface list and determine what new interfaces have
+ * appeared.
+ *
+ * At the same time, we try to determine whether existing interfaces have
+ * been rendered invalid; we do this by marking all virtual transports before
+ * we call virtual_bind_if () through if_map (), and then releasing those
+ * transports that have not been unmarked.
+ */
+void
+virtual_reinit(void)
+{
+ struct virtual_transport *v, *v2;
+
+ /* Mark all UDP transports, except the default ones. */
+ for (v = LIST_FIRST(&virtual_listen_list); v; v = LIST_NEXT(v, link))
+ if (&v->transport != default_transport
+ && &v->transport != default_transport6)
+ v->transport.flags |= TRANSPORT_MARK;
+
+ /* Re-probe interface list. */
+ if (if_map(virtual_bind_if, 0) == -1)
+ log_print("virtual_init: "
+ "could not bind the ISAKMP port(s) on all interfaces");
+
+ /*
+ * Release listening transports for local addresses that no
+ * longer exist. virtual_bind_if () will have left those still marked.
+ */
+ v = LIST_FIRST(&virtual_listen_list);
+ while (v) {
+ v2 = LIST_NEXT(v, link);
+ if (v->transport.flags & TRANSPORT_MARK) {
+ LIST_REMOVE(v, link);
+ transport_release(&v->transport);
+ }
+ v = v2;
+ }
+}
+
+struct virtual_transport *
+virtual_listen_lookup(struct sockaddr *addr)
+{
+ struct virtual_transport *v;
+ struct udp_transport *u;
+
+ for (v = LIST_FIRST(&virtual_listen_list); v;
+ v = LIST_NEXT(v, link)) {
+ u = (struct udp_transport *)v->main;
+ if (u->src->sa_family == addr->sa_family
+ && sockaddr_addrlen(u->src) == sockaddr_addrlen(addr)
+ && memcmp(sockaddr_addrdata (u->src),
+ sockaddr_addrdata(addr),
+ sockaddr_addrlen(addr)) == 0)
+ return v;
+ }
+
+ return 0;
+}
+
+/*
+ * Initialize an object of the VIRTUAL transport class.
+ */
+static struct transport *
+virtual_bind(const struct sockaddr *addr)
+{
+ struct virtual_transport *v;
+ struct sockaddr_storage tmp_sa;
+ char *port;
+ char *ep;
+ long lport;
+
+ v = (struct virtual_transport *)calloc(1, sizeof *v);
+ if (!v) {
+ log_error("virtual_bind: calloc(1, %lu) failed",
+ (unsigned long)sizeof *v);
+ return 0;
+ }
+
+ v->transport.vtbl = &virtual_transport_vtbl;
+
+ memcpy(&tmp_sa, addr, sysdep_sa_len((struct sockaddr *)addr));
+
+ /*
+ * Get port.
+ * XXX Use getservbyname too.
+ */
+ port = udp_default_port ? udp_default_port : UDP_DEFAULT_PORT_STR;
+ lport = strtol(port, &ep, 10);
+ if (*ep != '\0' || lport < 0 || lport > USHRT_MAX) {
+ log_print("virtual_bind: "
+ "port string \"%s\" not convertible to in_port_t", port);
+ return 0;
+ }
+
+ sockaddr_set_port((struct sockaddr *)&tmp_sa, (in_port_t)lport);
+ v->main = udp_bind((struct sockaddr *)&tmp_sa);
+ ((struct transport *)v->main)->virtual = (struct transport *)v;
+
+#if defined (USE_NAT_TRAVERSAL)
+ memcpy(&tmp_sa, addr, sysdep_sa_len((struct sockaddr *)addr));
+
+ /*
+ * Get port.
+ * XXX Use getservbyname too.
+ */
+ port = udp_encap_default_port
+ ? udp_encap_default_port : UDP_ENCAP_DEFAULT_PORT_STR;
+ lport = strtol(port, &ep, 10);
+ if (*ep != '\0' || lport < 0 || lport > USHRT_MAX) {
+ log_print("virtual_bind: "
+ "port string \"%s\" not convertible to in_port_t", port);
+ return 0;
+ }
+
+ sockaddr_set_port((struct sockaddr *)&tmp_sa, (in_port_t)lport);
+ v->encap = udp_encap_bind((struct sockaddr *)&tmp_sa);
+ ((struct transport *)v->encap)->virtual = (struct transport *)v;
+#endif
+ v->encap_is_active = 0;
+
+ transport_setup(&v->transport, 1);
+ transport_reference(&v->transport);
+ v->transport.flags |= TRANSPORT_LISTEN;
+
+ return (struct transport *)v;
+}
+
+static struct transport *
+virtual_bind_ADDR_ANY(sa_family_t af)
+{
+ struct sockaddr_storage dflt_stor;
+ struct sockaddr_in *d4 = (struct sockaddr_in *)&dflt_stor;
+ struct sockaddr_in6 *d6 = (struct sockaddr_in6 *)&dflt_stor;
+ struct transport *t;
+ struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+
+ memset(&dflt_stor, 0, sizeof dflt_stor);
+ switch (af) {
+ case AF_INET:
+ d4->sin_family = af;
+#if !defined (LINUX_IPSEC)
+ d4->sin_len = sizeof(struct sockaddr_in);
+#endif
+ d4->sin_addr.s_addr = INADDR_ANY;
+ break;
+
+ case AF_INET6:
+ d6->sin6_family = af;
+#if !defined (LINUX_IPSEC)
+ d6->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ memcpy(&d6->sin6_addr.s6_addr, &in6addr_any,
+ sizeof in6addr_any);
+ break;
+ }
+
+ t = virtual_bind((struct sockaddr *)&dflt_stor);
+ if (!t)
+ log_error("virtual_bind_ADDR_ANY: "
+ "could not allocate default IPv%s ISAKMP port(s)",
+ af == AF_INET ? "4" : "6");
+ return t;
+}
+
+static int
+virtual_bind_if(char *ifname, struct sockaddr *if_addr, void *arg)
+{
+ struct conf_list *listen_on;
+ struct virtual_transport *v;
+ struct conf_list_node *address;
+ struct sockaddr *addr;
+ struct transport *t;
+ struct ifreq flags_ifr;
+ char *addr_str;
+ int s, error;
+
+#if defined (USE_DEBUG)
+ if (sockaddr2text(if_addr, &addr_str, 0))
+ addr_str = 0;
+
+ LOG_DBG((LOG_TRANSPORT, 90,
+ "virtual_bind_if: interface %s family %s address %s",
+ ifname ? ifname : "<unknown>",
+ if_addr->sa_family == AF_INET ? "v4" :
+ (if_addr->sa_family == AF_INET6 ? "v6" : "<unknown>"),
+ addr_str ? addr_str : "<invalid>"));
+ if (addr_str)
+ free(addr_str);
+#endif
+
+ /*
+ * Drop non-Internet stuff.
+ */
+ if ((if_addr->sa_family != AF_INET
+ || sysdep_sa_len(if_addr) != sizeof (struct sockaddr_in))
+ && (if_addr->sa_family != AF_INET6
+ || sysdep_sa_len(if_addr) != sizeof (struct sockaddr_in6)))
+ return 0;
+
+ /*
+ * Only create sockets for families we should listen to.
+ */
+ if (bind_family)
+ switch (if_addr->sa_family) {
+ case AF_INET:
+ if ((bind_family & BIND_FAMILY_INET4) == 0)
+ return 0;
+ break;
+ case AF_INET6:
+ if ((bind_family & BIND_FAMILY_INET6) == 0)
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+
+ /*
+ * These special addresses are not useable as they have special meaning
+ * in the IP stack.
+ */
+ if (if_addr->sa_family == AF_INET
+ && (((struct sockaddr_in *)if_addr)->sin_addr.s_addr == INADDR_ANY
+ || (((struct sockaddr_in *)if_addr)->sin_addr.s_addr
+ == INADDR_NONE)))
+ return 0;
+
+ /*
+ * Go through the list of transports and see if we already have this
+ * address bound. If so, unmark the transport and skip it; this allows
+ * us to call this function when we suspect a new address has appeared.
+ */
+ if ((v = virtual_listen_lookup(if_addr)) != 0) {
+ LOG_DBG ((LOG_TRANSPORT, 90, "virtual_bind_if: "
+ "already bound"));
+ v->transport.flags &= ~TRANSPORT_MARK;
+ return 0;
+ }
+
+ /*
+ * Don't bother with interfaces that are down.
+ * Note: This socket is only used to collect the interface status.
+ */
+ s = socket(if_addr->sa_family, SOCK_DGRAM, 0);
+ if (s == -1) {
+ log_error("virtual_bind_if: "
+ "socket (%d, SOCK_DGRAM, 0) failed", if_addr->sa_family);
+ return -1;
+ }
+ strlcpy(flags_ifr.ifr_name, ifname, sizeof flags_ifr.ifr_name);
+ if (ioctl(s, SIOCGIFFLAGS, (caddr_t)&flags_ifr) == -1) {
+ log_error("virtual_bind_if: "
+ "ioctl (%d, SIOCGIFFLAGS, ...) failed", s);
+ return -1;
+ }
+ close(s);
+ if (!(flags_ifr.ifr_flags & IFF_UP))
+ return 0;
+
+ /* Set the port number to zero. */
+ switch (if_addr->sa_family) {
+ case AF_INET:
+ ((struct sockaddr_in *)if_addr)->sin_port = htons(0);
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)if_addr)->sin6_port = htons(0);
+ break;
+ default:
+ log_print("virtual_bind_if: unsupported protocol family %d",
+ if_addr->sa_family);
+ break;
+ }
+
+ /*
+ * If we are explicit about what addresses we can listen to, be sure
+ * to respect that option.
+ * This is quite wasteful redoing the list-run for every interface,
+ * but who cares? This is not an operation that needs to be fast.
+ */
+ listen_on = conf_get_list("General", "Listen-on");
+ if (listen_on) {
+ for (address = TAILQ_FIRST(&listen_on->fields); address;
+ address = TAILQ_NEXT(address, link)) {
+ if (text2sockaddr(address->field, 0, &addr)) {
+ log_print("virtual_bind_if: "
+ "invalid address %s in \"Listen-on\"",
+ address->field);
+ continue;
+ }
+
+ /* If found, take the easy way out. */
+ if (memcmp(addr, if_addr,
+ sysdep_sa_len(addr)) == 0) {
+ free(addr);
+ break;
+ }
+ free(addr);
+ }
+ conf_free_list(listen_on);
+
+ /*
+ * If address is zero then we did not find the address among
+ * the ones we should listen to.
+ * XXX We do not discover if we do not find our listen
+ * addresses. Maybe this should be the other way round.
+ */
+ if (!address)
+ return 0;
+ }
+
+ t = virtual_bind(if_addr);
+ if (!t) {
+ error = sockaddr2text(if_addr, &addr_str, 0);
+ log_print("virtual_bind_if: failed to create a socket on %s",
+ error ? "unknown" : addr_str);
+ if (!error)
+ free(addr_str);
+ return -1;
+ }
+ LIST_INSERT_HEAD(&virtual_listen_list, (struct virtual_transport *)t,
+ link);
+ return 0;
+}
+
+static struct transport *
+virtual_clone(struct transport *vt, struct sockaddr *raddr)
+{
+ struct virtual_transport *v = (struct virtual_transport *)vt;
+ struct virtual_transport *v2;
+ struct transport *t;
+
+ t = malloc(sizeof *v);
+ if (!t) {
+ log_error("virtual_clone: malloc(%lu) failed",
+ (unsigned long)sizeof *v);
+ return 0;
+ }
+ v2 = (struct virtual_transport *)t;
+
+ memcpy(v2, v, sizeof *v);
+
+ if (v->encap_is_active) {
+ v2->main = 0;
+ v2->encap = v->encap->vtbl->clone(v->encap, raddr);
+ v2->encap->virtual = (struct transport *)v2;
+ } else {
+ v2->main = v->main->vtbl->clone(v->main, raddr);
+ v2->main->virtual = (struct transport *)v2;
+ v2->encap = 0;
+ }
+ LOG_DBG((LOG_TRANSPORT, 50, "virtual_clone: old %p new %p (->%s %p)",
+ v, t, v->encap_is_active ? "encap" : "main",
+ v->encap_is_active ? v2->encap : v2->main));
+
+ t->flags &= ~TRANSPORT_LISTEN;
+ transport_setup(t, 1);
+
+ transport_reference(t);
+ transport_reference(v->encap_is_active ? v2->encap : v2->main);
+
+ return t;
+}
+
+static struct transport *
+virtual_create(char *name)
+{
+ struct virtual_transport *v;
+ struct transport *t, *t2;
+
+ t = transport_create("udp_physical", name);
+ if (!t)
+ return 0;
+
+#if defined (USE_NAT_TRAVERSAL)
+ t2 = transport_create("udp_encap", name);
+ if (!t2) {
+ t->vtbl->remove(t);
+ return 0;
+ }
+#else
+ t2 = 0;
+#endif
+
+ v = (struct virtual_transport *)calloc(1, sizeof *v);
+ if (!v) {
+ log_error("virtual_create: calloc(1, %lu) failed",
+ (unsigned long)sizeof *v);
+ t->vtbl->remove(t);
+ if (t2)
+ t2->vtbl->remove(t2);
+ return 0;
+ }
+
+ memcpy(v, t, sizeof *t);
+ v->transport.virtual = 0;
+ v->main = t;
+ v->encap = t2;
+ v->transport.vtbl = &virtual_transport_vtbl;
+ t->virtual = (struct transport *)v;
+ if (t2)
+ t2->virtual = (struct transport *)v;
+ transport_setup(&v->transport, 1);
+ LIST_INSERT_HEAD(&virtual_listen_list, v, link);
+
+ return (struct transport *)v;
+}
+
+static void
+virtual_remove(struct transport *t)
+{
+ struct virtual_transport *v = (struct virtual_transport *)t;
+
+ if (v->encap)
+ v->encap->vtbl->remove(v->encap);
+ if (v->main)
+ v->main->vtbl->remove(v->main);
+ if (v->link.le_prev)
+ LIST_REMOVE(v, link);
+
+ free(t);
+}
+
+static void
+virtual_report(struct transport *t)
+{
+ return;
+}
+
+static void
+virtual_handle_message(struct transport *t)
+{
+ struct virtual_transport *v = (struct virtual_transport *)t;
+
+ if (t == default_transport || t == default_transport6) {
+ /* XXX drain pending message. See udp_handle_message(). */
+
+ virtual_reinit();
+
+ /*
+ * As we don't know the actual destination address of the
+ * packet, we can't really deal with it. So, just ignore it
+ * and hope we catch the retransmission.
+ */
+ return;
+ }
+
+ if (v->encap_is_active)
+ v->encap->vtbl->handle_message(v->encap);
+ else
+ v->main->vtbl->handle_message(v->main);
+}
+
+static int
+virtual_send_message(struct message *msg, struct transport *t)
+{
+ struct virtual_transport *v =
+ (struct virtual_transport *)msg->transport;
+
+ /* XXX Debug */
+ if (t)
+ log_print("virtual_send_message: called with "
+ "transport %p != NULL", t);
+
+#if defined (USE_NAT_TRAVERSAL)
+ if (v->encap_is_active == 0 &&
+ (msg->exchange->flags & EXCHANGE_FLAG_NAT_T_ENABLE)) {
+ LOG_DBG((LOG_MESSAGE, 10, "virtual_send_message: "
+ "switching to ENCAP"));
+ v->encap_is_active++;
+ }
+#endif
+
+ if (v->encap_is_active)
+ return v->encap->vtbl->send_message(msg, v->encap);
+ else
+ return v->main->vtbl->send_message(msg, v->main);
+}
+
+static int
+virtual_fd_set(struct transport *t, fd_set *fds, int bit)
+{
+ struct virtual_transport *v = (struct virtual_transport *)t;
+ struct udp_transport *u;
+
+ if (v->encap_is_active)
+ u = (struct udp_transport *)v->encap;
+ else
+ u = (struct udp_transport *)v->main;
+
+ if (bit)
+ FD_SET(u->s, fds);
+ else
+ FD_CLR(u->s, fds);
+
+ return u->s + 1;
+}
+
+static int
+virtual_fd_isset(struct transport *t, fd_set *fds)
+{
+ struct virtual_transport *v = (struct virtual_transport *)t;
+ struct udp_transport *u;
+
+ if (v->encap_is_active)
+ u = (struct udp_transport *)v->encap;
+ else
+ u = (struct udp_transport *)v->main;
+
+ return FD_ISSET(u->s, fds);
+}
+
+static void
+virtual_get_src(struct transport *t, struct sockaddr **s)
+{
+ struct virtual_transport *v = (struct virtual_transport *)t;
+
+ if (v->encap_is_active)
+ v->encap->vtbl->get_src(v->encap, s);
+ else
+ v->main->vtbl->get_src(v->main, s);
+}
+
+static void
+virtual_get_dst(struct transport *t, struct sockaddr **s)
+{
+ struct virtual_transport *v = (struct virtual_transport *)t;
+
+ if (v->encap_is_active)
+ v->encap->vtbl->get_dst(v->encap, s);
+ else
+ v->main->vtbl->get_dst(v->main, s);
+}
+
+static char *
+virtual_decode_ids(struct transport *t)
+{
+ struct virtual_transport *v = (struct virtual_transport *)t;
+
+ if (v->encap_is_active)
+ return v->encap->vtbl->decode_ids(t);
+ else
+ return v->main->vtbl->decode_ids(t);
+}
+
+static struct msg_head *
+virtual_get_queue(struct message *msg)
+{
+ if (msg->flags & MSG_PRIORITIZED)
+ return &msg->transport->prio_sendq;
+ else
+ return &msg->transport->sendq;
+}