MFC rev. 1.18-1.21

- Fix thread safety.
 - Make some optimizations.
This commit is contained in:
mav 2007-10-12 05:09:13 +00:00
parent be00572ccd
commit 3750cc7cc3

View File

@ -125,11 +125,10 @@ struct l2tp_seq {
u_int16_t ssth; /* slow start threshold */
u_int16_t acks; /* # consecutive acks rec'd */
u_int16_t rexmits; /* # retransmits sent */
u_int16_t max_rexmits; /* max # retransmits sent */
u_int16_t max_rexmit_to; /* max retransmit timeout */
struct callout rack_timer; /* retransmit timer */
struct callout xack_timer; /* delayed ack timer */
struct mbuf *xwin[L2TP_MAX_XWIN]; /* transmit window */
struct mtx mtx; /* seq mutex */
};
/* Node private data */
@ -159,13 +158,11 @@ static ng_rcvmsg_t ng_l2tp_rcvmsg;
static ng_shutdown_t ng_l2tp_shutdown;
static ng_newhook_t ng_l2tp_newhook;
static ng_rcvdata_t ng_l2tp_rcvdata;
static ng_rcvdata_t ng_l2tp_rcvdata_lower;
static ng_rcvdata_t ng_l2tp_rcvdata_ctrl;
static ng_disconnect_t ng_l2tp_disconnect;
/* Internal functions */
static int ng_l2tp_recv_lower(node_p node, item_p item);
static int ng_l2tp_recv_ctrl(node_p node, item_p item);
static int ng_l2tp_recv_data(node_p node, item_p item, hookpriv_p hpriv);
static int ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns);
static void ng_l2tp_seq_init(priv_p priv);
@ -345,6 +342,8 @@ NETGRAPH_INIT(l2tp, &ng_l2tp_typestruct);
/* Whether to use m_copypacket() or m_dup() */
#define L2TP_COPY_MBUF m_copypacket
#define ERROUT(x) do { error = (x); goto done; } while (0)
/************************************************************************
NETGRAPH NODE STUFF
************************************************************************/
@ -389,10 +388,12 @@ ng_l2tp_newhook(node_p node, hook_p hook, const char *name)
if (priv->ctrl != NULL)
return (EISCONN);
priv->ctrl = hook;
NG_HOOK_SET_RCVDATA(hook, ng_l2tp_rcvdata_ctrl);
} else if (strcmp(name, NG_L2TP_HOOK_LOWER) == 0) {
if (priv->lower != NULL)
return (EISCONN);
priv->lower = hook;
NG_HOOK_SET_RCVDATA(hook, ng_l2tp_rcvdata_lower);
} else {
static const char hexdig[16] = "0123456789abcdef";
u_int16_t session_id;
@ -656,46 +657,6 @@ ng_l2tp_rcvmsg(node_p node, item_p item, hook_p lasthook)
return (error);
}
/*
* Receive incoming data on a hook.
*/
static int
ng_l2tp_rcvdata(hook_p hook, item_p item)
{
const node_p node = NG_HOOK_NODE(hook);
const priv_p priv = NG_NODE_PRIVATE(node);
int error;
/* Sanity check */
L2TP_SEQ_CHECK(&priv->seq);
/* If not configured, reject */
if (!priv->conf.enabled) {
NG_FREE_ITEM(item);
return (ENXIO);
}
/* Handle incoming frame from below */
if (hook == priv->lower) {
error = ng_l2tp_recv_lower(node, item);
goto done;
}
/* Handle outgoing control frame */
if (hook == priv->ctrl) {
error = ng_l2tp_recv_ctrl(node, item);
goto done;
}
/* Handle outgoing data frame */
error = ng_l2tp_recv_data(node, item, NG_HOOK_PRIVATE(hook));
done:
/* Done */
L2TP_SEQ_CHECK(&priv->seq);
return (error);
}
/*
* Destroy node
*/
@ -715,6 +676,8 @@ ng_l2tp_shutdown(node_p node)
ng_uncallout(&seq->rack_timer, node);
ng_uncallout(&seq->xack_timer, node);
mtx_destroy(&seq->mtx);
FREE(priv, M_NETGRAPH_L2TP);
/* Unref node */
@ -787,12 +750,13 @@ ng_l2tp_reset_session(hook_p hook, void *arg)
* Handle an incoming frame from below.
*/
static int
ng_l2tp_recv_lower(node_p node, item_p item)
ng_l2tp_rcvdata_lower(hook_p h, item_p item)
{
static const u_int16_t req_bits[2][2] = {
{ L2TP_DATA_0BITS, L2TP_DATA_1BITS },
{ L2TP_CTRL_0BITS, L2TP_CTRL_1BITS },
};
const node_p node = NG_HOOK_NODE(h);
const priv_p priv = NG_NODE_PRIVATE(node);
hookpriv_p hpriv = NULL;
hook_p hook = NULL;
@ -805,6 +769,15 @@ ng_l2tp_recv_lower(node_p node, item_p item)
int error;
int len, plen;
/* Sanity check */
L2TP_SEQ_CHECK(&priv->seq);
/* If not configured, reject */
if (!priv->conf.enabled) {
NG_FREE_ITEM(item);
ERROUT(ENXIO);
}
/* Grab mbuf */
NGI_GET_M(item, m);
@ -820,12 +793,12 @@ ng_l2tp_recv_lower(node_p node, item_p item)
priv->stats.recvRunts++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (EINVAL);
ERROUT(EINVAL);
}
if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) {
priv->stats.memoryFailures++;
NG_FREE_ITEM(item);
return (EINVAL);
ERROUT(EINVAL);
}
hdr = ntohs(*mtod(m, u_int16_t *));
m_adj(m, 2);
@ -837,7 +810,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
priv->stats.recvInvalid++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (EINVAL);
ERROUT(EINVAL);
}
if (m->m_pkthdr.len < 4 /* tunnel, session id */
+ (2 * ((hdr & L2TP_HDR_LEN) != 0)) /* length field */
@ -846,7 +819,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
priv->stats.recvRunts++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (EINVAL);
ERROUT(EINVAL);
}
/* Get and validate length field if present */
@ -854,7 +827,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) {
priv->stats.memoryFailures++;
NG_FREE_ITEM(item);
return (EINVAL);
ERROUT(EINVAL);
}
len = (u_int16_t)ntohs(*mtod(m, u_int16_t *)) - 4;
m_adj(m, 2);
@ -862,7 +835,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
priv->stats.recvInvalid++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (EINVAL);
ERROUT(EINVAL);
}
if (len < m->m_pkthdr.len) /* trim extra bytes */
m_adj(m, -(m->m_pkthdr.len - len));
@ -872,7 +845,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) {
priv->stats.memoryFailures++;
NG_FREE_ITEM(item);
return (EINVAL);
ERROUT(EINVAL);
}
memcpy(ids, mtod(m, u_int16_t *), 4);
m_adj(m, 4);
@ -883,7 +856,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
priv->stats.recvWrongTunnel++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (EADDRNOTAVAIL);
ERROUT(EADDRNOTAVAIL);
}
/* Check session ID (for data packets only) */
@ -894,7 +867,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
priv->stats.recvUnknownSID++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (ENOTCONN);
ERROUT(ENOTCONN);
}
hpriv = NG_HOOK_PRIVATE(hook);
}
@ -904,7 +877,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) {
priv->stats.memoryFailures++;
NG_FREE_ITEM(item);
return (EINVAL);
ERROUT(EINVAL);
}
memcpy(&ns, &mtod(m, u_int16_t *)[0], 2);
ns = ntohs(ns);
@ -921,7 +894,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) {
priv->stats.memoryFailures++;
NG_FREE_ITEM(item);
return (EINVAL);
ERROUT(EINVAL);
}
memcpy(&offset, mtod(m, u_int16_t *), 2);
offset = ntohs(offset);
@ -931,7 +904,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
priv->stats.recvInvalid++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (EINVAL);
ERROUT(EINVAL);
}
m_adj(m, 2+offset);
}
@ -947,7 +920,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
priv->stats.recvZLBs++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (0);
ERROUT(0);
}
/*
@ -960,7 +933,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
if (m == NULL) {
priv->stats.memoryFailures++;
NG_FREE_ITEM(item);
return (ENOBUFS);
ERROUT(ENOBUFS);
}
memcpy(mtod(m, u_int16_t *), &ids[1], 2);
@ -968,12 +941,12 @@ ng_l2tp_recv_lower(node_p node, item_p item)
if (ng_l2tp_seq_recv_ns(priv, ns) == -1) {
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (0);
ERROUT(0);
}
/* Deliver packet to upper layers */
NG_FWD_NEW_DATA(error, item, priv->ctrl, m);
return (error);
ERROUT(error);
}
/* Per session packet, account it. */
@ -991,7 +964,7 @@ ng_l2tp_recv_lower(node_p node, item_p item)
NG_FREE_ITEM(item); /* duplicate or out of order */
NG_FREE_M(m);
priv->stats.recvDataDrops++;
return (0);
ERROUT(0);
}
hpriv->nr = ns + 1;
}
@ -1000,11 +973,14 @@ ng_l2tp_recv_lower(node_p node, item_p item)
if (m->m_pkthdr.len == 0) {
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (0);
ERROUT(0);
}
/* Deliver data */
NG_FWD_NEW_DATA(error, item, hook, m);
done:
/* Done */
L2TP_SEQ_CHECK(&priv->seq);
return (error);
}
@ -1012,12 +988,24 @@ ng_l2tp_recv_lower(node_p node, item_p item)
* Handle an outgoing control frame.
*/
static int
ng_l2tp_recv_ctrl(node_p node, item_p item)
ng_l2tp_rcvdata_ctrl(hook_p hook, item_p item)
{
const node_p node = NG_HOOK_NODE(hook);
const priv_p priv = NG_NODE_PRIVATE(node);
struct l2tp_seq *const seq = &priv->seq;
struct mbuf *m;
int error;
int i;
u_int16_t ns;
/* Sanity check */
L2TP_SEQ_CHECK(&priv->seq);
/* If not configured, reject */
if (!priv->conf.enabled) {
NG_FREE_ITEM(item);
ERROUT(ENXIO);
}
/* Grab mbuf and discard other stuff XXX */
NGI_GET_M(item, m);
@ -1027,61 +1015,79 @@ ng_l2tp_recv_ctrl(node_p node, item_p item)
if (m->m_pkthdr.len < 2) {
priv->stats.xmitInvalid++;
m_freem(m);
return (EINVAL);
ERROUT(EINVAL);
}
/* Check max length */
if (m->m_pkthdr.len >= 0x10000 - 14) {
priv->stats.xmitTooBig++;
m_freem(m);
return (EOVERFLOW);
ERROUT(EOVERFLOW);
}
mtx_lock(&seq->mtx);
/* Find next empty slot in transmit queue */
for (i = 0; i < L2TP_MAX_XWIN && seq->xwin[i] != NULL; i++);
if (i == L2TP_MAX_XWIN) {
mtx_unlock(&seq->mtx);
priv->stats.xmitDrops++;
m_freem(m);
return (ENOBUFS);
ERROUT(ENOBUFS);
}
seq->xwin[i] = m;
/* Sanity check receive ack timer state */
KASSERT((i == 0) ^ callout_active(&seq->rack_timer),
("%s: xwin %d full but rack timer %s running",
__func__, i, callout_active(&seq->rack_timer) ? "" : "not "));
/* If peer's receive window is already full, nothing else to do */
if (i >= seq->cwnd)
return (0);
if (i >= seq->cwnd) {
mtx_unlock(&seq->mtx);
ERROUT(0);
}
/* Start retransmit timer if not already running */
if (!callout_active(&seq->rack_timer))
ng_callout(&seq->rack_timer, node, NULL,
hz, ng_l2tp_seq_rack_timeout, NULL, 0);
ns = seq->ns++;
mtx_unlock(&seq->mtx);
/* Copy packet */
if ((m = L2TP_COPY_MBUF(seq->xwin[i], M_DONTWAIT)) == NULL) {
if ((m = L2TP_COPY_MBUF(m, M_DONTWAIT)) == NULL) {
priv->stats.memoryFailures++;
return (ENOBUFS);
ERROUT(ENOBUFS);
}
/* Send packet and increment xmit sequence number */
return (ng_l2tp_xmit_ctrl(priv, m, seq->ns++));
error = ng_l2tp_xmit_ctrl(priv, m, ns);
done:
/* Done */
L2TP_SEQ_CHECK(&priv->seq);
return (error);
}
/*
* Handle an outgoing data frame.
*/
static int
ng_l2tp_recv_data(node_p node, item_p item, hookpriv_p hpriv)
ng_l2tp_rcvdata(hook_p hook, item_p item)
{
const priv_p priv = NG_NODE_PRIVATE(node);
const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook);
struct mbuf *m;
u_int16_t hdr;
int error;
int i = 1;
/* Sanity check */
L2TP_SEQ_CHECK(&priv->seq);
/* If not configured, reject */
if (!priv->conf.enabled) {
NG_FREE_ITEM(item);
ERROUT(ENXIO);
}
/* Get mbuf */
NGI_GET_M(item, m);
@ -1090,7 +1096,7 @@ ng_l2tp_recv_data(node_p node, item_p item, hookpriv_p hpriv)
priv->stats.xmitDataTooBig++;
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (EOVERFLOW);
ERROUT(EOVERFLOW);
}
/* Prepend L2TP header */
@ -1101,7 +1107,7 @@ ng_l2tp_recv_data(node_p node, item_p item, hookpriv_p hpriv)
if (m == NULL) {
priv->stats.memoryFailures++;
NG_FREE_ITEM(item);
return (ENOBUFS);
ERROUT(ENOBUFS);
}
hdr = L2TP_DATA_HDR;
if (hpriv->conf.include_length) {
@ -1128,6 +1134,9 @@ ng_l2tp_recv_data(node_p node, item_p item, hookpriv_p hpriv)
/* Send packet */
NG_FWD_NEW_DATA(error, item, priv->lower, m);
done:
/* Done */
L2TP_SEQ_CHECK(&priv->seq);
return (error);
}
@ -1166,10 +1175,9 @@ ng_l2tp_seq_init(priv_p priv)
if (seq->wmax > L2TP_MAX_XWIN)
seq->wmax = L2TP_MAX_XWIN;
seq->ssth = seq->wmax;
seq->max_rexmits = priv->conf.rexmit_max;
seq->max_rexmit_to = priv->conf.rexmit_max_to;
ng_callout_init(&seq->rack_timer);
ng_callout_init(&seq->xack_timer);
mtx_init(&seq->mtx, "ng_l2tp", NULL, MTX_DEF);
L2TP_SEQ_CHECK(seq);
}
@ -1223,10 +1231,6 @@ ng_l2tp_seq_adjust(priv_p priv, const struct ng_l2tp_config *conf)
return (EBUSY);
seq->wmax = new_wmax;
/* Update retransmit parameters */
seq->max_rexmits = conf->rexmit_max;
seq->max_rexmit_to = conf->rexmit_max_to;
/* Done */
return (0);
}
@ -1259,10 +1263,16 @@ ng_l2tp_seq_reset(priv_p priv)
NG_NODE_FOREACH_HOOK(priv->node, ng_l2tp_reset_session, NULL, hook);
/* Reset node's sequence number state */
memset(seq, 0, sizeof(*seq));
seq->cwnd = 1;
seq->ns = 0;
seq->nr = 0;
seq->rack = 0;
seq->xack = 0;
seq->wmax = L2TP_MAX_XWIN;
seq->cwnd = 1;
seq->ssth = seq->wmax;
seq->acks = 0;
seq->rexmits = 0;
bzero(seq->xwin, sizeof(seq->xwin));
/* Done */
L2TP_SEQ_CHECK(seq);
@ -1275,14 +1285,20 @@ static void
ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr)
{
struct l2tp_seq *const seq = &priv->seq;
struct mbuf *m;
int nack;
int i;
struct mbuf *xwin[L2TP_MAX_XWIN]; /* partial local copy */
int nack;
int i, j;
uint16_t ns;
mtx_lock(&seq->mtx);
/* Verify peer's ACK is in range */
if ((nack = L2TP_SEQ_DIFF(nr, seq->rack)) <= 0)
if ((nack = L2TP_SEQ_DIFF(nr, seq->rack)) <= 0) {
mtx_unlock(&seq->mtx);
return; /* duplicate ack */
}
if (L2TP_SEQ_DIFF(nr, seq->ns) > 0) {
mtx_unlock(&seq->mtx);
priv->stats.recvBadAcks++; /* ack for packet not sent */
return;
}
@ -1334,8 +1350,10 @@ ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr)
ng_uncallout(&seq->rack_timer, priv->node);
/* If transmit queue is empty, we're done for now */
if (seq->xwin[0] == NULL)
if (seq->xwin[0] == NULL) {
mtx_unlock(&seq->mtx);
return;
}
/* Start restransmit timer again */
ng_callout(&seq->rack_timer, priv->node, NULL,
@ -1343,16 +1361,30 @@ ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr)
/*
* Send more packets, trying to keep peer's receive window full.
* Make copy of everything we need before lock release.
*/
ns = seq->ns;
j = 0;
while ((i = L2TP_SEQ_DIFF(seq->ns, seq->rack)) < seq->cwnd
&& seq->xwin[i] != NULL) {
xwin[j++] = seq->xwin[i];
seq->ns++;
}
mtx_unlock(&seq->mtx);
/*
* Send prepared.
* If there is a memory error, pretend packet was sent, as it
* will get retransmitted later anyway.
*/
while ((i = L2TP_SEQ_DIFF(seq->ns, seq->rack)) < seq->cwnd
&& seq->xwin[i] != NULL) {
if ((m = L2TP_COPY_MBUF(seq->xwin[i], M_DONTWAIT)) == NULL)
for (i = 0; i < j; i++) {
struct mbuf *m;
if ((m = L2TP_COPY_MBUF(xwin[i], M_DONTWAIT)) == NULL)
priv->stats.memoryFailures++;
else
ng_l2tp_xmit_ctrl(priv, m, seq->ns);
seq->ns++;
ng_l2tp_xmit_ctrl(priv, m, ns);
ns++;
}
}
@ -1381,6 +1413,8 @@ ng_l2tp_seq_recv_ns(priv_p priv, u_int16_t ns)
return (-1);
}
mtx_lock(&seq->mtx);
/* Update recv sequence number */
seq->nr++;
@ -1389,6 +1423,8 @@ ng_l2tp_seq_recv_ns(priv_p priv, u_int16_t ns)
ng_callout(&seq->xack_timer, priv->node, NULL,
L2TP_DELAYED_ACK, ng_l2tp_seq_xack_timeout, NULL, 0);
mtx_unlock(&seq->mtx);
/* Accept packet */
return (0);
}
@ -1444,13 +1480,13 @@ ng_l2tp_seq_rack_timeout(node_p node, hook_p hook, void *arg1, int arg2)
priv->stats.xmitRetransmits++;
/* Have we reached the retransmit limit? If so, notify owner. */
if (seq->rexmits++ >= seq->max_rexmits)
if (seq->rexmits++ >= priv->conf.rexmit_max)
ng_l2tp_seq_failure(priv);
/* Restart timer, this time with an increased delay */
delay = (seq->rexmits > 12) ? (1 << 12) : (1 << seq->rexmits);
if (delay > seq->max_rexmit_to)
delay = seq->max_rexmit_to;
if (delay > priv->conf.rexmit_max_to)
delay = priv->conf.rexmit_max_to;
ng_callout(&seq->rack_timer, node, NULL,
hz * delay, ng_l2tp_seq_rack_timeout, NULL, 0);
@ -1483,6 +1519,8 @@ ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns)
u_int16_t session_id = 0;
int error;
mtx_lock(&seq->mtx);
/* Stop ack timer: we're sending an ack with this packet.
Doing this before to keep state predictable after error. */
if (callout_active(&seq->xack_timer))
@ -1490,6 +1528,8 @@ ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns)
seq->xack = seq->nr;
mtx_unlock(&seq->mtx);
/* If no mbuf passed, send an empty packet (ZLB) */
if (m == NULL) {
@ -1544,12 +1584,15 @@ ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns)
static void
ng_l2tp_seq_check(struct l2tp_seq *seq)
{
const int self_unack = L2TP_SEQ_DIFF(seq->nr, seq->xack);
const int peer_unack = L2TP_SEQ_DIFF(seq->ns, seq->rack);
int self_unack, peer_unack;
int i;
#define CHECK(p) KASSERT((p), ("%s: not: %s", __func__, #p))
mtx_lock(&seq->mtx);
self_unack = L2TP_SEQ_DIFF(seq->nr, seq->xack);
peer_unack = L2TP_SEQ_DIFF(seq->ns, seq->rack);
CHECK(seq->wmax <= L2TP_MAX_XWIN);
CHECK(seq->cwnd >= 1);
CHECK(seq->cwnd <= seq->wmax);
@ -1569,6 +1612,8 @@ ng_l2tp_seq_check(struct l2tp_seq *seq)
for ( ; i < seq->cwnd; i++) /* verify peer's recv window full */
CHECK(seq->xwin[i] == NULL);
mtx_unlock(&seq->mtx);
#undef CHECK
}
#endif /* INVARIANTS */