Home | History | Annotate | Line # | Download | only in npf
npf_inet.c revision 1.3
      1 /*	$NetBSD: npf_inet.c,v 1.3 2010/09/25 00:25:31 rmind Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2009-2010 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  * Various procotol related helper routines.
     34  */
     35 
     36 #ifdef _KERNEL
     37 #include <sys/cdefs.h>
     38 __KERNEL_RCSID(0, "$NetBSD: npf_inet.c,v 1.3 2010/09/25 00:25:31 rmind Exp $");
     39 
     40 #include <sys/param.h>
     41 #include <sys/kernel.h>
     42 
     43 #include <netinet/in_systm.h>
     44 #include <netinet/in.h>
     45 #include <netinet/ip.h>
     46 #include <netinet/tcp.h>
     47 #include <netinet/udp.h>
     48 #include <netinet/ip_icmp.h>
     49 
     50 #include <net/if.h>
     51 #include <net/ethertypes.h>
     52 #include <net/if_ether.h>
     53 #endif
     54 #include <net/pfil.h>
     55 
     56 #include "npf_impl.h"
     57 
     58 /*
     59  * npf_fixup{16,32}_cksum: update IPv4 checksum.
     60  */
     61 
     62 uint16_t
     63 npf_fixup16_cksum(uint16_t cksum, uint16_t odatum, uint16_t ndatum)
     64 {
     65 	uint32_t sum;
     66 
     67 	/*
     68 	 * RFC 1624:
     69 	 *	HC' = ~(~HC + ~m + m')
     70 	 */
     71 	sum = ~ntohs(cksum) & 0xffff;
     72 	sum += (~ntohs(odatum) & 0xffff) + ntohs(ndatum);
     73 	sum = (sum >> 16) + (sum & 0xffff);
     74 	sum += (sum >> 16);
     75 
     76 	return htons(~sum & 0xffff);
     77 }
     78 
     79 uint16_t
     80 npf_fixup32_cksum(uint16_t cksum, uint32_t odatum, uint32_t ndatum)
     81 {
     82 
     83 	cksum = npf_fixup16_cksum(cksum, odatum & 0xffff, ndatum & 0xffff);
     84 	cksum = npf_fixup16_cksum(cksum, odatum >> 16, ndatum >> 16);
     85 	return cksum;
     86 }
     87 
     88 /*
     89  * npf_ip4_proto: check IPv4 header length and match protocol number.
     90  *
     91  * => Returns pointer to protocol header or NULL on failure.
     92  * => Stores protocol number in the cache.
     93  * => Updates nbuf pointer to header's nbuf.
     94  */
     95 bool
     96 npf_ip4_proto(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
     97 {
     98 	u_int hlen, offby;
     99 	uint8_t val8;
    100 	int error;
    101 
    102 	/* IPv4 header: check IP version and header length. */
    103 	error = nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint8_t), &val8);
    104 	if (error || (val8 >> 4) != IPVERSION)
    105 		return false;
    106 	hlen = (val8 & 0xf) << 2;
    107 	if (hlen < sizeof(struct ip))
    108 		return false;
    109 
    110 	/* IPv4 header: check fragment offset. */
    111 	offby = offsetof(struct ip, ip_off);
    112 	error = nbuf_advfetch(&nbuf, &n_ptr, offby, sizeof(uint8_t), &val8);
    113 	if (error || (val8 & ~htons(IP_DF | IP_RF)))
    114 		return false;
    115 
    116 	/* Get and match protocol. */
    117 	KASSERT(offsetof(struct ip, ip_p) > offby);
    118 	offby = offsetof(struct ip, ip_p) - offby;
    119 	if (nbuf_advfetch(&nbuf, &n_ptr, offby, sizeof(uint8_t), &val8))
    120 		return false;
    121 
    122 	/* IP checksum. */
    123 	offby = offsetof(struct ip, ip_sum) - offsetof(struct ip, ip_p);
    124 	if (nbuf_advfetch(&nbuf, &n_ptr, offby,
    125 	    sizeof(uint16_t), &npc->npc_ipsum))
    126 		return false;
    127 
    128 	/* Cache: IPv4, protocol, header length. */
    129 	npc->npc_info |= NPC_IP46;
    130 	npc->npc_proto = val8;
    131 	npc->npc_hlen = hlen;
    132 	return true;
    133 }
    134 
    135 /*
    136  * npf_fetch_ip4addrs: fetch source and destination address from IPv4 header.
    137  *
    138  * => Stores both source and destination addresses into the cache.
    139  */
    140 bool
    141 npf_fetch_ip4addrs(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
    142 {
    143 	in_addr_t *src = &npc->npc_srcip, *dst = &npc->npc_dstip;
    144 	u_int offby;
    145 
    146 	/* Source address. */
    147 	offby = offsetof(struct ip, ip_src);
    148 	if (nbuf_advfetch(&nbuf, &n_ptr, offby, sizeof(in_addr_t), src))
    149 		return false;
    150 
    151 	/* Destination address. */
    152 	offby = offsetof(struct ip, ip_dst) - offby;
    153 	if (nbuf_advfetch(&nbuf, &n_ptr, offby, sizeof(in_addr_t), dst))
    154 		return false;
    155 
    156 	/* Both addresses are cached. */
    157 	npc->npc_info |= NPC_ADDRS;
    158 	return true;
    159 }
    160 
    161 /*
    162  * npf_fetch_ports: fetch ports from either TCP or UDP header.
    163  *
    164  * => Stores both source and destination ports into the cache.
    165  */
    166 bool
    167 npf_fetch_ports(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr, const int proto)
    168 {
    169 	u_int dst_off;
    170 
    171 	/* Perform checks, advance to TCP/UDP header. */
    172 	if (!npf_iscached(npc, NPC_IP46) && !npf_ip4_proto(npc, nbuf, n_ptr))
    173 		return false;
    174 	n_ptr = nbuf_advance(&nbuf, n_ptr, npc->npc_hlen);
    175 	if (n_ptr == NULL || npc->npc_proto != proto)
    176 		return false;
    177 
    178 	/*
    179 	 * TCP/UDP header: fetch source and destination ports.  For both
    180 	 * protocols offset of the source port offset is 0.
    181 	 */
    182 	CTASSERT(offsetof(struct tcphdr, th_sport) == 0);
    183 	CTASSERT(offsetof(struct udphdr, uh_sport) == 0);
    184 	if (proto == IPPROTO_TCP) {
    185 		dst_off = offsetof(struct tcphdr, th_dport);
    186 	} else {
    187 		KASSERT(proto == IPPROTO_UDP);
    188 		dst_off = offsetof(struct udphdr, uh_dport);
    189 	}
    190 
    191 	if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(in_port_t), &npc->npc_sport))
    192 		return false;
    193 	if ((n_ptr = nbuf_advance(&nbuf, n_ptr, dst_off)) == NULL)
    194 		return false;
    195 	if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(in_port_t), &npc->npc_dport))
    196 		return false;
    197 
    198 	/* Both ports are cached. */
    199 	npc->npc_info |= NPC_PORTS;
    200 	return true;
    201 }
    202 
    203 /*
    204  * npf_fetch_icmp: fetch ICMP code, type and possible query ID.
    205  *
    206  * => Stores both all fetched items into the cache.
    207  */
    208 bool
    209 npf_fetch_icmp(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
    210 {
    211 	uint8_t *type = &npc->npc_icmp_type, *code = &npc->npc_icmp_code;
    212 	u_int offby;
    213 
    214 	KASSERT(npf_iscached(npc, NPC_IP46));
    215 
    216 	/* ICMP type. */
    217 	offby = npc->npc_hlen;
    218 	CTASSERT(offsetof(struct icmp, icmp_type) == 0);
    219 	if (nbuf_advfetch(&nbuf, &n_ptr, offby, sizeof(uint8_t), type))
    220 		return false;
    221 
    222 	/* ICMP code. */
    223 	offby = offsetof(struct icmp, icmp_code);
    224 	if (nbuf_advfetch(&nbuf, &n_ptr, offby, sizeof(uint8_t), code))
    225 		return false;
    226 
    227 	/* Mark as cached. */
    228 	npc->npc_info |= NPC_ICMP;
    229 	return true;
    230 }
    231 
    232 /*
    233  * npf_fetch_tcpfl: fetch TCP flags and store into the cache.
    234  */
    235 bool
    236 npf_fetch_tcpfl(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
    237 {
    238 	const u_int offby = npc->npc_hlen + offsetof(struct tcphdr, th_flags);
    239 	uint8_t *tcpfl = &npc->npc_tcp_flags;
    240 
    241 	if (nbuf_advfetch(&nbuf, &n_ptr, offby, sizeof(uint8_t), tcpfl)) {
    242 		return false;
    243 	}
    244 	return true;
    245 }
    246 
    247 /*
    248  * npf_cache_all: general routine to cache all relevant IPv4 and
    249  * TCP, UDP or ICMP data.
    250  */
    251 bool
    252 npf_cache_all(npf_cache_t *npc, nbuf_t *nbuf)
    253 {
    254 	void *n_ptr = nbuf_dataptr(nbuf);
    255 
    256 	/* IPv4: get protocol, source and destination addresses. */
    257 	if (!npf_iscached(npc, NPC_IP46) && !npf_ip4_proto(npc, nbuf, n_ptr)) {
    258 		return false;
    259 	}
    260 	if (!npf_iscached(npc, NPC_ADDRS) &&
    261 	    !npf_fetch_ip4addrs(npc, nbuf, n_ptr)) {
    262 		return false;
    263 	}
    264 	switch (npc->npc_proto) {
    265 	case IPPROTO_TCP:
    266 		/* TCP flags. */
    267 		if (!npf_fetch_tcpfl(npc, nbuf, n_ptr)) {
    268 			return false;
    269 		}
    270 		/* FALLTHROUGH */
    271 
    272 	case IPPROTO_UDP:
    273 		/* Fetch TCP/UDP ports. */
    274 		return npf_fetch_ports(npc, nbuf, n_ptr, npc->npc_proto);
    275 
    276 	case IPPROTO_ICMP:
    277 		/* Fetch ICMP data. */
    278 		return npf_fetch_icmp(npc, nbuf, n_ptr);
    279 	}
    280 	return false;
    281 }
    282 
    283 /*
    284  * npf_rwrport: rewrite required TCP/UDP port and update checksum.
    285  */
    286 bool
    287 npf_rwrport(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr, const int di,
    288     in_port_t port, in_addr_t naddr)
    289 {
    290 	const int proto = npc->npc_proto;
    291 	u_int offby, toff;
    292 	in_addr_t oaddr;
    293 	in_port_t oport;
    294 	uint16_t cksum;
    295 
    296 	KASSERT(npf_iscached(npc, NPC_PORTS));
    297 	KASSERT(proto == IPPROTO_TCP || proto == IPPROTO_UDP);
    298 
    299 	offby = npc->npc_hlen;
    300 
    301 	if (di == PFIL_OUT) {
    302 		/* Offset to the source port is zero. */
    303 		CTASSERT(offsetof(struct tcphdr, th_sport) == 0);
    304 		CTASSERT(offsetof(struct udphdr, uh_sport) == 0);
    305 		if (proto == IPPROTO_TCP) {
    306 			toff = offsetof(struct tcphdr, th_sum);
    307 		} else {
    308 			toff = offsetof(struct udphdr, uh_sum);
    309 		}
    310 		oaddr = npc->npc_srcip;
    311 		oport = npc->npc_sport;
    312 	} else {
    313 		/* Calculate offset to destination port and checksum. */
    314 		u_int poff;
    315 		if (proto == IPPROTO_TCP) {
    316 			poff = offsetof(struct tcphdr, th_dport);
    317 			toff = offsetof(struct tcphdr, th_sum) - poff;
    318 		} else {
    319 			poff = offsetof(struct udphdr, uh_dport);
    320 			toff = offsetof(struct udphdr, uh_sum) - poff;
    321 		}
    322 		oaddr = npc->npc_dstip;
    323 		oport = npc->npc_dport;
    324 		offby += poff;
    325 	}
    326 
    327 	/* Advance and rewrite port. */
    328 	if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
    329 		return false;
    330 	if (nbuf_store_datum(nbuf, n_ptr, sizeof(in_port_t), &port))
    331 		return false;
    332 
    333 	/* Advance and update TCP/UDP checksum. */
    334 	if (nbuf_advfetch(&nbuf, &n_ptr, toff, sizeof(uint16_t), &cksum)) {
    335 		return false;
    336 	}
    337 	if (__predict_true(cksum || proto == IPPROTO_TCP)) {
    338 		cksum = npf_fixup32_cksum(cksum, oaddr, naddr);
    339 		cksum = npf_fixup16_cksum(cksum, oport, port);
    340 		if (nbuf_store_datum(nbuf, n_ptr, sizeof(uint16_t), &cksum))
    341 			return false;
    342 	}
    343 	return true;
    344 }
    345 
    346 /*
    347  * npf_rwrip: rewrite required IP address and update checksum.
    348  */
    349 bool
    350 npf_rwrip(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr, const int di,
    351     in_addr_t addr)
    352 {
    353 	u_int offby;
    354 	in_addr_t oaddr;
    355 
    356 	KASSERT(npf_iscached(npc, NPC_IP46 | NPC_ADDRS));
    357 
    358 	/* Advance to the checksum in IP header and fetch it. */
    359 	offby = offsetof(struct ip, ip_sum);
    360 	if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
    361 		return false;
    362 
    363 	if (di == PFIL_OUT) {
    364 		/* Rewrite source address, if outgoing. */
    365 		offby = offsetof(struct ip, ip_src) - offby;
    366 		oaddr = npc->npc_srcip;
    367 	} else {
    368 		/* Rewrite destination, if incoming. */
    369 		offby = offsetof(struct ip, ip_dst) - offby;
    370 		oaddr = npc->npc_dstip;
    371 	}
    372 
    373 	/* Write new IP checksum (it is acceptable to do this earlier). */
    374 	uint16_t cksum = npf_fixup32_cksum(npc->npc_ipsum, oaddr, addr);
    375 	if (nbuf_store_datum(nbuf, n_ptr, sizeof(uint16_t), &cksum))
    376 		return false;
    377 
    378 	/* Advance to address and rewrite it. */
    379 	if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
    380 		return false;
    381 	if (nbuf_store_datum(nbuf, n_ptr, sizeof(in_addr_t), &addr))
    382 		return false;
    383 
    384 	npc->npc_ipsum = cksum;
    385 	return true;
    386 }
    387