SEND(4)

πŸ’– SEND(4) πŸ’–

okay bestie, buckle up β€” this is the send kernel interface page from the Kernel Interfaces Manual, and it's lowkey one of the coolest things in the whole networking stack. no cap. let's get into it πŸ¦‹


✨ NAME ✨

send β€” kernel side support for Secure Neighbor Discovery (SeND). that's it, that's the whole vibe πŸ’…


✨ SYNOPSIS ✨

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet6/send.h>

int
socket(PF_INET6, SOCK_RAW, IPPROTO_SEND);

and if you wanna load the driver as a module at boot time, you put this line in loader.conf(5):

send_load="YES"

periodt. that's the whole setup 🌸


✨ DESCRIPTION ✨

so. IPv6 nodes use the Neighbor Discovery Protocol (NDP) to find other nodes on the link, to figure out their link-layer addresses, to find routers, and to keep track of reachability info about the paths to active members. very main character behavior. the problem? NDP is vulnerable to a whole bunch of attacks β€” see [RFC3756] for the receipts. Secure Neighbor Discovery is the set of extensions to NDP that counters those threats β€” that's [RFC3971], iykyk. πŸ‘‘

so the kernel side support for SeND is basically a kernel module with hooks. those hooks divert the relevant packets β€” and when we say relevant we mean Neighbor Solicitations, Neighbor Advertisements, Router Solicitations, Router Advertisements and Redirects β€” away from the NDP stack, send them to user space on a dedicated socket, and then reinject them back for further processing. and the important bit: hooks only get triggered if the send module is loaded. no module, no hooks, no drama πŸŽ€

the native SeND socket is basically a raw IP socket but with its own internal pseudo-protocol (IPPROTO_SEND). the struct sockaddr_send lives in <netinet6/send.h>, and it defines the total length of the structure, the address family, the packet's incoming or outgoing direction from the interface's point of view, and the interface index. iconic.

struct sockaddr_send {
        unsigned char           send_len;       /* total length */
        sa_family_t             send_family;    /* address family */
        int                     send_direction;
        int                     send_ifidx;
        char                    send_zero[8];
};

okay so breaking that down bestie:

  • the address family is always AF_INET6. no exceptions.
  • send_direction is the direction of the packet from the interface's point of view, and it's either the value SND_IN or SND_OUT.
  • send_ifidx is the interface index of the receiving or sending interface.
  • send_zero is just padding and it must always be zero. do not play with it. πŸ™ˆ

and here's a lil mercy for you: in case no user space application is connected to the send socket, processing just continues normally, as if the module was never loaded. slay, graceful degradation πŸ’œ


✨ INPUT HOOK ✨

the input hook is named after the input path of the incoming or outgoing NDP packets β€” the journey from the wire, through the nd6 stack, to user space. relevant packets get identified by adding an mbuf_tag (peep mbuf_tags(9)) to the mbuf(9), but only if the send module is loaded. after that it gets passed on to the kernel-userland interface, either for cryptographic protection or for validation by the SeND application. πŸ¦‹

the hook takes an argument that describes the direction of the packet β€” and this applies to both incoming and outgoing packets:

  • SND_IN β€” the direction of the incoming packets, which are usually protected by the SeND options and then sent to user space for cryptographic validation.
  • SND_OUT β€” the outgoing direction. it covers both reply packets and locally originated outgoing packets, which get sent to user space so SeND options can be added. πŸ’«

✨ INCOMING PACKETS ✨

the incoming ND packet from the wire:

                                               kernelspace ( userspace
                                                           )
        incoming SeND/ND packet                            (
                   |                                       )
                   v                 ( SND_IN )            (
                  icmp6_input() -> send_input_hook ---> send socket ----+
                   :                                       )            |
                   :             #                 #       (            |
          normal   :             #                 #       )            v
        processing :             #     send.ko     #       (    SeND application
           path    :             #                 #       )            |
                   :             #                 #       (            |
                   v                                       )            |
          icmp6/nd6_??_input() <- protocol switch  <--- send socket <---+
                   |         structure (IPPPROTO_SEND)     )
                   |                ( SND_IN )             (
                   v                                       )
        continue normal ND processing                      (

✨ OUTGOING PACKETS ✨

outgoing ND packet (reply or locally triggered):

                                               kernelspace ( userspace
                                                           )
        nd6_na_input()                                     (
        +PACKET_TAG_ND_OUTGOING                            )
        |                                                  )
        |   outgoing packet                                (
        |          |                                       )
        |          v                                       (
        |   icmp6_redirect_output()                        )
        |   nd6_ns_output()                                (
        |   nd6_na_output()                                )
        |   +PACKET_TAG_ND_OUTGOING                        (
        |          |                                       )
        |          +-----------<- rip6_output() <----------)----- rtsol/rtadvd/..
        |          |              +PACKET_TAG_ND_OUTGOING  (
        |          v                                       )
        |       ip6_output()                               (
        |          |                                       )
        +-------->-+                                       (
                   |                                       )
                   v                ( SND_OUT )            (
               nd6_output_lle() -> send_input_hook ---> send socket ----+
        -PACKET_TAG_ND_OUTGOING                            )            |
                   :             #                 #       (            |
          normal   :             #                 #       )            v
        processing :             #     send.ko     #       (    SeND application
           path    :             #                 #       )            |
                   :             #                 #       (            |
                   v                                       )            |
           (*ifp->if_output)() <- protocol switch  <--- send socket <---+
                   |         structure (IPPPROTO_SEND)     )
                   |                ( SND_OUT )            (
                   v                                       )
        continue with normal packet output                 (

✨ ERRORS ✨

a socket operation may fail, and when it does, it hands you one of these:

  • [EEXIST] β€” another user space SeND application is already bound to the socket. one app at a time, bestie, it's giving exclusive πŸ’€
  • [ENOBUFS] β€” shortage of space to receive the incoming (SeND-protected) or outgoing (SeND-validated) packet from the SeND application. out of room, out of luck 😭
  • [ENOSYS] β€” a packet received from user space and passed to the NDP stack for further processing is neither Neighbor Solicitation, Neighbor Advertisement, Router Solicitation, Router Advertisement nor Redirect. wrong packet, wrong party 🚫
  • [ENOENT] β€” occurs if interface output routines fail to send the packet out of the interface. the packet did not make it out the door πŸ™ˆ

✨ SEE ALSO ✨

recvfrom(2), sendto(2), socket(2), loader.conf(5)

go read those too, they're the supporting cast and they ate πŸ’…


✨ HISTORY ✨

the send module first appeared in FreeBSD 9.0. a legend was born ✨


✨ AUTHORS ✨

Ana Kukec <anchie@FreeBSD.org>, University of Zagreb πŸ‘‘


✨ BUGS ✨

due to the lack of NDP locking, it is currently not possible to unload the send module. yeah. once it's in, it's in. no takebacks, no unload, it's committed to the relationship fr fr πŸ’€

and that's the whole page bestie!! this one came from the Debian folks and it's dated September 19, 2010 β€” an absolute vintage classic, still serving looks in SEND(4) to this very day πŸŽ€πŸ’–βœ¨

πŸ“¦ where this page comes from