root/usr/src/uts/common/ipp/ipgpc/classifier.c
/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#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>

/* Implementation file for classifier used in ipgpc module */

/*
 * CHECK_MATCH_STATUS(match_status, slctrs_srchd, selector_mask)
 *
 * determines what the result of the selector search and what action needs to
 * be taken next.
 * if a NORMAL_MATCH occurs, business as usual NORMAL_MATCH
 * if the selector was not searched because only DONTCARE keys are loaded,
 * the selector is marked as not being searched
 * otherwise, memory error occurred or no matches were found, classify()
 * should return the error match status immediately
 */
#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)))

/* used to determine if an action instance already exists */
boolean_t ipgpc_action_exist = B_FALSE;
int ipgpc_debug = 0;            /* IPGPC debugging level */

/* Statics */
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 *);

/*
 * common_classify(packet, fid_table, slctrs_srchd)
 *
 * searches each of the common selectors
 * - will return NORMAL_MATCH on success.  NO_MATCHES on error
 */
static int
common_classify(ipgpc_packet_t *packet, ht_match_t *fid_table,
    uint16_t *slctrs_srchd)
{
        int match_status;

        /* Find on packet direction */
        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);
        }

        /* Find on IF_INDEX of packet */
        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);
        }

        /* Find on DS field */
        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);
        }

        /* Find on UID of packet */
        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);
        }

        /* Find on PROJID of packet */
        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);
        }

        /* Find on IP Protocol field */
        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 {
                /* skip search */
                *slctrs_srchd ^= ipgpc_table_list[PROTOID_IDX].info.mask;
        }

        /* Find on IP Source Port field */
        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 {
                /* skip search */
                *slctrs_srchd ^= ipgpc_trie_list[IPGPC_TRIE_SPORTID].info.mask;
        }

        /* Find on IP Destination Port field */
        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 {
                /* skip search */
                *slctrs_srchd ^= ipgpc_trie_list[IPGPC_TRIE_DPORTID].info.mask;
        }
        return (NORMAL_MATCH);
}

/*
 * update_stats(class_id, nbytes)
 *
 * if ipgpc_gather_stats == TRUE
 * updates the statistics for class pointed to be the input classid
 * and the global ipgpc kstats
 * updates the last time the class was matched with the current hrtime value,
 * number of packets and number of bytes with nbytes
 */
static void
update_stats(int class_id, uint_t nbytes)
{
        if (ipgpc_gather_stats) {
                /* update global stats */
                BUMP_STATS(ipgpc_npackets);
                UPDATE_STATS(ipgpc_nbytes, nbytes);
                if (ipgpc_cid_list[class_id].aclass.gather_stats) {
                        /* update per class 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);
                }
        }
}

/*
 * FREE_FID_TABLE(fid_table, p, q, i)
 *
 * searches fid_table for dynamically allocated memory and frees it
 * p, q, i are temps
 */
#define FREE_FID_TABLE(fid_table, p, q, i)                              \
        /* free all allocated memory in fid_table */                    \
        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_classify(af, packet)
 *
 * The function that drives the packet classification algorithm.  Given a
 * address family (either AF_INET or AF_INET6) the input packet structure
 * is matched against all the selector structures.  For each search of
 * a selector structure, all matched filters are collected.  Once all
 * selectors are searched, the best match of all matched filters is
 * determined.  Finally, the class associated with the best matching filter
 * is returned.  If no filters were matched, the default class is returned.
 * If a memory error occurred, NULL is returned.
 */
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) {
                /* zero filters are loaded, return default class */
                update_stats(ipgpc_def_class_id, packet->len);
                /*
                 * no need to free fid_table. Since zero selectors were
                 * searched and dynamic memory wasn't allocated.
                 */
                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);

        /* first search all address family independent selectors */
        rc = common_classify(packet, fid_table, &slctrs_srchd);
        if (rc != NORMAL_MATCH) {
                /* free all dynamic allocated memory */
                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 {        /* memory error */
                        return (NULL);
                }
        }

        switch (af) {           /* switch off of address family */
        case AF_INET:
                /* Find on IPv4 Source Address field */
                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 all dynamic allocated memory */
                        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 { /* memory error */
                                return (NULL);
                        }
                }
                /* Find on IPv4 Destination Address field */
                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 all dynamic allocated memory */
                        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 { /* memory error */
                                return (NULL);
                        }
                }
                break;
        case AF_INET6:
                /* Find on IPv6 Source Address field */
                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 all dynamic allocated memory */
                        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 { /* memory error */
                                return (NULL);
                        }
                }
                /* Find on IPv6 Destination Address field */
                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 all dynamic allocated memory */
                        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 all dynamic allocated memory */
                FREE_FID_TABLE(fid_table, p, q, i);
                return (NULL);
        }

        /* zero selectors were searched, return default */
        if (slctrs_srchd == 0) {
                /*
                 * no need to free fid_table.  Since zero selectors were
                 * searched and dynamic memory wasn't allocated
                 */
                update_stats(ipgpc_def_class_id, packet->len);
                return (&ipgpc_cid_list[ipgpc_def_class_id].aclass);
        }

        /* Perform best match search */
        class_id = bestmatch(fid_table, slctrs_srchd);
        /* free all dynamic allocated memory */
        FREE_FID_TABLE(fid_table, p, q, i);

        update_stats(class_id, packet->len);
        return (&ipgpc_cid_list[class_id].aclass);
}

