diff options
author | julian <julian@FreeBSD.org> | 1999-10-21 09:06:11 +0000 |
---|---|---|
committer | julian <julian@FreeBSD.org> | 1999-10-21 09:06:11 +0000 |
commit | c5c63975d538cf48ceb99ba48c341293676d15c0 (patch) | |
tree | 722c03ee4d750dd89ed43b028c35302fbfd03bfd /sys/netgraph/ng_pppoe.c | |
parent | 028ec91c46f181b4be2318c3bba8d194b5583f87 (diff) | |
download | FreeBSD-src-c5c63975d538cf48ceb99ba48c341293676d15c0.zip FreeBSD-src-c5c63975d538cf48ceb99ba48c341293676d15c0.tar.gz |
Whistle's Netgraph link-layer (sometimes more) networking infrastructure.
Been in production for 3 years now. Gives Instant Frame relay to if_sr
and if_ar drivers, and PPPOE support soon. See:
ftp://ftp.whistle.com/pub/archie/netgraph/index.html
for on-line manual pages.
Reviewed by: Doug Rabson (dfr@freebsd.org)
Obtained from: Whistle CVS tree
Diffstat (limited to 'sys/netgraph/ng_pppoe.c')
-rw-r--r-- | sys/netgraph/ng_pppoe.c | 1343 |
1 files changed, 1343 insertions, 0 deletions
diff --git a/sys/netgraph/ng_pppoe.c b/sys/netgraph/ng_pppoe.c new file mode 100644 index 0000000..b59dbb3 --- /dev/null +++ b/sys/netgraph/ng_pppoe.c @@ -0,0 +1,1343 @@ + +/* + * ng_pppoe.c + * + * Copyright (c) 1996-1999 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: Julian Elischer <julian@whistle.com> + * + * $FreeBSD$ + * $Whistle: ng_pppoe.c,v 1.7 1999/10/16 10:16:43 julian Exp $ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/errno.h> +#include <sys/syslog.h> +#include <net/ethernet.h> + +#include <netgraph/ng_message.h> +#include <netgraph/netgraph.h> +#include <netgraph/ng_pppoe.h> + +/* + * This section contains the netgraph method declarations for the + * sample node. These methods define the netgraph 'type'. + */ + +static int ng_PPPoE_constructor(node_p *node); +static int ng_PPPoE_rcvmsg(node_p node, struct ng_mesg *msg, + const char *retaddr, struct ng_mesg **resp); +static int ng_PPPoE_rmnode(node_p node); +static int ng_PPPoE_newhook(node_p node, hook_p hook, const char *name); +static int ng_PPPoE_connect(hook_p hook); +static int ng_PPPoE_rcvdata(hook_p hook, struct mbuf *m, meta_p meta); +static int ng_PPPoE_disconnect(hook_p hook); + +/* Netgraph node type descriptor */ +static struct ng_type typestruct = { + NG_VERSION, + NG_PPPOE_NODE_TYPE, + NULL, + ng_PPPoE_constructor, + ng_PPPoE_rcvmsg, + ng_PPPoE_rmnode, + ng_PPPoE_newhook, + NULL, + ng_PPPoE_connect, + ng_PPPoE_rcvdata, + ng_PPPoE_rcvdata, + ng_PPPoE_disconnect +}; +NETGRAPH_INIT(PPPoE, &typestruct); + +/* + * States for the session state machine. + * These have no meaning if there is no hook attached yet. + */ +enum state { + PPPOE_SNONE=0, /* [both] Initial state */ + PPPOE_SINIT, /* [Client] Sent discovery initiation */ + PPPOE_PRIMED, /* [Server] Sent offer message */ + PPPOE_SOFFER, /* [Server] Sent offer message */ + PPPOE_SREQ, /* [Client] Sent a Request */ + PPPOE_LISTENING, /* [Server] Listening for discover initiation msg */ + PPPOE_NEWCONNECTED, /* [Both] Connection established, No data received */ + PPPOE_CONNECTED, /* [Both] Connection established, Data received */ + PPPOE_DEAD /* [Both] */ +}; +/* + * Events for the state machine + */ +enum event { + PPPOE_TIMEOUT, /* It's time to do something */ + PPPOE_PACKET, /* a packet has been received. */ + PPPOE_CLOSE /* start shutdown processing */ +}; + +#define NUMTAGS 20 /* number of tags we are set up to work with */ + +/* + * Information we store for each hook on each node for negotiating the + * session. The mbuf and cluster are freed once negotiation has completed. + * The whole negotiation block is then discarded. + */ + +struct sess_neg { + struct mbuf *m; /* holds cluster with last sent packet */ + union packet *pkt; /* points within the above cluster */ + struct callout_handle timeout_handle; /* see timeout(9) */ + u_int timeout; /* 0,1,2,4,8,16 etc. seconds */ + u_int numtags; + struct pppoe_tag *tags[NUMTAGS]; + u_int service_len; + u_int ac_name_len; + + struct datatag service; + struct datatag ac_name; +}; +typedef struct sess_neg *negp; + +/* + * Session information that is needed after connection. + */ +struct session { + hook_p hook; + u_int16_t Session_ID; + struct session *hash_next; /* not yet uesed */ + enum state state; + char creator[NG_NODELEN + 1]; /* who to notify */ + struct pppoe_full_hdr pkt_hdr; /* used when connected */ + negp neg; /* used when negotiating */ +}; +typedef struct session *sessp; + +/* + * Information we store for each node + */ +struct PPPOE { + node_p node; /* back pointer to node */ + hook_p ethernet_hook; + hook_p debug_hook; + u_int packets_in; /* packets in from ethernet */ + u_int packets_out; /* packets out towards ethernet */ + u_int32_t flags; + /*struct session *buckets[HASH_SIZE];*/ /* not yet used */ +}; +typedef struct PPPOE *priv_p; + +const struct ether_header eh_prototype = + {{0xff,0xff,0xff,0xff,0xff,0xff}, + {0x00,0x00,0x00,0x00,0x00,0x00}, + ETHERTYPE_PPPOE_DISC}; + +union uniq { + char bytes[sizeof(void *)]; + void * pointer; + }; + +#define LEAVE(x) do { error = x; goto quit; } while(0) +static void pppoe_start(sessp sp); +static void sendpacket(sessp sp); +static void pppoe_ticker(void *arg); +static struct pppoe_tag* scan_tags(sessp sp, struct pppoe_hdr* ph); + +/************************************************************************* + * Some basic utilities from the Linux version with author's permission.* + * Author: Michal Ostrowski <mostrows@styx.uwaterloo.ca> * + ************************************************************************/ + +/* + * Generate a new session id + * XXX find out the freeBSD locking scheme. + */ +static u_int16_t +get_new_sid(node_p node) +{ + static int pppoe_sid = 10; + sessp sp; + hook_p hook; + u_int16_t val; + priv_p privp = node->private; + +restart: + val = pppoe_sid++; + /* + * Spec says 0xFFFF is reserved. + * Also don't use 0x0000 + */ + if (val == 0xffff) { + pppoe_sid = 20; + goto restart; + } + + /* Check it isn't already in use */ + LIST_FOREACH(hook, &node->hooks, hooks) { + /* don't check special hooks */ + if ((hook->private == &privp->debug_hook) + || (hook->private == &privp->ethernet_hook)) + continue; + sp = hook->private; + if (sp->Session_ID == val) + goto restart; + } + + return val; +} + + +/* + * Return the location where the next tag can be put + */ +static __inline struct pppoe_tag* +next_tag(struct pppoe_hdr* ph) +{ + return (struct pppoe_tag*)(((char*)&ph->tag[0]) + ntohs(ph->length)); +} + +/* + * Look for a tag of a specific type + * Don't trust any length the other end says. + * but assume we already sanity checked ph->length. + */ +static struct pppoe_tag* +get_tag(struct pppoe_hdr* ph, u_int16_t idx) +{ + char *end = (char *)next_tag(ph); + char *ptn; + struct pppoe_tag *pt = &ph->tag[0]; + /* + * Keep processing tags while a tag header will still fit. + */ + while((char*)(pt + 1) <= end) { + /* + * If the tag data would go past the end of the packet, abort. + */ + ptn = (((char *)(pt + 1)) + ntohs(pt->tag_len)); + if(ptn > end) + return NULL; + + if(pt->tag_type == idx) + return pt; + + pt = (struct pppoe_tag*)ptn; + } + return NULL; +} + +/************************************************************************** + * inlines to initialise or add tags to a session's tag list, + **************************************************************************/ +/* + * Initialise the session's tag list + */ +static void +init_tags(sessp sp) +{ + if(sp->neg == NULL) { + printf("pppoe: asked to init NULL neg pointer\n"); + return; + } + sp->neg->numtags = 0; +} + +static void +insert_tag(sessp sp, struct pppoe_tag *tp) +{ + int i; + negp neg; + + if((neg = sp->neg) == NULL) { + printf("pppoe: asked to use NULL neg pointer\n"); + return; + } + if ((i = neg->numtags++) < NUMTAGS) { + neg->tags[i] = tp; + } else { + printf("pppoe: asked to add too many tags to packet\n"); + } +} + +/* + * Make up a packet, using the tags filled out for the session. + * + * Assume that the actual pppoe header and ethernet header + * are filled out externally to this routine. + * Also assume that neg->wh points to the correct + * location at the front of the buffer space. + */ +static void +make_packet(sessp sp) { + struct pppoe_full_hdr *wh = &sp->neg->pkt->pkt_header; + struct pppoe_tag **tag; + char *dp; + int count; + int tlen; + u_int16_t length = 0; + + if ((sp->neg == NULL) || (sp->neg->m = NULL)) { + printf("pppoe: make_packet called from wrong state\n"); + } + dp = (char *)wh->ph.tag; + for (count = 0, tag = sp->neg->tags; + ((count < sp->neg->numtags) && (count < NUMTAGS)); + tag++, count++) { + tlen = ntohs((*tag)->tag_len) + sizeof(**tag); + if ((length + tlen) > (ETHER_MAX_LEN - 4 - sizeof(*wh))) { + printf("pppoe: tags too long\n"); + sp->neg->numtags = count; + break; /* XXX chop off what's too long */ + } + bcopy((char *)*tag, (char *)dp, tlen); + length += tlen; + dp += tlen; + } + wh->ph.length = htons(length); + sp->neg->m->m_len = length + sizeof(*wh); + sp->neg->m->m_pkthdr.len = length + sizeof(*wh); +} + +/************************************************************************** + * Routine to match a service offered * + **************************************************************************/ +/* + * Find a hook that has a service string that matches that + * we are seeking. for now use a simple string. + * In the future we may need something like regexp(). + * for testing allow a null string to match 1st found and a null service + * to match all requests. Also make '*' do the same. + */ +static hook_p +pppoe_match_svc(node_p node, char *svc_name, int svc_len) +{ + sessp sp = NULL; + negp neg = NULL; + priv_p privp = node->private; + hook_p hook; + + LIST_FOREACH(hook, &node->hooks, hooks) { + + /* skip any hook that is debug or ethernet */ + if ((hook->private == &privp->debug_hook) + || (hook->private == &privp->ethernet_hook)) + continue; + sp = hook->private; + + /* Skip any sessions which are not in LISTEN mode. */ + if ( sp->state != PPPOE_LISTENING) + continue; + + neg = sp->neg; + /* XXX check validity of this */ + /* special case, NULL request. match 1st found. */ + if (svc_len == 0) + break; + + /* XXX check validity of this */ + /* Special case for a blank or "*" service name (wildcard) */ + if ((neg->service_len == 0) + || ((neg->service_len == 1) + && (neg->service.data[0] == '*'))) { + break; + } + + /* If the lengths don't match, that aint it. */ + if (neg->service_len != svc_len) + continue; + + /* An exact match? */ + if (strncmp(svc_name, neg->service.data, svc_len) == 0) + break; + } + return (hook); +} +/************************************************************************** + * Routine to find a particular session that matches an incoming packet * + **************************************************************************/ +static hook_p +pppoe_findsession(node_p node, struct pppoe_full_hdr *wh) +{ + sessp sp = NULL; + hook_p hook = NULL; + priv_p privp = node->private; + u_int16_t session = wh->ph.sid; + + /* + * find matching peer/session combination. + */ + LIST_FOREACH(hook, &node->hooks, hooks) { + /* don't check special hooks */ + if ((hook->private == &privp->debug_hook) + || (hook->private == &privp->ethernet_hook)) { + continue; + } + sp = hook->private; + if ( ( (sp->state == PPPOE_CONNECTED) + || (sp->state == PPPOE_NEWCONNECTED) ) + && (sp->Session_ID == session) + && (bcmp(sp->pkt_hdr.eh.ether_dhost, + wh->eh.ether_shost, + ETHER_ADDR_LEN)) == 0) { + break; + } + } + return (hook); +} + +static hook_p +pppoe_finduniq(node_p node, struct pppoe_tag *tag) +{ + hook_p hook = NULL; + priv_p privp = node->private; + union uniq uniq; + + bcopy(tag->tag_data, uniq.bytes, sizeof(void *)); + /* cycle through all known hooks */ + LIST_FOREACH(hook, &node->hooks, hooks) { + /* don't check special hooks */ + if ((hook->private == &privp->debug_hook) + || (hook->private == &privp->ethernet_hook)) + continue; + if (uniq.pointer == hook->private) + break; + } + return (hook); +} + +/************************************************************************** + * start of Netgraph entrypoints * + **************************************************************************/ + +/* + * Allocate the private data structure and the generic node + * and link them together. + * + * ng_make_node_common() returns with a generic node struct + * with a single reference for us.. we transfer it to the + * private structure.. when we free the private struct we must + * unref the node so it gets freed too. + * + * If this were a device node than this work would be done in the attach() + * routine and the constructor would return EINVAL as you should not be able + * to creatednodes that depend on hardware (unless you can add the hardware :) + */ +static int +ng_PPPoE_constructor(node_p *nodep) +{ + priv_p privdata; + int error; + + /* Initialize private descriptor */ + MALLOC(privdata, priv_p, sizeof(*privdata), M_NETGRAPH, M_WAITOK); + if (privdata == NULL) + return (ENOMEM); + bzero(privdata, sizeof(*privdata)); + + /* Call the 'generic' (ie, superclass) node constructor */ + if ((error = ng_make_node_common(&typestruct, nodep))) { + FREE(privdata, M_NETGRAPH); + return (error); + } + + /* Link structs together; this counts as our one reference to *nodep */ + (*nodep)->private = privdata; + privdata->node = *nodep; + return (0); +} + +/* + * Give our ok for a hook to be added... + * point the hook's private info to the hook structure. + * + * The following hook names are special: + * Ethernet: the hook that should be connected to a NIC. + * debug: copies of data sent out here (when I write the code). + */ +static int +ng_PPPoE_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p privp = node->private; + sessp sp; + + if (strcmp(name, NG_PPPOE_HOOK_ETHERNET) == 0) { + privp->ethernet_hook = hook; + hook->private = &privp->ethernet_hook; + } else if (strcmp(name, NG_PPPOE_HOOK_DEBUG) == 0) { + privp->debug_hook = hook; + hook->private = &privp->debug_hook; + } else { + /* + * Any other unique name is OK. + * The infrastructure has already checked that it's unique, + * so just allocate it and hook it in. + */ + MALLOC(sp, sessp, sizeof(*sp), M_NETGRAPH, M_WAITOK); + if (sp == NULL) { + return (ENOMEM); + } + bzero(sp, sizeof(*sp)); + + hook->private = sp; + sp->hook = hook; + callout_handle_init( &sp->neg->timeout_handle); + } + return(0); +} + +/* + * Get a netgraph control message. + * Check it is one we understand. If needed, send a response. + * We sometimes save the address for an async action later. + * Always free the message. + */ +static int +ng_PPPoE_rcvmsg(node_p node, + struct ng_mesg *msg, const char *retaddr, struct ng_mesg **rptr) +{ + priv_p privp = node->private; + struct ngPPPoE_init_data *ourmsg = NULL; + struct ng_mesg *resp = NULL; + int error = 0; + hook_p hook = NULL; + sessp sp = NULL; + negp neg = NULL; + + /* Deal with message according to cookie and command */ + switch (msg->header.typecookie) { + case NGM_PPPOE_COOKIE: + switch (msg->header.cmd) { + case NGM_PPPOE_CONNECT: + case NGM_PPPOE_LISTEN: + case NGM_PPPOE_OFFER: + ourmsg = (struct ngPPPoE_init_data *)msg->data; + if (( sizeof(*ourmsg) > msg->header.arglen) + || ((sizeof(*ourmsg) + ourmsg->data_len) + > msg->header.arglen)) { + printf("PPPoE_rcvmsg: bad arg size"); + LEAVE(EMSGSIZE); + } + if (ourmsg->data_len > PPPOE_SERVICE_NAME_SIZE) { + printf("pppoe: init data too long (%d)\n", + ourmsg->data_len); + LEAVE(EMSGSIZE); + } + /* make sure strcmp will terminate safely */ + ourmsg->hook[sizeof(ourmsg->hook) - 1] = '\0'; + + /* cycle through all known hooks */ + LIST_FOREACH(hook, &node->hooks, hooks) { + if (hook->name + && strcmp(hook->name, ourmsg->hook) == 0) + break; + } + if (hook == NULL) { + LEAVE(ENOENT); + } + if ((hook->private == &privp->debug_hook) + || (hook->private == &privp->ethernet_hook)) { + LEAVE(EINVAL); + } + sp = hook->private; + if (sp->state |= PPPOE_SNONE) { + printf("pppoe: Session already active\n"); + LEAVE(EISCONN); + } + /* + * set up prototype header + */ + + MALLOC(neg, negp, sizeof(*neg), M_NETGRAPH, M_WAITOK); + + if (neg == NULL) { + printf("pppoe: Session out of memory\n"); + LEAVE(ENOMEM); + } + bzero(neg, sizeof(*neg)); + MGETHDR(neg->m, M_DONTWAIT, MT_DATA); + if(neg->m == NULL) { + FREE(neg, M_NETGRAPH); + LEAVE(ENOBUFS); + } + neg->m->m_pkthdr.rcvif = NULL; + MCLGET(neg->m, M_DONTWAIT); + if ((neg->m->m_flags & M_EXT) == 0) { + m_freem(neg->m); + FREE(neg, M_NETGRAPH); + LEAVE(ENOBUFS); + } + sp->neg = neg; + neg->m->m_len = sizeof(struct pppoe_full_hdr); + neg->pkt = mtod(neg->m, union packet*); + neg->pkt->pkt_header.eh = eh_prototype; + neg->pkt->pkt_header.ph.ver = 0x1; + neg->pkt->pkt_header.ph.type = 0x1; + neg->pkt->pkt_header.ph.sid = 0x0000; + neg->timeout = 0; + + strncpy(sp->creator, retaddr, NG_NODELEN); + sp->creator[NG_NODELEN] = '\0'; + } + switch (msg->header.cmd) { + case NGM_PPPOE_GET_STATUS: + { + struct ngPPPoEstat *stats; + + NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); + if (!resp) { + LEAVE(ENOMEM); + } + stats = (struct ngPPPoEstat *) resp->data; + stats->packets_in = privp->packets_in; + stats->packets_out = privp->packets_out; + break; + } + case NGM_PPPOE_CONNECT: + /* + * Check the hook exists and is Uninitialised. + * Send a PADI request, and start the timeout logic. + * Store the originator of this message so we can send + * a success of fail message to them later. + * Move the session to SINIT + * Set up the session to the correct state and + * start it. + */ + neg->service.hdr.tag_type = PTT_SRV_NAME; + neg->service.hdr.tag_len = + htons((u_int16_t)ourmsg->data_len); + bcopy(ourmsg->data, + neg->service.data, ourmsg->data_len); + neg->service_len = ourmsg->data_len; + neg->pkt->pkt_header.ph.code = PADI_CODE; + pppoe_start(sp); + break; + case NGM_PPPOE_LISTEN: + /* + * Check the hook exists and is Uninitialised. + * Install the service matching string. + * Store the originator of this message so we can send + * a success of fail message to them later. + * Move the hook to 'LISTENING' + */ + neg->service.hdr.tag_type = PTT_SRV_NAME; + neg->service.hdr.tag_len = + htons((u_int16_t)ourmsg->data_len); + bcopy(ourmsg->data, + neg->service.data, ourmsg->data_len); + neg->service_len = ourmsg->data_len; + neg->pkt->pkt_header.ph.code = PADT_CODE; + /* + * wait for PADI packet coming from ethernet + */ + sp->state = PPPOE_LISTENING; + break; + case NGM_PPPOE_OFFER: + /* + * Check the hook exists and is Uninitialised. + * Store the originator of this message so we can send + * a success of fail message to them later. + * Store the AC-Name given and go to PRIMED. + */ + neg->ac_name.hdr.tag_type = PTT_AC_NAME; + neg->ac_name.hdr.tag_len = + htons((u_int16_t)ourmsg->data_len); + bcopy(ourmsg->data, + neg->ac_name.data, ourmsg->data_len); + neg->ac_name_len = ourmsg->data_len; + neg->pkt->pkt_header.ph.code = PADO_CODE; + /* + * Wait for PADI packet coming from hook + */ + sp->state = PPPOE_PRIMED; + break; + default: + LEAVE(EINVAL); + } + break; + default: + LEAVE(EINVAL); + } + + /* Take care of synchronous response, if any */ + if (rptr) + *rptr = resp; + else if (resp) + FREE(resp, M_NETGRAPH); + + /* Free the message and return */ +quit: + FREE(msg, M_NETGRAPH); + return(error); +} + +static void +pppoe_start(sessp sp) +{ + struct { + struct pppoe_tag hdr; + union uniq data; + } uniqtag; + + /* + * kick the state machine into starting up + */ + sp->state = PPPOE_SINIT; + uniqtag.hdr.tag_type = PTT_HOST_UNIQ; + uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(uniqtag.data)); + uniqtag.data.pointer = sp; + init_tags(sp); + insert_tag(sp, &uniqtag.hdr); + insert_tag(sp, &sp->neg->service.hdr); + make_packet(sp); + sendpacket(sp); +} + +/* + * Receive data, and do something with it. + * The caller will never free m or meta, so + * if we use up this data or abort we must free BOTH of these. + */ +static int +ng_PPPoE_rcvdata(hook_p hook, struct mbuf *m, meta_p meta) +{ + node_p node = hook->node; + const priv_p privp = node->private; + sessp sp = hook->private; + struct pppoe_full_hdr *wh; + struct pppoe_hdr *ph; + int error = 0; + u_int16_t session; + u_int16_t length; + u_int8_t code; + struct pppoe_tag *tag = NULL; + hook_p sendhook; + struct { + struct pppoe_tag hdr; + union uniq data; + } uniqtag; + negp neg = NULL; + + if (hook->private == &privp->debug_hook) { + /* + * Data from the debug hook gets sent without modification + * straight to the ethernet. + */ + NG_SEND_DATA( error, privp->ethernet_hook, m, meta); + privp->packets_out++; + } else if (hook->private == &privp->ethernet_hook) { + /* + * Incoming data. + * Dig out various fields from the packet. + * use them to decide where to send it. + */ + + privp->packets_in++; + m_pullup(m, sizeof(*wh)); /* Checks length */ + if (m == NULL) { + LEAVE(ENOBUFS); + } + wh = mtod(m, struct pppoe_full_hdr *); + ph = &wh->ph; + session = ntohs(wh->ph.sid); + length = ntohs(wh->ph.length); + code = wh->ph.code; + switch(ntohs(wh->eh.ether_type)) { + case ETHERTYPE_PPPOE_DISC: + /* + * We need to try make sure that the tag area + * is contiguous, or we could wander of the end + * of a buffer and make a mess. + * (Linux wouldn't have this problem). + */ + if (m->m_len != m->m_pkthdr.len) { + /* + * It's not all in one piece. + * We need to do extra work. + */ + printf("packet fragmented\n"); + } + + switch(code) { + case PADI_CODE: + /* + * We are a server: + * Look for a hook with the required service + * and send the ENTIRE packet up there. + * It should come back to a new hook in + * PRIMED state. Look there for further + * processing. + */ + tag = get_tag(ph, PTT_SRV_NAME); + if (tag == NULL) { + LEAVE(ENETUNREACH); + } + sendhook = pppoe_match_svc(hook->node, + tag->tag_data, ntohs(tag->tag_len)); + if (sendhook) { + NG_SEND_DATA(error, sendhook, m, meta); + } else { + LEAVE(ENETUNREACH); + } + break; + case PADO_CODE: + /* + * We are a client: + * Use the host_uniq tag to find the + * hook this is in response to. + * + * For now simply accept the first we receive. + */ + tag = get_tag(ph, PTT_HOST_UNIQ); + if ((tag == NULL) + || (ntohs(tag->tag_len) != sizeof(sp))) { + LEAVE(ENETUNREACH); + } + + sendhook = pppoe_finduniq(node, tag); + if (sendhook == NULL) { + LEAVE(ENETUNREACH); + } + + /* + * Check the session is in the right state. + * It needs to be in PPPOE_SINIT. + */ + sp = sendhook->private; + if (sp->state != PPPOE_SINIT) { + LEAVE(ENETUNREACH); + } + neg = sp->neg; + untimeout(pppoe_ticker, sendhook, + neg->timeout_handle); + + /* + * This is the first time we hear + * from the server, so note it's + * unicast address, replacing the + * broadcast address . + */ + bcopy(wh->eh.ether_shost, + neg->pkt->pkt_header.eh.ether_dhost, + ETHER_ADDR_LEN); + neg->timeout = 0; + neg->pkt->pkt_header.ph.code = PADR_CODE; + init_tags(sp); + insert_tag(sp,&neg->service.hdr); /* Service */ + insert_tag(sp, tag); /* Host Unique */ + tag = get_tag(ph, PTT_AC_COOKIE); + insert_tag(sp, tag); /* returned cookie */ + scan_tags(sp, ph); + make_packet(sp); + sp->state = PPPOE_SREQ; + sendpacket(sp); + break; + case PADR_CODE: + + /* + * We are a server: + * Use the ac_cookie tag to find the + * hook this is in response to. + */ + tag = get_tag(ph, PTT_AC_COOKIE); + if ((tag == NULL) + || (ntohs(tag->tag_len) != sizeof(sp))) { + LEAVE(ENETUNREACH); + } + + sendhook = pppoe_finduniq(node, tag); + if (sendhook == NULL) { + LEAVE(ENETUNREACH); + } + + /* + * Check the session is in the right state. + * It needs to be in PPPOE_SOFFER + * or PPPOE_NEWCONNECTED. If the latter, + * then this is a retry by the client. + * so be nice, and resend. + */ + sp = sendhook->private; + if (sp->state == PPPOE_NEWCONNECTED) { + /* + * Whoa! drop back to resend that + * PADS packet. + * We should still have a copy of it. + */ + sp->state = PPPOE_SOFFER; + } + if (sp->state != PPPOE_SOFFER) { + LEAVE (ENETUNREACH); + break; + } + neg = sp->neg; + untimeout(pppoe_ticker, sendhook, + neg->timeout_handle); + neg->pkt->pkt_header.ph.code = PADS_CODE; + if (sp->Session_ID == 0) + neg->pkt->pkt_header.ph.sid = + sp->Session_ID = get_new_sid(node); + neg->timeout = 0; + /* + * start working out the tags to respond with. + */ + init_tags(sp); + insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */ + insert_tag(sp, tag); /* ac_cookie */ + tag = get_tag(ph, PTT_SRV_NAME); + insert_tag(sp, tag); /* returned service */ + tag = get_tag(ph, PTT_HOST_UNIQ); + insert_tag(sp, tag); /* returned hostuniq */ + scan_tags(sp, ph); + make_packet(sp); + sp->state = PPPOE_NEWCONNECTED; + sendpacket(sp); + /* + * Having sent the last Negotiation header, + * Set up the stored packet header to + * be correct for the actual session. + * But keep the negotialtion stuff + * around in case we need to resend this last + * packet. We'll discard it when we move + * from NEWCONNECTED to CONNECTED + */ + sp->pkt_hdr = neg->pkt->pkt_header; + sp->pkt_hdr.eh.ether_type + = ETHERTYPE_PPPOE_SESS; + sp->pkt_hdr.ph.code = 0; + break; + case PADS_CODE: + /* + * We are a client: + * Use the host_uniq tag to find the + * hook this is in response to. + * take the session ID and store it away. + * Also make sure the pre-made header is + * correct and set us into Session mode. + */ + tag = get_tag(ph, PTT_HOST_UNIQ); + if ((tag == NULL) + || (ntohs(tag->tag_len) != sizeof(sp))) { + LEAVE (ENETUNREACH); + break; + } + + sendhook = pppoe_finduniq(node, tag); + if (sendhook == NULL) { + LEAVE(ENETUNREACH); + } + + /* + * Check the session is in the right state. + * It needs to be in PPPOE_SREQ. + */ + sp = sendhook->private; + if (sp->state != PPPOE_SREQ) { + LEAVE(ENETUNREACH); + } + neg = sp->neg; + untimeout(pppoe_ticker, sendhook, + neg->timeout_handle); + sp->Session_ID = wh->ph.sid; + neg->timeout = 0; + sp->state = PPPOE_CONNECTED; + sendpacket(sp); + /* + * Now we have gone to Connected mode, + * Free all resources needed for + * negotiation. + * Keep a copy of the header we will be using. + */ + sp->pkt_hdr = neg->pkt->pkt_header; + sp->pkt_hdr.eh.ether_type + = ETHERTYPE_PPPOE_SESS; + sp->pkt_hdr.ph.code = 0; + m_freem(neg->m); + FREE(sp->neg, M_NETGRAPH); + sp->neg = NULL; + break; + case PADT_CODE: + /* + * Send a 'close' message to the controlling + * process (the one that set us up); + * And then tear everything down. + * + * Find matching peer/session combination. + */ + sendhook = pppoe_findsession(node, wh); + NG_FREE_DATA(m, meta); /* no longer needed */ + if (sendhook == NULL) { + LEAVE(ENETUNREACH); + } + /* send message to creator */ + /* close hook */ + ng_destroy_hook(sendhook); + break; + default: + LEAVE(EPFNOSUPPORT); + } + break; + case ETHERTYPE_PPPOE_SESS: + /* + * find matching peer/session combination. + */ + sendhook = pppoe_findsession(node, wh); + if (sendhook == NULL) { + LEAVE (ENETUNREACH); + break; + } + m_adj(m, sizeof(*wh)); + if (m->m_pkthdr.len < length) { + /* Packet too short, dump it */ + LEAVE(EMSGSIZE); + } + /* XXX also need to trim excess at end I should think */ + if ( sp->state != PPPOE_CONNECTED) { + if (sp->state == PPPOE_NEWCONNECTED) { + sp->state = PPPOE_CONNECTED; + /* + * Now we have gone to Connected mode, + * Free all resources needed for + * negotiation. + */ + m_freem(sp->neg->m); + FREE(sp->neg, M_NETGRAPH); + sp->neg = NULL; + } else { + LEAVE (ENETUNREACH); + break; + } + } + NG_SEND_DATA( error, sendhook, m, meta); + break; + default: + LEAVE(EPFNOSUPPORT); + } + } else { + /* + * Not ethernet or debug hook.. + * + * The packet has come in on a normal hook. + * We need to find out what kind of hook, + * So we can decide how to handle it. + * Check the hook's state. + */ + sp = hook->private; + switch (sp->state) { + case PPPOE_NEWCONNECTED: + case PPPOE_CONNECTED: { + struct pppoe_full_hdr *wh; + /* + * Bang in a pre-made header, and set the length up + * to be correct. Then send it to the ethernet driver. + */ + M_PREPEND(m, sizeof(*wh), M_DONTWAIT); + if (m == NULL) { + LEAVE(ENOBUFS); + } + wh = mtod(m, struct pppoe_full_hdr *); + bcopy(&sp->pkt_hdr, wh, sizeof(*wh)); + wh->ph.length = htons((short)(m->m_pkthdr.len)); + NG_SEND_DATA( error, privp->ethernet_hook, m, meta); + privp->packets_out++; + break; + } + case PPPOE_PRIMED: + /* + * A PADI packet is being returned by the application + * that has set up this hook. This indicates that it + * wants us to offer service. + */ + neg = sp->neg; + m_pullup(m, sizeof(*wh)); /* Checks length */ + if (m == NULL) { + LEAVE(ENOBUFS); + } + wh = mtod(m, struct pppoe_full_hdr *); + ph = &wh->ph; + session = ntohs(wh->ph.sid); + length = ntohs(wh->ph.length); + code = wh->ph.code; + + /* + * This is the first time we hear + * from the client, so note it's + * unicast address, replacing the + * broadcast address . + */ + bcopy(wh->eh.ether_shost, + neg->pkt->pkt_header.eh.ether_dhost, + ETHER_ADDR_LEN); + sp->state = PPPOE_SOFFER; + neg->timeout = 0; + neg->pkt->pkt_header.ph.code = PADO_CODE; + + /* + * start working out the tags to respond with. + */ + uniqtag.hdr.tag_type = PTT_AC_COOKIE; + uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(sp)); + uniqtag.data.pointer = sp; + init_tags(sp); + insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */ + insert_tag(sp, tag); /* returned hostunique */ + insert_tag(sp, &uniqtag.hdr); /* AC cookie */ + tag = get_tag(ph, PTT_SRV_NAME); + insert_tag(sp, tag); /* returned service */ + /* XXX maybe put the tag in the session store */ + scan_tags(sp, ph); + make_packet(sp); + sendpacket(sp); + break; + + /* + * Packets coming from the hook make no sense + * to sessions in these states. Throw them away. + */ + case PPPOE_SINIT: + case PPPOE_SREQ: + case PPPOE_SOFFER: + case PPPOE_SNONE: + case PPPOE_LISTENING: + case PPPOE_DEAD: + default: + LEAVE(ENETUNREACH); + } + } +quit: + NG_FREE_DATA(m, meta); + return error; +} + +/* + * Do local shutdown processing.. + * If we are a persistant device, we might refuse to go away, and + * we'd only remove our links and reset ourself. + */ +static int +ng_PPPoE_rmnode(node_p node) +{ + const priv_p privdata = node->private; + + node->flags |= NG_INVALID; + ng_cutlinks(node); + ng_unname(node); + node->private = NULL; + ng_unref(privdata->node); + FREE(privdata, M_NETGRAPH); + return (0); +} + +/* + * This is called once we've already connected a new hook to the other node. + * It gives us a chance to balk at the last minute. + */ +static int +ng_PPPoE_connect(hook_p hook) +{ + /* be really amiable and just say "YUP that's OK by me! " */ + return (0); +} + +/* + * Hook disconnection + * + * Clean up all dangling links and infirmation about the session/hook. + * For this type, removal of the last link destroys the node + */ +static int +ng_PPPoE_disconnect(hook_p hook) +{ + node_p node = hook->node; + priv_p privp = node->private; + sessp sp; + + if (hook->private == &privp->debug_hook) { + privp->debug_hook = NULL; + } else if (hook->private == &privp->ethernet_hook) { + privp->ethernet_hook = NULL; + } else { + sp = hook->private; + untimeout(pppoe_ticker, hook, sp->neg->timeout_handle); + FREE(sp, M_NETGRAPH); + } + if (node->numhooks == 0) + ng_rmnode(node); + return (0); +} + +/* + * timeouts come here. + */ +static void +pppoe_ticker(void *arg) +{ + int s = splnet(); + hook_p hook = arg; + sessp sp = hook->private; + negp neg = sp->neg; + int error = 0; + struct mbuf *m0 = NULL; + priv_p privp = hook->node->private; + meta_p dummy = NULL; + + switch(sp->state) { + /* + * resend the last packet, using an exponential backoff. + * After a period of time, stop growing the backoff, + * and either leave it, or reverst to the start. + */ + case PPPOE_SINIT: + case PPPOE_SREQ: + /* timeouts on these produce resends */ + m0 = m_copypacket(sp->neg->m, M_DONTWAIT); + NG_SEND_DATA( error, privp->ethernet_hook, m0, dummy); + neg->timeout_handle = timeout(pppoe_ticker, + hook, neg->timeout * hz); + if ((neg->timeout <<= 1) > PPPOE_TIMEOUT_LIMIT) { + if (sp->state == PPPOE_SREQ) { + /* revert to SINIT mode */ + } else { + neg->timeout = PPPOE_TIMEOUT_LIMIT; + } + } + break; + case PPPOE_PRIMED: + case PPPOE_SOFFER: + /* a timeout on these says "give up" */ + /* XXX should notify creator */ + ng_destroy_hook(hook); + break; + default: + /* timeouts have no meaning in other states */ + printf("pppoe: unexpected timeout\n"); + } + splx(s); +} + + +static void +sendpacket(sessp sp) +{ + int error = 0; + struct mbuf *m0 = NULL; + hook_p hook = sp->hook; + negp neg = sp->neg; + priv_p privp = hook->node->private; + meta_p dummy = NULL; + + switch(sp->state) { + case PPPOE_LISTENING: + case PPPOE_DEAD: + case PPPOE_SNONE: + case PPPOE_NEWCONNECTED: + case PPPOE_CONNECTED: + printf("pppoe: timeout: unexpected state\n"); + break; + + case PPPOE_PRIMED: + /* No packet to send, but set up the timeout */ + neg->timeout_handle = timeout(pppoe_ticker, + hook, PPPOE_OFFER_TIMEOUT * hz); + break; + + case PPPOE_SOFFER: + /* + * send the offer but if they don't respond + * in PPPOE_OFFER_TIMEOUT seconds, forget about it. + */ + m0 = m_copypacket(sp->neg->m, M_DONTWAIT); + NG_SEND_DATA( error, privp->ethernet_hook, m0, dummy); + neg->timeout_handle = timeout(pppoe_ticker, + hook, PPPOE_OFFER_TIMEOUT * hz); + break; + + case PPPOE_SINIT: + case PPPOE_SREQ: + m0 = m_copypacket(sp->neg->m, M_DONTWAIT); + NG_SEND_DATA( error, sp->hook, m0, dummy); + neg->timeout_handle = timeout(pppoe_ticker, hook, hz); + neg->timeout = 2; + break; + + default: + error = EINVAL; + printf("pppoe: timeout: bad state\n"); + } + /* return (error); */ +} + +/* + * Parse an incoming packet to see if any tags should be copied to the + * output packet. DOon't do any tags that are likely to have been + * handles a the main state machine. + */ +static struct pppoe_tag* +scan_tags(sessp sp, struct pppoe_hdr* ph) +{ + char *end = (char *)next_tag(ph); + char *ptn; + struct pppoe_tag *pt = &ph->tag[0]; + /* + * Keep processing tags while a tag header will still fit. + */ + while((char*)(pt + 1) <= end) { + /* + * If the tag data would go past the end of the packet, abort. + */ + ptn = (((char *)(pt + 1)) + ntohs(pt->tag_len)); + if(ptn > end) + return NULL; + + switch (pt->tag_type) { + case PTT_RELAY_SID: + insert_tag(sp, pt); + break; + case PTT_EOL: + return NULL; + case PTT_SRV_NAME: + case PTT_AC_NAME: + case PTT_HOST_UNIQ: + case PTT_AC_COOKIE: + case PTT_VENDOR: + case PTT_SRV_ERR: + case PTT_SYS_ERR: + case PTT_GEN_ERR: + break; + } + pt = (struct pppoe_tag*)ptn; + } + return NULL; +} + |