aboutsummaryrefslogtreecommitdiff
path: root/sys/net/if_ovpn.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/net/if_ovpn.c')
-rw-r--r--sys/net/if_ovpn.c321
1 files changed, 279 insertions, 42 deletions
diff --git a/sys/net/if_ovpn.c b/sys/net/if_ovpn.c
index 7bdbc565f4ca..fe3e7bbd7fff 100644
--- a/sys/net/if_ovpn.c
+++ b/sys/net/if_ovpn.c
@@ -34,11 +34,13 @@
#include <sys/epoch.h>
#include <sys/file.h>
#include <sys/filedesc.h>
+#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/nv.h>
+#include <sys/osd.h>
#include <sys/priv.h>
#include <sys/protosw.h>
#include <sys/rmlock.h>
@@ -79,7 +81,6 @@
#include "if_ovpn.h"
struct ovpn_kkey_dir {
- int refcount;
uint8_t key[32];
uint8_t keylen;
uint8_t nonce[8];
@@ -132,6 +133,9 @@ struct ovpn_notification {
/* Delete notification */
enum ovpn_del_reason del_reason;
struct ovpn_peer_counters counters;
+
+ /* Float notification */
+ struct sockaddr_storage address;
};
struct ovpn_softc;
@@ -196,6 +200,10 @@ struct ovpn_softc {
struct epoch_context epoch_ctx;
};
+struct ovpn_mtag {
+ struct sockaddr_storage addr;
+};
+
static struct ovpn_kpeer *ovpn_find_peer(struct ovpn_softc *, uint32_t);
static bool ovpn_udp_input(struct mbuf *, int, struct inpcb *,
const struct sockaddr *, void *);
@@ -205,7 +213,10 @@ static int ovpn_encap(struct ovpn_softc *, uint32_t, struct mbuf *);
static int ovpn_get_af(struct mbuf *);
static void ovpn_free_kkey_dir(struct ovpn_kkey_dir *);
static bool ovpn_check_replay(struct ovpn_kkey_dir *, uint32_t);
-static int ovpn_peer_compare(struct ovpn_kpeer *, struct ovpn_kpeer *);
+static int ovpn_peer_compare(const struct ovpn_kpeer *,
+ const struct ovpn_kpeer *);
+static bool ovpn_sockaddr_compare(const struct sockaddr *,
+ const struct sockaddr *);
static RB_PROTOTYPE(ovpn_kpeers, ovpn_kpeer, tree, ovpn_peer_compare);
static RB_GENERATE(ovpn_kpeers, ovpn_kpeer, tree, ovpn_peer_compare);
@@ -278,11 +289,48 @@ SYSCTL_INT(_net_link_openvpn, OID_AUTO, netisr_queue,
"Use netisr_queue() rather than netisr_dispatch().");
static int
-ovpn_peer_compare(struct ovpn_kpeer *a, struct ovpn_kpeer *b)
+ovpn_peer_compare(const struct ovpn_kpeer *a, const struct ovpn_kpeer *b)
{
return (a->peerid - b->peerid);
}
+static bool
+ovpn_sockaddr_compare(const struct sockaddr *a,
+ const struct sockaddr *b)
+{
+ if (a->sa_family != b->sa_family)
+ return (false);
+ MPASS(a->sa_len == b->sa_len);
+
+ switch (a->sa_family) {
+ case AF_INET: {
+ const struct sockaddr_in *a4, *b4;
+
+ a4 = (const struct sockaddr_in *)a;
+ b4 = (const struct sockaddr_in *)b;
+
+ if (a4->sin_port != b4->sin_port)
+ return (false);
+
+ return (a4->sin_addr.s_addr == b4->sin_addr.s_addr);
+ }
+ case AF_INET6: {
+ const struct sockaddr_in6 *a6, *b6;
+
+ a6 = (const struct sockaddr_in6 *)a;
+ b6 = (const struct sockaddr_in6 *)b;
+
+ if (a6->sin6_port != b6->sin6_port)
+ return (false);
+
+ return (memcmp(&a6->sin6_addr, &b6->sin6_addr,
+ sizeof(a6->sin6_addr)) == 0);
+ }
+ default:
+ panic("Unknown address family %d", a->sa_family);
+ }
+}
+
static struct ovpn_kpeer *
ovpn_find_peer(struct ovpn_softc *sc, uint32_t peerid)
{
@@ -304,15 +352,15 @@ ovpn_find_only_peer(struct ovpn_softc *sc)
}
static uint16_t
-ovpn_get_port(struct sockaddr_storage *s)
+ovpn_get_port(const struct sockaddr_storage *s)
{
switch (s->ss_family) {
case AF_INET: {
- struct sockaddr_in *in = (struct sockaddr_in *)s;
+ const struct sockaddr_in *in = (const struct sockaddr_in *)s;
return (in->sin_port);
}
case AF_INET6: {
- struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)s;
+ const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)s;
return (in6->sin6_port);
}
default:
@@ -320,6 +368,25 @@ ovpn_get_port(struct sockaddr_storage *s)
}
}
+static void
+ovpn_set_port(struct sockaddr_storage *s, unsigned short port)
+{
+ switch (s->ss_family) {
+ case AF_INET: {
+ struct sockaddr_in *in = (struct sockaddr_in *)s;
+ in->sin_port = port;
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)s;
+ in6->sin6_port = port;
+ break;
+ }
+ default:
+ panic("Unsupported address family %d", s->ss_family);
+ }
+}
+
static int
ovpn_nvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *sa)
{
@@ -333,14 +400,16 @@ ovpn_nvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *sa)
return (EINVAL);
af = nvlist_get_number(nvl, "af");
-
switch (af) {
#ifdef INET
case AF_INET: {
struct sockaddr_in *in = (struct sockaddr_in *)sa;
size_t len;
const void *addr = nvlist_get_binary(nvl, "address", &len);
+
+ memset(in, 0, sizeof(*in));
in->sin_family = af;
+ in->sin_len = sizeof(*in);
if (len != sizeof(in->sin_addr))
return (EINVAL);
@@ -354,7 +423,10 @@ ovpn_nvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *sa)
struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)sa;
size_t len;
const void *addr = nvlist_get_binary(nvl, "address", &len);
+
+ memset(in6, 0, sizeof(*in6));
in6->sin6_family = af;
+ in6->sin6_len = sizeof(*in6);
if (len != sizeof(in6->sin6_addr))
return (EINVAL);
@@ -370,31 +442,42 @@ ovpn_nvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *sa)
return (0);
}
-static bool
-ovpn_has_peers(struct ovpn_softc *sc)
+static int
+ovpn_add_sockaddr(nvlist_t *parent, const char *name, const struct sockaddr *s)
{
- OVPN_ASSERT(sc);
-
- return (sc->peercount > 0);
-}
+ nvlist_t *nvl;
-static void
-ovpn_rele_so(struct ovpn_softc *sc)
-{
- bool has_peers;
+ nvl = nvlist_create(0);
+ if (nvl == NULL)
+ return (ENOMEM);
- OVPN_WASSERT(sc);
+ nvlist_add_number(nvl, "af", s->sa_family);
- if (sc->so == NULL)
- return;
+ switch (s->sa_family) {
+ case AF_INET: {
+ const struct sockaddr_in *s4 = (const struct sockaddr_in *)s;
- has_peers = ovpn_has_peers(sc);
+ nvlist_add_number(nvl, "port", s4->sin_port);
+ nvlist_add_binary(nvl, "address", &s4->sin_addr,
+ sizeof(s4->sin_addr));
+ break;
+ }
+ case AF_INET6: {
+ const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *)s;
- if (! has_peers) {
- MPASS(sc->peercount == 0);
- } else {
- MPASS(sc->peercount > 0);
+ nvlist_add_number(nvl, "port", s6->sin6_port);
+ nvlist_add_binary(nvl, "address", &s6->sin6_addr,
+ sizeof(s6->sin6_addr));
+ break;
+ }
+ default:
+ nvlist_destroy(nvl);
+ return (EINVAL);
}
+
+ nvlist_move_nvlist(parent, name, nvl);
+
+ return (0);
}
static void
@@ -449,6 +532,33 @@ ovpn_notify_key_rotation(struct ovpn_softc *sc, struct ovpn_kpeer *peer)
}
}
+static int
+ovpn_notify_float(struct ovpn_softc *sc, uint32_t peerid,
+ const struct sockaddr_storage *remote)
+{
+ struct ovpn_notification *n;
+
+ n = malloc(sizeof(*n), M_OVPN, M_NOWAIT | M_ZERO);
+ if (n == NULL)
+ return (ENOMEM);
+
+ n->peerid = peerid;
+ n->type = OVPN_NOTIF_FLOAT;
+ memcpy(&n->address, remote, sizeof(n->address));
+
+ if (buf_ring_enqueue(sc->notifring, n) != 0) {
+ free(n, M_OVPN);
+ return (ENOMEM);
+ } else if (sc->so != NULL) {
+ /* Wake up userspace */
+ sc->so->so_error = EAGAIN;
+ sorwakeup(sc->so);
+ sowwakeup(sc->so);
+ }
+
+ return (0);
+}
+
static void
ovpn_peer_release_ref(struct ovpn_kpeer *peer, bool locked)
{
@@ -485,8 +595,6 @@ ovpn_peer_release_ref(struct ovpn_kpeer *peer, bool locked)
ovpn_free_kkey_dir(peer->keys[i].decrypt);
}
- ovpn_rele_so(sc);
-
callout_stop(&peer->ping_send);
callout_stop(&peer->ping_rcv);
uma_zfree_pcpu(pcpu_zone_4, peer->last_active);
@@ -502,7 +610,7 @@ ovpn_new_peer(struct ifnet *ifp, const nvlist_t *nvl)
#ifdef INET6
struct epoch_tracker et;
#endif
- struct sockaddr_storage remote;
+ struct sockaddr_storage local, remote;
struct ovpn_kpeer *peer = NULL;
struct file *fp = NULL;
struct ovpn_softc *sc = ifp->if_softc;
@@ -571,20 +679,37 @@ ovpn_new_peer(struct ifnet *ifp, const nvlist_t *nvl)
callout_init_rm(&peer->ping_send, &sc->lock, CALLOUT_SHAREDLOCK);
callout_init_rm(&peer->ping_rcv, &sc->lock, 0);
- peer->local.ss_len = sizeof(peer->local);
- ret = sosockaddr(so, (struct sockaddr *)&peer->local);
- if (ret)
+ memset(&local, 0, sizeof(local));
+ local.ss_len = sizeof(local);
+ ret = sosockaddr(so, (struct sockaddr *)&local);
+ if (ret != 0)
goto error;
+ if (nvlist_exists_nvlist(nvl, "local")) {
+ struct sockaddr_storage local1;
+
+ ret = ovpn_nvlist_to_sockaddr(nvlist_get_nvlist(nvl, "local"),
+ &local1);
+ if (ret != 0)
+ goto error;
- if (ovpn_get_port(&peer->local) == 0) {
+ /*
+ * openvpn doesn't provide a port here when in multihome mode,
+ * just steal the one the socket is bound to.
+ */
+ if (ovpn_get_port(&local1) == 0)
+ ovpn_set_port(&local1, ovpn_get_port(&local));
+ memcpy(&local, &local1, sizeof(local1));
+ }
+ if (ovpn_get_port(&local) == 0) {
ret = EINVAL;
goto error;
}
- if (peer->local.ss_family != remote.ss_family) {
+ if (local.ss_family != remote.ss_family) {
ret = EINVAL;
goto error;
}
+ memcpy(&peer->local, &local, sizeof(local));
memcpy(&peer->remote, &remote, sizeof(remote));
#ifdef INET6
@@ -633,6 +758,7 @@ ovpn_new_peer(struct ifnet *ifp, const nvlist_t *nvl)
* a new one.
*/
ret = udp_set_kernel_tunneling(sc->so, NULL, NULL, NULL);
+ MPASS(ret == 0);
sorele(sc->so);
sc->so = NULL;
}
@@ -1364,12 +1490,36 @@ opvn_get_pkt(struct ovpn_softc *sc, nvlist_t **onvl)
}
nvlist_add_number(nvl, "peerid", n->peerid);
nvlist_add_number(nvl, "notification", n->type);
- if (n->type == OVPN_NOTIF_DEL_PEER) {
+ switch (n->type) {
+ case OVPN_NOTIF_DEL_PEER: {
nvlist_add_number(nvl, "del_reason", n->del_reason);
/* No error handling, because we want to send the notification
* even if we can't attach the counters. */
ovpn_notif_add_counters(nvl, n);
+ break;
+ }
+ case OVPN_NOTIF_FLOAT: {
+ int ret;
+
+ ret = ovpn_add_sockaddr(nvl, "address",
+ (struct sockaddr *)&n->address);
+
+ if (ret) {
+ /*
+ * Try to re-enqueue the notification. Maybe we'll
+ * have better luck next time. No error handling,
+ * because if we fail to re-enqueue there's nothing we can do.
+ */
+ (void)ovpn_notify_float(sc, n->peerid, &n->address);
+ nvlist_destroy(nvl);
+ free(n, M_OVPN);
+ return (ret);
+ }
+ break;
+ }
+ default:
+ break;
}
free(n, M_OVPN);
@@ -1525,6 +1675,7 @@ ovpn_finish_rx(struct ovpn_softc *sc, struct mbuf *m,
struct rm_priotracker *_ovpn_lock_trackerp)
{
uint32_t af;
+ struct m_tag *mtag;
OVPN_RASSERT(sc);
NET_EPOCH_ASSERT();
@@ -1543,6 +1694,38 @@ ovpn_finish_rx(struct ovpn_softc *sc, struct mbuf *m,
OVPN_RUNLOCK(sc);
+ /* Check if the peer changed to a new source address. */
+ mtag = m_tag_find(m, PACKET_TAG_OVPN, NULL);
+ if (mtag != NULL) {
+ struct ovpn_mtag *ot = (struct ovpn_mtag *)(mtag + 1);
+
+ OVPN_WLOCK(sc);
+
+ /*
+ * Check the address against the peer's remote again, because we may race
+ * against ourselves (i.e. we may have tagged multiple packets to indicate we
+ * floated).
+ */
+ if (ovpn_sockaddr_compare((struct sockaddr *)&ot->addr,
+ (struct sockaddr *)&peer->remote)) {
+ OVPN_WUNLOCK(sc);
+ goto skip_float;
+ }
+
+ /* And notify userspace. */
+ if (ovpn_notify_float(sc, peer->peerid, &ot->addr) == 0) {
+ /*
+ * Update the 'remote' for this peer, but only if
+ * we've actually enqueued the notification.
+ * Otherwise we can try again later.
+ */
+ memcpy(&peer->remote, &ot->addr, sizeof(peer->remote));
+ }
+
+ OVPN_WUNLOCK(sc);
+ }
+
+skip_float:
OVPN_COUNTER_ADD(sc, received_data_pkts, 1);
OVPN_COUNTER_ADD(sc, tunnel_bytes_received, m->m_pkthdr.len);
OVPN_PEER_COUNTER_ADD(peer, pkt_in, 1);
@@ -2305,6 +2488,29 @@ ovpn_udp_input(struct mbuf *m, int off, struct inpcb *inp,
return (true);
}
+ /*
+ * If we got this from a different address than we expected tag the packet.
+ * We'll deal with notifiying userspace later, after we've decrypted and
+ * verified.
+ */
+ if (! ovpn_sockaddr_compare((struct sockaddr *)&peer->remote, sa)) {
+ struct m_tag *mt;
+ struct ovpn_mtag *ot;
+
+ MPASS(sa->sa_len <= sizeof(ot->addr));
+ mt = m_tag_get(PACKET_TAG_OVPN, sizeof(*ot), M_NOWAIT);
+ /*
+ * If we fail to allocate here we'll just try again on the next
+ * packet.
+ */
+ if (mt != NULL) {
+ ot = (struct ovpn_mtag *)(mt + 1);
+ memcpy(&ot->addr, sa, sa->sa_len);
+
+ m_tag_prepend(m, mt);
+ }
+ }
+
if (key->decrypt->cipher == OVPN_CIPHER_ALG_NONE) {
/* Now remove the outer headers */
m_adj_decap(m, sizeof(struct udphdr) + ohdrlen);
@@ -2519,6 +2725,7 @@ ovpn_clone_destroy_cb(struct epoch_context *ctx)
COUNTER_ARRAY_FREE(sc->counters, OVPN_COUNTER_SIZE);
+ rm_destroy(&sc->lock);
if_free(sc->ifp);
free(sc, M_OVPN);
}
@@ -2579,23 +2786,53 @@ vnet_ovpn_init(const void *unused __unused)
VNET_SYSINIT(vnet_ovpn_init, SI_SUB_PSEUDO, SI_ORDER_ANY,
vnet_ovpn_init, NULL);
-static void
-vnet_ovpn_uninit(const void *unused __unused)
+static int
+ovpn_prison_remove(void *obj, void *data __unused)
{
- if_clone_detach(V_ovpn_cloner);
+#ifdef VIMAGE
+ struct prison *pr;
+
+ pr = obj;
+ if (prison_owns_vnet(pr)) {
+ CURVNET_SET(pr->pr_vnet);
+ if (V_ovpn_cloner != NULL) {
+ ifc_detach_cloner(V_ovpn_cloner);
+ V_ovpn_cloner = NULL;
+ }
+ CURVNET_RESTORE();
+ }
+#endif
+ return (0);
}
-VNET_SYSUNINIT(vnet_ovpn_uninit, SI_SUB_PSEUDO, SI_ORDER_ANY,
- vnet_ovpn_uninit, NULL);
static int
ovpnmodevent(module_t mod, int type, void *data)
{
+ static int ovpn_osd_jail_slot;
+
switch (type) {
- case MOD_LOAD:
- /* Done in vnet_ovpn_init() */
+ case MOD_LOAD: {
+ /*
+ * Registration is handled in vnet_ovpn_init(), but cloned
+ * interfaces must be destroyed via PR_METHOD_REMOVE since they
+ * hold a reference to the prison via the UDP socket, which
+ * prevents the prison from being destroyed.
+ */
+ osd_method_t methods[PR_MAXMETHOD] = {
+ [PR_METHOD_REMOVE] = ovpn_prison_remove,
+ };
+ ovpn_osd_jail_slot = osd_jail_register(NULL, methods);
break;
+ }
case MOD_UNLOAD:
- /* Done in vnet_ovpn_uninit() */
+ if (ovpn_osd_jail_slot != 0)
+ osd_jail_deregister(ovpn_osd_jail_slot);
+ CURVNET_SET(vnet0);
+ if (V_ovpn_cloner != NULL) {
+ ifc_detach_cloner(V_ovpn_cloner);
+ V_ovpn_cloner = NULL;
+ }
+ CURVNET_RESTORE();
break;
default:
return (EOPNOTSUPP);