#include <sys/kmem.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <netinet/in.h>
#include <ipp/ipgpc/classifier.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <net/if.h>
#include <inet/ipp_common.h>
#define CHECK_MATCH_STATUS(match_status, slctrs_srchd, selector_mask) \
(((match_status) == NORMAL_MATCH) ? \
(NORMAL_MATCH) : \
(((match_status) == DONTCARE_ONLY_MATCH) ? \
(*(slctrs_srchd) ^= (selector_mask), NORMAL_MATCH) : \
(match_status)))
boolean_t ipgpc_action_exist = B_FALSE;
int ipgpc_debug = 0;
static int common_classify(ipgpc_packet_t *, ht_match_t *, uint16_t *);
static void update_stats(int, uint_t);
static int bestmatch(ht_match_t *, uint16_t);
static void get_port_info(ipgpc_packet_t *, void *, int, mblk_t *);
static int
common_classify(ipgpc_packet_t *packet, ht_match_t *fid_table,
uint16_t *slctrs_srchd)
{
int match_status;
match_status =
ipgpc_findfilters(IPGPC_TABLE_DIR, packet->direction, fid_table);
if (CHECK_MATCH_STATUS(match_status, slctrs_srchd,
ipgpc_table_list[DIR_IDX].info.mask) != NORMAL_MATCH) {
return (match_status);
}
match_status =
ipgpc_findfilters(IPGPC_TABLE_IF, packet->if_index, fid_table);
if (CHECK_MATCH_STATUS(match_status, slctrs_srchd,
ipgpc_table_list[IF_IDX].info.mask) != NORMAL_MATCH) {
return (match_status);
}
match_status =
ipgpc_findfilters(IPGPC_BA_DSID, packet->dsfield, fid_table);
if (CHECK_MATCH_STATUS(match_status, slctrs_srchd,
ipgpc_ds_table_id.info.mask) != NORMAL_MATCH) {
return (match_status);
}
match_status =
ipgpc_findfilters(IPGPC_TABLE_UID, packet->uid, fid_table);
if (CHECK_MATCH_STATUS(match_status, slctrs_srchd,
ipgpc_table_list[UID_IDX].info.mask) != NORMAL_MATCH) {
return (match_status);
}
match_status =
ipgpc_findfilters(IPGPC_TABLE_PROJID, packet->projid, fid_table);
if (CHECK_MATCH_STATUS(match_status, slctrs_srchd,
ipgpc_table_list[PROJID_IDX].info.mask) != NORMAL_MATCH) {
return (match_status);
}
if (packet->proto > 0) {
match_status = ipgpc_findfilters(IPGPC_TABLE_PROTOID,
packet->proto, fid_table);
if (CHECK_MATCH_STATUS(match_status, slctrs_srchd,
ipgpc_table_list[PROTOID_IDX].info.mask)
!= NORMAL_MATCH) {
return (match_status);
}
} else {
*slctrs_srchd ^= ipgpc_table_list[PROTOID_IDX].info.mask;
}
if (packet->sport > 0) {
match_status = ipgpc_findfilters(IPGPC_TRIE_SPORTID,
packet->sport, fid_table);
if (CHECK_MATCH_STATUS(match_status, slctrs_srchd,
ipgpc_trie_list[IPGPC_TRIE_SPORTID].info.mask)
!= NORMAL_MATCH) {
return (match_status);
}
} else {
*slctrs_srchd ^= ipgpc_trie_list[IPGPC_TRIE_SPORTID].info.mask;
}
if (packet->dport > 0) {
match_status = ipgpc_findfilters(IPGPC_TRIE_DPORTID,
packet->dport, fid_table);
if (CHECK_MATCH_STATUS(match_status, slctrs_srchd,
ipgpc_trie_list[IPGPC_TRIE_DPORTID].info.mask)
!= NORMAL_MATCH) {
return (match_status);
}
} else {
*slctrs_srchd ^= ipgpc_trie_list[IPGPC_TRIE_DPORTID].info.mask;
}
return (NORMAL_MATCH);
}
static void
update_stats(int class_id, uint_t nbytes)
{
if (ipgpc_gather_stats) {
BUMP_STATS(ipgpc_npackets);
UPDATE_STATS(ipgpc_nbytes, nbytes);
if (ipgpc_cid_list[class_id].aclass.gather_stats) {
SET_STATS(ipgpc_cid_list[class_id].stats.last_match,
gethrtime());
BUMP_STATS(ipgpc_cid_list[class_id].stats.npackets);
UPDATE_STATS(ipgpc_cid_list[class_id].stats.nbytes,
nbytes);
}
}
}
#define FREE_FID_TABLE(fid_table, p, q, i) \
\
for (i = 0; i < HASH_SIZE; ++i) { \
if (fid_table[i].next != NULL) { \
p = fid_table[i].next; \
while (p != NULL) { \
q = p; \
p = p->next; \
kmem_cache_free(ht_match_cache, q); \
} \
} \
}
ipgpc_class_t *
ipgpc_classify(int af, ipgpc_packet_t *packet)
{
int match_status;
uint16_t slctrs_srchd;
int class_id;
ht_match_t fid_table[HASH_SIZE];
ht_match_t *p, *q;
int i;
int rc;
if (ipgpc_num_fltrs == 0) {
update_stats(ipgpc_def_class_id, packet->len);
return (&ipgpc_cid_list[ipgpc_def_class_id].aclass);
}
match_status = 0;
slctrs_srchd = ALL_MATCH_MASK;
bzero(fid_table, sizeof (ht_match_t) * HASH_SIZE);
rc = common_classify(packet, fid_table, &slctrs_srchd);
if (rc != NORMAL_MATCH) {
FREE_FID_TABLE(fid_table, p, q, i);
if (rc == NO_MATCHES) {
update_stats(ipgpc_def_class_id, packet->len);
return (&ipgpc_cid_list[ipgpc_def_class_id].aclass);
} else {
return (NULL);
}
}
switch (af) {
case AF_INET:
match_status = ipgpc_findfilters(IPGPC_TRIE_SADDRID,
V4_PART_OF_V6(packet->saddr), fid_table);
if (CHECK_MATCH_STATUS(match_status, &slctrs_srchd,
ipgpc_trie_list[IPGPC_TRIE_SADDRID].info.mask)
!= NORMAL_MATCH) {
FREE_FID_TABLE(fid_table, p, q, i);
if (match_status == NO_MATCHES) {
update_stats(ipgpc_def_class_id, packet->len);
return (&ipgpc_cid_list[ipgpc_def_class_id].
aclass);
} else {
return (NULL);
}
}
match_status = ipgpc_findfilters(IPGPC_TRIE_DADDRID,
V4_PART_OF_V6(packet->daddr), fid_table);
if (CHECK_MATCH_STATUS(match_status, &slctrs_srchd,
ipgpc_trie_list[IPGPC_TRIE_DADDRID].info.mask)
!= NORMAL_MATCH) {
FREE_FID_TABLE(fid_table, p, q, i);
if (match_status == NO_MATCHES) {
update_stats(ipgpc_def_class_id, packet->len);
return (&ipgpc_cid_list[ipgpc_def_class_id].
aclass);
} else {
return (NULL);
}
}
break;
case AF_INET6:
match_status = ipgpc_findfilters6(IPGPC_TRIE_SADDRID6,
packet->saddr, fid_table);
if (CHECK_MATCH_STATUS(match_status, &slctrs_srchd,
ipgpc_trie_list[IPGPC_TRIE_SADDRID6].info.mask)
!= NORMAL_MATCH) {
FREE_FID_TABLE(fid_table, p, q, i);
if (match_status == NO_MATCHES) {
update_stats(ipgpc_def_class_id, packet->len);
return (&ipgpc_cid_list[ipgpc_def_class_id].
aclass);
} else {
return (NULL);
}
}
match_status = ipgpc_findfilters6(IPGPC_TRIE_DADDRID6,
packet->daddr, fid_table);
if (CHECK_MATCH_STATUS(match_status, &slctrs_srchd,
ipgpc_trie_list[IPGPC_TRIE_DADDRID6].info.mask)
!= NORMAL_MATCH) {
FREE_FID_TABLE(fid_table, p, q, i);
if (match_status == NO_MATCHES) {
update_stats(ipgpc_def_class_id, packet->len);
return (&ipgpc_cid_list[ipgpc_def_class_id].
aclass);
} else {
return (NULL);
}
}
break;
default:
ipgpc0dbg(("ipgpc_classify(): Unknown Address Family"));
FREE_FID_TABLE(fid_table, p, q, i);
return (NULL);
}
if (slctrs_srchd == 0) {
update_stats(ipgpc_def_class_id, packet->len);
return (&ipgpc_cid_list[ipgpc_def_class_id].aclass);
}
class_id = bestmatch(fid_table, slctrs_srchd);
FREE_FID_TABLE(fid_table, p, q, i);
update_stats(class_id, packet->len);
return (&ipgpc_cid_list[class_id].aclass);
}
static int
bestmatch(ht_match_t *fid_table, uint16_t bestmask)
{
int i, key;
int bestmatch = -1;
int oldbm = -1;
uint32_t temp_prec;
uint32_t temp_prio;
uint64_t best_prio;
uint64_t real_prio;
ht_match_t *item;
for (i = 0; i < HASH_SIZE; ++i) {
if (fid_table[i].key == 0) {
continue;
}
for (item = &fid_table[i]; item != NULL; item = item->next) {
if ((key = item->key) == 0) {
continue;
}
if (ipgpc_fid_list[key].info <= 0) {
continue;
}
if (((~bestmask) & ipgpc_fid_list[key].insert_map)
!= 0) {
continue;
}
if (item->match_map != ipgpc_fid_list[key].insert_map) {
continue;
}
if (bestmatch == -1) {
temp_prio =
ipgpc_fid_list[key].filter.priority;
temp_prec =
ipgpc_fid_list[key].filter.precedence;
best_prio = ((uint64_t)temp_prio << 32) |
(uint64_t)~temp_prec;
bestmatch = key;
continue;
}
real_prio =
((uint64_t)ipgpc_fid_list[key].filter.priority
<< 32) |
(uint64_t)~ipgpc_fid_list[key].filter.precedence;
if (real_prio > best_prio) {
oldbm = bestmatch;
ipgpc3dbg(("bestmatch: filter %s " \
"REJECTED because of better priority %d" \
" and/or precedence %d",
ipgpc_fid_list[oldbm].filter.filter_name,
ipgpc_fid_list[oldbm].filter.priority,
ipgpc_fid_list[oldbm].filter.precedence));
best_prio = real_prio;
bestmatch = key;
} else {
ipgpc3dbg(("bestmatch: filter %s " \
"REJECTED because of beter priority %d" \
" and/or precedence %d",
ipgpc_fid_list[key].filter.filter_name,
ipgpc_fid_list[key].filter.priority,
ipgpc_fid_list[key].filter.precedence));
}
}
}
if (bestmatch == -1) {
ipgpc3dbg(("bestmatch: No filters ACCEPTED"));
return (ipgpc_def_class_id);
} else {
ipgpc3dbg(("bestmatch: filter %s ACCEPTED with priority %d " \
"and precedence %d",
ipgpc_fid_list[bestmatch].filter.filter_name,
ipgpc_fid_list[bestmatch].filter.priority,
ipgpc_fid_list[bestmatch].filter.precedence));
return (ipgpc_fid_list[bestmatch].class_id);
}
}
static void
get_port_info(ipgpc_packet_t *packet, void *iph, int af, mblk_t *mp)
{
uint16_t *up;
if (af == AF_INET) {
uint32_t u2, u1;
uint_t iplen;
ipha_t *ipha = (ipha_t *)iph;
u2 = ntohs(ipha->ipha_fragment_offset_and_flags);
u1 = u2 & (IPH_MF | IPH_OFFSET);
if (u1) {
return;
}
iplen = (ipha->ipha_version_and_hdr_length & 0xF) << 2;
up = (uint16_t *)(mp->b_rptr + iplen);
packet->sport = (uint16_t)*up++;
packet->dport = (uint16_t)*up;
} else {
uint_t length = IPV6_HDR_LEN;
ip6_t *ip6h = (ip6_t *)iph;
uint_t ehdrlen;
uint8_t *nexthdrp, *whereptr, *endptr;
ip6_dest_t *desthdr;
ip6_rthdr_t *rthdr;
ip6_hbh_t *hbhhdr;
whereptr = ((uint8_t *)&ip6h[1]);
endptr = mp->b_wptr;
nexthdrp = &ip6h->ip6_nxt;
while (whereptr < endptr) {
switch (*nexthdrp) {
case IPPROTO_HOPOPTS:
hbhhdr = (ip6_hbh_t *)whereptr;
ehdrlen = 8 * (hbhhdr->ip6h_len + 1);
if ((uchar_t *)hbhhdr + ehdrlen > endptr)
return;
nexthdrp = &hbhhdr->ip6h_nxt;
break;
case IPPROTO_DSTOPTS:
desthdr = (ip6_dest_t *)whereptr;
ehdrlen = 8 * (desthdr->ip6d_len + 1);
if ((uchar_t *)desthdr + ehdrlen > endptr)
return;
nexthdrp = &desthdr->ip6d_nxt;
break;
case IPPROTO_ROUTING:
rthdr = (ip6_rthdr_t *)whereptr;
ehdrlen = 8 * (rthdr->ip6r_len + 1);
if ((uchar_t *)rthdr + ehdrlen > endptr)
return;
nexthdrp = &rthdr->ip6r_nxt;
break;
case IPPROTO_FRAGMENT:
return;
case IPPROTO_TCP:
case IPPROTO_UDP:
case IPPROTO_SCTP:
if (((uchar_t *)ip6h + length +
ICMP_MIN_TP_HDR_LEN) > endptr) {
return;
}
packet->proto = *nexthdrp;
up = (uint16_t *)((uchar_t *)ip6h + length);
packet->sport = (uint16_t)*up++;
packet->dport = (uint16_t)*up;
return;
case IPPROTO_ICMPV6:
case IPPROTO_ENCAP:
case IPPROTO_IPV6:
case IPPROTO_ESP:
case IPPROTO_AH:
packet->proto = *nexthdrp;
return;
case IPPROTO_NONE:
default:
return;
}
length += ehdrlen;
whereptr += ehdrlen;
}
}
}
static void
find_ids(ipgpc_packet_t *packet, mblk_t *mp)
{
cred_t *cr;
cr = msg_getcred(mp, NULL);
if (cr != NULL) {
packet->uid = crgetuid(cr);
packet->projid = crgetprojid(cr);
} else {
packet->uid = (uid_t)-1;
packet->projid = -1;
}
}
void
parse_packet(ipgpc_packet_t *packet, mblk_t *mp)
{
ipha_t *ipha;
ipha = (ipha_t *)mp->b_rptr;
V4_PART_OF_V6(packet->saddr) = (int32_t)ipha->ipha_src;
V4_PART_OF_V6(packet->daddr) = (int32_t)ipha->ipha_dst;
packet->dsfield = ipha->ipha_type_of_service;
packet->proto = ipha->ipha_protocol;
packet->sport = 0;
packet->dport = 0;
find_ids(packet, mp);
packet->len = msgdsize(mp);
if ((packet->proto == IPPROTO_TCP) || (packet->proto == IPPROTO_UDP) ||
(packet->proto == IPPROTO_SCTP)) {
get_port_info(packet, ipha, AF_INET, mp);
}
}
void
parse_packet6(ipgpc_packet_t *packet, mblk_t *mp)
{
ip6_t *ip6h = (ip6_t *)mp->b_rptr;
bcopy(ip6h->ip6_src.s6_addr32, packet->saddr.s6_addr32,
sizeof (ip6h->ip6_src.s6_addr32));
bcopy(ip6h->ip6_dst.s6_addr32, packet->daddr.s6_addr32,
sizeof (ip6h->ip6_dst.s6_addr32));
packet->proto = ip6h->ip6_nxt;
packet->dsfield = __IPV6_TCLASS_FROM_FLOW(ip6h->ip6_vcf);
find_ids(packet, mp);
packet->len = msgdsize(mp);
packet->sport = 0;
packet->dport = 0;
if (mp->b_cont != NULL) {
if (!pullupmsg(mp, -1)) {
ipgpc0dbg(("parse_packet6(): pullup error, can't " \
"find ports"));
return;
}
ip6h = (ip6_t *)mp->b_rptr;
}
get_port_info(packet, ip6h, AF_INET6, mp);
}
#ifdef IPGPC_DEBUG
void
print_packet(int af, ipgpc_packet_t *pkt)
{
char saddrbuf[INET6_ADDRSTRLEN];
char daddrbuf[INET6_ADDRSTRLEN];
if (af == AF_INET) {
(void) inet_ntop(af, &V4_PART_OF_V6(pkt->saddr), saddrbuf,
sizeof (saddrbuf));
(void) inet_ntop(af, &V4_PART_OF_V6(pkt->daddr), daddrbuf,
sizeof (daddrbuf));
ipgpc4dbg(("print_packet: saddr = %s, daddr = %s, sport = %u" \
", dport = %u, proto = %u, dsfield = %x, uid = %d," \
" if_index = %d, projid = %d, direction = %d", saddrbuf,
daddrbuf, ntohs(pkt->sport), ntohs(pkt->dport), pkt->proto,
pkt->dsfield, pkt->uid, pkt->if_index,
pkt->projid, pkt->direction));
} else if (af == AF_INET6) {
(void) inet_ntop(af, pkt->saddr.s6_addr32, saddrbuf,
sizeof (saddrbuf));
(void) inet_ntop(af, pkt->daddr.s6_addr32, daddrbuf,
sizeof (daddrbuf));
ipgpc4dbg(("print_packet: saddr = %s, daddr = %s, sport = %u" \
", dport = %u, proto = %u, dsfield = %x, uid = %d," \
" if_index = %d, projid = %d, direction = %d", saddrbuf,
daddrbuf, ntohs(pkt->sport), ntohs(pkt->dport), pkt->proto,
pkt->dsfield, pkt->uid, pkt->if_index,
pkt->projid, pkt->direction));
}
}
#endif