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