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_arp.h>
43 #include <net/if_vlan_var.h>
44
45 #include <netgraph/ng_message.h>
46 #include <netgraph/ng_parse.h>
47 #include <netgraph/ng_vlan.h>
48 #include <netgraph/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 NG_ABI_VERSION,
119 NG_VLAN_NODE_TYPE,
120 NULL,
121 ng_vlan_constructor,
122 ng_vlan_rcvmsg,
123 ng_vlan_shutdown,
124 ng_vlan_newhook,
125 NULL,
126 NULL,
127 ng_vlan_rcvdata,
128 ng_vlan_rcvdata,
129 ng_vlan_disconnect,
130 ng_vlan_cmdlist
131 };
132 NETGRAPH_INIT(vlan, &ng_vlan_typestruct);
133
134 struct filter {
135 LIST_ENTRY(filter) next;
136 u_int16_t vlan;
137 hook_p hook;
138 };
139
140 #define HASHSIZE 16
141 #define HASH(id) ((((id) >> 8) ^ ((id) >> 4) ^ (id)) & 0x0f)
142 LIST_HEAD(filterhead, filter);
143
144 typedef struct {
145 hook_p downstream_hook;
146 hook_p nomatch_hook;
147 struct filterhead hashtable[HASHSIZE];
148 u_int32_t nent;
149 } *priv_p;
150
151 static struct filter *
152 ng_vlan_findentry(priv_p priv, u_int16_t vlan)
153 {
154 struct filterhead *chain = &priv->hashtable[HASH(vlan)];
155 struct filter *f;
156
157 LIST_FOREACH(f, chain, next)
158 if (f->vlan == vlan)
159 return (f);
160 return (NULL);
161 }
162
163 static int
164 ng_vlan_constructor(node_p *nodep)
165 {
166 priv_p priv;
167 int error, i;
168
169 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO);
170 if (priv == NULL)
171 return (ENOMEM);
172 for (i = 0; i < HASHSIZE; i++)
173 LIST_INIT(&priv->hashtable[i]);
174 /* Call the generic node constructor. */
175 if ((error = ng_make_node_common(&ng_vlan_typestruct, nodep)) != 0) {
176 FREE(priv, M_NETGRAPH);
177 return (error);
178 }
179 NG_NODE_SET_PRIVATE(*nodep, priv);
180 return (0);
181 }
182
183 static int
184 ng_vlan_newhook(node_p node, hook_p hook, const char *name)
185 {
186 const priv_p priv = NG_NODE_PRIVATE(node);
187
188 if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0)
189 priv->downstream_hook = hook;
190 else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0)
191 priv->nomatch_hook = hook;
192 else {
193 /*
194 * Any other hook name is valid and can
195 * later be associated with a filter rule.
196 */
197 }
198 NG_HOOK_SET_PRIVATE(hook, NULL);
199 return (0);
200 }
201
202 static int
203 ng_vlan_rcvmsg(node_p node, struct ng_mesg *msg, const char *retaddr,
204 struct ng_mesg **rptr)
205 {
206 const priv_p priv = NG_NODE_PRIVATE(node);
207 int error = 0;
208 struct ng_mesg *resp = NULL;
209 struct ng_vlan_filter *vf;
210 struct filter *f;
211 hook_p hook;
212 struct ng_vlan_table *t;
213 int i;
214
215 /* Deal with message according to cookie and command. */
216 switch (msg->header.typecookie) {
217 case NGM_VLAN_COOKIE:
218 switch (msg->header.cmd) {
219 case NGM_VLAN_ADD_FILTER:
220 /* Check that message is long enough. */
221 if (msg->header.arglen != sizeof(*vf)) {
222 error = EINVAL;
223 break;
224 }
225 vf = (struct ng_vlan_filter *)msg->data;
226 /* Sanity check the VLAN ID value. */
227 #ifndef EVL_VLID_MASK
228 #define EVL_VLID_MASK 0x0FFF
229 #endif
230 if (vf->vlan & ~EVL_VLID_MASK) {
231 error = EINVAL;
232 break;
233 }
234 /* Check that a referenced hook exists. */
235 hook = ng_findhook(node, vf->hook);
236 if (hook == NULL) {
237 error = ENOENT;
238 break;
239 }
240 /* And is not one of the special hooks. */
241 if (hook == priv->downstream_hook ||
242 hook == priv->nomatch_hook) {
243 error = EINVAL;
244 break;
245 }
246 /* And is not already in service. */
247 if (NG_HOOK_PRIVATE(hook) != NULL) {
248 error = EEXIST;
249 break;
250 }
251 /* Check we don't already trap this VLAN. */
252 if (ng_vlan_findentry(priv, vf->vlan)) {
253 error = EEXIST;
254 break;
255 }
256 /* Create filter. */
257 MALLOC(f, struct filter *, sizeof(*f),
258 M_NETGRAPH, M_NOWAIT | M_ZERO);
259 if (f == NULL) {
260 error = ENOMEM;
261 break;
262 }
263 /* Link filter and hook together. */
264 f->hook = hook;
265 f->vlan = vf->vlan;
266 NG_HOOK_SET_PRIVATE(hook, f);
267 /* Register filter in a hash table. */
268 LIST_INSERT_HEAD(
269 &priv->hashtable[HASH(f->vlan)], f, next);
270 priv->nent++;
271 break;
272 case NGM_VLAN_DEL_FILTER:
273 /* Check that message is long enough. */
274 if (msg->header.arglen != NG_HOOKLEN + 1) {
275 error = EINVAL;
276 break;
277 }
278 /* Check that hook exists and is active. */
279 hook = ng_findhook(node, (char *)msg->data);
280 if (hook == NULL ||
281 (f = NG_HOOK_PRIVATE(hook)) == NULL) {
282 error = ENOENT;
283 break;
284 }
285 /* Purge a rule that refers to this hook. */
286 NG_HOOK_SET_PRIVATE(hook, NULL);
287 LIST_REMOVE(f, next);
288 priv->nent--;
289 FREE(f, M_NETGRAPH);
290 break;
291 case NGM_VLAN_GET_TABLE:
292 NG_MKRESPONSE(resp, msg, sizeof(*t) +
293 priv->nent * sizeof(*t->filter), M_NOWAIT);
294 if (resp == NULL) {
295 error = ENOMEM;
296 break;
297 }
298 t = (struct ng_vlan_table *)resp->data;
299 t->n = priv->nent;
300 vf = &t->filter[0];
301 for (i = 0; i < HASHSIZE; i++) {
302 LIST_FOREACH(f, &priv->hashtable[i], next) {
303 vf->vlan = f->vlan;
304 strncpy(vf->hook, NG_HOOK_NAME(f->hook),
305 NG_HOOKLEN + 1);
306 vf++;
307 }
308 }
309 break;
310 default: /* Unknown command. */
311 error = EINVAL;
312 break;
313 }
314 break;
315 default: /* Unknown type cookie. */
316 error = EINVAL;
317 break;
318 }
319 NG_RESPOND_MSG(error, node, retaddr, resp, rptr);
320 NG_FREE_MSG(msg);
321 return (error);
322 }
323
324 static int
325 ng_vlan_rcvdata(hook_p hook, struct mbuf *m, meta_p meta)
326 {
327 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
328 struct ether_header *eh;
329 struct ether_vlan_header *evl;
330 int error;
331 u_int16_t vlan;
332 struct filter *f;
333
334 /* Make sure we have an entire header. */
335 if (m->m_len < sizeof(*eh) &&
336 (m = m_pullup(m, sizeof(*eh))) == NULL) {
337 NG_FREE_META(meta);
338 return (EINVAL);
339 }
340 eh = mtod(m, struct ether_header *);
341 if (hook == priv->downstream_hook) {
342 /*
343 * If from downstream, select between a match hook
344 * or the nomatch hook.
345 */
346 if (eh->ether_type == htons(ETHERTYPE_VLAN)) {
347 {
348 if (m->m_len < sizeof(*evl) &&
349 (m = m_pullup(m, sizeof(*evl))) == NULL) {
350 NG_FREE_META(meta);
351 return (EINVAL);
352 }
353 evl = mtod(m, struct ether_vlan_header *);
354 vlan = EVL_VLANOFTAG(ntohs(evl->evl_tag));
355 }
356 if ((f = ng_vlan_findentry(priv, vlan)) != NULL) {
357 {
358 evl->evl_encap_proto = evl->evl_proto;
359 bcopy(mtod(m, caddr_t),
360 mtod(m, caddr_t) +
361 EVL_ENCAPLEN,
362 ETHER_HDR_LEN);
363 m_adj(m, EVL_ENCAPLEN);
364 }
365 }
366 } else
367 f = NULL;
368 if (f != NULL)
369 NG_SEND_DATA(error, f->hook, m, meta);
370 else
371 NG_SEND_DATA(error, priv->nomatch_hook, m, meta);
372 } else {
373 /*
374 * It is heading towards the downstream.
375 * If from nomatch, pass it unmodified.
376 * Otherwise, do the VLAN encapsulation.
377 */
378 if (hook != priv->nomatch_hook) {
379 if ((f = NG_HOOK_PRIVATE(hook)) == NULL) {
380 NG_FREE_DATA(m, meta);
381 return (EOPNOTSUPP);
382 }
383 M_PREPEND(m, EVL_ENCAPLEN, M_DONTWAIT);
384 /* M_PREPEND takes care of m_len and m_pkthdr.len. */
385 if (m == NULL || (m->m_len < sizeof(*evl) &&
386 (m = m_pullup(m, sizeof(*evl))) == NULL)) {
387 NG_FREE_META(meta);
388 return (ENOMEM);
389 }
390 /*
391 * Transform the Ethernet header into an Ethernet header
392 * with 802.1Q encapsulation.
393 */
394 bcopy(mtod(m, char *) + EVL_ENCAPLEN,
395 mtod(m, char *), ETHER_HDR_LEN);
396 evl = mtod(m, struct ether_vlan_header *);
397 evl->evl_proto = evl->evl_encap_proto;
398 evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
399 evl->evl_tag = htons(f->vlan);
400 }
401 NG_SEND_DATA(error, priv->downstream_hook, m, meta);
402 }
403 return (error);
404 }
405
406 static int
407 ng_vlan_shutdown(node_p node)
408 {
409 const priv_p priv = NG_NODE_PRIVATE(node);
410
411 node->flags |= NG_INVALID;
412 ng_cutlinks(node);
413 ng_unname(node);
414 NG_NODE_SET_PRIVATE(node, NULL);
415 NG_NODE_UNREF(node);
416 FREE(priv, M_NETGRAPH);
417 return (0);
418 }
419
420 static int
421 ng_vlan_disconnect(hook_p hook)
422 {
423 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
424 struct filter *f;
425
426 if (hook == priv->downstream_hook)
427 priv->downstream_hook = NULL;
428 else if (hook == priv->nomatch_hook)
429 priv->nomatch_hook = NULL;
430 else {
431 /* Purge a rule that refers to this hook. */
432 if ((f = NG_HOOK_PRIVATE(hook)) != NULL) {
433 LIST_REMOVE(f, next);
434 priv->nent--;
435 FREE(f, M_NETGRAPH);
436 }
437 }
438 NG_HOOK_SET_PRIVATE(hook, NULL);
439 if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
440 (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
441 ng_rmnode(NG_HOOK_NODE(hook));
442 return (0);
443 }
Cache object: db6ccef2062c4966484cc2071e6cf8e3
|