Initial import from FreeBSD RELENG_4:
[dragonfly.git] / sys / netgraph / cisco / ng_cisco.c
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@freebsd.org>
38  *
39  * $FreeBSD: src/sys/netgraph/ng_cisco.c,v 1.4.2.6 2002/07/02 23:44:02 archie Exp $
40  * $Whistle: ng_cisco.c,v 1.25 1999/11/01 09:24:51 julian Exp $
41  */
42
43 #include <sys/param.h>
44 #include <sys/systm.h>
45 #include <sys/errno.h>
46 #include <sys/kernel.h>
47 #include <sys/socket.h>
48 #include <sys/malloc.h>
49 #include <sys/mbuf.h>
50 #include <sys/syslog.h>
51
52 #include <net/if.h>
53
54 #include <netinet/in.h>
55 #include <netinet/if_ether.h>
56
57 #include <netatalk/at.h>
58
59 #include <netipx/ipx.h>
60 #include <netipx/ipx_if.h>
61
62 #include <netgraph/ng_message.h>
63 #include <netgraph/netgraph.h>
64 #include <netgraph/ng_parse.h>
65 #include <netgraph/ng_cisco.h>
66
67 #define CISCO_MULTICAST         0x8f    /* Cisco multicast address */
68 #define CISCO_UNICAST           0x0f    /* Cisco unicast address */
69 #define CISCO_KEEPALIVE         0x8035  /* Cisco keepalive protocol */
70 #define CISCO_ADDR_REQ          0       /* Cisco address request */
71 #define CISCO_ADDR_REPLY        1       /* Cisco address reply */
72 #define CISCO_KEEPALIVE_REQ     2       /* Cisco keepalive request */
73
74 #define KEEPALIVE_SECS          10
75
76 struct cisco_header {
77         u_char  address;
78         u_char  control;
79         u_short protocol;
80 };
81
82 #define CISCO_HEADER_LEN          sizeof (struct cisco_header)
83
84 struct cisco_packet {
85         u_long  type;
86         u_long  par1;
87         u_long  par2;
88         u_short rel;
89         u_short time0;
90         u_short time1;
91 };
92
93 #define CISCO_PACKET_LEN (sizeof(struct cisco_packet))
94
95 struct protoent {
96         hook_p  hook;           /* the hook for this proto */
97         u_short af;             /* address family, -1 = downstream */
98 };
99
100 struct cisco_priv {
101         u_long  local_seq;
102         u_long  remote_seq;
103         u_long  seqRetries;     /* how many times we've been here throwing out
104                                  * the same sequence number without ack */
105         node_p  node;
106         struct callout_handle handle;
107         struct protoent downstream;
108         struct protoent inet;           /* IP information */
109         struct in_addr localip;
110         struct in_addr localmask;
111         struct protoent inet6;          /* IPv6 information */
112         struct protoent atalk;          /* AppleTalk information */
113         struct protoent ipx;            /* IPX information */
114 };
115 typedef struct cisco_priv *sc_p;
116
117 /* Netgraph methods */
118 static ng_constructor_t         cisco_constructor;
119 static ng_rcvmsg_t              cisco_rcvmsg;
120 static ng_shutdown_t            cisco_rmnode;
121 static ng_newhook_t             cisco_newhook;
122 static ng_rcvdata_t             cisco_rcvdata;
123 static ng_disconnect_t          cisco_disconnect;
124
125 /* Other functions */
126 static int      cisco_input(sc_p sc, struct mbuf *m, meta_p meta);
127 static void     cisco_keepalive(void *arg);
128 static int      cisco_send(sc_p sc, int type, long par1, long par2);
129
130 /* Parse type for struct ng_cisco_ipaddr */
131 static const struct ng_parse_struct_field ng_cisco_ipaddr_type_fields[]
132         = NG_CISCO_IPADDR_TYPE_INFO;
133 static const struct ng_parse_type ng_cisco_ipaddr_type = {
134         &ng_parse_struct_type,
135         &ng_cisco_ipaddr_type_fields
136 };
137
138 /* Parse type for struct ng_async_stat */
139 static const struct ng_parse_struct_field ng_cisco_stats_type_fields[]
140         = NG_CISCO_STATS_TYPE_INFO;
141 static const struct ng_parse_type ng_cisco_stats_type = {
142         &ng_parse_struct_type,
143         &ng_cisco_stats_type_fields
144 };
145
146 /* List of commands and how to convert arguments to/from ASCII */
147 static const struct ng_cmdlist ng_cisco_cmdlist[] = {
148         {
149           NGM_CISCO_COOKIE,
150           NGM_CISCO_SET_IPADDR,
151           "setipaddr",
152           &ng_cisco_ipaddr_type,
153           NULL
154         },
155         {
156           NGM_CISCO_COOKIE,
157           NGM_CISCO_GET_IPADDR,
158           "getipaddr",
159           NULL,
160           &ng_cisco_ipaddr_type
161         },
162         {
163           NGM_CISCO_COOKIE,
164           NGM_CISCO_GET_STATUS,
165           "getstats",
166           NULL,
167           &ng_cisco_stats_type
168         },
169         { 0 }
170 };
171
172 /* Node type */
173 static struct ng_type typestruct = {
174         NG_VERSION,
175         NG_CISCO_NODE_TYPE,
176         NULL,
177         cisco_constructor,
178         cisco_rcvmsg,
179         cisco_rmnode,
180         cisco_newhook,
181         NULL,
182         NULL,
183         cisco_rcvdata,
184         cisco_rcvdata,
185         cisco_disconnect,
186         ng_cisco_cmdlist
187 };
188 NETGRAPH_INIT(cisco, &typestruct);
189
190 /*
191  * Node constructor
192  */
193 static int
194 cisco_constructor(node_p *nodep)
195 {
196         sc_p sc;
197         int error = 0;
198
199         MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_NOWAIT);
200         if (sc == NULL)
201                 return (ENOMEM);
202         bzero(sc, sizeof(struct cisco_priv));
203
204         callout_handle_init(&sc->handle);
205         if ((error = ng_make_node_common(&typestruct, nodep))) {
206                 FREE(sc, M_NETGRAPH);
207                 return (error);
208         }
209         (*nodep)->private = sc;
210         sc->node = *nodep;
211
212         /* Initialise the varous protocol hook holders */
213         sc->downstream.af = 0xffff;
214         sc->inet.af = AF_INET;
215         sc->inet6.af = AF_INET6;
216         sc->atalk.af = AF_APPLETALK;
217         sc->ipx.af = AF_IPX;
218         return (0);
219 }
220
221 /*
222  * Check new hook
223  */
224 static int
225 cisco_newhook(node_p node, hook_p hook, const char *name)
226 {
227         const sc_p sc = node->private;
228
229         if (strcmp(name, NG_CISCO_HOOK_DOWNSTREAM) == 0) {
230                 sc->downstream.hook = hook;
231                 hook->private = &sc->downstream;
232
233                 /* Start keepalives */
234                 sc->handle = timeout(cisco_keepalive, sc, hz * KEEPALIVE_SECS);
235         } else if (strcmp(name, NG_CISCO_HOOK_INET) == 0) {
236                 sc->inet.hook = hook;
237                 hook->private = &sc->inet;
238         } else if (strcmp(name, NG_CISCO_HOOK_APPLETALK) == 0) {
239                 sc->atalk.hook = hook;
240                 hook->private = &sc->atalk;
241         } else if (strcmp(name, NG_CISCO_HOOK_IPX) == 0) {
242                 sc->ipx.hook = hook;
243                 hook->private = &sc->ipx;
244         } else if (strcmp(name, NG_CISCO_HOOK_DEBUG) == 0) {
245                 hook->private = NULL;   /* unimplemented */
246         } else
247                 return (EINVAL);
248         return 0;
249 }
250
251 /*
252  * Receive control message.
253  */
254 static int
255 cisco_rcvmsg(node_p node, struct ng_mesg *msg,
256         const char *retaddr, struct ng_mesg **rptr)
257 {
258         const sc_p sc = node->private;
259         struct ng_mesg *resp = NULL;
260         int error = 0;
261
262         switch (msg->header.typecookie) {
263         case NGM_GENERIC_COOKIE:
264                 switch (msg->header.cmd) {
265                 case NGM_TEXT_STATUS:
266                     {
267                         char *arg;
268                         int pos;
269
270                         NG_MKRESPONSE(resp, msg, sizeof(struct ng_mesg)
271                             + NG_TEXTRESPONSE, M_NOWAIT);
272                         if (resp == NULL) {
273                                 error = ENOMEM;
274                                 break;
275                         }
276                         arg = (char *) resp->data;
277                         pos = sprintf(arg,
278                           "keepalive period: %d sec; ", KEEPALIVE_SECS);
279                         pos += sprintf(arg + pos,
280                           "unacknowledged keepalives: %ld", sc->seqRetries);
281                         resp->header.arglen = pos + 1;
282                         break;
283                     }
284                 default:
285                         error = EINVAL;
286                         break;
287                 }
288                 break;
289         case NGM_CISCO_COOKIE:
290                 switch (msg->header.cmd) {
291                 case NGM_CISCO_GET_IPADDR:      /* could be a late reply! */
292                         if ((msg->header.flags & NGF_RESP) == 0) {
293                                 struct in_addr *ips;
294
295                                 NG_MKRESPONSE(resp, msg,
296                                     2 * sizeof(*ips), M_NOWAIT);
297                                 if (!resp) {
298                                         error = ENOMEM;
299                                         break;
300                                 }
301                                 ips = (struct in_addr *) resp->data;
302                                 ips[0] = sc->localip;
303                                 ips[1] = sc->localmask;
304                                 break;
305                         }
306                         /* FALLTHROUGH */       /* ...if it's a reply */
307                 case NGM_CISCO_SET_IPADDR:
308                     {
309                         struct in_addr *const ips = (struct in_addr *)msg->data;
310
311                         if (msg->header.arglen < 2 * sizeof(*ips)) {
312                                 error = EINVAL;
313                                 break;
314                         }
315                         sc->localip = ips[0];
316                         sc->localmask = ips[1];
317                         break;
318                     }
319                 case NGM_CISCO_GET_STATUS:
320                     {
321                         struct ng_cisco_stats *stat;
322
323                         NG_MKRESPONSE(resp, msg, sizeof(*stat), M_NOWAIT);
324                         if (!resp) {
325                                 error = ENOMEM;
326                                 break;
327                         }
328                         stat = (struct ng_cisco_stats *)resp->data;
329                         stat->seqRetries = sc->seqRetries;
330                         stat->keepAlivePeriod = KEEPALIVE_SECS;
331                         break;
332                     }
333                 default:
334                         error = EINVAL;
335                         break;
336                 }
337                 break;
338         default:
339                 error = EINVAL;
340                 break;
341         }
342         if (rptr)
343                 *rptr = resp;
344         else if (resp)
345                 FREE(resp, M_NETGRAPH);
346         FREE(msg, M_NETGRAPH);
347         return (error);
348 }
349
350 /*
351  * Receive data
352  */
353 static int
354 cisco_rcvdata(hook_p hook, struct mbuf *m, meta_p meta)
355 {
356         const sc_p sc = hook->node->private;
357         struct protoent *pep;
358         struct cisco_header *h;
359         int error = 0;
360
361         if ((pep = hook->private) == NULL)
362                 goto out;
363
364         /* If it came from our downlink, deal with it separately */
365         if (pep->af == 0xffff)
366                 return (cisco_input(sc, m, meta));
367
368         /* OK so it came from a protocol, heading out. Prepend general data
369            packet header. For now, IP,IPX only  */
370         M_PREPEND(m, CISCO_HEADER_LEN, M_DONTWAIT);
371         if (!m) {
372                 error = ENOBUFS;
373                 goto out;
374         }
375         h = mtod(m, struct cisco_header *);
376         h->address = CISCO_UNICAST;
377         h->control = 0;
378
379         switch (pep->af) {
380         case AF_INET:           /* Internet Protocol */
381                 h->protocol = htons(ETHERTYPE_IP);
382                 break;
383         case AF_INET6:
384                 h->protocol = htons(ETHERTYPE_IPV6);
385                 break;
386         case AF_APPLETALK:      /* AppleTalk Protocol */
387                 h->protocol = htons(ETHERTYPE_AT);
388                 break;
389         case AF_IPX:            /* Novell IPX Protocol */
390                 h->protocol = htons(ETHERTYPE_IPX);
391                 break;
392         default:
393                 error = EAFNOSUPPORT;
394                 goto out;
395         }
396
397         /* Send it */
398         NG_SEND_DATA(error, sc->downstream.hook, m, meta);
399         return (error);
400
401 out:
402         NG_FREE_DATA(m, meta);
403         return (error);
404 }
405
406 /*
407  * Shutdown node
408  */
409 static int
410 cisco_rmnode(node_p node)
411 {
412         const sc_p sc = node->private;
413
414         node->flags |= NG_INVALID;
415         ng_cutlinks(node);
416         ng_unname(node);
417         node->private = NULL;
418         ng_unref(sc->node);
419         FREE(sc, M_NETGRAPH);
420         return (0);
421 }
422
423 /*
424  * Disconnection of a hook
425  *
426  * For this type, removal of the last link destroys the node
427  */
428 static int
429 cisco_disconnect(hook_p hook)
430 {
431         const sc_p sc = hook->node->private;
432         struct protoent *pep;
433
434         /* Check it's not the debug hook */
435         if ((pep = hook->private)) {
436                 pep->hook = NULL;
437                 if (pep->af == 0xffff) {
438                         /* If it is the downstream hook, stop the timers */
439                         untimeout(cisco_keepalive, sc, sc->handle);
440                 }
441         }
442
443         /* If no more hooks, remove the node */
444         if (hook->node->numhooks == 0)
445                 ng_rmnode(hook->node);
446         return (0);
447 }
448
449 /*
450  * Receive data
451  */
452 static int
453 cisco_input(sc_p sc, struct mbuf *m, meta_p meta)
454 {
455         const struct cisco_header *h;
456         struct cisco_header hdrbuf;
457         struct protoent *pep;
458         int error = 0;
459
460         /* Sanity check header length */
461         if (m->m_pkthdr.len < sizeof(*h)) {
462                 error = EINVAL;
463                 goto drop;
464         }
465
466         /* Get cisco header */
467         if (m->m_len >= sizeof(*h))                     /* the common case */
468                 h = mtod(m, const struct cisco_header *);
469         else {
470                 m_copydata(m, 0, sizeof(*h), (caddr_t)&hdrbuf);
471                 h = &hdrbuf;
472         }
473         m_adj(m, sizeof(*h));
474
475         /* Check header address */
476         switch (h->address) {
477         default:                /* Invalid Cisco packet. */
478                 goto drop;
479         case CISCO_UNICAST:
480         case CISCO_MULTICAST:
481                 /* Don't check the control field here (RFC 1547). */
482                 switch (ntohs(h->protocol)) {
483                 default:
484                         goto drop;
485                 case CISCO_KEEPALIVE:
486                     {
487                         const struct cisco_packet *p;
488                         struct cisco_packet pktbuf;
489
490                         /* Sanity check packet length */
491                         if (m->m_pkthdr.len < sizeof(*p)) {
492                                 error = EINVAL;
493                                 goto drop;
494                         }
495
496                         /* Get cisco packet */
497                         if (m->m_len >= sizeof(*p))     /* the common case */
498                                 p = mtod(m, const struct cisco_packet *);
499                         else {
500                                 m_copydata(m, 0, sizeof(*p), (caddr_t)&pktbuf);
501                                 p = &pktbuf;
502                         }
503
504                         /* Check packet type */
505                         switch (ntohl(p->type)) {
506                         default:
507                                 log(LOG_WARNING,
508                                     "cisco: unknown cisco packet type: 0x%lx\n",
509                                        ntohl(p->type));
510                                 break;
511                         case CISCO_ADDR_REPLY:
512                                 /* Reply on address request, ignore */
513                                 break;
514                         case CISCO_KEEPALIVE_REQ:
515                                 sc->remote_seq = ntohl(p->par1);
516                                 if (sc->local_seq == ntohl(p->par2)) {
517                                         sc->local_seq++;
518                                         sc->seqRetries = 0;
519                                 }
520                                 break;
521                         case CISCO_ADDR_REQ:
522                             {
523                                 struct ng_mesg *msg, *resp;
524
525                                 /* Ask inet peer for IP address information */
526                                 if (sc->inet.hook == NULL)
527                                         goto nomsg;
528                                 NG_MKMESSAGE(msg, NGM_CISCO_COOKIE,
529                                     NGM_CISCO_GET_IPADDR, 0, M_NOWAIT);
530                                 if (msg == NULL)
531                                         goto nomsg;
532                                 ng_send_msg(sc->node, msg,
533                                     NG_CISCO_HOOK_INET, &resp);
534                                 if (resp != NULL)
535                                         cisco_rcvmsg(sc->node, resp, ".", NULL);
536
537                 nomsg:
538                                 /* Send reply to peer device */
539                                 error = cisco_send(sc, CISCO_ADDR_REPLY,
540                                             ntohl(sc->localip.s_addr),
541                                             ntohl(sc->localmask.s_addr));
542                                 break;
543                             }
544                         }
545                         goto drop;
546                     }
547                 case ETHERTYPE_IP:
548                         pep = &sc->inet;
549                         break;
550                 case ETHERTYPE_IPV6:
551                         pep = &sc->inet6;
552                         break;
553                 case ETHERTYPE_AT:
554                         pep = &sc->atalk;
555                         break;
556                 case ETHERTYPE_IPX:
557                         pep = &sc->ipx;
558                         break;
559                 }
560                 break;
561         }
562
563         /* Drop if payload is empty */
564         if (m->m_pkthdr.len == 0) {
565                 error = EINVAL;
566                 goto drop;
567         }
568
569         /* Send it on */
570         if (pep->hook == NULL)
571                 goto drop;
572         NG_SEND_DATA(error, pep->hook, m, meta);
573         return (error);
574
575 drop:
576         NG_FREE_DATA(m, meta);
577         return (error);
578 }
579
580
581 /*
582  * Send keepalive packets, every 10 seconds.
583  */
584 static void
585 cisco_keepalive(void *arg)
586 {
587         const sc_p sc = arg;
588         int s = splimp();
589
590         cisco_send(sc, CISCO_KEEPALIVE_REQ, sc->local_seq, sc->remote_seq);
591         sc->seqRetries++;
592         splx(s);
593         sc->handle = timeout(cisco_keepalive, sc, hz * KEEPALIVE_SECS);
594 }
595
596 /*
597  * Send Cisco keepalive packet.
598  */
599 static int
600 cisco_send(sc_p sc, int type, long par1, long par2)
601 {
602         struct cisco_header *h;
603         struct cisco_packet *ch;
604         struct mbuf *m;
605         u_long  t;
606         int     error = 0;
607         meta_p  meta = NULL;
608         struct timeval time;
609
610         getmicrotime(&time);
611
612         MGETHDR(m, M_DONTWAIT, MT_DATA);
613         if (!m)
614                 return (ENOBUFS);
615
616         t = (time.tv_sec - boottime.tv_sec) * 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(error, sc->downstream.hook, m, meta);
634         return (error);
635 }