summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
Diffstat (limited to 'sys')
-rw-r--r--sys/net/if_pair.c28
-rw-r--r--sys/net/if_vlan.c17
-rw-r--r--sys/net/if_wg.c1991
-rw-r--r--sys/net/if_wg.h158
4 files changed, 2167 insertions, 27 deletions
diff --git a/sys/net/if_pair.c b/sys/net/if_pair.c
index 1a6684c4607..4df0527e3be 100644
--- a/sys/net/if_pair.c
+++ b/sys/net/if_pair.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_pair.c,v 1.11 2018/01/09 15:24:24 bluhm Exp $ */
+/* $OpenBSD: if_pair.c,v 1.12 2019/04/27 05:14:33 dlg Exp $ */
/*
* Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
@@ -167,29 +167,25 @@ pairstart(struct ifnet *ifp)
struct mbuf *m;
pairedifp = if_get(sc->sc_pairedif);
+ if (pairedifp == NULL) {
+ ifq_purge(&ifp->if_snd);
+ return;
+ }
- for (;;) {
- IFQ_DEQUEUE(&ifp->if_snd, m);
- if (m == NULL)
- break;
-
+ while ((m = ifq_dequeue(&ifp->if_snd)) != NULL) {
#if NBPFILTER > 0
if (ifp->if_bpf)
bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
#endif /* NBPFILTER > 0 */
- if (pairedifp != NULL) {
- if (m->m_flags & M_PKTHDR)
- m_resethdr(m);
- ml_enqueue(&ml, m);
- } else
- m_freem(m);
- }
+ if (m->m_flags & M_PKTHDR)
+ m_resethdr(m);
- if (pairedifp != NULL) {
- if_input(pairedifp, &ml);
- if_put(pairedifp);
+ ml_enqueue(&ml, m);
}
+
+ if_input(pairedifp, &ml);
+ if_put(pairedifp);
}
int
diff --git a/sys/net/if_vlan.c b/sys/net/if_vlan.c
index c4db8df5b27..48b7a091403 100644
--- a/sys/net/if_vlan.c
+++ b/sys/net/if_vlan.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_vlan.c,v 1.191 2019/04/27 05:11:50 dlg Exp $ */
+/* $OpenBSD: if_vlan.c,v 1.192 2019/04/27 05:14:33 dlg Exp $ */
/*
* Copyright 1998 Massachusetts Institute of Technology
@@ -84,14 +84,13 @@ struct vlan_mc_entry {
struct vlan_softc {
struct arpcom ifv_ac;
+#define ifv_if ifv_ac.ac_if
unsigned int ifv_ifidx0; /* parent interface */
int ifv_rxprio;
- struct ifv_linkmib {
- int ifvm_prio;
- u_int16_t ifvm_proto; /* encapsulation ethertype */
- u_int16_t ifvm_tag;
- u_int16_t ifvm_type; /* non-standard ethertype or 0x8100 */
- } ifv_mib;
+ int ifv_prio;
+ u_int16_t ifv_proto; /* encapsulation ethertype */
+ u_int16_t ifv_tag;
+ u_int16_t ifv_type; /* non-standard ethertype or 0x8100 */
LIST_HEAD(__vlan_mchead, vlan_mc_entry)
vlan_mc_listhead;
SRPL_ENTRY(vlan_softc) ifv_list;
@@ -102,10 +101,6 @@ struct vlan_softc {
struct ifih *ifv_ifih;
};
-#define ifv_if ifv_ac.ac_if
-#define ifv_tag ifv_mib.ifvm_tag
-#define ifv_prio ifv_mib.ifvm_prio
-#define ifv_type ifv_mib.ifvm_type
#define IFVF_PROMISC 0x01 /* the parent should be made promisc */
#define IFVF_LLADDR 0x02 /* don't inherit the parents mac */
diff --git a/sys/net/if_wg.c b/sys/net/if_wg.c
new file mode 100644
index 00000000000..4f491c7d799
--- /dev/null
+++ b/sys/net/if_wg.c
@@ -0,0 +1,1991 @@
+/* $OpenBSD: if_wg.c,v 1.1 2019/04/27 05:14:33 dlg Exp $ */
+
+/*
+ * Copyright (c) 2018, 2019 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+#include <sys/mbuf.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+#include <sys/syslog.h>
+#include <sys/selinfo.h>
+#include <sys/fcntl.h>
+#include <sys/time.h>
+#include <sys/device.h>
+#include <sys/vnode.h>
+#include <sys/poll.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/filedesc.h>
+#include <sys/socketvar.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_types.h>
+#include <net/rtable.h>
+#include <netinet/in.h>
+
+#include <netinet/ip_var.h>
+#include <netinet/udp.h>
+#include <netinet/udp_var.h>
+
+#include <netinet/ip.h>
+#include <net/route.h>
+#include <netinet/in_pcb.h>
+
+#include "bpfilter.h"
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#include <crypto/chacha.h>
+#include <crypto/poly1305.h>
+
+#include <net/if_wg.h>
+
+struct wg_data_hdr {
+ uint32_t msg_type;
+ uint32_t index;
+ uint32_t counter_lo;
+ uint32_t counter_hi;
+} __packed __aligned(4);
+
+CTASSERT(sizeof(struct chacha_key) == sizeof(struct wg_aead_key));
+
+#define DPRINTF(_ifp, _a...) do { \
+ if (ISSET((_ifp)->if_flags, IFF_DEBUG)) { \
+ printf("%s: ", (_ifp)->if_xname); \
+ printf(_a); \
+ printf("\n"); \
+ } \
+} while (0)
+
+struct wg_data_keys {
+ TAILQ_ENTRY(wg_data_keys)
+ wk_entry;
+ uint32_t wk_tx_idx;
+ uint32_t wk_rx_idx;
+ uint64_t wk_tx_seq;
+ uint64_t wk_rx_seq;
+ struct chacha_key wk_tx_key;
+ struct chacha_key wk_rx_key;
+
+ int wk_born;
+ unsigned int wk_rekeyed;
+};
+
+TAILQ_HEAD(wg_data_keys_list, wg_data_keys);
+
+struct wg_device;
+
+struct wg_softc {
+ unsigned int sc_unit; /* must be first */
+ struct ifnet sc_if;
+
+ RBT_ENTRY(wg_softc) sc_entry;
+ TAILQ_ENTRY(wg_softc) sc_lentry;
+ struct wg_device *sc_device;
+
+ struct file * volatile sc_fp;
+
+ unsigned int sc_initiator;
+
+ int sc_up_stamp;
+
+ int sc_rk_stamp;
+ TAILQ_ENTRY(wg_softc) sc_rk_entry;
+ unsigned int sc_rk_onqueue;
+ struct timeout sc_rk_timer;
+
+ struct mbuf_queue sc_tx_queue;
+ struct task sc_tx_task;
+
+ int sc_tx_stamp;
+ struct timeout sc_tx_timer;
+ struct task sc_tx_keepalive;
+
+ /* sc_data_keys is used by wg_start under the ifq serialiaser */
+ struct wg_data_keys *sc_tx_data_keys;
+ /* the list is used in the rx path under the lock */
+ struct rwlock sc_rx_data_keys_lk;
+ struct wg_data_keys_list sc_rx_data_keys;
+
+ int sc_rx_stamp;
+ struct timeout sc_rx_timer;
+};
+
+RBT_HEAD(wg_if_tree, wg_softc);
+TAILQ_HEAD(wg_if_list, wg_softc);
+
+struct wg_device {
+ dev_t wgd_dev; /* must be first */
+ RBT_ENTRY(wg_device) wgd_entry;
+ struct wg_if_list wgd_ifaces;
+
+ struct mutex wgd_rk_mtx;
+ struct wg_if_list wgd_rk_list;
+ unsigned int wgd_rk_reading;
+
+ struct selinfo wgd_rsel;
+ struct selinfo wgd_wsel;
+ struct mutex wgd_sel_mtx;
+ int wgd_nbio;
+};
+
+RBT_HEAD(wg_dv_tree, wg_device);
+
+struct {
+ struct rwlock wg_dv_lock;
+ struct rwlock wg_if_lock;
+ struct wg_dv_tree wg_dv_tree;
+ struct wg_if_tree wg_if_tree;
+} wg_state = {
+ RWLOCK_INITIALIZER("wgdevs"),
+ RWLOCK_INITIALIZER("wgifs"),
+ RBT_INITIALIZER(),
+ RBT_INITIALIZER(),
+};
+
+static int wg_filt_read(struct knote *, long);
+static void wg_filt_read_detach(struct knote *);
+static int wg_filt_write(struct knote *, long);
+static void wg_filt_write_detach(struct knote *);
+
+static struct filterops wg_filtops_read = {
+ 1, NULL, wg_filt_read_detach, wg_filt_read
+};
+
+static struct filterops wg_filtops_write = {
+ 1, NULL, wg_filt_write_detach, wg_filt_write
+};
+
+static struct wg_device *
+ wg_dev_lookup_locked(dev_t);
+static struct wg_device *
+ wg_dev_lookup(dev_t);
+
+static inline int
+ wg_if_cmp(const struct wg_softc *, const struct wg_softc *);
+static inline int
+ wg_dv_cmp(const struct wg_device *, const struct wg_device *);
+
+RBT_PROTOTYPE(wg_if_tree, wg_softc, sc_entry, wg_if_cmp);
+RBT_PROTOTYPE(wg_dv_tree, wg_device, wgd_entry, wg_dv_cmp);
+
+struct wg_aead_ctx {
+ struct chacha_stream
+ chacha20;
+ poly1305_state poly1305;
+ size_t datalen;
+};
+
+struct wg_aead_tag {
+ uint8_t tag[WG_POLY1305_TAG_LEN];
+} __packed __aligned(4);
+
+static void wg_aead_init(struct wg_aead_ctx *,
+ const struct chacha_key *, uint64_t);
+static void wg_aead_encrypt(struct wg_aead_ctx *, void *, size_t len);
+static void wg_aead_verify(struct wg_aead_ctx *, void *, size_t len);
+static void wg_aead_decrypt(struct wg_aead_ctx *, void *, size_t len);
+static void wg_aead_final(struct wg_aead_ctx *, struct wg_aead_tag *);
+
+static int wg_up(struct wg_softc *);
+static int wg_down(struct wg_softc *);
+
+static struct mbuf *
+ wg_input(void *, struct mbuf *, int);
+static int wg_output(struct ifnet *, struct mbuf *,
+ struct sockaddr *, struct rtentry *rt);
+static void wg_start(struct ifqueue *);
+static int wg_ioctl(struct ifnet *, u_long, caddr_t);
+static void wg_send(void *);
+static void wg_link_state(struct wg_softc *, int);
+static void wg_link_up(struct wg_softc *);
+static void wg_link_down(struct wg_softc *, int);
+
+static void wg_rekey(struct wg_softc *, int);
+static void wg_send_keepalive(void *);
+static void wg_rekey_timer(void *);
+static void wg_tx_timer(void *);
+static void wg_rx_timer(void *);
+
+void
+wgattach(int n)
+{
+
+}
+
+int
+wgopen(dev_t dev, int flag, int mode, struct proc *p)
+{
+ struct wg_device *wgd;
+ int rv;
+
+ rv = suser(p);
+ if (rv != 0)
+ return (rv);
+
+ rv = rw_enter(&wg_state.wg_dv_lock, RW_WRITE | RW_INTR);
+ if (rv != 0)
+ return (rv);
+
+ wgd = wg_dev_lookup_locked(dev);
+ if (wgd != NULL) {
+ rv = EBUSY;
+ goto out;
+ }
+
+ wgd = malloc(sizeof(*wgd), M_DEVBUF, M_WAITOK|M_CANFAIL);
+ if (wgd == NULL) {
+ rv = ENOMEM;
+ goto out;
+ }
+ wgd->wgd_dev = dev;
+ memset(&wgd->wgd_rsel, 0, sizeof(wgd->wgd_rsel));
+ memset(&wgd->wgd_wsel, 0, sizeof(wgd->wgd_wsel));
+ mtx_init(&wgd->wgd_sel_mtx, IPL_SOFTNET);
+ wgd->wgd_nbio = ISSET(flag, FNONBLOCK) ? IO_NDELAY : 0;
+ TAILQ_INIT(&wgd->wgd_ifaces);
+
+ mtx_init(&wgd->wgd_rk_mtx, IPL_SOFTNET);
+ TAILQ_INIT(&wgd->wgd_rk_list);
+ wgd->wgd_rk_reading = 0;
+
+ if (RBT_INSERT(wg_dv_tree, &wg_state.wg_dv_tree, wgd) != NULL)
+ panic("wg dev tree modified while lock was held");
+
+out:
+ rw_exit(&wg_state.wg_dv_lock);
+
+ return (rv);
+}
+
+int
+wgclose(dev_t dev, int flag, int mode, struct proc *p)
+{
+ struct wg_device *wgd;
+ struct wg_softc *sc;
+ struct file *fp;
+
+ rw_enter(&wg_state.wg_dv_lock, RW_WRITE);
+ wgd = wg_dev_lookup_locked(dev);
+ KASSERTMSG(wgd != NULL, "wg device missing in close");
+
+ RBT_REMOVE(wg_dv_tree, &wg_state.wg_dv_tree, wgd);
+ rw_exit(&wg_state.wg_dv_lock);
+
+ rw_enter(&wg_state.wg_if_lock, RW_WRITE);
+ TAILQ_FOREACH(sc, &wgd->wgd_ifaces, sc_lentry) {
+ struct ifnet *ifp = &sc->sc_if;
+
+ if (ifp->if_index != 0) {
+ NET_LOCK();
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ wg_down(sc);
+ NET_UNLOCK();
+
+ fp = sc->sc_fp;
+ if (fp != NULL) {
+ struct socket *so;
+ struct inpcb *inp;
+ int s;
+
+ so = (struct socket *)fp->f_data;
+ s = solock(so);
+ inp = sotoinpcb(so);
+ if (inp->inp_upcall != NULL) {
+ inp->inp_upcall_arg = NULL;
+ inp->inp_upcall = NULL;
+ }
+ sounlock(so, s);
+
+ sc->sc_fp = NULL;
+ FRELE(fp, p);
+ }
+
+ if_detach(ifp);
+ }
+
+ RBT_REMOVE(wg_if_tree, &wg_state.wg_if_tree, sc);
+ }
+ rw_exit(&wg_state.wg_if_lock);
+
+ while ((sc = TAILQ_FIRST(&wgd->wgd_ifaces)) != NULL) {
+ struct wg_data_keys *wk;
+
+ TAILQ_REMOVE(&wgd->wgd_ifaces, sc, sc_lentry);
+
+ while ((wk = TAILQ_FIRST(&sc->sc_rx_data_keys)) != NULL) {
+ TAILQ_REMOVE(&sc->sc_rx_data_keys, wk, wk_entry);
+ free(wk, M_DEVBUF, sizeof(*wk));
+ }
+
+ task_del(systq, &sc->sc_tx_task);
+ mq_purge(&sc->sc_tx_queue);
+
+ free(sc, M_DEVBUF, sizeof(*sc));
+ }
+
+ free(wgd, M_DEVBUF, sizeof(*wgd));
+
+ return (0);
+}
+
+static struct wg_softc *
+wg_if_lookup(struct wg_device *wgd, unsigned int unit)
+{
+ struct wg_softc *sc;
+
+ sc = RBT_FIND(wg_if_tree, &wg_state.wg_if_tree,
+ (struct wg_softc *)&unit);
+ if (sc == NULL || sc->sc_device != wgd)
+ return (NULL);
+
+ return (sc);
+}
+
+static int
+wg_if_create(struct wg_device *wgd, unsigned int unit)
+{
+ struct wg_softc *sc, *osc;
+ int error;
+
+ sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_CANFAIL|M_ZERO);
+ if (sc == NULL)
+ return (ENOMEM);
+
+ sc->sc_unit = unit;
+ sc->sc_device = wgd;
+ sc->sc_initiator = 1;
+ rw_init(&sc->sc_rx_data_keys_lk, "wgrxkeys");
+ TAILQ_INIT(&sc->sc_rx_data_keys);
+
+ mq_init(&sc->sc_tx_queue, 128, IPL_SOFTNET);
+ task_set(&sc->sc_tx_task, wg_send, sc);
+ task_set(&sc->sc_tx_keepalive, wg_send_keepalive, sc);
+
+ timeout_set(&sc->sc_rk_timer, wg_rekey_timer, sc);
+ timeout_set(&sc->sc_tx_timer, wg_tx_timer, sc);
+ timeout_set(&sc->sc_rx_timer, wg_rx_timer, sc);
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR);
+ if (error != 0)
+ goto fail;
+
+ osc = RBT_INSERT(wg_if_tree, &wg_state.wg_if_tree, sc);
+ if (osc != NULL) {
+ error = (osc->sc_device == wgd) ? EEXIST : EBUSY;
+ goto unlock;
+ }
+
+ TAILQ_INSERT_TAIL(&wgd->wgd_ifaces, sc, sc_lentry);
+ rw_exit(&wg_state.wg_if_lock);
+
+ return (0);
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+fail:
+ free(sc, M_DEVBUF, sizeof(*sc));
+ return (error);
+}
+
+static int
+wg_if_destroy(struct wg_device *wgd, unsigned int unit)
+{
+ struct wg_softc *sc;
+ int error;
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR);
+ if (error != 0)
+ goto fail;
+
+ sc = wg_if_lookup(wgd, unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+
+ if (sc->sc_if.if_index != 0) {
+ error = EBUSY;
+ goto unlock;
+ }
+
+ TAILQ_REMOVE(&wgd->wgd_ifaces, sc, sc_lentry);
+ RBT_REMOVE(wg_if_tree, &wg_state.wg_if_tree, sc);
+ rw_exit(&wg_state.wg_if_lock);
+
+ task_del(systq, &sc->sc_tx_task);
+ mq_purge(&sc->sc_tx_queue);
+
+ free(sc, M_DEVBUF, sizeof(*sc));
+
+ return (0);
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+fail:
+ return (error);
+}
+
+static int
+wg_if_attach(struct wg_device *wgd, unsigned int unit)
+{
+ struct wg_softc *sc;
+ struct ifnet *ifp;
+ int error;
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR);
+ if (error != 0)
+ return (error);
+
+ sc = wg_if_lookup(wgd, unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+
+ if (sc->sc_if.if_index != 0) {
+ error = EBUSY;
+ goto unlock;
+ }
+
+ ifp = &sc->sc_if;
+ snprintf(ifp->if_xname, sizeof(ifp->if_xname), "wg%u", sc->sc_unit);
+ ifp->if_softc = sc;
+ ifp->if_type = IFT_TUNNEL;
+ ifp->if_mtu = 1280;
+ ifp->if_flags = IFF_POINTOPOINT|IFF_MULTICAST;
+ ifp->if_xflags = IFXF_CLONED|IFXF_MPSAFE;
+ ifp->if_output = wg_output;
+ ifp->if_qstart = wg_start;
+ ifp->if_ioctl = wg_ioctl;
+ ifp->if_rtrequest = p2p_rtrequest;
+ ifp->if_link_state = LINK_STATE_DOWN;
+
+ if_attach(ifp);
+ if_alloc_sadl(ifp);
+
+ if_addgroup(ifp, "wg");
+
+#if NBPFILTER > 0
+ bpfattach(&ifp->if_bpf, ifp, DLT_LOOP, sizeof(uint32_t));
+#endif
+
+ KASSERT(ifp->if_index != 0);
+ rw_exit(&wg_state.wg_if_lock);
+
+ return (0);
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+ return (error);
+}
+
+static int
+wg_if_detach(struct wg_device *wgd, unsigned int unit)
+{
+ struct wg_softc *sc;
+ struct ifnet *ifp;
+ int error;
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR);
+ if (error != 0)
+ return (error);
+
+ sc = wg_if_lookup(wgd, unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+
+ if (sc->sc_if.if_index == 0) {
+ error = ENXIO;
+ goto unlock;
+ }
+
+ if (sc->sc_fp != NULL) {
+ error = EBUSY;
+ goto unlock;
+ }
+
+ NET_LOCK();
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ wg_down(sc);
+ NET_UNLOCK();
+
+ if_detach(ifp);
+
+ ifp->if_index = 0;
+ rw_exit(&wg_state.wg_if_lock);
+
+ return (0);
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+ return (error);
+}
+
+static void
+wg_frele(struct file *fp, struct proc *p)
+{
+ struct socket *so;
+ struct inpcb *inp;
+ int s;
+
+ so = (struct socket *)fp->f_data;
+ inp = sotoinpcb(so);
+
+ s = solock(so);
+ inp->inp_upcall = NULL;
+ inp->inp_upcall_arg = NULL;
+ sounlock(so, s);
+
+ FRELE(fp, p);
+}
+
+static int
+wg_if_set_sock(struct wg_device *wgd, struct proc *p,
+ const struct wg_if_sock *data)
+{
+ unsigned int unit = data->wg_unit;
+ struct wg_softc *sc;
+ struct ifnet *ifp;
+ struct file *fp, *ofp;
+ struct socket *so;
+ struct inpcb *inp;
+ int error;
+ int s;
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR);
+ if (error != 0)
+ return (error);
+
+ sc = wg_if_lookup(wgd, unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+ ifp = &sc->sc_if;
+
+ if (ifp->if_index == 0) {
+ error = ENXIO;
+ goto unlock;
+ }
+
+ ofp = sc->sc_fp;
+
+ error = getsock(p, data->wg_sock, &fp);
+ if (error != 0)
+ goto unlock;
+
+ so = (struct socket *)fp->f_data;
+ if (so->so_proto->pr_protocol != IPPROTO_UDP) {
+ FRELE(fp, p);
+ error = EPROTONOSUPPORT;
+ goto unlock;
+ }
+ if (!ISSET(so->so_state, SS_ISCONNECTED)) {
+ FRELE(fp, p);
+ error = ENOTCONN;
+ goto unlock;
+ }
+
+ s = solock(so);
+ inp = sotoinpcb(so);
+ if (inp->inp_upcall != NULL) {
+ FRELE(fp, p);
+ sounlock(so, s);
+ error = EISCONN;
+ goto unlock;
+ }
+
+ inp->inp_upcall_arg = sc;
+ inp->inp_upcall = wg_input;
+ sounlock(so, s);
+
+ sc->sc_fp = fp;
+ ifq_barrier(&ifp->if_snd);
+
+ rw_exit(&wg_state.wg_if_lock);
+
+ if (ofp)
+ wg_frele(ofp, p);
+ else {
+ KASSERT(ifp->if_link_state == LINK_STATE_DOWN);
+ wg_link_state(sc, LINK_STATE_KALIVE_DOWN);
+ if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+ if (sc->sc_initiator) {
+ /* force a rekey */
+ wg_rekey(sc, 1);
+ }
+ }
+ }
+
+ return (0);
+
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+ return (error);
+}
+
+static int
+wg_if_clr_sock(struct wg_device *wgd, struct proc *p, unsigned int unit)
+{
+ struct wg_softc *sc;
+ struct file *fp;
+ int error;
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR);
+ if (error != 0)
+ return (error);
+
+ sc = wg_if_lookup(wgd, unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+
+ fp = sc->sc_fp;
+ if (fp == NULL) {
+ error = ENOTCONN;
+ goto unlock;
+ }
+
+ sc->sc_fp = NULL;
+ ifq_barrier(&sc->sc_if.if_snd);
+
+ rw_exit(&wg_state.wg_if_lock);
+
+ wg_frele(fp, p);
+ wg_link_down(sc, LINK_STATE_DOWN);
+
+ return (0);
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+ return (error);
+}
+
+static struct wg_data_keys *
+wg_if_insert_data_keys(struct wg_softc *sc, struct wg_data_keys *wk)
+{
+ struct wg_data_keys *owk;
+
+ TAILQ_FOREACH(owk, &sc->sc_rx_data_keys, wk_entry) {
+ if (owk->wk_rx_idx == wk->wk_rx_idx ||
+ owk->wk_tx_idx == wk->wk_tx_idx)
+ return (owk);
+ }
+
+ TAILQ_INSERT_HEAD(&sc->sc_rx_data_keys, wk, wk_entry);
+
+ return (NULL);
+}
+
+static int
+wg_if_add_keys(struct wg_device *wgd, const struct wg_if_data_keys *keys)
+{
+ struct wg_softc *sc;
+ struct wg_data_keys *wk;
+ unsigned int unit = keys->wg_unit;
+ int error;
+
+ wk = malloc(sizeof(*wk), M_DEVBUF, M_WAITOK|M_CANFAIL);
+ if (wk == NULL)
+ return (ENOMEM);
+
+ wk->wk_tx_idx = keys->wg_tx_index;
+ wk->wk_rx_idx = keys->wg_rx_index;
+ wk->wk_tx_seq = 0;
+ wk->wk_rx_seq = 0;
+ memcpy(&wk->wk_tx_key, &keys->wg_tx_key, sizeof(wk->wk_tx_key));
+ memcpy(&wk->wk_rx_key, &keys->wg_rx_key, sizeof(wk->wk_rx_key));
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR);
+ if (error != 0)
+ goto free;
+
+ sc = wg_if_lookup(wgd, unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+
+ rw_enter_write(&sc->sc_rx_data_keys_lk);
+ if (wg_if_insert_data_keys(sc, wk) != NULL)
+ error = EEXIST;
+ rw_exit_write(&sc->sc_rx_data_keys_lk);
+ if (error != 0)
+ goto unlock;
+
+ sc->sc_up_stamp = ticks;
+
+ sc->sc_tx_data_keys = wk;
+ /* make sure wg_start isn't using the old head */
+ ifq_barrier(&sc->sc_if.if_snd);
+
+ rw_exit(&wg_state.wg_if_lock);
+
+ /* XXX locking */
+ if (sc->sc_if.if_link_state == LINK_STATE_KALIVE_DOWN) {
+ sc->sc_tx_stamp = ticks;
+ wg_link_up(sc);
+ }
+
+ return (0);
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+free:
+ free(wk, M_DEVBUF, sizeof(*wk));
+ return (error);
+}
+
+static int
+wg_if_clr_keys(struct wg_device *wgd, const struct wg_if_data_keys *keys)
+{
+ struct wg_softc *sc;
+ struct wg_data_keys *wk;
+ unsigned int unit = keys->wg_unit;
+ int error;
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_WRITE | RW_INTR);
+ if (error != 0)
+ return (error);
+
+ sc = wg_if_lookup(wgd, unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+
+ rw_enter_write(&sc->sc_rx_data_keys_lk);
+ TAILQ_FOREACH(wk, &sc->sc_rx_data_keys, wk_entry) {
+ if (wk->wk_rx_idx == keys->wg_rx_index &&
+ wk->wk_tx_idx == keys->wg_tx_index) {
+ TAILQ_REMOVE(&sc->sc_rx_data_keys, wk, wk_entry);
+ break;
+ }
+ }
+ rw_exit_write(&sc->sc_rx_data_keys_lk);
+
+ if (wk == NULL) {
+ error = ENOENT;
+ goto unlock;
+ }
+
+ if (sc->sc_tx_data_keys == wk) {
+ sc->sc_tx_data_keys = TAILQ_FIRST(&sc->sc_rx_data_keys);;
+ /* make sure wg_start isn't using the old head */
+ ifq_barrier(&sc->sc_if.if_snd);
+ }
+
+ rw_exit(&wg_state.wg_if_lock);
+
+ free(wk, M_DEVBUF, sizeof(*wk));
+
+ return (0);
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+ return (error);
+}
+
+static int
+wg_if_set_role(struct wg_device *wgd, const struct wg_if_role *role)
+{
+ struct wg_softc *sc;
+ int error = 0;
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_READ | RW_INTR);
+ if (error != 0)
+ return (error);
+
+ sc = wg_if_lookup(wgd, role->wg_unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+
+ switch (role->wg_role) {
+ case WG_DATA_INITIATOR:
+ sc->sc_initiator = 1;
+ break;
+ case WG_DATA_RESPONDER:
+ sc->sc_initiator = 0;
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+
+ rw_exit(&wg_state.wg_if_lock);
+
+ return (0);
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+ return (error);
+}
+
+static int
+wg_if_get_role(struct wg_device *wgd, struct wg_if_role *role)
+{
+ struct wg_softc *sc;
+ int error = 0;
+
+ error = rw_enter(&wg_state.wg_if_lock, RW_READ | RW_INTR);
+ if (error != 0)
+ return (error);
+
+ sc = wg_if_lookup(wgd, role->wg_unit);
+ if (sc == NULL) {
+ error = ESRCH;
+ goto unlock;
+ }
+
+ role->wg_role = sc->sc_initiator ?
+ WG_DATA_INITIATOR : WG_DATA_RESPONDER;
+
+ rw_exit(&wg_state.wg_if_lock);
+
+ return (0);
+
+unlock:
+ rw_exit(&wg_state.wg_if_lock);
+ return (error);
+}
+
+int
+wgioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ struct wg_device *wgd = wg_dev_lookup(dev);
+
+ switch (cmd) {
+ case FIONBIO:
+ wgd->wgd_nbio = *(int *)data ? IO_NDELAY : 0;
+ break;
+ case FIONREAD:
+ *(int *)data = TAILQ_EMPTY(&wgd->wgd_rk_list) ?
+ 0 : sizeof(struct wg_msg);
+ break;
+
+ case WGIFCREATE:
+ return (wg_if_create(wgd, *(unsigned int *)data));
+ case WGIFATTACH:
+ return (wg_if_attach(wgd, *(unsigned int *)data));
+ case WGIFDETACH:
+ return (wg_if_detach(wgd, *(unsigned int *)data));
+ case WGIFDESTROY:
+ return (wg_if_destroy(wgd, *(unsigned int *)data));
+
+ case WGIFSSOCK:
+ return (wg_if_set_sock(wgd, p,
+ (const struct wg_if_sock *)data));
+ case WGIFDSOCK:
+ return (wg_if_clr_sock(wgd, p, *(unsigned int *)data));
+
+ case WGIFADDKEYS:
+ return (wg_if_add_keys(wgd,
+ (const struct wg_if_data_keys *)data));
+ case WGIFDELKEYS:
+ return (wg_if_clr_keys(wgd,
+ (const struct wg_if_data_keys *)data));
+
+ case WGIFSROLE:
+ return (wg_if_set_role(wgd, (struct wg_if_role *)data));
+ case WGIFGROLE:
+ return (wg_if_get_role(wgd, (struct wg_if_role *)data));
+
+ default:
+ return (ENOTTY);
+ }
+
+ return (0);
+}
+
+int
+wgread(dev_t dev, struct uio *uio, int ioflag)
+{
+ struct wg_device *wgd = wg_dev_lookup(dev);
+ struct wg_softc *sc;
+ struct wg_msg msg;
+ int error;
+
+ if (uio->uio_resid < 0)
+ return (EINVAL);
+
+ ioflag |= wgd->wgd_nbio;
+
+ mtx_enter(&wgd->wgd_rk_mtx);
+
+ sc = TAILQ_FIRST(&wgd->wgd_rk_list);
+ if (sc == NULL) {
+ if (ioflag & IO_NDELAY) {
+ mtx_leave(&wgd->wgd_rk_mtx);
+ return (EWOULDBLOCK);
+ }
+
+ wgd->wgd_rk_reading++;
+ do {
+ error = msleep(&wgd->wgd_rk_list, &wgd->wgd_rk_mtx,
+ (PZERO + 1)|PCATCH, "wgread", 0);
+ if (error != 0) {
+ wgd->wgd_rk_reading--;
+ mtx_leave(&wgd->wgd_rk_mtx);
+ return (error);
+ }
+
+ sc = TAILQ_FIRST(&wgd->wgd_rk_list);
+ } while (sc == NULL);
+ wgd->wgd_rk_reading--;
+ }
+
+ sc->sc_rk_stamp = ticks;
+ sc->sc_rk_onqueue = 0;
+ TAILQ_REMOVE(&wgd->wgd_rk_list, sc, sc_rk_entry);
+
+ msg.wg_unit = sc->sc_unit;
+ msg.wg_type = WG_MSG_REKEY;
+ DPRINTF(&sc->sc_if, "rekey");
+
+ mtx_leave(&wgd->wgd_rk_mtx);
+
+ return (uiomove(&msg, ulmin(uio->uio_resid, sizeof(msg)), uio));
+}
+
+int
+wgwrite(dev_t dev, struct uio *uio, int ioflag)
+{
+ return (EOPNOTSUPP);
+}
+
+int
+wgpoll(dev_t dev, int events, struct proc *p)
+{
+ struct wg_device *wgd = wg_dev_lookup(dev);
+ int mevents, revents = 0;
+
+ mevents = ISSET(events, POLLIN | POLLRDNORM);
+ if (mevents) {
+ if (!TAILQ_EMPTY(&wgd->wgd_rk_list))
+ SET(revents, events & mevents);
+ else
+ selrecord(p, &wgd->wgd_rsel);
+ }
+
+ mevents = ISSET(events, POLLOUT | POLLWRNORM);
+ if (mevents)
+ SET(revents, events & mevents);
+
+ return (revents);
+}
+
+int
+wgkqfilter(dev_t dev, struct knote *kn)
+{
+ struct wg_device *wgd = wg_dev_lookup(dev);
+ struct klist *klist;
+
+ switch (kn->kn_filter) {
+ case EVFILT_READ:
+ klist = &wgd->wgd_rsel.si_note;
+ kn->kn_fop = &wg_filtops_read;
+ break;
+ case EVFILT_WRITE:
+ klist = &wgd->wgd_wsel.si_note;
+ kn->kn_fop = &wg_filtops_write;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ kn->kn_hook = wgd;
+
+ mtx_enter(&wgd->wgd_sel_mtx);
+ SLIST_INSERT_HEAD(klist, kn, kn_selnext);
+ mtx_leave(&wgd->wgd_sel_mtx);
+
+ return (0);
+}
+
+static void
+wg_filt_read_detach(struct knote *kn)
+{
+ struct wg_device *wgd = kn->kn_hook;
+ struct klist *klist = &wgd->wgd_rsel.si_note;
+
+ mtx_enter(&wgd->wgd_sel_mtx);
+ SLIST_REMOVE(klist, kn, knote, kn_selnext);
+ mtx_leave(&wgd->wgd_sel_mtx);
+}
+
+static int
+wg_filt_read(struct knote *kn, long hint)
+{
+ struct wg_device *wgd = kn->kn_hook;
+
+ kn->kn_data = TAILQ_EMPTY(&wgd->wgd_rk_list) ?
+ 0 : sizeof(struct wg_msg);
+
+ return (kn->kn_data != 0);
+}
+
+static void
+wg_filt_write_detach(struct knote *kn)
+{
+ struct wg_device *wgd = kn->kn_hook;
+ struct klist *klist = &wgd->wgd_wsel.si_note;
+
+ mtx_enter(&wgd->wgd_sel_mtx);
+ SLIST_REMOVE(klist, kn, knote, kn_selnext);
+ mtx_leave(&wgd->wgd_sel_mtx);
+}
+
+static int
+wg_filt_write(struct knote *kn, long hint)
+{
+ kn->kn_data = 0;
+
+ return (0);
+}
+
+static struct wg_device *
+wg_dev_lookup_locked(dev_t dev)
+{
+ return (RBT_FIND(wg_dv_tree, &wg_state.wg_dv_tree,
+ (const struct wg_device *)&dev));
+}
+
+static struct wg_device *
+wg_dev_lookup(dev_t dev)
+{
+ struct wg_device *wgd;
+
+ rw_enter_read(&wg_state.wg_dv_lock);
+ wgd = wg_dev_lookup_locked(dev);
+ rw_exit_read(&wg_state.wg_dv_lock);
+
+ return (wgd);
+}
+
+static inline int
+wg_dv_cmp(const struct wg_device *a, const struct wg_device *b)
+{
+ if (a->wgd_dev > b->wgd_dev)
+ return (1);
+ if (a->wgd_dev < b->wgd_dev)
+ return (-1);
+ return (0);
+}
+
+static inline int
+wg_if_cmp(const struct wg_softc *a, const struct wg_softc *b)
+{
+ if (a->sc_unit > b->sc_unit)
+ return (1);
+ if (a->sc_unit < b->sc_unit)
+ return (-1);
+ return (0);
+}
+
+RBT_GENERATE(wg_if_tree, wg_softc, sc_entry, wg_if_cmp);
+RBT_GENERATE(wg_dv_tree, wg_device, wgd_entry, wg_dv_cmp);
+
+#define WG_MS2TICKS(_m) (((_m) * 1000) / tick)
+
+static inline int
+wg_ratecheck(int stamp, int interval)
+{
+ int diff;
+
+ diff = ticks - stamp;
+ return (diff >= interval);
+}
+
+static void
+wg_rekey(struct wg_softc *sc, int force)
+{
+ struct wg_device *wgd = sc->sc_device;
+ int tmo = WG_MS2TICKS(WG_REKEY_TIMEOUT);
+ int wake = 0;
+ int next = 0;
+
+ if (sc->sc_rk_onqueue)
+ return;
+
+ if (!force && !wg_ratecheck(sc->sc_rk_stamp, tmo))
+ return;
+
+ mtx_enter(&wgd->wgd_rk_mtx);
+ if (!sc->sc_rk_onqueue) {
+ if (force || wg_ratecheck(sc->sc_rk_stamp, tmo)) {
+ TAILQ_INSERT_TAIL(&wgd->wgd_rk_list, sc, sc_rk_entry);
+ sc->sc_rk_onqueue = 1;
+
+ wake = wgd->wgd_rk_reading;
+ }
+ next = 1;
+ }
+ mtx_leave(&wgd->wgd_rk_mtx);
+
+ if (wake)
+ wakeup(&wgd->wgd_rk_list);
+ selwakeup(&wgd->wgd_rsel);
+
+ if (next) {
+ timeout_add_msec(&sc->sc_rk_timer,
+ WG_REKEY_TIMEOUT + arc4random_uniform(WG_REKEY_JITTER));
+ }
+
+ sc->sc_tx_stamp = ticks;
+}
+
+static int
+wg_up(struct wg_softc *sc)
+{
+ struct ifnet *ifp = &sc->sc_if;
+
+ SET(ifp->if_flags, IFF_RUNNING);
+
+ if (ifp->if_link_state == LINK_STATE_KALIVE_DOWN) {
+ if (sc->sc_initiator) {
+ /* force a rekey */
+ wg_rekey(sc, 1);
+ }
+ }
+
+ return (0);
+}
+
+static void
+wg_link_state(struct wg_softc *sc, int link_state)
+{
+ struct ifnet *ifp = &sc->sc_if;
+
+ ifp->if_link_state = link_state;
+ if_link_state_change(ifp);
+}
+
+static void
+wg_rekey_timer(void *arg)
+{
+ struct wg_softc *sc = arg;
+ struct ifnet *ifp = &sc->sc_if;
+
+ if (!ISSET(ifp->if_flags, IFF_RUNNING))
+ return;
+
+ if (ifp->if_link_state == LINK_STATE_UP &&
+ wg_ratecheck(sc->sc_up_stamp, WG_MS2TICKS(WG_REKEY_MAX)))
+ wg_link_down(sc, LINK_STATE_KALIVE_DOWN);
+
+ wg_rekey(sc, 0);
+}
+
+static void
+wg_link_up(struct wg_softc *sc)
+{
+ DPRINTF(&sc->sc_if, "link up");
+ timeout_add_msec(&sc->sc_tx_timer, WG_KEEPALIVE + WG_REKEY_TIMEOUT);
+ timeout_add_msec(&sc->sc_rx_timer, WG_KEEPALIVE);
+ wg_link_state(sc, LINK_STATE_UP);
+}
+
+static void
+wg_link_down(struct wg_softc *sc, int link_state)
+{
+ DPRINTF(&sc->sc_if, "link down");
+ timeout_del(&sc->sc_rx_timer);
+ timeout_del(&sc->sc_tx_timer);
+
+ if (link_state == LINK_STATE_DOWN)
+ timeout_del(&sc->sc_rk_timer);
+
+ timeout_barrier(&sc->sc_rx_timer); /* implies tx and rk bar too */
+
+ wg_link_state(sc, link_state);
+}
+
+static void
+wg_rx_timer(void *arg)
+{
+ struct wg_softc *sc = arg;
+ struct ifnet *ifp = &sc->sc_if;
+ const int tmo = WG_MS2TICKS(WG_KEEPALIVE);
+ int diff;
+
+ diff = ticks - sc->sc_rx_stamp;
+ DPRINTF(ifp, "%s, tmo %d, diff %d = ticks %d - stamp %d", __func__,
+ tmo, diff, ticks, sc->sc_rx_stamp);
+ if (diff >= tmo) {
+ DPRINTF(ifp, "rx timer expired, sending keepalive");
+ ifq_serialize(&ifp->if_snd, &sc->sc_tx_keepalive);
+ diff = 0;
+ }
+
+ DPRINTF(ifp, "%s, timeout_add rx timer %d", __func__, tmo - diff);
+ timeout_add(&sc->sc_rx_timer, tmo - diff);
+}
+
+static void
+wg_tx_timer(void *arg)
+{
+ struct wg_softc *sc = arg;
+ struct ifnet *ifp = &sc->sc_if;
+ const int tmo = WG_MS2TICKS(WG_KEEPALIVE + WG_REKEY_TIMEOUT);
+ int diff;
+
+ diff = ticks - sc->sc_tx_stamp;
+ DPRINTF(ifp, "%s, tmo %d, diff %d = ticks %d - stamp %d", __func__,
+ tmo, diff, ticks, sc->sc_tx_stamp);
+ if (diff >= tmo) {
+ DPRINTF(ifp, "tx timer expired, sending rekey");
+ wg_rekey(sc, 0);
+ diff = 0;
+ }
+
+ DPRINTF(ifp, "%s, timeout_add tx timer %d", __func__, tmo - diff);
+ timeout_add(&sc->sc_tx_timer, tmo - diff);
+}
+
+static int
+wg_down(struct wg_softc *sc)
+{
+ struct ifnet *ifp = &sc->sc_if;
+
+ CLR(ifp->if_flags, IFF_RUNNING);
+ ifq_barrier(&ifp->if_snd);
+
+ wg_link_down(sc, sc->sc_fp == NULL ?
+ LINK_STATE_DOWN : LINK_STATE_KALIVE_DOWN);
+
+ return (0);
+}
+
+static int
+wg_get_tunnel(struct wg_softc *sc, struct if_laddrreq *req)
+{
+ struct file *fp;
+ struct socket *so;
+ struct inpcb *inp;
+// int s;
+
+ fp = sc->sc_fp;
+ if (fp == NULL)
+ return (EADDRNOTAVAIL);
+
+ so = (struct socket *)fp->f_data;
+// s = solock(so);
+ inp = sotoinpcb(so);
+
+ if (inp->inp_flags & INP_IPV6) {
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)&req->addr;
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_len = sizeof(*sin6);
+ in6_recoverscope(sin6, &inp->inp_laddr6);
+ sin6->sin6_port = inp->inp_lport;
+
+ sin6 = (struct sockaddr_in6 *)&req->dstaddr;
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_len = sizeof(*sin6);
+ in6_recoverscope(sin6, &inp->inp_faddr6);
+ sin6->sin6_port = inp->inp_fport;
+#else /* INET6 */
+ unhandled_af(AF_INET6);
+#endif /* INET6 */
+ } else {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)&req->addr;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+ sin->sin_len = sizeof(*sin);
+ sin->sin_addr = inp->inp_laddr;
+ sin->sin_port = inp->inp_lport;
+
+ sin = (struct sockaddr_in *)&req->dstaddr;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+ sin->sin_len = sizeof(*sin);
+ sin->sin_addr = inp->inp_faddr;
+ sin->sin_port = inp->inp_fport;
+ }
+
+// sounlock(so, s);
+
+ return (0);
+}
+
+static int
+wg_get_rdomain(struct wg_softc *sc, struct ifreq *ifr)
+{
+ struct file *fp;
+ struct socket *so;
+ struct inpcb *inp;
+// int s;
+
+ fp = sc->sc_fp;
+ if (fp == NULL)
+ return (EADDRNOTAVAIL);
+
+ so = (struct socket *)fp->f_data;
+// s = solock(so);
+ inp = sotoinpcb(so);
+
+ ifr->ifr_rdomainid = inp->inp_rtableid;
+
+// sounlock(so, s);
+
+ return (0);
+}
+
+static int
+wg_get_ttl(struct wg_softc *sc, struct ifreq *ifr)
+{
+ struct file *fp;
+ struct socket *so;
+ struct inpcb *inp;
+// int s;
+
+ fp = sc->sc_fp;
+ if (fp == NULL)
+ return (EADDRNOTAVAIL);
+
+ so = (struct socket *)fp->f_data;
+// s = solock(so);
+ inp = sotoinpcb(so);
+
+ if (inp->inp_flags & INP_IPV6) {
+#ifdef INET6
+ ifr->ifr_ttl = inp->inp_ipv6.ip6_hlim;
+#else /* INET6 */
+ unhandled_af(AF_INET6);
+#endif /* INET6 */
+ } else {
+ ifr->ifr_ttl = inp->inp_ip.ip_ttl;
+ }
+
+// sounlock(so, s);
+
+ return (0);
+}
+
+static int
+wg_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct wg_softc *sc = ifp->if_softc;
+ struct ifreq *ifr = (struct ifreq *)data;
+ int error = 0;
+
+ switch (cmd) {
+ case SIOCSIFADDR:
+ break;
+
+ case SIOCSIFFLAGS:
+ if (ISSET(ifp->if_flags, IFF_UP)) {
+ if (!ISSET(ifp->if_flags, IFF_RUNNING))
+ error = wg_up(sc);
+ else
+ error = 0;
+ } else {
+ if (ISSET(ifp->if_flags, IFF_RUNNING))
+ error = wg_down(sc);
+ }
+ break;
+
+ case SIOCGLIFPHYADDR:
+ error = wg_get_tunnel(sc, (struct if_laddrreq *)data);
+ break;
+ case SIOCGLIFPHYRTABLE:
+ error = wg_get_rdomain(sc, ifr);
+ break;
+ case SIOCGLIFPHYTTL:
+ error = wg_get_ttl(sc, ifr);
+ break;
+
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ return (error);
+}
+
+static int
+wg_decap(struct ifnet *ifp, struct mbuf *m0,
+ const struct chacha_key *rxkey, uint64_t counter)
+{
+ struct wg_aead_ctx ctx;
+ struct wg_aead_tag ptag, ctag;
+ struct mbuf *mn, *m;
+ unsigned int len = m0->m_pkthdr.len;
+ unsigned int diff;
+ int rv = 0;
+
+ if (len < sizeof(ptag)) {
+ /* must be long enough to contain the tag */
+ ifp->if_iqdrops++;
+ return (-1);
+ }
+
+ len -= sizeof(ptag);
+ if (len % 16) {
+ /* be padded */
+ ifp->if_iqdrops++;
+ return (-1);
+ }
+
+ wg_aead_init(&ctx, rxkey, counter);
+
+ if (len) {
+ /* m_apply, but different */
+ mn = m0;
+ do {
+ m = mn;
+ KASSERT(m != NULL);
+
+ diff = min(m->m_len, len);
+ if (diff) {
+ wg_aead_verify(&ctx, m->m_data, diff);
+ len -= diff;
+ }
+
+ mn = m->m_next;
+ } while (len > 0);
+ } else {
+ m = m0;
+ diff = 0;
+ }
+
+ m_copydata(m, diff, sizeof(ptag), (caddr_t)&ptag);
+
+ wg_aead_final(&ctx, &ctag);
+ if (memcmp(&ctag, &ptag, sizeof(ctag)) != 0) {
+ /* mac didnt match */
+ ifp->if_ierrors++;
+ rv = -1;
+ goto out;
+ }
+
+ /* chop the poly bit off */
+ m_freem(m->m_next);
+ m->m_next = NULL;
+ m->m_len = diff;
+ m0->m_pkthdr.len -= sizeof(ptag);
+
+ if (len) {
+ m = m0;
+ do {
+ wg_aead_decrypt(&ctx, m->m_data, m->m_len);
+ m = m->m_next;
+ } while (m != NULL);
+ }
+
+out:
+ explicit_bzero(&ctx, sizeof(ctx));
+
+ return (rv);
+}
+
+static struct wg_data_keys *
+wg_match_rx_data_keys(struct wg_softc *sc, uint32_t index)
+{
+ struct wg_data_keys *wk;
+
+ TAILQ_FOREACH(wk, &sc->sc_rx_data_keys, wk_entry) {
+ if (wk->wk_rx_idx == index)
+ return (wk);
+ }
+
+ return (NULL);
+}
+
+static void
+wg_send_rekey(struct wg_softc *sc)
+{
+
+}
+
+static struct mbuf *
+wg_input(void *ctx, struct mbuf *m, int iphlen)
+{
+ struct wg_softc *sc = ctx;
+ struct ifnet *ifp = &sc->sc_if;
+ struct wg_data_hdr *hdr;
+ int hlen = iphlen + sizeof(*hdr);
+ struct wg_data_keys *wk;
+ struct mbuf *n;
+ uint64_t counter;
+ uint64_t diff;
+ int high;
+ int rv;
+ void (*input)(struct ifnet *, struct mbuf *);
+
+ soassertlocked((struct socket *)sc->sc_fp->f_data);
+
+ if (!ISSET(ifp->if_flags, IFF_RUNNING)) {
+ /* no point if we're not up */
+ return (m);
+ }
+
+ if (TAILQ_EMPTY(&sc->sc_rx_data_keys)) {
+ /* not set up with any keys */
+ return (m);
+ }
+
+ if (m->m_pkthdr.len < hlen) {
+ /* decline short packets */
+ return (m);
+ }
+
+ if (m->m_len < hlen) {
+ m = m_pullup(m, hlen);
+ if (m == NULL) {
+ /* oops */
+ return (NULL);
+ }
+ }
+
+ hdr = (struct wg_data_hdr *)(mtod(m, uint8_t *) + iphlen);
+ if (hdr->msg_type != htole32(WG_MSG_DATA)) {
+ /* we only handle data in the kernel */
+ return (m);
+ }
+
+ counter = lemtoh32(&hdr->counter_lo) |
+ ((uint64_t)lemtoh32(&hdr->counter_hi) << 32);
+
+ if (counter >= WG_REJECT_MSGS)
+ goto drop;
+
+ /* might be ours now */
+
+ rw_enter_write(&sc->sc_rx_data_keys_lk);
+
+ wk = wg_match_rx_data_keys(sc, hdr->index);
+ if (wk == NULL) {
+ /* not this connection */
+ goto unlock;
+ }
+
+ /* avoid {under,over}flow */
+ high = (counter >= wk->wk_rx_seq);
+ if (high)
+ diff = counter - wk->wk_rx_seq;
+ else
+ diff = wk->wk_rx_seq - counter;
+
+ if (diff >= WG_MSGS_WINDOW)
+ goto unlock;
+
+ m_adj(m, hlen);
+
+ rv = wg_decap(ifp, m, &wk->wk_rx_key, counter);
+
+ if (rv != 0)
+ goto drop;
+
+ /* actually ours now according to auth, so move forward */
+ if (high)
+ wk->wk_rx_seq = counter;
+
+ sc->sc_rx_stamp = ticks;
+
+ rw_exit_write(&sc->sc_rx_data_keys_lk);
+
+ if (counter >= WG_REKEY_MSGS)
+ wg_send_rekey(sc);
+
+ if (m->m_pkthdr.len == 0) {
+ /* keepalive */
+ return (NULL);
+ }
+
+ n = m;
+ while (n->m_len == 0) {
+ n = n->m_next;
+ if (n == NULL) {
+ ifp->if_ierrors++;
+ goto drop;
+ }
+ }
+
+ switch (*mtod(n, uint8_t *) >> 4) {
+ case 4:
+ input = ipv4_input;
+ m->m_pkthdr.ph_family = AF_INET;
+ break;
+
+#ifdef INET6
+ case 6:
+ input = ipv6_input;
+ m->m_pkthdr.ph_family = AF_INET6;
+ break;
+#endif
+ default:
+ ifp->if_noproto++;
+ goto drop;
+ }
+
+ m->m_flags &= ~(M_MCAST|M_BCAST);
+ m->m_pkthdr.ph_ifidx = ifp->if_index;
+ m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
+ m->m_pkthdr.ph_flowid = 0;
+
+#if NPF > 0
+ pf_pkt_addr_changed(m);
+#endif
+
+ ifp->if_ipackets++;
+ ifp->if_ibytes += m->m_pkthdr.len;
+
+#if NBPFILTER > 0
+ {
+ caddr_t if_bpf = ifp->if_bpf;
+ if (if_bpf) {
+ bpf_mtap_af(if_bpf, m->m_pkthdr.ph_family, m,
+ BPF_DIRECTION_IN);
+ }
+ }
+#endif
+
+ (*input)(ifp, m);
+
+ return (NULL);
+
+unlock:
+ rw_exit_write(&sc->sc_rx_data_keys_lk);
+drop:
+ m_freem(m);
+ return (NULL);
+}
+
+static int
+wg_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
+ struct rtentry *rt)
+{
+ struct wg_softc *sc = ifp->if_softc;
+ struct m_tag *mtag;
+ int error = 0;
+
+ if (!ISSET(ifp->if_flags, IFF_RUNNING) ||
+ sc->sc_fp == NULL ||
+ sc->sc_tx_data_keys == NULL) {
+ error = ENETDOWN;
+ goto drop;
+ }
+
+ switch (dst->sa_family) {
+ case AF_INET:
+#ifdef INET6
+ case AF_INET6:
+#endif
+ break;
+ default:
+ error = EAFNOSUPPORT;
+ goto drop;
+ }
+
+ /* Try to limit infinite recursion through misconfiguration. */
+ for (mtag = m_tag_find(m, PACKET_TAG_GRE, NULL); mtag;
+ mtag = m_tag_find(m, PACKET_TAG_GRE, mtag)) {
+ if (memcmp(mtag + 1, &ifp->if_index,
+ sizeof(ifp->if_index)) == 0) {
+ error = EIO;
+ goto drop;
+ }
+ }
+
+ mtag = m_tag_get(PACKET_TAG_GRE, sizeof(ifp->if_index), M_NOWAIT);
+ if (mtag == NULL) {
+ error = ENOBUFS;
+ goto drop;
+ }
+ memcpy(mtag + 1, &ifp->if_index, sizeof(ifp->if_index));
+ m_tag_prepend(m, mtag);
+
+ m->m_pkthdr.ph_family = dst->sa_family;
+
+ error = if_enqueue(ifp, m);
+ if (error)
+ ifp->if_oerrors++;
+ return (error);
+drop:
+ m_freem(m);
+ return (error);
+}
+
+static int
+wg_encap(struct wg_softc *sc, struct wg_data_keys *wk, struct mbuf *m0)
+{
+ struct wg_aead_ctx ctx;
+ struct wg_aead_tag *tag;
+ struct mbuf *mn, *m;
+ struct wg_data_hdr *hdr;
+ uint64_t counter = wk->wk_tx_seq++;
+ int padlen;
+
+ wg_aead_init(&ctx, &wk->wk_tx_key, counter);
+
+ mn = m0;
+ do {
+ m = mn;
+ mn = m->m_next;
+
+ if (m->m_len)
+ wg_aead_encrypt(&ctx, mtod(m, void *), m->m_len);
+ } while (mn != NULL);
+
+ padlen = m0->m_pkthdr.len % 16;
+ if (padlen) {
+ uint8_t *zero;
+
+ padlen = 16 - padlen;
+
+ if (m_trailingspace(m) < padlen) {
+ mn = m_get(M_DONTWAIT, MT_DATA);
+ if (mn == NULL)
+ goto drop;
+
+ m->m_next = mn;
+ m = mn;
+
+ m->m_len = 0;
+ }
+
+ zero = mtod(m, uint8_t *) + m->m_len;
+ memset(zero, 0, padlen);
+ wg_aead_encrypt(&ctx, zero, padlen);
+
+ m0->m_pkthdr.len += padlen;
+ m->m_len += padlen;
+ }
+
+ if (m_trailingspace(m) < sizeof(*tag)) {
+ mn = m_get(M_DONTWAIT, MT_DATA);
+ if (mn == NULL)
+ goto drop;
+
+ m->m_next = mn;
+ m = mn;
+
+ m->m_len = 0;
+ }
+
+ tag = (struct wg_aead_tag *)(mtod(m, uint8_t *) + m->m_len);
+ wg_aead_final(&ctx, tag);
+ explicit_bzero(&ctx, sizeof(ctx));
+
+ m0->m_pkthdr.len += sizeof(*tag);
+ m->m_len += sizeof(*tag);
+
+ m0 = m_prepend(m0, sizeof(*hdr), M_DONTWAIT);
+ if (m0 == NULL)
+ return (-1);
+
+ hdr = mtod(m0, struct wg_data_hdr *);
+ hdr->msg_type = htole32(WG_MSG_DATA);
+ hdr->index = wk->wk_tx_idx;
+ htolem32(&hdr->counter_lo, counter);
+ htolem32(&hdr->counter_hi, counter >> 32);
+
+ if (mq_enqueue(&sc->sc_tx_queue, m0) != 0)
+ return (-1);
+
+ task_add(systq, &sc->sc_tx_task);
+
+ return (0);
+
+drop:
+ m_freem(m0);
+ explicit_bzero(&ctx, sizeof(ctx));
+ return (-1);
+}
+
+static void
+wg_send_keepalive(void *v)
+{
+ struct wg_softc *sc = v;
+ struct wg_data_keys *wk = sc->sc_tx_data_keys;
+ struct mbuf *m;
+
+ if (wk == NULL)
+ return;
+
+ m = m_gethdr(M_DONTWAIT, MT_DATA);
+ if (m == NULL)
+ return;
+
+ m_align(m, sizeof(struct wg_aead_tag));
+ m->m_pkthdr.len = m->m_len = 0;
+
+ wg_encap(sc, wk, m);
+}
+
+static void
+wg_send(void *v)
+{
+ struct wg_softc *sc = v;
+ struct ifnet *ifp = &sc->sc_if;
+ struct file *fp;
+ struct socket *so;
+ struct mbuf_list ml;
+ struct mbuf *m;
+ int error;
+ int s;
+
+ mq_delist(&sc->sc_tx_queue, &ml);
+ if (ml_empty(&ml))
+ return;
+
+ fp = sc->sc_fp;
+ if (fp == NULL || (so = (struct socket *)fp->f_data) == NULL) {
+ ml_purge(&ml);
+ return;
+ }
+
+ /* XXX this knows too much about how udp_usrreq works internally. */
+
+ s = solock(so);
+ while ((m = ml_dequeue(&ml)) != NULL) {
+ error = udp_usrreq(so, PRU_SEND, m, NULL, NULL, NULL);
+ if (error)
+ ifp->if_oerrors++;
+ }
+ sounlock(so, s);
+
+ sc->sc_tx_stamp = ticks;
+
+}
+
+static void
+wg_start(struct ifqueue *ifq)
+{
+ struct ifnet *ifp = ifq->ifq_if;
+ struct wg_softc *sc = ifp->if_softc;
+ struct wg_data_keys *wk = sc->sc_tx_data_keys;
+ struct mbuf *m;
+
+ if (sc->sc_fp == NULL || wk == NULL) {
+ ifq_purge(ifq);
+ return;
+ }
+
+ while ((m = ifq_dequeue(ifq)) != NULL) {
+#if NBPFILTER
+ {
+ caddr_t if_bpf = ifp->if_bpf;
+ if (if_bpf) {
+ bpf_mtap_af(if_bpf, m->m_pkthdr.ph_family, m,
+ BPF_DIRECTION_OUT);
+ }
+ }
+#endif
+
+ if (!wg_encap(sc, wk, m)) {
+ ifq->ifq_errors++;
+ continue;
+ }
+ }
+}
+
+static void
+wg_aead_init(struct wg_aead_ctx *ctx, const struct chacha_key *key,
+ uint64_t counter)
+{
+ uint8_t block0[CHACHA_BLOCKSIZE];
+
+ chacha_stream_keysetup(&ctx->chacha20, key);
+
+ /*
+ * AEAD-ChaCha20-Poly1305 uses the IETF construction with the 96bit
+ * nonce and 32 bit counter starting from 0. WireGuard uses this AEAD
+ * with the counter off the wire as the nonce. That nonce is padded
+ * with zeros on the left, so the layout of the chacha state is
+ * predictable. Set it up directly, rather than stuff bits just so
+ * they can be unstuffed straight away.
+ */
+ ctx->chacha20.ctx.input[12] = 0; /* counter starts at 0 */
+ ctx->chacha20.ctx.input[13] = 0; /* nonce padding is 0 */
+ ctx->chacha20.ctx.input[14] = counter; /* the rest of the "nonce" */
+ ctx->chacha20.ctx.input[15] = counter >> 32;
+ ctx->chacha20.used = 0;
+
+ /* AEAD-ChaCha20-Poly1305 uses the first block for the poly key */
+ bzero(block0, sizeof(block0));
+ chacha_stream_update(&ctx->chacha20, block0, block0, sizeof(block0));
+ poly1305_init(&ctx->poly1305, block0);
+ explicit_bzero(block0, sizeof(block0));
+
+ /* data has no aad */
+
+ ctx->datalen = 0;
+}
+
+static void
+wg_aead_encrypt(struct wg_aead_ctx *ctx, void *mem, size_t len)
+{
+ chacha_stream_update(&ctx->chacha20, mem, mem, len);
+ poly1305_update(&ctx->poly1305, mem, len);
+ ctx->datalen += len;
+}
+
+static void
+wg_aead_verify(struct wg_aead_ctx *ctx, void *mem, size_t len)
+{
+ poly1305_update(&ctx->poly1305, mem, len);
+ ctx->datalen += len;
+}
+
+static void
+wg_aead_decrypt(struct wg_aead_ctx *ctx, void *mem, size_t len)
+{
+ chacha_stream_update(&ctx->chacha20, mem, mem, len);
+}
+
+static void
+wg_aead_final(struct wg_aead_ctx *ctx, struct wg_aead_tag *tag)
+{
+ uint8_t len[8];
+ uint64_t j;
+ unsigned int i;
+
+ /* data has no aad, so aad len is 0 */
+ memset(len, 0, sizeof(len));
+ poly1305_update(&ctx->poly1305, len, sizeof(len));
+
+ j = ctx->datalen;
+ for (i = 0; i < sizeof(len); i++) {
+ len[i] = j;
+ j >>= 8;
+ }
+ poly1305_update(&ctx->poly1305, len, sizeof(len));
+
+ poly1305_finish(&ctx->poly1305, tag->tag);
+}
diff --git a/sys/net/if_wg.h b/sys/net/if_wg.h
new file mode 100644
index 00000000000..107ae9c0b39
--- /dev/null
+++ b/sys/net/if_wg.h
@@ -0,0 +1,158 @@
+/* $OpenBSD: if_wg.h,v 1.1 2019/04/27 05:14:33 dlg Exp $ */
+
+/*
+ * Copyright (c) 2018 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _NET_IF_WG_H_
+#define _NET_IF_WG_H_
+
+/* the timing side of messages */
+
+#define WG_MSGS_WINDOW 2000
+
+#define WG_REKEY_MSGS (~0ULL - 0xffffULL)
+#define WG_REKEY_TIME 120000 /* msec */
+#define WG_KEEPALIVE 10000 /* msec */
+#define WG_REKEY_TIMEOUT 5000 /* msec */
+#define WG_REKEY_JITTER 333 /* msec */
+#define WG_REKEY_MAX 90000 /* msec */
+#define WG_REJECT_MSGS (~0ULL - WG_MSGS_WINDOW)
+#define WG_REJECT_TIME 180000 /* msec */
+
+
+/* the bytes side of messages */
+
+#define WG_POLY1305_TAG_LEN 16
+#define WG_CURVE25519_KEY_LEN 32
+#define WG_AEAD_CHACHA20_POLY1305_KEY_LEN \
+ 32
+#define WG_BLAKE2S_OUTPUT_LEN 16
+#define WG_CHACHA20_POLY1305_NONCE_LEN \
+ 24
+
+#define WG_AEAD_LEN(_l) ((_l) + WG_POLY1305_TAG_LEN)
+
+#define WG_ENCRYPTED_STATIC_LEN WG_AEAD_LEN(WG_CURVE25519_KEY_LEN)
+
+#define WG_MSG_HANDSHAKE_INIT 1
+#define WG_MSG_HANDSHAKE_RESP 2
+#define WG_MSG_COOKIE_RESP 3
+#define WG_MSG_DATA 4
+
+struct wg_header {
+ uint8_t msg_type;
+ uint8_t zero[3];
+} __packed __aligned(4);
+
+struct wg_tai64n {
+ uint64_t tai_seconds; /* BE */
+ uint32_t tai_nanoseconds; /* BE */
+} __packed __aligned(4);
+
+#define WG_ENCRYPTED_TAI64N_LEN WG_AEAD_LEN(sizeof(struct wg_tai64n))
+
+struct wg_handshake_init {
+ struct wg_header hdr;
+ uint32_t sndr_idx; /* LE */
+ uint8_t unencrypted_ephemeral[WG_CURVE25519_KEY_LEN];
+ uint8_t encrypted_static[WG_ENCRYPTED_STATIC_LEN];
+ uint8_t encrypted_timestamp[WG_ENCRYPTED_TAI64N_LEN];
+ uint8_t mac1[WG_BLAKE2S_OUTPUT_LEN];
+ uint8_t mac2[WG_BLAKE2S_OUTPUT_LEN];
+} __packed __aligned(4);
+
+struct wg_handshake_resp {
+ struct wg_header hdr;
+ uint32_t sndr_idx; /* LE */
+ uint32_t rcvr_idx; /* LE */
+ uint8_t unencrypted_ephemeral[WG_CURVE25519_KEY_LEN];
+ uint8_t encrypted_nothing[WG_AEAD_LEN(0)];
+ uint8_t mac1[WG_BLAKE2S_OUTPUT_LEN];
+ uint8_t mac2[WG_BLAKE2S_OUTPUT_LEN];
+} __packed __aligned(4);
+
+struct wg_cookie_resp {
+ struct wg_header hdr;
+ uint32_t rcvr_idx; /* LE */
+ uint8_t nonce[24];
+ uint8_t encrypted_cookie[WG_AEAD_LEN(0)];
+} __packed __aligned(4);
+
+struct wg_data {
+ struct wg_header hdr;
+ uint32_t rcvr_idx;
+ uint32_t counter_lo;
+ uint32_t counter_hi;
+
+ /* followed by encrypted data */
+} __packed __aligned(4);
+
+/*
+ * ioctl interface
+ */
+
+#include <sys/ioccom.h>
+
+#define WGIFCREATE _IOW('w', 10, unsigned int)
+#define WGIFATTACH _IOW('w', 11, unsigned int)
+#define WGIFDETACH _IOW('w', 12, unsigned int)
+#define WGIFDESTROY _IOW('w', 13, unsigned int)
+
+struct wg_if_sock {
+ unsigned int wg_unit;
+ int wg_sock;
+};
+
+#define WGIFSSOCK _IOW('w', 14, struct wg_if_sock)
+#define WGIFDSOCK _IOW('w', 15, unsigned int)
+
+struct wg_aead_key {
+ uint8_t key[WG_AEAD_CHACHA20_POLY1305_KEY_LEN];
+};
+
+struct wg_if_data_keys {
+ unsigned int wg_unit;
+ uint32_t wg_tx_index; /* LE */
+ uint32_t wg_rx_index; /* LE */
+ struct wg_aead_key wg_tx_key;
+ struct wg_aead_key wg_rx_key;
+};
+
+#define WGIFADDKEYS _IOW('w', 16, struct wg_if_data_keys)
+#define WGIFDELKEYS _IOW('w', 17, struct wg_if_data_keys)
+
+struct wg_if_role {
+ unsigned int wg_unit;
+ unsigned int wg_role;
+#define WG_DATA_INITIATOR 0x696e6974
+#define WG_DATA_RESPONDER 0x72657370
+};
+
+#define WGIFSROLE _IOW('w', 18, struct wg_if_role)
+#define WGIFGROLE _IOWR('w', 20, struct wg_if_role)
+
+/*
+ * messages via reads
+ */
+
+#define WG_MSG_REKEY 0x746b6579
+
+struct wg_msg {
+ unsigned int wg_unit;
+ unsigned int wg_type;
+};
+
+#endif /* _NET_IF_WG_H_ */