#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/time.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/mutex.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <sys/sysctl.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/ng_parse.h>
#include <netgraph/ng_pptpgre.h>
#ifdef NG_SEPARATE_MALLOC
static MALLOC_DEFINE(M_NETGRAPH_PPTP, "netgraph_pptp", "netgraph pptpgre node");
#else
#define M_NETGRAPH_PPTP M_NETGRAPH
#endif
struct greheader {
#if BYTE_ORDER == LITTLE_ENDIAN
u_char recursion:3;
u_char ssr:1;
u_char hasSeq:1;
u_char hasKey:1;
u_char hasRoute:1;
u_char hasSum:1;
u_char vers:3;
u_char flags:4;
u_char hasAck:1;
#elif BYTE_ORDER == BIG_ENDIAN
u_char hasSum:1;
u_char hasRoute:1;
u_char hasKey:1;
u_char hasSeq:1;
u_char ssr:1;
u_char recursion:3;
u_char hasAck:1;
u_char flags:4;
u_char vers:3;
#else
#error BYTE_ORDER is not defined properly
#endif
u_int16_t proto;
u_int16_t length;
u_int16_t cid;
u_int32_t data[0];
};
#define PPTP_GRE_PROTO 0x880b
#define PPTP_INIT_VALUE ((0x2001 << 16) | PPTP_GRE_PROTO)
#define PPTP_INIT_MASK 0xef7fffff
#define PPTP_MAX_PAYLOAD (0xffff - sizeof(struct greheader) - 8)
#define PPTP_TIME_SCALE 1024
typedef u_int64_t pptptime_t;
#define PPTP_XMIT_WIN 16
#define PPTP_MIN_TIMEOUT (PPTP_TIME_SCALE / 83)
#define PPTP_MAX_TIMEOUT (3 * PPTP_TIME_SCALE)
#define PPTP_REORDER_TIMEOUT 1
#define PPTP_MIN_ACK_DELAY (PPTP_TIME_SCALE / 500)
#define PPTP_MAX_ACK_DELAY (PPTP_TIME_SCALE / 2)
#define PPTP_ACK_ALPHA(x) (((x) + 4) >> 3)
#define PPTP_ACK_BETA(x) (((x) + 2) >> 2)
#define PPTP_ACK_CHI(x) ((x) << 2)
#define PPTP_ACK_DELTA(x) ((x) << 1)
#define PPTP_SEQ_DIFF(x,y) ((int32_t)(x) - (int32_t)(y))
#define SESSHASHSIZE 0x0020
#define SESSHASH(x) (((x) ^ ((x) >> 8)) & (SESSHASHSIZE - 1))
SYSCTL_NODE(_net_graph, OID_AUTO, pptpgre, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
"PPTPGRE");
static int reorder_max = 1;
SYSCTL_UINT(_net_graph_pptpgre, OID_AUTO, reorder_max, CTLFLAG_RWTUN,
&reorder_max, 0, "Reorder queue maximum length");
static int reorder_timeout = PPTP_REORDER_TIMEOUT;
SYSCTL_UINT(_net_graph_pptpgre, OID_AUTO, reorder_timeout, CTLFLAG_RWTUN,
&reorder_timeout, 0, "Reorder timeout is milliseconds");
struct ng_pptpgre_roq {
SLIST_ENTRY(ng_pptpgre_roq) next;
item_p item;
u_int32_t seq;
};
SLIST_HEAD(ng_pptpgre_roq_head, ng_pptpgre_roq);
typedef struct ng_pptpgre_roq_head roqh;
struct ng_pptpgre_sess {
node_p node;
hook_p hook;
struct ng_pptpgre_conf conf;
struct mtx mtx;
u_int32_t recvSeq;
u_int32_t xmitSeq;
u_int32_t recvAck;
u_int32_t xmitAck;
int32_t ato;
int32_t rtt;
int32_t dev;
u_int16_t xmitWin;
struct callout sackTimer;
struct callout rackTimer;
u_int32_t winAck;
pptptime_t timeSent[PPTP_XMIT_WIN];
LIST_ENTRY(ng_pptpgre_sess) sessions;
roqh roq;
u_int8_t roq_len;
struct callout reorderTimer;
};
typedef struct ng_pptpgre_sess *hpriv_p;
struct ng_pptpgre_private {
hook_p upper;
hook_p lower;
struct ng_pptpgre_sess uppersess;
LIST_HEAD(, ng_pptpgre_sess) sesshash[SESSHASHSIZE];
struct ng_pptpgre_stats stats;
};
typedef struct ng_pptpgre_private *priv_p;
static ng_constructor_t ng_pptpgre_constructor;
static ng_rcvmsg_t ng_pptpgre_rcvmsg;
static ng_shutdown_t ng_pptpgre_shutdown;
static ng_newhook_t ng_pptpgre_newhook;
static ng_rcvdata_t ng_pptpgre_rcvdata;
static ng_rcvdata_t ng_pptpgre_rcvdata_lower;
static ng_disconnect_t ng_pptpgre_disconnect;
static int ng_pptpgre_xmit(hpriv_p hpriv, item_p item);
static void ng_pptpgre_start_send_ack_timer(hpriv_p hpriv);
static void ng_pptpgre_start_recv_ack_timer(hpriv_p hpriv);
static void ng_pptpgre_start_reorder_timer(hpriv_p hpriv);
static void ng_pptpgre_recv_ack_timeout(node_p node, hook_p hook,
void *arg1, int arg2);
static void ng_pptpgre_send_ack_timeout(node_p node, hook_p hook,
void *arg1, int arg2);
static void ng_pptpgre_reorder_timeout(node_p node, hook_p hook,
void *arg1, int arg2);
static hpriv_p ng_pptpgre_find_session(priv_p privp, u_int16_t cid);
static void ng_pptpgre_reset(hpriv_p hpriv);
static pptptime_t ng_pptpgre_time(void);
static void ng_pptpgre_ack(const hpriv_p hpriv);
static int ng_pptpgre_sendq(const hpriv_p hpriv, roqh *q,
const struct ng_pptpgre_roq *st);
static const struct ng_parse_struct_field ng_pptpgre_conf_type_fields[]
= NG_PPTPGRE_CONF_TYPE_INFO;
static const struct ng_parse_type ng_pptpgre_conf_type = {
&ng_parse_struct_type,
&ng_pptpgre_conf_type_fields,
};
static const struct ng_parse_struct_field ng_pptpgre_stats_type_fields[]
= NG_PPTPGRE_STATS_TYPE_INFO;
static const struct ng_parse_type ng_pptp_stats_type = {
&ng_parse_struct_type,
&ng_pptpgre_stats_type_fields
};
static const struct ng_cmdlist ng_pptpgre_cmdlist[] = {
{
NGM_PPTPGRE_COOKIE,
NGM_PPTPGRE_SET_CONFIG,
"setconfig",
&ng_pptpgre_conf_type,
NULL
},
{
NGM_PPTPGRE_COOKIE,
NGM_PPTPGRE_GET_CONFIG,
"getconfig",
&ng_parse_hint16_type,
&ng_pptpgre_conf_type
},
{
NGM_PPTPGRE_COOKIE,
NGM_PPTPGRE_GET_STATS,
"getstats",
NULL,
&ng_pptp_stats_type
},
{
NGM_PPTPGRE_COOKIE,
NGM_PPTPGRE_CLR_STATS,
"clrstats",
NULL,
NULL
},
{
NGM_PPTPGRE_COOKIE,
NGM_PPTPGRE_GETCLR_STATS,
"getclrstats",
NULL,
&ng_pptp_stats_type
},
{ 0 }
};
static struct ng_type ng_pptpgre_typestruct = {
.version = NG_ABI_VERSION,
.name = NG_PPTPGRE_NODE_TYPE,
.constructor = ng_pptpgre_constructor,
.rcvmsg = ng_pptpgre_rcvmsg,
.shutdown = ng_pptpgre_shutdown,
.newhook = ng_pptpgre_newhook,
.rcvdata = ng_pptpgre_rcvdata,
.disconnect = ng_pptpgre_disconnect,
.cmdlist = ng_pptpgre_cmdlist,
};
NETGRAPH_INIT(pptpgre, &ng_pptpgre_typestruct);
#define ERROUT(x) do { error = (x); goto done; } while (0)
static int
ng_pptpgre_constructor(node_p node)
{
priv_p priv;
int i;
priv = malloc(sizeof(*priv), M_NETGRAPH_PPTP, M_WAITOK | M_ZERO);
NG_NODE_SET_PRIVATE(node, priv);
mtx_init(&priv->uppersess.mtx, "ng_pptp", NULL, MTX_DEF);
ng_callout_init(&priv->uppersess.sackTimer);
ng_callout_init(&priv->uppersess.rackTimer);
priv->uppersess.node = node;
SLIST_INIT(&priv->uppersess.roq);
priv->uppersess.roq_len = 0;
ng_callout_init(&priv->uppersess.reorderTimer);
for (i = 0; i < SESSHASHSIZE; i++)
LIST_INIT(&priv->sesshash[i]);
LIST_INSERT_HEAD(&priv->sesshash[0], &priv->uppersess, sessions);
return (0);
}
static int
ng_pptpgre_newhook(node_p node, hook_p hook, const char *name)
{
const priv_p priv = NG_NODE_PRIVATE(node);
if (strcmp(name, NG_PPTPGRE_HOOK_UPPER) == 0) {
priv->upper = hook;
priv->uppersess.hook = hook;
NG_HOOK_SET_PRIVATE(hook, &priv->uppersess);
} else if (strcmp(name, NG_PPTPGRE_HOOK_LOWER) == 0) {
priv->lower = hook;
NG_HOOK_SET_RCVDATA(hook, ng_pptpgre_rcvdata_lower);
} else {
static const char hexdig[16] = "0123456789abcdef";
const char *hex;
hpriv_p hpriv;
int i, j;
uint16_t cid, hash;
if (strncmp(name, NG_PPTPGRE_HOOK_SESSION_P,
sizeof(NG_PPTPGRE_HOOK_SESSION_P) - 1) != 0)
return (EINVAL);
hex = name + sizeof(NG_PPTPGRE_HOOK_SESSION_P) - 1;
for (cid = i = 0; i < 4; i++) {
for (j = 0; j < 16 && hex[i] != hexdig[j]; j++);
if (j == 16)
return (EINVAL);
cid = (cid << 4) | j;
}
if (hex[i] != '\0')
return (EINVAL);
hpriv = malloc(sizeof(*hpriv), M_NETGRAPH_PPTP, M_NOWAIT | M_ZERO);
if (hpriv == NULL)
return (ENOMEM);
mtx_init(&hpriv->mtx, "ng_pptp", NULL, MTX_DEF);
ng_callout_init(&hpriv->sackTimer);
ng_callout_init(&hpriv->rackTimer);
hpriv->conf.cid = cid;
hpriv->node = node;
hpriv->hook = hook;
SLIST_INIT(&hpriv->roq);
hpriv->roq_len = 0;
ng_callout_init(&hpriv->reorderTimer);
NG_HOOK_SET_PRIVATE(hook, hpriv);
hash = SESSHASH(cid);
LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, sessions);
}
return (0);
}
static int
ng_pptpgre_rcvmsg(node_p node, item_p item, hook_p lasthook)
{
const priv_p priv = NG_NODE_PRIVATE(node);
struct ng_mesg *resp = NULL;
int error = 0;
struct ng_mesg *msg;
NGI_GET_MSG(item, msg);
switch (msg->header.typecookie) {
case NGM_PPTPGRE_COOKIE:
switch (msg->header.cmd) {
case NGM_PPTPGRE_SET_CONFIG:
{
struct ng_pptpgre_conf *const newConf =
(struct ng_pptpgre_conf *) msg->data;
hpriv_p hpriv;
uint16_t hash;
if (msg->header.arglen != sizeof(*newConf))
ERROUT(EINVAL);
hpriv = ng_pptpgre_find_session(priv, newConf->cid);
if (hpriv == NULL) {
hpriv = &priv->uppersess;
LIST_REMOVE(hpriv, sessions);
hash = SESSHASH(newConf->cid);
LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv,
sessions);
}
ng_pptpgre_reset(hpriv);
hpriv->conf = *newConf;
break;
}
case NGM_PPTPGRE_GET_CONFIG:
{
hpriv_p hpriv;
if (msg->header.arglen == 2) {
hpriv = ng_pptpgre_find_session(priv,
*((uint16_t *)msg->data));
if (hpriv == NULL)
ERROUT(EINVAL);
} else if (msg->header.arglen == 0) {
hpriv = &priv->uppersess;
} else
ERROUT(EINVAL);
NG_MKRESPONSE(resp, msg, sizeof(hpriv->conf), M_NOWAIT);
if (resp == NULL)
ERROUT(ENOMEM);
bcopy(&hpriv->conf, resp->data, sizeof(hpriv->conf));
break;
}
case NGM_PPTPGRE_GET_STATS:
case NGM_PPTPGRE_CLR_STATS:
case NGM_PPTPGRE_GETCLR_STATS:
{
if (msg->header.cmd != NGM_PPTPGRE_CLR_STATS) {
NG_MKRESPONSE(resp, msg,
sizeof(priv->stats), M_NOWAIT);
if (resp == NULL)
ERROUT(ENOMEM);
bcopy(&priv->stats,
resp->data, sizeof(priv->stats));
}
if (msg->header.cmd != NGM_PPTPGRE_GET_STATS)
bzero(&priv->stats, sizeof(priv->stats));
break;
}
default:
error = EINVAL;
break;
}
break;
default:
error = EINVAL;
break;
}
done:
NG_RESPOND_MSG(error, node, item, resp);
NG_FREE_MSG(msg);
return (error);
}
static int
ng_pptpgre_rcvdata(hook_p hook, item_p item)
{
const hpriv_p hpriv = NG_HOOK_PRIVATE(hook);
int rval;
if (!hpriv->conf.enabled) {
NG_FREE_ITEM(item);
return (ENXIO);
}
mtx_lock(&hpriv->mtx);
rval = ng_pptpgre_xmit(hpriv, item);
mtx_assert(&hpriv->mtx, MA_NOTOWNED);
return (rval);
}
static int
ng_pptpgre_disconnect(hook_p hook)
{
const node_p node = NG_HOOK_NODE(hook);
const priv_p priv = NG_NODE_PRIVATE(node);
const hpriv_p hpriv = NG_HOOK_PRIVATE(hook);
if (hook == priv->upper) {
priv->upper = NULL;
priv->uppersess.hook = NULL;
} else if (hook == priv->lower) {
priv->lower = NULL;
} else {
ng_pptpgre_reset(hpriv);
LIST_REMOVE(hpriv, sessions);
mtx_destroy(&hpriv->mtx);
free(hpriv, M_NETGRAPH_PPTP);
}
if ((NG_NODE_NUMHOOKS(node) == 0)
&& (NG_NODE_IS_VALID(node)))
ng_rmnode_self(node);
return (0);
}
static int
ng_pptpgre_shutdown(node_p node)
{
const priv_p priv = NG_NODE_PRIVATE(node);
ng_pptpgre_reset(&priv->uppersess);
LIST_REMOVE(&priv->uppersess, sessions);
mtx_destroy(&priv->uppersess.mtx);
free(priv, M_NETGRAPH_PPTP);
NG_NODE_UNREF(node);
return (0);
}
static int
ng_pptpgre_xmit(hpriv_p hpriv, item_p item)
{
const priv_p priv = NG_NODE_PRIVATE(hpriv->node);
u_char buf[sizeof(struct greheader) + 2 * sizeof(u_int32_t)];
struct greheader *const gre = (struct greheader *)buf;
int grelen, error;
struct mbuf *m;
mtx_assert(&hpriv->mtx, MA_OWNED);
if (item) {
NGI_GET_M(item, m);
} else {
m = NULL;
}
if (m != NULL) {
if (hpriv->conf.enableWindowing) {
if ((u_int32_t)PPTP_SEQ_DIFF(hpriv->xmitSeq,
hpriv->recvAck) >= hpriv->xmitWin) {
priv->stats.xmitDrops++;
ERROUT(ENOBUFS);
}
}
if (m->m_pkthdr.len > PPTP_MAX_PAYLOAD) {
priv->stats.xmitTooBig++;
ERROUT(EMSGSIZE);
}
} else {
priv->stats.xmitLoneAcks++;
}
be32enc(gre, PPTP_INIT_VALUE);
be16enc(&gre->length, (m != NULL) ? m->m_pkthdr.len : 0);
be16enc(&gre->cid, hpriv->conf.peerCid);
if (m != NULL) {
gre->hasSeq = 1;
if (hpriv->conf.enableWindowing) {
hpriv->timeSent[hpriv->xmitSeq - hpriv->recvAck]
= ng_pptpgre_time();
}
hpriv->xmitSeq++;
be32enc(&gre->data[0], hpriv->xmitSeq);
}
if (hpriv->conf.enableAlwaysAck || hpriv->xmitAck != hpriv->recvSeq) {
gre->hasAck = 1;
be32enc(&gre->data[gre->hasSeq], hpriv->recvSeq);
hpriv->xmitAck = hpriv->recvSeq;
if (hpriv->conf.enableDelayedAck)
ng_uncallout(&hpriv->sackTimer, hpriv->node);
}
grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck);
if (m == NULL) {
MGETHDR(m, M_NOWAIT, MT_DATA);
if (m == NULL) {
priv->stats.memoryFailures++;
ERROUT(ENOBUFS);
}
m->m_len = m->m_pkthdr.len = grelen;
m->m_pkthdr.rcvif = NULL;
} else {
M_PREPEND(m, grelen, M_NOWAIT);
if (m == NULL || (m->m_len < grelen
&& (m = m_pullup(m, grelen)) == NULL)) {
priv->stats.memoryFailures++;
ERROUT(ENOBUFS);
}
}
bcopy(gre, mtod(m, u_char *), grelen);
priv->stats.xmitPackets++;
priv->stats.xmitOctets += m->m_pkthdr.len;
if (hpriv->conf.enableWindowing &&
gre->hasSeq && hpriv->xmitSeq == hpriv->recvAck + 1)
ng_pptpgre_start_recv_ack_timer(hpriv);
mtx_unlock(&hpriv->mtx);
if (item) {
NG_FWD_NEW_DATA(error, item, priv->lower, m);
} else {
NG_SEND_DATA_ONLY(error, priv->lower, m);
}
return (error);
done:
mtx_unlock(&hpriv->mtx);
NG_FREE_M(m);
if (item)
NG_FREE_ITEM(item);
return (error);
}
static void
ng_pptpgre_ack(const hpriv_p hpriv)
{
mtx_assert(&hpriv->mtx, MA_OWNED);
if (!(callout_pending(&hpriv->sackTimer))) {
if (!hpriv->conf.enableDelayedAck) {
ng_pptpgre_xmit(hpriv, NULL);
return;
}
ng_pptpgre_start_send_ack_timer(hpriv);
mtx_unlock(&hpriv->mtx);
return;
}
mtx_unlock(&hpriv->mtx);
}
static int
ng_pptpgre_sendq(const hpriv_p hpriv, roqh *q, const struct ng_pptpgre_roq *st)
{
struct ng_pptpgre_roq *np;
struct mbuf *m;
int error = 0;
mtx_assert(&hpriv->mtx, MA_NOTOWNED);
while (!SLIST_EMPTY(q)) {
np = SLIST_FIRST(q);
SLIST_REMOVE_HEAD(q, next);
NGI_GET_M(np->item, m);
NG_FWD_NEW_DATA(error, np->item, hpriv->hook, m);
if (np != st)
free(np, M_NETGRAPH_PPTP);
}
return (error);
}
static int
ng_pptpgre_rcvdata_lower(hook_p hook, item_p item)
{
hpriv_p hpriv;
node_p node = NG_HOOK_NODE(hook);
const priv_p priv = NG_NODE_PRIVATE(node);
int iphlen, grelen, extralen;
const struct greheader *gre;
const struct ip *ip;
int error = 0;
struct mbuf *m;
roqh sendq = SLIST_HEAD_INITIALIZER(sendq);
struct ng_pptpgre_roq *last = NULL;
struct ng_pptpgre_roq *np, *prev;
struct ng_pptpgre_roq temp = { { NULL }, NULL, 0 };
long diff;
u_int32_t seq;
m = NGI_M(item);
priv->stats.recvPackets++;
priv->stats.recvOctets += m->m_pkthdr.len;
if (m->m_pkthdr.len < sizeof(*ip) + sizeof(*gre)) {
priv->stats.recvRunts++;
ERROUT(EINVAL);
}
if (m->m_len < sizeof(*ip) + sizeof(*gre)) {
if ((m = m_pullup(m, sizeof(*ip) + sizeof(*gre))) == NULL) {
priv->stats.memoryFailures++;
_NGI_M(item) = NULL;
ERROUT(ENOBUFS);
}
_NGI_M(item) = m;
}
ip = mtod(m, const struct ip *);
iphlen = ip->ip_hl << 2;
if (m->m_len < iphlen + sizeof(*gre)) {
if ((m = m_pullup(m, iphlen + sizeof(*gre))) == NULL) {
priv->stats.memoryFailures++;
_NGI_M(item) = NULL;
ERROUT(ENOBUFS);
}
_NGI_M(item) = m;
ip = mtod(m, const struct ip *);
}
gre = (const struct greheader *)((const u_char *)ip + iphlen);
grelen = sizeof(*gre) + sizeof(u_int32_t) * (gre->hasSeq + gre->hasAck);
if (m->m_pkthdr.len < iphlen + grelen) {
priv->stats.recvRunts++;
ERROUT(EINVAL);
}
if (m->m_len < iphlen + grelen) {
if ((m = m_pullup(m, iphlen + grelen)) == NULL) {
priv->stats.memoryFailures++;
_NGI_M(item) = NULL;
ERROUT(ENOBUFS);
}
_NGI_M(item) = m;
ip = mtod(m, const struct ip *);
gre = (const struct greheader *)((const u_char *)ip + iphlen);
}
extralen = m->m_pkthdr.len
- (iphlen + grelen + gre->hasSeq * be16dec(&gre->length));
if (extralen < 0) {
priv->stats.recvBadGRE++;
ERROUT(EINVAL);
}
if ((be32dec(gre) & PPTP_INIT_MASK) != PPTP_INIT_VALUE) {
priv->stats.recvBadGRE++;
ERROUT(EINVAL);
}
hpriv = ng_pptpgre_find_session(priv, be16dec(&gre->cid));
if (hpriv == NULL || hpriv->hook == NULL || !hpriv->conf.enabled) {
priv->stats.recvBadCID++;
ERROUT(EINVAL);
}
mtx_lock(&hpriv->mtx);
if (gre->hasAck) {
const u_int32_t ack = be32dec(&gre->data[gre->hasSeq]);
const int index = ack - hpriv->recvAck - 1;
long sample;
if (PPTP_SEQ_DIFF(ack, hpriv->xmitSeq) > 0) {
priv->stats.recvBadAcks++;
goto badAck;
}
if (PPTP_SEQ_DIFF(ack, hpriv->recvAck) <= 0)
goto badAck;
hpriv->recvAck = ack;
if (hpriv->conf.enableWindowing) {
sample = ng_pptpgre_time() - hpriv->timeSent[index];
diff = sample - hpriv->rtt;
hpriv->rtt += PPTP_ACK_ALPHA(diff);
if (diff < 0)
diff = -diff;
hpriv->dev += PPTP_ACK_BETA(diff - hpriv->dev);
hpriv->ato = hpriv->rtt + PPTP_ACK_CHI(hpriv->dev + 2);
if (hpriv->ato > PPTP_MAX_TIMEOUT)
hpriv->ato = PPTP_MAX_TIMEOUT;
else if (hpriv->ato < PPTP_MIN_TIMEOUT)
hpriv->ato = PPTP_MIN_TIMEOUT;
bcopy(hpriv->timeSent + index + 1, hpriv->timeSent,
sizeof(*hpriv->timeSent)
* (PPTP_XMIT_WIN - (index + 1)));
if (PPTP_SEQ_DIFF(ack, hpriv->winAck) >= 0
&& hpriv->xmitWin < PPTP_XMIT_WIN) {
hpriv->xmitWin++;
hpriv->winAck = ack + hpriv->xmitWin;
}
ng_uncallout(&hpriv->rackTimer, hpriv->node);
if (hpriv->recvAck != hpriv->xmitSeq)
ng_pptpgre_start_recv_ack_timer(hpriv);
}
}
badAck:
if (!gre->hasSeq) {
priv->stats.recvLoneAcks++;
mtx_unlock(&hpriv->mtx);
ERROUT(0);
}
seq = be32dec(&gre->data[0]);
diff = PPTP_SEQ_DIFF(seq, hpriv->recvSeq);
if (diff <= 0) {
if (diff < 0 && reorder_max == 0)
priv->stats.recvOutOfOrder++;
else
priv->stats.recvDuplicates++;
mtx_unlock(&hpriv->mtx);
ERROUT(EINVAL);
}
m_adj(m, iphlen + grelen);
if (extralen > 0)
m_adj(m, -extralen);
#define INIT_SENDQ(t) do { \
t.item = item; \
t.seq = seq; \
SLIST_INSERT_HEAD(&sendq, &t, next); \
last = &t; \
hpriv->recvSeq = seq; \
goto deliver; \
} while(0)
if (diff == 1)
INIT_SENDQ(temp);
prev = SLIST_FIRST(&hpriv->roq);
SLIST_FOREACH(np, &hpriv->roq, next) {
diff = PPTP_SEQ_DIFF(np->seq, seq);
if (diff == 0) {
priv->stats.recvDuplicates++;
mtx_unlock(&hpriv->mtx);
ERROUT(EINVAL);
}
if (diff > 0) {
if (np == prev)
prev = NULL;
break;
}
prev = np;
}
priv->stats.recvOutOfOrder++;
if (hpriv->roq_len < reorder_max)
goto enqueue;
priv->stats.recvReorderOverflow++;
if (prev == NULL) {
INIT_SENDQ(temp);
}
#undef INIT_SENDQ
np = SLIST_FIRST(&hpriv->roq);
if (prev == np)
prev = NULL;
SLIST_REMOVE_HEAD(&hpriv->roq, next);
hpriv->roq_len--;
SLIST_INSERT_HEAD(&sendq, np, next);
last = np;
hpriv->recvSeq = np->seq;
enqueue:
np = malloc(sizeof(*np), M_NETGRAPH_PPTP, M_NOWAIT | M_ZERO);
if (np == NULL) {
priv->stats.memoryFailures++;
while (!SLIST_EMPTY(&hpriv->roq)) {
np = SLIST_FIRST(&hpriv->roq);
if (np->seq > seq)
break;
SLIST_REMOVE_HEAD(&hpriv->roq, next);
hpriv->roq_len--;
if (last == NULL)
SLIST_INSERT_HEAD(&sendq, np, next);
else
SLIST_INSERT_AFTER(last, np, next);
last = np;
}
hpriv->recvSeq = seq;
ng_pptpgre_ack(hpriv);
ng_pptpgre_sendq(hpriv, &sendq, &temp);
NG_FWD_NEW_DATA(error, item, hpriv->hook, m);
ERROUT(ENOMEM);
}
np->item = item;
np->seq = seq;
if (prev == NULL)
SLIST_INSERT_HEAD(&hpriv->roq, np, next);
else
SLIST_INSERT_AFTER(prev, np, next);
hpriv->roq_len++;
deliver:
while (!SLIST_EMPTY(&hpriv->roq)) {
np = SLIST_FIRST(&hpriv->roq);
if (PPTP_SEQ_DIFF(np->seq, hpriv->recvSeq) > 1)
break;
SLIST_REMOVE_HEAD(&hpriv->roq, next);
hpriv->roq_len--;
hpriv->recvSeq = np->seq;
if (last == NULL)
SLIST_INSERT_HEAD(&sendq, np, next);
else
SLIST_INSERT_AFTER(last, np, next);
last = np;
}
if (SLIST_EMPTY(&hpriv->roq)) {
if (callout_pending(&hpriv->reorderTimer))
ng_uncallout(&hpriv->reorderTimer, hpriv->node);
} else {
if (!callout_pending(&hpriv->reorderTimer))
ng_pptpgre_start_reorder_timer(hpriv);
}
if (SLIST_EMPTY(&sendq)) {
mtx_unlock(&hpriv->mtx);
return (error);
}
ng_pptpgre_ack(hpriv);
ng_pptpgre_sendq(hpriv, &sendq, &temp);
return (error);
done:
NG_FREE_ITEM(item);
return (error);
}
static void
ng_pptpgre_start_recv_ack_timer(hpriv_p hpriv)
{
int remain, ticks;
remain = (hpriv->timeSent[0] + hpriv->ato) - ng_pptpgre_time();
if (remain < 0)
remain = 0;
ticks = howmany(remain * hz, PPTP_TIME_SCALE) + 1;
ng_callout(&hpriv->rackTimer, hpriv->node, hpriv->hook,
ticks, ng_pptpgre_recv_ack_timeout, hpriv, 0);
}
static void
ng_pptpgre_recv_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2)
{
const priv_p priv = NG_NODE_PRIVATE(node);
const hpriv_p hpriv = arg1;
priv->stats.recvAckTimeouts++;
hpriv->rtt = PPTP_ACK_DELTA(hpriv->rtt) + 1;
hpriv->ato = hpriv->rtt + PPTP_ACK_CHI(hpriv->dev);
if (hpriv->ato > PPTP_MAX_TIMEOUT)
hpriv->ato = PPTP_MAX_TIMEOUT;
else if (hpriv->ato < PPTP_MIN_TIMEOUT)
hpriv->ato = PPTP_MIN_TIMEOUT;
hpriv->recvAck = hpriv->xmitSeq;
hpriv->xmitWin = (hpriv->xmitWin + 1) / 2;
hpriv->winAck = hpriv->recvAck + hpriv->xmitWin;
}
static void
ng_pptpgre_start_send_ack_timer(hpriv_p hpriv)
{
int ackTimeout, ticks;
ackTimeout = (hpriv->rtt >> 2);
if (ackTimeout < PPTP_MIN_ACK_DELAY)
ackTimeout = PPTP_MIN_ACK_DELAY;
else if (ackTimeout > PPTP_MAX_ACK_DELAY)
ackTimeout = PPTP_MAX_ACK_DELAY;
ticks = howmany(ackTimeout * hz, PPTP_TIME_SCALE);
ng_callout(&hpriv->sackTimer, hpriv->node, hpriv->hook,
ticks, ng_pptpgre_send_ack_timeout, hpriv, 0);
}
static void
ng_pptpgre_send_ack_timeout(node_p node, hook_p hook, void *arg1, int arg2)
{
const hpriv_p hpriv = arg1;
mtx_lock(&hpriv->mtx);
ng_pptpgre_xmit(hpriv, NULL);
mtx_assert(&hpriv->mtx, MA_NOTOWNED);
}
static void
ng_pptpgre_start_reorder_timer(hpriv_p hpriv)
{
int ticks;
ticks = (((reorder_timeout * hz) + 1000 - 1) / 1000) + 1;
ng_callout(&hpriv->reorderTimer, hpriv->node, hpriv->hook,
ticks, ng_pptpgre_reorder_timeout, hpriv, 0);
}
static void
ng_pptpgre_reorder_timeout(node_p node, hook_p hook, void *arg1, int arg2)
{
const priv_p priv = NG_NODE_PRIVATE(node);
const hpriv_p hpriv = arg1;
roqh sendq = SLIST_HEAD_INITIALIZER(sendq);
struct ng_pptpgre_roq *np, *last = NULL;
priv->stats.recvReorderTimeouts++;
mtx_lock(&hpriv->mtx);
if (SLIST_EMPTY(&hpriv->roq)) {
mtx_unlock(&hpriv->mtx);
return;
}
last = np = SLIST_FIRST(&hpriv->roq);
hpriv->roq_len--;
SLIST_REMOVE_HEAD(&hpriv->roq, next);
SLIST_INSERT_HEAD(&sendq, np, next);
while (!SLIST_EMPTY(&hpriv->roq)) {
np = SLIST_FIRST(&hpriv->roq);
if (PPTP_SEQ_DIFF(np->seq, last->seq) > 1)
break;
hpriv->roq_len--;
SLIST_REMOVE_HEAD(&hpriv->roq, next);
SLIST_INSERT_AFTER(last, np, next);
last = np;
}
hpriv->recvSeq = last->seq;
if (!SLIST_EMPTY(&hpriv->roq))
ng_pptpgre_start_reorder_timer(hpriv);
ng_pptpgre_ack(hpriv);
ng_pptpgre_sendq(hpriv, &sendq, NULL);
mtx_assert(&hpriv->mtx, MA_NOTOWNED);
}
static hpriv_p
ng_pptpgre_find_session(priv_p privp, u_int16_t cid)
{
uint16_t hash = SESSHASH(cid);
hpriv_p hpriv = NULL;
LIST_FOREACH(hpriv, &privp->sesshash[hash], sessions) {
if (hpriv->conf.cid == cid)
break;
}
return (hpriv);
}
static void
ng_pptpgre_reset(hpriv_p hpriv)
{
struct ng_pptpgre_roq *np;
hpriv->ato = PPTP_MAX_TIMEOUT;
hpriv->rtt = PPTP_TIME_SCALE / 10;
if (hpriv->conf.peerPpd > 1)
hpriv->rtt *= hpriv->conf.peerPpd;
hpriv->dev = 0;
hpriv->xmitWin = (hpriv->conf.recvWin + 1) / 2;
if (hpriv->xmitWin < 2)
hpriv->xmitWin = 2;
else if (hpriv->xmitWin > PPTP_XMIT_WIN)
hpriv->xmitWin = PPTP_XMIT_WIN;
hpriv->winAck = hpriv->xmitWin;
hpriv->recvSeq = ~0;
hpriv->recvAck = ~0;
hpriv->xmitSeq = ~0;
hpriv->xmitAck = ~0;
ng_uncallout(&hpriv->sackTimer, hpriv->node);
ng_uncallout(&hpriv->rackTimer, hpriv->node);
ng_uncallout(&hpriv->reorderTimer, hpriv->node);
while (!SLIST_EMPTY(&hpriv->roq)) {
np = SLIST_FIRST(&hpriv->roq);
SLIST_REMOVE_HEAD(&hpriv->roq, next);
NG_FREE_ITEM(np->item);
free(np, M_NETGRAPH_PPTP);
}
hpriv->roq_len = 0;
}
static pptptime_t
ng_pptpgre_time(void)
{
struct timeval tv;
pptptime_t t;
microuptime(&tv);
t = (pptptime_t)tv.tv_sec * PPTP_TIME_SCALE;
t += tv.tv_usec / (1000000 / PPTP_TIME_SCALE);
return(t);
}