]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/netinet/ip_fw_pfil.c
Avoid data copying when it is possible.
[FreeBSD/FreeBSD.git] / sys / netinet / ip_fw_pfil.c
1 /*-
2  * Copyright (c) 2004 Andre Oppermann, Internet Business Solutions AG
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
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29
30 #if !defined(KLD_MODULE)
31 #include "opt_ipfw.h"
32 #include "opt_ipdn.h"
33 #include "opt_inet.h"
34 #ifndef INET
35 #error IPFIREWALL requires INET.
36 #endif /* INET */
37 #endif /* KLD_MODULE */
38 #include "opt_inet6.h"
39
40 #include <sys/param.h>
41 #include <sys/systm.h>
42 #include <sys/malloc.h>
43 #include <sys/mbuf.h>
44 #include <sys/module.h>
45 #include <sys/kernel.h>
46 #include <sys/socket.h>
47 #include <sys/socketvar.h>
48 #include <sys/sysctl.h>
49 #include <sys/ucred.h>
50
51 #include <net/if.h>
52 #include <net/route.h>
53 #include <net/pfil.h>
54
55 #include <netinet/in.h>
56 #include <netinet/in_systm.h>
57 #include <netinet/in_var.h>
58 #include <netinet/ip.h>
59 #include <netinet/ip_var.h>
60 #include <netinet/ip_fw.h>
61 #include <netinet/ip_divert.h>
62 #include <netinet/ip_dummynet.h>
63
64 #include <netgraph/ng_ipfw.h>
65
66 #include <machine/in_cksum.h>
67
68 int fw_enable = 1;
69 #ifdef INET6
70 int fw6_enable = 1;
71 #endif
72
73 int ipfw_chg_hook(SYSCTL_HANDLER_ARGS);
74
75 /* Dummynet hooks. */
76 ip_dn_ruledel_t *ip_dn_ruledel_ptr = NULL;
77
78 /* Divert hooks. */
79 ip_divert_packet_t *ip_divert_ptr = NULL;
80
81 /* ng_ipfw hooks. */
82 ng_ipfw_input_t *ng_ipfw_input_p = NULL;
83
84 /* Forward declarations. */
85 static int      ipfw_divert(struct mbuf **, int, int);
86 #define DIV_DIR_IN      1
87 #define DIV_DIR_OUT     0
88
89 int
90 ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
91     struct inpcb *inp)
92 {
93         struct ip_fw_args args;
94         struct ng_ipfw_tag *ng_tag;
95         struct m_tag *dn_tag;
96         int ipfw = 0;
97         int divert;
98         int tee;
99 #ifdef IPFIREWALL_FORWARD
100         struct m_tag *fwd_tag;
101 #endif
102
103         KASSERT(dir == PFIL_IN, ("ipfw_check_in wrong direction!"));
104
105         bzero(&args, sizeof(args));
106
107         ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
108             NULL);
109         if (ng_tag != NULL) {
110                 KASSERT(ng_tag->dir == NG_IPFW_IN,
111                     ("ng_ipfw tag with wrong direction"));
112                 args.rule = ng_tag->rule;
113                 m_tag_delete(*m0, (struct m_tag *)ng_tag);
114         }
115
116 again:
117         dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
118         if (dn_tag != NULL){
119                 struct dn_pkt_tag *dt;
120
121                 dt = (struct dn_pkt_tag *)(dn_tag+1);
122                 args.rule = dt->rule;
123
124                 m_tag_delete(*m0, dn_tag);
125         }
126
127         args.m = *m0;
128         args.inp = inp;
129         ipfw = ipfw_chk(&args);
130         *m0 = args.m;
131         tee = 0;
132
133         KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
134             __func__));
135
136         switch (ipfw) {
137         case IP_FW_PASS:
138                 if (args.next_hop == NULL)
139                         goto pass;
140
141 #ifdef IPFIREWALL_FORWARD
142                 fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
143                                 sizeof(struct sockaddr_in), M_NOWAIT);
144                 if (fwd_tag == NULL)
145                         goto drop;
146                 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
147                 m_tag_prepend(*m0, fwd_tag);
148
149                 if (in_localip(args.next_hop->sin_addr))
150                         (*m0)->m_flags |= M_FASTFWD_OURS;
151                 goto pass;
152 #endif
153                 break;                  /* not reached */
154
155         case IP_FW_DENY:
156                 goto drop;
157                 break;                  /* not reached */
158
159         case IP_FW_DUMMYNET:
160                 if (!DUMMYNET_LOADED)
161                         goto drop;
162                 if (mtod(*m0, struct ip *)->ip_v == 4)
163                         ip_dn_io_ptr(m0, DN_TO_IP_IN, &args);
164                 else if (mtod(*m0, struct ip *)->ip_v == 6)
165                         ip_dn_io_ptr(m0, DN_TO_IP6_IN, &args);
166                 if (*m0 != NULL)
167                         goto again;
168                 return 0;               /* packet consumed */
169
170         case IP_FW_TEE:
171                 tee = 1;
172                 /* fall through */
173
174         case IP_FW_DIVERT:
175                 divert = ipfw_divert(m0, DIV_DIR_IN, tee);
176                 if (divert) {
177                         *m0 = NULL;
178                         return 0;       /* packet consumed */
179                 } else {
180                         args.rule = NULL;
181                         goto again;     /* continue with packet */
182                 }
183
184         case IP_FW_NGTEE:
185                 if (!NG_IPFW_LOADED)
186                         goto drop;
187                 (void)ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 1);
188                 goto again;             /* continue with packet */
189
190         case IP_FW_NETGRAPH:
191                 if (!NG_IPFW_LOADED)
192                         goto drop;
193                 return ng_ipfw_input_p(m0, NG_IPFW_IN, &args, 0);
194                 
195         case IP_FW_NAT:
196                 goto again;             /* continue with packet */
197
198         default:
199                 KASSERT(0, ("%s: unknown retval", __func__));
200         }
201
202 drop:
203         if (*m0)
204                 m_freem(*m0);
205         *m0 = NULL;
206         return (EACCES);
207 pass:
208         return 0;       /* not filtered */
209 }
210
211 int
212 ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
213     struct inpcb *inp)
214 {
215         struct ip_fw_args args;
216         struct ng_ipfw_tag *ng_tag;
217         struct m_tag *dn_tag;
218         int ipfw = 0;
219         int divert;
220         int tee;
221 #ifdef IPFIREWALL_FORWARD
222         struct m_tag *fwd_tag;
223 #endif
224
225         KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!"));
226
227         bzero(&args, sizeof(args));
228
229         ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
230             NULL);
231         if (ng_tag != NULL) {
232                 KASSERT(ng_tag->dir == NG_IPFW_OUT,
233                     ("ng_ipfw tag with wrong direction"));
234                 args.rule = ng_tag->rule;
235                 m_tag_delete(*m0, (struct m_tag *)ng_tag);
236         }
237
238 again:
239         dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
240         if (dn_tag != NULL) {
241                 struct dn_pkt_tag *dt;
242
243                 dt = (struct dn_pkt_tag *)(dn_tag+1);
244                 args.rule = dt->rule;
245
246                 m_tag_delete(*m0, dn_tag);
247         }
248
249         args.m = *m0;
250         args.oif = ifp;
251         args.inp = inp;
252         ipfw = ipfw_chk(&args);
253         *m0 = args.m;
254         tee = 0;
255
256         KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
257             __func__));
258
259         switch (ipfw) {
260         case IP_FW_PASS:
261                 if (args.next_hop == NULL)
262                         goto pass;
263 #ifdef IPFIREWALL_FORWARD
264                 /* Overwrite existing tag. */
265                 fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL);
266                 if (fwd_tag == NULL) {
267                         fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
268                                 sizeof(struct sockaddr_in), M_NOWAIT);
269                         if (fwd_tag == NULL)
270                                 goto drop;
271                 } else
272                         m_tag_unlink(*m0, fwd_tag);
273                 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
274                 m_tag_prepend(*m0, fwd_tag);
275
276                 if (in_localip(args.next_hop->sin_addr))
277                         (*m0)->m_flags |= M_FASTFWD_OURS;
278                 goto pass;
279 #endif
280                 break;                  /* not reached */
281
282         case IP_FW_DENY:
283                 goto drop;
284                 break;                  /* not reached */
285
286         case IP_FW_DUMMYNET:
287                 if (!DUMMYNET_LOADED)
288                         break;
289                 if (mtod(*m0, struct ip *)->ip_v == 4)
290                         ip_dn_io_ptr(m0, DN_TO_IP_OUT, &args);
291                 else if (mtod(*m0, struct ip *)->ip_v == 6)
292                         ip_dn_io_ptr(m0, DN_TO_IP6_OUT, &args);
293                 if (*m0 != NULL)
294                         goto again;
295                 return 0;               /* packet consumed */
296
297                 break;
298
299         case IP_FW_TEE:
300                 tee = 1;
301                 /* fall through */
302
303         case IP_FW_DIVERT:
304                 divert = ipfw_divert(m0, DIV_DIR_OUT, tee);
305                 if (divert) {
306                         *m0 = NULL;
307                         return 0;       /* packet consumed */
308                 } else {
309                         args.rule = NULL;
310                         goto again;     /* continue with packet */
311                 }
312
313         case IP_FW_NGTEE:
314                 if (!NG_IPFW_LOADED)
315                         goto drop;
316                 (void)ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 1);
317                 goto again;             /* continue with packet */
318
319         case IP_FW_NETGRAPH:
320                 if (!NG_IPFW_LOADED)
321                         goto drop;
322                 return ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 0);
323
324         case IP_FW_NAT:
325                 goto again;             /* continue with packet */
326                 
327         default:
328                 KASSERT(0, ("%s: unknown retval", __func__));
329         }
330
331 drop:
332         if (*m0)
333                 m_freem(*m0);
334         *m0 = NULL;
335         return (EACCES);
336 pass:
337         return 0;       /* not filtered */
338 }
339
340 static int
341 ipfw_divert(struct mbuf **m, int incoming, int tee)
342 {
343         /*
344          * ipfw_chk() has already tagged the packet with the divert tag.
345          * If tee is set, copy packet and return original.
346          * If not tee, consume packet and send it to divert socket.
347          */
348         struct mbuf *clone, *reass;
349         struct ip *ip;
350         int hlen;
351
352         reass = NULL;
353
354         /* Is divert module loaded? */
355         if (ip_divert_ptr == NULL)
356                 goto nodivert;
357
358         /* Cloning needed for tee? */
359         if (tee)
360                 clone = m_dup(*m, M_DONTWAIT);
361         else
362                 clone = *m;
363
364         /* In case m_dup was unable to allocate mbufs. */
365         if (clone == NULL)
366                 goto teeout;
367
368         /*
369          * Divert listeners can only handle non-fragmented packets.
370          * However when tee is set we will *not* de-fragment the packets;
371          * Doing do would put the reassembly into double-jeopardy.  On top
372          * of that someone doing a tee will probably want to get the packet
373          * in its original form.
374          */
375         ip = mtod(clone, struct ip *);
376         if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) {
377
378                 /* Reassemble packet. */
379                 reass = ip_reass(clone);
380
381                 /*
382                  * IP header checksum fixup after reassembly and leave header
383                  * in network byte order.
384                  */
385                 if (reass != NULL) {
386                         ip = mtod(reass, struct ip *);
387                         hlen = ip->ip_hl << 2;
388                         ip->ip_len = htons(ip->ip_len);
389                         ip->ip_off = htons(ip->ip_off);
390                         ip->ip_sum = 0;
391                         if (hlen == sizeof(struct ip))
392                                 ip->ip_sum = in_cksum_hdr(ip);
393                         else
394                                 ip->ip_sum = in_cksum(reass, hlen);
395                         clone = reass;
396                 } else
397                         clone = NULL;
398         } else {
399                 /* Convert header to network byte order. */
400                 ip->ip_len = htons(ip->ip_len);
401                 ip->ip_off = htons(ip->ip_off);
402         }
403
404         /* Do the dirty job... */
405         if (clone && ip_divert_ptr != NULL)
406                 ip_divert_ptr(clone, incoming);
407
408 teeout:
409         /*
410          * For tee we leave the divert tag attached to original packet.
411          * It will then continue rule evaluation after the tee rule.
412          */
413         if (tee)
414                 return 0;
415
416         /* Packet diverted and consumed */
417         return 1;
418
419 nodivert:
420         m_freem(*m);
421         return 1;
422 }
423
424 static int
425 ipfw_hook(void)
426 {
427         struct pfil_head *pfh_inet;
428
429         pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
430         if (pfh_inet == NULL)
431                 return ENOENT;
432
433         pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
434         pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
435
436         return 0;
437 }
438
439 static int
440 ipfw_unhook(void)
441 {
442         struct pfil_head *pfh_inet;
443
444         pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
445         if (pfh_inet == NULL)
446                 return ENOENT;
447
448         pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
449         pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
450
451         return 0;
452 }
453
454 #ifdef INET6
455 static int
456 ipfw6_hook(void)
457 {
458         struct pfil_head *pfh_inet6;
459
460         pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
461         if (pfh_inet6 == NULL)
462                 return ENOENT;
463
464         pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6);
465         pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6);
466
467         return 0;
468 }
469
470 static int
471 ipfw6_unhook(void)
472 {
473         struct pfil_head *pfh_inet6;
474
475         pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
476         if (pfh_inet6 == NULL)
477                 return ENOENT;
478
479         pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6);
480         pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6);
481
482         return 0;
483 }
484 #endif /* INET6 */
485
486 int
487 ipfw_chg_hook(SYSCTL_HANDLER_ARGS)
488 {
489         int enable = *(int *)arg1;
490         int error;
491
492         error = sysctl_handle_int(oidp, &enable, 0, req);
493         if (error)
494                 return (error);
495
496         enable = (enable) ? 1 : 0;
497
498         if (enable == *(int *)arg1)
499                 return (0);
500
501         if (arg1 == &fw_enable) {
502                 if (enable)
503                         error = ipfw_hook();
504                 else
505                         error = ipfw_unhook();
506         }
507 #ifdef INET6
508         if (arg1 == &fw6_enable) {
509                 if (enable)
510                         error = ipfw6_hook();
511                 else
512                         error = ipfw6_unhook();
513         }
514 #endif
515
516         if (error)
517                 return (error);
518
519         *(int *)arg1 = enable;
520
521         return (0);
522 }
523
524 static int
525 ipfw_modevent(module_t mod, int type, void *unused)
526 {
527         int err = 0;
528
529         switch (type) {
530         case MOD_LOAD:
531                 if ((err = ipfw_init()) != 0) {
532                         printf("ipfw_init() error\n");
533                         break;
534                 }
535                 if ((err = ipfw_hook()) != 0) {
536                         printf("ipfw_hook() error\n");
537                         break;
538                 }
539 #ifdef INET6
540                 if ((err = ipfw6_hook()) != 0) {
541                         printf("ipfw_hook() error\n");
542                         break;
543                 }
544 #endif
545                 break;
546
547         case MOD_UNLOAD:
548                 if ((err = ipfw_unhook()) > 0)
549                         break;
550 #ifdef INET6
551                 if ((err = ipfw6_unhook()) > 0)
552                         break;
553 #endif
554                 ipfw_destroy();
555                 break;
556
557         default:
558                 return EOPNOTSUPP;
559                 break;
560         }
561         return err;
562 }
563
564 static moduledata_t ipfwmod = {
565         "ipfw",
566         ipfw_modevent,
567         0
568 };
569 DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY);
570 MODULE_VERSION(ipfw, 2);