Update the work on ipfw tables, reduce diffs.
[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         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 (ip_dn_io_ptr == NULL)
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         case IP_FW_REASS:
199                 goto again;
200
201         default:
202                 KASSERT(0, ("%s: unknown retval", __func__));
203         }
204
205 drop:
206         if (*m0)
207                 m_freem(*m0);
208         *m0 = NULL;
209         return (EACCES);
210 pass:
211         return 0;       /* not filtered */
212 }
213
214 int
215 ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
216     struct inpcb *inp)
217 {
218         struct ip_fw_args args;
219         struct ng_ipfw_tag *ng_tag;
220         struct m_tag *dn_tag;
221         int ipfw = 0;
222         int divert;
223         int tee;
224 #ifdef IPFIREWALL_FORWARD
225         struct m_tag *fwd_tag;
226 #endif
227
228         KASSERT(dir == PFIL_OUT, ("ipfw_check_out wrong direction!"));
229
230         bzero(&args, sizeof(args));
231
232         ng_tag = (struct ng_ipfw_tag *)m_tag_locate(*m0, NGM_IPFW_COOKIE, 0,
233             NULL);
234         if (ng_tag != NULL) {
235                 KASSERT(ng_tag->dir == NG_IPFW_OUT,
236                     ("ng_ipfw tag with wrong direction"));
237                 args.rule = ng_tag->rule;
238                 args.rule_id = ng_tag->rule_id;
239                 args.chain_id = ng_tag->chain_id;
240                 m_tag_delete(*m0, (struct m_tag *)ng_tag);
241         }
242
243 again:
244         dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
245         if (dn_tag != NULL) {
246                 struct dn_pkt_tag *dt;
247
248                 dt = (struct dn_pkt_tag *)(dn_tag+1);
249                 args.rule = dt->rule;
250                 args.rule_id = dt->rule_id;
251                 args.chain_id = dt->chain_id;
252
253                 m_tag_delete(*m0, dn_tag);
254         }
255
256         args.m = *m0;
257         args.oif = ifp;
258         args.inp = inp;
259         ipfw = ipfw_chk(&args);
260         *m0 = args.m;
261         tee = 0;
262
263         KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
264             __func__));
265
266         switch (ipfw) {
267         case IP_FW_PASS:
268                 if (args.next_hop == NULL)
269                         goto pass;
270 #ifdef IPFIREWALL_FORWARD
271                 /* Overwrite existing tag. */
272                 fwd_tag = m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL);
273                 if (fwd_tag == NULL) {
274                         fwd_tag = m_tag_get(PACKET_TAG_IPFORWARD,
275                                 sizeof(struct sockaddr_in), M_NOWAIT);
276                         if (fwd_tag == NULL)
277                                 goto drop;
278                 } else
279                         m_tag_unlink(*m0, fwd_tag);
280                 bcopy(args.next_hop, (fwd_tag+1), sizeof(struct sockaddr_in));
281                 m_tag_prepend(*m0, fwd_tag);
282
283                 if (in_localip(args.next_hop->sin_addr))
284                         (*m0)->m_flags |= M_FASTFWD_OURS;
285                 goto pass;
286 #endif
287                 break;                  /* not reached */
288
289         case IP_FW_DENY:
290                 goto drop;
291                 break;                  /* not reached */
292
293         case IP_FW_DUMMYNET:
294                 if (ip_dn_io_ptr == NULL)
295                         break;
296                 if (mtod(*m0, struct ip *)->ip_v == 4)
297                         ip_dn_io_ptr(m0, DN_TO_IP_OUT, &args);
298                 else if (mtod(*m0, struct ip *)->ip_v == 6)
299                         ip_dn_io_ptr(m0, DN_TO_IP6_OUT, &args);
300                 if (*m0 != NULL)
301                         goto again;
302                 return 0;               /* packet consumed */
303
304                 break;
305
306         case IP_FW_TEE:
307                 tee = 1;
308                 /* fall through */
309
310         case IP_FW_DIVERT:
311                 divert = ipfw_divert(m0, DIV_DIR_OUT, tee);
312                 if (divert) {
313                         *m0 = NULL;
314                         return 0;       /* packet consumed */
315                 } else {
316                         args.rule = NULL;
317                         goto again;     /* continue with packet */
318                 }
319
320         case IP_FW_NGTEE:
321                 if (!NG_IPFW_LOADED)
322                         goto drop;
323                 (void)ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 1);
324                 goto again;             /* continue with packet */
325
326         case IP_FW_NETGRAPH:
327                 if (!NG_IPFW_LOADED)
328                         goto drop;
329                 return ng_ipfw_input_p(m0, NG_IPFW_OUT, &args, 0);
330
331         case IP_FW_NAT:
332                 goto again;             /* continue with packet */
333                 
334         case IP_FW_REASS:
335                 goto again;     
336         
337         default:
338                 KASSERT(0, ("%s: unknown retval", __func__));
339         }
340
341 drop:
342         if (*m0)
343                 m_freem(*m0);
344         *m0 = NULL;
345         return (EACCES);
346 pass:
347         return 0;       /* not filtered */
348 }
349
350 static int
351 ipfw_divert(struct mbuf **m, int incoming, int tee)
352 {
353         /*
354          * ipfw_chk() has already tagged the packet with the divert tag.
355          * If tee is set, copy packet and return original.
356          * If not tee, consume packet and send it to divert socket.
357          */
358         struct mbuf *clone, *reass;
359         struct ip *ip;
360         int hlen;
361
362         reass = NULL;
363
364         /* Is divert module loaded? */
365         if (ip_divert_ptr == NULL)
366                 goto nodivert;
367
368         /* Cloning needed for tee? */
369         if (tee)
370                 clone = m_dup(*m, M_DONTWAIT);
371         else
372                 clone = *m;
373
374         /* In case m_dup was unable to allocate mbufs. */
375         if (clone == NULL)
376                 goto teeout;
377
378         /*
379          * Divert listeners can only handle non-fragmented packets.
380          * However when tee is set we will *not* de-fragment the packets;
381          * Doing do would put the reassembly into double-jeopardy.  On top
382          * of that someone doing a tee will probably want to get the packet
383          * in its original form.
384          */
385         ip = mtod(clone, struct ip *);
386         if (!tee && ip->ip_off & (IP_MF | IP_OFFMASK)) {
387
388                 /* Reassemble packet. */
389                 reass = ip_reass(clone);
390
391                 /*
392                  * IP header checksum fixup after reassembly and leave header
393                  * in network byte order.
394                  */
395                 if (reass != NULL) {
396                         ip = mtod(reass, struct ip *);
397                         hlen = ip->ip_hl << 2;
398                         ip->ip_len = htons(ip->ip_len);
399                         ip->ip_off = htons(ip->ip_off);
400                         ip->ip_sum = 0;
401                         if (hlen == sizeof(struct ip))
402                                 ip->ip_sum = in_cksum_hdr(ip);
403                         else
404                                 ip->ip_sum = in_cksum(reass, hlen);
405                         clone = reass;
406                 } else
407                         clone = NULL;
408         } else {
409                 /* Convert header to network byte order. */
410                 ip->ip_len = htons(ip->ip_len);
411                 ip->ip_off = htons(ip->ip_off);
412         }
413
414         /* Do the dirty job... */
415         if (clone && ip_divert_ptr != NULL)
416                 ip_divert_ptr(clone, incoming);
417
418 teeout:
419         /*
420          * For tee we leave the divert tag attached to original packet.
421          * It will then continue rule evaluation after the tee rule.
422          */
423         if (tee)
424                 return 0;
425
426         /* Packet diverted and consumed */
427         return 1;
428
429 nodivert:
430         m_freem(*m);
431         return 1;
432 }
433
434 int
435 ipfw_hook(void)
436 {
437         struct pfil_head *pfh_inet;
438
439         pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
440         if (pfh_inet == NULL)
441                 return ENOENT;
442
443         (void)pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK,
444                 pfh_inet);
445         (void)pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
446                 pfh_inet);
447
448         return 0;
449 }
450
451 int
452 ipfw_unhook(void)
453 {
454         struct pfil_head *pfh_inet;
455
456         pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
457         if (pfh_inet == NULL)
458                 return ENOENT;
459
460         (void)pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK,
461                 pfh_inet);
462         (void)pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
463                 pfh_inet);
464
465         return 0;
466 }
467
468 #ifdef INET6
469 int
470 ipfw6_hook(void)
471 {
472         struct pfil_head *pfh_inet6;
473
474         pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
475         if (pfh_inet6 == NULL)
476                 return ENOENT;
477
478         (void)pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK,
479                 pfh_inet6);
480         (void)pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
481                 pfh_inet6);
482
483         return 0;
484 }
485
486 int
487 ipfw6_unhook(void)
488 {
489         struct pfil_head *pfh_inet6;
490
491         pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6);
492         if (pfh_inet6 == NULL)
493                 return ENOENT;
494
495         (void)pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK,
496                 pfh_inet6);
497         (void)pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK,
498                 pfh_inet6);
499
500         return 0;
501 }
502 #endif /* INET6 */
503
504 int
505 ipfw_chg_hook(SYSCTL_HANDLER_ARGS)
506 {
507         int enable = *(int *)arg1;
508         int error;
509
510         error = sysctl_handle_int(oidp, &enable, 0, req);
511         if (error)
512                 return (error);
513
514         enable = (enable) ? 1 : 0;
515
516         if (enable == *(int *)arg1)
517                 return (0);
518
519         if (arg1 == &fw_enable) {
520                 if (enable)
521                         error = ipfw_hook();
522                 else
523                         error = ipfw_unhook();
524         }
525 #ifdef INET6
526         if (arg1 == &fw6_enable) {
527                 if (enable)
528                         error = ipfw6_hook();
529                 else
530                         error = ipfw6_unhook();
531         }
532 #endif
533
534         if (error)
535                 return (error);
536
537         *(int *)arg1 = enable;
538
539         return (0);
540 }
541
542 static int
543 ipfw_modevent(module_t mod, int type, void *unused)
544 {
545         int err = 0;
546
547         switch (type) {
548         case MOD_LOAD:
549                 if ((err = ipfw_init()) != 0) {
550                         printf("ipfw_init() error\n");
551                         break;
552                 }
553                 if ((err = ipfw_hook()) != 0) {
554                         printf("ipfw_hook() error\n");
555                         break;
556                 }
557 #ifdef INET6
558                 if ((err = ipfw6_hook()) != 0) {
559                         printf("ipfw_hook() error\n");
560                         break;
561                 }
562 #endif
563                 break;
564
565         case MOD_UNLOAD:
566                 if ((err = ipfw_unhook()) > 0)
567                         break;
568 #ifdef INET6
569                 if ((err = ipfw6_unhook()) > 0)
570                         break;
571 #endif
572                 ipfw_destroy();
573                 break;
574
575         default:
576                 return EOPNOTSUPP;
577                 break;
578         }
579         return err;
580 }
581
582 static moduledata_t ipfwmod = {
583         "ipfw",
584         ipfw_modevent,
585         0
586 };
587 DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY - 256);
588 MODULE_VERSION(ipfw, 2);