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