the last assignment i set for the operating systems course i was
helping with was to write a driver for a virtual network interface
that implemented the client side of a protocol i made up.

the protocol was largely inspired by vxlan, but requires some
negotiation for a client to get a working link with the concentrator
(server).

the spec is up at http://www.uq.id.au/dlg/comp3301/assignment3.pdf.

i wrote a dodgy server for the students to run their code against,
which is up at https://source.eait.uq.edu.au/viewvc/comp3301-pracs/2015/.

my implementation of the driver (so i could test the server) is
below.

Index: conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/conf/GENERIC,v
retrieving revision 1.220
diff -u -p -r1.220 GENERIC
--- conf/GENERIC        10 Aug 2015 20:35:36 -0000      1.220
+++ conf/GENERIC        29 Oct 2015 23:14:30 -0000
@@ -105,6 +105,7 @@ pseudo-device       tun             # network tunneling o
 pseudo-device  vether          # Virtual ethernet
 pseudo-device  vxlan           # Virtual extensible LAN
 pseudo-device  vlan            # IEEE 802.1Q VLAN
+pseudo-device  eou
 
 pseudo-device  bio     1       # ioctl multiplexing device
 
Index: conf/files
===================================================================
RCS file: /cvs/src/sys/conf/files,v
retrieving revision 1.603
diff -u -p -r1.603 files
--- conf/files  28 Sep 2015 08:32:04 -0000      1.603
+++ conf/files  29 Oct 2015 23:14:30 -0000
@@ -541,6 +541,7 @@ pseudo-device trunk: ifnet, ether, ifmed
 pseudo-device mpe: ifnet, ether
 pseudo-device mpw: ifnet, ether
 pseudo-device vether: ifnet, ether
+pseudo-device eou: ifnet, ether
 pseudo-device pppx: ifnet
 pseudo-device vxlan: ifnet, ether, ifmedia
 
@@ -786,6 +787,7 @@ file net/trunklacp.c                        trunk
 file net/if_mpe.c                      mpe                     needs-count
 file net/if_mpw.c                      mpw & bridge            needs-count
 file net/if_vether.c                   vether                  needs-count
+file net/if_eou.c                      eou                     needs-count
 file net/if_pppx.c                     pppx                    needs-count
 file net/if_vxlan.c                    vxlan                   needs-count
 file net80211/ieee80211.c              wlan
