NetBSD/usr.sbin/npf/npfctl/npf_data.c

457 lines
10 KiB
C

/* $NetBSD: npf_data.c,v 1.7 2011/02/02 02:20:25 rmind Exp $ */
/*-
* Copyright (c) 2009-2011 The NetBSD Foundation, Inc.
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/*
* npfctl(8) helper routines.
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: npf_data.c,v 1.7 2011/02/02 02:20:25 rmind Exp $");
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <err.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <assert.h>
#include "npfctl.h"
static struct ifaddrs * ifs_list = NULL;
nl_config_t * npf_conf = NULL;
void
npfctl_init_data(void)
{
npf_conf = npf_config_create();
if (npf_conf == NULL) {
errx(EXIT_FAILURE, "npf_config_create");
}
if (getifaddrs(&ifs_list) == -1) {
err(EXIT_FAILURE, "getifaddrs");
}
}
int
npfctl_ioctl_send(int fd)
{
int error = npf_config_submit(npf_conf, fd);
npf_config_destroy(npf_conf);
return error;
}
/*
* Helper routines:
*
* npfctl_getif() - get interface addresses and index number from name.
* npfctl_parse_v4mask() - parse address/mask integers from CIDR block.
* npfctl_parse_port() - parse port number (which may be a service name).
* npfctl_parse_tcpfl() - parse TCP flags.
*/
struct ifaddrs *
npfctl_getif(char *ifname, unsigned int *if_idx, bool reqaddr)
{
struct ifaddrs *ifent;
struct sockaddr_in *sin;
for (ifent = ifs_list; ifent != NULL; ifent = ifent->ifa_next) {
sin = (struct sockaddr_in *)ifent->ifa_addr;
if (sin->sin_family != AF_INET && reqaddr)
continue;
if (strcmp(ifent->ifa_name, ifname) == 0)
break;
}
if (ifent) {
*if_idx = if_nametoindex(ifname);
}
return ifent;
}
bool
npfctl_parse_v4mask(char *ostr, in_addr_t *addr, in_addr_t *mask)
{
char *str = xstrdup(ostr);
char *p = strchr(str, '/');
u_int bits;
bool ret;
/* In network byte order. */
if (p) {
*p++ = '\0';
bits = (u_int)atoi(p);
*mask = bits ? htonl(0xffffffff << (32 - bits)) : 0;
} else {
*mask = 0xffffffff;
}
ret = inet_aton(str, (struct in_addr *)addr) != 0;
free(str);
return ret;
}
bool
npfctl_parse_port(char *ostr, bool *range, in_port_t *fport, in_port_t *tport)
{
char *str = xstrdup(ostr), *sep;
*range = false;
if ((sep = strchr(str, ':')) != NULL) {
/* Port range (only numeric). */
*range = true;
*sep = '\0';
} else if (isalpha((unsigned char)*str)) {
struct servent *se;
se = getservbyname(str, NULL);
if (se == NULL) {
free(str);
return false;
}
*fport = se->s_port;
} else {
*fport = htons(atoi(str));
}
*tport = sep ? htons(atoi(sep + 1)) : *fport;
free(str);
return true;
}
void
npfctl_parse_cidr(char *str, in_addr_t *addr, in_addr_t *mask)
{
if (strcmp(str, "any") == 0) {
*addr = 0x0;
*mask = 0x0;
} else if (isalpha((unsigned char)*str)) {
struct ifaddrs *ifa;
struct sockaddr_in *sin;
u_int idx;
if ((ifa = npfctl_getif(str, &idx, true)) == NULL) {
errx(EXIT_FAILURE, "invalid interface '%s'", str);
}
/* Interface address. */
sin = (struct sockaddr_in *)ifa->ifa_addr;
*addr = sin->sin_addr.s_addr;
*mask = 0xffffffff;
} else if (!npfctl_parse_v4mask(str, addr, mask)) {
errx(EXIT_FAILURE, "invalid CIDR '%s'\n", str);
}
}
static bool
npfctl_parse_tcpfl(char *s, uint8_t *tfl, uint8_t *tfl_mask)
{
uint8_t tcpfl = 0;
bool mask = false;
while (*s) {
switch (*s) {
case 'F': tcpfl |= TH_FIN; break;
case 'S': tcpfl |= TH_SYN; break;
case 'R': tcpfl |= TH_RST; break;
case 'P': tcpfl |= TH_PUSH; break;
case 'A': tcpfl |= TH_ACK; break;
case 'U': tcpfl |= TH_URG; break;
case 'E': tcpfl |= TH_ECE; break;
case 'W': tcpfl |= TH_CWR; break;
case '/':
*s = '\0';
*tfl = tcpfl;
tcpfl = 0;
mask = true;
break;
default:
return false;
}
s++;
}
if (!mask) {
*tfl = tcpfl;
}
*tfl_mask = tcpfl;
return true;
}
void
npfctl_fill_table(nl_table_t *tl, char *fname)
{
char *buf;
FILE *fp;
size_t n;
int l;
fp = fopen(fname, "r");
if (fp == NULL) {
err(EXIT_FAILURE, "open '%s'", fname);
}
l = 1;
buf = NULL;
while (getline(&buf, &n, fp) != -1) {
in_addr_t addr, mask;
if (*buf == '\n' || *buf == '#')
continue;
/* IPv4 CIDR: a.b.c.d/mask */
if (!npfctl_parse_v4mask(buf, &addr, &mask)) {
errx(EXIT_FAILURE, "invalid table entry at line %d", l);
}
/* Create and add table entry. */
npf_table_add_entry(tl, addr, mask);
l++;
}
if (buf != NULL) {
free(buf);
}
}
/*
* N-code generation helpers.
*/
static void
npfctl_rulenc_v4cidr(void **nc, int nblocks[], var_t *dat, bool sd)
{
element_t *el = dat->v_elements;
int foff;
/* If table, generate a single table matching block. */
if (dat->v_type == VAR_TABLE) {
u_int tid = atoi(el->e_data);
nblocks[0]--;
foff = npfctl_failure_offset(nblocks);
npfctl_gennc_tbl(nc, foff, tid, sd);
return;
}
/* Generate v4 CIDR matching blocks. */
for (el = dat->v_elements; el != NULL; el = el->e_next) {
in_addr_t addr, mask;
npfctl_parse_cidr(el->e_data, &addr, &mask);
nblocks[1]--;
foff = npfctl_failure_offset(nblocks);
npfctl_gennc_v4cidr(nc, foff, addr, mask, sd);
}
}
static void
npfctl_rulenc_ports(void **nc, int nblocks[], var_t *dat, bool tcpudp,
bool both, bool sd)
{
element_t *el = dat->v_elements;
int foff;
assert(dat->v_type != VAR_TABLE);
/* Generate TCP/UDP port matching blocks. */
for (el = dat->v_elements; el != NULL; el = el->e_next) {
in_port_t fport, tport;
bool range;
if (!npfctl_parse_port(el->e_data, &range, &fport, &tport)) {
errx(EXIT_FAILURE, "invalid service '%s'", el->e_data);
}
nblocks[0]--;
foff = both ? 0 : npfctl_failure_offset(nblocks);
npfctl_gennc_ports(nc, foff, fport, tport, tcpudp, sd);
}
}
static void
npfctl_rulenc_block(void **nc, int nblocks[], var_t *cidr, var_t *ports,
bool both, bool tcpudp, bool sd)
{
npfctl_rulenc_v4cidr(nc, nblocks, cidr, sd);
if (ports == NULL) {
return;
}
npfctl_rulenc_ports(nc, nblocks, ports, tcpudp, both, sd);
if (!both) {
return;
}
npfctl_rulenc_ports(nc, nblocks, ports, !tcpudp, false, sd);
}
void
npfctl_rule_ncode(nl_rule_t *rl, char *proto, char *tcpfl, int icmp_type,
int icmp_code, var_t *from, var_t *fports, var_t *to, var_t *tports)
{
int nblocks[3] = { 0, 0, 0 };
bool icmp, tcpudp, both;
void *ncptr, *nc;
size_t sz, foff;
/*
* Default: both TCP and UDP.
*/
icmp = false;
tcpudp = true;
if (proto == NULL) {
both = true;
goto skip_proto;
}
both = false;
if (strcmp(proto, "icmp") == 0) {
/* ICMP case. */
fports = NULL;
tports = NULL;
icmp = true;
} else if (strcmp(proto, "tcp") == 0) {
/* Just TCP. */
tcpudp = true;
} else if (strcmp(proto, "udp") == 0) {
/* Just UDP. */
tcpudp = false;
} else {
/* Default. */
}
skip_proto:
if (icmp || icmp_type != -1) {
assert(tcpfl == NULL);
icmp = true;
nblocks[2] += 1;
}
if (tcpudp && tcpfl) {
assert(icmp_type == -1 && icmp_code == -1);
nblocks[2] += 1;
}
/* Calculate how blocks to determince n-code. */
if (from && from->v_count) {
if (from->v_type == VAR_TABLE)
nblocks[0] += 1;
else
nblocks[1] += from->v_count;
if (fports && fports->v_count)
nblocks[0] += fports->v_count * (both ? 2 : 1);
}
if (to && to->v_count) {
if (to->v_type == VAR_TABLE)
nblocks[0] += 1;
else
nblocks[1] += to->v_count;
if (tports && tports->v_count)
nblocks[0] += tports->v_count * (both ? 2 : 1);
}
/* Any n-code to generate? */
if (!icmp && (nblocks[0] + nblocks[1] + nblocks[2]) == 0) {
/* Done, if none. */
return;
}
/* Allocate memory for the n-code. */
sz = npfctl_calc_ncsize(nblocks);
ncptr = malloc(sz);
if (ncptr == NULL) {
err(EXIT_FAILURE, "malloc");
}
nc = ncptr;
/*
* Generate v4 CIDR matching blocks and TCP/UDP port matching.
*/
if (from) {
npfctl_rulenc_block(&nc, nblocks, from, fports,
both, tcpudp, true);
}
if (to) {
npfctl_rulenc_block(&nc, nblocks, to, tports,
both, tcpudp, false);
}
if (icmp) {
/*
* ICMP case.
*/
nblocks[2]--;
foff = npfctl_failure_offset(nblocks);
npfctl_gennc_icmp(&nc, foff, icmp_type, icmp_code);
} else if (tcpudp && tcpfl) {
/*
* TCP case, flags.
*/
uint8_t tfl = 0, tfl_mask;
nblocks[2]--;
foff = npfctl_failure_offset(nblocks);
if (!npfctl_parse_tcpfl(tcpfl, &tfl, &tfl_mask)) {
errx(EXIT_FAILURE, "invalid TCP flags '%s'", tcpfl);
}
npfctl_gennc_tcpfl(&nc, foff, tfl, tfl_mask);
}
npfctl_gennc_complete(&nc);
if ((uintptr_t)nc - (uintptr_t)ncptr != sz) {
errx(EXIT_FAILURE, "n-code size got wrong (%tu != %zu)",
(uintptr_t)nc - (uintptr_t)ncptr, sz);
}
#ifdef DEBUG
uint32_t *op = ncptr;
size_t n = sz;
do {
DPRINTF(("\t> |0x%02x|\n", (u_int)*op));
op++;
n -= sizeof(*op);
} while (n);
#endif
/* Create a final memory block of data, ready to send. */
if (npf_rule_setcode(rl, NPF_CODE_NCODE, ncptr, sz) == -1) {
errx(EXIT_FAILURE, "npf_rule_setcode");
}
free(ncptr);
}