diff options
-rw-r--r-- | sys/netgraph/ng_ppp.c | 1308 | ||||
-rw-r--r-- | sys/netgraph/ng_ppp.h | 98 |
2 files changed, 1218 insertions, 188 deletions
diff --git a/sys/netgraph/ng_ppp.c b/sys/netgraph/ng_ppp.c index 3377a80..9dd2752 100644 --- a/sys/netgraph/ng_ppp.c +++ b/sys/netgraph/ng_ppp.c @@ -41,9 +41,7 @@ */ /* - * This node does PPP protocol multiplexing based on PPP protocol - * ID numbers. This node does not add address and control fields, - * as that is considered a ``device layer'' issue. + * PPP node type. */ #include <sys/param.h> @@ -59,30 +57,127 @@ #include <netgraph/ng_message.h> #include <netgraph/netgraph.h> #include <netgraph/ng_ppp.h> - -/* Protocol stuff */ -#define PROT_DOWNLINK 0xffff -#define PROT_BYPASS 0x0000 +#include <netgraph/ng_vjc.h> #define PROT_VALID(p) (((p) & 0x0101) == 0x0001) -#define PROT_COMPRESSIBLE(p) (((p) & 0xFF00) == 0x0000) +#define PROT_COMPRESSIBLE(p) (((p) & 0xff00) == 0x0000) + +/* Some PPP protocol numbers we're interested in */ +#define PROT_APPLETALK 0x0029 +#define PROT_COMPD 0x00fd +#define PROT_CRYPTD 0x0053 +#define PROT_IP 0x0021 +#define PROT_IPX 0x002B +#define PROT_MP 0x003d +#define PROT_VJCOMP 0x002d +#define PROT_VJUNCOMP 0x002f + +/* Multilink PPP definitions */ +#define MP_MIN_MRRU 1500 /* per RFC 1990 */ +#define MP_INITIAL_SEQ 0 /* per RFC 1990 */ +#define MP_MIN_LINK_MRU 32 + +#define MP_MAX_SEQ_LINGER 64 /* max frags we will hold */ +#define MP_INSANE_SEQ_JUMP 128 /* a sequence # jump too far */ +#define MP_MIN_FRAG_LEN 6 /* don't frag smaller frames */ + +#define MP_SHORT_SEQ_MASK 0x00000fff /* short seq # mask */ +#define MP_SHORT_SEQ_HIBIT 0x00000800 /* short seq # high bit */ +#define MP_SHORT_FIRST_FLAG 0x00008000 /* first fragment in frame */ +#define MP_SHORT_LAST_FLAG 0x00004000 /* last fragment in frame */ -/* Extract protocol from hook private pointer */ -#define HOOK_PROTO(hook) (*((u_int16_t *) &hook->private)) +#define MP_LONG_SEQ_MASK 0x00ffffff /* long seq # mask */ +#define MP_LONG_SEQ_HIBIT 0x00800000 /* long seq # high bit */ +#define MP_LONG_FIRST_FLAG 0x80000000 /* first fragment in frame */ +#define MP_LONG_LAST_FLAG 0x40000000 /* last fragment in frame */ + +#define MP_SEQ_MASK (priv->conf.recvShortSeq ? \ + MP_SHORT_SEQ_MASK : MP_LONG_SEQ_MASK) + +/* Sign extension of MP sequence numbers */ +#define MP_SHORT_EXTEND(s) (((s) & MP_SHORT_SEQ_HIBIT) ? \ + ((s) | ~MP_SHORT_SEQ_MASK) : (s)) +#define MP_LONG_EXTEND(s) (((s) & MP_LONG_SEQ_HIBIT) ? \ + ((s) | ~MP_LONG_SEQ_MASK) : (s)) + +/* Comparision of MP sequence numbers */ +#define MP_SHORT_SEQ_DIFF(x,y) (MP_SHORT_EXTEND(x) - MP_SHORT_EXTEND(y)) +#define MP_LONG_SEQ_DIFF(x,y) (MP_LONG_EXTEND(x) - MP_LONG_EXTEND(y)) + +#define MP_SEQ_DIFF(x,y) (priv->conf.recvShortSeq ? \ + MP_SHORT_SEQ_DIFF((x), (y)) : \ + MP_LONG_SEQ_DIFF((x), (y))) + +/* We store incoming fragments this way */ +struct ng_ppp_frag { + int seq; + u_char first; + u_char last; + struct mbuf *data; + meta_p meta; + CIRCLEQ_ENTRY(ng_ppp_frag) f_qent; +}; + +/* We keep track of link queue status this way */ +struct ng_ppp_link_qstat { + struct timeval lastWrite; /* time of last write */ + int bytesInQueue; /* bytes in the queue then */ +}; + +/* We use integer indicies to refer to the non-link hooks */ +static const char *const ng_ppp_hook_names[] = { + NG_PPP_HOOK_ATALK, +#define HOOK_INDEX_ATALK 0 + NG_PPP_HOOK_BYPASS, +#define HOOK_INDEX_BYPASS 1 + NG_PPP_HOOK_COMPRESS, +#define HOOK_INDEX_COMPRESS 2 + NG_PPP_HOOK_ENCRYPT, +#define HOOK_INDEX_ENCRYPT 3 + NG_PPP_HOOK_DECOMPRESS, +#define HOOK_INDEX_DECOMPRESS 4 + NG_PPP_HOOK_DECRYPT, +#define HOOK_INDEX_DECRYPT 5 + NG_PPP_HOOK_INET, +#define HOOK_INDEX_INET 6 + NG_PPP_HOOK_IPX, +#define HOOK_INDEX_IPX 7 + NG_PPP_HOOK_VJC_COMP, +#define HOOK_INDEX_VJC_COMP 8 + NG_PPP_HOOK_VJC_IP, +#define HOOK_INDEX_VJC_IP 9 + NG_PPP_HOOK_VJC_UNCOMP, +#define HOOK_INDEX_VJC_UNCOMP 10 + NG_PPP_HOOK_VJC_VJIP, +#define HOOK_INDEX_VJC_VJIP 11 + NULL +#define HOOK_INDEX_MAX 12 +}; + +/* We store index numbers in the hook private pointer. The HOOK_INDEX() + for a hook is either the index (above) for normal hooks, or the ones + complement of the link number for link hooks. */ +#define HOOK_INDEX(hook) (*((int16_t *) &(hook)->private)) /* Node private data */ struct private { - struct ng_ppp_stat stats; - u_int protocomp:1; + struct ng_ppp_node_config conf; + struct ng_ppp_link_stat bundleStats; + struct ng_ppp_link_stat linkStats[NG_PPP_MAX_LINKS]; + hook_p links[NG_PPP_MAX_LINKS]; + hook_p hooks[HOOK_INDEX_MAX]; + u_char vjCompHooked; + u_char allLinksEqual; + u_short activeLinks[NG_PPP_MAX_LINKS]; + u_int numActiveLinks; + u_int lastLink; /* for round robin */ + struct ng_ppp_link_qstat qstat[NG_PPP_MAX_LINKS]; + CIRCLEQ_HEAD(ng_ppp_fraglist, ng_ppp_frag) + frags; /* incoming fragments */ + int mpSeqOut; /* next out MP seq # */ }; typedef struct private *priv_p; -/* Protocol aliases */ -struct protoalias { - char *name; - u_int16_t proto; -}; - /* Netgraph node methods */ static int ng_ppp_constructor(node_p *nodep); static int ng_ppp_rcvmsg(node_p node, struct ng_mesg *msg, @@ -92,12 +187,21 @@ static int ng_ppp_newhook(node_p node, hook_p hook, const char *name); static int ng_ppp_rcvdata(hook_p hook, struct mbuf *m, meta_p meta); static int ng_ppp_disconnect(hook_p hook); -/* Helper stuff */ -static int ng_ppp_decodehookname(const char *name); -static hook_p ng_ppp_findhook(node_p node, int proto); +/* Helper functions */ +static int ng_ppp_input(node_p node, int ln, struct mbuf *m, meta_p meta); +static int ng_ppp_output(node_p node, int ln, struct mbuf *m, meta_p meta); +static int ng_ppp_mp_input(node_p nd, int ln, struct mbuf *m, meta_p meta); +static int ng_ppp_mp_output(node_p node, struct mbuf *m, meta_p meta); +static void ng_ppp_mp_strategy(node_p node, int len, int *distrib); +static int ng_ppp_intcmp(const void *v1, const void *v2); +static struct mbuf *ng_ppp_addproto(struct mbuf *m, int proto, int compOK); +static int ng_ppp_config_valid(node_p node, + const struct ng_ppp_node_config *newConf); +static void ng_ppp_update(node_p node, int newConf); +static void ng_ppp_free_frags(node_p node); /* Node type descriptor */ -static struct ng_type typestruct = { +static struct ng_type ng_ppp_typestruct = { NG_VERSION, NG_PPP_NODE_TYPE, NULL, @@ -111,29 +215,9 @@ static struct ng_type typestruct = { ng_ppp_rcvdata, ng_ppp_disconnect }; -NETGRAPH_INIT(ppp, &typestruct); +NETGRAPH_INIT(ppp, &ng_ppp_typestruct); -/* Protocol aliases */ -static const struct protoalias gAliases[] = -{ - { NG_PPP_HOOK_DOWNLINK, PROT_DOWNLINK }, - { NG_PPP_HOOK_BYPASS, PROT_BYPASS }, - { NG_PPP_HOOK_LCP, 0xc021 }, - { NG_PPP_HOOK_IPCP, 0x8021 }, - { NG_PPP_HOOK_ATCP, 0x8029 }, - { NG_PPP_HOOK_CCP, 0x80fd }, - { NG_PPP_HOOK_ECP, 0x8053 }, - { NG_PPP_HOOK_IP, 0x0021 }, - { NG_PPP_HOOK_VJCOMP, 0x002d }, - { NG_PPP_HOOK_VJUNCOMP, 0x002f }, - { NG_PPP_HOOK_MP, 0x003d }, - { NG_PPP_HOOK_COMPD, 0x00fd }, - { NG_PPP_HOOK_CRYPTD, 0x0053 }, - { NG_PPP_HOOK_PAP, 0xc023 }, - { NG_PPP_HOOK_CHAP, 0xc223 }, - { NG_PPP_HOOK_LQR, 0xc025 }, - { NULL, 0 } -}; +static int *compareLatencies; /* hack for ng_ppp_intcmp() */ #define ERROUT(x) do { error = (x); goto done; } while (0) @@ -142,7 +226,7 @@ static const struct protoalias gAliases[] = ************************************************************************/ /* - * Node constructor + * Node type constructor */ static int ng_ppp_constructor(node_p *nodep) @@ -157,12 +241,15 @@ ng_ppp_constructor(node_p *nodep) bzero(priv, sizeof(*priv)); /* Call generic node constructor */ - if ((error = ng_make_node_common(&typestruct, nodep))) { + if ((error = ng_make_node_common(&ng_ppp_typestruct, nodep))) { FREE(priv, M_NETGRAPH); return (error); } (*nodep)->private = priv; + /* Initialize state */ + CIRCLEQ_INIT(&priv->frags); + /* Done */ return (0); } @@ -174,22 +261,59 @@ static int ng_ppp_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = node->private; - int proto; + int linkNum = -1; + hook_p *hookPtr = NULL; + int hookIndex = -1; - /* Decode protocol number */ - if ((proto = ng_ppp_decodehookname(name)) < 0) - return (EINVAL); + /* Figure out which hook it is */ + if (strncmp(name, NG_PPP_HOOK_LINK_PREFIX, /* a link hook? */ + strlen(NG_PPP_HOOK_LINK_PREFIX)) == 0) { + int gotDigit = 0; + const char *cp; + + for (cp = name + strlen(NG_PPP_HOOK_LINK_PREFIX); + *cp >= '0' && *cp <= '9'; cp++) { + if (!gotDigit) { + if (*cp == '0') /* no leading zeros */ + return (EINVAL); + linkNum = *cp - '0'; + gotDigit = 1; + } else + linkNum = (linkNum * 10) + (*cp - '0'); + if (linkNum >= NG_PPP_MAX_LINKS) + return (EINVAL); + } + if (!gotDigit || *cp != '\0') + return (EINVAL); + hookPtr = &priv->links[linkNum]; + hookIndex = ~linkNum; + } else { /* must be a non-link hook */ + int i; + + for (i = 0; ng_ppp_hook_names[i] != NULL; i++) { + if (strcmp(name, ng_ppp_hook_names[i]) == 0) { + hookPtr = &priv->hooks[i]; + hookIndex = i; + break; + } + } + if (ng_ppp_hook_names[i] == NULL) + return (EINVAL); /* no such hook */ + } - /* See if already connected */ - if (ng_ppp_findhook(node, proto) != NULL) + /* See if hook is already connected */ + if (*hookPtr != NULL) return (EISCONN); - /* Clear stats when downstream hook reconnected */ - if (proto == PROT_DOWNLINK) - bzero(&priv->stats, sizeof(priv->stats)); + /* Disallow more than one link unless multilink is enabled */ + if (linkNum != -1 && priv->conf.links[linkNum].enableLink + && !priv->conf.enableMultilink && priv->numActiveLinks >= 1) + return (ENODEV); /* OK */ - HOOK_PROTO(hook) = proto; + *hookPtr = hook; + HOOK_INDEX(hook) = hookIndex; + ng_ppp_update(node, 0); return (0); } @@ -207,25 +331,67 @@ ng_ppp_rcvmsg(node_p node, struct ng_mesg *msg, switch (msg->header.typecookie) { case NGM_PPP_COOKIE: switch (msg->header.cmd) { - case NGM_PPP_SET_PROTOCOMP: - if (msg->header.arglen < sizeof(int)) + case NGM_PPP_SET_CONFIG: + { + struct ng_ppp_node_config *const newConf = + (struct ng_ppp_node_config *) msg->data; + + /* Check for invalid or illegal config */ + if (msg->header.arglen != sizeof(*newConf)) + ERROUT(EINVAL); + if (!ng_ppp_config_valid(node, newConf)) ERROUT(EINVAL); - priv->protocomp = !!*((int *) msg->data); + priv->conf = *newConf; + ng_ppp_update(node, 1); break; - case NGM_PPP_GET_STATS: - NG_MKRESPONSE(resp, msg, sizeof(priv->stats), M_NOWAIT); + } + case NGM_PPP_GET_CONFIG: + NG_MKRESPONSE(resp, msg, sizeof(priv->conf), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); - *((struct ng_ppp_stat *) resp->data) = priv->stats; + bcopy(&priv->conf, resp->data, sizeof(priv->conf)); break; - case NGM_PPP_CLR_STATS: - bzero(&priv->stats, sizeof(priv->stats)); + case NGM_PPP_GET_LINK_STATS: + case NGM_PPP_CLR_LINK_STATS: + { + struct ng_ppp_link_stat *stats; + u_int16_t linkNum; + + if (msg->header.arglen != sizeof(u_int16_t)) + ERROUT(EINVAL); + linkNum = *((u_int16_t *) msg->data); + if (linkNum >= NG_PPP_MAX_LINKS + && linkNum != NG_PPP_BUNDLE_LINKNUM) + ERROUT(EINVAL); + stats = (linkNum == NG_PPP_BUNDLE_LINKNUM) ? + &priv->bundleStats : &priv->linkStats[linkNum]; + if (msg->header.cmd == NGM_PPP_GET_LINK_STATS) { + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_ppp_link_stat), M_NOWAIT); + if (resp == NULL) + ERROUT(ENOMEM); + bcopy(stats, resp->data, sizeof(*stats)); + } else + bzero(stats, sizeof(*stats)); break; + } default: error = EINVAL; break; } break; + case NGM_VJC_COOKIE: + { + char path[NG_PATHLEN + 1]; + node_p origNode; + + if ((error = ng_path2node(node, raddr, &origNode, NULL)) != 0) + ERROUT(error); + snprintf(path, sizeof(path), "[%lx]:%s", + (long) node, NG_PPP_HOOK_VJC_IP); + return ng_send_msg(origNode, msg, path, rptr); + break; + } default: error = EINVAL; break; @@ -248,80 +414,178 @@ ng_ppp_rcvdata(hook_p hook, struct mbuf *m, meta_p meta) { const node_p node = hook->node; const priv_p priv = node->private; - u_int16_t proto = HOOK_PROTO(hook); - int error = 0; + const int index = HOOK_INDEX(hook); + u_int16_t linkNum = NG_PPP_BUNDLE_LINKNUM; + hook_p outHook = NULL; + int proto = 0, error; - switch (proto) { + /* Did it come from a link hook? */ + if (index < 0) { - /* Prepend the (possibly compressed) protocol number */ - default: - { - int psize = (priv->protocomp - && PROT_COMPRESSIBLE(proto)) ? 1 : 2; - - M_PREPEND(m, psize, M_NOWAIT); - if (!m || !(m = m_pullup(m, psize))) - ERROUT(ENOBUFS); - if (psize == 1) - *mtod(m, u_char *) = proto; - else - *mtod(m, u_short *) = htons(proto); - hook = ng_ppp_findhook(node, PROT_DOWNLINK); - break; - } + /* Is link active? */ + linkNum = (u_int16_t)~index; + KASSERT(linkNum < NG_PPP_MAX_LINKS, + ("%s: bogus index 0x%x", __FUNCTION__, index)); + if (!priv->conf.links[linkNum].enableLink) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } - /* Extract the protocol number and direct to the corresponding hook */ - case PROT_DOWNLINK: - { /* Stats */ - priv->stats.recvFrames++; - priv->stats.recvOctets += m->m_pkthdr.len; - - /* Extract protocol number */ - for (proto = 0; - !PROT_VALID(proto); - proto = (proto << 8) + *mtod(m, u_char *), m_adj(m, 1)) { - if (m == NULL) { - priv->stats.badProto++; - ERROUT(EINVAL); - } - if ((m = m_pullup(m, 1)) == NULL) - ERROUT(ENOBUFS); - } + priv->linkStats[linkNum].recvFrames++; + priv->linkStats[linkNum].recvOctets += m->m_pkthdr.len; + + /* Dispatch incoming frame */ + return ng_ppp_input(node, linkNum, m, meta); + } + + /* Get protocol & check if data allowed from this hook */ + switch (index) { - /* Find corresponding hook; if none, use the "unhooked" - hook and leave the two-byte protocol prepended */ - if ((hook = ng_ppp_findhook(node, proto)) == NULL) { - priv->stats.unknownProto++; - hook = ng_ppp_findhook(node, PROT_BYPASS); - M_PREPEND(m, 2, M_NOWAIT); - if (m == NULL || (m = m_pullup(m, 2)) == NULL) - ERROUT(ENOBUFS); - *mtod(m, u_short *) = htons(proto); + /* Downwards flowing data */ + case HOOK_INDEX_ATALK: + if (!priv->conf.enableAtalk) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } + proto = PROT_APPLETALK; + break; + case HOOK_INDEX_IPX: + if (!priv->conf.enableIPX) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } + proto = PROT_IPX; + break; + case HOOK_INDEX_INET: + case HOOK_INDEX_VJC_VJIP: + if (!priv->conf.enableIP) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } + proto = PROT_IP; + break; + case HOOK_INDEX_VJC_COMP: + if (!priv->conf.enableVJCompression) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } + proto = PROT_VJCOMP; + break; + case HOOK_INDEX_VJC_UNCOMP: + if (!priv->conf.enableVJCompression) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } + proto = PROT_VJUNCOMP; + break; + case HOOK_INDEX_COMPRESS: + if (!priv->conf.enableCompression) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } + proto = PROT_COMPD; + break; + case HOOK_INDEX_ENCRYPT: + if (!priv->conf.enableEncryption) { + NG_FREE_DATA(m, meta); + return (ENXIO); } + proto = PROT_CRYPTD; + break; + case HOOK_INDEX_BYPASS: + if (m->m_pkthdr.len < 4) { + NG_FREE_META(meta); + return (EINVAL); + } + if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { + NG_FREE_META(meta); + return (ENOBUFS); + } + linkNum = ntohs(mtod(m, u_int16_t *)[0]); + proto = ntohs(mtod(m, u_int16_t *)[1]); + m_adj(m, 4); + if (linkNum >= NG_PPP_MAX_LINKS + && linkNum != NG_PPP_BUNDLE_LINKNUM) + return (EINVAL); break; - } - /* Send raw data from "unhooked" hook as-is; we assume the - protocol is already prepended */ - case PROT_BYPASS: - hook = ng_ppp_findhook(node, PROT_DOWNLINK); + /* Upwards flowing data */ + case HOOK_INDEX_VJC_IP: + if (!priv->conf.enableVJDecompression) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } + break; + case HOOK_INDEX_DECOMPRESS: + if (!priv->conf.enableDecompression) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } break; + case HOOK_INDEX_DECRYPT: + if (!priv->conf.enableDecryption) { + NG_FREE_DATA(m, meta); + return (ENXIO); + } + break; + default: + KASSERT(0, ("%s: bogus index 0x%x", __FUNCTION__, index)); } - /* Stats */ - if (m != NULL && hook != NULL && HOOK_PROTO(hook) == PROT_DOWNLINK) { - priv->stats.xmitFrames++; - priv->stats.xmitOctets += m->m_pkthdr.len; - } + /* Now figure out what to do with the frame */ + switch (index) { + case HOOK_INDEX_INET: + if (priv->conf.enableVJCompression && priv->vjCompHooked) { + outHook = priv->hooks[HOOK_INDEX_VJC_IP]; + break; + } + /* FALLTHROUGH */ + case HOOK_INDEX_ATALK: + case HOOK_INDEX_IPX: + case HOOK_INDEX_VJC_COMP: + case HOOK_INDEX_VJC_UNCOMP: + case HOOK_INDEX_VJC_VJIP: + if (priv->conf.enableCompression + && priv->hooks[HOOK_INDEX_COMPRESS] != NULL) { + if ((m = ng_ppp_addproto(m, proto, 1)) == NULL) { + NG_FREE_META(meta); + return (ENOBUFS); + } + outHook = priv->hooks[HOOK_INDEX_COMPRESS]; + break; + } + /* FALLTHROUGH */ + case HOOK_INDEX_COMPRESS: + if (priv->conf.enableEncryption + && priv->hooks[HOOK_INDEX_ENCRYPT] != NULL) { + if ((m = ng_ppp_addproto(m, proto, 1)) == NULL) { + NG_FREE_META(meta); + return (ENOBUFS); + } + outHook = priv->hooks[HOOK_INDEX_ENCRYPT]; + break; + } + /* FALLTHROUGH */ + case HOOK_INDEX_ENCRYPT: + case HOOK_INDEX_BYPASS: + if ((m = ng_ppp_addproto(m, proto, + linkNum == NG_PPP_BUNDLE_LINKNUM + || priv->conf.links[linkNum].enableProtoComp)) == NULL) { + NG_FREE_META(meta); + return (ENOBUFS); + } + return ng_ppp_output(node, NG_PPP_BUNDLE_LINKNUM, m, meta); - /* Forward packet on hook */ - NG_SEND_DATA(error, hook, m, meta); - return (error); + /* Incoming data */ + case HOOK_INDEX_DECRYPT: + case HOOK_INDEX_DECOMPRESS: + case HOOK_INDEX_VJC_IP: + return ng_ppp_input(node, NG_PPP_BUNDLE_LINKNUM, m, meta); + } -done: - /* Something went wrong */ - NG_FREE_DATA(m, meta); + /* Send packet out hook */ + NG_SEND_DATA(error, outHook, m, meta); return (error); } @@ -360,47 +624,781 @@ ng_ppp_disconnect(hook_p hook) ************************************************************************/ /* - * Decode ASCII protocol name + * Handle an incoming frame. Extract the PPP protocol number + * and dispatch accordingly. */ static int -ng_ppp_decodehookname(const char *name) +ng_ppp_input(node_p node, int linkNum, struct mbuf *m, meta_p meta) { - int k, proto; - - for (k = 0; gAliases[k].name; k++) - if (!strcmp(gAliases[k].name, name)) - return (gAliases[k].proto); - if (strlen(name) != 6 || name[0] != '0' || name[1] != 'x') - return (-1); - for (proto = k = 2; k < 6; k++) { - const u_char ch = name[k] | 0x20; - int dig; - - if (ch >= '0' && ch <= '9') - dig = ch - '0'; - else if (ch >= 'a' && ch <= 'f') - dig = ch - 'a' + 10; + const priv_p priv = node->private; + hook_p outHook = NULL; + int proto, error; + + /* Extract protocol number */ + for (proto = 0; !PROT_VALID(proto) && m->m_pkthdr.len > 0; ) { + if (m->m_len < 1 && (m = m_pullup(m, 1)) == NULL) { + NG_FREE_META(meta); + return (ENOBUFS); + } + proto = (proto << 8) + *mtod(m, u_char *); + m_adj(m, 1); + } + if (!PROT_VALID(proto)) { + if (linkNum == NG_PPP_BUNDLE_LINKNUM) + priv->bundleStats.badProtos++; else - return (-1); - proto = (proto << 4) + dig; + priv->linkStats[linkNum].badProtos++; + NG_FREE_DATA(m, meta); + return (EINVAL); + } + + /* Check protocol */ + switch (proto) { + case PROT_COMPD: + if (priv->conf.enableDecompression) + outHook = priv->hooks[HOOK_INDEX_DECOMPRESS]; + break; + case PROT_CRYPTD: + if (priv->conf.enableDecryption) + outHook = priv->hooks[HOOK_INDEX_DECRYPT]; + break; + case PROT_VJCOMP: + if (priv->conf.enableVJDecompression && priv->vjCompHooked) + outHook = priv->hooks[HOOK_INDEX_VJC_COMP]; + break; + case PROT_VJUNCOMP: + if (priv->conf.enableVJDecompression && priv->vjCompHooked) + outHook = priv->hooks[HOOK_INDEX_VJC_UNCOMP]; + break; + case PROT_MP: + if (priv->conf.enableMultilink) { + NG_FREE_META(meta); + return ng_ppp_mp_input(node, linkNum, m, meta); + } + break; + case PROT_APPLETALK: + if (priv->conf.enableAtalk) + outHook = priv->hooks[HOOK_INDEX_ATALK]; + break; + case PROT_IPX: + if (priv->conf.enableIPX) + outHook = priv->hooks[HOOK_INDEX_IPX]; + break; + case PROT_IP: + if (priv->conf.enableIP) + outHook = priv->hooks[HOOK_INDEX_INET]; + break; + } + + /* For unknown/inactive protocols, forward out the bypass hook */ + if (outHook == NULL) { + M_PREPEND(m, 4, M_NOWAIT); + if (m == NULL || (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL)) + return (ENOBUFS); + mtod(m, u_int16_t *)[0] = htons(linkNum); + mtod(m, u_int16_t *)[1] = htons(proto); + outHook = priv->hooks[HOOK_INDEX_BYPASS]; + } + + /* Forward frame */ + NG_SEND_DATA(error, outHook, m, meta); + return (error); +} + +/* + * Deliver a frame out a link, either a real one or NG_PPP_BUNDLE_LINKNUM + */ +static int +ng_ppp_output(node_p node, int linkNum, struct mbuf *m, meta_p meta) +{ + const priv_p priv = node->private; + int error; + + /* Check for bundle virtual link */ + if (linkNum == NG_PPP_BUNDLE_LINKNUM) { + if (priv->conf.enableMultilink) + return ng_ppp_mp_output(node, m, meta); + linkNum = priv->activeLinks[0]; } - if (!PROT_VALID(proto)) - return(-1); - return (proto); + + /* Check link status */ + if (!priv->conf.links[linkNum].enableLink) + return (ENXIO); + if (priv->links[linkNum] == NULL) { + NG_FREE_DATA(m, meta); + return (ENETDOWN); + } + + /* Update stats XXX even if error? */ + priv->linkStats[linkNum].xmitFrames++; + priv->linkStats[linkNum].xmitOctets += m->m_pkthdr.len; + + /* Deliver frame */ + NG_SEND_DATA(error, priv->links[linkNum], m, meta); + return error; } /* - * Find a hook by protocol number + * Handle an incoming multi-link fragment */ -static hook_p -ng_ppp_findhook(node_p node, int proto) +static int +ng_ppp_mp_input(node_p node, int linkNum, struct mbuf *m, meta_p meta) { - hook_p hook; + const priv_p priv = node->private; + struct ng_ppp_frag frag0, *frag = &frag0; + struct ng_ppp_frag *qent, *qnext; + struct ng_ppp_frag *first = NULL, *last = NULL; + int diff, highSeq, nextSeq; + struct mbuf *tail; + + /* Extract fragment information from MP header */ + if (priv->conf.recvShortSeq) { + u_int16_t shdr; + + if (m->m_pkthdr.len < 2) { + NG_FREE_DATA(m, meta); + return (EINVAL); + } + if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { + NG_FREE_META(meta); + return (ENOBUFS); + } + shdr = ntohs(*mtod(m, u_int16_t *)); + frag->seq = shdr & MP_SHORT_SEQ_MASK; + frag->first = (shdr & MP_SHORT_FIRST_FLAG) != 0; + frag->last = (shdr & MP_SHORT_LAST_FLAG) != 0; + highSeq = CIRCLEQ_EMPTY(&priv->frags) ? + frag->seq : CIRCLEQ_FIRST(&priv->frags)->seq; + diff = MP_SHORT_SEQ_DIFF(frag->seq, highSeq); + m_adj(m, 2); + } else { + u_int32_t lhdr; + + if (m->m_pkthdr.len < 4) { + NG_FREE_DATA(m, meta); + return (EINVAL); + } + if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { + NG_FREE_META(meta); + return (ENOBUFS); + } + lhdr = ntohl(*mtod(m, u_int32_t *)); + frag->seq = lhdr & MP_LONG_SEQ_MASK; + frag->first = (lhdr & MP_LONG_FIRST_FLAG) != 0; + frag->last = (lhdr & MP_LONG_LAST_FLAG) != 0; + highSeq = CIRCLEQ_EMPTY(&priv->frags) ? + frag->seq : CIRCLEQ_FIRST(&priv->frags)->seq; + diff = MP_LONG_SEQ_DIFF(frag->seq, highSeq); + m_adj(m, 4); + } + frag->data = m; + frag->meta = meta; + + /* If the sequence number makes a large jump, empty the queue */ + if (diff <= -MP_INSANE_SEQ_JUMP || diff >= MP_INSANE_SEQ_JUMP) + ng_ppp_free_frags(node); + + /* Optimization: handle a frame that's all in one fragment */ + if (frag->first && frag->last) + return ng_ppp_input(node, NG_PPP_BUNDLE_LINKNUM, m, meta); + + /* Allocate a new frag struct for the queue */ + MALLOC(frag, struct ng_ppp_frag *, sizeof(*frag), M_NETGRAPH, M_NOWAIT); + if (frag == NULL) { + NG_FREE_DATA(m, meta); + return (ENOMEM); + } + *frag = frag0; + meta = NULL; + m = NULL; + + /* Add fragment to queue, which is reverse sorted by sequence number */ + CIRCLEQ_FOREACH(qent, &priv->frags, f_qent) { + diff = MP_SEQ_DIFF(frag->seq, qent->seq); + if (diff > 0) { + CIRCLEQ_INSERT_BEFORE(&priv->frags, qent, frag, f_qent); + break; + } else if (diff == 0) { /* should never happen! */ + log(LOG_ERR, "%s: rec'd dup MP fragment\n", node->name); + if (linkNum == NG_PPP_BUNDLE_LINKNUM) + priv->linkStats[linkNum].dupFragments++; + else + priv->bundleStats.dupFragments++; + NG_FREE_DATA(frag->data, frag->meta); + FREE(frag, M_NETGRAPH); + return (EINVAL); + } + } + if (qent == NULL) + CIRCLEQ_INSERT_TAIL(&priv->frags, frag, f_qent); + + /* Find the first fragment in the possibly newly completed frame */ + for (nextSeq = frag->seq, qent = frag; + qent != (void *) &priv->frags; + qent = CIRCLEQ_PREV(qent, f_qent)) { + if (qent->seq != nextSeq) + goto pruneQueue; + if (qent->first) { + first = qent; + break; + } + nextSeq = (nextSeq + 1) & MP_SEQ_MASK; + } + + /* Find the last fragment in the possibly newly completed frame */ + for (nextSeq = frag->seq, qent = frag; + qent != (void *) &priv->frags; + qent = CIRCLEQ_NEXT(qent, f_qent)) { + if (qent->seq != nextSeq) + goto pruneQueue; + if (qent->last) { + last = qent; + break; + } + nextSeq = (nextSeq - 1) & MP_SEQ_MASK; + } + + /* We have a complete frame, extract it from the queue */ + for (tail = NULL, qent = first; qent != NULL; qent = qnext) { + qnext = CIRCLEQ_PREV(qent, f_qent); + CIRCLEQ_REMOVE(&priv->frags, qent, f_qent); + if (tail == NULL) { + tail = m = qent->data; + meta = qent->meta; /* inherit first frag's meta */ + } else { + m->m_pkthdr.len += qent->data->m_pkthdr.len; + tail->m_next = qent->data; + NG_FREE_META(qent->meta); /* drop other frag's metas */ + } + while (tail->m_next != NULL) + tail = tail->m_next; + if (qent == last) + qnext = NULL; + FREE(qent, M_NETGRAPH); + } + +pruneQueue: + /* Prune out stale entries in the queue */ + for (qent = CIRCLEQ_LAST(&priv->frags); + qent != (void *) &priv->frags; qent = qnext) { + if (MP_SEQ_DIFF(highSeq, qent->seq) <= MP_MAX_SEQ_LINGER) + break; + qnext = CIRCLEQ_PREV(qent, f_qent); + CIRCLEQ_REMOVE(&priv->frags, qent, f_qent); + NG_FREE_DATA(qent->data, qent->meta); + FREE(qent, M_NETGRAPH); + } + + /* Deliver newly completed frame, if any */ + return m ? ng_ppp_input(node, NG_PPP_BUNDLE_LINKNUM, m, meta) : 0; +} + +/* + * Deliver a frame out on the bundle, i.e., figure out how to fragment + * the frame across the individual PPP links and do so. + */ +static int +ng_ppp_mp_output(node_p node, struct mbuf *m, meta_p meta) +{ + const priv_p priv = node->private; + int distrib[NG_PPP_MAX_LINKS]; + int firstFragment; + int activeLinkNum; + + /* At least one link must be active */ + if (priv->numActiveLinks == 0) { + NG_FREE_DATA(m, meta); + return (ENETDOWN); + } + + /* Round-robin strategy */ + if (priv->conf.enableRoundRobin || m->m_pkthdr.len < MP_MIN_FRAG_LEN) { + activeLinkNum = priv->lastLink++ % priv->numActiveLinks; + bzero(&distrib, priv->numActiveLinks * sizeof(distrib[0])); + distrib[activeLinkNum] = m->m_pkthdr.len; + goto deliver; + } + + /* Strategy when all links are equivalent (optimize the common case) */ + if (priv->allLinksEqual) { + const int fraction = m->m_pkthdr.len / priv->numActiveLinks; + int i, remain; + + for (i = 0; i < priv->numActiveLinks; i++) + distrib[priv->lastLink++ % priv->numActiveLinks] + = fraction; + remain = m->m_pkthdr.len - (fraction * priv->numActiveLinks); + while (remain > 0) { + distrib[priv->lastLink++ % priv->numActiveLinks]++; + remain--; + } + goto deliver; + } + + /* Strategy when all links are not equivalent */ + ng_ppp_mp_strategy(node, m->m_pkthdr.len, distrib); + +deliver: + /* Update stats */ + priv->bundleStats.xmitFrames++; + priv->bundleStats.xmitOctets += m->m_pkthdr.len; + + /* Send alloted portions of frame out on the link(s) */ + for (firstFragment = 1, activeLinkNum = priv->numActiveLinks - 1; + activeLinkNum >= 0; activeLinkNum--) { + const int linkNum = priv->activeLinks[activeLinkNum]; + + /* Deliver fragment(s) out the next link */ + for ( ; distrib[activeLinkNum] > 0; firstFragment = 0) { + int len, lastFragment, error; + struct mbuf *m2; + meta_p meta2; + + /* Calculate fragment length; don't exceed link MTU */ + len = distrib[activeLinkNum]; + if (len > priv->conf.links[linkNum].mru) + len = priv->conf.links[linkNum].mru; + distrib[activeLinkNum] -= len; + lastFragment = (len == m->m_pkthdr.len); + + /* Split off next fragment as "m2" */ + m2 = m; + if (!lastFragment) { + struct mbuf *n = m_split(m, len, M_NOWAIT); + + if (n == NULL) { + NG_FREE_DATA(m, meta); + return (ENOMEM); + } + m = n; + } + + /* Prepend MP header */ + if (priv->conf.xmitShortSeq) { + u_int16_t shdr; + + M_PREPEND(m2, 2, M_NOWAIT); + if (m2 == NULL + || (m2->m_len < 2 + && (m2 = m_pullup(m2, 2)) == NULL)) { + if (!lastFragment) + m_freem(m); + NG_FREE_META(meta); + return (ENOBUFS); + } + shdr = priv->mpSeqOut; + priv->mpSeqOut = + (priv->mpSeqOut + 1) % MP_SHORT_SEQ_MASK; + if (firstFragment) + shdr |= MP_SHORT_FIRST_FLAG; + if (lastFragment) + shdr |= MP_SHORT_LAST_FLAG; + *mtod(m2, u_int16_t *) = htons(shdr); + } else { + u_int32_t lhdr; + + M_PREPEND(m2, 4, M_NOWAIT); + if (m2 == NULL + || (m2->m_len < 4 + && (m2 = m_pullup(m2, 4)) == NULL)) { + if (!lastFragment) + m_freem(m); + NG_FREE_META(meta); + return (ENOBUFS); + } + lhdr = priv->mpSeqOut; + priv->mpSeqOut = + (priv->mpSeqOut + 1) % MP_LONG_SEQ_MASK; + if (firstFragment) + lhdr |= MP_LONG_FIRST_FLAG; + if (lastFragment) + lhdr |= MP_LONG_LAST_FLAG; + *mtod(m2, u_int32_t *) = htonl(lhdr); + } + + /* Add MP protocol number */ + m2 = ng_ppp_addproto(m, PROT_MP, + priv->conf.links[linkNum].enableProtoComp); + if (m2 == NULL) { + if (!lastFragment) + m_freem(m); + NG_FREE_META(meta); + return (ENOBUFS); + } + + /* Copy the meta information, if any */ + if (meta != NULL && !lastFragment) { + MALLOC(meta2, meta_p, + meta->used_len, M_NETGRAPH, M_NOWAIT); + if (meta2 == NULL) { + m_freem(m2); + NG_FREE_DATA(m, meta); + return (ENOMEM); + } + meta2->allocated_len = meta->used_len; + bcopy(meta, meta2, meta->used_len); + } else + meta2 = meta; + + /* Send fragment */ + error = ng_ppp_output(node, linkNum, m2, meta2); + + /* Abort for error */ + if (error != 0) { + if (!lastFragment) + NG_FREE_DATA(m, meta); + return (error); + } + } + } + + /* Done */ + return (0); +} + +/* + * Computing the optimal fragmentation + * ----------------------------------- + * + * This routine tries to compute the optimal fragmentation pattern based + * on each link's latency, bandwidth, and calculated additional latency. + * The latter quantity is the additional latency caused by previously + * written data that has not been transmitted yet. + * + * This algorithm is only useful when not all of the links have the + * same latency and bandwidth values. + * + * The essential idea is to make the last bit of each fragment of the + * frame arrive at the opposite end at the exact same time. This greedy + * algorithm is optimal, in that no other scheduling could result in any + * packet arriving any sooner unless packets are delivered out of order. + * + * Suppose link i has bandwidth b_i (in tens of bytes per milisecond) and + * latency l_i (in miliseconds). Consider the function function f_i(t) + * which is equal to the number of bytes that will have arrived at + * the peer after t miliseconds if we start writing continuously at + * time t = 0. Then f_i(t) = b_i * (t - l_i) = ((b_i * t) - (l_i * b_i). + * That is, f_i(t) is a line with slope b_i and y-intersect -(l_i * b_i). + * Note that the y-intersect is always <= zero because latency can't be + * negative. Note also that really the function is f_i(t) except when + * f_i(t) is negative, in which case the function is zero. To take + * care of this, let Q_i(t) = { if (f_i(t) > 0) return 1; else return 0; }. + * So the actual number of bytes that will have arrived at the peer after + * t miliseconds is f_i(t) * Q_i(t). + * + * At any given time, each link has some additional latency a_i >= 0 + * due to previously written fragment(s) which are still in the queue. + * This value is easily computed from the time since last transmission, + * the previous latency value, the number of bytes written, and the + * link's bandwidth. + * + * Assume that l_i includes any a_i already, and that the links are + * sorted by latency, so that l_i <= l_{i+1}. + * + * Let N be the total number of bytes in the current frame we are sending. + * + * Suppose we were to start writing bytes at time t = 0 on all links + * simultaneously, which is the most we can possibly do. Then let + * F(t) be equal to the total number of bytes received by the peer + * after t miliseconds. Then F(t) = Sum_i (f_i(t) * Q_i(t)). + * + * Our goal is simply this: fragment the frame across the links such + * that the peer is able to reconstruct the completed frame as soon as + * possible, i.e., at the least possible value of t. Call this value t_0. + * + * Then it follows that F(t_0) = N. Our strategy is first to find the value + * of t_0, and then deduce how many bytes to write to each link. + * + * Rewriting F(t_0): + * + * t_0 = ( N + Sum_i ( l_i * b_i * Q_i(t_0) ) ) / Sum_i ( b_i * Q_i(t_0) ) + * + * Now, we note that Q_i(t) is constant for l_i <= t <= l_{i+1}. t_0 will + * lie in one of these ranges. To find it, we just need to find the i such + * that F(l_i) <= N <= F(l_{i+1}). Then we compute all the constant values + * for Q_i() in this range, plug in the remaining values, solving for t_0. + * + * Once t_0 is known, then the number of bytes to send on link i is + * just f_i(t_0) * Q_i(t_0). + * + * In other words, we start allocating bytes to the links one at a time. + * We keep adding links until the frame is completely sent. Some links + * may not get any bytes because their latency is too high. + * + * Is all this work really worth the trouble? Depends on the situation. + * The bigger the ratio of computer speed to link speed, and the more + * important total bundle latency is (e.g., for interactive response time), + * the more it's worth it. There is however the cost of calling this + * function for every frame. The running time is O(n^2) where n is the + * number of links that receive a non-zero number of bytes. + * + * Since latency is measured in miliseconds, the "resolution" of this + * algorithm is one milisecond. + * + * To avoid this algorithm altogether, configure all links to have the + * same latency and bandwidth. + */ +static void +ng_ppp_mp_strategy(node_p node, int len, int *distrib) +{ + const priv_p priv = node->private; + int latency[NG_PPP_MAX_LINKS]; + int sortByLatency[NG_PPP_MAX_LINKS]; + int activeLinkNum, linkNum; + int t0, total, topSum, botSum; + struct timeval now; + int i, numFragments; + + /* If only one link, this gets real easy */ + if (priv->numActiveLinks == 1) { + distrib[0] = len; + return; + } + + /* Get current time */ + getmicrotime(&now); + + /* Compute latencies for each link at this point in time */ + for (activeLinkNum = 0; + activeLinkNum < priv->numActiveLinks; activeLinkNum++) { + struct timeval diff; + int xmitBytes; + + /* Start with base latency value */ + linkNum = priv->activeLinks[activeLinkNum]; + latency[activeLinkNum] = priv->conf.links[linkNum].latency; + sortByLatency[activeLinkNum] = activeLinkNum; /* see below */ + + /* Any additional latency? */ + if (priv->qstat[activeLinkNum].bytesInQueue == 0) + continue; + + /* Compute time delta since last write */ + diff = now; + timevalsub(&diff, &priv->qstat[activeLinkNum].lastWrite); + if (now.tv_sec < 0 || diff.tv_sec >= 10) { /* sanity */ + priv->qstat[activeLinkNum].bytesInQueue = 0; + continue; + } + + /* How many bytes could have transmitted since last write? */ + xmitBytes = priv->conf.links[linkNum].bandwidth * diff.tv_sec + + (priv->conf.links[linkNum].bandwidth + * (diff.tv_usec / 1000)) / 100; + priv->qstat[activeLinkNum].bytesInQueue -= xmitBytes; + if (priv->qstat[activeLinkNum].bytesInQueue < 0) + priv->qstat[activeLinkNum].bytesInQueue = 0; + else + latency[activeLinkNum] += + (100 * priv->qstat[activeLinkNum].bytesInQueue) + / priv->conf.links[linkNum].bandwidth; + } + + /* Sort links by latency */ + compareLatencies = latency; + qsort(sortByLatency, + priv->numActiveLinks, sizeof(*sortByLatency), ng_ppp_intcmp); + compareLatencies = NULL; + + /* Find the interval we need (add links in sortByLatency[] order) */ + for (numFragments = 1; + numFragments < priv->numActiveLinks; numFragments++) { + for (total = i = 0; i < numFragments; i++) { + int flowTime; + + flowTime = latency[sortByLatency[numFragments]] + - latency[sortByLatency[i]]; + total += ((flowTime * priv->conf.links[ + priv->activeLinks[sortByLatency[i]]].bandwidth) + + 99) / 100; + } + if (total >= len) + break; + } + + /* Solve for t_0 in that interval */ + for (topSum = botSum = i = 0; i < numFragments; i++) { + int bw = priv->conf.links[ + priv->activeLinks[sortByLatency[i]]].bandwidth; + + topSum += latency[sortByLatency[i]] * bw; /* / 100 */ + botSum += bw; /* / 100 */ + } + t0 = ((len * 100) + topSum + botSum / 2) / botSum; + + /* Compute f_i(t_0) all i */ + bzero(distrib, priv->numActiveLinks * sizeof(*distrib)); + for (total = i = 0; i < numFragments; i++) { + int bw = priv->conf.links[ + priv->activeLinks[sortByLatency[i]]].bandwidth; + + distrib[sortByLatency[i]] = + (bw * (t0 - latency[sortByLatency[i]]) + 50) / 100; + total += distrib[sortByLatency[i]]; + } + + /* Deal with any rounding error by adjusting fastest link */ + if (total != len) { + int fast = 0; + + for (i = 1; i < numFragments; i++) { + if (priv->conf.links[ + priv->activeLinks[sortByLatency[i]]].bandwidth > + priv->conf.links[ + priv->activeLinks[sortByLatency[fast]]].bandwidth) + fast = i; + } + distrib[sortByLatency[fast]] += len - total; + } + + /* Update bytes in queue counters */ + for (i = 0; i < priv->numActiveLinks; i++) { + priv->qstat[i].bytesInQueue += distrib[i]; + priv->qstat[i].lastWrite = now; + } +} + +/* + * Compare two integers + */ +static int +ng_ppp_intcmp(const void *v1, const void *v2) +{ + const int index1 = *((const int *) v1); + const int index2 = *((const int *) v2); + + return compareLatencies[index1] - compareLatencies[index2]; +} + +/* + * Prepend a possibly compressed PPP protocol number in front of a frame + */ +static struct mbuf * +ng_ppp_addproto(struct mbuf *m, int proto, int compOK) +{ + int psize = (PROT_COMPRESSIBLE(proto) && compOK) ? 1 : 2; + + /* Add protocol number */ + M_PREPEND(m, psize, M_NOWAIT); + if (m == NULL || (m->m_len < psize && (m = m_pullup(m, psize)) == NULL)) + return (NULL); + if (psize == 1) + *mtod(m, u_char *) = (u_char)proto; + else + *mtod(m, u_int16_t *) = htons((u_int16_t)proto); + return (m); +} + +/* + * Update private information that is derived from other private information + */ +static void +ng_ppp_update(node_p node, int newConf) +{ + const priv_p priv = node->private; + int i; + + /* Update active status for VJ Compression */ + priv->vjCompHooked = priv->hooks[HOOK_INDEX_VJC_IP] != NULL + && priv->hooks[HOOK_INDEX_VJC_COMP] != NULL + && priv->hooks[HOOK_INDEX_VJC_UNCOMP] != NULL + && priv->hooks[HOOK_INDEX_VJC_VJIP] != NULL; + + /* Increase latency for each link an amount equal to one MP header */ + if (newConf) { + for (i = 0; i < NG_PPP_MAX_LINKS; i++) { + int hdrBytes; + + hdrBytes = (priv->conf.links[i].enableProtoComp ? 1 : 2) + + (priv->conf.xmitShortSeq ? 2 : 4); + priv->conf.links[i].latency += + ((hdrBytes * priv->conf.links[i].bandwidth) + 50) + / 100; + } + } + + /* Update list of active links */ + bzero(&priv->activeLinks, sizeof(priv->activeLinks)); + priv->numActiveLinks = 0; + priv->allLinksEqual = 1; + for (i = 0; i < NG_PPP_MAX_LINKS; i++) { + if (priv->conf.links[i].enableLink && priv->links[i] != NULL) { + priv->activeLinks[priv->numActiveLinks++] = i; + if (priv->conf.links[i].latency + != priv->conf.links[0].latency + || priv->conf.links[i].bandwidth + != priv->conf.links[0].bandwidth) + priv->allLinksEqual = 0; + } + } + + /* Reset MP state if no longer active */ + if (!priv->conf.enableMultilink || priv->numActiveLinks == 0) { + ng_ppp_free_frags(node); + priv->mpSeqOut = MP_INITIAL_SEQ; + bzero(&priv->qstat, sizeof(priv->qstat)); + } +} + +/* + * Determine if a new configuration would represent a valid change + * from the current configuration and link activity status. + */ +static int +ng_ppp_config_valid(node_p node, const struct ng_ppp_node_config *newConf) +{ + const priv_p priv = node->private; + int i, newNumLinksActive; + + /* Check per-link config and count how many links would be active */ + for (newNumLinksActive = i = 0; i < NG_PPP_MAX_LINKS; i++) { + if (newConf->links[i].mru < MP_MIN_LINK_MRU) + return (0); + if (newConf->links[i].bandwidth == 0) + return (0); + if (newConf->links[i].bandwidth > NG_PPP_MAX_BANDWIDTH) + return (0); + if (newConf->links[i].latency > NG_PPP_MAX_LATENCY) + return (0); + if (newConf->links[i].enableLink && priv->links[i] != NULL) + newNumLinksActive++; + } + + /* Check bundle parameters */ + if (priv->conf.enableMultilink && newConf->mrru < MP_MIN_MRRU) + return (0); + + /* At most one link can be active unless multi-link is enabled */ + if (!newConf->enableMultilink && newNumLinksActive > 1) + return (0); + + /* Disallow changes to multi-link configuration while MP is active */ + if (priv->numActiveLinks > 0 && newNumLinksActive > 0) { + if (!priv->conf.enableMultilink != !newConf->enableMultilink + || !priv->conf.xmitShortSeq != !newConf->xmitShortSeq + || !priv->conf.recvShortSeq != !newConf->recvShortSeq) + return (0); + } + + /* Configuration change is valid */ + return (1); +} + +/* + * Free all entries in the fragment queue + */ +static void +ng_ppp_free_frags(node_p node) +{ + const priv_p priv = node->private; + struct ng_ppp_frag *qent, *next; - LIST_FOREACH(hook, &node->hooks, hooks) { - if (HOOK_PROTO(hook) == proto) - return (hook); + for (qent = CIRCLEQ_FIRST(&priv->frags); + qent != (void *) &priv->frags; qent = next) { + next = CIRCLEQ_NEXT(qent, f_qent); + NG_FREE_DATA(qent->data, qent->meta); + FREE(qent, M_NETGRAPH); } - return (NULL); + CIRCLEQ_INIT(&priv->frags); } diff --git a/sys/netgraph/ng_ppp.h b/sys/netgraph/ng_ppp.h index 06a6ddd..c858a65 100644 --- a/sys/netgraph/ng_ppp.h +++ b/sys/netgraph/ng_ppp.h @@ -45,47 +45,79 @@ /* Node type name and magic cookie */ #define NG_PPP_NODE_TYPE "ppp" -#define NGM_PPP_COOKIE 860635544 +#define NGM_PPP_COOKIE 940897792 + +/* Maximum number of supported links */ +#define NG_PPP_MAX_LINKS 16 + +/* Pseudo-link number representing the multi-link bundle */ +#define NG_PPP_BUNDLE_LINKNUM 0xffff + +/* Max allowable link latency (miliseconds) and bandwidth (bytes/second/10) */ +#define NG_PPP_MAX_LATENCY 1000 /* 1 second */ +#define NG_PPP_MAX_BANDWIDTH 125000 /* 10 Mbits / second */ /* Hook names */ -#define NG_PPP_HOOK_DOWNLINK "downlink" /* downstream hook */ -#define NG_PPP_HOOK_BYPASS "bypass" /* any unhooked protocol */ +#define NG_PPP_HOOK_BYPASS "bypass" /* unknown protocols */ +#define NG_PPP_HOOK_COMPRESS "compress" /* outgoing compression */ +#define NG_PPP_HOOK_DECOMPRESS "decompress" /* incoming decompression */ +#define NG_PPP_HOOK_ENCRYPT "encrypt" /* outgoing encryption */ +#define NG_PPP_HOOK_DECRYPT "decrypt" /* incoming decryption */ +#define NG_PPP_HOOK_VJC_IP "vjc_ip" /* VJC raw IP */ +#define NG_PPP_HOOK_VJC_COMP "vjc_vjcomp" /* VJC compressed TCP */ +#define NG_PPP_HOOK_VJC_UNCOMP "vjc_vjuncomp" /* VJC uncompressed TCP */ +#define NG_PPP_HOOK_VJC_VJIP "vjc_vjip" /* VJC uncompressed IP */ +#define NG_PPP_HOOK_INET "inet" /* IP packet data */ +#define NG_PPP_HOOK_ATALK "atalk" /* AppleTalk packet data */ +#define NG_PPP_HOOK_IPX "ipx" /* IPX packet data */ + +#define NG_PPP_HOOK_LINK_PREFIX "link" /* append decimal link number */ /* Netgraph commands */ enum { - NGM_PPP_SET_PROTOCOMP = 1, /* takes an integer 0 or 1 */ - NGM_PPP_GET_STATS, /* returns struct ng_ppp_stat */ - NGM_PPP_CLR_STATS, /* clear stats */ + NGM_PPP_SET_CONFIG = 1, /* takes struct ng_ppp_bundle_config */ + NGM_PPP_GET_CONFIG, /* returns ng_ppp_bundle_config */ + NGM_PPP_GET_LINK_STATS, /* takes link #, returns stats struct */ + NGM_PPP_CLR_LINK_STATS, /* takes link #, clears link stats */ }; -/* Statistics struct */ -struct ng_ppp_stat { - u_int32_t xmitFrames; /* xmit frames on "downstream" */ - u_int32_t xmitOctets; /* xmit octets on "downstream" */ - u_int32_t recvFrames; /* recv frames on "downstream" */ - u_int32_t recvOctets; /* recv octets on "downstream" */ - u_int32_t badProto; /* frames with invalid protocol */ - u_int32_t unknownProto; /* frames sent to "unhooked" */ +/* Per-link config structure */ +struct ng_ppp_link_config { + u_char enableLink; /* enable this link */ + u_char enableProtoComp;/* enable protocol field compression */ + u_int16_t mru; /* peer MRU */ + u_int32_t latency; /* link latency (in milliseconds) */ + u_int32_t bandwidth; /* link bandwidth (in bytes/second) */ }; -/* - * We recognize these hook names for some various PPP protocols. But we - * always recognize the hook name "0xNNNN" for any protocol, including these. - * So these are really just alias hook names. - */ -#define NG_PPP_HOOK_LCP "lcp" /* 0xc021 */ -#define NG_PPP_HOOK_IPCP "ipcp" /* 0x8021 */ -#define NG_PPP_HOOK_ATCP "atcp" /* 0x8029 */ -#define NG_PPP_HOOK_CCP "ccp" /* 0x80fd */ -#define NG_PPP_HOOK_ECP "ecp" /* 0x8053 */ -#define NG_PPP_HOOK_IP "ip" /* 0x0021 */ -#define NG_PPP_HOOK_VJCOMP "vjcomp" /* 0x002d */ -#define NG_PPP_HOOK_VJUNCOMP "vjuncomp" /* 0x002f */ -#define NG_PPP_HOOK_MP "mp" /* 0x003d */ -#define NG_PPP_HOOK_COMPD "compd" /* 0x00fd */ -#define NG_PPP_HOOK_CRYPTD "cryptd" /* 0x0053 */ -#define NG_PPP_HOOK_PAP "pap" /* 0xc023 */ -#define NG_PPP_HOOK_CHAP "chap" /* 0xc223 */ -#define NG_PPP_HOOK_LQR "lqr" /* 0xc025 */ +/* Node config structure */ +struct ng_ppp_node_config { + u_int16_t mrru; /* multilink peer MRRU */ + u_char enableMultilink; /* enable multilink */ + u_char recvShortSeq; /* recv multilink short seq # */ + u_char xmitShortSeq; /* xmit multilink short seq # */ + u_char enableRoundRobin; /* xmit whole packets */ + u_char enableIP; /* enable IP data flow */ + u_char enableAtalk; /* enable AppleTalk data flow */ + u_char enableIPX; /* enable IPX data flow */ + u_char enableCompression; /* enable PPP compression */ + u_char enableDecompression; /* enable PPP decompression */ + u_char enableEncryption; /* enable PPP encryption */ + u_char enableDecryption; /* enable PPP decryption */ + u_char enableVJCompression; /* enable VJ compression */ + u_char enableVJDecompression; /* enable VJ decompression */ + struct ng_ppp_link_config /* per link config params */ + links[NG_PPP_MAX_LINKS]; +}; + +/* Statistics struct for a link (or the bundle if NG_PPP_BUNDLE_LINKNUM) */ +struct ng_ppp_link_stat { + u_int32_t xmitFrames; /* xmit frames on link */ + u_int32_t xmitOctets; /* xmit octets on link */ + u_int32_t recvFrames; /* recv frames on link */ + u_int32_t recvOctets; /* recv octets on link */ + u_int32_t badProtos; /* frames rec'd with bogus protocol */ + u_int32_t dupFragments; /* MP frames with duplicate seq # */ +}; #endif /* _NETGRAPH_PPP_H_ */ |