ng_pppoe.c revision 70931
153405Sarchie
252419Sjulian/*
352419Sjulian * ng_pppoe.c
452419Sjulian *
552419Sjulian * Copyright (c) 1996-1999 Whistle Communications, Inc.
652419Sjulian * All rights reserved.
752419Sjulian *
852419Sjulian * Subject to the following obligations and disclaimer of warranty, use and
952419Sjulian * redistribution of this software, in source or object code forms, with or
1052419Sjulian * without modifications are expressly permitted by Whistle Communications;
1152419Sjulian * provided, however, that:
1252419Sjulian * 1. Any and all reproductions of the source or object code must include the
1352419Sjulian *    copyright notice above and the following disclaimer of warranties; and
1452419Sjulian * 2. No rights are granted, in any manner or form, to use Whistle
1552419Sjulian *    Communications, Inc. trademarks, including the mark "WHISTLE
1652419Sjulian *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
1752419Sjulian *    such appears in the above copyright notice or in the software.
1852419Sjulian *
1952419Sjulian * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
2052419Sjulian * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
2152419Sjulian * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
2252419Sjulian * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
2352419Sjulian * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
2452419Sjulian * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
2552419Sjulian * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
2652419Sjulian * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
2752419Sjulian * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
2852419Sjulian * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
2952419Sjulian * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
3052419Sjulian * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
3152419Sjulian * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
3252419Sjulian * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3352419Sjulian * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3452419Sjulian * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
3552419Sjulian * OF SUCH DAMAGE.
3652419Sjulian *
3767506Sjulian * Author: Julian Elischer <julian@freebsd.org>
3852419Sjulian *
3952419Sjulian * $FreeBSD: head/sys/netgraph/ng_pppoe.c 70931 2001-01-11 15:42:22Z julian $
4052752Sjulian * $Whistle: ng_pppoe.c,v 1.10 1999/11/01 09:24:52 julian Exp $
4152419Sjulian */
4252443Sjulian#if 0
4352443Sjulian#define AAA printf("pppoe: %s\n", __FUNCTION__ );
4452510Sjulian#define BBB printf("-%d-", __LINE__ );
4552443Sjulian#else
4652443Sjulian#define AAA
4752510Sjulian#define BBB
4852443Sjulian#endif
4952419Sjulian
5052419Sjulian#include <sys/param.h>
5152419Sjulian#include <sys/systm.h>
5252419Sjulian#include <sys/kernel.h>
5352419Sjulian#include <sys/mbuf.h>
5452419Sjulian#include <sys/malloc.h>
5552419Sjulian#include <sys/errno.h>
5652419Sjulian#include <net/ethernet.h>
5752419Sjulian
5852419Sjulian#include <netgraph/ng_message.h>
5952419Sjulian#include <netgraph/netgraph.h>
6068031Sbrian#include <netgraph/ng_parse.h>
6152419Sjulian#include <netgraph/ng_pppoe.h>
6252419Sjulian
6370870Sjulian#ifdef NG_SEPARATE_MALLOC
6470870SjulianMALLOC_DEFINE(M_NETGRAPH_PPPOE, "netgraph_pppoe", "netgraph pppoe node");
6570870Sjulian#else
6670870Sjulian#define M_NETGRAPH_PPPOE M_NETGRAPH
6770870Sjulian#endif
6870870Sjulian
6953405Sarchie#define SIGNOFF "session closed"
7068031Sbrian#define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0))
7153405Sarchie
7252419Sjulian/*
7352419Sjulian * This section contains the netgraph method declarations for the
7452419Sjulian * sample node. These methods define the netgraph 'type'.
7552419Sjulian */
7652419Sjulian
7752752Sjulianstatic ng_constructor_t	ng_pppoe_constructor;
7852752Sjulianstatic ng_rcvmsg_t	ng_pppoe_rcvmsg;
7970700Sjulianstatic ng_shutdown_t	ng_pppoe_shutdown;
8052752Sjulianstatic ng_newhook_t	ng_pppoe_newhook;
8152752Sjulianstatic ng_connect_t	ng_pppoe_connect;
8252752Sjulianstatic ng_rcvdata_t	ng_pppoe_rcvdata;
8352752Sjulianstatic ng_disconnect_t	ng_pppoe_disconnect;
8452419Sjulian
8568031Sbrian/* Parse type for struct ngpppoe_init_data */
8668845Sbrianstatic const struct ng_parse_struct_info ngpppoe_init_data_type_info
8768031Sbrian	= NG_PPPOE_INIT_DATA_TYPE_INFO;
8868845Sbrianstatic const struct ng_parse_type ngpppoe_init_data_state_type = {
8968031Sbrian	&ng_parse_struct_type,
9068845Sbrian	&ngpppoe_init_data_type_info
9168031Sbrian};
9268031Sbrian
9368031Sbrian/* Parse type for struct ngpppoe_sts */
9468031Sbrianstatic const struct ng_parse_struct_info ng_pppoe_sts_type_info
9568031Sbrian	= NG_PPPOE_STS_TYPE_INFO;
9668031Sbrianstatic const struct ng_parse_type ng_pppoe_sts_state_type = {
9768031Sbrian	&ng_parse_struct_type,
9868031Sbrian	&ng_pppoe_sts_type_info
9968031Sbrian};
10068031Sbrian
10168031Sbrian/* List of commands and how to convert arguments to/from ASCII */
10268031Sbrianstatic const struct ng_cmdlist ng_pppoe_cmds[] = {
10368031Sbrian	{
10468031Sbrian	  NGM_PPPOE_COOKIE,
10568031Sbrian	  NGM_PPPOE_CONNECT,
10668031Sbrian	  "pppoe_connect",
10768845Sbrian	  &ngpppoe_init_data_state_type,
10868031Sbrian	  NULL
10968031Sbrian	},
11068031Sbrian	{
11168031Sbrian	  NGM_PPPOE_COOKIE,
11268031Sbrian	  NGM_PPPOE_LISTEN,
11368031Sbrian	  "pppoe_listen",
11468845Sbrian	  &ngpppoe_init_data_state_type,
11568031Sbrian	  NULL
11668031Sbrian	},
11768031Sbrian	{
11868031Sbrian	  NGM_PPPOE_COOKIE,
11968031Sbrian	  NGM_PPPOE_OFFER,
12068031Sbrian	  "pppoe_offer",
12168845Sbrian	  &ngpppoe_init_data_state_type,
12268031Sbrian	  NULL
12368031Sbrian	},
12468031Sbrian	{
12568031Sbrian	  NGM_PPPOE_COOKIE,
12669922Sjulian	  NGM_PPPOE_SERVICE,
12769922Sjulian	  "pppoe_service",
12869922Sjulian	  &ngpppoe_init_data_state_type,
12969922Sjulian	  NULL
13069922Sjulian	},
13169922Sjulian	{
13269922Sjulian	  NGM_PPPOE_COOKIE,
13368031Sbrian	  NGM_PPPOE_SUCCESS,
13468031Sbrian	  "pppoe_success",
13568031Sbrian	  &ng_pppoe_sts_state_type,
13668031Sbrian	  NULL
13768031Sbrian	},
13868031Sbrian	{
13968031Sbrian	  NGM_PPPOE_COOKIE,
14068031Sbrian	  NGM_PPPOE_FAIL,
14168031Sbrian	  "pppoe_fail",
14268031Sbrian	  &ng_pppoe_sts_state_type,
14368031Sbrian	  NULL
14468031Sbrian	},
14568031Sbrian	{
14668031Sbrian	  NGM_PPPOE_COOKIE,
14768031Sbrian	  NGM_PPPOE_CLOSE,
14868031Sbrian	  "pppoe_close",
14968031Sbrian	  &ng_pppoe_sts_state_type,
15068031Sbrian	  NULL
15168031Sbrian	},
15268031Sbrian	{ 0 }
15368031Sbrian};
15468031Sbrian
15552419Sjulian/* Netgraph node type descriptor */
15652419Sjulianstatic struct ng_type typestruct = {
15770159Sjulian	NG_ABI_VERSION,
15852419Sjulian	NG_PPPOE_NODE_TYPE,
15952419Sjulian	NULL,
16052562Sjulian	ng_pppoe_constructor,
16152562Sjulian	ng_pppoe_rcvmsg,
16270700Sjulian	ng_pppoe_shutdown,
16352562Sjulian	ng_pppoe_newhook,
16452419Sjulian	NULL,
16552562Sjulian	ng_pppoe_connect,
16652562Sjulian	ng_pppoe_rcvdata,
16753913Sarchie	ng_pppoe_disconnect,
16868031Sbrian	ng_pppoe_cmds
16952419Sjulian};
17052562SjulianNETGRAPH_INIT(pppoe, &typestruct);
17152419Sjulian
17252419Sjulian/*
17352419Sjulian * States for the session state machine.
17452419Sjulian * These have no meaning if there is no hook attached yet.
17552419Sjulian */
17652419Sjulianenum state {
17752419Sjulian    PPPOE_SNONE=0,	/* [both] Initial state */
17853498Sjulian    PPPOE_LISTENING,	/* [Daemon] Listening for discover initiation pkt */
17952419Sjulian    PPPOE_SINIT,	/* [Client] Sent discovery initiation */
18053498Sjulian    PPPOE_PRIMED,	/* [Server] Awaiting PADI from daemon */
18153498Sjulian    PPPOE_SOFFER,	/* [Server] Sent offer message  (got PADI)*/
18252419Sjulian    PPPOE_SREQ,		/* [Client] Sent a Request */
18353498Sjulian    PPPOE_NEWCONNECTED,	/* [Server] Connection established, No data received */
18452419Sjulian    PPPOE_CONNECTED,	/* [Both] Connection established, Data received */
18552419Sjulian    PPPOE_DEAD		/* [Both] */
18652419Sjulian};
18752419Sjulian
18852419Sjulian#define NUMTAGS 20 /* number of tags we are set up to work with */
18952419Sjulian
19052419Sjulian/*
19152419Sjulian * Information we store for each hook on each node for negotiating the
19252419Sjulian * session. The mbuf and cluster are freed once negotiation has completed.
19352419Sjulian * The whole negotiation block is then discarded.
19452419Sjulian */
19552419Sjulian
19652419Sjulianstruct sess_neg {
19752419Sjulian	struct mbuf 		*m; /* holds cluster with last sent packet */
19852419Sjulian	union	packet		*pkt; /* points within the above cluster */
19952419Sjulian	struct callout_handle	timeout_handle;   /* see timeout(9) */
20052419Sjulian	u_int			timeout; /* 0,1,2,4,8,16 etc. seconds */
20152419Sjulian	u_int			numtags;
20252419Sjulian	struct pppoe_tag	*tags[NUMTAGS];
20352419Sjulian	u_int			service_len;
20452419Sjulian	u_int			ac_name_len;
20552419Sjulian
20652419Sjulian	struct datatag		service;
20752419Sjulian	struct datatag		ac_name;
20852419Sjulian};
20952419Sjuliantypedef struct sess_neg *negp;
21052419Sjulian
21152419Sjulian/*
21252419Sjulian * Session information that is needed after connection.
21352419Sjulian */
21466052Sarchiestruct sess_con {
21552419Sjulian	hook_p  		hook;
21652419Sjulian	u_int16_t		Session_ID;
21752419Sjulian	enum state		state;
21870700Sjulian	ng_ID_t			creator;		/* who to notify */
21952419Sjulian	struct pppoe_full_hdr	pkt_hdr;	/* used when connected */
22052419Sjulian	negp			neg;		/* used when negotiating */
22166052Sarchie	/*struct sess_con	*hash_next;*/	/* not yet used */
22252419Sjulian};
22366052Sarchietypedef struct sess_con *sessp;
22452419Sjulian
22552419Sjulian/*
22652419Sjulian * Information we store for each node
22752419Sjulian */
22852419Sjulianstruct PPPOE {
22952419Sjulian	node_p		node;		/* back pointer to node */
23052419Sjulian	hook_p  	ethernet_hook;
23152419Sjulian	hook_p  	debug_hook;
23252419Sjulian	u_int   	packets_in;	/* packets in from ethernet */
23352419Sjulian	u_int   	packets_out;	/* packets out towards ethernet */
23452419Sjulian	u_int32_t	flags;
23566052Sarchie	/*struct sess_con *buckets[HASH_SIZE];*/	/* not yet used */
23652419Sjulian};
23752419Sjuliantypedef struct PPPOE *priv_p;
23852419Sjulian
23952419Sjulianconst struct ether_header eh_prototype =
24052419Sjulian	{{0xff,0xff,0xff,0xff,0xff,0xff},
24152419Sjulian	 {0x00,0x00,0x00,0x00,0x00,0x00},
24252419Sjulian	 ETHERTYPE_PPPOE_DISC};
24352419Sjulian
24452419Sjulianunion uniq {
24552419Sjulian	char bytes[sizeof(void *)];
24652419Sjulian	void * pointer;
24752419Sjulian	};
24852419Sjulian
24952419Sjulian#define	LEAVE(x) do { error = x; goto quit; } while(0)
25052419Sjulianstatic void	pppoe_start(sessp sp);
25152419Sjulianstatic void	sendpacket(sessp sp);
25252419Sjulianstatic void	pppoe_ticker(void *arg);
25352419Sjulianstatic struct pppoe_tag* scan_tags(sessp	sp, struct pppoe_hdr* ph);
25452441Sjulianstatic	int	pppoe_send_event(sessp sp, enum cmd cmdid);
25552419Sjulian
25652419Sjulian/*************************************************************************
25752419Sjulian * Some basic utilities  from the Linux version with author's permission.*
25852419Sjulian * Author:	Michal Ostrowski <mostrows@styx.uwaterloo.ca>		 *
25952419Sjulian ************************************************************************/
26052419Sjulian
26152419Sjulian/*
26252419Sjulian * Generate a new session id
26353145Sjulian * XXX find out the FreeBSD locking scheme.
26452419Sjulian */
26552419Sjulianstatic u_int16_t
26652419Sjulianget_new_sid(node_p node)
26752419Sjulian{
26852419Sjulian	static int pppoe_sid = 10;
26952419Sjulian	sessp sp;
27052419Sjulian	hook_p	hook;
27152419Sjulian	u_int16_t val;
27270784Sjulian	priv_p privp = NG_NODE_PRIVATE(node);
27352419Sjulian
27452443SjulianAAA
27552419Sjulianrestart:
27652419Sjulian	val = pppoe_sid++;
27752419Sjulian	/*
27852419Sjulian	 * Spec says 0xFFFF is reserved.
27952419Sjulian	 * Also don't use 0x0000
28052419Sjulian	 */
28152419Sjulian	if (val == 0xffff) {
28252419Sjulian		pppoe_sid = 20;
28352419Sjulian		goto restart;
28452419Sjulian	}
28552419Sjulian
28652419Sjulian	/* Check it isn't already in use */
28770784Sjulian	LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
28852419Sjulian		/* don't check special hooks */
28970784Sjulian		if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook)
29070784Sjulian		||  (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook))
29152419Sjulian			continue;
29270784Sjulian		sp = NG_HOOK_PRIVATE(hook);
29352419Sjulian		if (sp->Session_ID == val)
29452419Sjulian			goto restart;
29552419Sjulian	}
29652419Sjulian
29752419Sjulian	return val;
29852419Sjulian}
29952419Sjulian
30052419Sjulian
30152419Sjulian/*
30252419Sjulian * Return the location where the next tag can be put
30352419Sjulian */
30452419Sjulianstatic __inline struct pppoe_tag*
30552419Sjuliannext_tag(struct pppoe_hdr* ph)
30652419Sjulian{
30752419Sjulian	return (struct pppoe_tag*)(((char*)&ph->tag[0]) + ntohs(ph->length));
30852419Sjulian}
30952419Sjulian
31052419Sjulian/*
31152419Sjulian * Look for a tag of a specific type
31252419Sjulian * Don't trust any length the other end says.
31352419Sjulian * but assume we already sanity checked ph->length.
31452419Sjulian */
31552419Sjulianstatic struct pppoe_tag*
31652419Sjulianget_tag(struct pppoe_hdr* ph, u_int16_t idx)
31752419Sjulian{
31852419Sjulian	char *end = (char *)next_tag(ph);
31952419Sjulian	char *ptn;
32052419Sjulian	struct pppoe_tag *pt = &ph->tag[0];
32152419Sjulian	/*
32252419Sjulian	 * Keep processing tags while a tag header will still fit.
32352419Sjulian	 */
32452443SjulianAAA
32552419Sjulian	while((char*)(pt + 1) <= end) {
32652419Sjulian	    /*
32752419Sjulian	     * If the tag data would go past the end of the packet, abort.
32852419Sjulian	     */
32952419Sjulian	    ptn = (((char *)(pt + 1)) + ntohs(pt->tag_len));
33052419Sjulian	    if(ptn > end)
33152419Sjulian		return NULL;
33252419Sjulian
33352419Sjulian	    if(pt->tag_type == idx)
33452419Sjulian		return pt;
33552419Sjulian
33652419Sjulian	    pt = (struct pppoe_tag*)ptn;
33752419Sjulian	}
33852419Sjulian	return NULL;
33952419Sjulian}
34052419Sjulian
34152419Sjulian/**************************************************************************
34252419Sjulian * inlines to initialise or add tags to a session's tag list,
34352419Sjulian **************************************************************************/
34452419Sjulian/*
34552419Sjulian * Initialise the session's tag list
34652419Sjulian */
34752419Sjulianstatic void
34852419Sjulianinit_tags(sessp sp)
34952419Sjulian{
35052443SjulianAAA
35152419Sjulian	if(sp->neg == NULL) {
35252419Sjulian		printf("pppoe: asked to init NULL neg pointer\n");
35352419Sjulian		return;
35452419Sjulian	}
35552419Sjulian	sp->neg->numtags = 0;
35652419Sjulian}
35752419Sjulian
35852419Sjulianstatic void
35952419Sjulianinsert_tag(sessp sp, struct pppoe_tag *tp)
36052419Sjulian{
36152419Sjulian	int	i;
36252419Sjulian	negp neg;
36352419Sjulian
36452443SjulianAAA
36552419Sjulian	if((neg = sp->neg) == NULL) {
36652419Sjulian		printf("pppoe: asked to use NULL neg pointer\n");
36752419Sjulian		return;
36852419Sjulian	}
36952419Sjulian	if ((i = neg->numtags++) < NUMTAGS) {
37052419Sjulian		neg->tags[i] = tp;
37152419Sjulian	} else {
37252419Sjulian		printf("pppoe: asked to add too many tags to packet\n");
37353042Sjulian		neg->numtags--;
37452419Sjulian	}
37552419Sjulian}
37652419Sjulian
37752419Sjulian/*
37852419Sjulian * Make up a packet, using the tags filled out for the session.
37952419Sjulian *
38052419Sjulian * Assume that the actual pppoe header and ethernet header
38152419Sjulian * are filled out externally to this routine.
38252419Sjulian * Also assume that neg->wh points to the correct
38352419Sjulian * location at the front of the buffer space.
38452419Sjulian */
38552419Sjulianstatic void
38652419Sjulianmake_packet(sessp sp) {
38752419Sjulian	struct pppoe_full_hdr *wh = &sp->neg->pkt->pkt_header;
38852419Sjulian	struct pppoe_tag **tag;
38952419Sjulian	char *dp;
39052419Sjulian	int count;
39152419Sjulian	int tlen;
39252419Sjulian	u_int16_t length = 0;
39352419Sjulian
39452443SjulianAAA
39552443Sjulian	if ((sp->neg == NULL) || (sp->neg->m == NULL)) {
39652419Sjulian		printf("pppoe: make_packet called from wrong state\n");
39752419Sjulian	}
39852419Sjulian	dp = (char *)wh->ph.tag;
39952419Sjulian	for (count = 0, tag = sp->neg->tags;
40052419Sjulian	    ((count < sp->neg->numtags) && (count < NUMTAGS));
40152419Sjulian	    tag++, count++) {
40252419Sjulian		tlen = ntohs((*tag)->tag_len) + sizeof(**tag);
40352419Sjulian		if ((length + tlen) > (ETHER_MAX_LEN - 4 - sizeof(*wh))) {
40452419Sjulian			printf("pppoe: tags too long\n");
40552419Sjulian			sp->neg->numtags = count;
40652419Sjulian			break;	/* XXX chop off what's too long */
40752419Sjulian		}
40852419Sjulian		bcopy((char *)*tag, (char *)dp, tlen);
40952419Sjulian		length += tlen;
41052419Sjulian		dp += tlen;
41152419Sjulian	}
41252419Sjulian 	wh->ph.length = htons(length);
41352419Sjulian	sp->neg->m->m_len = length + sizeof(*wh);
41452419Sjulian	sp->neg->m->m_pkthdr.len = length + sizeof(*wh);
41552419Sjulian}
41652419Sjulian
41752419Sjulian/**************************************************************************
41852419Sjulian * Routine to match a service offered					  *
41952419Sjulian **************************************************************************/
42052419Sjulian/*
42152419Sjulian * Find a hook that has a service string that matches that
42252419Sjulian * we are seeking. for now use a simple string.
42352419Sjulian * In the future we may need something like regexp().
42452419Sjulian * for testing allow a null string to match 1st found and a null service
42552419Sjulian * to match all requests. Also make '*' do the same.
42652419Sjulian */
42752419Sjulianstatic hook_p
42852419Sjulianpppoe_match_svc(node_p node, char *svc_name, int svc_len)
42952419Sjulian{
43052419Sjulian	sessp	sp	= NULL;
43152419Sjulian	negp	neg	= NULL;
43270784Sjulian	priv_p	privp	= NG_NODE_PRIVATE(node);
43352419Sjulian	hook_p hook;
43452419Sjulian
43552443SjulianAAA
43670784Sjulian	LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
43752419Sjulian
43852419Sjulian		/* skip any hook that is debug or ethernet */
43970784Sjulian		if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook)
44070784Sjulian		||  (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook))
44152419Sjulian			continue;
44270784Sjulian		sp = NG_HOOK_PRIVATE(hook);
44352419Sjulian
44452419Sjulian		/* Skip any sessions which are not in LISTEN mode. */
44552419Sjulian		if ( sp->state != PPPOE_LISTENING)
44652419Sjulian			continue;
44752419Sjulian
44852419Sjulian		neg = sp->neg;
44952419Sjulian		/* XXX check validity of this */
45052419Sjulian		/* special case, NULL request. match 1st found. */
45152419Sjulian		if (svc_len == 0)
45252419Sjulian			break;
45352419Sjulian
45452419Sjulian		/* XXX check validity of this */
45552419Sjulian		/* Special case for a blank or "*" service name (wildcard) */
45652419Sjulian		if ((neg->service_len == 0)
45752419Sjulian		||  ((neg->service_len == 1)
45852419Sjulian		  && (neg->service.data[0] == '*'))) {
45952419Sjulian			break;
46052419Sjulian		}
46152419Sjulian
46252419Sjulian		/* If the lengths don't match, that aint it. */
46352419Sjulian		if (neg->service_len != svc_len)
46452419Sjulian			continue;
46552419Sjulian
46652419Sjulian		/* An exact match? */
46752419Sjulian		if (strncmp(svc_name, neg->service.data, svc_len) == 0)
46852419Sjulian			break;
46952419Sjulian	}
47052419Sjulian	return (hook);
47152419Sjulian}
47252419Sjulian/**************************************************************************
47352419Sjulian * Routine to find a particular session that matches an incoming packet	  *
47452419Sjulian **************************************************************************/
47552419Sjulianstatic hook_p
47652419Sjulianpppoe_findsession(node_p node, struct pppoe_full_hdr *wh)
47752419Sjulian{
47852419Sjulian	sessp	sp = NULL;
47952419Sjulian	hook_p hook = NULL;
48070784Sjulian	priv_p	privp = NG_NODE_PRIVATE(node);
48152448Sjulian	u_int16_t	session = ntohs(wh->ph.sid);
48252419Sjulian
48352419Sjulian	/*
48452419Sjulian	 * find matching peer/session combination.
48552419Sjulian	 */
48652443SjulianAAA
48770784Sjulian	LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
48852419Sjulian		/* don't check special hooks */
48970784Sjulian		if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook)
49070784Sjulian		||  (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook)) {
49152419Sjulian			continue;
49252419Sjulian		}
49370784Sjulian		sp = NG_HOOK_PRIVATE(hook);
49452419Sjulian		if ( ( (sp->state == PPPOE_CONNECTED)
49552419Sjulian		    || (sp->state == PPPOE_NEWCONNECTED) )
49652419Sjulian		&& (sp->Session_ID == session)
49752419Sjulian		&& (bcmp(sp->pkt_hdr.eh.ether_dhost,
49852419Sjulian		    wh->eh.ether_shost,
49952419Sjulian		    ETHER_ADDR_LEN)) == 0) {
50052419Sjulian			break;
50152419Sjulian		}
50252419Sjulian	}
50352419Sjulian	return (hook);
50452419Sjulian}
50552419Sjulian
50652419Sjulianstatic hook_p
50752419Sjulianpppoe_finduniq(node_p node, struct pppoe_tag *tag)
50852419Sjulian{
50952419Sjulian	hook_p hook = NULL;
51070784Sjulian	priv_p	privp = NG_NODE_PRIVATE(node);
51152419Sjulian	union uniq		uniq;
51252419Sjulian
51352443SjulianAAA
51452419Sjulian	bcopy(tag->tag_data, uniq.bytes, sizeof(void *));
51552419Sjulian	/* cycle through all known hooks */
51670784Sjulian	LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
51752419Sjulian		/* don't check special hooks */
51870784Sjulian		if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook)
51970784Sjulian		||  (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook))
52052419Sjulian			continue;
52170784Sjulian		if (uniq.pointer == NG_HOOK_PRIVATE(hook))
52252419Sjulian			break;
52352419Sjulian	}
52452419Sjulian	return (hook);
52552419Sjulian}
52652419Sjulian
52752419Sjulian/**************************************************************************
52852419Sjulian * start of Netgraph entrypoints					  *
52952419Sjulian **************************************************************************/
53052419Sjulian
53152419Sjulian/*
53252419Sjulian * Allocate the private data structure and the generic node
53352419Sjulian * and link them together.
53452419Sjulian *
53552419Sjulian * ng_make_node_common() returns with a generic node struct
53652419Sjulian * with a single reference for us.. we transfer it to the
53752419Sjulian * private structure.. when we free the private struct we must
53852419Sjulian * unref the node so it gets freed too.
53952419Sjulian */
54052419Sjulianstatic int
54170700Sjulianng_pppoe_constructor(node_p node)
54252419Sjulian{
54352419Sjulian	priv_p privdata;
54452419Sjulian
54552443SjulianAAA
54652419Sjulian	/* Initialize private descriptor */
54770870Sjulian	MALLOC(privdata, priv_p, sizeof(*privdata), M_NETGRAPH_PPPOE,
54868876Sdwmalone	    M_NOWAIT | M_ZERO);
54952419Sjulian	if (privdata == NULL)
55052419Sjulian		return (ENOMEM);
55152419Sjulian
55252419Sjulian	/* Link structs together; this counts as our one reference to *nodep */
55370784Sjulian	NG_NODE_SET_PRIVATE(node, privdata);
55470700Sjulian	privdata->node = node;
55552419Sjulian	return (0);
55652419Sjulian}
55752419Sjulian
55852419Sjulian/*
55952419Sjulian * Give our ok for a hook to be added...
56052419Sjulian * point the hook's private info to the hook structure.
56152419Sjulian *
56252419Sjulian * The following hook names are special:
56352419Sjulian *  Ethernet:  the hook that should be connected to a NIC.
56452419Sjulian *  debug:	copies of data sent out here  (when I write the code).
56569922Sjulian * All other hook names need only be unique. (the framework checks this).
56652419Sjulian */
56752419Sjulianstatic int
56852562Sjulianng_pppoe_newhook(node_p node, hook_p hook, const char *name)
56952419Sjulian{
57070784Sjulian	const priv_p privp = NG_NODE_PRIVATE(node);
57152419Sjulian	sessp sp;
57252419Sjulian
57352443SjulianAAA
57452419Sjulian	if (strcmp(name, NG_PPPOE_HOOK_ETHERNET) == 0) {
57552419Sjulian		privp->ethernet_hook = hook;
57670784Sjulian		NG_HOOK_SET_PRIVATE(hook, &privp->ethernet_hook);
57752419Sjulian	} else if (strcmp(name, NG_PPPOE_HOOK_DEBUG) == 0) {
57852419Sjulian		privp->debug_hook = hook;
57970784Sjulian		NG_HOOK_SET_PRIVATE(hook, &privp->debug_hook);
58052419Sjulian	} else {
58152419Sjulian		/*
58252419Sjulian		 * Any other unique name is OK.
58352419Sjulian		 * The infrastructure has already checked that it's unique,
58452419Sjulian		 * so just allocate it and hook it in.
58552419Sjulian		 */
58670870Sjulian		MALLOC(sp, sessp, sizeof(*sp), M_NETGRAPH_PPPOE, M_NOWAIT | M_ZERO);
58752419Sjulian		if (sp == NULL) {
58852419Sjulian				return (ENOMEM);
58952419Sjulian		}
59052419Sjulian
59170784Sjulian		NG_HOOK_SET_PRIVATE(hook, sp);
59252419Sjulian		sp->hook = hook;
59352419Sjulian	}
59452419Sjulian	return(0);
59552419Sjulian}
59652419Sjulian
59752419Sjulian/*
59852419Sjulian * Get a netgraph control message.
59952419Sjulian * Check it is one we understand. If needed, send a response.
60052419Sjulian * We sometimes save the address for an async action later.
60152419Sjulian * Always free the message.
60252419Sjulian */
60352419Sjulianstatic int
60470700Sjulianng_pppoe_rcvmsg(node_p node, item_p item, hook_p lasthook)
60552419Sjulian{
60670784Sjulian	priv_p privp = NG_NODE_PRIVATE(node);
60752562Sjulian	struct ngpppoe_init_data *ourmsg = NULL;
60852419Sjulian	struct ng_mesg *resp = NULL;
60952419Sjulian	int error = 0;
61052419Sjulian	hook_p hook = NULL;
61152419Sjulian	sessp sp = NULL;
61252419Sjulian	negp neg = NULL;
61370700Sjulian	struct ng_mesg *msg;
61452419Sjulian
61552443SjulianAAA
61670700Sjulian	NGI_GET_MSG(item, msg);
61752419Sjulian	/* Deal with message according to cookie and command */
61852419Sjulian	switch (msg->header.typecookie) {
61952419Sjulian	case NGM_PPPOE_COOKIE:
62052419Sjulian		switch (msg->header.cmd) {
62152419Sjulian		case NGM_PPPOE_CONNECT:
62252419Sjulian		case NGM_PPPOE_LISTEN:
62352419Sjulian		case NGM_PPPOE_OFFER:
62469922Sjulian		case NGM_PPPOE_SERVICE:
62568845Sbrian			ourmsg = (struct ngpppoe_init_data *)msg->data;
62668845Sbrian			if (msg->header.arglen < sizeof(*ourmsg)) {
62768845Sbrian				printf("pppoe: init data too small\n");
62868845Sbrian				LEAVE(EMSGSIZE);
62968031Sbrian			}
63068031Sbrian			if (msg->header.arglen - sizeof(*ourmsg) >
63168031Sbrian			    PPPOE_SERVICE_NAME_SIZE) {
63268031Sbrian				printf("pppoe_rcvmsg: service name too big");
63352419Sjulian				LEAVE(EMSGSIZE);
63452419Sjulian			}
63568845Sbrian			if (msg->header.arglen - sizeof(*ourmsg) <
63668845Sbrian			    ourmsg->data_len) {
63768845Sbrian				printf("pppoe: init data has bad length,"
63868845Sbrian				    " %d should be %d\n", ourmsg->data_len,
63968845Sbrian				    msg->header.arglen - sizeof (*ourmsg));
64052419Sjulian				LEAVE(EMSGSIZE);
64152419Sjulian			}
64268031Sbrian
64352419Sjulian			/* make sure strcmp will terminate safely */
64452419Sjulian			ourmsg->hook[sizeof(ourmsg->hook) - 1] = '\0';
64552419Sjulian
64652419Sjulian			/* cycle through all known hooks */
64770784Sjulian			LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
64870784Sjulian				if (NG_HOOK_NAME(hook)
64970784Sjulian				&& strcmp(NG_HOOK_NAME(hook), ourmsg->hook) == 0)
65052419Sjulian					break;
65152419Sjulian			}
65252419Sjulian			if (hook == NULL) {
65352419Sjulian				LEAVE(ENOENT);
65452419Sjulian			}
65570784Sjulian			if ((NG_HOOK_PRIVATE(hook) == &privp->debug_hook)
65670784Sjulian			||  (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook)) {
65752419Sjulian				LEAVE(EINVAL);
65852419Sjulian			}
65970784Sjulian			sp = NG_HOOK_PRIVATE(hook);
66069922Sjulian
66169922Sjulian			/*
66269922Sjulian			 * PPPOE_SERVICE advertisments are set up
66369922Sjulian			 * on sessions that are in PRIMED state.
66469922Sjulian			 */
66569922Sjulian			if (msg->header.cmd == NGM_PPPOE_SERVICE) {
66669922Sjulian				break;
66769922Sjulian			}
66852419Sjulian			if (sp->state |= PPPOE_SNONE) {
66952419Sjulian				printf("pppoe: Session already active\n");
67052419Sjulian				LEAVE(EISCONN);
67152419Sjulian			}
67252443Sjulian
67352419Sjulian			/*
67452419Sjulian			 * set up prototype header
67552419Sjulian			 */
67670870Sjulian			MALLOC(neg, negp, sizeof(*neg), M_NETGRAPH_PPPOE,
67768876Sdwmalone			    M_NOWAIT | M_ZERO);
67852419Sjulian
67952419Sjulian			if (neg == NULL) {
68052419Sjulian				printf("pppoe: Session out of memory\n");
68152419Sjulian				LEAVE(ENOMEM);
68252419Sjulian			}
68352419Sjulian			MGETHDR(neg->m, M_DONTWAIT, MT_DATA);
68452419Sjulian			if(neg->m == NULL) {
68552443Sjulian				printf("pppoe: Session out of mbufs\n");
68670870Sjulian				FREE(neg, M_NETGRAPH_PPPOE);
68752419Sjulian				LEAVE(ENOBUFS);
68852419Sjulian			}
68952419Sjulian			neg->m->m_pkthdr.rcvif = NULL;
69052419Sjulian			MCLGET(neg->m, M_DONTWAIT);
69152419Sjulian			if ((neg->m->m_flags & M_EXT) == 0) {
69252443Sjulian				printf("pppoe: Session out of mcls\n");
69352419Sjulian				m_freem(neg->m);
69470870Sjulian				FREE(neg, M_NETGRAPH_PPPOE);
69552419Sjulian				LEAVE(ENOBUFS);
69652419Sjulian			}
69752419Sjulian			sp->neg = neg;
69852443Sjulian			callout_handle_init( &neg->timeout_handle);
69952419Sjulian			neg->m->m_len = sizeof(struct pppoe_full_hdr);
70052419Sjulian			neg->pkt = mtod(neg->m, union packet*);
70152419Sjulian			neg->pkt->pkt_header.eh = eh_prototype;
70252419Sjulian			neg->pkt->pkt_header.ph.ver = 0x1;
70352419Sjulian			neg->pkt->pkt_header.ph.type = 0x1;
70452419Sjulian			neg->pkt->pkt_header.ph.sid = 0x0000;
70552419Sjulian			neg->timeout = 0;
70652419Sjulian
70770700Sjulian			sp->creator = NGI_RETADDR(item);
70852419Sjulian		}
70952419Sjulian		switch (msg->header.cmd) {
71052419Sjulian		case NGM_PPPOE_GET_STATUS:
71152419Sjulian		    {
71252562Sjulian			struct ngpppoestat *stats;
71352419Sjulian
71452419Sjulian			NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT);
71552419Sjulian			if (!resp) {
71652419Sjulian				LEAVE(ENOMEM);
71752419Sjulian			}
71852562Sjulian			stats = (struct ngpppoestat *) resp->data;
71952419Sjulian			stats->packets_in = privp->packets_in;
72052419Sjulian			stats->packets_out = privp->packets_out;
72152419Sjulian			break;
72252419Sjulian		    }
72352419Sjulian		case NGM_PPPOE_CONNECT:
72452419Sjulian			/*
72552419Sjulian			 * Check the hook exists and is Uninitialised.
72652419Sjulian			 * Send a PADI request, and start the timeout logic.
72752419Sjulian			 * Store the originator of this message so we can send
72852419Sjulian			 * a success of fail message to them later.
72952419Sjulian			 * Move the session to SINIT
73052419Sjulian			 * Set up the session to the correct state and
73152419Sjulian			 * start it.
73252419Sjulian			 */
73352419Sjulian			neg->service.hdr.tag_type = PTT_SRV_NAME;
73468845Sbrian			neg->service.hdr.tag_len =
73568845Sbrian			    htons((u_int16_t)ourmsg->data_len);
73668845Sbrian			if (ourmsg->data_len)
73768845Sbrian				bcopy(ourmsg->data, neg->service.data,
73868845Sbrian				    ourmsg->data_len);
73968845Sbrian			neg->service_len = ourmsg->data_len;
74052419Sjulian			pppoe_start(sp);
74152419Sjulian			break;
74252419Sjulian		case NGM_PPPOE_LISTEN:
74352419Sjulian			/*
74452419Sjulian			 * Check the hook exists and is Uninitialised.
74552419Sjulian			 * Install the service matching string.
74652419Sjulian			 * Store the originator of this message so we can send
74752419Sjulian			 * a success of fail message to them later.
74852419Sjulian			 * Move the hook to 'LISTENING'
74952419Sjulian			 */
75052419Sjulian			neg->service.hdr.tag_type = PTT_SRV_NAME;
75168845Sbrian			neg->service.hdr.tag_len =
75268845Sbrian			    htons((u_int16_t)ourmsg->data_len);
75352443Sjulian
75468845Sbrian			if (ourmsg->data_len)
75568845Sbrian				bcopy(ourmsg->data, neg->service.data,
75668845Sbrian				    ourmsg->data_len);
75768845Sbrian			neg->service_len = ourmsg->data_len;
75852419Sjulian			neg->pkt->pkt_header.ph.code = PADT_CODE;
75952419Sjulian			/*
76052419Sjulian			 * wait for PADI packet coming from ethernet
76152419Sjulian			 */
76252419Sjulian			sp->state = PPPOE_LISTENING;
76352419Sjulian			break;
76452419Sjulian		case NGM_PPPOE_OFFER:
76552419Sjulian			/*
76652419Sjulian			 * Check the hook exists and is Uninitialised.
76752419Sjulian			 * Store the originator of this message so we can send
76852419Sjulian			 * a success of fail message to them later.
76952419Sjulian			 * Store the AC-Name given and go to PRIMED.
77052419Sjulian			 */
77152419Sjulian			neg->ac_name.hdr.tag_type = PTT_AC_NAME;
77268845Sbrian			neg->ac_name.hdr.tag_len =
77368845Sbrian			    htons((u_int16_t)ourmsg->data_len);
77468845Sbrian			if (ourmsg->data_len)
77568845Sbrian				bcopy(ourmsg->data, neg->ac_name.data,
77668845Sbrian				    ourmsg->data_len);
77768845Sbrian			neg->ac_name_len = ourmsg->data_len;
77852419Sjulian			neg->pkt->pkt_header.ph.code = PADO_CODE;
77952419Sjulian			/*
78052419Sjulian			 * Wait for PADI packet coming from hook
78152419Sjulian			 */
78252419Sjulian			sp->state = PPPOE_PRIMED;
78352419Sjulian			break;
78469922Sjulian		case NGM_PPPOE_SERVICE:
78569922Sjulian			/*
78669922Sjulian			 * Check the session is primed.
78769922Sjulian			 * for now just allow ONE service to be advertised.
78869922Sjulian			 * If you do it twice you just overwrite.
78969922Sjulian			 */
79070148Sjulian			if (sp->state != PPPOE_PRIMED) {
79169922Sjulian				printf("pppoe: Session not primed\n");
79269922Sjulian				LEAVE(EISCONN);
79369922Sjulian			}
79470931Sjulian			neg = sp->neg;
79569922Sjulian			neg->service.hdr.tag_type = PTT_SRV_NAME;
79669922Sjulian			neg->service.hdr.tag_len =
79769922Sjulian			    htons((u_int16_t)ourmsg->data_len);
79869922Sjulian
79969922Sjulian			if (ourmsg->data_len)
80069922Sjulian				bcopy(ourmsg->data, neg->service.data,
80169922Sjulian				    ourmsg->data_len);
80269922Sjulian			neg->service_len = ourmsg->data_len;
80369922Sjulian			break;
80452419Sjulian		default:
80552419Sjulian			LEAVE(EINVAL);
80652419Sjulian		}
80752419Sjulian		break;
80852419Sjulian	default:
80952419Sjulian		LEAVE(EINVAL);
81052419Sjulian	}
81152419Sjulian
81252419Sjulian	/* Take care of synchronous response, if any */
81370700Sjulianquit:
81470700Sjulian	NG_RESPOND_MSG(error, node, item, resp);
81552419Sjulian	/* Free the message and return */
81670700Sjulian	NG_FREE_MSG(msg);
81752419Sjulian	return(error);
81852419Sjulian}
81952419Sjulian
82052443Sjulian/*
82152443Sjulian * Start a client into the first state. A separate function because
82252443Sjulian * it can be needed if the negotiation times out.
82352443Sjulian */
82452419Sjulianstatic void
82552419Sjulianpppoe_start(sessp sp)
82652419Sjulian{
82752419Sjulian	struct {
82852419Sjulian		struct pppoe_tag hdr;
82952419Sjulian		union	uniq	data;
83052419Sjulian	} uniqtag;
83152419Sjulian
83252419Sjulian	/*
83352419Sjulian	 * kick the state machine into starting up
83452419Sjulian	 */
83552443SjulianAAA
83652419Sjulian	sp->state = PPPOE_SINIT;
83752443Sjulian	/* reset the packet header to broadcast */
83852443Sjulian	sp->neg->pkt->pkt_header.eh = eh_prototype;
83952443Sjulian	sp->neg->pkt->pkt_header.ph.code = PADI_CODE;
84052419Sjulian	uniqtag.hdr.tag_type = PTT_HOST_UNIQ;
84152419Sjulian	uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(uniqtag.data));
84252419Sjulian	uniqtag.data.pointer = sp;
84352419Sjulian	init_tags(sp);
84468079Sjulian	insert_tag(sp, &uniqtag.hdr);
84553154Sjulian	insert_tag(sp, &sp->neg->service.hdr);
84652419Sjulian	make_packet(sp);
84752419Sjulian	sendpacket(sp);
84852419Sjulian}
84952419Sjulian
85052419Sjulian/*
85152419Sjulian * Receive data, and do something with it.
85252419Sjulian * The caller will never free m or meta, so
85352419Sjulian * if we use up this data or abort we must free BOTH of these.
85452419Sjulian */
85552419Sjulianstatic int
85670700Sjulianng_pppoe_rcvdata(hook_p hook, item_p item)
85752419Sjulian{
85870784Sjulian	node_p			node = NG_HOOK_NODE(hook);
85970784Sjulian	const priv_p		privp = NG_NODE_PRIVATE(node);
86070784Sjulian	sessp			sp = NG_HOOK_PRIVATE(hook);
86152419Sjulian	struct pppoe_full_hdr	*wh;
86252419Sjulian	struct pppoe_hdr	*ph;
86352419Sjulian	int			error = 0;
86452419Sjulian	u_int16_t		session;
86552419Sjulian	u_int16_t		length;
86652419Sjulian	u_int8_t		code;
86753154Sjulian	struct pppoe_tag	*utag = NULL, *tag = NULL;
86852419Sjulian	hook_p 			sendhook;
86952419Sjulian	struct {
87052419Sjulian		struct pppoe_tag hdr;
87152419Sjulian		union	uniq	data;
87252419Sjulian	} uniqtag;
87352419Sjulian	negp			neg = NULL;
87470700Sjulian	struct mbuf		*m;
87552419Sjulian
87652443SjulianAAA
87770700Sjulian	NGI_GET_M(item, m);
87870784Sjulian	if (NG_HOOK_PRIVATE(hook) == &privp->debug_hook) {
87952419Sjulian		/*
88052419Sjulian		 * Data from the debug hook gets sent without modification
88152419Sjulian		 * straight to the ethernet.
88252419Sjulian		 */
88370784Sjulian		NG_FWD_ITEM_HOOK( error, item, privp->ethernet_hook);
88452419Sjulian	 	privp->packets_out++;
88570784Sjulian	} else if (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook) {
88652419Sjulian		/*
88752419Sjulian		 * Incoming data.
88852419Sjulian		 * Dig out various fields from the packet.
88952419Sjulian		 * use them to decide where to send it.
89052419Sjulian		 */
89152419Sjulian
89252419Sjulian 		privp->packets_in++;
89352510Sjulian		if( m->m_len < sizeof(*wh)) {
89452510Sjulian			m = m_pullup(m, sizeof(*wh)); /* Checks length */
89552510Sjulian			if (m == NULL) {
89652510Sjulian				printf("couldn't m_pullup\n");
89752510Sjulian				LEAVE(ENOBUFS);
89852510Sjulian			}
89952419Sjulian		}
90052419Sjulian		wh = mtod(m, struct pppoe_full_hdr *);
90152419Sjulian		length = ntohs(wh->ph.length);
90252510Sjulian		switch(wh->eh.ether_type) {
90352419Sjulian		case	ETHERTYPE_PPPOE_DISC:
90452419Sjulian			/*
90553172Sjulian			 * We need to try to make sure that the tag area
90653172Sjulian			 * is contiguous, or we could wander off the end
90752419Sjulian			 * of a buffer and make a mess.
90852419Sjulian			 * (Linux wouldn't have this problem).
90952419Sjulian			 */
91052510Sjulian			if (m->m_pkthdr.len <= MHLEN) {
91152510Sjulian				if( m->m_len < m->m_pkthdr.len) {
91252510Sjulian					m = m_pullup(m, m->m_pkthdr.len);
91352510Sjulian					if (m == NULL) {
91452510Sjulian						printf("couldn't m_pullup\n");
91552510Sjulian						LEAVE(ENOBUFS);
91652510Sjulian					}
91752510Sjulian				}
91852510Sjulian			}
91952510Sjulian			if (m->m_len != m->m_pkthdr.len) {
92052419Sjulian				/*
92152419Sjulian				 * It's not all in one piece.
92252419Sjulian				 * We need to do extra work.
92370700Sjulian				 * Put it into a cluster.
92452419Sjulian				 */
92570700Sjulian				struct mbuf *n;
92670700Sjulian				n = m_dup(m, M_DONTWAIT);
92770700Sjulian				m_freem(m);
92870700Sjulian				m = n;
92970700Sjulian				if (m) {
93070700Sjulian					/* just check we got a cluster */
93170700Sjulian					if (m->m_len != m->m_pkthdr.len) {
93270700Sjulian						m_freem(m);
93370700Sjulian						m = NULL;
93470700Sjulian					}
93570700Sjulian				}
93670700Sjulian				if (m == NULL) {
93770700Sjulian					printf("packet fragmented\n");
93870700Sjulian					LEAVE(EMSGSIZE);
93970700Sjulian				}
94053498Sjulian			}
94170700Sjulian			wh = mtod(m, struct pppoe_full_hdr *);
94270700Sjulian			length = ntohs(wh->ph.length);
94370700Sjulian			ph = &wh->ph;
94470700Sjulian			session = ntohs(wh->ph.sid);
94570700Sjulian			code = wh->ph.code;
94652419Sjulian
94752419Sjulian			switch(code) {
94852419Sjulian			case	PADI_CODE:
94952419Sjulian				/*
95052419Sjulian				 * We are a server:
95152419Sjulian				 * Look for a hook with the required service
95252419Sjulian				 * and send the ENTIRE packet up there.
95352419Sjulian				 * It should come back to a new hook in
95452419Sjulian				 * PRIMED state. Look there for further
95552419Sjulian				 * processing.
95652419Sjulian				 */
95752419Sjulian				tag = get_tag(ph, PTT_SRV_NAME);
95852419Sjulian				if (tag == NULL) {
95952448Sjulian					printf("no service tag\n");
96052419Sjulian					LEAVE(ENETUNREACH);
96152419Sjulian				}
96270784Sjulian				sendhook = pppoe_match_svc(NG_HOOK_NODE(hook),
96352419Sjulian			    		tag->tag_data, ntohs(tag->tag_len));
96452419Sjulian				if (sendhook) {
96570700Sjulian					NG_FWD_NEW_DATA(error, item,
96670700Sjulian								sendhook, m);
96752419Sjulian				} else {
96852448Sjulian					printf("no such service\n");
96952419Sjulian					LEAVE(ENETUNREACH);
97052419Sjulian				}
97152419Sjulian				break;
97252419Sjulian			case	PADO_CODE:
97352419Sjulian				/*
97452419Sjulian				 * We are a client:
97552419Sjulian				 * Use the host_uniq tag to find the
97652419Sjulian				 * hook this is in response to.
97752448Sjulian				 * Received #2, now send #3
97852419Sjulian				 * For now simply accept the first we receive.
97952419Sjulian				 */
98053154Sjulian				utag = get_tag(ph, PTT_HOST_UNIQ);
98153154Sjulian				if ((utag == NULL)
98253154Sjulian				|| (ntohs(utag->tag_len) != sizeof(sp))) {
98352448Sjulian					printf("no host unique field\n");
98452419Sjulian					LEAVE(ENETUNREACH);
98552419Sjulian				}
98652419Sjulian
98753154Sjulian				sendhook = pppoe_finduniq(node, utag);
98852419Sjulian				if (sendhook == NULL) {
98952448Sjulian					printf("no matching session\n");
99052419Sjulian					LEAVE(ENETUNREACH);
99152419Sjulian				}
99252419Sjulian
99352419Sjulian				/*
99452419Sjulian				 * Check the session is in the right state.
99552419Sjulian				 * It needs to be in PPPOE_SINIT.
99652419Sjulian				 */
99770784Sjulian				sp = NG_HOOK_PRIVATE(sendhook);
99852419Sjulian				if (sp->state != PPPOE_SINIT) {
99952448Sjulian					printf("session in wrong state\n");
100052419Sjulian					LEAVE(ENETUNREACH);
100152419Sjulian				}
100252419Sjulian				neg = sp->neg;
100352419Sjulian				untimeout(pppoe_ticker, sendhook,
100452419Sjulian				    neg->timeout_handle);
100552419Sjulian
100652419Sjulian				/*
100752419Sjulian				 * This is the first time we hear
100852419Sjulian				 * from the server, so note it's
100952419Sjulian				 * unicast address, replacing the
101052419Sjulian				 * broadcast address .
101152419Sjulian				 */
101252419Sjulian				bcopy(wh->eh.ether_shost,
101352419Sjulian					neg->pkt->pkt_header.eh.ether_dhost,
101452419Sjulian					ETHER_ADDR_LEN);
101552419Sjulian				neg->timeout = 0;
101652419Sjulian				neg->pkt->pkt_header.ph.code = PADR_CODE;
101752419Sjulian				init_tags(sp);
101868079Sjulian				insert_tag(sp, utag);      /* Host Unique */
101953154Sjulian				if ((tag = get_tag(ph, PTT_AC_COOKIE)))
102052448Sjulian					insert_tag(sp, tag); /* return cookie */
102153154Sjulian				if ((tag = get_tag(ph, PTT_AC_NAME)))
102253154Sjulian					insert_tag(sp, tag); /* return it */
102368079Sjulian				insert_tag(sp, &neg->service.hdr); /* Service */
102452419Sjulian				scan_tags(sp, ph);
102552419Sjulian				make_packet(sp);
102652419Sjulian				sp->state = PPPOE_SREQ;
102752419Sjulian				sendpacket(sp);
102852419Sjulian				break;
102952419Sjulian			case	PADR_CODE:
103052419Sjulian
103152419Sjulian				/*
103252419Sjulian				 * We are a server:
103352419Sjulian				 * Use the ac_cookie tag to find the
103452419Sjulian				 * hook this is in response to.
103552419Sjulian				 */
103653154Sjulian				utag = get_tag(ph, PTT_AC_COOKIE);
103753154Sjulian				if ((utag == NULL)
103853154Sjulian				|| (ntohs(utag->tag_len) != sizeof(sp))) {
103952419Sjulian					LEAVE(ENETUNREACH);
104052419Sjulian				}
104152419Sjulian
104253154Sjulian				sendhook = pppoe_finduniq(node, utag);
104352419Sjulian				if (sendhook == NULL) {
104452419Sjulian					LEAVE(ENETUNREACH);
104552419Sjulian				}
104652419Sjulian
104752419Sjulian				/*
104852419Sjulian				 * Check the session is in the right state.
104952419Sjulian				 * It needs to be in PPPOE_SOFFER
105052419Sjulian				 * or PPPOE_NEWCONNECTED. If the latter,
105152419Sjulian				 * then this is a retry by the client.
105252419Sjulian				 * so be nice, and resend.
105352419Sjulian				 */
105470784Sjulian				sp = NG_HOOK_PRIVATE(sendhook);
105552419Sjulian				if (sp->state == PPPOE_NEWCONNECTED) {
105652419Sjulian					/*
105752419Sjulian					 * Whoa! drop back to resend that
105852419Sjulian					 * PADS packet.
105952419Sjulian					 * We should still have a copy of it.
106052419Sjulian					 */
106152419Sjulian					sp->state = PPPOE_SOFFER;
106252419Sjulian				}
106352419Sjulian				if (sp->state != PPPOE_SOFFER) {
106452419Sjulian					LEAVE (ENETUNREACH);
106552419Sjulian					break;
106652419Sjulian				}
106752419Sjulian				neg = sp->neg;
106852419Sjulian				untimeout(pppoe_ticker, sendhook,
106952419Sjulian				    neg->timeout_handle);
107052419Sjulian				neg->pkt->pkt_header.ph.code = PADS_CODE;
107152419Sjulian				if (sp->Session_ID == 0)
107252419Sjulian					neg->pkt->pkt_header.ph.sid =
107352448Sjulian					    htons(sp->Session_ID
107452448Sjulian						= get_new_sid(node));
107552419Sjulian				neg->timeout = 0;
107652419Sjulian				/*
107752419Sjulian				 * start working out the tags to respond with.
107852419Sjulian				 */
107952419Sjulian				init_tags(sp);
108052419Sjulian				insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */
108153172Sjulian				if ((tag = get_tag(ph, PTT_SRV_NAME)))
108253145Sjulian					insert_tag(sp, tag);/* return service */
108353154Sjulian				if ((tag = get_tag(ph, PTT_HOST_UNIQ)))
108453145Sjulian					insert_tag(sp, tag); /* return it */
108553154Sjulian				insert_tag(sp, utag);	/* ac_cookie */
108652419Sjulian				scan_tags(sp, ph);
108752419Sjulian				make_packet(sp);
108853498Sjulian				sp->state = PPPOE_NEWCONNECTED;
108953172Sjulian				sendpacket(sp);
109052419Sjulian				/*
109152419Sjulian				 * Having sent the last Negotiation header,
109252419Sjulian				 * Set up the stored packet header to
109352419Sjulian				 * be correct for the actual session.
109452419Sjulian				 * But keep the negotialtion stuff
109552419Sjulian				 * around in case we need to resend this last
109652419Sjulian				 * packet. We'll discard it when we move
109752419Sjulian				 * from NEWCONNECTED to CONNECTED
109852419Sjulian				 */
109952419Sjulian				sp->pkt_hdr = neg->pkt->pkt_header;
110052419Sjulian				sp->pkt_hdr.eh.ether_type
110152419Sjulian						= ETHERTYPE_PPPOE_SESS;
110252419Sjulian				sp->pkt_hdr.ph.code = 0;
110352441Sjulian				pppoe_send_event(sp, NGM_PPPOE_SUCCESS);
110452419Sjulian				break;
110552419Sjulian			case	PADS_CODE:
110652419Sjulian				/*
110752419Sjulian				 * We are a client:
110852419Sjulian				 * Use the host_uniq tag to find the
110952419Sjulian				 * hook this is in response to.
111052419Sjulian				 * take the session ID and store it away.
111152419Sjulian				 * Also make sure the pre-made header is
111252419Sjulian				 * correct and set us into Session mode.
111352419Sjulian				 */
111453154Sjulian				utag = get_tag(ph, PTT_HOST_UNIQ);
111553154Sjulian				if ((utag == NULL)
111653154Sjulian				|| (ntohs(utag->tag_len) != sizeof(sp))) {
111752419Sjulian					LEAVE (ENETUNREACH);
111852419Sjulian					break;
111952419Sjulian				}
112053154Sjulian				sendhook = pppoe_finduniq(node, utag);
112152419Sjulian				if (sendhook == NULL) {
112252419Sjulian					LEAVE(ENETUNREACH);
112352419Sjulian				}
112452419Sjulian
112552419Sjulian				/*
112652419Sjulian				 * Check the session is in the right state.
112752419Sjulian				 * It needs to be in PPPOE_SREQ.
112852419Sjulian				 */
112970784Sjulian				sp = NG_HOOK_PRIVATE(sendhook);
113052419Sjulian				if (sp->state != PPPOE_SREQ) {
113152419Sjulian					LEAVE(ENETUNREACH);
113252419Sjulian				}
113352419Sjulian				neg = sp->neg;
113452419Sjulian				untimeout(pppoe_ticker, sendhook,
113552419Sjulian				    neg->timeout_handle);
113652524Sjulian				neg->pkt->pkt_header.ph.sid = wh->ph.sid;
113752448Sjulian				sp->Session_ID = ntohs(wh->ph.sid);
113852419Sjulian				neg->timeout = 0;
113952419Sjulian				sp->state = PPPOE_CONNECTED;
114052419Sjulian				/*
114152419Sjulian				 * Now we have gone to Connected mode,
114252419Sjulian				 * Free all resources needed for
114352419Sjulian				 * negotiation.
114452419Sjulian				 * Keep a copy of the header we will be using.
114552419Sjulian				 */
114652419Sjulian				sp->pkt_hdr = neg->pkt->pkt_header;
114752419Sjulian				sp->pkt_hdr.eh.ether_type
114852419Sjulian						= ETHERTYPE_PPPOE_SESS;
114952419Sjulian				sp->pkt_hdr.ph.code = 0;
115052419Sjulian				m_freem(neg->m);
115170870Sjulian				FREE(sp->neg, M_NETGRAPH_PPPOE);
115252419Sjulian				sp->neg = NULL;
115352441Sjulian				pppoe_send_event(sp, NGM_PPPOE_SUCCESS);
115452419Sjulian				break;
115552419Sjulian			case	PADT_CODE:
115652419Sjulian				/*
115752419Sjulian				 * Send a 'close' message to the controlling
115852419Sjulian				 * process (the one that set us up);
115952419Sjulian				 * And then tear everything down.
116052419Sjulian				 *
116152419Sjulian				 * Find matching peer/session combination.
116252419Sjulian				 */
116352419Sjulian				sendhook = pppoe_findsession(node, wh);
116452419Sjulian				if (sendhook == NULL) {
116552419Sjulian					LEAVE(ENETUNREACH);
116652419Sjulian				}
116752419Sjulian				/* send message to creator */
116852419Sjulian				/* close hook */
116952441Sjulian				if (sendhook) {
117052441Sjulian					ng_destroy_hook(sendhook);
117152441Sjulian				}
117252419Sjulian				break;
117352419Sjulian			default:
117452419Sjulian				LEAVE(EPFNOSUPPORT);
117552419Sjulian			}
117652419Sjulian			break;
117752419Sjulian		case	ETHERTYPE_PPPOE_SESS:
117852419Sjulian			/*
117952419Sjulian			 * find matching peer/session combination.
118052419Sjulian			 */
118152419Sjulian			sendhook = pppoe_findsession(node, wh);
118252419Sjulian			if (sendhook == NULL) {
118352419Sjulian				LEAVE (ENETUNREACH);
118452419Sjulian				break;
118552419Sjulian			}
118670784Sjulian			sp = NG_HOOK_PRIVATE(sendhook);
118752419Sjulian			m_adj(m, sizeof(*wh));
118852419Sjulian			if (m->m_pkthdr.len < length) {
118952419Sjulian				/* Packet too short, dump it */
119052419Sjulian				LEAVE(EMSGSIZE);
119152419Sjulian			}
119252523Sjulian
119353145Sjulian			/* Also need to trim excess at the end */
119452523Sjulian			if (m->m_pkthdr.len > length) {
119552523Sjulian				m_adj(m, -((int)(m->m_pkthdr.len - length)));
119652523Sjulian			}
119752419Sjulian			if ( sp->state != PPPOE_CONNECTED) {
119852419Sjulian				if (sp->state == PPPOE_NEWCONNECTED) {
119952419Sjulian					sp->state = PPPOE_CONNECTED;
120052419Sjulian					/*
120152419Sjulian					 * Now we have gone to Connected mode,
120252419Sjulian					 * Free all resources needed for
120359728Sjulian					 * negotiation. Be paranoid about
120459728Sjulian					 * whether there may be a timeout.
120552419Sjulian					 */
120652419Sjulian					m_freem(sp->neg->m);
120759728Sjulian					untimeout(pppoe_ticker, sendhook,
120859728Sjulian				    		sp->neg->timeout_handle);
120970870Sjulian					FREE(sp->neg, M_NETGRAPH_PPPOE);
121052419Sjulian					sp->neg = NULL;
121152419Sjulian				} else {
121252419Sjulian					LEAVE (ENETUNREACH);
121352419Sjulian					break;
121452419Sjulian				}
121552419Sjulian			}
121670700Sjulian			NG_FWD_NEW_DATA( error, item, sendhook, m);
121752419Sjulian			break;
121852419Sjulian		default:
121952522Sjulian			LEAVE(EPFNOSUPPORT);
122052419Sjulian		}
122152419Sjulian	} else {
122252419Sjulian		/*
122352419Sjulian		 * 	Not ethernet or debug hook..
122452419Sjulian		 *
122552419Sjulian		 * The packet has come in on a normal hook.
122652419Sjulian		 * We need to find out what kind of hook,
122752419Sjulian		 * So we can decide how to handle it.
122852419Sjulian		 * Check the hook's state.
122952419Sjulian		 */
123070784Sjulian		sp = NG_HOOK_PRIVATE(hook);
123152419Sjulian		switch (sp->state) {
123252419Sjulian		case	PPPOE_NEWCONNECTED:
123352419Sjulian		case	PPPOE_CONNECTED: {
123464502Sarchie			static const u_char addrctrl[] = { 0xff, 0x03 };
123552419Sjulian			struct pppoe_full_hdr *wh;
123664502Sarchie
123752419Sjulian			/*
123864502Sarchie			 * Remove PPP address and control fields, if any.
123964502Sarchie			 * For example, ng_ppp(4) always sends LCP packets
124064502Sarchie			 * with address and control fields as required by
124164502Sarchie			 * generic PPP. PPPoE is an exception to the rule.
124264502Sarchie			 */
124364502Sarchie			if (m->m_pkthdr.len >= 2) {
124464502Sarchie				if (m->m_len < 2 && !(m = m_pullup(m, 2)))
124564502Sarchie					LEAVE(ENOBUFS);
124664502Sarchie				if (bcmp(mtod(m, u_char *), addrctrl, 2) == 0)
124764502Sarchie					m_adj(m, 2);
124864502Sarchie			}
124964502Sarchie			/*
125052419Sjulian			 * Bang in a pre-made header, and set the length up
125152419Sjulian			 * to be correct. Then send it to the ethernet driver.
125252614Sjulian			 * But first correct the length.
125352419Sjulian			 */
125452614Sjulian			sp->pkt_hdr.ph.length = htons((short)(m->m_pkthdr.len));
125552419Sjulian			M_PREPEND(m, sizeof(*wh), M_DONTWAIT);
125652419Sjulian			if (m == NULL) {
125752419Sjulian				LEAVE(ENOBUFS);
125852419Sjulian			}
125952419Sjulian			wh = mtod(m, struct pppoe_full_hdr *);
126052419Sjulian			bcopy(&sp->pkt_hdr, wh, sizeof(*wh));
126170700Sjulian			NG_FWD_NEW_DATA( error, item, privp->ethernet_hook, m);
126252419Sjulian			privp->packets_out++;
126352419Sjulian			break;
126452419Sjulian			}
126552419Sjulian		case	PPPOE_PRIMED:
126652419Sjulian			/*
126752419Sjulian			 * A PADI packet is being returned by the application
126852419Sjulian			 * that has set up this hook. This indicates that it
126952419Sjulian			 * wants us to offer service.
127052419Sjulian			 */
127152419Sjulian			neg = sp->neg;
127253172Sjulian			if (m->m_len < sizeof(*wh)) {
127353172Sjulian				m = m_pullup(m, sizeof(*wh));
127453172Sjulian				if (m == NULL) {
127553172Sjulian					LEAVE(ENOBUFS);
127653172Sjulian				}
127752419Sjulian			}
127852419Sjulian			wh = mtod(m, struct pppoe_full_hdr *);
127952419Sjulian			ph = &wh->ph;
128052419Sjulian			session = ntohs(wh->ph.sid);
128152419Sjulian			length = ntohs(wh->ph.length);
128252419Sjulian			code = wh->ph.code;
128352443Sjulian			if ( code != PADI_CODE) {
128452443Sjulian				LEAVE(EINVAL);
128552443Sjulian			};
128652443Sjulian			untimeout(pppoe_ticker, hook,
128752443Sjulian				    neg->timeout_handle);
128852419Sjulian
128952419Sjulian			/*
129052419Sjulian			 * This is the first time we hear
129152419Sjulian			 * from the client, so note it's
129252419Sjulian			 * unicast address, replacing the
129353145Sjulian			 * broadcast address.
129452419Sjulian			 */
129552419Sjulian			bcopy(wh->eh.ether_shost,
129652419Sjulian				neg->pkt->pkt_header.eh.ether_dhost,
129752419Sjulian				ETHER_ADDR_LEN);
129852419Sjulian			sp->state = PPPOE_SOFFER;
129952419Sjulian			neg->timeout = 0;
130052419Sjulian			neg->pkt->pkt_header.ph.code = PADO_CODE;
130152419Sjulian
130252419Sjulian			/*
130352419Sjulian			 * start working out the tags to respond with.
130452419Sjulian			 */
130552419Sjulian			uniqtag.hdr.tag_type = PTT_AC_COOKIE;
130652419Sjulian			uniqtag.hdr.tag_len = htons((u_int16_t)sizeof(sp));
130752419Sjulian			uniqtag.data.pointer = sp;
130852419Sjulian			init_tags(sp);
130952419Sjulian			insert_tag(sp, &neg->ac_name.hdr); /* AC_NAME */
131053154Sjulian			if ((tag = get_tag(ph, PTT_SRV_NAME)))
131153154Sjulian				insert_tag(sp, tag);	  /* return service */
131269922Sjulian			/*
131369922Sjulian			 * If we have a NULL service request
131469922Sjulian			 * and have an extra service defined in this hook,
131569922Sjulian			 * then also add a tag for the extra service.
131669922Sjulian			 * XXX this is a hack. eventually we should be able
131769922Sjulian			 * to support advertising many services, not just one
131869922Sjulian			 */
131969922Sjulian			if (((tag == NULL) || (tag->tag_len == 0))
132069922Sjulian			&& (neg->service.hdr.tag_len != 0)) {
132169922Sjulian				insert_tag(sp, &neg->service.hdr); /* SERVICE */
132269922Sjulian			}
132353154Sjulian			if ((tag = get_tag(ph, PTT_HOST_UNIQ)))
132453145Sjulian				insert_tag(sp, tag); /* returned hostunique */
132553154Sjulian			insert_tag(sp, &uniqtag.hdr);
132652419Sjulian			scan_tags(sp, ph);
132752419Sjulian			make_packet(sp);
132852419Sjulian			sendpacket(sp);
132952419Sjulian			break;
133052419Sjulian
133152419Sjulian		/*
133252419Sjulian		 * Packets coming from the hook make no sense
133352419Sjulian		 * to sessions in these states. Throw them away.
133452419Sjulian		 */
133552419Sjulian		case	PPPOE_SINIT:
133652419Sjulian		case	PPPOE_SREQ:
133752419Sjulian		case	PPPOE_SOFFER:
133852419Sjulian		case	PPPOE_SNONE:
133952419Sjulian		case	PPPOE_LISTENING:
134052419Sjulian		case	PPPOE_DEAD:
134152419Sjulian		default:
134252419Sjulian			LEAVE(ENETUNREACH);
134352419Sjulian		}
134452419Sjulian	}
134552419Sjulianquit:
134670914Sjulian	if (item)
134770914Sjulian		NG_FREE_ITEM(item);
134870700Sjulian	NG_FREE_M(m);
134952419Sjulian	return error;
135052419Sjulian}
135152419Sjulian
135252419Sjulian/*
135352419Sjulian * Do local shutdown processing..
135452419Sjulian * If we are a persistant device, we might refuse to go away, and
135552419Sjulian * we'd only remove our links and reset ourself.
135652419Sjulian */
135752419Sjulianstatic int
135870700Sjulianng_pppoe_shutdown(node_p node)
135952419Sjulian{
136070784Sjulian	const priv_p privdata = NG_NODE_PRIVATE(node);
136152419Sjulian
136252443SjulianAAA
136370784Sjulian	NG_NODE_SET_PRIVATE(node, NULL);
136470784Sjulian	NG_NODE_UNREF(privdata->node);
136570870Sjulian	FREE(privdata, M_NETGRAPH_PPPOE);
136652419Sjulian	return (0);
136752419Sjulian}
136852419Sjulian
136952419Sjulian/*
137052419Sjulian * This is called once we've already connected a new hook to the other node.
137152419Sjulian * It gives us a chance to balk at the last minute.
137252419Sjulian */
137352419Sjulianstatic int
137452562Sjulianng_pppoe_connect(hook_p hook)
137552419Sjulian{
137652419Sjulian	/* be really amiable and just say "YUP that's OK by me! " */
137752419Sjulian	return (0);
137852419Sjulian}
137952419Sjulian
138052419Sjulian/*
138152419Sjulian * Hook disconnection
138252419Sjulian *
138353498Sjulian * Clean up all dangling links and information about the session/hook.
138452419Sjulian * For this type, removal of the last link destroys the node
138552419Sjulian */
138652419Sjulianstatic int
138752562Sjulianng_pppoe_disconnect(hook_p hook)
138852419Sjulian{
138970784Sjulian	node_p node = NG_HOOK_NODE(hook);
139070784Sjulian	priv_p privp = NG_NODE_PRIVATE(node);
139152419Sjulian	sessp	sp;
139252563Sjulian	int 	hooks;
139352419Sjulian
139452443SjulianAAA
139570784Sjulian	hooks = NG_NODE_NUMHOOKS(node); /* this one already not counted */
139670784Sjulian	if (NG_HOOK_PRIVATE(hook) == &privp->debug_hook) {
139752419Sjulian		privp->debug_hook = NULL;
139870784Sjulian	} else if (NG_HOOK_PRIVATE(hook) == &privp->ethernet_hook) {
139952419Sjulian		privp->ethernet_hook = NULL;
140070784Sjulian		if (NG_NODE_IS_VALID(node))
140170700Sjulian			ng_rmnode_self(node);
140252419Sjulian	} else {
140370784Sjulian		sp = NG_HOOK_PRIVATE(hook);
140452441Sjulian		if (sp->state != PPPOE_SNONE ) {
140552441Sjulian			pppoe_send_event(sp, NGM_PPPOE_CLOSE);
140652441Sjulian		}
140759728Sjulian		/*
140859728Sjulian		 * According to the spec, if we are connected,
140959728Sjulian		 * we should send a DISC packet if we are shutting down
141059728Sjulian		 * a session.
141159728Sjulian		 */
141252523Sjulian		if ((privp->ethernet_hook)
141352523Sjulian		&& ((sp->state == PPPOE_CONNECTED)
141452523Sjulian		 || (sp->state == PPPOE_NEWCONNECTED))) {
141552523Sjulian			struct mbuf *m;
141652523Sjulian			struct pppoe_full_hdr *wh;
141752523Sjulian			struct pppoe_tag *tag;
141852523Sjulian			int	msglen = strlen(SIGNOFF);
141952523Sjulian			int error = 0;
142052523Sjulian
142152523Sjulian			/* revert the stored header to DISC/PADT mode */
142252523Sjulian		 	wh = &sp->pkt_hdr;
142352523Sjulian			wh->ph.code = PADT_CODE;
142452523Sjulian			wh->eh.ether_type = ETHERTYPE_PPPOE_DISC;
142552523Sjulian
142652523Sjulian			/* generate a packet of that type */
142752523Sjulian			MGETHDR(m, M_DONTWAIT, MT_DATA);
142853498Sjulian			if(m == NULL)
142953498Sjulian				printf("pppoe: Session out of mbufs\n");
143053498Sjulian			else {
143153498Sjulian				m->m_pkthdr.rcvif = NULL;
143253498Sjulian				m->m_pkthdr.len = m->m_len = sizeof(*wh);
143353498Sjulian				bcopy((caddr_t)wh, mtod(m, caddr_t),
143453498Sjulian				    sizeof(*wh));
143553498Sjulian				/*
143653498Sjulian				 * Add a General error message and adjust
143753498Sjulian				 * sizes
143853498Sjulian				 */
143953498Sjulian				wh = mtod(m, struct pppoe_full_hdr *);
144053498Sjulian				tag = wh->ph.tag;
144153498Sjulian				tag->tag_type = PTT_GEN_ERR;
144253498Sjulian				tag->tag_len = htons((u_int16_t)msglen);
144353498Sjulian				strncpy(tag->tag_data, SIGNOFF, msglen);
144453498Sjulian				m->m_pkthdr.len = (m->m_len += sizeof(*tag) +
144553498Sjulian				    msglen);
144653498Sjulian				wh->ph.length = htons(sizeof(*tag) + msglen);
144770700Sjulian				NG_SEND_DATA_ONLY(error,
144870700Sjulian					privp->ethernet_hook, m);
144953498Sjulian			}
145052523Sjulian		}
145159728Sjulian		/*
145263138Sasmodai		 * As long as we have somewhere to store the timeout handle,
145359728Sjulian		 * we may have a timeout pending.. get rid of it.
145459728Sjulian		 */
145552443Sjulian		if (sp->neg) {
145652443Sjulian			untimeout(pppoe_ticker, hook, sp->neg->timeout_handle);
145752443Sjulian			if (sp->neg->m)
145852443Sjulian				m_freem(sp->neg->m);
145970870Sjulian			FREE(sp->neg, M_NETGRAPH_PPPOE);
146052443Sjulian		}
146170870Sjulian		FREE(sp, M_NETGRAPH_PPPOE);
146270784Sjulian		NG_HOOK_SET_PRIVATE(hook, NULL);
146352564Sjulian		/* work out how many session hooks there are */
146452563Sjulian		/* Node goes away on last session hook removal */
146552563Sjulian		if (privp->ethernet_hook) hooks -= 1;
146652564Sjulian		if (privp->debug_hook) hooks -= 1;
146752419Sjulian	}
146870784Sjulian	if ((NG_NODE_NUMHOOKS(node) == 0)
146970784Sjulian	&& (NG_NODE_IS_VALID(node)))
147070700Sjulian		ng_rmnode_self(node);
147152419Sjulian	return (0);
147252419Sjulian}
147352419Sjulian
147452419Sjulian/*
147552419Sjulian * timeouts come here.
147652419Sjulian */
147752419Sjulianstatic void
147852419Sjulianpppoe_ticker(void *arg)
147952419Sjulian{
148052419Sjulian	int s = splnet();
148152419Sjulian	hook_p hook = arg;
148270784Sjulian	sessp	sp = NG_HOOK_PRIVATE(hook);
148352419Sjulian	negp	neg = sp->neg;
148452419Sjulian	int	error = 0;
148552419Sjulian	struct mbuf *m0 = NULL;
148670784Sjulian	priv_p privp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
148752419Sjulian
148852443SjulianAAA
148952419Sjulian	switch(sp->state) {
149052419Sjulian		/*
149152419Sjulian		 * resend the last packet, using an exponential backoff.
149252419Sjulian		 * After a period of time, stop growing the backoff,
149353145Sjulian		 * and either leave it, or revert to the start.
149452419Sjulian		 */
149552419Sjulian	case	PPPOE_SINIT:
149652419Sjulian	case	PPPOE_SREQ:
149752419Sjulian		/* timeouts on these produce resends */
149852419Sjulian		m0 = m_copypacket(sp->neg->m, M_DONTWAIT);
149970700Sjulian		NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0);
150052419Sjulian		neg->timeout_handle = timeout(pppoe_ticker,
150152419Sjulian					hook, neg->timeout * hz);
150252419Sjulian		if ((neg->timeout <<= 1) > PPPOE_TIMEOUT_LIMIT) {
150352419Sjulian			if (sp->state == PPPOE_SREQ) {
150452419Sjulian				/* revert to SINIT mode */
150552441Sjulian				pppoe_start(sp);
150652419Sjulian			} else {
150752419Sjulian				neg->timeout = PPPOE_TIMEOUT_LIMIT;
150852419Sjulian			}
150952419Sjulian		}
151052419Sjulian		break;
151152419Sjulian	case	PPPOE_PRIMED:
151252419Sjulian	case	PPPOE_SOFFER:
151352419Sjulian		/* a timeout on these says "give up" */
151452419Sjulian		ng_destroy_hook(hook);
151552419Sjulian		break;
151652419Sjulian	default:
151752419Sjulian		/* timeouts have no meaning in other states */
151852419Sjulian		printf("pppoe: unexpected timeout\n");
151952419Sjulian	}
152052419Sjulian	splx(s);
152152419Sjulian}
152252419Sjulian
152352419Sjulian
152452419Sjulianstatic void
152552419Sjuliansendpacket(sessp sp)
152652419Sjulian{
152752419Sjulian	int	error = 0;
152852419Sjulian	struct mbuf *m0 = NULL;
152952419Sjulian	hook_p hook = sp->hook;
153052419Sjulian	negp	neg = sp->neg;
153170784Sjulian	priv_p	privp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
153252419Sjulian
153352443SjulianAAA
153452419Sjulian	switch(sp->state) {
153552419Sjulian	case	PPPOE_LISTENING:
153652419Sjulian	case	PPPOE_DEAD:
153752419Sjulian	case	PPPOE_SNONE:
153852419Sjulian	case	PPPOE_CONNECTED:
153952448Sjulian		printf("pppoe: sendpacket: unexpected state\n");
154052419Sjulian		break;
154152419Sjulian
154253498Sjulian	case	PPPOE_NEWCONNECTED:
154353498Sjulian		/* send the PADS without a timeout - we're now connected */
154453498Sjulian		m0 = m_copypacket(sp->neg->m, M_DONTWAIT);
154570700Sjulian		NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0);
154653498Sjulian		break;
154753498Sjulian
154852419Sjulian	case	PPPOE_PRIMED:
154952419Sjulian		/* No packet to send, but set up the timeout */
155052419Sjulian		neg->timeout_handle = timeout(pppoe_ticker,
155152419Sjulian					hook, PPPOE_OFFER_TIMEOUT * hz);
155252419Sjulian		break;
155352419Sjulian
155452419Sjulian	case	PPPOE_SOFFER:
155552419Sjulian		/*
155652419Sjulian		 * send the offer but if they don't respond
155752419Sjulian		 * in PPPOE_OFFER_TIMEOUT seconds, forget about it.
155852419Sjulian		 */
155952419Sjulian		m0 = m_copypacket(sp->neg->m, M_DONTWAIT);
156070700Sjulian		NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0);
156152419Sjulian		neg->timeout_handle = timeout(pppoe_ticker,
156252419Sjulian					hook, PPPOE_OFFER_TIMEOUT * hz);
156352419Sjulian		break;
156452419Sjulian
156552419Sjulian	case	PPPOE_SINIT:
156652419Sjulian	case	PPPOE_SREQ:
156752419Sjulian		m0 = m_copypacket(sp->neg->m, M_DONTWAIT);
156870700Sjulian		NG_SEND_DATA_ONLY( error, privp->ethernet_hook, m0);
156953979Sjulian		neg->timeout_handle = timeout(pppoe_ticker, hook,
157053979Sjulian					(hz * PPPOE_INITIAL_TIMEOUT));
157153979Sjulian		neg->timeout = PPPOE_INITIAL_TIMEOUT * 2;
157252419Sjulian		break;
157352419Sjulian
157452419Sjulian	default:
157552419Sjulian		error = EINVAL;
157652419Sjulian		printf("pppoe: timeout: bad state\n");
157752419Sjulian	}
157852419Sjulian	/* return (error); */
157952419Sjulian}
158052419Sjulian
158152419Sjulian/*
158252419Sjulian * Parse an incoming packet to see if any tags should be copied to the
158353145Sjulian * output packet. Don't do any tags that have been handled in the main
158453145Sjulian * state machine.
158552419Sjulian */
158652419Sjulianstatic struct pppoe_tag*
158752419Sjulianscan_tags(sessp	sp, struct pppoe_hdr* ph)
158852419Sjulian{
158952419Sjulian	char *end = (char *)next_tag(ph);
159052419Sjulian	char *ptn;
159152419Sjulian	struct pppoe_tag *pt = &ph->tag[0];
159252419Sjulian	/*
159352419Sjulian	 * Keep processing tags while a tag header will still fit.
159452419Sjulian	 */
159552443SjulianAAA
159652419Sjulian	while((char*)(pt + 1) <= end) {
159752419Sjulian		/*
159852419Sjulian		 * If the tag data would go past the end of the packet, abort.
159952419Sjulian		 */
160052419Sjulian		ptn = (((char *)(pt + 1)) + ntohs(pt->tag_len));
160152419Sjulian		if(ptn > end)
160252419Sjulian			return NULL;
160352419Sjulian
160452419Sjulian		switch (pt->tag_type) {
160552419Sjulian		case	PTT_RELAY_SID:
160652419Sjulian			insert_tag(sp, pt);
160752419Sjulian			break;
160852419Sjulian		case	PTT_EOL:
160952419Sjulian			return NULL;
161052419Sjulian		case	PTT_SRV_NAME:
161152419Sjulian		case	PTT_AC_NAME:
161252419Sjulian		case	PTT_HOST_UNIQ:
161352419Sjulian		case	PTT_AC_COOKIE:
161452419Sjulian		case	PTT_VENDOR:
161552419Sjulian		case	PTT_SRV_ERR:
161652419Sjulian		case	PTT_SYS_ERR:
161752419Sjulian		case	PTT_GEN_ERR:
161852419Sjulian			break;
161952419Sjulian		}
162052419Sjulian		pt = (struct pppoe_tag*)ptn;
162152419Sjulian	}
162252419Sjulian	return NULL;
162352419Sjulian}
162452419Sjulian
162552441Sjulianstatic	int
162652441Sjulianpppoe_send_event(sessp sp, enum cmd cmdid)
162752441Sjulian{
162852441Sjulian	int error;
162952441Sjulian	struct ng_mesg *msg;
163052562Sjulian	struct ngpppoe_sts *sts;
163152441Sjulian
163252443SjulianAAA
163368845Sbrian	NG_MKMESSAGE(msg, NGM_PPPOE_COOKIE, cmdid,
163452562Sjulian			sizeof(struct ngpppoe_sts), M_NOWAIT);
163569922Sjulian	if (msg == NULL)
163669922Sjulian		return (ENOMEM);
163752562Sjulian	sts = (struct ngpppoe_sts *)msg->data;
163870784Sjulian	strncpy(sts->hook, NG_HOOK_NAME(sp->hook), NG_HOOKLEN + 1);
163970784Sjulian	NG_SEND_MSG_ID(error, NG_HOOK_NODE(sp->hook), msg, sp->creator, NULL);
164052441Sjulian	return (error);
164152441Sjulian}
1642