snoop_pf.c revision 041bde0a02e9359336a030297bb507ce6bda43f1
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <stddef.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/isa_defs.h>
#include <sys/socket.h>
#include <sys/vlan.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <netdb.h>
#include <rpc/rpc.h>
#include <setjmp.h>
#include <sys/pfmod.h>
#include "snoop.h"
#include "snoop_vlan.h"
/*
* This module generates code for the kernel packet filter.
* The kernel packet filter is more efficient since it
* operates without context switching or moving data into
* the capture buffer. On the other hand, it is limited
* in its filtering ability i.e. can't cope with variable
* length headers, can't compare the packet size, 1 and 4 octet
* comparisons are awkward, code space is limited to ENMAXFILTERS
* halfwords, etc.
* The parser is the same for the user-level packet filter though
* more limited in the variety of expressions it can generate
* code for. If the pf compiler finds an expression it can't
* handle, it tries to set up a split filter in kernel and do the
* remaining filtering in userland. If that also fails, it resorts
* to userland filter. (See additional comment in pf_compile)
*/
extern struct Pf_ext_packetfilt pf;
static ushort_t *pfp;
jmp_buf env;
int eaddr; /* need ethernet addr */
int opstack; /* operand stack depth */
#define EQ(val) (strcmp(token, val) == 0)
#define IPV4_ONLY 0
#define IPV6_ONLY 1
#define IPV4_AND_IPV6 2
typedef struct {
int transport_protocol;
int network_protocol;
/*
* offset is the offset in bytes from the beginning
* of the network protocol header to where the transport
* protocol type is.
*/
int offset;
} transport_table_t;
typedef struct network_table {
char *nmt_name;
int nmt_val;
} network_table_t;
static network_table_t ether_network_mapping_table[] = {
{ "pup", ETHERTYPE_PUP },
{ "ip", ETHERTYPE_IP },
{ "arp", ETHERTYPE_ARP },
{ "revarp", ETHERTYPE_REVARP },
{ "at", ETHERTYPE_AT },
{ "aarp", ETHERTYPE_AARP },
{ "vlan", ETHERTYPE_VLAN },
{ "ip6", ETHERTYPE_IPV6 },
{ "slow", ETHERTYPE_SLOW },
{ "ppoed", ETHERTYPE_PPPOED },
{ "ppoes", ETHERTYPE_PPPOES },
{ "NULL", -1 }
};
static network_table_t ib_network_mapping_table[] = {
{ "pup", ETHERTYPE_PUP },
{ "ip", ETHERTYPE_IP },
{ "arp", ETHERTYPE_ARP },
{ "revarp", ETHERTYPE_REVARP },
{ "at", ETHERTYPE_AT },
{ "aarp", ETHERTYPE_AARP },
{ "vlan", ETHERTYPE_VLAN },
{ "ip6", ETHERTYPE_IPV6 },
{ "slow", ETHERTYPE_SLOW },
{ "ppoed", ETHERTYPE_PPPOED },
{ "ppoes", ETHERTYPE_PPPOES },
{ "NULL", -1 }
};
static network_table_t ipnet_network_mapping_table[] = {
{ "ip", (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION) },
{ "ip6", (DL_IPNETINFO_VERSION << 8 | IPV6_VERSION) },
{ "NULL", -1 }
};
static transport_table_t ether_transport_mapping_table[] = {
{IPPROTO_TCP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_TCP, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_UDP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_UDP, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_OSPF, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_OSPF, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_SCTP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_SCTP, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_ICMP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ICMPV6, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_ENCAP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ESP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ESP, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_AH, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_AH, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{-1, 0, 0} /* must be the final entry */
};
static transport_table_t ipnet_transport_mapping_table[] = {
{IPPROTO_TCP, (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION),
IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_TCP, (DL_IPNETINFO_VERSION << 8 | IPV6_VERSION),
IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_UDP, (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION),
IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_UDP, (DL_IPNETINFO_VERSION << 8 | IPV6_VERSION),
IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_OSPF, (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION),
IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_OSPF, (DL_IPNETINFO_VERSION << 8 | IPV6_VERSION),
IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_SCTP, (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION),
IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_SCTP, (DL_IPNETINFO_VERSION << 8 | IPV6_VERSION),
IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_ICMP, (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION),
IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ICMPV6, (DL_IPNETINFO_VERSION << 8 | IPV6_VERSION),
IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_ENCAP, (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION),
IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ESP, (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION),
IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ESP, (DL_IPNETINFO_VERSION << 8 | IPV6_VERSION),
IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_AH, (DL_IPNETINFO_VERSION << 8 | IPV4_VERSION),
IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_AH, (DL_IPNETINFO_VERSION << 8 | IPV6_VERSION),
IPV6_TYPE_HEADER_OFFSET},
{-1, 0, 0} /* must be the final entry */
};
static transport_table_t ib_transport_mapping_table[] = {
{IPPROTO_TCP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_TCP, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_UDP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_UDP, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_OSPF, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_OSPF, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_SCTP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_SCTP, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_ICMP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ICMPV6, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_ENCAP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ESP, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_ESP, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{IPPROTO_AH, ETHERTYPE_IP, IPV4_TYPE_HEADER_OFFSET},
{IPPROTO_AH, ETHERTYPE_IPV6, IPV6_TYPE_HEADER_OFFSET},
{-1, 0, 0} /* must be the final entry */
};
typedef struct datalink {
uint_t dl_type;
void (*dl_match_fn)(uint_t datatype);
transport_table_t *dl_trans_map_tbl;
network_table_t *dl_net_map_tbl;
int dl_link_header_len;
int dl_link_type_offset;
int dl_link_dest_offset;
int dl_link_src_offset;
int dl_link_addr_len;
} datalink_t;
datalink_t dl;
#define IPV4_SRCADDR_OFFSET (dl.dl_link_header_len + 12)
#define IPV4_DSTADDR_OFFSET (dl.dl_link_header_len + 16)
#define IPV6_SRCADDR_OFFSET (dl.dl_link_header_len + 8)
#define IPV6_DSTADDR_OFFSET (dl.dl_link_header_len + 24)
#define IPNET_SRCZONE_OFFSET 8
#define IPNET_DSTZONE_OFFSET 16
static int inBrace = 0, inBraceOR = 0;
static int foundOR = 0;
char *tkp, *sav_tkp;
char *token;
enum { EOL, ALPHA, NUMBER, FIELD, ADDR_IP, ADDR_ETHER, SPECIAL,
ADDR_IP6 } tokentype;
uint_t tokenval;
enum direction { ANY, TO, FROM };
enum direction dir;
extern void next();
static void pf_expression();
static void pf_check_vlan_tag(uint_t offset);
static void pf_clear_offset_register();
static void pf_emit_load_offset(uint_t offset);
static void pf_match_ethertype(uint_t ethertype);
static void pf_match_ipnettype(uint_t type);
static void pf_match_ibtype(uint_t type);
static void pf_check_transport_protocol(uint_t transport_protocol);
static void pf_compare_value_mask_generic(int offset, uint_t len,
uint_t val, int mask, uint_t op);
static void pf_matchfn(const char *name);
/*
* This pointer points to the function that last generated
* instructions to change the offset register. It's used
* for comparisons to see if we need to issue more instructions
* to change the register.
*
* It's initialized to pf_clear_offset_register because the offset
* register in pfmod is initialized to zero, similar to the state
* it would be in after executing the instructions issued by
* pf_clear_offset_register.
*/
static void *last_offset_operation = (void*)pf_clear_offset_register;
static void
pf_emit(x)
ushort_t x;
{
if (pfp > &pf.Pf_Filter[PF_MAXFILTERS - 1])
longjmp(env, 1);
*pfp++ = x;
}
static void
pf_codeprint(code, len)
ushort_t *code;
int len;
{
ushort_t *pc;
ushort_t *plast = code + len;
int op, action;
if (len > 0) {
printf("Kernel Filter:\n");
}
for (pc = code; pc < plast; pc++) {
printf("\t%3d: ", pc - code);
op = *pc & 0xfc00; /* high 10 bits */
action = *pc & 0x3ff; /* low 6 bits */
switch (action) {
case ENF_PUSHLIT:
printf("PUSHLIT ");
break;
case ENF_PUSHZERO:
printf("PUSHZERO ");
break;
#ifdef ENF_PUSHONE
case ENF_PUSHONE:
printf("PUSHONE ");
break;
#endif
#ifdef ENF_PUSHFFFF
case ENF_PUSHFFFF:
printf("PUSHFFFF ");
break;
#endif
#ifdef ENF_PUSHFF00
case ENF_PUSHFF00:
printf("PUSHFF00 ");
break;
#endif
#ifdef ENF_PUSH00FF
case ENF_PUSH00FF:
printf("PUSH00FF ");
break;
#endif
case ENF_LOAD_OFFSET:
printf("LOAD_OFFSET ");
break;
case ENF_BRTR:
printf("BRTR ");
break;
case ENF_BRFL:
printf("BRFL ");
break;
case ENF_POP:
printf("POP ");
break;
}
if (action >= ENF_PUSHWORD)
printf("PUSHWORD %d ", action - ENF_PUSHWORD);
switch (op) {
case ENF_EQ:
printf("EQ ");
break;
case ENF_LT:
printf("LT ");
break;
case ENF_LE:
printf("LE ");
break;
case ENF_GT:
printf("GT ");
break;
case ENF_GE:
printf("GE ");
break;
case ENF_AND:
printf("AND ");
break;
case ENF_OR:
printf("OR ");
break;
case ENF_XOR:
printf("XOR ");
break;
case ENF_COR:
printf("COR ");
break;
case ENF_CAND:
printf("CAND ");
break;
case ENF_CNOR:
printf("CNOR ");
break;
case ENF_CNAND:
printf("CNAND ");
break;
case ENF_NEQ:
printf("NEQ ");
break;
}
if (action == ENF_PUSHLIT ||
action == ENF_LOAD_OFFSET ||
action == ENF_BRTR ||
action == ENF_BRFL) {
pc++;
printf("\n\t%3d: %d (0x%04x)", pc - code, *pc, *pc);
}
printf("\n");
}
}
/*
* Emit packet filter code to check a
* field in the packet for a particular value.
* Need different code for each field size.
* Since the pf can only compare 16 bit quantities
* we have to use masking to compare byte values.
* Long word (32 bit) quantities have to be done
* as two 16 bit comparisons.
*/
static void
pf_compare_value(int offset, uint_t len, uint_t val)
{
/*
* If the property being filtered on is absent in the media
* packet, error out.
*/
if (offset == -1)
pr_err("filter option unsupported on media");
switch (len) {
case 1:
pf_emit(ENF_PUSHWORD + offset / 2);
#if defined(_BIG_ENDIAN)
if (offset % 2)
#else
if (!(offset % 2))
#endif
{
#ifdef ENF_PUSH00FF
pf_emit(ENF_PUSH00FF | ENF_AND);
#else
pf_emit(ENF_PUSHLIT | ENF_AND);
pf_emit(0x00FF);
#endif
pf_emit(ENF_PUSHLIT | ENF_EQ);
pf_emit(val);
} else {
#ifdef ENF_PUSHFF00
pf_emit(ENF_PUSHFF00 | ENF_AND);
#else
pf_emit(ENF_PUSHLIT | ENF_AND);
pf_emit(0xFF00);
#endif
pf_emit(ENF_PUSHLIT | ENF_EQ);
pf_emit(val << 8);
}
break;
case 2:
pf_emit(ENF_PUSHWORD + offset / 2);
pf_emit(ENF_PUSHLIT | ENF_EQ);
pf_emit((ushort_t)val);
break;
case 4:
pf_emit(ENF_PUSHWORD + offset / 2);
pf_emit(ENF_PUSHLIT | ENF_EQ);
#if defined(_BIG_ENDIAN)
pf_emit(val >> 16);
#elif defined(_LITTLE_ENDIAN)
pf_emit(val & 0xffff);
#else
#error One of _BIG_ENDIAN and _LITTLE_ENDIAN must be defined
#endif
pf_emit(ENF_PUSHWORD + (offset / 2) + 1);
pf_emit(ENF_PUSHLIT | ENF_EQ);
#if defined(_BIG_ENDIAN)
pf_emit(val & 0xffff);
#else
pf_emit(val >> 16);
#endif
pf_emit(ENF_AND);
break;
}
}
/*
* same as pf_compare_value, but only for emiting code to
* compare ipv6 addresses.
*/
static void
pf_compare_value_v6(int offset, uint_t len, struct in6_addr val)
{
int i;
for (i = 0; i < len; i += 2) {
pf_emit(ENF_PUSHWORD + offset / 2 + i / 2);
pf_emit(ENF_PUSHLIT | ENF_EQ);
pf_emit(*(uint16_t *)&val.s6_addr[i]);
if (i != 0)
pf_emit(ENF_AND);
}
}
/*
* Same as above except mask the field value
* before doing the comparison. The comparison checks
* to make sure the values are equal.
*/
static void
pf_compare_value_mask(int offset, uint_t len, uint_t val, int mask)
{
pf_compare_value_mask_generic(offset, len, val, mask, ENF_EQ);
}
/*
* Same as above except the values are compared to see if they are not
* equal.
*/
static void
pf_compare_value_mask_neq(int offset, uint_t len, uint_t val, int mask)
{
pf_compare_value_mask_generic(offset, len, val, mask, ENF_NEQ);
}
/*
* Similar to pf_compare_value.
*
* This is the utility function that does the actual work to compare
* two values using a mask. The comparison operation is passed into
* the function.
*/
static void
pf_compare_value_mask_generic(int offset, uint_t len, uint_t val, int mask,
uint_t op)
{
/*
* If the property being filtered on is absent in the media
* packet, error out.
*/
if (offset == -1)
pr_err("filter option unsupported on media");
switch (len) {
case 1:
pf_emit(ENF_PUSHWORD + offset / 2);
#if defined(_BIG_ENDIAN)
if (offset % 2)
#else
if (!offset % 2)
#endif
{
pf_emit(ENF_PUSHLIT | ENF_AND);
pf_emit(mask & 0x00ff);
pf_emit(ENF_PUSHLIT | op);
pf_emit(val);
} else {
pf_emit(ENF_PUSHLIT | ENF_AND);
pf_emit((mask << 8) & 0xff00);
pf_emit(ENF_PUSHLIT | op);
pf_emit(val << 8);
}
break;
case 2:
pf_emit(ENF_PUSHWORD + offset / 2);
pf_emit(ENF_PUSHLIT | ENF_AND);
pf_emit(htons((ushort_t)mask));
pf_emit(ENF_PUSHLIT | op);
pf_emit(htons((ushort_t)val));
break;
case 4:
pf_emit(ENF_PUSHWORD + offset / 2);
pf_emit(ENF_PUSHLIT | ENF_AND);
pf_emit(htons((ushort_t)((mask >> 16) & 0xffff)));
pf_emit(ENF_PUSHLIT | op);
pf_emit(htons((ushort_t)((val >> 16) & 0xffff)));
pf_emit(ENF_PUSHWORD + (offset / 2) + 1);
pf_emit(ENF_PUSHLIT | ENF_AND);
pf_emit(htons((ushort_t)(mask & 0xffff)));
pf_emit(ENF_PUSHLIT | op);
pf_emit(htons((ushort_t)(val & 0xffff)));
pf_emit(ENF_AND);
break;
}
}
/*
* Like pf_compare_value() but compare on a 64-bit zoneid value.
* The argument val passed in is in network byte order.
*/
static void
pf_compare_zoneid(int offset, uint64_t val)
{
int i;
for (i = 0; i < sizeof (uint64_t) / 2; i ++) {
pf_emit(ENF_PUSHWORD + offset / 2 + i);
pf_emit(ENF_PUSHLIT | ENF_EQ);
pf_emit(((uint16_t *)&val)[i]);
if (i != 0)
pf_emit(ENF_AND);
}
}
/*
* Generate pf code to match an IPv4 or IPv6 address.
*/
static void
pf_ipaddr_match(which, hostname, inet_type)
enum direction which;
char *hostname;
int inet_type;
{
bool_t found_host;
uint_t *addr4ptr;
uint_t addr4;
struct in6_addr *addr6ptr;
int h_addr_index;
struct hostent *hp = NULL;
int error_num = 0;
boolean_t first = B_TRUE;
int pass = 0;
int i;
/*
* The addr4offset and addr6offset variables simplify the code which
* generates the address comparison filter. With these two variables,
* duplicate code need not exist for the TO and FROM case.
* A value of -1 describes the ANY case (TO and FROM).
*/
int addr4offset;
int addr6offset;
found_host = 0;
if (tokentype == ADDR_IP) {
hp = getipnodebyname(hostname, AF_INET, 0, &error_num);
if (hp == NULL) {
if (error_num == TRY_AGAIN) {
pr_err("could not resolve %s (try again later)",
hostname);
} else {
pr_err("could not resolve %s", hostname);
}
}
inet_type = IPV4_ONLY;
} else if (tokentype == ADDR_IP6) {
hp = getipnodebyname(hostname, AF_INET6, 0, &error_num);
if (hp == NULL) {
if (error_num == TRY_AGAIN) {
pr_err("could not resolve %s (try again later)",
hostname);
} else {
pr_err("could not resolve %s", hostname);
}
}
inet_type = IPV6_ONLY;
} else if (tokentype == ALPHA) {
/* Some hostname i.e. tokentype is ALPHA */
switch (inet_type) {
case IPV4_ONLY:
/* Only IPv4 address is needed */
hp = getipnodebyname(hostname, AF_INET, 0, &error_num);
if (hp != NULL) {
found_host = 1;
}
break;
case IPV6_ONLY:
/* Only IPv6 address is needed */
hp = getipnodebyname(hostname, AF_INET6, 0, &error_num);
if (hp != NULL) {
found_host = 1;
}
break;
case IPV4_AND_IPV6:
/* Both IPv4 and IPv6 are needed */
hp = getipnodebyname(hostname, AF_INET6,
AI_ALL | AI_V4MAPPED, &error_num);
if (hp != NULL) {
found_host = 1;
}
break;
default:
found_host = 0;
}
if (!found_host) {
if (error_num == TRY_AGAIN) {
pr_err("could not resolve %s (try again later)",
hostname);
} else {
pr_err("could not resolve %s", hostname);
}
}
} else {
pr_err("unknown token type: %s", hostname);
}
switch (which) {
case TO:
addr4offset = IPV4_DSTADDR_OFFSET;
addr6offset = IPV6_DSTADDR_OFFSET;
break;
case FROM:
addr4offset = IPV4_SRCADDR_OFFSET;
addr6offset = IPV6_SRCADDR_OFFSET;
break;
case ANY:
addr4offset = -1;
addr6offset = -1;
break;
}
if (hp != NULL && hp->h_addrtype == AF_INET) {
pf_matchfn("ip");
if (dl.dl_type == DL_ETHER)
pf_check_vlan_tag(ENCAP_ETHERTYPE_OFF/2);
h_addr_index = 0;
addr4ptr = (uint_t *)hp->h_addr_list[h_addr_index];
while (addr4ptr != NULL) {
if (addr4offset == -1) {
pf_compare_value(IPV4_SRCADDR_OFFSET, 4,
*addr4ptr);
if (h_addr_index != 0)
pf_emit(ENF_OR);
pf_compare_value(IPV4_DSTADDR_OFFSET, 4,
*addr4ptr);
pf_emit(ENF_OR);
} else {
pf_compare_value(addr4offset, 4,
*addr4ptr);
if (h_addr_index != 0)
pf_emit(ENF_OR);
}
addr4ptr = (uint_t *)hp->h_addr_list[++h_addr_index];
}
pf_emit(ENF_AND);
} else {
/* first pass: IPv4 addresses */
h_addr_index = 0;
addr6ptr = (struct in6_addr *)hp->h_addr_list[h_addr_index];
first = B_TRUE;
while (addr6ptr != NULL) {
if (IN6_IS_ADDR_V4MAPPED(addr6ptr)) {
if (first) {
pf_matchfn("ip");
if (dl.dl_type == DL_ETHER) {
pf_check_vlan_tag(
ENCAP_ETHERTYPE_OFF/2);
}
pass++;
}
IN6_V4MAPPED_TO_INADDR(addr6ptr,
(struct in_addr *)&addr4);
if (addr4offset == -1) {
pf_compare_value(IPV4_SRCADDR_OFFSET, 4,
addr4);
if (!first)
pf_emit(ENF_OR);
pf_compare_value(IPV4_DSTADDR_OFFSET, 4,
addr4);
pf_emit(ENF_OR);
} else {
pf_compare_value(addr4offset, 4,
addr4);
if (!first)
pf_emit(ENF_OR);
}
if (first)
first = B_FALSE;
}
addr6ptr = (struct in6_addr *)
hp->h_addr_list[++h_addr_index];
}
if (!first) {
pf_emit(ENF_AND);
}
/* second pass: IPv6 addresses */
h_addr_index = 0;
addr6ptr = (struct in6_addr *)hp->h_addr_list[h_addr_index];
first = B_TRUE;
while (addr6ptr != NULL) {
if (!IN6_IS_ADDR_V4MAPPED(addr6ptr)) {
if (first) {
pf_matchfn("ip6");
if (dl.dl_type == DL_ETHER) {
pf_check_vlan_tag(
ENCAP_ETHERTYPE_OFF/2);
}
pass++;
}
if (addr6offset == -1) {
pf_compare_value_v6(IPV6_SRCADDR_OFFSET,
16, *addr6ptr);
if (!first)
pf_emit(ENF_OR);
pf_compare_value_v6(IPV6_DSTADDR_OFFSET,
16, *addr6ptr);
pf_emit(ENF_OR);
} else {
pf_compare_value_v6(addr6offset, 16,
*addr6ptr);
if (!first)
pf_emit(ENF_OR);
}
if (first)
first = B_FALSE;
}
addr6ptr = (struct in6_addr *)
hp->h_addr_list[++h_addr_index];
}
if (!first) {
pf_emit(ENF_AND);
}
if (pass == 2) {
pf_emit(ENF_OR);
}
}
if (hp != NULL) {
freehostent(hp);
}
}
static void
pf_compare_address(int offset, uint_t len, uchar_t *addr)
{
uint32_t val;
uint16_t sval;
boolean_t didone = B_FALSE;
/*
* If the property being filtered on is absent in the media
* packet, error out.
*/
if (offset == -1)
pr_err("filter option unsupported on media");
while (len > 0) {
if (len >= 4) {
(void) memcpy(&val, addr, 4);
pf_compare_value(offset, 4, val);
addr += 4;
offset += 4;
len -= 4;
} else if (len >= 2) {
(void) memcpy(&sval, addr, 2);
pf_compare_value(offset, 2, sval);
addr += 2;
offset += 2;
len -= 2;
} else {
pf_compare_value(offset++, 1, *addr++);
len--;
}
if (didone)
pf_emit(ENF_AND);
didone = B_TRUE;
}
}
/*
* Compare ethernet addresses.
*/
static void
pf_etheraddr_match(which, hostname)
enum direction which;
char *hostname;
{
struct ether_addr e, *ep = NULL;
if (isxdigit(*hostname))
ep = ether_aton(hostname);
if (ep == NULL) {
if (ether_hostton(hostname, &e))
if (!arp_for_ether(hostname, &e))
pr_err("cannot obtain ether addr for %s",
hostname);
ep = &e;
}
pf_clear_offset_register();
switch (which) {
case TO:
pf_compare_address(dl.dl_link_dest_offset, dl.dl_link_addr_len,
(uchar_t *)ep);
break;
case FROM:
pf_compare_address(dl.dl_link_src_offset, dl.dl_link_addr_len,
(uchar_t *)ep);
break;
case ANY:
pf_compare_address(dl.dl_link_dest_offset, dl.dl_link_addr_len,
(uchar_t *)ep);
pf_compare_address(dl.dl_link_src_offset, dl.dl_link_addr_len,
(uchar_t *)ep);
pf_emit(ENF_OR);
break;
}
}
/*
* Emit code to compare the network part of
* an IP address.
*/
static void
pf_netaddr_match(which, netname)
enum direction which;
char *netname;
{
uint_t addr;
uint_t mask = 0xff000000;
struct netent *np;
if (isdigit(*netname)) {
addr = inet_network(netname);
} else {
np = getnetbyname(netname);
if (np == NULL)
pr_err("net %s not known", netname);
addr = np->n_net;
}
/*
* Left justify the address and figure
* out a mask based on the supplied address.
* Set the mask according to the number of zero
* low-order bytes.
* Note: this works only for whole octet masks.
*/
if (addr) {
while ((addr & ~mask) != 0) {
mask |= (mask >> 8);
}
}
pf_check_vlan_tag(ENCAP_ETHERTYPE_OFF/2);
switch (which) {
case TO:
pf_compare_value_mask(IPV4_DSTADDR_OFFSET, 4, addr, mask);
break;
case FROM:
pf_compare_value_mask(IPV4_SRCADDR_OFFSET, 4, addr, mask);
break;
case ANY:
pf_compare_value_mask(IPV4_SRCADDR_OFFSET, 4, addr, mask);
pf_compare_value_mask(IPV4_DSTADDR_OFFSET, 4, addr, mask);
pf_emit(ENF_OR);
break;
}
}
/*
* Emit code to match on src or destination zoneid.
* The zoneid passed in is in network byte order.
*/
static void
pf_match_zone(enum direction which, uint64_t zoneid)
{
if (dl.dl_type != DL_IPNET)
pr_err("zone filter option unsupported on media");
switch (which) {
case TO:
pf_compare_zoneid(IPNET_DSTZONE_OFFSET, zoneid);
break;
case FROM:
pf_compare_zoneid(IPNET_SRCZONE_OFFSET, zoneid);
break;
case ANY:
pf_compare_zoneid(IPNET_SRCZONE_OFFSET, zoneid);
pf_compare_zoneid(IPNET_DSTZONE_OFFSET, zoneid);
pf_emit(ENF_OR);
break;
}
}
/*
* A helper function to keep the code to emit instructions
* to change the offset register in one place.
*
* INPUTS: offset - An value representing an offset in 16-bit
* words.
* OUTPUTS: If there is enough room in the storage for the
* packet filtering program, instructions to load
* a constant to the offset register. Otherwise,
* nothing.
*/
static void
pf_emit_load_offset(uint_t offset)
{
pf_emit(ENF_LOAD_OFFSET | ENF_NOP);
pf_emit(offset);
}
/*
* Clear pfmod's offset register.
*
* INPUTS: none
* OUTPUTS: Instructions to clear the offset register if
* there is enough space remaining in the packet
* filtering program structure's storage, and
* the last thing done to the offset register was
* not clearing the offset register. Otherwise,
* nothing.
*/
static void
pf_clear_offset_register()
{
if (last_offset_operation != (void*)pf_clear_offset_register) {
pf_emit_load_offset(0);
last_offset_operation = (void*)pf_clear_offset_register;
}
}
/*
* This function will issue opcodes to check if a packet
* is VLAN tagged, and if so, update the offset register
* with the appropriate offset.
*
* Note that if the packet is not VLAN tagged, then the offset
* register will be cleared.
*
* If the interface type is not an ethernet type, then this
* function returns without doing anything.
*
* If the last attempt to change the offset register occured because
* of a call to this function that was called with the same offset,
* then we don't issue packet filtering instructions.
*
* INPUTS: offset - an offset in 16 bit words. The function
* will set the offset register to this
* value if the packet is VLAN tagged.
* OUTPUTS: If the conditions are met, packet filtering instructions.
*/
static void
pf_check_vlan_tag(uint_t offset)
{
static uint_t last_offset = 0;
if ((interface->mac_type == DL_ETHER ||
interface->mac_type == DL_CSMACD) &&
(last_offset_operation != (void*)pf_check_vlan_tag ||
last_offset != offset)) {
/*
* First thing is to clear the offset register.
* We don't know what state it is in, and if it
* is not zero, then we have no idea what we load
* when we execute ENF_PUSHWORD.
*/
pf_clear_offset_register();
/*
* Check the ethertype.
*/
pf_compare_value(dl.dl_link_type_offset, 2,
htons(ETHERTYPE_VLAN));
/*
* And if it's not VLAN, don't load offset to the offset
* register.
*/
pf_emit(ENF_BRFL | ENF_NOP);
pf_emit(3);
/*
* Otherwise, load offset to the offset register.
*/
pf_emit_load_offset(offset);
/*
* Now get rid of the results of the comparison,
* we don't want the results of the comparison to affect
* other logic in the packet filtering program.
*/
pf_emit(ENF_POP | ENF_NOP);
/*
* Set the last operation at the end, or any time
* after the call to pf_clear_offset because
* pf_clear_offset uses it.
*/
last_offset_operation = (void*)pf_check_vlan_tag;
last_offset = offset;
}
}
/*
* Utility function used to emit packet filtering code
* to match an ethertype.
*
* INPUTS: ethertype - The ethertype we want to check for.
* Don't call htons on the ethertype before
* calling this function.
* OUTPUTS: If there is sufficient storage available, packet
* filtering code to check an ethertype. Otherwise,
* nothing.
*/
static void
pf_match_ethertype(uint_t ethertype)
{
/*
* If the user wants to filter on ethertype VLAN,
* then clear the offset register so that the offset
* for ENF_PUSHWORD points to the right place in the
* packet.
*
* Otherwise, call pf_check_vlan_tag to set the offset
* register such that the contents of the offset register
* plus the argument for ENF_PUSHWORD point to the right
* part of the packet, whether or not the packet is VLAN
* tagged. We call pf_check_vlan_tag with an offset of
* two words because if the packet is VLAN tagged, we have
* to move past the ethertype in the ethernet header, and
* past the lower two octets of the VLAN header to get to
* the ethertype in the VLAN header.
*/
if (ethertype == ETHERTYPE_VLAN)
pf_clear_offset_register();
else
pf_check_vlan_tag(2);
pf_compare_value(dl.dl_link_type_offset, 2, htons(ethertype));
}
static void
pf_match_ipnettype(uint_t type)
{
pf_compare_value(dl.dl_link_type_offset, 2, htons(type));
}
static void
pf_match_ibtype(uint_t type)
{
pf_compare_value(dl.dl_link_type_offset, 2, htons(type));
}
/*
* This function uses the table above to generate a
* piece of a packet filtering program to check a transport
* protocol type.
*
* INPUTS: tranport_protocol - the transport protocol we're
* interested in.
* OUTPUTS: If there is sufficient storage, then packet filtering
* code to check a transport protocol type. Otherwise,
* nothing.
*/
static void
pf_check_transport_protocol(uint_t transport_protocol)
{
int i;
uint_t number_of_matches = 0;
for (i = 0; dl.dl_trans_map_tbl[i].transport_protocol != -1; i++) {
if (transport_protocol ==
(uint_t)dl.dl_trans_map_tbl[i].transport_protocol) {
number_of_matches++;
dl.dl_match_fn(dl.dl_trans_map_tbl[i].network_protocol);
pf_check_vlan_tag(ENCAP_ETHERTYPE_OFF/2);
pf_compare_value(dl.dl_trans_map_tbl[i].offset +
dl.dl_link_header_len, 1,
transport_protocol);
pf_emit(ENF_AND);
if (number_of_matches > 1) {
/*
* Since we have two or more matches, in
* order to have a correct and complete
* program we need to OR the result of
* each block of comparisons together.
*/
pf_emit(ENF_OR);
}
}
}
}
static void
pf_matchfn(const char *proto)
{
int i;
for (i = 0; dl.dl_net_map_tbl[i].nmt_val != -1; i++) {
if (strcmp(proto, dl.dl_net_map_tbl[i].nmt_name) == 0) {
dl.dl_match_fn(dl.dl_net_map_tbl[i].nmt_val);
break;
}
}
}
static void
pf_primary()
{
for (;;) {
if (tokentype == FIELD)
break;
if (EQ("ip")) {
pf_matchfn("ip");
opstack++;
next();
break;
}
if (EQ("ip6")) {
pf_matchfn("ip6");
opstack++;
next();
break;
}
if (EQ("pppoe")) {
pf_matchfn("pppoe");
pf_match_ethertype(ETHERTYPE_PPPOES);
pf_emit(ENF_OR);
opstack++;
next();
break;
}
if (EQ("pppoed")) {
pf_matchfn("pppoed");
opstack++;
next();
break;
}
if (EQ("pppoes")) {
pf_matchfn("pppoes");
opstack++;
next();
break;
}
if (EQ("arp")) {
pf_matchfn("arp");
opstack++;
next();
break;
}
if (EQ("vlan")) {
pf_matchfn("vlan");
pf_compare_value_mask_neq(VLAN_ID_OFFSET, 2,
0, VLAN_ID_MASK);
pf_emit(ENF_AND);
opstack++;
next();
break;
}
if (EQ("vlan-id")) {
next();
if (tokentype != NUMBER)
pr_err("VLAN ID expected");
pf_matchfn("vlan-id");
pf_compare_value_mask(VLAN_ID_OFFSET, 2, tokenval,
VLAN_ID_MASK);
pf_emit(ENF_AND);
opstack++;
next();
break;
}
if (EQ("rarp")) {
pf_matchfn("rarp");
opstack++;
next();
break;
}
if (EQ("tcp")) {
pf_check_transport_protocol(IPPROTO_TCP);
opstack++;
next();
break;
}
if (EQ("udp")) {
pf_check_transport_protocol(IPPROTO_UDP);
opstack++;
next();
break;
}
if (EQ("ospf")) {
pf_check_transport_protocol(IPPROTO_OSPF);
opstack++;
next();
break;
}
if (EQ("sctp")) {
pf_check_transport_protocol(IPPROTO_SCTP);
opstack++;
next();
break;
}
if (EQ("icmp")) {
pf_check_transport_protocol(IPPROTO_ICMP);
opstack++;
next();
break;
}
if (EQ("icmp6")) {
pf_check_transport_protocol(IPPROTO_ICMPV6);
opstack++;
next();
break;
}
if (EQ("ip-in-ip")) {
pf_check_transport_protocol(IPPROTO_ENCAP);
opstack++;
next();
break;
}
if (EQ("esp")) {
pf_check_transport_protocol(IPPROTO_ESP);
opstack++;
next();
break;
}
if (EQ("ah")) {
pf_check_transport_protocol(IPPROTO_AH);
opstack++;
next();
break;
}
if (EQ("(")) {
inBrace++;
next();
pf_expression();
if (EQ(")")) {
if (inBrace)
inBraceOR--;
inBrace--;
next();
}
break;
}
if (EQ("to") || EQ("dst")) {
dir = TO;
next();
continue;
}
if (EQ("from") || EQ("src")) {
dir = FROM;
next();
continue;
}
if (EQ("ether")) {
eaddr = 1;
next();
continue;
}
if (EQ("inet")) {
next();
if (EQ("host"))
next();
if (tokentype != ALPHA && tokentype != ADDR_IP)
pr_err("host/IPv4 addr expected after inet");
pf_ipaddr_match(dir, token, IPV4_ONLY);
opstack++;
next();
break;
}
if (EQ("inet6")) {
next();
if (EQ("host"))
next();
if (tokentype != ALPHA && tokentype != ADDR_IP6)
pr_err("host/IPv6 addr expected after inet6");
pf_ipaddr_match(dir, token, IPV6_ONLY);
opstack++;
next();
break;
}
if (EQ("proto")) {
next();
if (tokentype != NUMBER)
pr_err("IP proto type expected");
pf_check_vlan_tag(ENCAP_ETHERTYPE_OFF/2);
pf_compare_value(
IPV4_TYPE_HEADER_OFFSET + dl.dl_link_header_len, 1,
tokenval);
opstack++;
next();
break;
}
if (EQ("broadcast")) {
pf_clear_offset_register();
pf_compare_value(dl.dl_link_dest_offset, 4, 0xffffffff);
opstack++;
next();
break;
}
if (EQ("multicast")) {
pf_clear_offset_register();
pf_compare_value_mask(
dl.dl_link_dest_offset, 1, 0x01, 0x01);
opstack++;
next();
break;
}
if (EQ("ethertype")) {
next();
if (tokentype != NUMBER)
pr_err("ether type expected");
pf_match_ethertype(tokenval);
opstack++;
next();
break;
}
if (EQ("net") || EQ("dstnet") || EQ("srcnet")) {
if (EQ("dstnet"))
dir = TO;
else if (EQ("srcnet"))
dir = FROM;
next();
pf_netaddr_match(dir, token);
dir = ANY;
opstack++;
next();
break;
}
if (EQ("zone")) {
next();
if (tokentype != NUMBER)
pr_err("zoneid expected after inet");
pf_match_zone(dir, BE_64((uint64_t)(tokenval)));
opstack++;
next();
break;
}
/*
* Give up on anything that's obviously
* not a primary.
*/
if (EQ("and") || EQ("or") ||
EQ("not") || EQ("decnet") || EQ("apple") ||
EQ("length") || EQ("less") || EQ("greater") ||
EQ("port") || EQ("srcport") || EQ("dstport") ||
EQ("rpc") || EQ("gateway") || EQ("nofrag") ||
EQ("bootp") || EQ("dhcp") || EQ("dhcp6") ||
EQ("slp") || EQ("ldap")) {
break;
}
if (EQ("host") || EQ("between") ||
tokentype == ALPHA || /* assume its a hostname */
tokentype == ADDR_IP ||
tokentype == ADDR_IP6 ||
tokentype == ADDR_ETHER) {
if (EQ("host") || EQ("between"))
next();
if (eaddr || tokentype == ADDR_ETHER) {
pf_etheraddr_match(dir, token);
} else if (tokentype == ALPHA) {
pf_ipaddr_match(dir, token, IPV4_AND_IPV6);
} else if (tokentype == ADDR_IP) {
pf_ipaddr_match(dir, token, IPV4_ONLY);
} else {
pf_ipaddr_match(dir, token, IPV6_ONLY);
}
dir = ANY;
eaddr = 0;
opstack++;
next();
break;
}
break; /* unknown token */
}
}
static void
pf_alternation()
{
int s = opstack;
pf_primary();
for (;;) {
if (EQ("and"))
next();
pf_primary();
if (opstack != s + 2)
break;
pf_emit(ENF_AND);
opstack--;
}
}
static void
pf_expression()
{
pf_alternation();
while (EQ("or") || EQ(",")) {
if (inBrace)
inBraceOR++;
else
foundOR++;
next();
pf_alternation();
pf_emit(ENF_OR);
opstack--;
}
}
/*
* Attempt to compile the expression
* in the string "e". If we can generate
* pf code for it then return 1 - otherwise
* return 0 and leave it up to the user-level
* filter.
*/
int
pf_compile(e, print)
char *e;
int print;
{
char *argstr;
char *sav_str, *ptr, *sav_ptr;
int inBr = 0, aheadOR = 0;
argstr = strdup(e);
sav_str = e;
tkp = argstr;
dir = ANY;
pfp = &pf.Pf_Filter[0];
if (setjmp(env)) {
return (0);
}
/*
* Set media specific packet offsets that this code uses.
*/
if (interface->mac_type == DL_ETHER) {
dl.dl_type = DL_ETHER;
dl.dl_match_fn = pf_match_ethertype;
dl.dl_trans_map_tbl = ether_transport_mapping_table;
dl.dl_net_map_tbl = ether_network_mapping_table;
dl.dl_link_header_len = 14;
dl.dl_link_type_offset = 12;
dl.dl_link_dest_offset = 0;
dl.dl_link_src_offset = 6;
dl.dl_link_addr_len = 6;
}
if (interface->mac_type == DL_IB) {
dl.dl_type = DL_IB;
dl.dl_link_header_len = 4;
dl.dl_link_type_offset = 0;
dl.dl_link_dest_offset = dl.dl_link_src_offset = -1;
dl.dl_link_addr_len = 20;
dl.dl_match_fn = pf_match_ibtype;
dl.dl_trans_map_tbl = ib_transport_mapping_table;
dl.dl_net_map_tbl = ib_network_mapping_table;
}
if (interface->mac_type == DL_IPNET) {
dl.dl_type = DL_IPNET;
dl.dl_link_header_len = 24;
dl.dl_link_type_offset = 0;
dl.dl_link_dest_offset = dl.dl_link_src_offset = -1;
dl.dl_link_addr_len = -1;
dl.dl_match_fn = pf_match_ipnettype;
dl.dl_trans_map_tbl = ipnet_transport_mapping_table;
dl.dl_net_map_tbl = ipnet_network_mapping_table;
}
next();
pf_expression();
if (tokentype != EOL) {
/*
* The idea here is to do as much filtering as possible in
* the kernel. So even if we find a token we don't understand,
* we try to see if we can still set up a portion of the filter
* in the kernel and use the userland filter to filter the
* remaining stuff. Obviously, if our filter expression is of
* type A AND B, we can filter A in kernel and then apply B
* to the packets that got through. The same is not true for
* a filter of type A OR B. We can't apply A first and then B
* on the packets filtered through A.
*
* (We need to keep track of the fact when we find an OR,
* and the fact that we are inside brackets when we find OR.
* The variable 'foundOR' tells us if there was an OR behind,
* 'inBraceOR' tells us if we found an OR before we could find
* the end brace i.e. ')', and variable 'aheadOR' checks if
* there is an OR in the expression ahead. if either of these
* cases become true, we can't split the filtering)
*/
if (foundOR || inBraceOR) {
/* FORGET IN KERNEL FILTERING */
return (0);
} else {
/* CHECK IF NO OR AHEAD */
sav_ptr = (char *)((uintptr_t)sav_str +
(uintptr_t)sav_tkp -
(uintptr_t)argstr);
ptr = sav_ptr;
while (*ptr != '\0') {
switch (*ptr) {
case '(':
inBr++;
break;
case ')':
inBr--;
break;
case 'o':
case 'O':
if ((*(ptr + 1) == 'R' ||
*(ptr + 1) == 'r') && !inBr)
aheadOR = 1;
break;
case ',':
if (!inBr)
aheadOR = 1;
break;
}
ptr++;
}
if (!aheadOR) {
/* NO OR AHEAD, SPLIT UP THE FILTERING */
pf.Pf_FilterLen = pfp - &pf.Pf_Filter[0];
pf.Pf_Priority = 5;
if (print) {
pf_codeprint(&pf.Pf_Filter[0],
pf.Pf_FilterLen);
}
compile(sav_ptr, print);
return (2);
} else
return (0);
}
}
pf.Pf_FilterLen = pfp - &pf.Pf_Filter[0];
pf.Pf_Priority = 5; /* unimportant, so long as > 2 */
if (print) {
pf_codeprint(&pf.Pf_Filter[0], pf.Pf_FilterLen);
}
return (1);
}