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