NetBSD/sys/netinet/fil.c
thorpej ad9d3794b0 Implement support for IP/TCP/UDP checksum offloading provided by
network interfaces.  This works by pre-computing the pseudo-header
checksum and caching it, delaying the actual checksum to ip_output()
if the hardware cannot perform the sum for us.  In-bound checksums
can either be fully-checked by hardware, or summed up for final
verification by software.  This method was modeled after how this
is done in FreeBSD, although the code is significantly different in
most places.

We don't delay checksums for IPv6/TCP, but we do take advantage of the
cached pseudo-header checksum.

Note: hardware-assisted checksumming defaults to "off".  It is
enabled with ifconfig(8).  See the manual page for details.

Implement hardware-assisted checksumming on the DP83820 Gigabit Ethernet,
3c90xB/3c90xC 10/100 Ethernet, and Alteon Tigon/Tigon2 Gigabit Ethernet.
2001-06-02 16:17:09 +00:00

2189 lines
49 KiB
C

/* $NetBSD: fil.c,v 1.47 2001/06/02 16:17:09 thorpej Exp $ */
/*
* Copyright (C) 1993-2000 by Darren Reed.
*
* Redistribution and use in source and binary forms are permitted
* provided that this notice is preserved and due credit is given
* to the original author and the contributors.
*/
#if !defined(lint)
#if defined(__NetBSD__)
static const char rcsid[] = "$NetBSD: fil.c,v 1.47 2001/06/02 16:17:09 thorpej Exp $";
#else
static const char sccsid[] = "@(#)fil.c 1.36 6/5/96 (C) 1993-2000 Darren Reed";
static const char rcsid[] = "@(#)Id: fil.c,v 2.35.2.30 2000/12/17 05:49:22 darrenr Exp";
#endif
#endif
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/file.h>
#if defined(__NetBSD__) && (NetBSD >= 199905) && !defined(IPFILTER_LKM) && \
defined(_KERNEL) && !defined(_LKM)
# include "opt_ipfilter_log.h"
#endif
#if (defined(KERNEL) || defined(_KERNEL)) && defined(__FreeBSD_version) && \
(__FreeBSD_version >= 220000)
# if (__FreeBSD_version >= 400000)
# ifndef KLD_MODULE
# include "opt_inet6.h"
# endif
# if (__FreeBSD_version == 400019)
# define CSUM_DELAY_DATA
# endif
# endif
# include <sys/filio.h>
# include <sys/fcntl.h>
#else
# include <sys/ioctl.h>
#endif
#if (defined(_KERNEL) || defined(KERNEL)) && !defined(linux)
# include <sys/systm.h>
#else
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
#endif
#include <sys/uio.h>
#if !defined(__SVR4) && !defined(__svr4__)
# ifndef linux
# include <sys/mbuf.h>
# endif
#else
# include <sys/byteorder.h>
# if SOLARIS2 < 5
# include <sys/dditypes.h>
# endif
# include <sys/stream.h>
#endif
#ifndef linux
# include <sys/protosw.h>
# include <sys/socket.h>
#endif
#include <net/if.h>
#ifdef sun
# include <net/af.h>
#endif
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#ifndef linux
# include <netinet/ip_var.h>
#endif
#if defined(__sgi) && defined(IFF_DRVRLOCK) /* IRIX 6 */
# include <sys/hashing.h>
# include <netinet/in_var.h>
#endif
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include "netinet/ip_compat.h"
#ifdef USE_INET6
# include <netinet/icmp6.h>
# if !SOLARIS && defined(_KERNEL)
# include <netinet6/in6_var.h>
# endif
#endif
#include <netinet/tcpip.h>
#include "netinet/ip_fil.h"
#include "netinet/ip_proxy.h"
#include "netinet/ip_nat.h"
#include "netinet/ip_frag.h"
#include "netinet/ip_state.h"
#include "netinet/ip_auth.h"
# if defined(__FreeBSD_version) && (__FreeBSD_version >= 300000)
# include <sys/malloc.h>
# if defined(_KERNEL) && !defined(IPFILTER_LKM)
# include "opt_ipfilter.h"
# endif
# endif
#include "netinet/ipl.h"
#ifndef _KERNEL
# include "ipf.h"
# include "ipt.h"
extern int opts;
# define FR_VERBOSE(verb_pr) verbose verb_pr
# define FR_DEBUG(verb_pr) debug verb_pr
# define IPLLOG(a, c, d, e) ipllog()
#else /* #ifndef _KERNEL */
# define FR_VERBOSE(verb_pr)
# define FR_DEBUG(verb_pr)
# define IPLLOG(a, c, d, e) ipflog(a, c, d, e)
# if SOLARIS || defined(__sgi)
extern KRWLOCK_T ipf_mutex, ipf_auth, ipf_nat;
extern kmutex_t ipf_rw;
# endif
# if SOLARIS
# define FR_NEWAUTH(m, fi, ip, qif) fr_newauth((mb_t *)m, fi, \
ip, qif)
# else /* SOLARIS */
# define FR_NEWAUTH(m, fi, ip, qif) fr_newauth((mb_t *)m, fi, ip)
# endif /* SOLARIS || __sgi */
#endif /* _KERNEL */
struct filterstats frstats[2] = {{0,0,0,0,0},{0,0,0,0,0}};
struct frentry *ipfilter[2][2] = { { NULL, NULL }, { NULL, NULL } },
#ifdef USE_INET6
*ipfilter6[2][2] = { { NULL, NULL }, { NULL, NULL } },
*ipacct6[2][2] = { { NULL, NULL }, { NULL, NULL } },
#endif
*ipacct[2][2] = { { NULL, NULL }, { NULL, NULL } };
struct frgroup *ipfgroups[3][2];
int fr_flags = IPF_LOGGING;
int fr_active = 0;
int fr_chksrc = 0;
int fr_minttl = 3;
int fr_minttllog = 1;
#if defined(IPFILTER_DEFAULT_BLOCK)
int fr_pass = FR_NOMATCH|FR_BLOCK;
#else
int fr_pass = (IPF_DEFAULT_PASS|FR_NOMATCH);
#endif
char ipfilter_version[] = IPL_VERSION;
fr_info_t frcache[2];
static int frflushlist __P((int, minor_t, int *, frentry_t **));
#ifdef _KERNEL
static void frsynclist __P((frentry_t *));
#endif
/*
* bit values for identifying presence of individual IP options
*/
const struct optlist ipopts[20] = {
{ IPOPT_NOP, 0x000001 },
{ IPOPT_RR, 0x000002 },
{ IPOPT_ZSU, 0x000004 },
{ IPOPT_MTUP, 0x000008 },
{ IPOPT_MTUR, 0x000010 },
{ IPOPT_ENCODE, 0x000020 },
{ IPOPT_TS, 0x000040 },
{ IPOPT_TR, 0x000080 },
{ IPOPT_SECURITY, 0x000100 },
{ IPOPT_LSRR, 0x000200 },
{ IPOPT_E_SEC, 0x000400 },
{ IPOPT_CIPSO, 0x000800 },
{ IPOPT_SATID, 0x001000 },
{ IPOPT_SSRR, 0x002000 },
{ IPOPT_ADDEXT, 0x004000 },
{ IPOPT_VISA, 0x008000 },
{ IPOPT_IMITD, 0x010000 },
{ IPOPT_EIP, 0x020000 },
{ IPOPT_FINN, 0x040000 },
{ 0, 0x000000 }
};
/*
* bit values for identifying presence of individual IP security options
*/
const struct optlist secopt[8] = {
{ IPSO_CLASS_RES4, 0x01 },
{ IPSO_CLASS_TOPS, 0x02 },
{ IPSO_CLASS_SECR, 0x04 },
{ IPSO_CLASS_RES3, 0x08 },
{ IPSO_CLASS_CONF, 0x10 },
{ IPSO_CLASS_UNCL, 0x20 },
{ IPSO_CLASS_RES2, 0x40 },
{ IPSO_CLASS_RES1, 0x80 }
};
/*
* compact the IP header into a structure which contains just the info.
* which is useful for comparing IP headers with.
*/
void fr_makefrip(hlen, ip, fin)
int hlen;
ip_t *ip;
fr_info_t *fin;
{
u_short optmsk = 0, secmsk = 0, auth = 0;
int i, mv, ol, off, p, plen, v;
fr_ip_t *fi = &fin->fin_fi;
const struct optlist *op;
u_char *s, opt;
tcphdr_t *tcp;
fin->fin_rev = 0;
fin->fin_fr = NULL;
fin->fin_tcpf = 0;
fin->fin_data[0] = 0;
fin->fin_data[1] = 0;
fin->fin_rule = -1;
fin->fin_group = -1;
#ifdef _KERNEL
fin->fin_icode = ipl_unreach;
#endif
v = fin->fin_v;
fi->fi_v = v;
fin->fin_hlen = hlen;
if (v == 4) {
fin->fin_id = ip->ip_id;
fi->fi_tos = ip->ip_tos;
off = (ip->ip_off & IP_OFFMASK) << 3;
tcp = (tcphdr_t *)((char *)ip + hlen);
(*(((u_short *)fi) + 1)) = (*(((u_short *)ip) + 4));
fi->fi_src.i6[1] = 0;
fi->fi_src.i6[2] = 0;
fi->fi_src.i6[3] = 0;
fi->fi_dst.i6[1] = 0;
fi->fi_dst.i6[2] = 0;
fi->fi_dst.i6[3] = 0;
fi->fi_saddr = ip->ip_src.s_addr;
fi->fi_daddr = ip->ip_dst.s_addr;
p = ip->ip_p;
fi->fi_fl = (hlen > sizeof(ip_t)) ? FI_OPTIONS : 0;
if (ip->ip_off & 0x3fff)
fi->fi_fl |= FI_FRAG;
plen = ip->ip_len;
fin->fin_dlen = plen - hlen;
}
#ifdef USE_INET6
else if (v == 6) {
ip6_t *ip6 = (ip6_t *)ip;
off = 0;
p = ip6->ip6_nxt;
fi->fi_p = p;
fi->fi_ttl = ip6->ip6_hlim;
tcp = (tcphdr_t *)(ip6 + 1);
fi->fi_src.in6 = ip6->ip6_src;
fi->fi_dst.in6 = ip6->ip6_dst;
fin->fin_id = (u_short)(ip6->ip6_flow & 0xffff);
fi->fi_tos = 0;
fi->fi_fl = 0;
plen = ntohs(ip6->ip6_plen);
fin->fin_dlen = plen;
}
#endif
else
return;
fin->fin_off = off;
fin->fin_plen = plen;
fin->fin_dp = (void *)tcp;
switch (p)
{
#ifdef USE_INET6
case IPPROTO_ICMPV6 :
{
int minicmpsz = sizeof(struct icmp6_hdr);
struct icmp6_hdr *icmp6;
if (fin->fin_dlen > 1) {
fin->fin_data[0] = *(u_short *)tcp;
icmp6 = (struct icmp6_hdr *)tcp;
switch (icmp6->icmp6_type)
{
case ICMP6_ECHO_REPLY :
case ICMP6_ECHO_REQUEST :
minicmpsz = ICMP6ERR_MINPKTLEN;
break;
case ICMP6_DST_UNREACH :
case ICMP6_PACKET_TOO_BIG :
case ICMP6_TIME_EXCEEDED :
case ICMP6_PARAM_PROB :
minicmpsz = ICMP6ERR_IPICMPHLEN;
break;
default :
break;
}
}
if (!(plen >= hlen + minicmpsz))
fi->fi_fl |= FI_SHORT;
break;
}
#endif
case IPPROTO_ICMP :
{
int minicmpsz = sizeof(struct icmp);
icmphdr_t *icmp;
if (!off && (fin->fin_dlen > 1)) {
fin->fin_data[0] = *(u_short *)tcp;
icmp = (icmphdr_t *)tcp;
if (icmp->icmp_type == ICMP_ECHOREPLY ||
icmp->icmp_type == ICMP_ECHO)
minicmpsz = ICMP_MINLEN;
/*
* type(1) + code(1) + cksum(2) + id(2) seq(2) +
* 3*timestamp(3*4)
*/
else if (icmp->icmp_type == ICMP_TSTAMP ||
icmp->icmp_type == ICMP_TSTAMPREPLY)
minicmpsz = 20;
/*
* type(1) + code(1) + cksum(2) + id(2) seq(2) +
* mask(4)
*/
else if (icmp->icmp_type == ICMP_MASKREQ ||
icmp->icmp_type == ICMP_MASKREPLY)
minicmpsz = 12;
}
if ((!(plen >= hlen + minicmpsz) && !off) ||
(off && off < sizeof(struct icmp)))
fi->fi_fl |= FI_SHORT;
break;
}
case IPPROTO_TCP :
fi->fi_fl |= FI_TCPUDP;
#ifdef USE_INET6
if (v == 6) {
if (plen < sizeof(struct tcphdr))
fi->fi_fl |= FI_SHORT;
} else
#endif
if (v == 4) {
if ((!IPMINLEN(ip, tcphdr) && !off) ||
(off && off < sizeof(struct tcphdr)))
fi->fi_fl |= FI_SHORT;
}
if (!(fi->fi_fl & FI_SHORT) && !off)
fin->fin_tcpf = tcp->th_flags;
goto getports;
case IPPROTO_UDP :
fi->fi_fl |= FI_TCPUDP;
#ifdef USE_INET6
if (v == 6) {
if (plen < sizeof(struct udphdr))
fi->fi_fl |= FI_SHORT;
} else
#endif
if (v == 4) {
if ((!IPMINLEN(ip, udphdr) && !off) ||
(off && off < sizeof(struct udphdr)))
fi->fi_fl |= FI_SHORT;
}
getports:
if (!off && (fin->fin_dlen > 3)) {
fin->fin_data[0] = ntohs(tcp->th_sport);
fin->fin_data[1] = ntohs(tcp->th_dport);
}
break;
default :
break;
}
#ifdef USE_INET6
if (v == 6) {
fi->fi_optmsk = 0;
fi->fi_secmsk = 0;
fi->fi_auth = 0;
return;
}
#endif
for (s = (u_char *)(ip + 1), hlen -= (int)sizeof(*ip); hlen > 0; ) {
opt = *s;
if (opt == '\0')
break;
else if (opt == IPOPT_NOP)
ol = 1;
else {
if (hlen < 2)
break;
ol = (int)*(s + 1);
if (ol < 2 || ol > hlen)
break;
}
for (i = 9, mv = 4; mv >= 0; ) {
op = ipopts + i;
if (opt == (u_char)op->ol_val) {
optmsk |= op->ol_bit;
if (opt == IPOPT_SECURITY) {
const struct optlist *sp;
u_char sec;
int j, m;
sec = *(s + 2); /* classification */
for (j = 3, m = 2; m >= 0; ) {
sp = secopt + j;
if (sec == sp->ol_val) {
secmsk |= sp->ol_bit;
auth = *(s + 3);
auth *= 256;
auth += *(s + 4);
break;
}
if (sec < sp->ol_val)
j -= m--;
else
j += m--;
}
}
break;
}
if (opt < op->ol_val)
i -= mv--;
else
i += mv--;
}
hlen -= ol;
s += ol;
}
if (auth && !(auth & 0x0100))
auth &= 0xff00;
fi->fi_optmsk = optmsk;
fi->fi_secmsk = secmsk;
fi->fi_auth = auth;
}
/*
* check an IP packet for TCP/UDP characteristics such as ports and flags.
*/
int fr_tcpudpchk(ft, fin)
frtuc_t *ft;
fr_info_t *fin;
{
u_short po, tup;
char i;
int err = 1;
/*
* Both ports should *always* be in the first fragment.
* So far, I cannot find any cases where they can not be.
*
* compare destination ports
*/
if ((i = (int)ft->ftu_dcmp)) {
po = ft->ftu_dport;
tup = fin->fin_data[1];
/*
* Do opposite test to that required and
* continue if that succeeds.
*/
if (!--i && tup != po) /* EQUAL */
err = 0;
else if (!--i && tup == po) /* NOTEQUAL */
err = 0;
else if (!--i && tup >= po) /* LESSTHAN */
err = 0;
else if (!--i && tup <= po) /* GREATERTHAN */
err = 0;
else if (!--i && tup > po) /* LT or EQ */
err = 0;
else if (!--i && tup < po) /* GT or EQ */
err = 0;
else if (!--i && /* Out of range */
(tup >= po && tup <= ft->ftu_dtop))
err = 0;
else if (!--i && /* In range */
(tup <= po || tup >= ft->ftu_dtop))
err = 0;
}
/*
* compare source ports
*/
if (err && (i = (int)ft->ftu_scmp)) {
po = ft->ftu_sport;
tup = fin->fin_data[0];
if (!--i && tup != po)
err = 0;
else if (!--i && tup == po)
err = 0;
else if (!--i && tup >= po)
err = 0;
else if (!--i && tup <= po)
err = 0;
else if (!--i && tup > po)
err = 0;
else if (!--i && tup < po)
err = 0;
else if (!--i && /* Out of range */
(tup >= po && tup <= ft->ftu_stop))
err = 0;
else if (!--i && /* In range */
(tup <= po || tup >= ft->ftu_stop))
err = 0;
}
/*
* If we don't have all the TCP/UDP header, then how can we
* expect to do any sort of match on it ? If we were looking for
* TCP flags, then NO match. If not, then match (which should
* satisfy the "short" class too).
*/
if (err && (fin->fin_fi.fi_p == IPPROTO_TCP)) {
if (fin->fin_fi.fi_fl & FI_SHORT)
return !(ft->ftu_tcpf | ft->ftu_tcpfm);
/*
* Match the flags ? If not, abort this match.
*/
if (ft->ftu_tcpfm &&
ft->ftu_tcpf != (fin->fin_tcpf & ft->ftu_tcpfm)) {
FR_DEBUG(("f. %#x & %#x != %#x\n", fin->fin_tcpf,
ft->ftu_tcpfm, ft->ftu_tcpf));
err = 0;
}
}
return err;
}
/*
* Check the input/output list of rules for a match and result.
* Could be per interface, but this gets real nasty when you don't have
* kernel sauce.
*/
int fr_scanlist(pass, ip, fin, m)
u_32_t pass;
ip_t *ip;
fr_info_t *fin;
void *m;
{
struct frentry *fr;
fr_ip_t *fi = &fin->fin_fi;
int rulen, portcmp = 0, off, skip = 0, logged = 0;
u_32_t passt;
fr = fin->fin_fr;
fin->fin_fr = NULL;
fin->fin_rule = 0;
fin->fin_group = 0;
if (fin->fin_v == 4)
off = ip->ip_off & IP_OFFMASK;
else
off = 0;
pass |= (fi->fi_fl << 24);
if ((fi->fi_fl & FI_TCPUDP) && (fin->fin_dlen > 3) && !off)
portcmp = 1;
for (rulen = 0; fr; fr = fr->fr_next, rulen++) {
if (skip) {
skip--;
continue;
}
/*
* In all checks below, a null (zero) value in the
* filter struture is taken to mean a wildcard.
*
* check that we are working for the right interface
*/
#ifdef _KERNEL
# if BSD >= 199306
if (fin->fin_out != 0) {
if ((fr->fr_oifa &&
fr->fr_oifa != ((mb_t *)m)->m_pkthdr.rcvif) ||
(fr->fr_ifa && fr->fr_ifa != fin->fin_ifp))
continue;
} else
# endif
if (fr->fr_ifa && fr->fr_ifa != fin->fin_ifp)
continue;
#else
if (opts & (OPT_VERBOSE|OPT_DEBUG))
printf("\n");
FR_VERBOSE(("%c", (pass & FR_PASS) ? 'p' :
(pass & FR_AUTH) ? 'a' : 'b'));
if (fr->fr_ifa && fr->fr_ifa != fin->fin_ifp)
continue;
FR_VERBOSE((":i"));
#endif
{
u_32_t *ld, *lm, *lip;
int i;
lip = (u_32_t *)fi;
lm = (u_32_t *)&fr->fr_mip;
ld = (u_32_t *)&fr->fr_ip;
i = ((*lip & *lm) != *ld);
FR_DEBUG(("0. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
if (i)
continue;
/*
* We now know whether the packet version and the
* rule version match, along with protocol, ttl and
* tos.
*/
lip++, lm++, ld++;
/*
* Unrolled loops (4 each, for 32 bits).
*/
i |= ((*lip & *lm) != *ld) << 19;
FR_DEBUG(("1a. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
if (fi->fi_v == 6) {
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld) << 19;
FR_DEBUG(("1b. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld) << 19;
FR_DEBUG(("1c. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld) << 19;
FR_DEBUG(("1d. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
} else {
lip += 3;
lm += 3;
ld += 3;
}
i ^= (fr->fr_flags & FR_NOTSRCIP);
if (i)
continue;
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld) << 20;
FR_DEBUG(("2a. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
if (fi->fi_v == 6) {
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld) << 20;
FR_DEBUG(("2b. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld) << 20;
FR_DEBUG(("2c. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld) << 20;
FR_DEBUG(("2d. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
} else {
lip += 3;
lm += 3;
ld += 3;
}
i ^= (fr->fr_flags & FR_NOTDSTIP);
if (i)
continue;
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld);
FR_DEBUG(("3. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
lip++, lm++, ld++;
i |= ((*lip & *lm) != *ld);
FR_DEBUG(("4. %#08x & %#08x != %#08x\n",
*lip, *lm, *ld));
if (i)
continue;
}
/*
* If a fragment, then only the first has what we're looking
* for here...
*/
if (!portcmp && (fr->fr_dcmp || fr->fr_scmp || fr->fr_tcpf ||
fr->fr_tcpfm))
continue;
if (fi->fi_fl & FI_TCPUDP) {
if (!fr_tcpudpchk(&fr->fr_tuc, fin))
continue;
} else if (fr->fr_icmpm || fr->fr_icmp) {
if ((fi->fi_p != IPPROTO_ICMP) || off ||
(fin->fin_dlen < 2))
continue;
if ((fin->fin_data[0] & fr->fr_icmpm) != fr->fr_icmp) {
FR_DEBUG(("i. %#x & %#x != %#x\n",
fin->fin_data[0], fr->fr_icmpm,
fr->fr_icmp));
continue;
}
}
FR_VERBOSE(("*"));
/*
* Just log this packet...
*/
passt = fr->fr_flags;
#if (BSD >= 199306) && (defined(_KERNEL) || defined(KERNEL))
if (securelevel <= 0)
#endif
if ((passt & FR_CALLNOW) && fr->fr_func)
passt = (*fr->fr_func)(passt, ip, fin);
fin->fin_fr = fr;
#ifdef IPFILTER_LOG
if ((passt & FR_LOGMASK) == FR_LOG) {
if (!IPLLOG(passt, ip, fin, m)) {
if (passt & FR_LOGORBLOCK)
passt |= FR_BLOCK|FR_QUICK;
ATOMIC_INCL(frstats[fin->fin_out].fr_skip);
}
ATOMIC_INCL(frstats[fin->fin_out].fr_pkl);
logged = 1;
}
#endif /* IPFILTER_LOG */
if (!(skip = fr->fr_skip) && (passt & FR_LOGMASK) != FR_LOG)
pass = passt;
FR_DEBUG(("pass %#x\n", pass));
ATOMIC_INCL(fr->fr_hits);
if (pass & FR_ACCOUNT)
fr->fr_bytes += (U_QUAD_T)ip->ip_len;
else
fin->fin_icode = fr->fr_icode;
fin->fin_rule = rulen;
fin->fin_group = fr->fr_group;
if (fr->fr_grp) {
fin->fin_fr = fr->fr_grp;
pass = fr_scanlist(pass, ip, fin, m);
if (fin->fin_fr == NULL) {
fin->fin_rule = rulen;
fin->fin_group = fr->fr_group;
fin->fin_fr = fr;
}
if (pass & FR_DONTCACHE)
logged = 1;
}
if (pass & FR_QUICK)
break;
}
if (logged)
pass |= FR_DONTCACHE;
return pass;
}
#if defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 105110000) && \
defined(_KERNEL)
#include <net/pfil.h>
int
fr_check_wrapper(void *arg, struct mbuf **mp, struct ifnet *ifp, int dir)
{
struct ip *ip = mtod(*mp, struct ip *);
int rv, hlen = ip->ip_hl << 2;
#if defined(M_CSUM_TCPv4)
/*
* If the packet is out-bound, we can't delay checksums
* here. For in-bound, the checksum has already been
* validated.
*/
if (dir == PFIL_OUT) {
if ((*mp)->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4)) {
in_delayed_cksum(*mp);
(*mp)->m_pkthdr.csum_flags &=
~(M_CSUM_TCPv4|M_CSUM_UDPv4);
}
}
#endif /* M_CSUM_TCPv4 */
/*
* We get the packet with all fields in network byte
* order. We expect ip_len and ip_off to be in host
* order. We frob them, call the filter, then frob
* them back.
*
* Note, we don't need to update the checksum, because
* it has already been verified.
*/
NTOHS(ip->ip_len);
NTOHS(ip->ip_off);
rv = fr_check(ip, hlen, ifp, (dir == PFIL_OUT), mp);
if (rv == 0 && *mp != NULL) {
ip = mtod(*mp, struct ip *);
HTONS(ip->ip_len);
HTONS(ip->ip_off);
}
return (rv);
}
#ifdef USE_INET6
#include <netinet/ip6.h>
int
fr_check_wrapper6(void *arg, struct mbuf **mp, struct ifnet *ifp, int dir)
{
return (fr_check(mtod(*mp, struct ip *), sizeof(struct ip6_hdr),
ifp, (dir == PFIL_OUT), mp));
}
#endif
#endif /* __NetBSD_Version >= 105110000 && _KERNEL */
/*
* frcheck - filter check
* check using source and destination addresses/ports in a packet whether
* or not to pass it on or not.
*/
int fr_check(ip, hlen, ifp, out
#if defined(_KERNEL) && SOLARIS
, qif, mp)
qif_t *qif;
#else
, mp)
#endif
mb_t **mp;
ip_t *ip;
int hlen;
void *ifp;
int out;
{
/*
* The above really sucks, but short of writing a diff
*/
fr_info_t frinfo, *fc;
register fr_info_t *fin = &frinfo;
int changed, error = EHOSTUNREACH, v = ip->ip_v;
frentry_t *fr = NULL, *list;
u_32_t pass, apass;
#if !SOLARIS || !defined(_KERNEL)
mb_t *m = *mp;
#endif
#ifdef _KERNEL
int p, len, drop = 0, logit = 0;
mb_t *mc = NULL;
# if !defined(__SVR4) && !defined(__svr4__)
# ifdef __sgi
char hbuf[(0xf << 2) + sizeof(struct icmp) + sizeof(ip_t) + 8];
# endif
int up;
# ifdef M_CANFASTFWD
/*
* XXX For now, IP Filter and fast-forwarding of cached flows
* XXX are mutually exclusive. Eventually, IP Filter should
* XXX get a "can-fast-forward" filter rule.
*/
m->m_flags &= ~M_CANFASTFWD;
# endif /* M_CANFASTFWD */
# ifdef CSUM_DELAY_DATA
/*
* disable delayed checksums.
*/
if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
in_delayed_cksum(m);
m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
}
# endif /* CSUM_DELAY_DATA */
# ifdef USE_INET6
if (v == 6) {
len = ntohs(((ip6_t*)ip)->ip6_plen);
p = ((ip6_t *)ip)->ip6_nxt;
} else
# endif
{
p = ip->ip_p;
len = ip->ip_len;
}
if ((p == IPPROTO_TCP || p == IPPROTO_UDP || p == IPPROTO_ICMP
# ifdef USE_INET6
|| (v == 6 && p == IPPROTO_ICMPV6)
# endif
)) {
int plen = 0;
if ((v == 6) || (ip->ip_off & IP_OFFMASK) == 0)
switch(p)
{
case IPPROTO_TCP:
plen = sizeof(tcphdr_t);
break;
case IPPROTO_UDP:
plen = sizeof(udphdr_t);
break;
/* 96 - enough for complete ICMP error IP header */
case IPPROTO_ICMP:
plen = ICMPERR_MAXPKTLEN - sizeof(ip_t);
break;
# ifdef USE_INET6
case IPPROTO_ICMPV6 :
/*
* XXX does not take intermediate header
* into account
*/
plen = ICMP6ERR_MINPKTLEN + 8 - sizeof(ip6_t);
break;
# endif
}
up = MIN(hlen + plen, len);
if (up > m->m_len) {
# ifdef __sgi
/* Under IRIX, avoid m_pullup as it makes ping <hostname> panic */
if ((up > sizeof(hbuf)) || (m_length(m) < up)) {
ATOMIC_INCL(frstats[out].fr_pull[1]);
return -1;
}
m_copydata(m, 0, up, hbuf);
ATOMIC_INCL(frstats[out].fr_pull[0]);
ip = (ip_t *)hbuf;
# else /* __ sgi */
# ifndef linux
if ((*mp = m_pullup(m, up)) == 0) {
ATOMIC_INCL(frstats[out].fr_pull[1]);
return -1;
} else {
ATOMIC_INCL(frstats[out].fr_pull[0]);
m = *mp;
ip = mtod(m, ip_t *);
}
# endif /* !linux */
# endif /* __sgi */
} else
up = 0;
} else
up = 0;
# endif /* !defined(__SVR4) && !defined(__svr4__) */
# if SOLARIS
mb_t *m = qif->qf_m;
if ((u_int)ip & 0x3)
return 2;
fin->fin_qfm = m;
fin->fin_qif = qif;
# endif
#endif /* _KERNEL */
/*
* Be careful here: ip_id is in network byte order when called
* from ip_output()
*/
if ((out) && (v == 4))
ip->ip_id = ntohs(ip->ip_id);
changed = 0;
fin->fin_ifp = ifp;
fin->fin_v = v;
fin->fin_out = out;
fin->fin_mp = mp;
fr_makefrip(hlen, ip, fin);
#ifdef _KERNEL
# ifdef USE_INET6
if (v == 6) {
ATOMIC_INCL(frstats[0].fr_ipv6[out]);
if (((ip6_t *)ip)->ip6_hlim < fr_minttl) {
ATOMIC_INCL(frstats[0].fr_badttl);
if (fr_minttllog)
logit = -2;
}
} else
# endif
if (!out) {
if (fr_chksrc && !fr_verifysrc(ip->ip_src, ifp)) {
ATOMIC_INCL(frstats[0].fr_badsrc);
if (fr_chksrc == 2)
logit = -2;
} else if (ip->ip_ttl < fr_minttl) {
ATOMIC_INCL(frstats[0].fr_badttl);
if (fr_minttllog)
logit = -3;
}
}
if (drop) {
# ifdef IPFILTER_LOG
if (logit) {
fin->fin_group = logit;
pass = FR_INQUE|FR_NOMATCH|FR_LOGB;
(void) IPLLOG(pass, ip, fin, m);
}
# endif
# if !SOLARIS
m_freem(m);
# endif
return error;
}
#endif
pass = fr_pass;
if (fin->fin_fi.fi_fl & FI_SHORT) {
ATOMIC_INCL(frstats[out].fr_short);
}
READ_ENTER(&ipf_mutex);
if (fin->fin_fi.fi_fl & FI_SHORT)
ATOMIC_INCL(frstats[out].fr_short);
/*
* Check auth now. This, combined with the check below to see if apass
* is 0 is to ensure that we don't count the packet twice, which can
* otherwise occur when we reprocess it. As it is, we only count it
* after it has no auth. table matchup. This also stops NAT from
* occuring until after the packet has been auth'd.
*/
apass = fr_checkauth(ip, fin);
if (!out) {
#ifdef USE_INET6
if (v == 6)
list = ipacct6[0][fr_active];
else
#endif
list = ipacct[0][fr_active];
changed = ip_natin(ip, fin);
if (!apass && (fin->fin_fr = list) &&
(fr_scanlist(FR_NOMATCH, ip, fin, m) & FR_ACCOUNT)) {
ATOMIC_INCL(frstats[0].fr_acct);
}
}
if (apass || (!(fr = ipfr_knownfrag(ip, fin)) &&
!(fr = fr_checkstate(ip, fin)))) {
/*
* If a packet is found in the auth table, then skip checking
* the access lists for permission but we do need to consider
* the result as if it were from the ACL's.
*/
if (!apass) {
fc = frcache + out;
if (!bcmp((char *)fin, (char *)fc, FI_CSIZE)) {
/*
* copy cached data so we can unlock the mutex
* earlier.
*/
bcopy((char *)fc, (char *)fin, FI_COPYSIZE);
ATOMIC_INCL(frstats[out].fr_chit);
if ((fr = fin->fin_fr)) {
ATOMIC_INCL(fr->fr_hits);
pass = fr->fr_flags;
}
} else {
#ifdef USE_INET6
if (v == 6)
list = ipfilter6[out][fr_active];
else
#endif
list = ipfilter[out][fr_active];
if ((fin->fin_fr = list))
pass = fr_scanlist(fr_pass, ip, fin, m);
if (!(pass & (FR_KEEPSTATE|FR_DONTCACHE)))
bcopy((char *)fin, (char *)fc,
FI_COPYSIZE);
if (pass & FR_NOMATCH) {
ATOMIC_INCL(frstats[out].fr_nom);
}
}
fr = fin->fin_fr;
} else
pass = apass;
/*
* If we fail to add a packet to the authorization queue,
* then we drop the packet later. However, if it was added
* then pretend we've dropped it already.
*/
if ((pass & FR_AUTH))
if (fr_newauth((mb_t *)m, fin, ip) != 0)
#ifdef _KERNEL
m = *mp = NULL;
#else
;
#endif
if (pass & FR_PREAUTH) {
READ_ENTER(&ipf_auth);
if ((fin->fin_fr = ipauth) &&
(pass = fr_scanlist(0, ip, fin, m))) {
ATOMIC_INCL(fr_authstats.fas_hits);
} else {
ATOMIC_INCL(fr_authstats.fas_miss);
}
RWLOCK_EXIT(&ipf_auth);
}
fin->fin_fr = fr;
if ((pass & (FR_KEEPFRAG|FR_KEEPSTATE)) == FR_KEEPFRAG) {
if (fin->fin_fi.fi_fl & FI_FRAG) {
if (ipfr_newfrag(ip, fin, pass) == -1) {
ATOMIC_INCL(frstats[out].fr_bnfr);
} else {
ATOMIC_INCL(frstats[out].fr_nfr);
}
} else {
ATOMIC_INCL(frstats[out].fr_cfr);
}
}
if (pass & FR_KEEPSTATE) {
if (fr_addstate(ip, fin, 0) == NULL) {
ATOMIC_INCL(frstats[out].fr_bads);
} else {
ATOMIC_INCL(frstats[out].fr_ads);
}
}
} else if (fr != NULL) {
pass = fr->fr_flags;
if (pass & FR_LOGFIRST)
pass &= ~(FR_LOGFIRST|FR_LOG);
}
#if (BSD >= 199306) && (defined(_KERNEL) || defined(KERNEL))
if (securelevel <= 0)
#endif
if (fr && fr->fr_func && !(pass & FR_CALLNOW))
pass = (*fr->fr_func)(pass, ip, fin);
/*
* Only count/translate packets which will be passed on, out the
* interface.
*/
if (out && (pass & FR_PASS)) {
#ifdef USE_INET6
if (v == 6)
list = ipacct6[1][fr_active];
else
#endif
list = ipacct[1][fr_active];
if ((fin->fin_fr = list) &&
(fr_scanlist(FR_NOMATCH, ip, fin, m) & FR_ACCOUNT)) {
ATOMIC_INCL(frstats[1].fr_acct);
}
fin->fin_fr = fr;
changed = ip_natout(ip, fin);
} else
fin->fin_fr = fr;
RWLOCK_EXIT(&ipf_mutex);
#ifdef IPFILTER_LOG
if ((fr_flags & FF_LOGGING) || (pass & FR_LOGMASK)) {
if ((fr_flags & FF_LOGNOMATCH) && (pass & FR_NOMATCH)) {
pass |= FF_LOGNOMATCH;
ATOMIC_INCL(frstats[out].fr_npkl);
goto logit;
} else if (((pass & FR_LOGMASK) == FR_LOGP) ||
((pass & FR_PASS) && (fr_flags & FF_LOGPASS))) {
if ((pass & FR_LOGMASK) != FR_LOGP)
pass |= FF_LOGPASS;
ATOMIC_INCL(frstats[out].fr_ppkl);
goto logit;
} else if (((pass & FR_LOGMASK) == FR_LOGB) ||
((pass & FR_BLOCK) && (fr_flags & FF_LOGBLOCK))) {
if ((pass & FR_LOGMASK) != FR_LOGB)
pass |= FF_LOGBLOCK;
ATOMIC_INCL(frstats[out].fr_bpkl);
logit:
if (!IPLLOG(pass, ip, fin, m)) {
ATOMIC_INCL(frstats[out].fr_skip);
if ((pass & (FR_PASS|FR_LOGORBLOCK)) ==
(FR_PASS|FR_LOGORBLOCK))
pass ^= FR_PASS|FR_BLOCK;
}
}
}
#endif /* IPFILTER_LOG */
if ((out) && (v == 4))
ip->ip_id = htons(ip->ip_id);
#ifdef _KERNEL
/*
* Only allow FR_DUP to work if a rule matched - it makes no sense to
* set FR_DUP as a "default" as there are no instructions about where
* to send the packet.
*/
if (fr && (pass & FR_DUP))
# if SOLARIS
mc = dupmsg(m);
# else
# ifndef linux
mc = m_copy(m, 0, M_COPYALL);
# else
;
# endif
# endif
#endif
if (pass & FR_PASS) {
ATOMIC_INCL(frstats[out].fr_pass);
} else if (pass & FR_BLOCK) {
ATOMIC_INCL(frstats[out].fr_block);
/*
* Should we return an ICMP packet to indicate error
* status passing through the packet filter ?
* WARNING: ICMP error packets AND TCP RST packets should
* ONLY be sent in repsonse to incoming packets. Sending them
* in response to outbound packets can result in a panic on
* some operating systems.
*/
if (!out) {
#ifdef _KERNEL
if (pass & FR_RETICMP) {
int dst;
if ((pass & FR_RETMASK) == FR_FAKEICMP)
dst = 1;
else
dst = 0;
send_icmp_err(ip, ICMP_UNREACH, fin, dst);
ATOMIC_INCL(frstats[0].fr_ret);
} else if (((pass & FR_RETMASK) == FR_RETRST) &&
!(fin->fin_fi.fi_fl & FI_SHORT)) {
if (send_reset(ip, fin) == 0) {
ATOMIC_INCL(frstats[1].fr_ret);
}
}
#else
if ((pass & FR_RETMASK) == FR_RETICMP) {
verbose("- ICMP unreachable sent\n");
ATOMIC_INCL(frstats[0].fr_ret);
} else if ((pass & FR_RETMASK) == FR_FAKEICMP) {
verbose("- forged ICMP unreachable sent\n");
ATOMIC_INCL(frstats[0].fr_ret);
} else if (((pass & FR_RETMASK) == FR_RETRST) &&
!(fin->fin_fi.fi_fl & FI_SHORT)) {
verbose("- TCP RST sent\n");
ATOMIC_INCL(frstats[1].fr_ret);
}
#endif
} else {
if (pass & FR_RETRST)
error = ECONNRESET;
}
}
/*
* If we didn't drop off the bottom of the list of rules (and thus
* the 'current' rule fr is not NULL), then we may have some extra
* instructions about what to do with a packet.
* Once we're finished return to our caller, freeing the packet if
* we are dropping it (* BSD ONLY *).
*/
if ((changed == -1) && (pass & FR_PASS)) {
pass &= ~FR_PASS;
pass |= FR_BLOCK;
}
#if defined(_KERNEL)
# if !SOLARIS
# if !defined(linux)
if (fr) {
frdest_t *fdp = &fr->fr_tif;
if (((pass & FR_FASTROUTE) && !out) ||
(fdp->fd_ifp && fdp->fd_ifp != (struct ifnet *)-1)) {
if (ipfr_fastroute(m, fin, fdp) == 0)
m = *mp = NULL;
}
if (mc)
ipfr_fastroute(mc, fin, &fr->fr_dif);
}
if (!(pass & FR_PASS) && m)
m_freem(m);
# ifdef __sgi
else if (changed && up && m)
m_copyback(m, 0, up, hbuf);
# endif
# endif /* !linux */
# else /* !SOLARIS */
if (fr) {
frdest_t *fdp = &fr->fr_tif;
if (((pass & FR_FASTROUTE) && !out) ||
(fdp->fd_ifp && fdp->fd_ifp != (struct ifnet *)-1)) {
if (ipfr_fastroute(ip, m, mp, fin, fdp) == 0)
m = *mp = NULL;
}
if (mc)
ipfr_fastroute(ip, mc, mp, fin, &fr->fr_dif);
}
# endif /* !SOLARIS */
return (pass & FR_PASS) ? 0 : error;
#else /* _KERNEL */
if (pass & FR_NOMATCH)
return 1;
if (pass & FR_PASS)
return 0;
if (pass & FR_AUTH)
return -2;
return -1;
#endif /* _KERNEL */
}
/*
* ipf_cksum
* addr should be 16bit aligned and len is in bytes.
* length is in bytes
*/
u_short ipf_cksum(addr, len)
u_short *addr;
int len;
{
u_32_t sum = 0;
for (sum = 0; len > 1; len -= 2)
sum += *addr++;
/* mop up an odd byte, if necessary */
if (len == 1)
sum += *(u_char *)addr;
/*
* add back carry outs from top 16 bits to low 16 bits
*/
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
return (u_short)(~sum);
}
/*
* NB: This function assumes we've pullup'd enough for all of the IP header
* and the TCP header. We also assume that data blocks aren't allocated in
* odd sizes.
*/
u_short fr_tcpsum(m, ip, tcp)
mb_t *m;
ip_t *ip;
tcphdr_t *tcp;
{
u_short *sp, slen, ts;
u_int sum, sum2;
int hlen;
/*
* Add up IP Header portion
*/
hlen = ip->ip_hl << 2;
slen = ip->ip_len - hlen;
sum = htons((u_short)ip->ip_p);
sum += htons(slen);
sp = (u_short *)&ip->ip_src;
sum += *sp++; /* ip_src */
sum += *sp++;
sum += *sp++; /* ip_dst */
sum += *sp++;
ts = tcp->th_sum;
tcp->th_sum = 0;
#ifdef KERNEL
# if SOLARIS
sum2 = ip_cksum(m, hlen, sum); /* hlen == offset */
sum2 = (sum2 & 0xffff) + (sum2 >> 16);
sum2 = ~sum2 & 0xffff;
# else /* SOLARIS */
# if defined(BSD) || defined(sun)
# if BSD >= 199306
m->m_data += hlen;
# else
m->m_off += hlen;
# endif
m->m_len -= hlen;
sum2 = in_cksum(m, slen);
m->m_len += hlen;
# if BSD >= 199306
m->m_data -= hlen;
# else
m->m_off -= hlen;
# endif
/*
* Both sum and sum2 are partial sums, so combine them together.
*/
sum = (sum & 0xffff) + (sum >> 16);
sum = ~sum & 0xffff;
sum2 += sum;
sum2 = (sum2 & 0xffff) + (sum2 >> 16);
# else /* defined(BSD) || defined(sun) */
{
union {
u_char c[2];
u_short s;
} bytes;
u_short len = ip->ip_len;
# if defined(__sgi)
int add;
# endif
/*
* Add up IP Header portion
*/
sp = (u_short *)&ip->ip_src;
len -= (ip->ip_hl << 2);
sum = ntohs(IPPROTO_TCP);
sum += htons(len);
sum += *sp++; /* ip_src */
sum += *sp++;
sum += *sp++; /* ip_dst */
sum += *sp++;
if (sp != (u_short *)tcp)
sp = (u_short *)tcp;
sum += *sp++; /* sport */
sum += *sp++; /* dport */
sum += *sp++; /* seq */
sum += *sp++;
sum += *sp++; /* ack */
sum += *sp++;
sum += *sp++; /* off */
sum += *sp++; /* win */
sum += *sp++; /* Skip over checksum */
sum += *sp++; /* urp */
# ifdef __sgi
/*
* In case we had to copy the IP & TCP header out of mbufs,
* skip over the mbuf bits which are the header
*/
if ((caddr_t)ip != mtod(m, caddr_t)) {
hlen = (caddr_t)sp - (caddr_t)ip;
while (hlen) {
add = MIN(hlen, m->m_len);
sp = (u_short *)(mtod(m, caddr_t) + add);
hlen -= add;
if (add == m->m_len) {
m = m->m_next;
if (!hlen) {
if (!m)
break;
sp = mtod(m, u_short *);
}
PANIC((!m),("fr_tcpsum(1): not enough data"));
}
}
}
# endif
if (!(len -= sizeof(*tcp)))
goto nodata;
while (len > 1) {
if (((caddr_t)sp - mtod(m, caddr_t)) >= m->m_len) {
m = m->m_next;
PANIC((!m),("fr_tcpsum(2): not enough data"));
sp = mtod(m, u_short *);
}
if (((caddr_t)(sp + 1) - mtod(m, caddr_t)) > m->m_len) {
bytes.c[0] = *(u_char *)sp;
m = m->m_next;
PANIC((!m),("fr_tcpsum(3): not enough data"));
sp = mtod(m, u_short *);
bytes.c[1] = *(u_char *)sp;
sum += bytes.s;
sp = (u_short *)((u_char *)sp + 1);
}
if ((u_long)sp & 1) {
bcopy((char *)sp++, (char *)&bytes.s, sizeof(bytes.s));
sum += bytes.s;
} else
sum += *sp++;
len -= 2;
}
if (len)
sum += ntohs(*(u_char *)sp << 8);
nodata:
while (sum > 0xffff)
sum = (sum & 0xffff) + (sum >> 16);
sum2 = (u_short)(~sum & 0xffff);
}
# endif /* defined(BSD) || defined(sun) */
# endif /* SOLARIS */
#else /* KERNEL */
sum2 = 0;
#endif /* KERNEL */
tcp->th_sum = ts;
return sum2;
}
#if defined(_KERNEL) && ( ((BSD < 199306) && !SOLARIS) || defined(__sgi) )
/*
* Copyright (c) 1982, 1986, 1988, 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)uipc_mbuf.c 8.2 (Berkeley) 1/4/94
* Id: fil.c,v 2.35.2.30 2000/12/17 05:49:22 darrenr Exp
*/
/*
* Copy data from an mbuf chain starting "off" bytes from the beginning,
* continuing for "len" bytes, into the indicated buffer.
*/
void
m_copydata(m, off, len, cp)
mb_t *m;
int off;
int len;
caddr_t cp;
{
unsigned count;
if (off < 0 || len < 0)
panic("m_copydata");
while (off > 0) {
if (m == 0)
panic("m_copydata");
if (off < m->m_len)
break;
off -= m->m_len;
m = m->m_next;
}
while (len > 0) {
if (m == 0)
panic("m_copydata");
count = MIN(m->m_len - off, len);
bcopy(mtod(m, caddr_t) + off, cp, count);
len -= count;
cp += count;
off = 0;
m = m->m_next;
}
}
# ifndef linux
/*
* Copy data from a buffer back into the indicated mbuf chain,
* starting "off" bytes from the beginning, extending the mbuf
* chain if necessary.
*/
void
m_copyback(m0, off, len, cp)
struct mbuf *m0;
int off;
int len;
caddr_t cp;
{
int mlen;
struct mbuf *m = m0, *n;
int totlen = 0;
if (m0 == 0)
return;
while (off > (mlen = m->m_len)) {
off -= mlen;
totlen += mlen;
if (m->m_next == 0) {
n = m_getclr(M_DONTWAIT, m->m_type);
if (n == 0)
goto out;
n->m_len = min(MLEN, len + off);
m->m_next = n;
}
m = m->m_next;
}
while (len > 0) {
mlen = min (m->m_len - off, len);
bcopy(cp, off + mtod(m, caddr_t), (unsigned)mlen);
cp += mlen;
len -= mlen;
mlen += off;
off = 0;
totlen += mlen;
if (len == 0)
break;
if (m->m_next == 0) {
n = m_get(M_DONTWAIT, m->m_type);
if (n == 0)
break;
n->m_len = min(MLEN, len);
m->m_next = n;
}
m = m->m_next;
}
out:
#if 0
if (((m = m0)->m_flags & M_PKTHDR) && (m->m_pkthdr.len < totlen))
m->m_pkthdr.len = totlen;
#endif
return;
}
# endif /* linux */
#endif /* (_KERNEL) && ( ((BSD < 199306) && !SOLARIS) || __sgi) */
frgroup_t *fr_findgroup(num, flags, which, set, fgpp)
u_32_t num, flags;
minor_t which;
int set;
frgroup_t ***fgpp;
{
frgroup_t *fg, **fgp;
if (which == IPL_LOGAUTH)
fgp = &ipfgroups[2][set];
else if (flags & FR_ACCOUNT)
fgp = &ipfgroups[1][set];
else if (flags & (FR_OUTQUE|FR_INQUE))
fgp = &ipfgroups[0][set];
else
return NULL;
num &= 0xffff;
while ((fg = *fgp))
if (fg->fg_num == num)
break;
else
fgp = &fg->fg_next;
if (fgpp)
*fgpp = fgp;
return fg;
}
frgroup_t *fr_addgroup(num, fp, which, set)
u_32_t num;
frentry_t *fp;
minor_t which;
int set;
{
frgroup_t *fg, **fgp;
if ((fg = fr_findgroup(num, fp->fr_flags, which, set, &fgp)))
return fg;
KMALLOC(fg, frgroup_t *);
if (fg) {
fg->fg_num = num;
fg->fg_next = *fgp;
fg->fg_head = fp;
fg->fg_start = &fp->fr_grp;
*fgp = fg;
}
return fg;
}
void fr_delgroup(num, flags, which, set)
u_32_t num, flags;
minor_t which;
int set;
{
frgroup_t *fg, **fgp;
if (!(fg = fr_findgroup(num, flags, which, set, &fgp)))
return;
*fgp = fg->fg_next;
KFREE(fg);
}
/*
* recursively flush rules from the list, descending groups as they are
* encountered. if a rule is the head of a group and it has lost all its
* group members, then also delete the group reference.
*/
static int frflushlist(set, unit, nfreedp, listp)
int set;
minor_t unit;
int *nfreedp;
frentry_t **listp;
{
int freed = 0, i;
frentry_t *fp;
while ((fp = *listp)) {
*listp = fp->fr_next;
if (fp->fr_grp) {
i = frflushlist(set, unit, nfreedp, &fp->fr_grp);
MUTEX_ENTER(&ipf_rw);
fp->fr_ref -= i;
MUTEX_EXIT(&ipf_rw);
}
ATOMIC_DEC32(fp->fr_ref);
if (fp->fr_grhead) {
fr_delgroup(fp->fr_grhead, fp->fr_flags,
unit, set);
fp->fr_grhead = 0;
}
if (fp->fr_ref == 0) {
KFREE(fp);
freed++;
} else
fp->fr_next = NULL;
}
*nfreedp += freed;
return freed;
}
int frflush(unit, flags)
minor_t unit;
int flags;
{
int flushed = 0, set;
if (unit != IPL_LOGIPF)
return 0;
WRITE_ENTER(&ipf_mutex);
bzero((char *)frcache, sizeof(frcache[0]) * 2);
set = fr_active;
if (flags & FR_INACTIVE)
set = 1 - set;
if (flags & FR_OUTQUE) {
#ifdef USE_INET6
(void) frflushlist(set, unit, &flushed, &ipfilter6[1][set]);
(void) frflushlist(set, unit, &flushed, &ipacct6[1][set]);
#endif
(void) frflushlist(set, unit, &flushed, &ipfilter[1][set]);
(void) frflushlist(set, unit, &flushed, &ipacct[1][set]);
}
if (flags & FR_INQUE) {
#ifdef USE_INET6
(void) frflushlist(set, unit, &flushed, &ipfilter6[0][set]);
(void) frflushlist(set, unit, &flushed, &ipacct6[0][set]);
#endif
(void) frflushlist(set, unit, &flushed, &ipfilter[0][set]);
(void) frflushlist(set, unit, &flushed, &ipacct[0][set]);
}
RWLOCK_EXIT(&ipf_mutex);
return flushed;
}
char *memstr(src, dst, slen, dlen)
char *src, *dst;
int slen, dlen;
{
char *s = NULL;
while (dlen >= slen) {
if (bcmp(src, dst, slen) == 0) {
s = dst;
break;
}
dst++;
dlen--;
}
return s;
}
void fixskip(listp, rp, addremove)
frentry_t **listp, *rp;
int addremove;
{
frentry_t *fp;
int rules = 0, rn = 0;
for (fp = *listp; fp && (fp != rp); fp = fp->fr_next, rules++)
;
if (!fp)
return;
for (fp = *listp; fp && (fp != rp); fp = fp->fr_next, rn++)
if (fp->fr_skip && (rn + fp->fr_skip >= rules))
fp->fr_skip += addremove;
}
#ifdef _KERNEL
/*
* count consecutive 1's in bit mask. If the mask generated by counting
* consecutive 1's is different to that passed, return -1, else return #
* of bits.
*/
int countbits(ip)
u_32_t ip;
{
u_32_t ipn;
int cnt = 0, i, j;
ip = ipn = ntohl(ip);
for (i = 32; i; i--, ipn *= 2)
if (ipn & 0x80000000)
cnt++;
else
break;
ipn = 0;
for (i = 32, j = cnt; i; i--, j--) {
ipn *= 2;
if (j > 0)
ipn++;
}
if (ipn == ip)
return cnt;
return -1;
}
/*
* return the first IP Address associated with an interface
*/
int fr_ifpaddr(v, ifptr, inp)
int v;
void *ifptr;
struct in_addr *inp;
{
# ifdef USE_INET6
struct in6_addr *inp6 = NULL;
# endif
# if SOLARIS
ill_t *ill = ifptr;
# else
struct ifnet *ifp = ifptr;
# endif
struct in_addr in;
# if SOLARIS
# ifdef USE_INET6
if (v == 6) {
struct in6_addr in6;
/*
* First is always link local.
*/
if (ill->ill_ipif->ipif_next)
in6 = ill->ill_ipif->ipif_next->ipif_v6lcl_addr;
else
bzero((char *)&in6, sizeof(in6));
bcopy((char *)&in6, (char *)inp, sizeof(in6));
} else
# endif
{
in.s_addr = ill->ill_ipif->ipif_local_addr;
*inp = in;
}
# else /* SOLARIS */
# if linux
;
# else /* linux */
struct sockaddr_in *sin;
struct ifaddr *ifa;
# if (__FreeBSD_version >= 300000)
ifa = TAILQ_FIRST(&ifp->if_addrhead);
# else
# if defined(__NetBSD__) || defined(__OpenBSD__)
ifa = ifp->if_addrlist.tqh_first;
# else
# if defined(__sgi) && defined(IFF_DRVRLOCK) /* IRIX 6 */
ifa = &((struct in_ifaddr *)ifp->in_ifaddr)->ia_ifa;
# else
ifa = ifp->if_addrlist;
# endif
# endif /* __NetBSD__ || __OpenBSD__ */
# endif /* __FreeBSD_version >= 300000 */
# if (BSD < 199306) && !(/*IRIX6*/defined(__sgi) && defined(IFF_DRVRLOCK))
sin = (struct sockaddr_in *)&ifa->ifa_addr;
# else
sin = (struct sockaddr_in *)ifa->ifa_addr;
while (sin && ifa) {
if ((v == 4) && (sin->sin_family == AF_INET))
break;
# ifdef USE_INET6
if ((v == 6) && (sin->sin_family == AF_INET6)) {
inp6 = &((struct sockaddr_in6 *)sin)->sin6_addr;
if (!IN6_IS_ADDR_LINKLOCAL(inp6) &&
!IN6_IS_ADDR_LOOPBACK(inp6))
break;
}
# endif
# if (__FreeBSD_version >= 300000)
ifa = TAILQ_NEXT(ifa, ifa_link);
# else
# if defined(__NetBSD__) || defined(__OpenBSD__)
ifa = ifa->ifa_list.tqe_next;
# else
ifa = ifa->ifa_next;
# endif
# endif /* __FreeBSD_version >= 300000 */
if (ifa)
sin = (struct sockaddr_in *)ifa->ifa_addr;
}
if (ifa == NULL)
sin = NULL;
if (sin == NULL)
return -1;
# endif /* (BSD < 199306) && (!__sgi && IFF_DRVLOCK) */
# ifdef USE_INET6
if (v == 6)
bcopy((char *)inp6, (char *)inp, sizeof(*inp6));
else
# endif
{
in = sin->sin_addr;
*inp = in;
}
# endif /* linux */
# endif /* SOLARIS */
return 0;
}
static void frsynclist(fr)
frentry_t *fr;
{
for (; fr; fr = fr->fr_next) {
if (fr->fr_ifa != NULL) {
fr->fr_ifa = GETUNIT(fr->fr_ifname, fr->fr_ip.fi_v);
if (fr->fr_ifa == NULL)
fr->fr_ifa = (void *)-1;
}
if (fr->fr_grp)
frsynclist(fr->fr_grp);
}
}
void frsync()
{
# if !SOLARIS
register struct ifnet *ifp;
# if defined(__OpenBSD__) || ((NetBSD >= 199511) && (NetBSD < 1991011)) || \
(defined(__FreeBSD_version) && (__FreeBSD_version >= 300000))
# if (NetBSD >= 199905) || defined(__OpenBSD__)
for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_list.tqe_next)
# else
for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_link.tqe_next)
# endif
# else
for (ifp = ifnet; ifp; ifp = ifp->if_next)
# endif
{
ip_natsync(ifp);
ip_statesync(ifp);
}
ip_natsync((struct ifnet *)-1);
# endif
WRITE_ENTER(&ipf_mutex);
frsynclist(ipacct[0][fr_active]);
frsynclist(ipacct[1][fr_active]);
frsynclist(ipfilter[0][fr_active]);
frsynclist(ipfilter[1][fr_active]);
#ifdef USE_INET6
frsynclist(ipacct6[0][fr_active]);
frsynclist(ipacct6[1][fr_active]);
frsynclist(ipfilter6[0][fr_active]);
frsynclist(ipfilter6[1][fr_active]);
#endif
RWLOCK_EXIT(&ipf_mutex);
}
/*
* In the functions below, bcopy() is called because the pointer being
* copied _from_ in this instance is a pointer to a char buf (which could
* end up being unaligned) and on the kernel's local stack.
*/
int ircopyptr(a, b, c)
void *a, *b;
size_t c;
{
caddr_t ca;
int err;
#if SOLARIS
if (copyin(a, (char *)&ca, sizeof(ca)))
return EFAULT;
#else
bcopy(a, &ca, sizeof(ca));
#endif
err = copyin(ca, b, c);
if (err)
err = EFAULT;
return err;
}
int iwcopyptr(a, b, c)
void *a, *b;
size_t c;
{
caddr_t ca;
int err;
#if SOLARIS
if (copyin(b, (char *)&ca, sizeof(ca)))
return EFAULT;
#else
bcopy(b, &ca, sizeof(ca));
#endif
err = copyout(a, ca, c);
if (err)
err = EFAULT;
return err;
}
#else /* _KERNEL */
/*
* return the first IP Address associated with an interface
*/
int fr_ifpaddr(v, ifptr, inp)
int v;
void *ifptr;
struct in_addr *inp;
{
return 0;
}
int ircopyptr(a, b, c)
void *a, *b;
size_t c;
{
caddr_t ca;
bcopy(a, &ca, sizeof(ca));
bcopy(ca, b, c);
return 0;
}
int iwcopyptr(a, b, c)
void *a, *b;
size_t c;
{
caddr_t ca;
bcopy(b, &ca, sizeof(ca));
bcopy(a, ca, c);
return 0;
}
#endif
int fr_lock(data, lockp)
caddr_t data;
int *lockp;
{
int arg, error;
error = IRCOPY(data, (caddr_t)&arg, sizeof(arg));
if (!error) {
error = IWCOPY((caddr_t)lockp, data, sizeof(*lockp));
if (!error)
*lockp = arg;
}
return error;
}
void fr_getstat(fiop)
friostat_t *fiop;
{
bcopy((char *)frstats, (char *)fiop->f_st, sizeof(filterstats_t) * 2);
fiop->f_locks[0] = fr_state_lock;
fiop->f_locks[1] = fr_nat_lock;
fiop->f_locks[2] = fr_frag_lock;
fiop->f_locks[3] = fr_auth_lock;
fiop->f_fin[0] = ipfilter[0][0];
fiop->f_fin[1] = ipfilter[0][1];
fiop->f_fout[0] = ipfilter[1][0];
fiop->f_fout[1] = ipfilter[1][1];
fiop->f_acctin[0] = ipacct[0][0];
fiop->f_acctin[1] = ipacct[0][1];
fiop->f_acctout[0] = ipacct[1][0];
fiop->f_acctout[1] = ipacct[1][1];
#ifdef USE_INET6
fiop->f_fin6[0] = ipfilter6[0][0];
fiop->f_fin6[1] = ipfilter6[0][1];
fiop->f_fout6[0] = ipfilter6[1][0];
fiop->f_fout6[1] = ipfilter6[1][1];
fiop->f_acctin6[0] = ipacct6[0][0];
fiop->f_acctin6[1] = ipacct6[0][1];
fiop->f_acctout6[0] = ipacct6[1][0];
fiop->f_acctout6[1] = ipacct6[1][1];
#else
fiop->f_fin6[0] = NULL;
fiop->f_fin6[1] = NULL;
fiop->f_fout6[0] = NULL;
fiop->f_fout6[1] = NULL;
fiop->f_acctin6[0] = NULL;
fiop->f_acctin6[1] = NULL;
fiop->f_acctout6[0] = NULL;
fiop->f_acctout6[1] = NULL;
#endif
fiop->f_active = fr_active;
fiop->f_froute[0] = ipl_frouteok[0];
fiop->f_froute[1] = ipl_frouteok[1];
fiop->f_running = fr_running;
fiop->f_groups[0][0] = ipfgroups[0][0];
fiop->f_groups[0][1] = ipfgroups[0][1];
fiop->f_groups[1][0] = ipfgroups[1][0];
fiop->f_groups[1][1] = ipfgroups[1][1];
fiop->f_groups[2][0] = ipfgroups[2][0];
fiop->f_groups[2][1] = ipfgroups[2][1];
#ifdef IPFILTER_LOG
fiop->f_logging = 1;
#else
fiop->f_logging = 0;
#endif
fiop->f_defpass = fr_pass;
strncpy(fiop->f_version, ipfilter_version, sizeof(fiop->f_version));
}
#ifdef USE_INET6
int icmptoicmp6types[ICMP_MAXTYPE+1] = {
ICMP6_ECHO_REPLY, /* 0: ICMP_ECHOREPLY */
-1, /* 1: UNUSED */
-1, /* 2: UNUSED */
ICMP6_DST_UNREACH, /* 3: ICMP_UNREACH */
-1, /* 4: ICMP_SOURCEQUENCH */
ND_REDIRECT, /* 5: ICMP_REDIRECT */
-1, /* 6: UNUSED */
-1, /* 7: UNUSED */
ICMP6_ECHO_REQUEST, /* 8: ICMP_ECHO */
-1, /* 9: UNUSED */
-1, /* 10: UNUSED */
ICMP6_TIME_EXCEEDED, /* 11: ICMP_TIMXCEED */
ICMP6_PARAM_PROB, /* 12: ICMP_PARAMPROB */
-1, /* 13: ICMP_TSTAMP */
-1, /* 14: ICMP_TSTAMPREPLY */
-1, /* 15: ICMP_IREQ */
-1, /* 16: ICMP_IREQREPLY */
-1, /* 17: ICMP_MASKREQ */
-1, /* 18: ICMP_MASKREPLY */
};
int icmptoicmp6unreach[ICMP_MAX_UNREACH] = {
ICMP6_DST_UNREACH_ADDR, /* 0: ICMP_UNREACH_NET */
ICMP6_DST_UNREACH_ADDR, /* 1: ICMP_UNREACH_HOST */
-1, /* 2: ICMP_UNREACH_PROTOCOL */
ICMP6_DST_UNREACH_NOPORT, /* 3: ICMP_UNREACH_PORT */
-1, /* 4: ICMP_UNREACH_NEEDFRAG */
ICMP6_DST_UNREACH_NOTNEIGHBOR, /* 5: ICMP_UNREACH_SRCFAIL */
ICMP6_DST_UNREACH_ADDR, /* 6: ICMP_UNREACH_NET_UNKNOWN */
ICMP6_DST_UNREACH_ADDR, /* 7: ICMP_UNREACH_HOST_UNKNOWN */
-1, /* 8: ICMP_UNREACH_ISOLATED */
ICMP6_DST_UNREACH_ADMIN, /* 9: ICMP_UNREACH_NET_PROHIB */
ICMP6_DST_UNREACH_ADMIN, /* 10: ICMP_UNREACH_HOST_PROHIB */
-1, /* 11: ICMP_UNREACH_TOSNET */
-1, /* 12: ICMP_UNREACH_TOSHOST */
ICMP6_DST_UNREACH_ADMIN, /* 13: ICMP_UNREACH_ADMIN_PROHIBIT */
};
#endif