Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/if_wg/module/module.c
- This file was added.
/* | |||||
* Copyright (c) 2019-2020 Netgate, Inc. | |||||
* | |||||
* 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include "opt_inet.h" | |||||
#include "opt_inet6.h" | |||||
#include <sys/param.h> | |||||
#include <sys/types.h> | |||||
#include <sys/systm.h> | |||||
#include <sys/kernel.h> | |||||
#include <sys/lock.h> | |||||
#include <sys/priv.h> | |||||
#include <sys/mutex.h> | |||||
#include <sys/mbuf.h> | |||||
#include <sys/module.h> | |||||
#include <sys/proc.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/sockio.h> | |||||
#include <sys/queue.h> | |||||
#include <sys/smp.h> | |||||
#include <net/if.h> | |||||
#include <net/ethernet.h> | |||||
#include <net/if_var.h> | |||||
#include <net/iflib.h> | |||||
#include <net/if_clone.h> | |||||
#include <net/radix.h> | |||||
#include <net/bpf.h> | |||||
#include <net/mp_ring.h> | |||||
#include "ifdi_if.h" | |||||
#include <sys/wg_module.h> | |||||
#include <crypto/zinc.h> | |||||
#include <sys/wg_noise.h> | |||||
#include <sys/if_wg_session_vars.h> | |||||
#include <sys/if_wg_session.h> | |||||
MALLOC_DEFINE(M_WG, "WG", "wireguard"); | |||||
#define WG_CAPS IFCAP_LINKSTATE | |||||
#define ph_family PH_loc.eight[5] | |||||
TASKQGROUP_DECLARE(if_io_tqg); | |||||
static int clone_count; | |||||
uma_zone_t ratelimit_zone; | |||||
void | |||||
wg_encrypt_dispatch(struct wg_softc *sc) | |||||
{ | |||||
for (int i = 0; i < mp_ncpus; i++) { | |||||
if (sc->sc_encrypt[i].gt_task.ta_flags & TASK_ENQUEUED) | |||||
continue; | |||||
GROUPTASK_ENQUEUE(&sc->sc_encrypt[i]); | |||||
} | |||||
} | |||||
void | |||||
wg_decrypt_dispatch(struct wg_softc *sc) | |||||
{ | |||||
for (int i = 0; i < mp_ncpus; i++) { | |||||
if (sc->sc_decrypt[i].gt_task.ta_flags & TASK_ENQUEUED) | |||||
continue; | |||||
GROUPTASK_ENQUEUE(&sc->sc_decrypt[i]); | |||||
} | |||||
} | |||||
static void | |||||
crypto_taskq_setup(struct wg_softc *sc) | |||||
{ | |||||
device_t dev = iflib_get_dev(sc->wg_ctx); | |||||
sc->sc_encrypt = malloc(sizeof(struct grouptask)*mp_ncpus, M_WG, M_WAITOK); | |||||
sc->sc_decrypt = malloc(sizeof(struct grouptask)*mp_ncpus, M_WG, M_WAITOK); | |||||
for (int i = 0; i < mp_ncpus; i++) { | |||||
GROUPTASK_INIT(&sc->sc_encrypt[i], 0, | |||||
(gtask_fn_t *)wg_softc_encrypt, sc); | |||||
taskqgroup_attach_cpu(qgroup_if_io_tqg, &sc->sc_encrypt[i], sc, i, dev, NULL, "wg encrypt"); | |||||
GROUPTASK_INIT(&sc->sc_decrypt[i], 0, | |||||
(gtask_fn_t *)wg_softc_decrypt, sc); | |||||
taskqgroup_attach_cpu(qgroup_if_io_tqg, &sc->sc_decrypt[i], sc, i, dev, NULL, "wg decrypt"); | |||||
} | |||||
} | |||||
static void | |||||
crypto_taskq_destroy(struct wg_softc *sc) | |||||
{ | |||||
for (int i = 0; i < mp_ncpus; i++) { | |||||
taskqgroup_detach(qgroup_if_io_tqg, &sc->sc_encrypt[i]); | |||||
taskqgroup_detach(qgroup_if_io_tqg, &sc->sc_decrypt[i]); | |||||
} | |||||
free(sc->sc_encrypt, M_WG); | |||||
free(sc->sc_decrypt, M_WG); | |||||
} | |||||
static int | |||||
wg_cloneattach(if_ctx_t ctx, struct if_clone *ifc, const char *name, caddr_t params) | |||||
{ | |||||
struct wg_softc *sc = iflib_get_softc(ctx); | |||||
if_softc_ctx_t scctx; | |||||
device_t dev; | |||||
struct iovec iov; | |||||
nvlist_t *nvl; | |||||
void *packed; | |||||
struct noise_local *local; | |||||
uint8_t public[WG_KEY_SIZE]; | |||||
struct noise_upcall noise_upcall; | |||||
int err; | |||||
uint16_t listen_port; | |||||
const void *key; | |||||
size_t size; | |||||
err = 0; | |||||
dev = iflib_get_dev(ctx); | |||||
if (params == NULL) { | |||||
key = NULL; | |||||
listen_port = 0; | |||||
nvl = NULL; | |||||
packed = NULL; | |||||
goto unpacked; | |||||
} | |||||
if (copyin(params, &iov, sizeof(iov))) | |||||
return (EFAULT); | |||||
/* check that this is reasonable */ | |||||
size = iov.iov_len; | |||||
packed = malloc(size, M_TEMP, M_WAITOK); | |||||
if (copyin(iov.iov_base, packed, size)) { | |||||
err = EFAULT; | |||||
goto out; | |||||
} | |||||
nvl = nvlist_unpack(packed, size, 0); | |||||
if (nvl == NULL) { | |||||
device_printf(dev, "%s nvlist_unpack failed\n", __func__); | |||||
err = EBADMSG; | |||||
goto out; | |||||
} | |||||
if (!nvlist_exists_number(nvl, "listen-port")) { | |||||
device_printf(dev, "%s listen-port not set\n", __func__); | |||||
err = EBADMSG; | |||||
goto nvl_out; | |||||
} | |||||
listen_port = nvlist_get_number(nvl, "listen-port"); | |||||
if (!nvlist_exists_binary(nvl, "private-key")) { | |||||
device_printf(dev, "%s private-key not set\n", __func__); | |||||
err = EBADMSG; | |||||
goto nvl_out; | |||||
} | |||||
key = nvlist_get_binary(nvl, "private-key", &size); | |||||
if (size != CURVE25519_KEY_SIZE) { | |||||
device_printf(dev, "%s bad length for private-key %zu\n", __func__, size); | |||||
err = EBADMSG; | |||||
goto nvl_out; | |||||
} | |||||
unpacked: | |||||
local = &sc->sc_local; | |||||
noise_upcall.u_arg = sc; | |||||
noise_upcall.u_remote_get = | |||||
(struct noise_remote *(*)(void *, uint8_t *))wg_remote_get; | |||||
noise_upcall.u_index_set = | |||||
(uint32_t (*)(void *, struct noise_remote *))wg_index_set; | |||||
noise_upcall.u_index_drop = | |||||
(void (*)(void *, uint32_t))wg_index_drop; | |||||
noise_local_init(local, &noise_upcall); | |||||
cookie_checker_init(&sc->sc_cookie, ratelimit_zone); | |||||
sc->sc_socket.so_port = listen_port; | |||||
if (key != NULL) { | |||||
noise_local_set_private(local, __DECONST(uint8_t *, key)); | |||||
noise_local_keys(local, public, NULL); | |||||
cookie_checker_update(&sc->sc_cookie, public); | |||||
} | |||||
atomic_add_int(&clone_count, 1); | |||||
scctx = sc->shared = iflib_get_softc_ctx(ctx); | |||||
scctx->isc_capenable = WG_CAPS; | |||||
scctx->isc_tx_csum_flags = CSUM_TCP | CSUM_UDP | CSUM_TSO | CSUM_IP6_TCP \ | |||||
| CSUM_IP6_UDP | CSUM_IP6_TCP; | |||||
sc->wg_ctx = ctx; | |||||
sc->sc_ifp = iflib_get_ifp(ctx); | |||||
mbufq_init(&sc->sc_handshake_queue, MAX_QUEUED_INCOMING_HANDSHAKES); | |||||
mtx_init(&sc->sc_mtx, NULL, "wg softc lock", MTX_DEF); | |||||
rw_init(&sc->sc_index_lock, "wg index lock"); | |||||
sc->sc_encap_ring = buf_ring_alloc(MAX_QUEUED_PACKETS, M_WG, M_WAITOK, &sc->sc_mtx); | |||||
sc->sc_decap_ring = buf_ring_alloc(MAX_QUEUED_PACKETS, M_WG, M_WAITOK, &sc->sc_mtx); | |||||
GROUPTASK_INIT(&sc->sc_handshake, 0, | |||||
(gtask_fn_t *)wg_softc_handshake_receive, sc); | |||||
taskqgroup_attach(qgroup_if_io_tqg, &sc->sc_handshake, sc, dev, NULL, "wg tx initiation"); | |||||
crypto_taskq_setup(sc); | |||||
nvl_out: | |||||
if (nvl != NULL) | |||||
nvlist_destroy(nvl); | |||||
out: | |||||
free(packed, M_TEMP); | |||||
return (err); | |||||
} | |||||
static int | |||||
wg_transmit(struct ifnet *ifp, struct mbuf *m) | |||||
{ | |||||
struct wg_softc *sc; | |||||
sa_family_t family; | |||||
struct epoch_tracker et; | |||||
struct wg_peer *peer; | |||||
struct wg_tag *t; | |||||
uint32_t af; | |||||
int rc; | |||||
/* | |||||
* Work around the raw, unbridled, steaming | |||||
* brokenness of the ipv6 mld code. | |||||
*/ | |||||
if (__predict_false(ifp->if_flags & IFF_DYING)) | |||||
return (ENXIO); | |||||
rc = 0; | |||||
sc = iflib_get_softc(ifp->if_softc); | |||||
if ((t = wg_tag_get(m)) == NULL) { | |||||
rc = ENOBUFS; | |||||
goto early_out; | |||||
} | |||||
af = m->m_pkthdr.ph_family; | |||||
BPF_MTAP2(ifp, &af, sizeof(af), m); | |||||
NET_EPOCH_ENTER(et); | |||||
peer = wg_route_lookup(&sc->sc_routes, m, OUT); | |||||
if (__predict_false(peer == NULL)) { | |||||
rc = ENOKEY; | |||||
printf("peer not found - dropping %p\n", m); | |||||
/* XXX log */ | |||||
goto err; | |||||
} | |||||
family = atomic_load_acq(peer->p_endpoint.e_remote.r_sa.sa_family); | |||||
if (__predict_false(family != AF_INET && family != AF_INET6)) { | |||||
rc = EHOSTUNREACH; | |||||
/* XXX log */ | |||||
goto err; | |||||
} | |||||
t->t_peer = peer; | |||||
t->t_mbuf = NULL; | |||||
t->t_done = 0; | |||||
t->t_mtu = ifp->if_mtu; | |||||
rc = wg_queue_out(peer, m); | |||||
if (rc == 0) | |||||
wg_encrypt_dispatch(peer->p_sc); | |||||
NET_EPOCH_EXIT(et); | |||||
return (rc); | |||||
err: | |||||
NET_EPOCH_EXIT(et); | |||||
early_out: | |||||
if_inc_counter(sc->sc_ifp, IFCOUNTER_OERRORS, 1); | |||||
/* XXX send ICMP unreachable */ | |||||
m_free(m); | |||||
return (rc); | |||||
} | |||||
static int | |||||
wg_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *sa, struct route *rt) | |||||
{ | |||||
m->m_pkthdr.ph_family = sa->sa_family; | |||||
return (wg_transmit(ifp, m)); | |||||
} | |||||
static int | |||||
wg_attach_post(if_ctx_t ctx) | |||||
{ | |||||
struct ifnet *ifp; | |||||
struct wg_softc *sc; | |||||
sc = iflib_get_softc(ctx); | |||||
ifp = iflib_get_ifp(ctx); | |||||
if_setmtu(ifp, ETHERMTU - 80); | |||||
if_setflagbits(ifp, IFF_NOARP, IFF_POINTOPOINT); | |||||
ifp->if_transmit = wg_transmit; | |||||
ifp->if_output = wg_output; | |||||
//CK_LIST_INIT(&sc->wg_peer_list); | |||||
//mtx_init(&sc->wg_socket_lock, "sock lock", NULL, MTX_DEF); | |||||
wg_hashtable_init(&sc->sc_hashtable); | |||||
sc->sc_index = hashinit(HASHTABLE_INDEX_SIZE, M_DEVBUF, &sc->sc_index_mask); | |||||
wg_route_init(&sc->sc_routes); | |||||
return (0); | |||||
} | |||||
static int | |||||
wg_mtu_set(if_ctx_t ctx, uint32_t mtu) | |||||
{ | |||||
return (0); | |||||
} | |||||
static int | |||||
wg_set_promisc(if_ctx_t ctx, int flags) | |||||
{ | |||||
return (0); | |||||
} | |||||
static int | |||||
wg_detach(if_ctx_t ctx) | |||||
{ | |||||
struct wg_softc *sc; | |||||
sc = iflib_get_softc(ctx); | |||||
if_link_state_change(sc->sc_ifp, LINK_STATE_DOWN); | |||||
NET_EPOCH_WAIT(); | |||||
wg_socket_reinit(sc, NULL, NULL); | |||||
taskqgroup_drain_all(qgroup_if_io_tqg); | |||||
pause("link_down", hz/4); | |||||
wg_peer_remove_all(sc); | |||||
mtx_destroy(&sc->sc_mtx); | |||||
rw_destroy(&sc->sc_index_lock); | |||||
taskqgroup_detach(qgroup_if_io_tqg, &sc->sc_handshake); | |||||
crypto_taskq_destroy(sc); | |||||
buf_ring_free(sc->sc_encap_ring, M_WG); | |||||
buf_ring_free(sc->sc_decap_ring, M_WG); | |||||
wg_route_destroy(&sc->sc_routes); | |||||
wg_hashtable_destroy(&sc->sc_hashtable); | |||||
atomic_add_int(&clone_count, -1); | |||||
return (0); | |||||
} | |||||
static void | |||||
wg_init(if_ctx_t ctx) | |||||
{ | |||||
struct ifnet *ifp; | |||||
struct wg_softc *sc; | |||||
//struct wg_peer *peer; | |||||
int rc; | |||||
sc = iflib_get_softc(ctx); | |||||
ifp = iflib_get_ifp(ctx); | |||||
rc = wg_socket_init(sc); | |||||
if (rc) | |||||
return; | |||||
if_link_state_change(ifp, LINK_STATE_UP); | |||||
/* | |||||
CK_STAILQ_FOREACH(&sc->wg_peer_list, ...) { | |||||
wg_pkt_staged_tx(peer); | |||||
if (peer->wp_keepalive_intvl) | |||||
wg_pkt_keepalive_tx(peer); | |||||
} | |||||
*/ | |||||
} | |||||
static void | |||||
wg_stop(if_ctx_t ctx) | |||||
{ | |||||
struct wg_softc *sc; | |||||
struct ifnet *ifp; | |||||
sc = iflib_get_softc(ctx); | |||||
ifp = iflib_get_ifp(ctx); | |||||
if_link_state_change(ifp, LINK_STATE_DOWN); | |||||
/* | |||||
CK_LIST_FOREACH(&sc->wg_peer_list, ...) { | |||||
wg_staged_pktq_purge(peer); | |||||
wg_timers_stop(peer); | |||||
wg_noise_handshake_clear(&peer->handshake); | |||||
wg_noise_keypairs_clear(&peer->keypairs); | |||||
wg_noise_reset_last_sent_handshake(&peer->last_sent_handshake); | |||||
} | |||||
*/ | |||||
//mbufq_drain(&sc->wg_rx_handshakes); | |||||
//wg_socket_reinit(sc, NULL, NULL); | |||||
} | |||||
static nvlist_t * | |||||
wg_peer_to_nvl(struct wg_peer *peer) | |||||
{ | |||||
struct wg_route *rt; | |||||
int i, count; | |||||
nvlist_t *nvl; | |||||
caddr_t key; | |||||
struct wg_allowedip *aip; | |||||
if ((nvl = nvlist_create(0)) == NULL) | |||||
return (NULL); | |||||
key = peer->p_remote.r_public; | |||||
nvlist_add_binary(nvl, "public-key", key, WG_KEY_SIZE); | |||||
nvlist_add_binary(nvl, "endpoint", &peer->p_endpoint.e_remote, sizeof(struct sockaddr)); | |||||
i = count = 0; | |||||
CK_LIST_FOREACH(rt, &peer->p_routes, r_entry) { | |||||
count++; | |||||
} | |||||
aip = malloc(count*sizeof(*aip), M_TEMP, M_WAITOK); | |||||
CK_LIST_FOREACH(rt, &peer->p_routes, r_entry) { | |||||
memcpy(&aip[i++], &rt->r_cidr, sizeof(*aip)); | |||||
} | |||||
nvlist_add_binary(nvl, "allowed-ips", aip, count*sizeof(*aip)); | |||||
free(aip, M_TEMP); | |||||
return (nvl); | |||||
} | |||||
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; | |||||
#ifdef INVARIANTS | |||||
void *packed; | |||||
size_t size; | |||||
#endif | |||||
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) { | |||||
printf("no peers found\n"); | |||||
return (ENOENT); | |||||
} | |||||
if (nvlp && (nvl = nvlist_create(0)) == NULL) | |||||
return (ENOMEM); | |||||
err = i = 0; | |||||
nvl_array = malloc(peer_count*sizeof(void*), M_TEMP, M_WAITOK); | |||||
NET_EPOCH_ENTER(et); | |||||
CK_LIST_FOREACH(peer, &sc->sc_hashtable.h_peers_list, p_entry) { | |||||
nvl_array[i] = wg_peer_to_nvl(peer); | |||||
if (nvl_array[i] == NULL) { | |||||
printf("wg_peer_to_nvl failed on %d peer\n", i); | |||||
break; | |||||
} | |||||
#ifdef INVARIANTS | |||||
packed = nvlist_pack(nvl_array[i], &size); | |||||
if (packed == NULL) { | |||||
printf("nvlist_pack(%p, %p) => %d", | |||||
nvl_array[i], &size, nvlist_error(nvl)); | |||||
} | |||||
free(packed, M_NVLIST); | |||||
#endif | |||||
i++; | |||||
if (i == peer_count) | |||||
break; | |||||
} | |||||
NET_EPOCH_EXIT(et); | |||||
*peer_countp = peer_count = i; | |||||
if (peer_count == 0) { | |||||
printf("no peers found in list\n"); | |||||
err = ENOENT; | |||||
goto out; | |||||
} | |||||
if (nvl) { | |||||
nvlist_add_nvlist_array(nvl, "peer-list", | |||||
(const nvlist_t * const *)nvl_array, peer_count); | |||||
if ((err = nvlist_error(nvl))) { | |||||
printf("nvlist_add_nvlist_array(%p, \"peer-list\", %p, %d) => %d\n", | |||||
nvl, nvl_array, peer_count, err); | |||||
goto out; | |||||
} | |||||
*nvlp = nvl; | |||||
} | |||||
*nvl_arrayp = nvl_array; | |||||
return (0); | |||||
out: | |||||
return (err); | |||||
} | |||||
#if 0 | |||||
static void | |||||
wg_marshalled_peers_free(nvlist_t *nvl, nvlist_t **nvl_array, int peer_count) | |||||
{ | |||||
if (nvl_array != NULL) { | |||||
for (int i = 0; i < peer_count; i++) | |||||
nvlist_destroy(nvl_array[i]); | |||||
free(nvl_array, M_TEMP); | |||||
} | |||||
if (nvl) | |||||
nvlist_destroy(nvl); | |||||
} | |||||
#endif | |||||
static int | |||||
wgc_get(struct wg_softc *sc, struct ifdrv *ifd) | |||||
{ | |||||
nvlist_t *nvl, **nvl_array; | |||||
void *packed; | |||||
size_t size; | |||||
int peer_count, err; | |||||
nvl = nvlist_create(0); | |||||
if (nvl == NULL) | |||||
return (ENOMEM); | |||||
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_local.l_has_identity) { | |||||
nvlist_add_binary(nvl, "public-key", sc->sc_local.l_public, WG_KEY_SIZE); | |||||
if (curthread->td_ucred->cr_uid == 0) | |||||
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); | |||||
if (err) | |||||
goto out; | |||||
nvlist_add_nvlist_array(nvl, "peer-list", | |||||
(const nvlist_t * const *)nvl_array, peer_count); | |||||
} | |||||
packed = nvlist_pack(nvl, &size); | |||||
if (packed == NULL) | |||||
return (ENOMEM); | |||||
if (ifd->ifd_len == 0) { | |||||
ifd->ifd_len = size; | |||||
goto out; | |||||
} | |||||
if (ifd->ifd_len < size) { | |||||
err = ENOSPC; | |||||
goto out; | |||||
} | |||||
if (ifd->ifd_data == NULL) { | |||||
err = EFAULT; | |||||
goto out; | |||||
} | |||||
err = copyout(packed, ifd->ifd_data, size); | |||||
ifd->ifd_len = size; | |||||
out: | |||||
//if (peer_count) | |||||
// wg_marshalled_peers_free(nvl_peers, nvl_array, peer_count); | |||||
nvlist_destroy(nvl); | |||||
free(packed, M_NVLIST); | |||||
return (err); | |||||
} | |||||
static bool | |||||
wg_allowedip_valid(const struct wg_allowedip *wip) | |||||
{ | |||||
return (true); | |||||
} | |||||
static int | |||||
wg_peer_add(struct wg_softc *sc, const nvlist_t *nvl) | |||||
{ | |||||
uint8_t public[WG_KEY_SIZE]; | |||||
const void *pub_key; | |||||
const struct sockaddr *endpoint; | |||||
int i, err, allowedip_count; | |||||
device_t dev; | |||||
size_t size; | |||||
struct wg_peer *peer = NULL; | |||||
bool need_insert = false; | |||||
dev = iflib_get_dev(sc->wg_ctx); | |||||
if (!nvlist_exists_binary(nvl, "public-key")) { | |||||
device_printf(dev, "peer has no public-key\n"); | |||||
return (EINVAL); | |||||
} | |||||
pub_key = nvlist_get_binary(nvl, "public-key", &size); | |||||
if (size != CURVE25519_KEY_SIZE) { | |||||
device_printf(dev, "%s bad length for public-key %zu\n", __func__, size); | |||||
return (EINVAL); | |||||
} | |||||
if (noise_local_keys(&sc->sc_local, public, NULL) == 0 && | |||||
bcmp(public, pub_key, WG_KEY_SIZE) == 0) { | |||||
device_printf(dev, "public-key for peer already in use by host\n"); | |||||
return (EINVAL); | |||||
} | |||||
peer = wg_peer_lookup(sc, pub_key); | |||||
if (nvlist_exists_bool(nvl, "peer-remove") && | |||||
nvlist_get_bool(nvl, "peer-remove")) { | |||||
if (peer != NULL) { | |||||
wg_hashtable_peer_remove(&sc->sc_hashtable, peer); | |||||
wg_peer_destroy(peer); | |||||
/* XXX free */ | |||||
printf("peer removed\n"); | |||||
} | |||||
return (0); | |||||
} | |||||
if (nvlist_exists_bool(nvl, "replace-allowedips") && | |||||
nvlist_get_bool(nvl, "replace-allowedips") && | |||||
peer != NULL) { | |||||
wg_route_delete(&peer->p_sc->sc_routes, peer); | |||||
} | |||||
if (peer == NULL) { | |||||
need_insert = true; | |||||
peer = wg_peer_alloc(sc); | |||||
noise_remote_init(&peer->p_remote, pub_key, &sc->sc_local); | |||||
cookie_maker_init(&peer->p_cookie, pub_key); | |||||
} | |||||
if (nvlist_exists_binary(nvl, "endpoint")) { | |||||
endpoint = nvlist_get_binary(nvl, "endpoint", &size); | |||||
if (size != sizeof(*endpoint)) { | |||||
device_printf(dev, "%s bad length for endpoint %zu\n", __func__, size); | |||||
err = EBADMSG; | |||||
goto out; | |||||
} | |||||
memcpy(&peer->p_endpoint.e_remote, endpoint, | |||||
sizeof(peer->p_endpoint.e_remote)); | |||||
} | |||||
if (nvlist_exists_binary(nvl, "pre-shared-key")) { | |||||
const void *key; | |||||
key = nvlist_get_binary(nvl, "pre-shared-key", &size); | |||||
noise_remote_set_psk(&peer->p_remote, key); | |||||
} | |||||
if (nvlist_exists_number(nvl, "persistent-keepalive-interval")) { | |||||
uint16_t pki; | |||||
pki = nvlist_get_number(nvl, "persistent-keepalive-interval"); | |||||
wg_timers_set_persistent_keepalive(&peer->p_timers, pki); | |||||
} | |||||
if (nvlist_exists_binary(nvl, "allowed-ips")) { | |||||
const struct wg_allowedip *aip, *aip_base; | |||||
aip = aip_base = nvlist_get_binary(nvl, "allowed-ips", &size); | |||||
if (size % sizeof(struct wg_allowedip) != 0) { | |||||
device_printf(dev, "%s bad length for allowed-ips %zu not integer multiple of struct size\n", __func__, size); | |||||
err = EBADMSG; | |||||
goto out; | |||||
} | |||||
allowedip_count = size/sizeof(struct wg_allowedip); | |||||
for (i = 0; i < allowedip_count; i++) { | |||||
if (!wg_allowedip_valid(&aip_base[i])) { | |||||
device_printf(dev, "%s allowedip %d not valid\n", __func__, i); | |||||
err = EBADMSG; | |||||
goto out; | |||||
} | |||||
} | |||||
for (int i = 0; i < allowedip_count; i++, aip++) { | |||||
if ((err = wg_route_add(&sc->sc_routes, peer, aip)) != 0) { | |||||
printf("route add %d failed -> %d\n", i, err); | |||||
} | |||||
} | |||||
} | |||||
if (need_insert) | |||||
wg_hashtable_peer_insert(&sc->sc_hashtable, peer); | |||||
return (0); | |||||
out: | |||||
wg_peer_destroy(peer); | |||||
return (err); | |||||
} | |||||
static int | |||||
wgc_set(struct wg_softc *sc, struct ifdrv *ifd) | |||||
{ | |||||
uint8_t public[WG_KEY_SIZE]; | |||||
void *nvlpacked; | |||||
nvlist_t *nvl; | |||||
device_t dev; | |||||
ssize_t size; | |||||
int err; | |||||
if (ifd->ifd_len == 0 || ifd->ifd_data == NULL) | |||||
return (EFAULT); | |||||
dev = iflib_get_dev(sc->wg_ctx); | |||||
nvlpacked = malloc(ifd->ifd_len, M_TEMP, M_WAITOK); | |||||
err = copyin(ifd->ifd_data, nvlpacked, ifd->ifd_len); | |||||
if (err) | |||||
goto out; | |||||
nvl = nvlist_unpack(nvlpacked, ifd->ifd_len, 0); | |||||
if (nvl == NULL) { | |||||
device_printf(dev, "%s nvlist_unpack failed\n", __func__); | |||||
err = EBADMSG; | |||||
goto out; | |||||
} | |||||
if (nvlist_exists_bool(nvl, "replace-peers") && | |||||
nvlist_get_bool(nvl, "replace-peers")) | |||||
wg_peer_remove_all(sc); | |||||
if (nvlist_exists_number(nvl, "listen-port")) { | |||||
int listen_port __unused = nvlist_get_number(nvl, "listen-port"); | |||||
/* | |||||
* Set listen port | |||||
*/ | |||||
if_link_state_change(sc->sc_ifp, LINK_STATE_DOWN); | |||||
pause("link_down", hz/4); | |||||
wg_socket_reinit(sc, NULL, NULL); | |||||
sc->sc_socket.so_port = listen_port; | |||||
if ((err = wg_socket_init(sc)) != 0) | |||||
goto out; | |||||
if_link_state_change(sc->sc_ifp, LINK_STATE_UP); | |||||
} | |||||
if (nvlist_exists_binary(nvl, "private-key")) { | |||||
struct noise_local *local; | |||||
const void *key = nvlist_get_binary(nvl, "private-key", &size); | |||||
if (size != CURVE25519_KEY_SIZE) { | |||||
device_printf(dev, "%s bad length for private-key %zu\n", __func__, size); | |||||
err = EBADMSG; | |||||
goto nvl_out; | |||||
} | |||||
/* | |||||
* set private key | |||||
*/ | |||||
local = &sc->sc_local; | |||||
noise_local_set_private(local, __DECONST(uint8_t *, key)); | |||||
noise_local_keys(local, public, NULL); | |||||
cookie_checker_update(&sc->sc_cookie, public); | |||||
} | |||||
if (nvlist_exists_number(nvl, "user-cookie")) { | |||||
sc->sc_user_cookie = nvlist_get_number(nvl, "user-cookie"); | |||||
/* | |||||
* setsockopt | |||||
*/ | |||||
} | |||||
if (nvlist_exists_nvlist_array(nvl, "peer-list")) { | |||||
size_t peercount; | |||||
const nvlist_t * const*nvl_peers; | |||||
nvl_peers = nvlist_get_nvlist_array(nvl, "peer-list", &peercount); | |||||
for (int i = 0; i < peercount; i++) { | |||||
wg_peer_add(sc, nvl_peers[i]); | |||||
} | |||||
} | |||||
nvl_out: | |||||
nvlist_destroy(nvl); | |||||
out: | |||||
free(nvlpacked, M_TEMP); | |||||
return (err); | |||||
} | |||||
static int | |||||
wg_priv_ioctl(if_ctx_t ctx, u_long command, caddr_t data) | |||||
{ | |||||
struct wg_softc *sc = iflib_get_softc(ctx); | |||||
struct ifdrv *ifd = (struct ifdrv *)data; | |||||
int ifd_cmd; | |||||
switch (command) { | |||||
case SIOCGDRVSPEC: | |||||
case SIOCSDRVSPEC: | |||||
ifd_cmd = ifd->ifd_cmd; | |||||
break; | |||||
default: | |||||
return (EINVAL); | |||||
} | |||||
switch (ifd_cmd) { | |||||
case WGC_GET: | |||||
return (wgc_get(sc, ifd)); | |||||
break; | |||||
case WGC_SET: | |||||
if (priv_check(curthread, PRIV_NET_HWIOCTL)) | |||||
return (EPERM); | |||||
return (wgc_set(sc, ifd)); | |||||
break; | |||||
} | |||||
return (ENOTSUP); | |||||
} | |||||
static device_method_t wg_if_methods[] = { | |||||
DEVMETHOD(ifdi_cloneattach, wg_cloneattach), | |||||
DEVMETHOD(ifdi_attach_post, wg_attach_post), | |||||
DEVMETHOD(ifdi_detach, wg_detach), | |||||
DEVMETHOD(ifdi_init, wg_init), | |||||
DEVMETHOD(ifdi_stop, wg_stop), | |||||
DEVMETHOD(ifdi_priv_ioctl, wg_priv_ioctl), | |||||
DEVMETHOD(ifdi_mtu_set, wg_mtu_set), | |||||
DEVMETHOD(ifdi_promisc_set, wg_set_promisc), | |||||
DEVMETHOD_END | |||||
}; | |||||
static driver_t wg_iflib_driver = { | |||||
"wg", wg_if_methods, sizeof(struct wg_softc) | |||||
}; | |||||
char wg_driver_version[] = "0.0.1"; | |||||
static struct if_shared_ctx wg_sctx_init = { | |||||
.isc_magic = IFLIB_MAGIC, | |||||
.isc_driver_version = wg_driver_version, | |||||
.isc_driver = &wg_iflib_driver, | |||||
.isc_flags = IFLIB_PSEUDO, | |||||
.isc_name = "wg", | |||||
}; | |||||
if_shared_ctx_t wg_sctx = &wg_sctx_init; | |||||
static if_pseudo_t wg_pseudo; | |||||
int | |||||
wg_ctx_init(void) | |||||
{ | |||||
ratelimit_zone = uma_zcreate("wg ratelimit", sizeof(struct ratelimit), | |||||
NULL, NULL, NULL, NULL, 0, 0); | |||||
return (0); | |||||
} | |||||
void | |||||
wg_ctx_uninit(void) | |||||
{ | |||||
uma_zdestroy(ratelimit_zone); | |||||
} | |||||
static int | |||||
wg_module_init(void) | |||||
{ | |||||
int rc; | |||||
if ((rc = wg_ctx_init())) | |||||
return (rc); | |||||
wg_pseudo = iflib_clone_register(wg_sctx); | |||||
if (wg_pseudo == NULL) | |||||
return (ENXIO); | |||||
return (0); | |||||
} | |||||
static void | |||||
wg_module_deinit(void) | |||||
{ | |||||
wg_ctx_uninit(); | |||||
iflib_clone_deregister(wg_pseudo); | |||||
} | |||||
static int | |||||
wg_module_event_handler(module_t mod, int what, void *arg) | |||||
{ | |||||
int err; | |||||
switch (what) { | |||||
case MOD_LOAD: | |||||
if ((err = wg_module_init()) != 0) | |||||
return (err); | |||||
break; | |||||
case MOD_UNLOAD: | |||||
if (clone_count == 0) | |||||
wg_module_deinit(); | |||||
else | |||||
return (EBUSY); | |||||
break; | |||||
default: | |||||
return (EOPNOTSUPP); | |||||
} | |||||
return (0); | |||||
} | |||||
static moduledata_t wg_moduledata = { | |||||
"wg", | |||||
wg_module_event_handler, | |||||
NULL | |||||
}; | |||||
DECLARE_MODULE(wg, wg_moduledata, SI_SUB_PSEUDO, SI_ORDER_ANY); | |||||
MODULE_VERSION(wg, 1); | |||||
MODULE_DEPEND(wg, iflib, 1, 1, 1); | |||||
MODULE_DEPEND(wg, blake2, 1, 1, 1); | |||||
MODULE_DEPEND(wg, crypto, 1, 1, 1); |