Merge branch 'vendor/GREP'
[dragonfly.git] / sys / netgraph7 / ng_vlan.c
1 /*-
2  * Copyright (c) 2003 IPNET Internet Communication Company
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * Author: Ruslan Ermilov <ru@FreeBSD.org>
27  *
28  * $FreeBSD: src/sys/netgraph/ng_vlan.c,v 1.5 2007/06/11 15:29:02 imp Exp $
29  */
30
31 #include <sys/param.h>
32 #include <sys/errno.h>
33 #include <sys/kernel.h>
34 #include <sys/malloc.h>
35 #include <sys/mbuf.h>
36 #include <sys/queue.h>
37 #include <sys/socket.h>
38 #include <sys/systm.h>
39
40 #include <net/ethernet.h>
41 #include <net/if.h>
42 #include <net/if_vlan_var.h>
43
44 #include "ng_message.h"
45 #include "ng_parse.h"
46 #include "ng_vlan.h"
47 #include "netgraph.h"
48
49 static ng_constructor_t ng_vlan_constructor;
50 static ng_rcvmsg_t      ng_vlan_rcvmsg;
51 static ng_shutdown_t    ng_vlan_shutdown;
52 static ng_newhook_t     ng_vlan_newhook;
53 static ng_rcvdata_t     ng_vlan_rcvdata;
54 static ng_disconnect_t  ng_vlan_disconnect;
55
56 /* Parse type for struct ng_vlan_filter. */
57 static const struct ng_parse_struct_field ng_vlan_filter_fields[] =
58         NG_VLAN_FILTER_FIELDS;
59 static const struct ng_parse_type ng_vlan_filter_type = {
60         &ng_parse_struct_type,
61         &ng_vlan_filter_fields
62 };
63
64 static int
65 ng_vlan_getTableLength(const struct ng_parse_type *type,
66     const u_char *start, const u_char *buf)
67 {
68         const struct ng_vlan_table *const table =
69             (const struct ng_vlan_table *)(buf - sizeof(u_int32_t));
70
71         return table->n;
72 }
73
74 /* Parse type for struct ng_vlan_table. */
75 static const struct ng_parse_array_info ng_vlan_table_array_info = {
76         &ng_vlan_filter_type,
77         ng_vlan_getTableLength
78 };
79 static const struct ng_parse_type ng_vlan_table_array_type = {
80         &ng_parse_array_type,
81         &ng_vlan_table_array_info
82 };
83 static const struct ng_parse_struct_field ng_vlan_table_fields[] =
84         NG_VLAN_TABLE_FIELDS;
85 static const struct ng_parse_type ng_vlan_table_type = {
86         &ng_parse_struct_type,
87         &ng_vlan_table_fields
88 };
89
90 /* List of commands and how to convert arguments to/from ASCII. */
91 static const struct ng_cmdlist ng_vlan_cmdlist[] = {
92         {
93           NGM_VLAN_COOKIE,
94           NGM_VLAN_ADD_FILTER,
95           "addfilter",
96           &ng_vlan_filter_type,
97           NULL
98         },
99         {
100           NGM_VLAN_COOKIE,
101           NGM_VLAN_DEL_FILTER,
102           "delfilter",
103           &ng_parse_hookbuf_type,
104           NULL
105         },
106         {
107           NGM_VLAN_COOKIE,
108           NGM_VLAN_GET_TABLE,
109           "gettable",
110           NULL,
111           &ng_vlan_table_type
112         },
113         { 0 }
114 };
115
116 static struct ng_type ng_vlan_typestruct = {
117         .version =      NG_ABI_VERSION,
118         .name =         NG_VLAN_NODE_TYPE,
119         .constructor =  ng_vlan_constructor,
120         .rcvmsg =       ng_vlan_rcvmsg,
121         .shutdown =     ng_vlan_shutdown,
122         .newhook =      ng_vlan_newhook,
123         .rcvdata =      ng_vlan_rcvdata,
124         .disconnect =   ng_vlan_disconnect,
125         .cmdlist =      ng_vlan_cmdlist,
126 };
127 NETGRAPH_INIT(vlan, &ng_vlan_typestruct);
128
129 struct filter {
130         LIST_ENTRY(filter) next;
131         u_int16_t       vlan;
132         hook_p          hook;
133 };
134
135 #define HASHSIZE        16
136 #define HASH(id)        ((((id) >> 8) ^ ((id) >> 4) ^ (id)) & 0x0f)
137 LIST_HEAD(filterhead, filter);
138
139 typedef struct {
140         hook_p          downstream_hook;
141         hook_p          nomatch_hook;
142         struct filterhead hashtable[HASHSIZE];
143         u_int32_t       nent;
144 } *priv_p;
145
146 static struct filter *
147 ng_vlan_findentry(priv_p priv, u_int16_t vlan)
148 {
149         struct filterhead *chain = &priv->hashtable[HASH(vlan)];
150         struct filter *f;
151
152         LIST_FOREACH(f, chain, next)
153                 if (f->vlan == vlan)
154                         return (f);
155         return (NULL);
156 }
157
158 static int
159 ng_vlan_constructor(node_p node)
160 {
161         priv_p priv;
162         int i;
163
164         priv = kmalloc(sizeof(*priv), M_NETGRAPH,
165                        M_WAITOK | M_NULLOK | M_ZERO);
166         if (priv == NULL)
167                 return (ENOMEM);
168         for (i = 0; i < HASHSIZE; i++)
169                 LIST_INIT(&priv->hashtable[i]);
170         NG_NODE_SET_PRIVATE(node, priv);
171         return (0);
172 }
173
174 static int
175 ng_vlan_newhook(node_p node, hook_p hook, const char *name)
176 {
177         const priv_p priv = NG_NODE_PRIVATE(node);
178
179         if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0)
180                 priv->downstream_hook = hook;
181         else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0)
182                 priv->nomatch_hook = hook;
183         else {
184                 /*
185                  * Any other hook name is valid and can
186                  * later be associated with a filter rule.
187                  */
188         }
189         NG_HOOK_SET_PRIVATE(hook, NULL);
190         return (0);
191 }
192
193 static int
194 ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook)
195 {
196         const priv_p priv = NG_NODE_PRIVATE(node);
197         int error = 0;
198         struct ng_mesg *msg, *resp = NULL;
199         struct ng_vlan_filter *vf;
200         struct filter *f;
201         hook_p hook;
202         struct ng_vlan_table *t;
203         int i;
204
205         NGI_GET_MSG(item, msg);
206         /* Deal with message according to cookie and command. */
207         switch (msg->header.typecookie) {
208         case NGM_VLAN_COOKIE:
209                 switch (msg->header.cmd) {
210                 case NGM_VLAN_ADD_FILTER:
211                         /* Check that message is long enough. */
212                         if (msg->header.arglen != sizeof(*vf)) {
213                                 error = EINVAL;
214                                 break;
215                         }
216                         vf = (struct ng_vlan_filter *)msg->data;
217                         /* Sanity check the VLAN ID value. */
218                         if (vf->vlan & ~EVL_VLID_MASK) {
219                                 error = EINVAL;
220                                 break;
221                         }
222                         /* Check that a referenced hook exists. */
223                         hook = ng_findhook(node, vf->hook);
224                         if (hook == NULL) {
225                                 error = ENOENT;
226                                 break;
227                         }
228                         /* And is not one of the special hooks. */
229                         if (hook == priv->downstream_hook ||
230                             hook == priv->nomatch_hook) {
231                                 error = EINVAL;
232                                 break;
233                         }
234                         /* And is not already in service. */
235                         if (NG_HOOK_PRIVATE(hook) != NULL) {
236                                 error = EEXIST;
237                                 break;
238                         }
239                         /* Check we don't already trap this VLAN. */
240                         if (ng_vlan_findentry(priv, vf->vlan)) {
241                                 error = EEXIST;
242                                 break;
243                         }
244                         /* Create filter. */
245                         f = kmalloc(sizeof(*f), M_NETGRAPH,
246                                     M_WAITOK | M_NULLOK | M_ZERO);
247                         if (f == NULL) {
248                                 error = ENOMEM;
249                                 break;
250                         }
251                         /* Link filter and hook together. */
252                         f->hook = hook;
253                         f->vlan = vf->vlan;
254                         NG_HOOK_SET_PRIVATE(hook, f);
255                         /* Register filter in a hash table. */
256                         LIST_INSERT_HEAD(
257                             &priv->hashtable[HASH(f->vlan)], f, next);
258                         priv->nent++;
259                         break;
260                 case NGM_VLAN_DEL_FILTER:
261                         /* Check that message is long enough. */
262                         if (msg->header.arglen != NG_HOOKSIZ) {
263                                 error = EINVAL;
264                                 break;
265                         }
266                         /* Check that hook exists and is active. */
267                         hook = ng_findhook(node, (char *)msg->data);
268                         if (hook == NULL ||
269                             (f = NG_HOOK_PRIVATE(hook)) == NULL) {
270                                 error = ENOENT;
271                                 break;
272                         }
273                         /* Purge a rule that refers to this hook. */
274                         NG_HOOK_SET_PRIVATE(hook, NULL);
275                         LIST_REMOVE(f, next);
276                         priv->nent--;
277                         kfree(f, M_NETGRAPH);
278                         break;
279                 case NGM_VLAN_GET_TABLE:
280                         NG_MKRESPONSE(resp, msg, sizeof(*t) +
281                             priv->nent * sizeof(*t->filter), M_WAITOK | M_NULLOK);
282                         if (resp == NULL) {
283                                 error = ENOMEM;
284                                 break;
285                         }
286                         t = (struct ng_vlan_table *)resp->data;
287                         t->n = priv->nent;
288                         vf = &t->filter[0];
289                         for (i = 0; i < HASHSIZE; i++) {
290                                 LIST_FOREACH(f, &priv->hashtable[i], next) {
291                                         vf->vlan = f->vlan;
292                                         strncpy(vf->hook, NG_HOOK_NAME(f->hook),
293                                             NG_HOOKSIZ);
294                                         vf++;
295                                 }
296                         }
297                         break;
298                 default:                /* Unknown command. */
299                         error = EINVAL;
300                         break;
301                 }
302                 break;
303         case NGM_FLOW_COOKIE:
304             {
305                 struct ng_mesg *copy;
306                 struct filterhead *chain;
307                 struct filter *f;
308
309                 /*
310                  * Flow control messages should come only
311                  * from downstream.
312                  */
313
314                 if (lasthook == NULL)
315                         break;
316                 if (lasthook != priv->downstream_hook)
317                         break;
318
319                 /* Broadcast the event to all uplinks. */
320                 for (i = 0, chain = priv->hashtable; i < HASHSIZE;
321                     i++, chain++) {
322                         LIST_FOREACH(f, chain, next) {
323                                 NG_COPYMESSAGE(copy, msg, M_WAITOK | M_NULLOK);
324                                 if (copy == NULL)
325                                         continue;
326                                 NG_SEND_MSG_HOOK(error, node, copy, f->hook, 0);
327                         }
328                 }
329
330                 break;
331             }
332         default:                        /* Unknown type cookie. */
333                 error = EINVAL;
334                 break;
335         }
336         NG_RESPOND_MSG(error, node, item, resp);
337         NG_FREE_MSG(msg);
338         return (error);
339 }
340
341 static int
342 ng_vlan_rcvdata(hook_p hook, item_p item)
343 {
344         const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
345         struct ether_header *eh;
346         struct ether_vlan_header *evl = NULL;
347         int error;
348         u_int16_t vlan;
349         struct mbuf *m;
350         struct filter *f;
351
352         /* Make sure we have an entire header. */
353         NGI_GET_M(item, m);
354         if (m->m_len < sizeof(*eh) &&
355             (m = m_pullup(m, sizeof(*eh))) == NULL) {
356                 NG_FREE_ITEM(item);
357                 return (EINVAL);
358         }
359         eh = mtod(m, struct ether_header *);
360         if (hook == priv->downstream_hook) {
361                 /*
362                  * If from downstream, select between a match hook
363                  * or the nomatch hook.
364                  */
365                 if (m->m_flags & M_VLANTAG ||
366                     eh->ether_type == htons(ETHERTYPE_VLAN)) {
367                         if (m->m_flags & M_VLANTAG) {
368                                 /*
369                                  * Packet is tagged, m contains a normal
370                                  * Ethernet frame; tag is stored out-of-band.
371                                  */
372                                 vlan = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
373                         } else {
374                                 if (m->m_len < sizeof(*evl) &&
375                                     (m = m_pullup(m, sizeof(*evl))) == NULL) {
376                                         NG_FREE_ITEM(item);
377                                         return (EINVAL);
378                                 }
379                                 evl = mtod(m, struct ether_vlan_header *);
380                                 vlan = EVL_VLANOFTAG(ntohs(evl->evl_tag));
381                         }
382                         if ((f = ng_vlan_findentry(priv, vlan)) != NULL) {
383                                 if (m->m_flags & M_VLANTAG) {
384                                         m->m_pkthdr.ether_vtag = 0;
385                                         m->m_flags &= ~M_VLANTAG;
386                                 } else {
387                                         evl->evl_encap_proto = evl->evl_proto;
388                                         bcopy(mtod(m, caddr_t),
389                                             mtod(m, caddr_t) +
390                                             ETHER_VLAN_ENCAP_LEN,
391                                             ETHER_HDR_LEN);
392                                         m_adj(m, ETHER_VLAN_ENCAP_LEN);
393                                 }
394                         }
395                 } else
396                         f = NULL;
397                 if (f != NULL)
398                         NG_FWD_NEW_DATA(error, item, f->hook, m);
399                 else
400                         NG_FWD_NEW_DATA(error, item, priv->nomatch_hook, m);
401         } else {
402                 /*
403                  * It is heading towards the downstream.
404                  * If from nomatch, pass it unmodified.
405                  * Otherwise, do the VLAN encapsulation.
406                  */
407                 if (hook != priv->nomatch_hook) {
408                         if ((f = NG_HOOK_PRIVATE(hook)) == NULL) {
409                                 NG_FREE_ITEM(item);
410                                 NG_FREE_M(m);
411                                 return (EOPNOTSUPP);
412                         }
413                         M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, MB_DONTWAIT);
414                         /* M_PREPEND takes care of m_len and m_pkthdr.len. */
415                         if (m == NULL || (m->m_len < sizeof(*evl) &&
416                             (m = m_pullup(m, sizeof(*evl))) == NULL)) {
417                                 NG_FREE_ITEM(item);
418                                 return (ENOMEM);
419                         }
420                         /*
421                          * Transform the Ethernet header into an Ethernet header
422                          * with 802.1Q encapsulation.
423                          */
424                         bcopy(mtod(m, char *) + ETHER_VLAN_ENCAP_LEN,
425                             mtod(m, char *), ETHER_HDR_LEN);
426                         evl = mtod(m, struct ether_vlan_header *);
427                         evl->evl_proto = evl->evl_encap_proto;
428                         evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
429                         evl->evl_tag = htons(f->vlan);
430                 }
431                 NG_FWD_NEW_DATA(error, item, priv->downstream_hook, m);
432         }
433         return (error);
434 }
435
436 static int
437 ng_vlan_shutdown(node_p node)
438 {
439         const priv_p priv = NG_NODE_PRIVATE(node);
440
441         NG_NODE_SET_PRIVATE(node, NULL);
442         NG_NODE_UNREF(node);
443         kfree(priv, M_NETGRAPH);
444         return (0);
445 }
446
447 static int
448 ng_vlan_disconnect(hook_p hook)
449 {
450         const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
451         struct filter *f;
452
453         if (hook == priv->downstream_hook)
454                 priv->downstream_hook = NULL;
455         else if (hook == priv->nomatch_hook)
456                 priv->nomatch_hook = NULL;
457         else {
458                 /* Purge a rule that refers to this hook. */
459                 if ((f = NG_HOOK_PRIVATE(hook)) != NULL) {
460                         LIST_REMOVE(f, next);
461                         priv->nent--;
462                         kfree(f, M_NETGRAPH);
463                 }
464         }
465         NG_HOOK_SET_PRIVATE(hook, NULL);
466         if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
467             (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
468                 ng_rmnode_self(NG_HOOK_NODE(hook));
469         return (0);
470 }