diff options
Diffstat (limited to 'sys/net/if_ovpn.c')
-rw-r--r-- | sys/net/if_ovpn.c | 321 |
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); |