Index: net/if_eou.c
===================================================================
RCS file: net/if_eou.c
diff -N net/if_eou.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ net/if_eou.c        29 Oct 2015 23:14:32 -0000
@@ -0,0 +1,781 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Theo de Raadt
+ * Copyright (c) 2015 David Gwynne <d...@uq.edu.au>
+ *
+ * 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/systm.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/ioctl.h>
+#include <sys/protosw.h>
+#include <sys/socketvar.h>
+#include <sys/task.h>
+#include <sys/timeout.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <crypto/siphash.h>
+
+#include "bpfilter.h"
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+/*
+ * protocol
+ */
+struct eou_header {
+       uint32_t                eou_network;
+       uint16_t                eou_type;
+} __packed;
+
+#define EOU_T_DATA      0x0000
+#define EOU_T_PING      0x8000
+#define EOU_T_PONG      0x8001
+
+struct eou_pingpong {
+       struct eou_header       hdr;
+       uint16_t                _pad;
+       uint64_t                utime;
+       uint8_t                 random[32];
+       uint8_t                 mac[8];
+} __packed;
+
+#define EOU_PORT       3301
+
+/*
+ * driver
+ */
+struct eou_softc;
+TAILQ_HEAD(eou_softcs, eou_softc);
+
+struct eou_socket {
+       TAILQ_ENTRY(eou_socket)  eso_entry;
+       struct eou_softcs        eso_softcs;
+
+       struct socket           *eso_so;
+       struct sockaddr_storage  eso_src;
+       struct sockaddr_storage  eso_dst;
+};
+
+struct eou_softc {
+       struct arpcom            sc_ac;
+       struct ifmedia           sc_media;
+
+       uint32_t                 sc_vnetid; /* network byte order */
+
+       struct eou_socket       *sc_eso;
+       TAILQ_ENTRY(eou_softc)   sc_entry;
+
+       struct task              sc_send;
+       struct timeout           sc_tick;
+       struct task              sc_ping;
+       struct timeout           sc_dead;
+};
+
+TAILQ_HEAD(eou_sockets, eou_socket) eou_sockets;
+union {
+       uint8_t         bytes[SIPHASH_KEY_LENGTH];
+       SIPHASH_KEY     key;
+} _eou_key = {
+       .bytes = {
+               'c', 'o', 'm', 'p', '3', '3', '0', '1',
+               'c', 'o', 'm', 'p', '7', '3', '0', '8'
+       }
+};
+#define eou_key _eou_key.key
+
+void   eouattach(int);
+
+int    eou_create(struct if_clone *, int);
+int    eou_destroy(struct ifnet *);
+
+int    eou_ioctl(struct ifnet *, u_long, caddr_t);
+void   eou_start(struct ifnet *);
+int    eou_media_change(struct ifnet *);
+void   eou_media_status(struct ifnet *, struct ifmediareq *);
+
+int    eou_can_run(struct eou_softc *);
+void   eou_run(struct eou_softc *);
+void   eou_stop(struct eou_softc *);
+void   eou_bounce(struct eou_softc *);
+
+int    eou_set_tunnel(struct eou_softc *, struct if_laddrreq *);
+int    eou_get_tunnel(struct eou_softc *, struct if_laddrreq *);
+int    eou_del_tunnel(struct eou_softc *, struct if_laddrreq *);
+
+int    eou_vnetid_used(struct eou_socket *, uint32_t);
+int    eou_socreate(struct eou_softc *, const struct sockaddr *,
+           const struct sockaddr *, struct eou_socket **);
+void   eou_soclose(struct eou_softc *, struct eou_socket *);
+
+void   eou_upcall(struct socket *, caddr_t, int);
+void   eou_input(struct eou_socket *, struct mbuf *);
+
+void   eou_send(void *);
+void   eou_tick(void *);
+void   eou_ping(void *);
+void   eou_dead(void *);
+
+void   eou_pong(struct eou_softc *, struct mbuf *);
+
+struct if_clone        eou_cloner =
+    IF_CLONE_INITIALIZER("eou", eou_create, eou_destroy);
+
+void
+eouattach(int n)
+{
+       TAILQ_INIT(&eou_sockets);
+       if_clone_attach(&eou_cloner);
+}
+
+int
+eou_create(struct if_clone *ifc, int unit)
+{
+       struct eou_softc *sc;
+       struct ifnet *ifp;
+
+       sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
+
+       task_set(&sc->sc_send, eou_send, sc);
+       timeout_set(&sc->sc_tick, eou_tick, sc);
+       task_set(&sc->sc_ping, eou_ping, sc);
+       timeout_set(&sc->sc_dead, eou_dead, sc);
+
+       ifp = &sc->sc_ac.ac_if;
+       snprintf(ifp->if_xname, sizeof(ifp->if_xname), "%s%d",
+           ifc->ifc_name, unit);
+       ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+       ether_fakeaddr(ifp);
+
+       ifp->if_softc = sc;
+       ifp->if_ioctl = eou_ioctl;
+       ifp->if_start = eou_start;
+       IFQ_SET_MAXLEN(&ifp->if_snd, 1);
+       IFQ_SET_READY(&ifp->if_snd);
+
+       ifp->if_capabilities = IFCAP_VLAN_MTU;
+       ifp->if_link_state = LINK_STATE_DOWN;
+
+       ifmedia_init(&sc->sc_media, 0, eou_media_change, eou_media_status);
+       ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_AUTO, 0, NULL);
+       ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_AUTO);
+
+       if_attach(ifp);
+       ether_ifattach(ifp);
+       return (0);
+}
+
+int
+eou_destroy(struct ifnet *ifp)
+{
+       struct eou_softc *sc = ifp->if_softc;
+
+       timeout_del(&sc->sc_tick);
+       timeout_del(&sc->sc_dead);
+
+       if (sc->sc_eso != NULL)
+               eou_soclose(sc, sc->sc_eso);
+
+       ifmedia_delete_instance(&sc->sc_media, IFM_INST_ANY);
+       ether_ifdetach(ifp);
+       if_detach(ifp);
+       free(sc, M_DEVBUF, sizeof(*sc));
+       return (0);
+}
+
+void
+eou_upcall(struct socket *so, caddr_t arg, int waitflag)
+{
+       struct eou_socket *eso = (struct eou_socket *)arg;
+       struct mbuf *m;
+       struct uio uio;
+       int flags;
+       int error;
+
+       KASSERT(so == eso->eso_so);
+
+       do {
+               uio.uio_resid = 1000000000;
+               flags = MSG_DONTWAIT;
+               error = soreceive(so, NULL, &uio, &m, NULL, &flags, 0);
+               if (m != NULL)
+                       eou_input(eso, m);
+
+               if (error != EWOULDBLOCK &&
+                   ISSET(so->so_proto->pr_flags, PR_CONNREQUIRED)) {
+                       /* link down? */
+                       break;
+               }
+       } while (m != NULL);
+}
+
+void
+eou_input(struct eou_socket *eso, struct mbuf *m)
+{
+       struct eou_softc *sc;
+       struct ifnet *ifp;
+       struct mbuf *mp;
+       struct eou_header *eouh;
+       int offp;
+       struct mbuf_list ml;
+
+       mp = m_pulldown(m, 0, sizeof(*eouh), &offp);
+       if (mp == NULL)
+               return;
+
+       eouh = (struct eou_header *)(mp->m_data + offp);
+       TAILQ_FOREACH(sc, &eso->eso_softcs, sc_entry) {
+               if (eouh->eou_network == sc->sc_vnetid)
+                       break;
+       }
+       if (sc == NULL)
+               goto done;
+
+       switch (bemtoh16(&eouh->eou_type)) {
+       case EOU_T_DATA:
+               ifp = &sc->sc_ac.ac_if;
+               if (!LINK_STATE_IS_UP(ifp->if_link_state))
+                       goto done;
+
+               m_adj(m, sizeof(*eouh));
+               ml_init(&ml);
+               ml_enqueue(&ml, m);
+               if_input(ifp, &ml);
+               break;
+
+       case EOU_T_PONG:
+               eou_pong(sc, m);
+               break;
+       default:
+               m_freem(m);
+               break;
+       }
+
+       return;
+done:
+       m_freem(m);
+}
+
+/* ARGSUSED */
+int
+eou_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+       struct eou_softc *sc = (struct eou_softc *)ifp->if_softc;
+       struct ifaddr *ifa = (struct ifaddr *)data;
+       struct ifreq *ifr = (struct ifreq *)data;
+       uint32_t vnetid;
+       int error = 0;
+
+       switch (cmd) {
+       case SIOCSIFADDR:
+               ifp->if_flags |= IFF_UP;
+               if (ifa->ifa_addr->sa_family == AF_INET)
+                       arp_ifinit(&sc->sc_ac, ifa);
+               /* FALLTHROUGH */
+
+       case SIOCSIFFLAGS:
+               if (eou_can_run(sc))
+                       eou_run(sc);
+               else
+                       eou_stop(sc);
+               break;
+
+       case SIOCADDMULTI:
+       case SIOCDELMULTI:
+               break;
+
+       case SIOCGIFMEDIA:
+       case SIOCSIFMEDIA:
+               error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd);
+               break;
+
+       case SIOCSLIFPHYADDR:
+               error = eou_set_tunnel(sc, (struct if_laddrreq *)data);
+               break;
+       case SIOCGLIFPHYADDR:
+               error = eou_get_tunnel(sc, (struct if_laddrreq *)data);
+               break;
+       case SIOCDIFPHYADDR:
+               error = eou_del_tunnel(sc, (struct if_laddrreq *)data);
+               break;
+
+       case SIOCSVNETID:
+               if ((error = suser(curproc, 0)) != 0)
+                       break;
+
+               vnetid = htobe32(ifr->ifr_vnetid);
+               if (sc->sc_vnetid == vnetid)
+                       break;
+
+               if (sc->sc_eso != NULL &&
+                   eou_vnetid_used(sc->sc_eso, vnetid)) {
+                       error = EINVAL;
+                       break;
+               }
+
+               sc->sc_vnetid = vnetid;
+               if (ISSET(ifp->if_flags, IFF_RUNNING))
+                       eou_bounce(sc);
+                break;
+
+       case SIOCGVNETID:
+               ifr->ifr_vnetid = betoh32(sc->sc_vnetid);
+               break;
+
+       default:
+               error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
+       }
+
+       return (error);
+}
+
+int
+eou_can_run(struct eou_softc *sc)
+{
+       struct ifnet *ifp = &sc->sc_ac.ac_if;
+
+       return (ISSET(ifp->if_flags, IFF_UP) && sc->sc_eso != NULL);
+}
+
+void
+eou_bounce(struct eou_softc *sc)
+{
+       eou_stop(sc);
+       eou_run(sc);
+}
+
+void
+eou_run(struct eou_softc *sc)
+{
+       struct ifnet *ifp = &sc->sc_ac.ac_if;
+
+       if (ISSET(ifp->if_flags, IFF_RUNNING))
+               return;
+
+       SET(ifp->if_flags, IFF_RUNNING);
+
+       eou_tick(sc);
+}
+
+void
+eou_stop(struct eou_softc *sc)
+{
+       struct ifnet *ifp = &sc->sc_ac.ac_if;
+
+       if (!ISSET(ifp->if_flags, IFF_RUNNING))
+               return;
+
+       if (LINK_STATE_IS_UP(ifp->if_link_state)) {
+               ifp->if_link_state = LINK_STATE_DOWN;
+               if_link_state_change(ifp);
+       }
+
+       timeout_del(&sc->sc_tick);
+       timeout_del(&sc->sc_dead);
+
+       CLR(ifp->if_flags, IFF_RUNNING);
+}
+
+int
+eou_set_tunnel(struct eou_softc *sc, struct if_laddrreq *req)
+{
+       struct eou_socket *eso, *oeso;
+       struct sockaddr *src = (struct sockaddr *)&req->addr;
+       struct sockaddr *dst = (struct sockaddr *)&req->dstaddr;
+       struct sockaddr_in *sin;
+       struct sockaddr_in6 *sin6;
+       int error;
+
+       /* sa_family and sa_len must be equal */
+       if (src->sa_family != dst->sa_family || src->sa_len != dst->sa_len)
+               return (EINVAL);
+
+       /* validate */
+       switch (dst->sa_family) {
+       case AF_INET:
+               if (dst->sa_len != sizeof(*sin))
+                       return (EINVAL);
+
+               sin = (struct sockaddr_in *)src;
+               if (in_nullhost(sin->sin_addr))
+                       return (EINVAL);
+
+               sin = (struct sockaddr_in *)dst;
+               if (in_nullhost(sin->sin_addr))
+                       return (EINVAL);
+               if (sin->sin_port == htons(0))
+                       sin->sin_port = htons(EOU_PORT);
+               break;
+#ifdef INET6
+       case AF_INET6:
+               if (dst->sa_len != sizeof(*sin6))
+                       return (EINVAL);
+
+               sin6 = (struct sockaddr_in6 *)src;
+               if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+                       return (EINVAL);
+
+               sin6 = (struct sockaddr_in6 *)dst;
+               if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+                       return (EINVAL);
+               if (sin6->sin6_port == htons(0))
+                       sin6->sin6_port = htons(EOU_PORT);
+               break;
+#endif
+       default:
+               return (EAFNOSUPPORT);
+       }
+
+       oeso = sc->sc_eso;
+       if (oeso != NULL) {
+               if (memcmp(&req->addr, &oeso->eso_src,
+                   oeso->eso_src.ss_len) == 0 &&
+                   memcmp(&req->dstaddr, &oeso->eso_dst,
+                   oeso->eso_dst.ss_len) == 0)
+                       return (0);
+
+               eou_soclose(sc, oeso);
+       }
+
+       /* let's go */
+       error = eou_socreate(sc, src, dst, &eso);
+       if (error != 0)
+               return (error);
+
+       /* commit */
+       sc->sc_eso = eso;
+
+       if (ISSET(sc->sc_ac.ac_if.if_flags, IFF_RUNNING))
+               eou_bounce(sc);
+
+       return (0);
+}
+
+int
+eou_vnetid_used(struct eou_socket *eso, uint32_t vnetid)
+{
+       struct eou_softc *sc;
+
+       TAILQ_FOREACH(sc, &eso->eso_softcs, sc_entry) {
+               if (sc->sc_vnetid == vnetid)
+                       return (1);
+       }
+
+       return (0);
+}
+
+int
+eou_socreate(struct eou_softc *sc, const struct sockaddr *src,
+    const struct sockaddr *dst, struct eou_socket **esop)
+{
+       struct eou_socket *eso;
+       struct socket *so;
+       struct sockaddr *nam;
+       struct mbuf *m;
+       int error;
+       int s;
+
+       TAILQ_FOREACH(eso, &eou_sockets, eso_entry) {
+               if (memcmp(src, &eso->eso_src, eso->eso_src.ss_len) == 0 &&
+                   memcmp(dst, &eso->eso_dst, eso->eso_dst.ss_len) == 0) {
+                       if (eou_vnetid_used(eso, sc->sc_vnetid))
+                               return (EINVAL);
+
+                       goto done;
+               }
+       }
+
+       eso = malloc(sizeof(*eso), M_IFADDR, M_WAITOK|M_ZERO);
+
+       error = socreate(dst->sa_family, &so, SOCK_DGRAM, 0);
+       if (error != 0)
+               goto free;
+
+       MGET(m, M_WAIT, MT_SONAME);
+       m->m_len = src->sa_len;
+       nam = mtod(m, struct sockaddr *);
+
+       memcpy(nam, src, src->sa_len);
+       error = sobind(so, m, curproc);
+       if (error)
+               goto close;
+
+       memcpy(nam, dst, dst->sa_len);
+
+       s = splsoftnet();
+       error = soconnect(so, m);
+       if (error != 0)
+               goto splx;
+
+       while (ISSET(so->so_state, SS_ISCONNECTING) && so->so_error == 0) {
+               error = tsleep(&so->so_timeo, PSOCK | PCATCH, "eoucon", 0);
+               if (error != 0)
+                       goto splx;
+       }
+       splx(s);
+
+       error = so->so_error;
+       if (error != 0)
+               goto close;
+
+       m_freem(m);
+
+       so->so_upcall = eou_upcall;
+       so->so_upcallarg = (caddr_t)eso;
+
+       eso->eso_so = so;
+       memcpy(&eso->eso_src, src, src->sa_len);
+       memcpy(&eso->eso_dst, dst, dst->sa_len);
+       TAILQ_INIT(&eso->eso_softcs);
+
+       TAILQ_INSERT_TAIL(&eou_sockets, eso, eso_entry);
+
+done:
+       TAILQ_INSERT_TAIL(&eso->eso_softcs, sc, sc_entry);
+       *esop = eso;
+       return (0);
+
+splx:
+       splx(s);
+close:
+       m_freem(m);
+       soclose(so);
+free:
+       free(eso, M_IFADDR, sizeof(*eso));
+       return (error);
+}
+
+static inline int
+eou_sosend(struct eou_softc *sc, struct mbuf *m)
+{
+       KASSERT(sc->sc_eso);
+
+       return (sosend(sc->sc_eso->eso_so, NULL, NULL, m, NULL, MSG_DONTWAIT));
+}
+
+void
+eou_soclose(struct eou_softc *sc, struct eou_socket *eso)
+{
+       TAILQ_REMOVE(&eso->eso_softcs, sc, sc_entry);
+       if (!TAILQ_EMPTY(&eso->eso_softcs))
+               return;
+
+       soclose(eso->eso_so);
+       TAILQ_REMOVE(&eou_sockets, eso, eso_entry);
+       free(eso, M_IFADDR, sizeof(*eso));
+}
+
+int
+eou_get_tunnel(struct eou_softc *sc, struct if_laddrreq *req)
+{
+       struct eou_socket *eso;
+
+       eso = sc->sc_eso;
+       if (eso == NULL)
+               return (EADDRNOTAVAIL);
+
+       KASSERT(sizeof(req->addr) == sizeof(eso->eso_src));
+       KASSERT(sizeof(req->dstaddr) == sizeof(eso->eso_dst));
+
+       memcpy(&req->addr, &eso->eso_src, sizeof(req->addr));
+       memcpy(&req->dstaddr, &eso->eso_dst, sizeof(req->dstaddr));
+
+       return (0);
+}
+
+int
+eou_del_tunnel(struct eou_softc *sc, struct if_laddrreq *req)
+{
+       struct ifnet *ifp = &sc->sc_ac.ac_if;
+       struct eou_socket *eso;
+
+       eso = sc->sc_eso;
+       if (eso == NULL)
+               return (EADDRNOTAVAIL);
+
+       eou_soclose(sc, eso);
+       sc->sc_eso = NULL;
+
+       if (ISSET(ifp->if_flags, IFF_UP)) {
+               CLR(ifp->if_flags, IFF_RUNNING);
+               ifp->if_link_state = LINK_STATE_DOWN;
+               if_link_state_change(ifp);
+       }
+
+       return (0);
+}
+
+int
+eou_media_change(struct ifnet *ifp)
+{
+       return (0);
+}
+
+void
+eou_media_status(struct ifnet *ifp, struct ifmediareq *imr)
+{
+       if (LINK_STATE_IS_UP(ifp->if_link_state)) {
+               imr->ifm_status = IFM_AVALID | IFM_ACTIVE;
+               imr->ifm_active = IFM_ETHER | IFM_AUTO | IFM_FDX;
+       } else {
+               imr->ifm_status = IFM_AVALID;
+               imr->ifm_active = IFM_ETHER | IFM_AUTO;
+       }
+}
+
+void
+eou_mac(const SIPHASH_KEY *key, const struct eou_pingpong *p, void *digest)
+{
+       SIPHASH_CTX ctx;
+
+       SipHash24_Init(&ctx, key);
+       SipHash24_Update(&ctx, &p->hdr.eou_network, sizeof(p->hdr.eou_network));
+       SipHash24_Update(&ctx, &p->utime, sizeof(p->utime));
+       SipHash24_Update(&ctx, &p->random, sizeof(p->random));
+       SipHash24_Final(digest, &ctx);
+}
+
+void
+eou_tick(void *x)
+{
+       struct eou_softc *sc = x;
+       task_add(systq, &sc->sc_ping);
+       timeout_add_sec(&sc->sc_tick, 30);
+}
+
+void
+eou_start(struct ifnet *ifp)
+{
+       struct eou_softc *sc = ifp->if_softc;
+
+       task_add(systq, &sc->sc_send);
+}
+
+void
+eou_send(void *scp)
+{
+       struct eou_softc *sc = scp;
+       struct ifnet *ifp = &sc->sc_ac.ac_if;
+       struct eou_header eouh;
+       struct mbuf *m;
+
+       eouh.eou_network = sc->sc_vnetid;
+       eouh.eou_type = htobe16(EOU_T_DATA);
+
+       for (;;) {
+               IFQ_DEQUEUE(&ifp->if_snd, m);
+               if (m == NULL)
+                       return;
+
+#if NBPFILTER > 0
+               if (ifp->if_bpf)
+                       bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
+#endif /* NBPFILTER > 0 */
+
+               m = m_prepend(m, sizeof(eouh), M_WAITOK);
+               if (m == NULL) {
+                       ifp->if_oerrors++;
+                       continue;
+               }
+
+               memcpy(mtod(m, void *), &eouh, sizeof(eouh));
+
+               if (eou_sosend(sc, m) != 0) {
+                       ifp->if_oerrors++;
+                       continue;
+               }
+
+               ifp->if_opackets++;
+       }
+}
+
+void
+eou_ping(void *x)
+{
+       struct eou_softc *sc = x;
+       struct mbuf *m;
+       struct eou_pingpong *ping;
+
+       MGETHDR(m, M_WAIT, MT_DATA);
+       if (max_linkhdr + max_protohdr + sizeof(*ping) > MHLEN)
+               MCLGET(m, M_WAIT);
+
+       m->m_data += max_linkhdr + max_protohdr;
+       m->m_len = m->m_pkthdr.len = sizeof(*ping);
+
+       ping = mtod(m, struct eou_pingpong *);
+       ping->hdr.eou_network = sc->sc_vnetid;
+       htobem16(&ping->hdr.eou_type, EOU_T_PING);
+       ping->_pad = 0;
+       htobem64(&ping->utime, time_second);
+       arc4random_buf(ping->random, sizeof(ping->random));
+       eou_mac(&eou_key, ping, ping->mac);
+
+       eou_sosend(sc, m);
+}
+
+void
+eou_pong(struct eou_softc *sc, struct mbuf *m)
+{
+       struct ifnet *ifp = &sc->sc_ac.ac_if;
+       struct mbuf *mp;
+       struct eou_pingpong *pong;
+       int offp;
+       uint8_t mac[8];
+       uint64_t utime;
+
+       mp = m_pulldown(m, 0, sizeof(*pong), &offp);
+       if (mp == NULL)
+               return;
+
+       pong = (struct eou_pingpong *)(mp->m_data + offp);
+       eou_mac(&eou_key, pong, mac);
+       if (memcmp(mac, pong->mac, sizeof(mac)) != 0)
+               goto done;
+
+       utime = bemtoh64(&pong->utime);
+       if (utime > time_second + 30 || utime < time_second - 30)
+               goto done;
+
+       /* validation succeeded */
+       if (!LINK_STATE_IS_UP(ifp->if_link_state)) {
+               ifp->if_link_state = LINK_STATE_UP;
+               if_link_state_change(ifp);
+       }
+
+       timeout_add_sec(&sc->sc_dead, 100); 
+
+done:
+       m_freem(m);
+}
+
+void
+eou_dead(void *x)
+{
+       struct eou_softc *sc = x;
+       struct ifnet *ifp = &sc->sc_ac.ac_if;
+
+       ifp->if_link_state = LINK_STATE_DOWN;
+       if_link_state_change(ifp);
+}

Reply via email to