⬢ DragonFlyBSD Kernel Audit
DF-5739 / hci_stub.c
← back to finding ↓ download raw
/*
 * hci_stub.c -- minimal L2CAP/SCO/socket stubs for the hci_link_harness KLD.
 *
 * The production netbt code (sys/netbt/hci_event.c, hci_link.c, hci_misc.c,
 * hci_unit.c, bt_input.c) is compiled VERBATIM into this module.  Everything
 * it needs from the layers we do not compile (l2cap_*.c, sco_upper.c,
 * sco_socket.c, hci_socket.c, bt_proto.c) is stubbed here with the minimum
 * behaviour for the real link-engine code to run unmodified:
 *
 *   - M_BLUETOOTH malloc type (lives in bt_proto.c normally)
 *   - l2cap_pdu_pool / l2cap_req_pool vm_zones (bt_proto.c netbt_init)
 *   - l2cap_active_list (l2cap_misc.c)
 *   - l2cap_close/l2cap_start/l2cap_request_free/l2cap_recv_frame
 *   - hci_mtap/hci_drop (hci_socket.c) -- no raw HCI sockets in the harness
 *
 * SCO support (needed for DF-5738: the SCO->ACL anchor reference taken by
 * production hci_sco_newconn(), sys/netbt/hci_link.c:810):
 *
 *   - a LISTENING sco_pcb in the sco_pcb list, exactly what a userland
 *     sco socket in listen(2) state looks like to hci_sco_newconn();
 *   - a fake btproto whose newconn op returns a fresh stub pcb (what
 *     sco_socket.c:sco_newconn does), so the FULL production path
 *     hci_event_con_req -> hci_sco_newconn -> hci_acl_open runs verbatim.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <net/if.h>

#include <netbt/bluetooth.h>
#include <netbt/hci.h>
#include <netbt/l2cap.h>
#include <netbt/sco.h>

#include "stub.h"

/* real malloc type lives in bt_proto.c which we do not compile in */
MALLOC_DEFINE(M_BLUETOOTH, "Bluetooth", "Bluetooth system memory");

/* zones normally created by netbt_init() in bt_proto.c */
vm_zone_t l2cap_pdu_pool;
vm_zone_t l2cap_req_pool;

/* channel list normally in l2cap_misc.c */
struct l2cap_channel_list l2cap_active_list = LIST_HEAD_INITIALIZER(l2cap_active_list);

/* sco listener list normally in sco_upper.c */
struct sco_pcb_list sco_pcb = LIST_HEAD_INITIALIZER(sco_pcb);

/* counters the harness uses to verify SCO pcb lifetime after the runs */
int stub_sco_newconn_count;
int stub_sco_disconnected_count;
int stub_sco_pcb_leaks;

/* forward */
static void *stub_sco_newconn(void *, struct sockaddr_bt *, struct sockaddr_bt *);
static void stub_sco_disconnected(void *, int);
static void stub_sco_connected(void *);

static const struct btproto stub_sco_btproto = {
	NULL,			/* connecting */
	stub_sco_connected,	/* connected */
	stub_sco_disconnected,	/* disconnected */
	stub_sco_newconn,	/* newconn */
	NULL,			/* complete */
	NULL,			/* linkmode */
	NULL,			/* input */
};

/*
 * The listening pcb: an unprivileged userland program does
 *   s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); listen(s, 1);
 * which lands in sco_listen() setting SP_LISTENING.  hci_sco_newconn()
 * walks this list.
 */
static struct sco_pcb stub_listener;

void
stub_sco_init(void)
{
	memset(&stub_listener, 0, sizeof(stub_listener));
	stub_listener.sp_flags = SP_LISTENING;
	stub_listener.sp_proto = &stub_sco_btproto;
	LIST_INSERT_HEAD(&sco_pcb, &stub_listener, sp_next);
}

/*
 * fake upper-layer newconn: allocate a pcb like sco_socket.c does
 * (attach a new socket+pcb).  hci_sco_newconn() fills in addresses,
 * anchors the ACL link and links it both ways.
 */
static void *
stub_sco_newconn(void *upper, struct sockaddr_bt *laddr, struct sockaddr_bt *raddr)
{
	struct sco_pcb *pcb;

	pcb = kmalloc(sizeof(*pcb), M_BLUETOOTH, M_NOWAIT | M_ZERO);
	if (pcb == NULL)
		return NULL;

	pcb->sp_proto = &stub_sco_btproto;
	LIST_INSERT_HEAD(&sco_pcb, pcb, sp_next);
	stub_sco_newconn_count++;

	return pcb;
}

static void
stub_sco_connected(void *arg)
{
	/* socket layer: soisconnected(); nothing to do in the harness */
}

static void
stub_sco_disconnected(void *arg, int err)
{
	stub_sco_disconnected_count++;
}

int
sco_detach(struct sco_pcb **handle)
{
	struct sco_pcb *pcb = *handle;

	*handle = NULL;
	if (pcb == NULL)
		return EINVAL;

	/* production sco_detach(): free the pcb unless it is our listener */
	if (pcb != &stub_listener) {
		LIST_REMOVE(pcb, sp_next);
		kfree(pcb, M_BLUETOOTH);
	}
	return 0;
}

void
hci_mtap(struct mbuf *m, struct hci_unit *unit)
{
	/* no raw HCI sockets attached in the harness */
}

void
hci_drop(void *arg)
{
	/* no socket bookkeeping to drop */
}

void
l2cap_close(struct l2cap_channel *chan, int err)
{
	if (chan->lc_lcid != L2CAP_NULL_CID) {
		LIST_REMOVE(chan, lc_ncid);
		chan->lc_lcid = L2CAP_NULL_CID;
	}
}

int
l2cap_start(struct l2cap_channel *chan)
{
	return 0;
}

void
l2cap_request_free(struct l2cap_req *req)
{
	kfree(req, M_BLUETOOTH);
}

void
l2cap_recv_frame(struct mbuf *m, struct hci_link *link)
{
	m_freem(m);
}

/* signalling senders used by hci_acl_linkmode() (l2cap_signal.c) */
int
l2cap_send_connect_req(struct l2cap_channel *chan)
{
	return 0;
}

int
l2cap_send_connect_rsp(struct hci_link *link, uint8_t ident,
    uint16_t dcid, uint16_t scid, uint16_t result)
{
	return 0;
}

int
l2cap_send_config_req(struct l2cap_channel *chan)
{
	return 0;
}