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