ng_cisco.c revision 174118
1/*
2 * ng_cisco.c
3 */
4
5/*-
6 * Copyright (c) 1996-1999 Whistle Communications, Inc.
7 * All rights reserved.
8 *
9 * Subject to the following obligations and disclaimer of warranty, use and
10 * redistribution of this software, in source or object code forms, with or
11 * without modifications are expressly permitted by Whistle Communications;
12 * provided, however, that:
13 * 1. Any and all reproductions of the source or object code must include the
14 *    copyright notice above and the following disclaimer of warranties; and
15 * 2. No rights are granted, in any manner or form, to use Whistle
16 *    Communications, Inc. trademarks, including the mark "WHISTLE
17 *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
18 *    such appears in the above copyright notice or in the software.
19 *
20 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
21 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
22 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
23 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
25 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
26 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
27 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
28 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
29 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
30 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
31 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
36 * OF SUCH DAMAGE.
37 *
38 * Author: Julian Elischer <julian@freebsd.org>
39 *
40 * $FreeBSD: head/sys/netgraph/ng_cisco.c 174118 2007-11-30 23:27:39Z julian $
41 * $Whistle: ng_cisco.c,v 1.25 1999/11/01 09:24:51 julian Exp $
42 */
43
44#include <sys/param.h>
45#include <sys/systm.h>
46#include <sys/errno.h>
47#include <sys/kernel.h>
48#include <sys/socket.h>
49#include <sys/malloc.h>
50#include <sys/mbuf.h>
51#include <sys/syslog.h>
52
53#include <net/if.h>
54
55#include <netinet/in.h>
56#include <netinet/if_ether.h>
57
58#include <netatalk/at.h>
59
60#include <netipx/ipx.h>
61#include <netipx/ipx_if.h>
62
63#include <netgraph/ng_message.h>
64#include <netgraph/netgraph.h>
65#include <netgraph/ng_parse.h>
66#include <netgraph/ng_cisco.h>
67
68#define	CISCO_MULTICAST		0x8f	/* Cisco multicast address */
69#define	CISCO_UNICAST		0x0f	/* Cisco unicast address */
70#define	CISCO_KEEPALIVE		0x8035	/* Cisco keepalive protocol */
71#define	CISCO_ADDR_REQ		0	/* Cisco address request */
72#define	CISCO_ADDR_REPLY	1	/* Cisco address reply */
73#define	CISCO_KEEPALIVE_REQ	2	/* Cisco keepalive request */
74
75#define KEEPALIVE_SECS		10
76
77struct cisco_header {
78	u_char  address;
79	u_char  control;
80	u_short protocol;
81};
82
83#define	CISCO_HEADER_LEN	sizeof (struct cisco_header)
84
85struct cisco_packet {
86	u_long  type;
87	u_long  par1;
88	u_long  par2;
89	u_short rel;
90	u_short time0;
91	u_short time1;
92};
93
94#define	CISCO_PACKET_LEN (sizeof(struct cisco_packet))
95
96struct protoent {
97	hook_p  hook;		/* the hook for this proto */
98	u_short af;		/* address family, -1 = downstream */
99};
100
101struct cisco_priv {
102	u_long  local_seq;
103	u_long  remote_seq;
104	u_long  seqRetries;	/* how many times we've been here throwing out
105				 * the same sequence number without ack */
106	node_p  node;
107	struct callout handle;
108	struct protoent downstream;
109	struct protoent inet;		/* IP information */
110	struct in_addr localip;
111	struct in_addr localmask;
112	struct protoent inet6;		/* IPv6 information */
113	struct protoent atalk;		/* AppleTalk information */
114	struct protoent ipx;		/* IPX information */
115};
116typedef struct cisco_priv *sc_p;
117
118/* Netgraph methods */
119static ng_constructor_t		cisco_constructor;
120static ng_rcvmsg_t		cisco_rcvmsg;
121static ng_shutdown_t		cisco_shutdown;
122static ng_newhook_t		cisco_newhook;
123static ng_rcvdata_t		cisco_rcvdata;
124static ng_disconnect_t		cisco_disconnect;
125
126/* Other functions */
127static int	cisco_input(sc_p sc, item_p item);
128static void	cisco_keepalive(node_p node, hook_p hook, void *arg1, int arg2);
129static int	cisco_send(sc_p sc, int type, long par1, long par2);
130static void	cisco_notify(sc_p sc, uint32_t cmd);
131
132/* Parse type for struct ng_cisco_ipaddr */
133static const struct ng_parse_struct_field ng_cisco_ipaddr_type_fields[]
134	= NG_CISCO_IPADDR_TYPE_INFO;
135static const struct ng_parse_type ng_cisco_ipaddr_type = {
136	&ng_parse_struct_type,
137	&ng_cisco_ipaddr_type_fields
138};
139
140/* Parse type for struct ng_async_stat */
141static const struct ng_parse_struct_field ng_cisco_stats_type_fields[]
142	= NG_CISCO_STATS_TYPE_INFO;
143static const struct ng_parse_type ng_cisco_stats_type = {
144	&ng_parse_struct_type,
145	&ng_cisco_stats_type_fields
146};
147
148/* List of commands and how to convert arguments to/from ASCII */
149static const struct ng_cmdlist ng_cisco_cmdlist[] = {
150	{
151	  NGM_CISCO_COOKIE,
152	  NGM_CISCO_SET_IPADDR,
153	  "setipaddr",
154	  &ng_cisco_ipaddr_type,
155	  NULL
156	},
157	{
158	  NGM_CISCO_COOKIE,
159	  NGM_CISCO_GET_IPADDR,
160	  "getipaddr",
161	  NULL,
162	  &ng_cisco_ipaddr_type
163	},
164	{
165	  NGM_CISCO_COOKIE,
166	  NGM_CISCO_GET_STATUS,
167	  "getstats",
168	  NULL,
169	  &ng_cisco_stats_type
170	},
171	{ 0 }
172};
173
174/* Node type */
175static struct ng_type typestruct = {
176	.version =	NG_ABI_VERSION,
177	.name =		NG_CISCO_NODE_TYPE,
178	.constructor =	cisco_constructor,
179	.rcvmsg =	cisco_rcvmsg,
180	.shutdown =	cisco_shutdown,
181	.newhook =	cisco_newhook,
182	.rcvdata =	cisco_rcvdata,
183	.disconnect =	cisco_disconnect,
184	.cmdlist =	ng_cisco_cmdlist,
185};
186NETGRAPH_INIT(cisco, &typestruct);
187
188/*
189 * Node constructor
190 */
191static int
192cisco_constructor(node_p node)
193{
194	sc_p sc;
195
196	MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_NOWAIT | M_ZERO);
197	if (sc == NULL)
198		return (ENOMEM);
199
200	ng_callout_init(&sc->handle);
201	NG_NODE_SET_PRIVATE(node, sc);
202	sc->node = node;
203
204	/* Initialise the varous protocol hook holders */
205	sc->downstream.af = 0xffff;
206	sc->inet.af = AF_INET;
207	sc->inet6.af = AF_INET6;
208	sc->atalk.af = AF_APPLETALK;
209	sc->ipx.af = AF_IPX;
210	return (0);
211}
212
213/*
214 * Check new hook
215 */
216static int
217cisco_newhook(node_p node, hook_p hook, const char *name)
218{
219	const sc_p sc = NG_NODE_PRIVATE(node);
220
221	if (strcmp(name, NG_CISCO_HOOK_DOWNSTREAM) == 0) {
222		sc->downstream.hook = hook;
223		NG_HOOK_SET_PRIVATE(hook, &sc->downstream);
224
225		/* Start keepalives */
226		ng_callout(&sc->handle, node, NULL, (hz * KEEPALIVE_SECS),
227		    &cisco_keepalive, (void *)sc, 0);
228	} else if (strcmp(name, NG_CISCO_HOOK_INET) == 0) {
229		sc->inet.hook = hook;
230		NG_HOOK_SET_PRIVATE(hook, &sc->inet);
231	} else if (strcmp(name, NG_CISCO_HOOK_INET6) == 0) {
232		sc->inet6.hook = hook;
233		NG_HOOK_SET_PRIVATE(hook, &sc->inet6);
234	} else if (strcmp(name, NG_CISCO_HOOK_APPLETALK) == 0) {
235		sc->atalk.hook = hook;
236		NG_HOOK_SET_PRIVATE(hook, &sc->atalk);
237	} else if (strcmp(name, NG_CISCO_HOOK_IPX) == 0) {
238		sc->ipx.hook = hook;
239		NG_HOOK_SET_PRIVATE(hook, &sc->ipx);
240	} else if (strcmp(name, NG_CISCO_HOOK_DEBUG) == 0) {
241		NG_HOOK_SET_PRIVATE(hook, NULL);	/* unimplemented */
242	} else
243		return (EINVAL);
244	return 0;
245}
246
247/*
248 * Receive control message.
249 */
250static int
251cisco_rcvmsg(node_p node, item_p item, hook_p lasthook)
252{
253	struct ng_mesg *msg;
254	const sc_p sc = NG_NODE_PRIVATE(node);
255	struct ng_mesg *resp = NULL;
256	int error = 0;
257
258	NGI_GET_MSG(item, msg);
259	switch (msg->header.typecookie) {
260	case NGM_GENERIC_COOKIE:
261		switch (msg->header.cmd) {
262		case NGM_TEXT_STATUS:
263		    {
264			char *arg;
265			int pos;
266
267			NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_NOWAIT);
268			if (resp == NULL) {
269				error = ENOMEM;
270				break;
271			}
272			arg = (char *) resp->data;
273			pos = sprintf(arg,
274			  "keepalive period: %d sec; ", KEEPALIVE_SECS);
275			pos += sprintf(arg + pos,
276			  "unacknowledged keepalives: %ld", sc->seqRetries);
277			resp->header.arglen = pos + 1;
278			break;
279		    }
280		default:
281			error = EINVAL;
282			break;
283		}
284		break;
285	case NGM_CISCO_COOKIE:
286		switch (msg->header.cmd) {
287		case NGM_CISCO_GET_IPADDR:	/* could be a late reply! */
288			if ((msg->header.flags & NGF_RESP) == 0) {
289				struct in_addr *ips;
290
291				NG_MKRESPONSE(resp, msg,
292				    2 * sizeof(*ips), M_NOWAIT);
293				if (!resp) {
294					error = ENOMEM;
295					break;
296				}
297				ips = (struct in_addr *) resp->data;
298				ips[0] = sc->localip;
299				ips[1] = sc->localmask;
300				break;
301			}
302			/* FALLTHROUGH */	/* ...if it's a reply */
303		case NGM_CISCO_SET_IPADDR:
304		    {
305			struct in_addr *const ips = (struct in_addr *)msg->data;
306
307			if (msg->header.arglen < 2 * sizeof(*ips)) {
308				error = EINVAL;
309				break;
310			}
311			sc->localip = ips[0];
312			sc->localmask = ips[1];
313			break;
314		    }
315		case NGM_CISCO_GET_STATUS:
316		    {
317			struct ng_cisco_stats *stat;
318
319			NG_MKRESPONSE(resp, msg, sizeof(*stat), M_NOWAIT);
320			if (!resp) {
321				error = ENOMEM;
322				break;
323			}
324			stat = (struct ng_cisco_stats *)resp->data;
325			stat->seqRetries = sc->seqRetries;
326			stat->keepAlivePeriod = KEEPALIVE_SECS;
327			break;
328		    }
329		default:
330			error = EINVAL;
331			break;
332		}
333		break;
334	default:
335		error = EINVAL;
336		break;
337	}
338	NG_RESPOND_MSG(error, node, item, resp);
339	NG_FREE_MSG(msg);
340	return (error);
341}
342
343/*
344 * Receive data
345 */
346static int
347cisco_rcvdata(hook_p hook, item_p item)
348{
349	const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
350	struct protoent *pep;
351	struct cisco_header *h;
352	struct mbuf *m;
353	int error = 0;
354
355	if ((pep = NG_HOOK_PRIVATE(hook)) == NULL)
356		goto out;
357
358	/* If it came from our downlink, deal with it separately */
359	if (pep->af == 0xffff)
360		return (cisco_input(sc, item));
361
362	/* OK so it came from a protocol, heading out. Prepend general data
363	   packet header. For now, IP,IPX only  */
364	m = NGI_M(item); /* still associated with item */
365	M_PREPEND(m, CISCO_HEADER_LEN, M_DONTWAIT);
366	if (!m) {
367		error = ENOBUFS;
368		goto out;
369	}
370	h = mtod(m, struct cisco_header *);
371	h->address = CISCO_UNICAST;
372	h->control = 0;
373
374	switch (pep->af) {
375	case AF_INET:		/* Internet Protocol */
376		h->protocol = htons(ETHERTYPE_IP);
377		break;
378	case AF_INET6:
379		h->protocol = htons(ETHERTYPE_IPV6);
380		break;
381	case AF_APPLETALK:	/* AppleTalk Protocol */
382		h->protocol = htons(ETHERTYPE_AT);
383		break;
384	case AF_IPX:		/* Novell IPX Protocol */
385		h->protocol = htons(ETHERTYPE_IPX);
386		break;
387	default:
388		error = EAFNOSUPPORT;
389		goto out;
390	}
391
392	/* Send it */
393	NG_FWD_NEW_DATA(error, item,  sc->downstream.hook, m);
394	return (error);
395
396out:
397	NG_FREE_ITEM(item);
398	return (error);
399}
400
401/*
402 * Shutdown node
403 */
404static int
405cisco_shutdown(node_p node)
406{
407	const sc_p sc = NG_NODE_PRIVATE(node);
408
409	NG_NODE_SET_PRIVATE(node, NULL);
410	NG_NODE_UNREF(sc->node);
411	FREE(sc, M_NETGRAPH);
412	return (0);
413}
414
415/*
416 * Disconnection of a hook
417 *
418 * For this type, removal of the last link destroys the node
419 */
420static int
421cisco_disconnect(hook_p hook)
422{
423	const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
424	struct protoent *pep;
425
426	/* Check it's not the debug hook */
427	if ((pep = NG_HOOK_PRIVATE(hook))) {
428		pep->hook = NULL;
429		if (pep->af == 0xffff)
430			/* If it is the downstream hook, stop the timers */
431			ng_uncallout(&sc->handle, NG_HOOK_NODE(hook));
432	}
433
434	/* If no more hooks, remove the node */
435	if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0)
436	&& (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
437		ng_rmnode_self(NG_HOOK_NODE(hook));
438	return (0);
439}
440
441/*
442 * Receive data
443 */
444static int
445cisco_input(sc_p sc, item_p item)
446{
447	const struct cisco_header *h;
448	struct cisco_header hdrbuf;
449	struct protoent *pep;
450	struct mbuf *m;
451	int error = 0;
452
453	/* Get data */
454	m = NGI_M(item);
455
456	/* Sanity check header length */
457	if (m->m_pkthdr.len < sizeof(*h)) {
458		error = EINVAL;
459		goto drop;
460	}
461
462	/* Get cisco header */
463	if (m->m_len >= sizeof(*h))			/* the common case */
464		h = mtod(m, const struct cisco_header *);
465	else {
466		m_copydata(m, 0, sizeof(*h), (caddr_t)&hdrbuf);
467		h = &hdrbuf;
468	}
469	m_adj(m, sizeof(*h));
470
471	/* Check header address */
472	switch (h->address) {
473	default:		/* Invalid Cisco packet. */
474		goto drop;
475	case CISCO_UNICAST:
476	case CISCO_MULTICAST:
477		/* Don't check the control field here (RFC 1547). */
478		switch (ntohs(h->protocol)) {
479		default:
480			goto drop;
481		case CISCO_KEEPALIVE:
482		    {
483			const struct cisco_packet *p;
484			struct cisco_packet pktbuf;
485
486			/* Sanity check packet length */
487			if (m->m_pkthdr.len < sizeof(*p)) {
488				error = EINVAL;
489				goto drop;
490			}
491
492			/* Get cisco packet */
493			if (m->m_len >= sizeof(*p))	/* the common case */
494				p = mtod(m, const struct cisco_packet *);
495			else {
496				m_copydata(m, 0, sizeof(*p), (caddr_t)&pktbuf);
497				p = &pktbuf;
498			}
499
500			/* Check packet type */
501			switch (ntohl(p->type)) {
502			default:
503				log(LOG_WARNING,
504				    "cisco: unknown cisco packet type: 0x%lx\n",
505				       (long)ntohl(p->type));
506				break;
507			case CISCO_ADDR_REPLY:
508				/* Reply on address request, ignore */
509				break;
510			case CISCO_KEEPALIVE_REQ:
511				sc->remote_seq = ntohl(p->par1);
512				if (sc->local_seq == ntohl(p->par2)) {
513					sc->local_seq++;
514					if (sc->seqRetries > 1)
515						cisco_notify(sc, NGM_LINK_IS_UP);
516					sc->seqRetries = 0;
517				}
518				break;
519			case CISCO_ADDR_REQ:
520			    {
521				struct ng_mesg *msg;
522				int dummy_error = 0;
523
524				/* Ask inet peer for IP address information */
525				if (sc->inet.hook == NULL)
526					goto nomsg;
527				NG_MKMESSAGE(msg, NGM_CISCO_COOKIE,
528				    NGM_CISCO_GET_IPADDR, 0, M_NOWAIT);
529				if (msg == NULL)
530					goto nomsg;
531				NG_SEND_MSG_HOOK(dummy_error,
532				    sc->node, msg, sc->inet.hook, 0);
533		/*
534		 * XXX Now maybe we should set a flag telling
535		 * our receiver to send this message when the response comes in
536		 * instead of now when the data may be bad.
537		 */
538		nomsg:
539				/* Send reply to peer device */
540				error = cisco_send(sc, CISCO_ADDR_REPLY,
541					    ntohl(sc->localip.s_addr),
542					    ntohl(sc->localmask.s_addr));
543				break;
544			    }
545			}
546			goto drop;
547		    }
548		case ETHERTYPE_IP:
549			pep = &sc->inet;
550			break;
551		case ETHERTYPE_IPV6:
552			pep = &sc->inet6;
553			break;
554		case ETHERTYPE_AT:
555			pep = &sc->atalk;
556			break;
557		case ETHERTYPE_IPX:
558			pep = &sc->ipx;
559			break;
560		}
561		break;
562	}
563
564	/* Drop if payload is empty */
565	if (m->m_pkthdr.len == 0) {
566		error = EINVAL;
567		goto drop;
568	}
569
570	/* Send it on */
571	if (pep->hook == NULL)
572		goto drop;
573	NG_FWD_NEW_DATA(error, item, pep->hook, m);
574	return (error);
575
576drop:
577	NG_FREE_ITEM(item);
578	return (error);
579}
580
581
582/*
583 * Send keepalive packets, every 10 seconds.
584 */
585static void
586cisco_keepalive(node_p node, hook_p hook, void *arg1, int arg2)
587{
588	const sc_p sc = arg1;
589
590	cisco_send(sc, CISCO_KEEPALIVE_REQ, sc->local_seq, sc->remote_seq);
591	if (sc->seqRetries++ > 1)
592		cisco_notify(sc, NGM_LINK_IS_DOWN);
593	ng_callout(&sc->handle, node, NULL, (hz * KEEPALIVE_SECS),
594	    &cisco_keepalive, (void *)sc, 0);
595}
596
597/*
598 * Send Cisco keepalive packet.
599 */
600static int
601cisco_send(sc_p sc, int type, long par1, long par2)
602{
603	struct cisco_header *h;
604	struct cisco_packet *ch;
605	struct mbuf *m;
606	struct timeval time;
607	u_long  t;
608	int     error = 0;
609
610	getmicrouptime(&time);
611
612	MGETHDR(m, M_DONTWAIT, MT_DATA);
613	if (!m)
614		return (ENOBUFS);
615
616	t = time.tv_sec * 1000 + time.tv_usec / 1000;
617	m->m_pkthdr.len = m->m_len = CISCO_HEADER_LEN + CISCO_PACKET_LEN;
618	m->m_pkthdr.rcvif = 0;
619
620	h = mtod(m, struct cisco_header *);
621	h->address = CISCO_MULTICAST;
622	h->control = 0;
623	h->protocol = htons(CISCO_KEEPALIVE);
624
625	ch = (struct cisco_packet *) (h + 1);
626	ch->type = htonl(type);
627	ch->par1 = htonl(par1);
628	ch->par2 = htonl(par2);
629	ch->rel = -1;
630	ch->time0 = htons((u_short) (t >> 16));
631	ch->time1 = htons((u_short) t);
632
633	NG_SEND_DATA_ONLY(error, sc->downstream.hook, m);
634	return (error);
635}
636
637/*
638 * Send linkstate to upstream node.
639 */
640static void
641cisco_notify(sc_p sc, uint32_t cmd)
642{
643	struct ng_mesg *msg;
644	int dummy_error = 0;
645
646	if (sc->inet.hook == NULL) /* nothing to notify */
647		return;
648
649	NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT);
650	if (msg != NULL)
651		NG_SEND_MSG_HOOK(dummy_error, sc->node, msg, sc->inet.hook, 0);
652}
653