From 0687984e4493465e3ef3b648b0b40a5270c0e95f Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 13 Apr 2021 18:58:51 -0600 Subject: if_wg: remove peer marshalling from get request Signed-off-by: Jason A. Donenfeld --- TODO.md | 5 - src/if_wg.c | 401 ++++++++++++++---------------------------------------------- 2 files changed, 94 insertions(+), 312 deletions(-) diff --git a/TODO.md b/TODO.md index 377ac8b..b525083 100644 --- a/TODO.md +++ b/TODO.md @@ -2,11 +2,6 @@ - Finish porting [this script](https://git.zx2c4.com/wireguard-linux/tree/tools/testing/selftests/wireguard/netns.sh) to `./tests/netns.sh` using vnets and epairs. -- Marshall peers directly to nvlists without going through intermediate - structures (`wg_peer_export`) and the like. This means grappling with the use - of epoch there, and either making nvl allocations not sleep, or doing nvl - allocations outside of epoch critical sections, or possibly deciding epoch - doesn't actually matter there because we're holding an sx. - Rework locking and epoch lifetimes; come up with consistent set of rules. - Chop off padding on rx after verifying lengths, so that tcpdump doesn't see zeros. diff --git a/src/if_wg.c b/src/if_wg.c index 7d8c0eb..cdd61be 100644 --- a/src/if_wg.c +++ b/src/if_wg.c @@ -359,19 +359,6 @@ struct wg_timespec64 { uint64_t tv_nsec; }; -struct wg_peer_export { - struct sockaddr_storage endpoint; - struct timespec last_handshake; - uint8_t public_key[WG_KEY_SIZE]; - uint8_t preshared_key[NOISE_SYMMETRIC_KEY_LEN]; - size_t endpoint_sz; - struct wg_allowedip *aip; - uint64_t rx_bytes; - uint64_t tx_bytes; - int aip_count; - uint16_t persistent_keepalive; -}; - static struct wg_tag *wg_tag_get(struct mbuf *); static struct wg_endpoint *wg_mbuf_endpoint_get(struct mbuf *); static int wg_socket_init(struct wg_softc *, in_port_t); @@ -403,7 +390,7 @@ static void wg_timers_init(struct wg_timers *); static void wg_timers_enable(struct wg_timers *); static void wg_timers_disable(struct wg_timers *); static void wg_timers_set_persistent_keepalive(struct wg_timers *, uint16_t); -static void wg_timers_get_last_handshake(struct wg_timers *, struct timespec *); +static void wg_timers_get_last_handshake(struct wg_timers *, struct wg_timespec64 *); static int wg_timers_expired_handshake_last_sent(struct wg_timers *); static int wg_timers_check_handshake_last_sent(struct wg_timers *); static void wg_queue_init(struct wg_queue *, const char *); @@ -464,7 +451,6 @@ static void wg_qflush(struct ifnet *); static int wg_transmit(struct ifnet *, struct mbuf *); static int wg_output(struct ifnet *, struct mbuf *, const struct sockaddr *, struct route *); static void wg_clone_destroy(struct ifnet *); -static int wg_peer_to_export(struct wg_peer *, struct wg_peer_export *); static bool wgc_privileged(struct wg_softc *); static int wgc_get(struct wg_softc *, struct wg_data_io *); static int wgc_set(struct wg_softc *, struct wg_data_io *); @@ -1275,7 +1261,7 @@ wg_timers_set_persistent_keepalive(struct wg_timers *t, uint16_t interval) } static void -wg_timers_get_last_handshake(struct wg_timers *t, struct timespec *time) +wg_timers_get_last_handshake(struct wg_timers *t, struct wg_timespec64 *time) { rw_rlock(&t->t_lock); time->tv_sec = t->t_handshake_complete.tv_sec; @@ -2771,297 +2757,30 @@ wgc_set(struct wg_softc *sc, struct wg_data_io *wgd) nvlist_destroy(nvl); out: + explicit_bzero(nvlpacked, wgd->wgd_size); free(nvlpacked, M_TEMP); sx_xunlock(&sc->sc_lock); return (err); } -static unsigned int -in_mask2len(struct in_addr *mask) -{ - unsigned int x, y; - uint8_t *p; - - p = (uint8_t *)mask; - for (x = 0; x < sizeof(*mask); x++) { - if (p[x] != 0xff) - break; - } - y = 0; - if (x < sizeof(*mask)) { - for (y = 0; y < NBBY; y++) { - if ((p[x] & (0x80 >> y)) == 0) - break; - } - } - return x * NBBY + y; -} - static int -wg_peer_to_export(struct wg_peer *peer, struct wg_peer_export *exp) -{ - struct wg_endpoint *ep; - struct wg_aip *rt; - struct noise_remote *remote; - int i; - - /* Non-sleepable context. */ - NET_EPOCH_ASSERT(); - - bzero(&exp->endpoint, sizeof(exp->endpoint)); - remote = &peer->p_remote; - ep = &peer->p_endpoint; - if (ep->e_remote.r_sa.sa_family != 0) { - exp->endpoint_sz = (ep->e_remote.r_sa.sa_family == AF_INET) ? - sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); - - memcpy(&exp->endpoint, &ep->e_remote, exp->endpoint_sz); - } - - /* We always export it. */ - (void)noise_remote_keys(remote, exp->public_key, exp->preshared_key); - exp->persistent_keepalive = - peer->p_timers.t_persistent_keepalive_interval; - wg_timers_get_last_handshake(&peer->p_timers, &exp->last_handshake); - exp->rx_bytes = counter_u64_fetch(peer->p_rx_bytes); - exp->tx_bytes = counter_u64_fetch(peer->p_tx_bytes); - - exp->aip_count = 0; - CK_LIST_FOREACH(rt, &peer->p_aips, r_entry) { - exp->aip_count++; - } - - /* Early success; no allowed-ips to copy out. */ - if (exp->aip_count == 0) - return (0); - - exp->aip = mallocarray(exp->aip_count, sizeof(*exp->aip), M_TEMP, M_NOWAIT); - if (exp->aip == NULL) - return (ENOMEM); - - i = 0; - CK_LIST_FOREACH(rt, &peer->p_aips, r_entry) { - exp->aip[i].family = rt->r_addr.ss_family; - if (exp->aip[i].family == AF_INET) { - struct sockaddr_in *sin = - (struct sockaddr_in *)&rt->r_addr; - - exp->aip[i].ip4 = sin->sin_addr; - - sin = (struct sockaddr_in *)&rt->r_mask; - exp->aip[i].cidr = in_mask2len(&sin->sin_addr); - } else if (exp->aip[i].family == AF_INET6) { - struct sockaddr_in6 *sin6 = - (struct sockaddr_in6 *)&rt->r_addr; - - exp->aip[i].ip6 = sin6->sin6_addr; - - sin6 = (struct sockaddr_in6 *)&rt->r_mask; - exp->aip[i].cidr = in6_mask2len(&sin6->sin6_addr, NULL); - } - i++; - if (i == exp->aip_count) - break; - } - - /* Again, AllowedIPs might have shrank; update it. */ - exp->aip_count = i; - - return (0); -} - -static nvlist_t * -wg_peer_export_to_nvl(struct wg_softc *sc, struct wg_peer_export *exp) +wgc_get(struct wg_softc *sc, struct wg_data_io *wgd) { + uint8_t public_key[WG_KEY_SIZE] = { 0 }, preshared_key[NOISE_SYMMETRIC_KEY_LEN] = { 0 }; + nvlist_t *nvl, *nvl_peer, *nvl_aip, **nvl_peers, **nvl_aips; + size_t size, peer_count, aip_count, i, j; struct wg_timespec64 ts64; - nvlist_t *nvl, **nvl_aips; - size_t i; - uint16_t family; - - nvl_aips = NULL; - if ((nvl = nvlist_create(0)) == NULL) - return (NULL); - - nvlist_add_binary(nvl, "public-key", exp->public_key, - sizeof(exp->public_key)); - if (wgc_privileged(sc)) - nvlist_add_binary(nvl, "preshared-key", exp->preshared_key, - sizeof(exp->preshared_key)); - if (exp->endpoint_sz != 0) - nvlist_add_binary(nvl, "endpoint", &exp->endpoint, - exp->endpoint_sz); - - if (exp->aip_count != 0) { - nvl_aips = mallocarray(exp->aip_count, sizeof(*nvl_aips), - M_WG, M_WAITOK | M_ZERO); - } - - for (i = 0; i < exp->aip_count; i++) { - nvl_aips[i] = nvlist_create(0); - if (nvl_aips[i] == NULL) - goto err; - family = exp->aip[i].family; - nvlist_add_number(nvl_aips[i], "cidr", exp->aip[i].cidr); - if (family == AF_INET) - nvlist_add_binary(nvl_aips[i], "ipv4", - &exp->aip[i].ip4, sizeof(exp->aip[i].ip4)); - else if (family == AF_INET6) - nvlist_add_binary(nvl_aips[i], "ipv6", - &exp->aip[i].ip6, sizeof(exp->aip[i].ip6)); - } - - if (i != 0) { - nvlist_add_nvlist_array(nvl, "allowed-ips", - (const nvlist_t *const *)nvl_aips, i); - } - - for (i = 0; i < exp->aip_count; ++i) - nvlist_destroy(nvl_aips[i]); - - free(nvl_aips, M_WG); - nvl_aips = NULL; - - ts64.tv_sec = exp->last_handshake.tv_sec; - ts64.tv_nsec = exp->last_handshake.tv_nsec; - nvlist_add_binary(nvl, "last-handshake-time", &ts64, sizeof(ts64)); - - if (exp->persistent_keepalive != 0) - nvlist_add_number(nvl, "persistent-keepalive-interval", - exp->persistent_keepalive); - - if (exp->rx_bytes != 0) - nvlist_add_number(nvl, "rx-bytes", exp->rx_bytes); - if (exp->tx_bytes != 0) - nvlist_add_number(nvl, "tx-bytes", exp->tx_bytes); - - return (nvl); -err: - for (i = 0; i < exp->aip_count && nvl_aips[i] != NULL; i++) { - nvlist_destroy(nvl_aips[i]); - } - - free(nvl_aips, M_WG); - nvlist_destroy(nvl); - return (NULL); -} - -static int -wg_marshal_peers(struct wg_softc *sc, nvlist_t **nvlp, nvlist_t ***nvl_arrayp, int *peer_countp) -{ struct wg_peer *peer; - int err, i, peer_count; - nvlist_t *nvl, **nvl_array; - struct epoch_tracker et; - struct wg_peer_export *wpe; - - nvl = NULL; - nvl_array = NULL; - if (nvl_arrayp) - *nvl_arrayp = NULL; - if (nvlp) - *nvlp = NULL; - if (peer_countp) - *peer_countp = 0; - peer_count = sc->sc_hashtable.h_num_peers; - if (peer_count == 0) { - return (ENOENT); - } - - if (nvlp && (nvl = nvlist_create(0)) == NULL) - return (ENOMEM); - - err = i = 0; - nvl_array = mallocarray(peer_count, sizeof(void *), M_TEMP, M_WAITOK | M_ZERO); - wpe = mallocarray(peer_count, sizeof(*wpe), M_TEMP, M_WAITOK | M_ZERO); - - NET_EPOCH_ENTER(et); - CK_LIST_FOREACH(peer, &sc->sc_hashtable.h_peers_list, p_entry) { - if ((err = wg_peer_to_export(peer, &wpe[i])) != 0) { - break; - } - - i++; - if (i == peer_count) - break; - } - NET_EPOCH_EXIT(et); - - if (err != 0) - goto out; - - /* Update the peer count, in case we found fewer entries. */ - *peer_countp = peer_count = i; - if (peer_count == 0) { - err = ENOENT; - goto out; - } - - for (i = 0; i < peer_count; i++) { - int idx; - - /* - * Peers are added to the list in reverse order, effectively, - * because it's simpler/quicker to add at the head every time. - * - * Export them in reverse order. No worries if we fail mid-way - * through, the cleanup below will DTRT. - */ - idx = peer_count - i - 1; - nvl_array[idx] = wg_peer_export_to_nvl(sc, &wpe[i]); - if (nvl_array[idx] == NULL) { - break; - } - } - - if (i < peer_count) { - /* Error! */ - *peer_countp = 0; - err = ENOMEM; - } else if (nvl) { - nvlist_add_nvlist_array(nvl, "peers", - (const nvlist_t * const *)nvl_array, peer_count); - if ((err = nvlist_error(nvl))) { - goto out; - } - *nvlp = nvl; - } - *nvl_arrayp = nvl_array; - out: - if (err != 0) { - if (nvl_array) { - /* Note that nvl_array is populated in reverse order. */ - for (i = 0; i < peer_count; i++) { - nvlist_destroy(nvl_array[i]); - } - free(nvl_array, M_TEMP); - } - if (nvl) - nvlist_destroy(nvl); - } - if (wpe) { - for (i = 0; i < peer_count; i++) - free(wpe[i].aip, M_TEMP); - free(wpe, M_TEMP); - } - return (err); -} - -static int -wgc_get(struct wg_softc *sc, struct wg_data_io *wgd) -{ - nvlist_t *nvl, **nvl_array; + struct wg_aip *rt; void *packed; - size_t size; - int peer_count, err; + int err = 0; nvl = nvlist_create(0); - if (nvl == NULL) - return (ENOMEM); + if (!nvl) + return ENOMEM; sx_slock(&sc->sc_lock); - err = 0; - packed = NULL; if (sc->sc_socket.so_port != 0) nvlist_add_number(nvl, "listen-port", sc->sc_socket.so_port); if (sc->sc_socket.so_user_cookie != 0) @@ -3071,39 +2790,107 @@ wgc_get(struct wg_softc *sc, struct wg_data_io *wgd) if (wgc_privileged(sc)) nvlist_add_binary(nvl, "private-key", sc->sc_local.l_private, WG_KEY_SIZE); } - if (sc->sc_hashtable.h_num_peers > 0) { - err = wg_marshal_peers(sc, NULL, &nvl_array, &peer_count); + peer_count = sc->sc_hashtable.h_num_peers; + if (peer_count) { + nvl_peers = mallocarray(peer_count, sizeof(void *), M_NVLIST, M_WAITOK | M_ZERO); + i = 0; + CK_LIST_FOREACH(peer, &sc->sc_hashtable.h_peers_list, p_entry) { + if (i >= peer_count) + panic("peers changed from under us"); + + nvl_peers[i++] = nvl_peer = nvlist_create(0); + if (!nvl_peer) { + err = ENOMEM; + goto err_peer; + } + + (void)noise_remote_keys(&peer->p_remote, public_key, preshared_key); + nvlist_add_binary(nvl_peer, "public-key", public_key, sizeof(public_key)); + if (wgc_privileged(sc)) + nvlist_add_binary(nvl_peer, "preshared-key", preshared_key, sizeof(preshared_key)); + explicit_bzero(preshared_key, sizeof(preshared_key)); + if (peer->p_endpoint.e_remote.r_sa.sa_family == AF_INET) + nvlist_add_binary(nvl_peer, "endpoint", &peer->p_endpoint.e_remote, sizeof(struct sockaddr_in)); + else if (peer->p_endpoint.e_remote.r_sa.sa_family == AF_INET6) + nvlist_add_binary(nvl_peer, "endpoint", &peer->p_endpoint.e_remote, sizeof(struct sockaddr_in6)); + wg_timers_get_last_handshake(&peer->p_timers, &ts64); + nvlist_add_binary(nvl_peer, "last-handshake-time", &ts64, sizeof(ts64)); + nvlist_add_number(nvl_peer, "persistent-keepalive-interval", peer->p_timers.t_persistent_keepalive_interval); + nvlist_add_number(nvl_peer, "rx-bytes", counter_u64_fetch(peer->p_rx_bytes)); + nvlist_add_number(nvl_peer, "tx-bytes", counter_u64_fetch(peer->p_tx_bytes)); + + aip_count = 0; + CK_LIST_FOREACH(rt, &peer->p_aips, r_entry) { + ++aip_count; + } + if (aip_count) { + nvl_aips = mallocarray(aip_count, sizeof(void *), M_NVLIST, M_WAITOK | M_ZERO); + j = 0; + CK_LIST_FOREACH(rt, &peer->p_aips, r_entry) { + if (j >= aip_count) + panic("aips changed from under us"); + + nvl_aips[j++] = nvl_aip = nvlist_create(0); + if (!nvl_aip) { + err = ENOMEM; + goto err_aip; + } + if (rt->r_addr.ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)&rt->r_addr; + nvlist_add_binary(nvl_aip, "ipv4", &sin->sin_addr, sizeof(sin->sin_addr)); + sin = (struct sockaddr_in *)&rt->r_mask; + nvlist_add_number(nvl_aip, "cidr", __builtin_popcount(sin->sin_addr.s_addr)); + } else if (rt->r_addr.ss_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&rt->r_addr; + nvlist_add_binary(nvl_aip, "ipv6", &sin6->sin6_addr, sizeof(sin6->sin6_addr)); + sin6 = (struct sockaddr_in6 *)&rt->r_mask; + nvlist_add_number(nvl_aip, "cidr", in6_mask2len(&sin6->sin6_addr, NULL)); + } + } + nvlist_add_nvlist_array(nvl_peer, "allowed-ips", (const nvlist_t *const *)nvl_aips, aip_count); + err_aip: + for (j = 0; j < aip_count; ++j) + nvlist_destroy(nvl_aips[j]); + free(nvl_aips, M_NVLIST); + if (err) + goto err_peer; + } + } + nvlist_add_nvlist_array(nvl, "peers", (const nvlist_t * const *)nvl_peers, peer_count); + err_peer: + for (i = 0; i < peer_count; ++i) + nvlist_destroy(nvl_peers[i]); + free(nvl_peers, M_NVLIST); if (err) - goto out_nvl; - nvlist_add_nvlist_array(nvl, "peers", - (const nvlist_t * const *)nvl_array, peer_count); + goto err; } packed = nvlist_pack(nvl, &size); - if (packed == NULL) { + if (!packed) { err = ENOMEM; - goto out_nvl; + goto err; } - if (wgd->wgd_size == 0) { + if (!wgd->wgd_size) { wgd->wgd_size = size; - goto out_packed; + goto out; } if (wgd->wgd_size < size) { err = ENOSPC; - goto out_packed; + goto out; } - if (wgd->wgd_data == NULL) { + if (!wgd->wgd_data) { err = EFAULT; - goto out_packed; + goto out; } err = copyout(packed, wgd->wgd_data, size); wgd->wgd_size = size; -out_packed: +out: + explicit_bzero(packed, size); free(packed, M_NVLIST); -out_nvl: +err: nvlist_destroy(nvl); sx_sunlock(&sc->sc_lock); - return (err); + return err; } static int -- cgit v1.2.3-59-g8ed1b