30e00da8c9608a0828e5f670e92052e140bb2928
[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  * $DragonFly: src/sys/netgraph7/ng_vlan.c,v 1.2 2008/06/26 23:05:35 dillon Exp $
30  */
31
32 #include <sys/param.h>
33 #include <sys/errno.h>
34 #include <sys/kernel.h>
35 #include <sys/malloc.h>
36 #include <sys/mbuf.h>
37 #include <sys/queue.h>
38 #include <sys/socket.h>
39 #include <sys/systm.h>
40
41 #include <net/ethernet.h>
42 #include <net/if.h>
43 #include <net/if_vlan_var.h>
44
45 #include "ng_message.h"
46 #include "ng_parse.h"
47 #include "ng_vlan.h"
48 #include "netgraph.h"
49
50 static ng_constructor_t ng_vlan_constructor;
51 static ng_rcvmsg_t      ng_vlan_rcvmsg;
52 static ng_shutdown_t    ng_vlan_shutdown;
53 static ng_newhook_t     ng_vlan_newhook;
54 static ng_rcvdata_t     ng_vlan_rcvdata;
55 static ng_disconnect_t  ng_vlan_disconnect;
56
57 /* Parse type for struct ng_vlan_filter. */
58 static const struct ng_parse_struct_field ng_vlan_filter_fields[] =
59         NG_VLAN_FILTER_FIELDS;
60 static const struct ng_parse_type ng_vlan_filter_type = {
61         &ng_parse_struct_type,
62         &ng_vlan_filter_fields
63 };
64
65 static int
66 ng_vlan_getTableLength(const struct ng_parse_type *type,
67     const u_char *start, const u_char *buf)
68 {
69         const struct ng_vlan_table *const table =
70             (const struct ng_vlan_table *)(buf - sizeof(u_int32_t));
71
72         return table->n;
73 }
74
75 /* Parse type for struct ng_vlan_table. */
76 static const struct ng_parse_array_info ng_vlan_table_array_info = {
77         &ng_vlan_filter_type,
78         ng_vlan_getTableLength
79 };
80 static const struct ng_parse_type ng_vlan_table_array_type = {
81         &ng_parse_array_type,
82         &ng_vlan_table_array_info
83 };
84 static const struct ng_parse_struct_field ng_vlan_table_fields[] =
85         NG_VLAN_TABLE_FIELDS;
86 static const struct ng_parse_type ng_vlan_table_type = {
87         &ng_parse_struct_type,
88         &ng_vlan_table_fields
89 };
90
91 /* List of commands and how to convert arguments to/from ASCII. */
92 static const struct ng_cmdlist ng_vlan_cmdlist[] = {
93         {
94           NGM_VLAN_COOKIE,
95           NGM_VLAN_ADD_FILTER,
96           "addfilter",
97           &ng_vlan_filter_type,
98           NULL
99         },
100         {
101           NGM_VLAN_COOKIE,
102           NGM_VLAN_DEL_FILTER,
103           "delfilter",
104           &ng_parse_hookbuf_type,
105           NULL
106         },
107         {
108           NGM_VLAN_COOKIE,
109           NGM_VLAN_GET_TABLE,
110           "gettable",
111           NULL,
112           &ng_vlan_table_type
113         },
114         { 0 }
115 };
116
117 static struct ng_type ng_vlan_typestruct = {
118         .version =      NG_ABI_VERSION,
119         .name =         NG_VLAN_NODE_TYPE,
120         .constructor =  ng_vlan_constructor,
121         .rcvmsg =       ng_vlan_rcvmsg,
122         .shutdown =     ng_vlan_shutdown,
123         .newhook =      ng_vlan_newhook,
124         .rcvdata =      ng_vlan_rcvdata,
125         .disconnect =   ng_vlan_disconnect,
126         .cmdlist =      ng_vlan_cmdlist,
127 };
128 NETGRAPH_INIT(vlan, &ng_vlan_typestruct);
129
130 struct filter {
131         LIST_ENTRY(filter) next;
132         u_int16_t       vlan;
133         hook_p          hook;
134 };
135
136 #define HASHSIZE        16
137 #define HASH(id)        ((((id) >> 8) ^ ((id) >> 4) ^ (id)) & 0x0f)
138 LIST_HEAD(filterhead, filter);
139
140 typedef struct {
141         hook_p          downstream_hook;
142         hook_p          nomatch_hook;
143         struct filterhead hashtable[HASHSIZE];
144         u_int32_t       nent;
145 } *priv_p;
146
147 static struct filter *
148 ng_vlan_findentry(priv_p priv, u_int16_t vlan)
149 {
150         struct filterhead *chain = &priv->hashtable[HASH(vlan)];
151         struct filter *f;
152
153         LIST_FOREACH(f, chain, next)
154                 if (f->vlan == vlan)
155                         return (f);
156         return (NULL);
157 }
158
159 static int
160 ng_vlan_constructor(node_p node)
161 {
162         priv_p priv;
163         int i;
164
165         MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, 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                         MALLOC(f, struct filter *, sizeof(*f),
246                             M_NETGRAPH, 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                         FREE(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                 break;
330             }
331         default:                        /* Unknown type cookie. */
332                 error = EINVAL;
333                 break;
334         }
335         NG_RESPOND_MSG(error, node, item, resp);
336         NG_FREE_MSG(msg);
337         return (error);
338 }
339
340 static int
341 ng_vlan_rcvdata(hook_p hook, item_p item)
342 {
343         const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
344         struct ether_header *eh;
345         struct ether_vlan_header *evl = NULL;
346         int error;
347         u_int16_t vlan;
348         struct mbuf *m;
349         struct filter *f;
350
351         /* Make sure we have an entire header. */
352         NGI_GET_M(item, m);
353         if (m->m_len < sizeof(*eh) &&
354             (m = m_pullup(m, sizeof(*eh))) == NULL) {
355                 NG_FREE_ITEM(item);
356                 return (EINVAL);
357         }
358         eh = mtod(m, struct ether_header *);
359         if (hook == priv->downstream_hook) {
360                 /*
361                  * If from downstream, select between a match hook
362                  * or the nomatch hook.
363                  */
364                 if (m->m_flags & M_VLANTAG ||
365                     eh->ether_type == htons(ETHERTYPE_VLAN)) {
366                         if (m->m_flags & M_VLANTAG) {
367                                 /*
368                                  * Packet is tagged, m contains a normal
369                                  * Ethernet frame; tag is stored out-of-band.
370                                  */
371                                 vlan = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
372                         } else {
373                                 if (m->m_len < sizeof(*evl) &&
374                                     (m = m_pullup(m, sizeof(*evl))) == NULL) {
375                                         NG_FREE_ITEM(item);
376                                         return (EINVAL);
377                                 }
378                                 evl = mtod(m, struct ether_vlan_header *);
379                                 vlan = EVL_VLANOFTAG(ntohs(evl->evl_tag));
380                         }
381                         if ((f = ng_vlan_findentry(priv, vlan)) != NULL) {
382                                 if (m->m_flags & M_VLANTAG) {
383                                         m->m_pkthdr.ether_vtag = 0;
384                                         m->m_flags &= ~M_VLANTAG;
385                                 } else {
386                                         evl->evl_encap_proto = evl->evl_proto;
387                                         bcopy(mtod(m, caddr_t),
388                                             mtod(m, caddr_t) +
389                                             ETHER_VLAN_ENCAP_LEN,
390                                             ETHER_HDR_LEN);
391                                         m_adj(m, ETHER_VLAN_ENCAP_LEN);
392                                 }
393                         }
394                 } else
395                         f = NULL;
396                 if (f != NULL)
397                         NG_FWD_NEW_DATA(error, item, f->hook, m);
398                 else
399                         NG_FWD_NEW_DATA(error, item, priv->nomatch_hook, m);
400         } else {
401                 /*
402                  * It is heading towards the downstream.
403                  * If from nomatch, pass it unmodified.
404                  * Otherwise, do the VLAN encapsulation.
405                  */
406                 if (hook != priv->nomatch_hook) {
407                         if ((f = NG_HOOK_PRIVATE(hook)) == NULL) {
408                                 NG_FREE_ITEM(item);
409                                 NG_FREE_M(m);
410                                 return (EOPNOTSUPP);
411                         }
412                         M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, MB_DONTWAIT);
413                         /* M_PREPEND takes care of m_len and m_pkthdr.len. */
414                         if (m == NULL || (m->m_len < sizeof(*evl) &&
415                             (m = m_pullup(m, sizeof(*evl))) == NULL)) {
416                                 NG_FREE_ITEM(item);
417                                 return (ENOMEM);
418                         }
419                         /*
420                          * Transform the Ethernet header into an Ethernet header
421                          * with 802.1Q encapsulation.
422                          */
423                         bcopy(mtod(m, char *) + ETHER_VLAN_ENCAP_LEN,
424                             mtod(m, char *), ETHER_HDR_LEN);
425                         evl = mtod(m, struct ether_vlan_header *);
426                         evl->evl_proto = evl->evl_encap_proto;
427                         evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
428                         evl->evl_tag = htons(f->vlan);
429                 }
430                 NG_FWD_NEW_DATA(error, item, priv->downstream_hook, m);
431         }
432         return (error);
433 }
434
435 static int
436 ng_vlan_shutdown(node_p node)
437 {
438         const priv_p priv = NG_NODE_PRIVATE(node);
439
440         NG_NODE_SET_PRIVATE(node, NULL);
441         NG_NODE_UNREF(node);
442         FREE(priv, M_NETGRAPH);
443         return (0);
444 }
445
446 static int
447 ng_vlan_disconnect(hook_p hook)
448 {
449         const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
450         struct filter *f;
451
452         if (hook == priv->downstream_hook)
453                 priv->downstream_hook = NULL;
454         else if (hook == priv->nomatch_hook)
455                 priv->nomatch_hook = NULL;
456         else {
457                 /* Purge a rule that refers to this hook. */
458                 if ((f = NG_HOOK_PRIVATE(hook)) != NULL) {
459                         LIST_REMOVE(f, next);
460                         priv->nent--;
461                         FREE(f, M_NETGRAPH);
462                 }
463         }
464         NG_HOOK_SET_PRIVATE(hook, NULL);
465         if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
466             (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
467                 ng_rmnode_self(NG_HOOK_NODE(hook));
468         return (0);
469 }