+
+
+/* ----------------------------------------------------------------------------------
+Application of kernel control for interface creation
+
+Theory of operation:
+ipsecif acts as glue between kernel control sockets and ipsec network interfaces. This
+kernel control will register an interface for every client that connects.
+ipsec interface do not send or receive packets, an they are intercepted by ipsec before
+they reach the interface. ipsec needs interface to attach tunnel ip addresses.
+In the future, we may want to change the control mechanism to use PF_KEY to create
+interfaces for ipsec
+---------------------------------------------------------------------------------- */
+
+#include <sys/systm.h>
+//#include "if_ip.h"
+#include <sys/kern_control.h>
+#include <net/kpi_protocol.h>
+#include <net/kpi_interface.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/bpf.h>
+#include <libkern/OSMalloc.h>
+#include <libkern/OSAtomic.h>
+#include <sys/mbuf.h> /* Until leopard, our ugly bpf protocol prepend will need this */
+#include <sys/sockio.h>
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+
+/*
+*/
+
+#define IPSECIF_CONTROL_NAME "com.apple.net.ipsecif_control"
+
+/* Kernel Control functions */
+static errno_t ipsecif_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac,
+ void **unitinfo);
+static errno_t ipsecif_ctl_disconnect(kern_ctl_ref kctlref, u_int32_t unit,
+ void *unitinfo);
+static errno_t ipsecif_ctl_send(kern_ctl_ref kctlref, u_int32_t unit,
+ void *unitinfo, mbuf_t m, int flags);
+
+/* Network Interface functions */
+static errno_t ipsecif_output(ifnet_t interface, mbuf_t data);
+static errno_t ipsecif_demux(ifnet_t interface, mbuf_t data, char *frame_header,
+ protocol_family_t *protocol);
+static errno_t ipsecif_add_proto(ifnet_t interface, protocol_family_t protocol,
+ const struct ifnet_demux_desc *demux_array,
+ u_int32_t demux_count);
+static errno_t ipsecif_del_proto(ifnet_t interface, protocol_family_t protocol);
+static errno_t ipsecif_ioctl(ifnet_t interface, u_int32_t cmd, void *data);
+static errno_t ipsecif_settap(ifnet_t interface, bpf_tap_mode mode,
+ bpf_packet_func callback);
+static void ipsecif_detached(ifnet_t interface);
+
+/* Protocol handlers */
+static errno_t ipsecif_attach_proto(ifnet_t interface, protocol_family_t proto);
+static errno_t ipsecif_proto_input(ifnet_t interface, protocol_family_t protocol,
+ mbuf_t m, char *frame_header);
+
+/* Control block allocated for each kernel control connection */
+struct ipsecif_pcb {
+ kern_ctl_ref ctlref;
+ u_int32_t unit;
+ ifnet_t ifp;
+ bpf_tap_mode mode;
+ bpf_packet_func tap;
+};
+
+static kern_ctl_ref ipsecif_kctlref;
+static u_int32_t ipsecif_family;
+static OSMallocTag ipsecif_malloc_tag;
+static SInt32 ipsecif_ifcount = 0;
+
+/* Prepend length */
+static void*
+ipsecif_alloc(size_t size)
+{
+ size_t *mem = OSMalloc(size + sizeof(size_t), ipsecif_malloc_tag);
+
+ if (mem) {
+ *mem = size + sizeof(size_t);
+ mem++;
+ }
+
+ return (void*)mem;
+}
+
+static void
+ipsecif_free(void *ptr)
+{
+ size_t *size = ptr;
+ size--;
+ OSFree(size, *size, ipsecif_malloc_tag);
+}
+
+static errno_t
+ipsecif_register_control(void)
+{
+ struct kern_ctl_reg kern_ctl;
+ errno_t result = 0;
+
+ /* Create a tag to allocate memory */
+ ipsecif_malloc_tag = OSMalloc_Tagalloc(IPSECIF_CONTROL_NAME, OSMT_DEFAULT);
+
+ /* Find a unique value for our interface family */
+ result = mbuf_tag_id_find(IPSECIF_CONTROL_NAME, &ipsecif_family);
+ if (result != 0) {
+ printf("ipsecif_register_control - mbuf_tag_id_find_internal failed: %d\n", result);
+ return result;
+ }
+
+ bzero(&kern_ctl, sizeof(kern_ctl));
+ strncpy(kern_ctl.ctl_name, IPSECIF_CONTROL_NAME, sizeof(kern_ctl.ctl_name));
+ kern_ctl.ctl_name[sizeof(kern_ctl.ctl_name) - 1] = 0;
+ kern_ctl.ctl_flags = CTL_FLAG_PRIVILEGED; /* Require root */
+ kern_ctl.ctl_connect = ipsecif_ctl_connect;
+ kern_ctl.ctl_disconnect = ipsecif_ctl_disconnect;
+ kern_ctl.ctl_send = ipsecif_ctl_send;
+
+ result = ctl_register(&kern_ctl, &ipsecif_kctlref);
+ if (result != 0) {
+ printf("ipsecif_register_control - ctl_register failed: %d\n", result);
+ return result;
+ }
+
+ /* Register the protocol plumbers */
+ if ((result = proto_register_plumber(PF_INET, ipsecif_family,
+ ipsecif_attach_proto, NULL)) != 0) {
+ printf("ipsecif_register_control - proto_register_plumber(PF_INET, %d) failed: %d\n",
+ ipsecif_family, result);
+ ctl_deregister(ipsecif_kctlref);
+ return result;
+ }
+
+ /* Register the protocol plumbers */
+ if ((result = proto_register_plumber(PF_INET6, ipsecif_family,
+ ipsecif_attach_proto, NULL)) != 0) {
+ proto_unregister_plumber(PF_INET, ipsecif_family);
+ ctl_deregister(ipsecif_kctlref);
+ printf("ipsecif_register_control - proto_register_plumber(PF_INET6, %d) failed: %d\n",
+ ipsecif_family, result);
+ return result;
+ }
+
+ return 0;
+}
+
+/* Kernel control functions */
+
+static errno_t
+ipsecif_ctl_connect(
+ kern_ctl_ref kctlref,
+ struct sockaddr_ctl *sac,
+ void **unitinfo)
+{
+ struct ifnet_init_params ipsecif_init;
+ struct ipsecif_pcb *pcb;
+ errno_t result;
+
+ /* kernel control allocates, interface frees */
+ pcb = ipsecif_alloc(sizeof(*pcb));
+ if (pcb == NULL)
+ return ENOMEM;
+
+ /* Setup the protocol control block */
+ bzero(pcb, sizeof(*pcb));
+ *unitinfo = pcb;
+ pcb->ctlref = kctlref;
+ pcb->unit = sac->sc_unit;
+ printf("ipsecif_ctl_connect: creating unit ip%d\n", pcb->unit);
+
+ /* Create the interface */
+ bzero(&ipsecif_init, sizeof(ipsecif_init));
+ ipsecif_init.name = "ipsec";
+ ipsecif_init.unit = pcb->unit;
+ ipsecif_init.family = ipsecif_family;
+ ipsecif_init.type = IFT_OTHER;
+ ipsecif_init.output = ipsecif_output;
+ ipsecif_init.demux = ipsecif_demux;
+ ipsecif_init.add_proto = ipsecif_add_proto;
+ ipsecif_init.del_proto = ipsecif_del_proto;
+ ipsecif_init.softc = pcb;
+ ipsecif_init.ioctl = ipsecif_ioctl;
+ ipsecif_init.set_bpf_tap = ipsecif_settap;
+ ipsecif_init.detach = ipsecif_detached;
+
+ result = ifnet_allocate(&ipsecif_init, &pcb->ifp);
+ if (result != 0) {
+ printf("ipsecif_ctl_connect - ifnet_allocate failed: %d\n", result);
+ ipsecif_free(pcb);
+ return result;
+ }
+ OSIncrementAtomic(&ipsecif_ifcount);
+
+ /* Set flags and additional information. */
+ ifnet_set_mtu(pcb->ifp, 1280);
+ ifnet_set_flags(pcb->ifp, IFF_UP | IFF_MULTICAST | IFF_BROADCAST, 0xffff);
+// ifnet_set_flags(pcb->ifp, IFF_UP | IFF_MULTICAST | IFF_POINTOPOINT, 0xffff);
+
+ /* Attach the interface */
+ result = ifnet_attach(pcb->ifp, NULL);
+ if (result != 0) {
+ printf("ipsecif_ctl_connect - ifnet_allocate failed: %d\n", result);
+ ifnet_release(pcb->ifp);
+ ipsecif_free(pcb);
+ }
+
+ /* Attach to bpf */
+ if (result == 0)
+ bpfattach(pcb->ifp, DLT_NULL, 4);
+
+ return result;
+}
+
+/*
+ * These defines are marked private but it's impossible to remove an interface
+ * without them.
+ */
+#ifndef SIOCPROTODETACH
+#define SIOCPROTODETACH _IOWR('i', 81, struct ifreq) /* detach proto from interface */
+#endif /* SIOCPROTODETACH */
+
+#ifndef SIOCPROTODETACH_IN6
+#define SIOCPROTODETACH_IN6 _IOWR('i', 111, struct in6_ifreq) /* detach proto from interface */
+#endif /* SIOCPROTODETACH */
+
+
+static errno_t
+ipsecif_detach_ip(
+ ifnet_t interface,
+ protocol_family_t protocol,
+ socket_t pf_socket)
+{
+ errno_t result = EPROTONOSUPPORT;
+
+ /* Attempt a detach */
+ if (protocol == PF_INET) {
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d",
+ ifnet_name(interface), ifnet_unit(interface));
+
+ result = sock_ioctl(pf_socket, SIOCPROTODETACH, &ifr);
+ }
+ else if (protocol == PF_INET6) {
+ struct in6_ifreq ifr6;
+
+ bzero(&ifr6, sizeof(ifr6));
+ snprintf(ifr6.ifr_name, sizeof(ifr6.ifr_name), "%s%d",
+ ifnet_name(interface), ifnet_unit(interface));
+
+ result = sock_ioctl(pf_socket, SIOCPROTODETACH_IN6, &ifr6);
+ }
+
+ return result;
+}
+
+static void
+ipsecif_remove_address(
+ ifnet_t interface,
+ protocol_family_t protocol,
+ ifaddr_t address,
+ socket_t pf_socket)
+{
+ errno_t result = 0;
+
+ /* Attempt a detach */
+ if (protocol == PF_INET) {
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d",
+ ifnet_name(interface), ifnet_unit(interface));
+ result = ifaddr_address(address, &ifr.ifr_addr, sizeof(ifr.ifr_addr));
+ if (result != 0) {
+ printf("ipsecif_remove_address - ifaddr_address failed: %d", result);
+ }
+ else {
+ result = sock_ioctl(pf_socket, SIOCDIFADDR, &ifr);
+ if (result != 0) {
+ printf("ipsecif_remove_address - SIOCDIFADDR failed: %d", result);
+ }
+ }
+ }
+ else if (protocol == PF_INET6) {
+ struct in6_ifreq ifr6;
+
+ bzero(&ifr6, sizeof(ifr6));
+ snprintf(ifr6.ifr_name, sizeof(ifr6.ifr_name), "%s%d",
+ ifnet_name(interface), ifnet_unit(interface));
+ result = ifaddr_address(address, (struct sockaddr*)&ifr6.ifr_addr,
+ sizeof(ifr6.ifr_addr));
+ if (result != 0) {
+ printf("ipsecif_remove_address - ifaddr_address failed (v6): %d",
+ result);
+ }
+ else {
+ result = sock_ioctl(pf_socket, SIOCDIFADDR_IN6, &ifr6);
+ if (result != 0) {
+ printf("ipsecif_remove_address - SIOCDIFADDR_IN6 failed: %d",
+ result);
+ }
+ }
+ }
+}
+
+static void
+ipsecif_cleanup_family(
+ ifnet_t interface,
+ protocol_family_t protocol)
+{
+ errno_t result = 0;
+ socket_t pf_socket = NULL;
+ ifaddr_t *addresses = NULL;
+ int i;
+
+ if (protocol != PF_INET && protocol != PF_INET6) {
+ printf("ipsecif_cleanup_family - invalid protocol family %d\n", protocol);
+ return;
+ }
+
+ /* Create a socket for removing addresses and detaching the protocol */
+ result = sock_socket(protocol, SOCK_DGRAM, 0, NULL, NULL, &pf_socket);
+ if (result != 0) {
+ if (result != EAFNOSUPPORT)
+ printf("ipsecif_cleanup_family - failed to create %s socket: %d\n",
+ protocol == PF_INET ? "IP" : "IPv6", result);
+ goto cleanup;
+ }
+
+ result = ipsecif_detach_ip(interface, protocol, pf_socket);
+ if (result == 0 || result == ENXIO) {
+ /* We are done! We either detached or weren't attached. */
+ goto cleanup;
+ }
+ else if (result != EBUSY) {
+ /* Uh, not really sure what happened here... */
+ printf("ipsecif_cleanup_family - ipsecif_detach_ip failed: %d\n", result);
+ goto cleanup;
+ }
+
+ /*
+ * At this point, we received an EBUSY error. This means there are
+ * addresses attached. We should detach them and then try again.
+ */
+ result = ifnet_get_address_list_family(interface, &addresses, protocol);
+ if (result != 0) {
+ printf("fnet_get_address_list_family(%s%d, 0xblah, %s) - failed: %d\n",
+ ifnet_name(interface), ifnet_unit(interface),
+ protocol == PF_INET ? "PF_INET" : "PF_INET6", result);
+ goto cleanup;
+ }
+
+ for (i = 0; addresses[i] != 0; i++) {
+ ipsecif_remove_address(interface, protocol, addresses[i], pf_socket);
+ }
+ ifnet_free_address_list(addresses);
+ addresses = NULL;
+
+ /*
+ * The addresses should be gone, we should try the remove again.
+ */
+ result = ipsecif_detach_ip(interface, protocol, pf_socket);
+ if (result != 0 && result != ENXIO) {
+ printf("ipsecif_cleanup_family - ipsecif_detach_ip failed: %d\n", result);
+ }
+
+cleanup:
+ if (pf_socket != NULL)
+ sock_close(pf_socket);
+
+ if (addresses != NULL)
+ ifnet_free_address_list(addresses);
+}
+
+static errno_t
+ipsecif_ctl_disconnect(
+ __unused kern_ctl_ref kctlref,
+ __unused u_int32_t unit,
+ void *unitinfo)
+{
+ struct ipsecif_pcb *pcb = unitinfo;
+ ifnet_t ifp = pcb->ifp;
+ errno_t result = 0;
+
+ pcb->ctlref = NULL;
+ pcb->unit = 0;
+
+ /*
+ * We want to do everything in our power to ensure that the interface
+ * really goes away when the socket is closed. We must remove IP/IPv6
+ * addresses and detach the protocols. Finally, we can remove and
+ * release the interface.
+ */
+ ipsecif_cleanup_family(ifp, AF_INET);
+ ipsecif_cleanup_family(ifp, AF_INET6);
+
+ if ((result = ifnet_detach(ifp)) != 0) {
+ printf("ipsecif_ctl_disconnect - ifnet_detach failed: %d\n", result);
+ }
+
+ if ((result = ifnet_release(ifp)) != 0) {
+ printf("ipsecif_ctl_disconnect - ifnet_release failed: %d\n", result);
+ }
+
+ return 0;
+}
+
+static inline void
+call_bpf_tap(
+ ifnet_t ifp,
+ bpf_packet_func tap,
+ mbuf_t m)
+{
+ struct m_hdr hack_hdr;
+ struct mbuf *n;
+ int af;
+
+ if (!tap)
+ return;
+
+ af = (((*(char*)(mbuf_data(m))) & 0xf0) >> 4); // 4 or 6
+ if(af == 4) {
+ af = AF_INET;
+ }
+ else if (af == 6) {
+ af = AF_INET6;
+ }
+ else {
+ /* Uh...this ain't right */
+ af = 0;
+ }
+
+ hack_hdr.mh_next = (struct mbuf*)m;
+ hack_hdr.mh_nextpkt = NULL;
+ hack_hdr.mh_len = 4;
+ hack_hdr.mh_data = (char *)⁡
+ hack_hdr.mh_type = ((struct mbuf*)m)->m_type;
+ hack_hdr.mh_flags = 0;
+
+ n = (struct mbuf*)&hack_hdr;
+
+ tap(ifp, (mbuf_t)n);
+}
+
+
+static errno_t
+ipsecif_ctl_send(
+ __unused kern_ctl_ref kctlref,
+ __unused u_int32_t unit,
+ void *unitinfo,
+ mbuf_t m,
+ __unused int flags)
+{
+ struct ipsecif_pcb *pcb = unitinfo;
+ struct ifnet_stat_increment_param incs;
+ errno_t result;
+
+ bzero(&incs, sizeof(incs));
+
+ mbuf_pkthdr_setrcvif(m, pcb->ifp);
+
+ if (pcb->mode & BPF_MODE_INPUT) {
+ call_bpf_tap(pcb->ifp, pcb->tap, m);
+ }
+
+ incs.packets_in = 1;
+ incs.bytes_in = mbuf_pkthdr_len(m);
+ result = ifnet_input(pcb->ifp, m, &incs);
+ if (result != 0) {
+ ifnet_stat_increment_in(pcb->ifp, 0, 0, 1);
+ printf("ipsecif_ctl_send - ifnet_input failed: %d\n", result);
+ mbuf_freem(m);
+ }
+
+ return 0;
+}
+
+/* Network Interface functions */
+static errno_t
+ipsecif_output(
+ ifnet_t interface,
+ mbuf_t data)
+{
+ struct ipsecif_pcb *pcb = ifnet_softc(interface);
+ errno_t result;
+
+ if (pcb->mode & BPF_MODE_OUTPUT) {
+ call_bpf_tap(interface, pcb->tap, data);
+ }
+
+ // no packet should go to the ipsec interface
+ mbuf_freem(data);
+
+#if 0
+ if (pcb->ctlref) {
+ int length = mbuf_pkthdr_len(data);
+ result = ctl_enqueuembuf(pcb->ctlref, pcb->unit, data, CTL_DATA_EOR);
+ if (result != 0) {
+ mbuf_freem(data);
+ printf("ipsecif_output - ctl_enqueuembuf failed: %d\n", result);
+ ifnet_stat_increment_out(interface, 0, 0, 1);
+ }
+ else {
+ ifnet_stat_increment_out(interface, 1, length, 0);
+ }
+ }
+ else
+ mbuf_freem(data);
+#endif
+
+ return 0;
+}
+
+/* Network Interface functions */
+static errno_t
+ipsecif_demux(
+ __unused ifnet_t interface,
+ mbuf_t data,
+ __unused char *frame_header,
+ protocol_family_t *protocol)
+{
+ u_int8_t *vers;
+
+ while (data != NULL && mbuf_len(data) < 1) {
+ data = mbuf_next(data);
+ }
+
+ if (data != NULL) {
+ vers = mbuf_data(data);
+ switch(((*vers) & 0xf0) >> 4) {
+ case 4:
+ *protocol = PF_INET;
+ return 0;
+
+ case 6:
+ *protocol = PF_INET6;
+ return 0;
+ }
+ }
+
+ return ENOENT;
+}
+
+static errno_t
+ipsecif_add_proto(
+ __unused ifnet_t interface,
+ protocol_family_t protocol,
+ __unused const struct ifnet_demux_desc *demux_array,
+ __unused u_int32_t demux_count)
+{
+ switch(protocol) {
+ case PF_INET:
+ return 0;
+ case PF_INET6:
+ return 0;
+ default:
+ break;
+ }
+
+ return ENOPROTOOPT;
+}
+
+static errno_t
+ipsecif_del_proto(
+ __unused ifnet_t interface,
+ __unused protocol_family_t protocol)
+{
+ return 0;
+}
+
+static errno_t
+ipsecif_ioctl(
+ __unused ifnet_t interface,
+ __unused u_int32_t command,
+ __unused void *data)
+{
+ errno_t result = 0;
+
+ switch(command) {
+ case SIOCSIFMTU:
+ ifnet_set_mtu(interface, ((struct ifreq*)data)->ifr_mtu);
+ break;
+
+ default:
+ result = EOPNOTSUPP;
+ }
+
+ return result;
+}
+
+static errno_t
+ipsecif_settap(
+ ifnet_t interface,
+ bpf_tap_mode mode,
+ bpf_packet_func callback)
+{
+ struct ipsecif_pcb *pcb = ifnet_softc(interface);
+
+ pcb->mode = mode;
+ pcb->tap = callback;
+
+ return 0;
+}
+
+static void
+ipsecif_detached(
+ ifnet_t interface)
+{
+ struct ipsecif_pcb *pcb = ifnet_softc(interface);
+
+ ipsecif_free(pcb);
+
+ OSDecrementAtomic(&ipsecif_ifcount);
+}
+
+/* Protocol Handlers */
+
+static errno_t
+ipsecif_proto_input(
+ __unused ifnet_t interface,
+ protocol_family_t protocol,
+ mbuf_t m,
+ __unused char *frame_header)
+{
+ proto_input(protocol, m);
+
+ return 0;
+}
+
+static errno_t
+ipsecif_attach_proto(
+ ifnet_t interface,
+ protocol_family_t protocol)
+{
+ struct ifnet_attach_proto_param proto;
+ errno_t result;
+
+ bzero(&proto, sizeof(proto));
+ proto.input = ipsecif_proto_input;
+
+ result = ifnet_attach_protocol(interface, protocol, &proto);
+ if (result != 0 && result != EEXIST) {
+ printf("ipsecif_attach_inet - ifnet_attach_protocol %d failed: %d\n",
+ protocol, result);
+ }
+
+ return result;
+}
+