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