]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - sys/netgraph/ng_vlan.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / sys / netgraph / 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$
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 <netgraph/ng_message.h>
45 #include <netgraph/ng_parse.h>
46 #include <netgraph/ng_vlan.h>
47 #include <netgraph/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 = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO);
165         for (i = 0; i < HASHSIZE; i++)
166                 LIST_INIT(&priv->hashtable[i]);
167         NG_NODE_SET_PRIVATE(node, priv);
168         return (0);
169 }
170
171 static int
172 ng_vlan_newhook(node_p node, hook_p hook, const char *name)
173 {
174         const priv_p priv = NG_NODE_PRIVATE(node);
175
176         if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0)
177                 priv->downstream_hook = hook;
178         else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0)
179                 priv->nomatch_hook = hook;
180         else {
181                 /*
182                  * Any other hook name is valid and can
183                  * later be associated with a filter rule.
184                  */
185         }
186         NG_HOOK_SET_PRIVATE(hook, NULL);
187         return (0);
188 }
189
190 static int
191 ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook)
192 {
193         const priv_p priv = NG_NODE_PRIVATE(node);
194         int error = 0;
195         struct ng_mesg *msg, *resp = NULL;
196         struct ng_vlan_filter *vf;
197         struct filter *f;
198         hook_p hook;
199         struct ng_vlan_table *t;
200         int i;
201
202         NGI_GET_MSG(item, msg);
203         /* Deal with message according to cookie and command. */
204         switch (msg->header.typecookie) {
205         case NGM_VLAN_COOKIE:
206                 switch (msg->header.cmd) {
207                 case NGM_VLAN_ADD_FILTER:
208                         /* Check that message is long enough. */
209                         if (msg->header.arglen != sizeof(*vf)) {
210                                 error = EINVAL;
211                                 break;
212                         }
213                         vf = (struct ng_vlan_filter *)msg->data;
214                         /* Sanity check the VLAN ID value. */
215                         if (vf->vlan & ~EVL_VLID_MASK) {
216                                 error = EINVAL;
217                                 break;
218                         }
219                         /* Check that a referenced hook exists. */
220                         hook = ng_findhook(node, vf->hook);
221                         if (hook == NULL) {
222                                 error = ENOENT;
223                                 break;
224                         }
225                         /* And is not one of the special hooks. */
226                         if (hook == priv->downstream_hook ||
227                             hook == priv->nomatch_hook) {
228                                 error = EINVAL;
229                                 break;
230                         }
231                         /* And is not already in service. */
232                         if (NG_HOOK_PRIVATE(hook) != NULL) {
233                                 error = EEXIST;
234                                 break;
235                         }
236                         /* Check we don't already trap this VLAN. */
237                         if (ng_vlan_findentry(priv, vf->vlan)) {
238                                 error = EEXIST;
239                                 break;
240                         }
241                         /* Create filter. */
242                         f = malloc(sizeof(*f),
243                             M_NETGRAPH, M_NOWAIT | M_ZERO);
244                         if (f == NULL) {
245                                 error = ENOMEM;
246                                 break;
247                         }
248                         /* Link filter and hook together. */
249                         f->hook = hook;
250                         f->vlan = vf->vlan;
251                         NG_HOOK_SET_PRIVATE(hook, f);
252                         /* Register filter in a hash table. */
253                         LIST_INSERT_HEAD(
254                             &priv->hashtable[HASH(f->vlan)], f, next);
255                         priv->nent++;
256                         break;
257                 case NGM_VLAN_DEL_FILTER:
258                         /* Check that message is long enough. */
259                         if (msg->header.arglen != NG_HOOKSIZ) {
260                                 error = EINVAL;
261                                 break;
262                         }
263                         /* Check that hook exists and is active. */
264                         hook = ng_findhook(node, (char *)msg->data);
265                         if (hook == NULL ||
266                             (f = NG_HOOK_PRIVATE(hook)) == NULL) {
267                                 error = ENOENT;
268                                 break;
269                         }
270                         /* Purge a rule that refers to this hook. */
271                         NG_HOOK_SET_PRIVATE(hook, NULL);
272                         LIST_REMOVE(f, next);
273                         priv->nent--;
274                         free(f, M_NETGRAPH);
275                         break;
276                 case NGM_VLAN_GET_TABLE:
277                         NG_MKRESPONSE(resp, msg, sizeof(*t) +
278                             priv->nent * sizeof(*t->filter), M_NOWAIT);
279                         if (resp == NULL) {
280                                 error = ENOMEM;
281                                 break;
282                         }
283                         t = (struct ng_vlan_table *)resp->data;
284                         t->n = priv->nent;
285                         vf = &t->filter[0];
286                         for (i = 0; i < HASHSIZE; i++) {
287                                 LIST_FOREACH(f, &priv->hashtable[i], next) {
288                                         vf->vlan = f->vlan;
289                                         strncpy(vf->hook, NG_HOOK_NAME(f->hook),
290                                             NG_HOOKSIZ);
291                                         vf++;
292                                 }
293                         }
294                         break;
295                 default:                /* Unknown command. */
296                         error = EINVAL;
297                         break;
298                 }
299                 break;
300         case NGM_FLOW_COOKIE:
301             {
302                 struct ng_mesg *copy;
303                 struct filterhead *chain;
304                 struct filter *f;
305
306                 /*
307                  * Flow control messages should come only
308                  * from downstream.
309                  */
310
311                 if (lasthook == NULL)
312                         break;
313                 if (lasthook != priv->downstream_hook)
314                         break;
315
316                 /* Broadcast the event to all uplinks. */
317                 for (i = 0, chain = priv->hashtable; i < HASHSIZE;
318                     i++, chain++)
319                 LIST_FOREACH(f, chain, next) {
320                         NG_COPYMESSAGE(copy, msg, M_NOWAIT);
321                         if (copy == NULL)
322                                 continue;
323                         NG_SEND_MSG_HOOK(error, node, copy, f->hook, 0);
324                 }
325
326                 break;
327             }
328         default:                        /* Unknown type cookie. */
329                 error = EINVAL;
330                 break;
331         }
332         NG_RESPOND_MSG(error, node, item, resp);
333         NG_FREE_MSG(msg);
334         return (error);
335 }
336
337 static int
338 ng_vlan_rcvdata(hook_p hook, item_p item)
339 {
340         const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
341         struct ether_header *eh;
342         struct ether_vlan_header *evl = NULL;
343         int error;
344         u_int16_t vlan;
345         struct mbuf *m;
346         struct filter *f;
347
348         /* Make sure we have an entire header. */
349         NGI_GET_M(item, m);
350         if (m->m_len < sizeof(*eh) &&
351             (m = m_pullup(m, sizeof(*eh))) == NULL) {
352                 NG_FREE_ITEM(item);
353                 return (EINVAL);
354         }
355         eh = mtod(m, struct ether_header *);
356         if (hook == priv->downstream_hook) {
357                 /*
358                  * If from downstream, select between a match hook
359                  * or the nomatch hook.
360                  */
361                 if (m->m_flags & M_VLANTAG ||
362                     eh->ether_type == htons(ETHERTYPE_VLAN)) {
363                         if (m->m_flags & M_VLANTAG) {
364                                 /*
365                                  * Packet is tagged, m contains a normal
366                                  * Ethernet frame; tag is stored out-of-band.
367                                  */
368                                 vlan = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
369                         } else {
370                                 if (m->m_len < sizeof(*evl) &&
371                                     (m = m_pullup(m, sizeof(*evl))) == NULL) {
372                                         NG_FREE_ITEM(item);
373                                         return (EINVAL);
374                                 }
375                                 evl = mtod(m, struct ether_vlan_header *);
376                                 vlan = EVL_VLANOFTAG(ntohs(evl->evl_tag));
377                         }
378                         if ((f = ng_vlan_findentry(priv, vlan)) != NULL) {
379                                 if (m->m_flags & M_VLANTAG) {
380                                         m->m_pkthdr.ether_vtag = 0;
381                                         m->m_flags &= ~M_VLANTAG;
382                                 } else {
383                                         evl->evl_encap_proto = evl->evl_proto;
384                                         bcopy(mtod(m, caddr_t),
385                                             mtod(m, caddr_t) +
386                                             ETHER_VLAN_ENCAP_LEN,
387                                             ETHER_HDR_LEN);
388                                         m_adj(m, ETHER_VLAN_ENCAP_LEN);
389                                 }
390                         }
391                 } else
392                         f = NULL;
393                 if (f != NULL)
394                         NG_FWD_NEW_DATA(error, item, f->hook, m);
395                 else
396                         NG_FWD_NEW_DATA(error, item, priv->nomatch_hook, m);
397         } else {
398                 /*
399                  * It is heading towards the downstream.
400                  * If from nomatch, pass it unmodified.
401                  * Otherwise, do the VLAN encapsulation.
402                  */
403                 if (hook != priv->nomatch_hook) {
404                         if ((f = NG_HOOK_PRIVATE(hook)) == NULL) {
405                                 NG_FREE_ITEM(item);
406                                 NG_FREE_M(m);
407                                 return (EOPNOTSUPP);
408                         }
409                         M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_DONTWAIT);
410                         /* M_PREPEND takes care of m_len and m_pkthdr.len. */
411                         if (m == NULL || (m->m_len < sizeof(*evl) &&
412                             (m = m_pullup(m, sizeof(*evl))) == NULL)) {
413                                 NG_FREE_ITEM(item);
414                                 return (ENOMEM);
415                         }
416                         /*
417                          * Transform the Ethernet header into an Ethernet header
418                          * with 802.1Q encapsulation.
419                          */
420                         bcopy(mtod(m, char *) + ETHER_VLAN_ENCAP_LEN,
421                             mtod(m, char *), ETHER_HDR_LEN);
422                         evl = mtod(m, struct ether_vlan_header *);
423                         evl->evl_proto = evl->evl_encap_proto;
424                         evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
425                         evl->evl_tag = htons(f->vlan);
426                 }
427                 NG_FWD_NEW_DATA(error, item, priv->downstream_hook, m);
428         }
429         return (error);
430 }
431
432 static int
433 ng_vlan_shutdown(node_p node)
434 {
435         const priv_p priv = NG_NODE_PRIVATE(node);
436
437         NG_NODE_SET_PRIVATE(node, NULL);
438         NG_NODE_UNREF(node);
439         free(priv, M_NETGRAPH);
440         return (0);
441 }
442
443 static int
444 ng_vlan_disconnect(hook_p hook)
445 {
446         const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
447         struct filter *f;
448
449         if (hook == priv->downstream_hook)
450                 priv->downstream_hook = NULL;
451         else if (hook == priv->nomatch_hook)
452                 priv->nomatch_hook = NULL;
453         else {
454                 /* Purge a rule that refers to this hook. */
455                 if ((f = NG_HOOK_PRIVATE(hook)) != NULL) {
456                         LIST_REMOVE(f, next);
457                         priv->nent--;
458                         free(f, M_NETGRAPH);
459                 }
460         }
461         NG_HOOK_SET_PRIVATE(hook, NULL);
462         if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
463             (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
464                 ng_rmnode_self(NG_HOOK_NODE(hook));
465         return (0);
466 }