/*
 * bestmatch(fid_table, bestmask)
 *
 * determines the bestmatching filter in fid_table which matches the criteria
 * described below and returns the class id
 */
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) {
                        /*
                         * BESTMATCH is:
                         * 1. Matches in all selectors searched
                         * 2. highest priority of filters that meet 1.
                         * 3. best precedence of filters that meet 2
                         *    with the same priority
                         */
                        if ((key = item->key) == 0) {
                                continue;
                        }
                        if (ipgpc_fid_list[key].info <= 0) {
                                continue;
                        }

                        /*
                         * check to see if fid has been inserted into a
                         * selector structure we did not search
                         * if so, then this filter is not a valid match
                         * and bestmatch() should continue
                         * this statement will == 0
                         * - a selector has been searched and this filter
                         *   either describes don't care or has inserted a
                         *   value into this selector structure
                         * - a selector has not been searched and this filter
                         *   has described don't care for this selector
                         */
                        if (((~bestmask) & ipgpc_fid_list[key].insert_map)
                            != 0) {
                                continue;
                        }

                        /*
                         * tests to see if the map of selectors that
                         * were matched, equals the map of selectors
                         * structures this filter inserts into
                         */
                        if (item->match_map != ipgpc_fid_list[key].insert_map) {
                                continue;
                        }

                        if (bestmatch == -1) { /* first matching filter */
                                /* this filter becomes the bestmatch */
                                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;
                        }

                        /*
                         * calculate the real priority by combining priority
                         * and precedence
                         */
                        real_prio =
                            ((uint64_t)ipgpc_fid_list[key].filter.priority
                            << 32) |
                            (uint64_t)~ipgpc_fid_list[key].filter.precedence;

                        /* check to see if this is the new bestmatch */
                        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) {  /* no best matches were found */
                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);
        }
}

/*
 * get_port_info(packet, iph, af, mp)
 *
 * Gets the source and destination ports from the ULP header, if present.
 * If this is a fragment, don't try to get the port information even if this
 * is the first fragment. The reason being we won't have this information
 * in subsequent fragments and may end up classifying the first fragment
 * differently than others. This is not desired.
 * For IPv6 packets, step through the extension headers, if present, in
 * order to get to the ULP header.
 */
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 {        /* AF_INET6 */
                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:
                                /*
                                 * Verify we have at least ICMP_MIN_TP_HDR_LEN
                                 * bytes of the ULP's header to get the port
                                 * info.
                                 */
                                if (((uchar_t *)ip6h + length +
                                    ICMP_MIN_TP_HDR_LEN)  > endptr) {
                                        return;
                                }
                                /* Get the protocol and the ports */
                                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;
                }
        }
}

/*
 * find_ids(packet, mp)
 *
 * attempt to discern the uid and projid of the originator of a packet by
 * looking at the dblks making up the packet - yeuch!
 *
 * We do it by skipping any fragments with a credp of NULL (originated in
 * kernel), taking the first value that isn't NULL to be the credp for the
 * whole packet. We also suck the projid from the same fragment.
 */
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;
        }
}

/*
 * parse_packet(packet, mp)
 *
 * parses the given message block into a ipgpc_packet_t structure
 */
void
parse_packet(ipgpc_packet_t *packet, mblk_t *mp)
{
        ipha_t  *ipha;

        /* parse message block for IP header and ports */
        ipha = (ipha_t *)mp->b_rptr; /* get ip header */
        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);
        /* parse out TCP/UDP ports, if appropriate */
        if ((packet->proto == IPPROTO_TCP) || (packet->proto == IPPROTO_UDP) ||
            (packet->proto == IPPROTO_SCTP)) {
                get_port_info(packet, ipha, AF_INET, mp);
        }
}

/*
 * parse_packet6(packet, mp)
 *
 * parses the message block into a ipgpc_packet_t structure for IPv6 traffic
 */
void
parse_packet6(ipgpc_packet_t *packet, mblk_t *mp)
{
        ip6_t *ip6h = (ip6_t *)mp->b_rptr;

        /* parse message block for IP header and ports */
        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));
        /* Will be (re-)assigned in get_port_info */
        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;
        /* Need to pullup everything. */
        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
/*
 * print_packet(af, packet)
 *
 * prints the contents of the packet structure for specified address family
 */
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 /* IPGPC_DEBUG */