Home | History | Annotate | Line # | Download | only in npf
npf_ruleset.c revision 1.5.2.1
      1 /*	$NetBSD: npf_ruleset.c,v 1.5.2.1 2011/06/06 09:09:53 jruoho Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2009-2011 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This material is based upon work partially supported by The
      8  * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /*
     33  * NPF ruleset module.
     34  */
     35 
     36 #include <sys/cdefs.h>
     37 __KERNEL_RCSID(0, "$NetBSD: npf_ruleset.c,v 1.5.2.1 2011/06/06 09:09:53 jruoho Exp $");
     38 
     39 #include <sys/param.h>
     40 #include <sys/kernel.h>
     41 
     42 #include <sys/atomic.h>
     43 #include <sys/kmem.h>
     44 #include <sys/pool.h>
     45 #include <sys/queue.h>
     46 #include <sys/types.h>
     47 
     48 #include <net/pfil.h>
     49 #include <net/if.h>
     50 
     51 #include "npf_ncode.h"
     52 #include "npf_impl.h"
     53 
     54 /* Ruleset structre (queue and default rule). */
     55 struct npf_ruleset {
     56 	TAILQ_HEAD(, npf_rule)	rs_queue;
     57 	npf_rule_t *		rs_default;
     58 };
     59 
     60 /* Rule hook entry. */
     61 struct npf_hook {
     62 	void			(*hk_fn)(npf_cache_t *, nbuf_t *, void *);
     63 	void *			hk_arg;
     64 	LIST_ENTRY(npf_hook)	hk_entry;
     65 };
     66 
     67 #define	NPF_RNAME_LEN		16
     68 
     69 /* Rule procedure structure. */
     70 struct npf_rproc {
     71 	/* Name. */
     72 	char			rp_name[NPF_RNAME_LEN];
     73 	/* Reference count. */
     74 	u_int			rp_refcnt;
     75 	uint32_t		rp_flags;
     76 	/* Normalization options. */
     77 	bool			rp_rnd_ipid;
     78 	bool			rp_no_df;
     79 	u_int			rp_minttl;
     80 	u_int			rp_maxmss;
     81 	/* Logging interface. */
     82 	u_int			rp_log_ifid;
     83 };
     84 
     85 /* Rule structure. */
     86 struct npf_rule {
     87 	/* Rule name (optional) and list entry. */
     88 	char			r_name[NPF_RNAME_LEN];
     89 	TAILQ_ENTRY(npf_rule)	r_entry;
     90 	/* Optional: sub-ruleset, NAT policy. */
     91 	npf_ruleset_t		r_subset;
     92 	npf_natpolicy_t *	r_natp;
     93 	/* Rule priority: (highest) 0, 1, 2 ... n (lowest). */
     94 	pri_t			r_priority;
     95 	/* N-code to process. */
     96 	void *			r_ncode;
     97 	size_t			r_nc_size;
     98 	/* Attributes of this rule. */
     99 	uint32_t		r_attr;
    100 	/* Interface. */
    101 	u_int			r_ifid;
    102 	/* Rule procedure data. */
    103 	npf_rproc_t *		r_rproc;
    104 	/* List of hooks to process on match. */
    105 	kmutex_t		r_hooks_lock;
    106 	LIST_HEAD(, npf_hook)	r_hooks;
    107 };
    108 
    109 npf_ruleset_t *
    110 npf_ruleset_create(void)
    111 {
    112 	npf_ruleset_t *rlset;
    113 
    114 	rlset = kmem_zalloc(sizeof(npf_ruleset_t), KM_SLEEP);
    115 	TAILQ_INIT(&rlset->rs_queue);
    116 	return rlset;
    117 }
    118 
    119 void
    120 npf_ruleset_destroy(npf_ruleset_t *rlset)
    121 {
    122 	npf_rule_t *rl;
    123 
    124 	while ((rl = TAILQ_FIRST(&rlset->rs_queue)) != NULL) {
    125 		TAILQ_REMOVE(&rlset->rs_queue, rl, r_entry);
    126 		npf_rule_free(rl);
    127 	}
    128 	kmem_free(rlset, sizeof(npf_ruleset_t));
    129 }
    130 
    131 /*
    132  * npf_ruleset_insert: insert the rule into the specified ruleset.
    133  *
    134  * Note: multiple rules at the same priority are allowed.
    135  */
    136 void
    137 npf_ruleset_insert(npf_ruleset_t *rlset, npf_rule_t *rl)
    138 {
    139 	npf_rule_t *it;
    140 
    141 	if (rl->r_attr & NPF_RULE_DEFAULT) {
    142 		rlset->rs_default = rl;
    143 		return;
    144 	}
    145 	TAILQ_FOREACH(it, &rlset->rs_queue, r_entry) {
    146 		/* Rule priority: (highest) 0, 1, 2, 4 ... n (lowest). */
    147 		if (it->r_priority > rl->r_priority)
    148 			break;
    149 	}
    150 	if (it == NULL) {
    151 		TAILQ_INSERT_TAIL(&rlset->rs_queue, rl, r_entry);
    152 	} else {
    153 		TAILQ_INSERT_BEFORE(it, rl, r_entry);
    154 	}
    155 }
    156 
    157 /*
    158  * npf_ruleset_matchnat: find a matching NAT policy in the ruleset.
    159  */
    160 npf_rule_t *
    161 npf_ruleset_matchnat(npf_ruleset_t *rlset, npf_natpolicy_t *mnp)
    162 {
    163 	npf_rule_t *rl;
    164 
    165 	/* Find a matching NAT policy in the old ruleset. */
    166 	TAILQ_FOREACH(rl, &rlset->rs_queue, r_entry) {
    167 		if (npf_nat_matchpolicy(rl->r_natp, mnp))
    168 			break;
    169 	}
    170 	return rl;
    171 }
    172 
    173 npf_rule_t *
    174 npf_ruleset_sharepm(npf_ruleset_t *rlset, npf_natpolicy_t *mnp)
    175 {
    176 	npf_natpolicy_t *np;
    177 	npf_rule_t *rl;
    178 
    179 	/* Find a matching NAT policy in the old ruleset. */
    180 	TAILQ_FOREACH(rl, &rlset->rs_queue, r_entry) {
    181 		/*
    182 		 * NAT policy might not yet be set during the creation of
    183 		 * the ruleset (in such case, rule is for our policy), or
    184 		 * policies might be equal due to rule exchange on reload.
    185 		 */
    186 		np = rl->r_natp;
    187 		if (np == NULL || np == mnp)
    188 			continue;
    189 		if (npf_nat_sharepm(np, mnp))
    190 			break;
    191 	}
    192 	return rl;
    193 }
    194 
    195 /*
    196  * npf_ruleset_natreload: minimum reload of NAT policies by maching
    197  * two (active and new) NAT rulesets.
    198  *
    199  * => Active ruleset should be exclusively locked.
    200  */
    201 void
    202 npf_ruleset_natreload(npf_ruleset_t *nrlset, npf_ruleset_t *arlset)
    203 {
    204 	npf_natpolicy_t *np, *anp;
    205 	npf_rule_t *rl, *arl;
    206 
    207 	KASSERT(npf_core_locked());
    208 
    209 	/* Scan a new NAT ruleset against NAT policies in old ruleset. */
    210 	TAILQ_FOREACH(rl, &nrlset->rs_queue, r_entry) {
    211 		np = rl->r_natp;
    212 		arl = npf_ruleset_matchnat(arlset, np);
    213 		if (arl == NULL) {
    214 			continue;
    215 		}
    216 		/* On match - we exchange NAT policies. */
    217 		anp = arl->r_natp;
    218 		rl->r_natp = anp;
    219 		arl->r_natp = np;
    220 		/* Update other NAT policies to share portmap. */
    221 		(void)npf_ruleset_sharepm(nrlset, anp);
    222 	}
    223 }
    224 
    225 npf_rproc_t *
    226 npf_rproc_create(prop_dictionary_t rpdict)
    227 {
    228 	npf_rproc_t *rp;
    229 	const char *rname;
    230 
    231 	rp = kmem_zalloc(sizeof(npf_rproc_t), KM_SLEEP);
    232 	rp->rp_refcnt = 1;
    233 
    234 	/* Name and flags. */
    235 	prop_dictionary_get_cstring_nocopy(rpdict, "name", &rname);
    236 	strlcpy(rp->rp_name, rname, NPF_RNAME_LEN);
    237 	prop_dictionary_get_uint32(rpdict, "flags", &rp->rp_flags);
    238 
    239 	/* Logging interface ID (integer). */
    240 	prop_dictionary_get_uint32(rpdict, "log-interface", &rp->rp_log_ifid);
    241 
    242 	/* IP ID randomization and IP_DF flag cleansing. */
    243 	prop_dictionary_get_bool(rpdict, "randomize-id", &rp->rp_rnd_ipid);
    244 	prop_dictionary_get_bool(rpdict, "no-df", &rp->rp_no_df);
    245 
    246 	/* Minimum IP TTL and maximum TCP MSS. */
    247 	prop_dictionary_get_uint32(rpdict, "min-ttl", &rp->rp_minttl);
    248 	prop_dictionary_get_uint32(rpdict, "max-mss", &rp->rp_maxmss);
    249 
    250 	return rp;
    251 }
    252 
    253 npf_rproc_t *
    254 npf_rproc_return(npf_rule_t *rl)
    255 {
    256 	npf_rproc_t *rp = rl->r_rproc;
    257 
    258 	KASSERT(npf_core_locked());
    259 	if (rp) {
    260 		atomic_inc_uint(&rp->rp_refcnt);
    261 	}
    262 	return rp;
    263 }
    264 
    265 void
    266 npf_rproc_release(npf_rproc_t *rp)
    267 {
    268 
    269 	/* Destroy on last reference. */
    270 	if (atomic_dec_uint_nv(&rp->rp_refcnt) != 0) {
    271 		return;
    272 	}
    273 	kmem_free(rp, sizeof(npf_rproc_t));
    274 }
    275 
    276 void
    277 npf_rproc_run(npf_cache_t *npc, nbuf_t *nbuf, npf_rproc_t *rp, int error)
    278 {
    279 	const uint32_t flags = rp->rp_flags;
    280 
    281 	KASSERT(rp->rp_refcnt > 0);
    282 
    283 	/* Normalize the packet, if required. */
    284 	if ((flags & NPF_RPROC_NORMALIZE) != 0 && !error) {
    285 		(void)npf_normalize(npc, nbuf,
    286 		    rp->rp_rnd_ipid, rp->rp_no_df,
    287 		    rp->rp_minttl, rp->rp_maxmss);
    288 		npf_stats_inc(NPF_STAT_RPROC_NORM);
    289 	}
    290 
    291 	/* Log packet, if required. */
    292 	if ((flags & NPF_RPROC_LOG) != 0) {
    293 		npf_log_packet(npc, nbuf, rp->rp_log_ifid);
    294 		npf_stats_inc(NPF_STAT_RPROC_LOG);
    295 	}
    296 }
    297 
    298 /*
    299  * npf_rule_alloc: allocate a rule and copy n-code from user-space.
    300  *
    301  * => N-code should be validated by the caller.
    302  */
    303 npf_rule_t *
    304 npf_rule_alloc(prop_dictionary_t rldict, npf_rproc_t *rp,
    305    void *nc, size_t nc_size)
    306 {
    307 	npf_rule_t *rl;
    308 	const char *rname;
    309 	int errat;
    310 
    311 	/* Allocate a rule structure. */
    312 	rl = kmem_alloc(sizeof(npf_rule_t), KM_SLEEP);
    313 	TAILQ_INIT(&rl->r_subset.rs_queue);
    314 	mutex_init(&rl->r_hooks_lock, MUTEX_DEFAULT, IPL_SOFTNET);
    315 	LIST_INIT(&rl->r_hooks);
    316 	rl->r_natp = NULL;
    317 
    318 	/* N-code. */
    319 	(void)errat;
    320 	KASSERT(nc == NULL || npf_ncode_validate(nc, nc_size, &errat) == 0);
    321 	rl->r_ncode = nc;
    322 	rl->r_nc_size = nc_size;
    323 
    324 	/* Name (string, optional) */
    325 	if (prop_dictionary_get_cstring_nocopy(rldict, "name", &rname)) {
    326 		strlcpy(rl->r_name, rname, NPF_RNAME_LEN);
    327 	} else {
    328 		rl->r_name[0] = '\0';
    329 	}
    330 
    331 	/* Attributes, priority and interface ID. */
    332 	prop_dictionary_get_uint32(rldict, "attributes", &rl->r_attr);
    333 	prop_dictionary_get_int32(rldict, "priority", &rl->r_priority);
    334 	prop_dictionary_get_uint32(rldict, "interface", &rl->r_ifid);
    335 
    336 	/* Rule procedure. */
    337 	if (rp) {
    338 		atomic_inc_uint(&rp->rp_refcnt);
    339 	}
    340 	rl->r_rproc = rp;
    341 
    342 	return rl;
    343 }
    344 
    345 /*
    346  * npf_rule_free: free the specified rule.
    347  */
    348 void
    349 npf_rule_free(npf_rule_t *rl)
    350 {
    351 	npf_natpolicy_t *np = rl->r_natp;
    352 	npf_rproc_t *rp = rl->r_rproc;
    353 
    354 	if (np) {
    355 		/* Free NAT policy. */
    356 		npf_nat_freepolicy(np);
    357 	}
    358 	if (rp) {
    359 		/* Release rule procedure. */
    360 		npf_rproc_release(rp);
    361 	}
    362 	if (rl->r_ncode) {
    363 		/* Free n-code. */
    364 		npf_ncode_free(rl->r_ncode, rl->r_nc_size);
    365 	}
    366 	mutex_destroy(&rl->r_hooks_lock);
    367 	kmem_free(rl, sizeof(npf_rule_t));
    368 }
    369 
    370 /*
    371  * npf_rule_subset: return sub-ruleset, if any.
    372  * npf_rule_getnat: get NAT policy assigned to the rule.
    373  */
    374 
    375 npf_ruleset_t *
    376 npf_rule_subset(npf_rule_t *rl)
    377 {
    378 	return &rl->r_subset;
    379 }
    380 
    381 npf_natpolicy_t *
    382 npf_rule_getnat(const npf_rule_t *rl)
    383 {
    384 	return rl->r_natp;
    385 }
    386 
    387 /*
    388  * npf_rule_setnat: assign NAT policy to the rule and insert into the
    389  * NAT policy list in the ruleset.
    390  */
    391 void
    392 npf_rule_setnat(npf_rule_t *rl, npf_natpolicy_t *np)
    393 {
    394 
    395 	KASSERT(rl->r_natp == NULL);
    396 	rl->r_natp = np;
    397 }
    398 
    399 #if 0
    400 /*
    401  * npf_hook_register: register action hook in the rule.
    402  */
    403 npf_hook_t *
    404 npf_hook_register(npf_rule_t *rl,
    405     void (*fn)(npf_cache_t *, nbuf_t *, void *), void *arg)
    406 {
    407 	npf_hook_t *hk;
    408 
    409 	hk = kmem_alloc(sizeof(npf_hook_t), KM_SLEEP);
    410 	if (hk != NULL) {
    411 		hk->hk_fn = fn;
    412 		hk->hk_arg = arg;
    413 		mutex_enter(&rl->r_hooks_lock);
    414 		LIST_INSERT_HEAD(&rl->r_hooks, hk, hk_entry);
    415 		mutex_exit(&rl->r_hooks_lock);
    416 	}
    417 	return hk;
    418 }
    419 
    420 /*
    421  * npf_hook_unregister: unregister a specified hook.
    422  *
    423  * => Hook should have been registered in the rule.
    424  */
    425 void
    426 npf_hook_unregister(npf_rule_t *rl, npf_hook_t *hk)
    427 {
    428 
    429 	mutex_enter(&rl->r_hooks_lock);
    430 	LIST_REMOVE(hk, hk_entry);
    431 	mutex_exit(&rl->r_hooks_lock);
    432 	kmem_free(hk, sizeof(npf_hook_t));
    433 }
    434 #endif
    435 
    436 npf_rule_t *
    437 npf_ruleset_replace(const char *name, npf_ruleset_t *rlset)
    438 {
    439 	npf_ruleset_t orlset;
    440 	npf_rule_t *rl;
    441 
    442 	npf_core_enter(); /* XXX */
    443 	rlset = npf_core_ruleset();
    444 	TAILQ_FOREACH(rl, &rlset->rs_queue, r_entry) {
    445 		if (rl->r_name[0] == '\0')
    446 			continue;
    447 		if (strncmp(rl->r_name, name, NPF_RNAME_LEN))
    448 			continue;
    449 		memcpy(&orlset, &rl->r_subset, sizeof(npf_ruleset_t));
    450 		break;
    451 	}
    452 	npf_core_exit();
    453 	return rl;
    454 }
    455 
    456 /*
    457  * npf_ruleset_inspect: inspect the packet against the given ruleset.
    458  *
    459  * Loop through the rules in the set and run n-code processor of each rule
    460  * against the packet (nbuf chain).  If sub-ruleset is found, inspect it.
    461  *
    462  * => If not found, core ruleset lock is released.
    463  * => Caller should protect the nbuf chain.
    464  */
    465 npf_rule_t *
    466 npf_ruleset_inspect(npf_cache_t *npc, nbuf_t *nbuf, npf_ruleset_t *mainrlset,
    467     ifnet_t *ifp, const int di, const int layer)
    468 {
    469 	const int di_mask = (di & PFIL_IN) ? NPF_RULE_IN : NPF_RULE_OUT;
    470 	npf_ruleset_t *rlset = mainrlset;
    471 	npf_rule_t *final_rl = NULL, *rl;
    472 	bool defed = false;
    473 
    474 	KASSERT(npf_core_locked());
    475 	KASSERT(((di & PFIL_IN) != 0) ^ ((di & PFIL_OUT) != 0));
    476 again:
    477 	TAILQ_FOREACH(rl, &rlset->rs_queue, r_entry) {
    478 		KASSERT(!final_rl || rl->r_priority >= final_rl->r_priority);
    479 
    480 		/* Match the interface. */
    481 		if (rl->r_ifid && rl->r_ifid != ifp->if_index) {
    482 			continue;
    483 		}
    484 		/* Match the direction. */
    485 		if ((rl->r_attr & NPF_RULE_DIMASK) != NPF_RULE_DIMASK) {
    486 			if ((rl->r_attr & di_mask) == 0)
    487 				continue;
    488 		}
    489 		/* Process the n-code, if any. */
    490 		const void *nc = rl->r_ncode;
    491 		if (nc && npf_ncode_process(npc, nc, nbuf, layer)) {
    492 			continue;
    493 		}
    494 		/* Set the matching rule and check for "final". */
    495 		final_rl = rl;
    496 		if (rl->r_attr & NPF_RULE_FINAL) {
    497 			break;
    498 		}
    499 	}
    500 
    501 	/* If no final rule, then - default. */
    502 	if (final_rl == NULL && !defed) {
    503 		final_rl = mainrlset->rs_default;
    504 		defed = true;
    505 	}
    506 	/* Inspect the sub-ruleset, if any. */
    507 	if (final_rl && !TAILQ_EMPTY(&final_rl->r_subset.rs_queue)) {
    508 		rlset = &final_rl->r_subset;
    509 		final_rl = NULL;
    510 		goto again;
    511 	}
    512 	if (final_rl == NULL) {
    513 		npf_core_exit();
    514 	}
    515 	return final_rl;
    516 }
    517 
    518 /*
    519  * npf_rule_apply: apply the rule i.e. run hooks and return appropriate value.
    520  *
    521  * => Returns ENETUNREACH if "block" and 0 if "pass".
    522  * => Releases the ruleset lock.
    523  */
    524 int
    525 npf_rule_apply(npf_cache_t *npc, nbuf_t *nbuf, npf_rule_t *rl, int *retfl)
    526 {
    527 	npf_hook_t *hk;
    528 	int error;
    529 
    530 	KASSERT(npf_core_locked());
    531 
    532 	/* If not passing - drop the packet. */
    533 	if ((rl->r_attr & NPF_RULE_PASS) == 0) {
    534 		error = ENETUNREACH;
    535 		goto done;
    536 	}
    537 	error = 0;
    538 
    539 	/* Passing.  Run the hooks. */
    540 	LIST_FOREACH(hk, &rl->r_hooks, hk_entry) {
    541 		KASSERT(hk->hk_fn != NULL);
    542 		(*hk->hk_fn)(npc, nbuf, hk->hk_arg);
    543 	}
    544 done:
    545 	*retfl = rl->r_attr;
    546 	npf_core_exit();
    547 	return error;
    548 }
    549 
    550 #if defined(DDB) || defined(_NPF_TESTING)
    551 
    552 void
    553 npf_rulenc_dump(npf_rule_t *rl)
    554 {
    555 	uint32_t *op = rl->r_ncode;
    556 	size_t n = rl->r_nc_size;
    557 
    558 	while (n) {
    559 		printf("\t> |0x%02x|\n", (uint32_t)*op);
    560 		op++;
    561 		n -= sizeof(*op);
    562 	}
    563 	printf("-> %s\n", (rl->r_attr & NPF_RULE_PASS) ? "pass" : "block");
    564 }
    565 
    566 #endif
    567