diff options
author | Hakan Olsson <ho@cvs.openbsd.org> | 2004-06-20 15:24:06 +0000 |
---|---|---|
committer | Hakan Olsson <ho@cvs.openbsd.org> | 2004-06-20 15:24:06 +0000 |
commit | 296e43726af7240ad61013d007264b8effec9333 (patch) | |
tree | b4f391ee607488f132ec2354d64361bf9b4e3735 /sbin/isakmpd/virtual.c | |
parent | dce4168d7b1745fda1db84e6b445d6e2141503cf (diff) |
NAT-Traversal for isakmpd. Work in progress...
hshoexer@ ok.
Diffstat (limited to 'sbin/isakmpd/virtual.c')
-rw-r--r-- | sbin/isakmpd/virtual.c | 706 |
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; +} |