0b064b2f0cfa2df01f219480fc8ec407198262c2
[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         priv = kmalloc(sizeof(*priv), M_NETGRAPH,
166                        M_WAITOK | M_NULLOK | M_ZERO);
167         if (priv == NULL)
168                 return (ENOMEM);
169         for (i = 0; i < HASHSIZE; i++)
170                 LIST_INIT(&priv->hashtable[i]);
171         NG_NODE_SET_PRIVATE(node, priv);
172         return (0);
173 }
174
175 static int
176 ng_vlan_newhook(node_p node, hook_p hook, const char *name)
177 {
178         const priv_p priv = NG_NODE_PRIVATE(node);
179
180         if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0)
181                 priv->downstream_hook = hook;
182         else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0)
183                 priv->nomatch_hook = hook;
184         else {
185                 /*
186                  * Any other hook name is valid and can
187                  * later be associated with a filter rule.
188                  */
189         }
190         NG_HOOK_SET_PRIVATE(hook, NULL);
191         return (0);
192 }
193
194 static int
195 ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook)
196 {
197         const priv_p priv = NG_NODE_PRIVATE(node);
198         int error = 0;
199         struct ng_mesg *msg, *resp = NULL;
200         struct ng_vlan_filter *vf;
201         struct filter *f;
202         hook_p hook;
203         struct ng_vlan_table *t;
204         int i;
205
206         NGI_GET_MSG(item, msg);
207         /* Deal with message according to cookie and command. */
208         switch (msg->header.typecookie) {
209         case NGM_VLAN_COOKIE:
210                 switch (msg->header.cmd) {
211                 case NGM_VLAN_ADD_FILTER:
212                         /* Check that message is long enough. */
213                         if (msg->header.arglen != sizeof(*vf)) {
214                                 error = EINVAL;
215                                 break;
216                         }
217                         vf = (struct ng_vlan_filter *)msg->data;
218                         /* Sanity check the VLAN ID value. */
219                         if (vf->vlan & ~EVL_VLID_MASK) {
220                                 error = EINVAL;
221                                 break;
222                         }
223                         /* Check that a referenced hook exists. */
224                         hook = ng_findhook(node, vf->hook);
225                         if (hook == NULL) {
226                                 error = ENOENT;
227                                 break;
228                         }
229                         /* And is not one of the special hooks. */
230                         if (hook == priv->downstream_hook ||
231                             hook == priv->nomatch_hook) {
232                                 error = EINVAL;
233                                 break;
234                         }
235                         /* And is not already in service. */
236                         if (NG_HOOK_PRIVATE(hook) != NULL) {
237                                 error = EEXIST;
238                                 break;
239                         }
240                         /* Check we don't already trap this VLAN. */
241                         if (ng_vlan_findentry(priv, vf->vlan)) {
242                                 error = EEXIST;
243                                 break;
244                         }
245                         /* Create filter. */
246                         f = kmalloc(sizeof(*f), M_NETGRAPH,
247                                     M_WAITOK | M_NULLOK | M_ZERO);
248                         if (f == NULL) {
249                                 error = ENOMEM;
250                                 break;
251                         }
252                         /* Link filter and hook together. */
253                         f->hook = hook;
254                         f->vlan = vf->vlan;
255                         NG_HOOK_SET_PRIVATE(hook, f);
256                         /* Register filter in a hash table. */
257                         LIST_INSERT_HEAD(
258                             &priv->hashtable[HASH(f->vlan)], f, next);
259                         priv->nent++;
260                         break;
261                 case NGM_VLAN_DEL_FILTER:
262                         /* Check that message is long enough. */
263                         if (msg->header.arglen != NG_HOOKSIZ) {
264                                 error = EINVAL;
265                                 break;
266                         }
267                         /* Check that hook exists and is active. */
268                         hook = ng_findhook(node, (char *)msg->data);
269                         if (hook == NULL ||
270                             (f = NG_HOOK_PRIVATE(hook)) == NULL) {
271                                 error = ENOENT;
272                                 break;
273                         }
274                         /* Purge a rule that refers to this hook. */
275                         NG_HOOK_SET_PRIVATE(hook, NULL);
276                         LIST_REMOVE(f, next);
277                         priv->nent--;
278                         kfree(f, M_NETGRAPH);
279                         break;
280                 case NGM_VLAN_GET_TABLE:
281                         NG_MKRESPONSE(resp, msg, sizeof(*t) +
282                             priv->nent * sizeof(*t->filter), M_WAITOK | M_NULLOK);
283                         if (resp == NULL) {
284                                 error = ENOMEM;
285                                 break;
286                         }
287                         t = (struct ng_vlan_table *)resp->data;
288                         t->n = priv->nent;
289                         vf = &t->filter[0];
290                         for (i = 0; i < HASHSIZE; i++) {
291                                 LIST_FOREACH(f, &priv->hashtable[i], next) {
292                                         vf->vlan = f->vlan;
293                                         strncpy(vf->hook, NG_HOOK_NAME(f->hook),
294                                             NG_HOOKSIZ);
295                                         vf++;
296                                 }
297                         }
298                         break;
299                 default:                /* Unknown command. */
300                         error = EINVAL;
301                         break;
302                 }
303                 break;
304         case NGM_FLOW_COOKIE:
305             {
306                 struct ng_mesg *copy;
307                 struct filterhead *chain;
308                 struct filter *f;
309
310                 /*
311                  * Flow control messages should come only
312                  * from downstream.
313                  */
314
315                 if (lasthook == NULL)
316                         break;
317                 if (lasthook != priv->downstream_hook)
318                         break;
319
320                 /* Broadcast the event to all uplinks. */
321                 for (i = 0, chain = priv->hashtable; i < HASHSIZE;
322                     i++, chain++)
323                 LIST_FOREACH(f, chain, next) {
324                         NG_COPYMESSAGE(copy, msg, M_WAITOK | M_NULLOK);
325                         if (copy == NULL)
326                                 continue;
327                         NG_SEND_MSG_HOOK(error, node, copy, f->hook, 0);
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 }