/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * flowacct - IPQoS accounting module. The module maintains an array * of 256 hash buckets. When the action routine is invoked for a flow, * if the flow (identified by the 5-tuple: saddr, daddr, sport, dport, proto) * is already present in the flow table (indexed by the hash function FLOW_HASH) * then a check is made to see if an item for this flow with the same * dsfield, projid & user id is present. If it is, then the number of packets * and the bytes are incremented for that item. If the item does * not exist a new item is added for the flow. If the flow is not present * an entry is made for the flow. * * A timer runs thru the table and writes all the flow items that have * timed out to the accounting file (via exacct PSARC/1999/119), if present * Configuration commands to change the timing interval is provided. The * flow timeout value can also be configured. While the timeout is in nsec, * the flow timer interval is in usec. * Information for an active flow can be obtained by using kstats. */ /* Used in computing the hash index */ #define FLOWACCT_ADDR_HASH(addr) \ ((addr).s6_addr8[8] ^ (addr).s6_addr8[9] ^ \ (addr).s6_addr8[10] ^ (addr).s6_addr8[13] ^ \ (addr).s6_addr8[14] ^ (addr).s6_addr8[15]) #define FLOWACCT_FLOW_HASH(f) \ (((FLOWACCT_ADDR_HASH(f->saddr)) + \ (FLOWACCT_ADDR_HASH(f->daddr)) + \ (f->proto) + (f->sport) + (f->dport)) \ % FLOW_TBL_COUNT) /* * Compute difference between a and b in nsec and store in delta. * delta should be a hrtime_t. Taken from ip_mroute.c. */ #define FLOWACCT_DELTA(a, b, delta) { \ int xxs; \ \ delta = (a).tv_nsec - (b).tv_nsec; \ if ((xxs = (a).tv_sec - (b).tv_sec) != 0) { \ switch (xxs) { \ case 2: \ delta += NANOSEC; \ /*FALLTHRU*/ \ case 1: \ delta += NANOSEC; \ break; \ default: \ delta += ((hrtime_t)NANOSEC * xxs); \ } \ } \ } /* Debug level */ int flowacct_debug = 0; /* Collect timed out flows to be written to the accounting file */ typedef struct flow_records_s { flow_usage_t *fl_use; struct flow_records_s *next; }flow_records_t; /* Get port information from the packet. Ignore fragments. */ static void flowacct_port_info(header_t *header, void *iph, int af, mblk_t *mp) { uint16_t *up; if (af == AF_INET) { ipha_t *ipha = (ipha_t *)iph; uint32_t u2, u1; uint_t iplen; u2 = ntohs(ipha->ipha_fragment_offset_and_flags); u1 = u2 & (IPH_MF | IPH_OFFSET); if (u1 != 0) { return; } iplen = (ipha->ipha_version_and_hdr_length & 0xF) << 2; up = (uint16_t *)(mp->b_rptr + iplen); header->sport = (uint16_t)*up++; header->dport = (uint16_t)*up; } else { ip6_t *ip6h = (ip6_t *)iph; uint_t length = IPV6_HDR_LEN; 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 & ports */ header->proto = *nexthdrp; up = (uint16_t *)((uchar_t *)ip6h + length); header->sport = (uint16_t)*up++; header->dport = (uint16_t)*up; return; case IPPROTO_ICMPV6: case IPPROTO_ENCAP: case IPPROTO_IPV6: case IPPROTO_ESP: case IPPROTO_AH: header->proto = *nexthdrp; return; case IPPROTO_NONE: default: return; } length += ehdrlen; whereptr += ehdrlen; } } } /* * flowacct_find_ids(mp, header) * * 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 cred_t for the * whole packet. */ static void flowacct_find_ids(mblk_t *mp, header_t *header) { cred_t *cr; while (DB_CRED(mp) == NULL && mp->b_cont != NULL) mp = mp->b_cont; if ((cr = DB_CRED(mp)) != NULL) { header->uid = crgetuid(cr); header->projid = crgetprojid(cr); } else { header->uid = (uid_t)-1; header->projid = -1; } } /* * Extract header information in a header_t structure so that we don't have * have to parse the packet everytime. */ static int flowacct_extract_header(mblk_t *mp, header_t *header) { ipha_t *ipha; ip6_t *ip6h; #define rptr ((uchar_t *)ipha) /* 0 means no port extracted. */ header->sport = 0; header->dport = 0; flowacct_find_ids(mp, header); V6_SET_ZERO(header->saddr); V6_SET_ZERO(header->daddr); ipha = (ipha_t *)mp->b_rptr; header->isv4 = IPH_HDR_VERSION(ipha) == IPV4_VERSION; if (header->isv4) { ipha = (ipha_t *)mp->b_rptr; V4_PART_OF_V6(header->saddr) = (int32_t)ipha->ipha_src; V4_PART_OF_V6(header->daddr) = (int32_t)ipha->ipha_dst; header->dsfield = ipha->ipha_type_of_service; header->proto = ipha->ipha_protocol; header->pktlen = ntohs(ipha->ipha_length); if ((header->proto == IPPROTO_TCP) || (header->proto == IPPROTO_UDP) || (header->proto == IPPROTO_SCTP)) { flowacct_port_info(header, ipha, AF_INET, mp); } } else { /* * Need to pullup everything. */ if (mp->b_cont != NULL) { if (!pullupmsg(mp, -1)) { flowacct0dbg(("flowacct_extract_header: "\ "pullup error")); return (-1); } } ip6h = (ip6_t *)mp->b_rptr; bcopy(ip6h->ip6_src.s6_addr32, header->saddr.s6_addr32, sizeof (ip6h->ip6_src.s6_addr32)); bcopy(ip6h->ip6_dst.s6_addr32, header->daddr.s6_addr32, sizeof (ip6h->ip6_dst.s6_addr32)); header->dsfield = __IPV6_TCLASS_FROM_FLOW(ip6h->ip6_vcf); header->proto = ip6h->ip6_nxt; header->pktlen = ntohs(ip6h->ip6_plen) + ip_hdr_length_v6(mp, ip6h); flowacct_port_info(header, ip6h, AF_INET6, mp); } #undef rptr return (0); } /* Check if the flow (identified by the 5-tuple) exists in the hash table */ static flow_t * flowacct_flow_present(header_t *header, int index, flowacct_data_t *flowacct_data) { list_hdr_t *hdr = flowacct_data->flows_tbl[index].head; flow_t *flow; while (hdr != NULL) { flow = (flow_t *)hdr->objp; if ((flow != NULL) && (IN6_ARE_ADDR_EQUAL(&flow->saddr, &header->saddr)) && (IN6_ARE_ADDR_EQUAL(&flow->daddr, &header->daddr)) && (flow->proto == header->proto) && (flow->sport == header->sport) && (flow->dport == header->dport)) { return (flow); } hdr = hdr->next; } return ((flow_t *)NULL); } /* * Add an object to the list at insert_point. This could be a flow item or * a flow itself. */ static list_hdr_t * flowacct_add_obj(list_head_t *tophdr, list_hdr_t *insert_point, void *obj) { list_hdr_t *new_hdr; if (tophdr == NULL) { return ((list_hdr_t *)NULL); } new_hdr = (list_hdr_t *)kmem_zalloc(FLOWACCT_HDR_SZ, KM_NOSLEEP); if (new_hdr == NULL) { flowacct0dbg(("flowacct_add_obj: error allocating mem")); return ((list_hdr_t *)NULL); } gethrestime(&new_hdr->last_seen); new_hdr->objp = obj; tophdr->nbr_items++; if (insert_point == NULL) { if (tophdr->head == NULL) { tophdr->head = new_hdr; tophdr->tail = new_hdr; return (new_hdr); } new_hdr->next = tophdr->head; tophdr->head->prev = new_hdr; tophdr->head = new_hdr; return (new_hdr); } if (insert_point == tophdr->tail) { tophdr->tail->next = new_hdr; new_hdr->prev = tophdr->tail; tophdr->tail = new_hdr; return (new_hdr); } new_hdr->next = insert_point->next; new_hdr->prev = insert_point; insert_point->next->prev = new_hdr; insert_point->next = new_hdr; return (new_hdr); } /* Delete an obj from the list. This could be a flow item or the flow itself */ static void flowacct_del_obj(list_head_t *tophdr, list_hdr_t *hdr, uint_t mode) { size_t length; uint_t type; if ((tophdr == NULL) || (hdr == NULL)) { return; } type = ((flow_t *)hdr->objp)->type; tophdr->nbr_items--; if (hdr->next != NULL) { hdr->next->prev = hdr->prev; } if (hdr->prev != NULL) { hdr->prev->next = hdr->next; } if (tophdr->head == hdr) { tophdr->head = hdr->next; } if (tophdr->tail == hdr) { tophdr->tail = hdr->prev; } if (mode == FLOWACCT_DEL_OBJ) { switch (type) { case FLOWACCT_FLOW: length = FLOWACCT_FLOW_SZ; break; case FLOWACCT_ITEM: length = FLOWACCT_ITEM_SZ; break; } kmem_free(hdr->objp, length); hdr->objp = NULL; } kmem_free((void *)hdr, FLOWACCT_HDR_SZ); } /* * Checks if the given item (identified by dsfield, project id and uid) * is already present for the flow. */ static flow_item_t * flowacct_item_present(flow_t *flow, uint8_t dsfield, pid_t proj_id, uint_t uid) { list_hdr_t *itemhdr; flow_item_t *item; itemhdr = flow->items.head; while (itemhdr != NULL) { item = (flow_item_t *)itemhdr->objp; if ((item->dsfield != dsfield) || (item->projid != proj_id) || (item->uid != uid)) { itemhdr = itemhdr->next; continue; } return (item); } return ((flow_item_t *)NULL); } /* * Add the flow to the table, if not already present. If the flow is * present in the table, add the item. Also, update the flow stats. * Additionally, re-adjust the timout list as well. */ static int flowacct_update_flows_tbl(header_t *header, flowacct_data_t *flowacct_data) { int index; list_head_t *fhead; list_head_t *thead; list_head_t *ihead; boolean_t added_flow = B_FALSE; timespec_t now; flow_item_t *item; flow_t *flow; index = FLOWACCT_FLOW_HASH(header); fhead = &flowacct_data->flows_tbl[index]; /* The timeout list */ thead = &flowacct_data->flows_tbl[FLOW_TBL_COUNT]; mutex_enter(&fhead->lock); flow = flowacct_flow_present(header, index, flowacct_data); if (flow == NULL) { flow = (flow_t *)kmem_zalloc(FLOWACCT_FLOW_SZ, KM_NOSLEEP); if (flow == NULL) { mutex_exit(&fhead->lock); flowacct0dbg(("flowacct_update_flows_tbl: mem alloc "\ "error")); return (-1); } flow->hdr = flowacct_add_obj(fhead, fhead->tail, (void *)flow); if (flow->hdr == NULL) { mutex_exit(&fhead->lock); kmem_free(flow, FLOWACCT_FLOW_SZ); flowacct0dbg(("flowacct_update_flows_tbl: mem alloc "\ "error")); return (-1); } flow->type = FLOWACCT_FLOW; flow->isv4 = header->isv4; bcopy(header->saddr.s6_addr32, flow->saddr.s6_addr32, sizeof (header->saddr.s6_addr32)); bcopy(header->daddr.s6_addr32, flow->daddr.s6_addr32, sizeof (header->daddr.s6_addr32)); flow->proto = header->proto; flow->sport = header->sport; flow->dport = header->dport; flow->back_ptr = fhead; added_flow = B_TRUE; } else { /* * We need to make sure that this 'flow' is not deleted * either by a scheduled timeout or an explict call * to flowacct_timer() below. */ flow->inuse = B_TRUE; } ihead = &flow->items; item = flowacct_item_present(flow, header->dsfield, header->projid, header->uid); if (item == NULL) { boolean_t just_once = B_TRUE; /* * For all practical purposes, we limit the no. of entries in * the flow table - i.e. the max_limt that a user specifies is * the maximum no. of flow items in the table. */ try_again: atomic_add_32(&flowacct_data->nflows, 1); if (flowacct_data->nflows > flowacct_data->max_limit) { atomic_add_32(&flowacct_data->nflows, -1); /* Try timing out once */ if (just_once) { /* * Need to release the lock, as this entry * could contain a flow that can be timed * out. */ mutex_exit(&fhead->lock); flowacct_timer(FLOWACCT_JUST_ONE, flowacct_data); mutex_enter(&fhead->lock); /* Lets check again */ just_once = B_FALSE; goto try_again; } else { flow->inuse = B_FALSE; /* Need to remove the flow, if one was added */ if (added_flow) { flowacct_del_obj(fhead, flow->hdr, FLOWACCT_DEL_OBJ); } mutex_exit(&fhead->lock); flowacct1dbg(("flowacct_update_flows_tbl: "\ "maximum active flows exceeded\n")); return (-1); } } item = (flow_item_t *)kmem_zalloc(FLOWACCT_ITEM_SZ, KM_NOSLEEP); if (item == NULL) { flow->inuse = B_FALSE; /* Need to remove the flow, if one was added */ if (added_flow) { flowacct_del_obj(fhead, flow->hdr, FLOWACCT_DEL_OBJ); } mutex_exit(&fhead->lock); atomic_add_32(&flowacct_data->nflows, -1); flowacct0dbg(("flowacct_update_flows_tbl: mem alloc "\ "error")); return (-1); } item->hdr = flowacct_add_obj(ihead, ihead->tail, (void *)item); if (item->hdr == NULL) { flow->inuse = B_FALSE; /* Need to remove the flow, if one was added */ if (added_flow) { flowacct_del_obj(fhead, flow->hdr, FLOWACCT_DEL_OBJ); } mutex_exit(&fhead->lock); atomic_add_32(&flowacct_data->nflows, -1); kmem_free(item, FLOWACCT_ITEM_SZ); flowacct0dbg(("flowacct_update_flows_tbl: mem alloc "\ "error\n")); return (-1); } /* If a flow was added, add it too */ if (added_flow) { atomic_add_64(&flowacct_data->usedmem, FLOWACCT_FLOW_RECORD_SZ); } atomic_add_64(&flowacct_data->usedmem, FLOWACCT_ITEM_RECORD_SZ); item->type = FLOWACCT_ITEM; item->dsfield = header->dsfield; item->projid = header->projid; item->uid = header->uid; item->npackets = 1; item->nbytes = header->pktlen; item->creation_time = item->hdr->last_seen; } else { item->npackets++; item->nbytes += header->pktlen; } gethrestime(&now); flow->hdr->last_seen = item->hdr->last_seen = now; mutex_exit(&fhead->lock); /* * Re-adjust the timeout list. The timer takes the thead lock * follwed by fhead lock(s), so we release fhead, take thead * and re-take fhead. */ mutex_enter(&thead->lock); mutex_enter(&fhead->lock); /* If the flow was added, append it to the tail of the timeout list */ if (added_flow) { if (thead->head == NULL) { thead->head = flow->hdr; thead->tail = flow->hdr; } else { thead->tail->timeout_next = flow->hdr; flow->hdr->timeout_prev = thead->tail; thead->tail = flow->hdr; } /* * Else, move this flow to the tail of the timeout list, if it is not * already. * flow->hdr in the timeout list :- * timeout_next = NULL, timeout_prev != NULL, at the tail end. * timeout_next != NULL, timeout_prev = NULL, at the head. * timeout_next != NULL, timeout_prev != NULL, in the middle. * timeout_next = NULL, timeout_prev = NULL, not in the timeout list, * ignore such flow. */ } else if ((flow->hdr->timeout_next != NULL) || (flow->hdr->timeout_prev != NULL)) { if (flow->hdr != thead->tail) { if (flow->hdr == thead->head) { thead->head->timeout_next->timeout_prev = NULL; thead->head = thead->head->timeout_next; flow->hdr->timeout_next = NULL; thead->tail->timeout_next = flow->hdr; flow->hdr->timeout_prev = thead->tail; thead->tail = flow->hdr; } else { flow->hdr->timeout_prev->timeout_next = flow->hdr->timeout_next; flow->hdr->timeout_next->timeout_prev = flow->hdr->timeout_prev; flow->hdr->timeout_next = NULL; thead->tail->timeout_next = flow->hdr; flow->hdr->timeout_prev = thead->tail; thead->tail = flow->hdr; } } } /* * Unset this variable, now it is fine even if this * flow gets deleted (i.e. after timing out its * flow items) since we are done using it. */ flow->inuse = B_FALSE; mutex_exit(&fhead->lock); mutex_exit(&thead->lock); atomic_add_64(&flowacct_data->tbytes, header->pktlen); return (0); } /* Timer for timing out flows/items from the flow table */ void flowacct_timeout_flows(void *args) { flowacct_data_t *flowacct_data = (flowacct_data_t *)args; flowacct_timer(FLOWACCT_FLOW_TIMER, flowacct_data); flowacct_data->flow_tid = timeout(flowacct_timeout_flows, flowacct_data, drv_usectohz(flowacct_data->timer)); } /* Delete the item from the flow in the flow table */ static void flowacct_timeout_item(flow_t **flow, list_hdr_t **item_hdr) { list_hdr_t *next_it_hdr; next_it_hdr = (*item_hdr)->next; flowacct_del_obj(&(*flow)->items, *item_hdr, FLOWACCT_DEL_OBJ); *item_hdr = next_it_hdr; } /* Create a flow record for this timed out item */ static flow_records_t * flowacct_create_record(flow_t *flow, list_hdr_t *ithdr) { int count; flow_item_t *item = (flow_item_t *)ithdr->objp; flow_records_t *tmp_frec = NULL; /* Record to be written into the accounting file */ tmp_frec = kmem_zalloc(sizeof (flow_records_t), KM_NOSLEEP); if (tmp_frec == NULL) { flowacct0dbg(("flowacct_create_record: mem alloc error.\n")); return (NULL); } tmp_frec->fl_use = kmem_zalloc(sizeof (flow_usage_t), KM_NOSLEEP); if (tmp_frec->fl_use == NULL) { flowacct0dbg(("flowacct_create_record: mem alloc error\n")); kmem_free(tmp_frec, sizeof (flow_records_t)); return (NULL); } /* Copy the IP address */ for (count = 0; count < 4; count++) { tmp_frec->fl_use->fu_saddr[count] = htonl(flow->saddr.s6_addr32[count]); tmp_frec->fl_use->fu_daddr[count] = htonl(flow->daddr.s6_addr32[count]); } /* * Ports, protocol, version, dsfield, project id, uid, nbytes, npackets * creation time and last seen. */ tmp_frec->fl_use->fu_sport = htons(flow->sport); tmp_frec->fl_use->fu_dport = htons(flow->dport); tmp_frec->fl_use->fu_protocol = flow->proto; tmp_frec->fl_use->fu_isv4 = flow->isv4; tmp_frec->fl_use->fu_dsfield = item->dsfield; tmp_frec->fl_use->fu_projid = item->projid; tmp_frec->fl_use->fu_userid = item->uid; tmp_frec->fl_use->fu_nbytes = item->nbytes; tmp_frec->fl_use->fu_npackets = item->npackets; tmp_frec->fl_use->fu_lseen = (uint64_t)(ulong_t)ithdr->last_seen.tv_sec; tmp_frec->fl_use->fu_ctime = (uint64_t)(ulong_t)item->creation_time.tv_sec; return (tmp_frec); } /* * Scan thru the timeout list and write the records to the accounting file, if * possible. Basically step thru the timeout list maintained in the last * hash bucket, FLOW_COUNT_TBL + 1, and timeout flows. This could be called * from the timer, FLOWACCT_TIMER - delete only timed out flows or when this * instance is deleted, FLOWACCT_PURGE_FLOW - delete all the flows from the * table or as FLOWACCT_JUST_ONE - delete the first timed out flow. Since the * flows are cronologically arranged in the timeout list, when called as * FLOWACCT_TIMER and FLOWACCT_JUST_ONE, we can stop when we come across * the first flow that has not timed out (which means none of the following * flows would have timed out). */ void flowacct_timer(int type, flowacct_data_t *flowacct_data) { hrtime_t diff; timespec_t now; list_head_t *head, *thead; flow_t *flow; flow_item_t *item; list_hdr_t *fl_hdr, *next_fl_hdr; list_hdr_t *ithdr = (list_hdr_t *)NULL; flow_records_t *frec = NULL, *tmp_frec, *tail; uint64_t flow_size; uint64_t item_size; ASSERT(flowacct_data != NULL); /* 2s-complement for subtraction */ flow_size = ~FLOWACCT_FLOW_RECORD_SZ + 1; item_size = ~FLOWACCT_ITEM_RECORD_SZ + 1; /* Get the current time */ gethrestime(&now); /* * For each flow in the table, scan thru all the items and delete * those that have exceeded the timeout. If all the items in a * flow have timed out, delete the flow entry as well. Finally, * write all the delted items to the accounting file. */ thead = &flowacct_data->flows_tbl[FLOW_TBL_COUNT]; mutex_enter(&thead->lock); fl_hdr = thead->head; while (fl_hdr != NULL) { uint32_t items_deleted = 0; next_fl_hdr = fl_hdr->timeout_next; flow = (flow_t *)fl_hdr->objp; head = flow->back_ptr; mutex_enter(&head->lock); /*LINTED*/ FLOWACCT_DELTA(now, fl_hdr->last_seen, diff); /* * If type is FLOW_TIMER, then check if the item has timed out. * If type is FLOW_PURGE delete the entry anyways. */ if ((type != FLOWACCT_PURGE_FLOW) && (diff < flowacct_data->timeout)) { mutex_exit(&head->lock); mutex_exit(&thead->lock); goto write_records; } ithdr = flow->items.head; while (ithdr != NULL) { item = (flow_item_t *)ithdr->objp; /* * Fill in the flow record to be * written to the accounting file. */ tmp_frec = flowacct_create_record(flow, ithdr); /* * If we don't have memory for records, * we will come back in case this is * called as FLOW_TIMER, else we will * go ahead and delete the item from * the table (when asked to PURGE the * table), so there could be some * entries not written to the file * when this action instance is * deleted. */ if (tmp_frec != NULL) { tmp_frec->fl_use->fu_aname = flowacct_data->act_name; if (frec == NULL) { frec = tmp_frec; tail = frec; } else { tail->next = tmp_frec; tail = tmp_frec; } } else if (type != FLOWACCT_PURGE_FLOW) { mutex_exit(&head->lock); mutex_exit(&thead->lock); atomic_add_32(&flowacct_data->nflows, (~items_deleted + 1)); goto write_records; } /* Update stats */ atomic_add_64(&flowacct_data->tbytes, (~item->nbytes + 1)); /* Delete the item */ flowacct_timeout_item(&flow, &ithdr); items_deleted++; atomic_add_64(&flowacct_data->usedmem, item_size); } ASSERT(flow->items.nbr_items == 0); atomic_add_32(&flowacct_data->nflows, (~items_deleted + 1)); /* * Don't delete this flow if we are making place for * a new item for this flow. */ if (!flow->inuse) { if (fl_hdr->timeout_prev != NULL) { fl_hdr->timeout_prev->timeout_next = fl_hdr->timeout_next; } else { thead->head = fl_hdr->timeout_next; } if (fl_hdr->timeout_next != NULL) { fl_hdr->timeout_next->timeout_prev = fl_hdr->timeout_prev; } else { thead->tail = fl_hdr->timeout_prev; } fl_hdr->timeout_prev = NULL; fl_hdr->timeout_next = NULL; flowacct_del_obj(head, fl_hdr, FLOWACCT_DEL_OBJ); atomic_add_64(&flowacct_data->usedmem, flow_size); } mutex_exit(&head->lock); if (type == FLOWACCT_JUST_ONE) { mutex_exit(&thead->lock); goto write_records; } fl_hdr = next_fl_hdr; } mutex_exit(&thead->lock); write_records: /* Write all the timed out flows to the accounting file */ while (frec != NULL) { tmp_frec = frec->next; exacct_commit_flow(frec->fl_use); kmem_free(frec->fl_use, sizeof (flow_usage_t)); kmem_free(frec, sizeof (flow_records_t)); frec = tmp_frec; } } /* * Get the IP header contents from the packet, update the flow table with * this item and return. */ int flowacct_process(mblk_t **mpp, flowacct_data_t *flowacct_data) { header_t *header; mblk_t *mp = *mpp; ASSERT(mp != NULL); /* If we don't find an M_DATA, return error */ if (mp->b_datap->db_type != M_DATA) { if ((mp->b_cont != NULL) && (mp->b_cont->b_datap->db_type == M_DATA)) { mp = mp->b_cont; } else { flowacct0dbg(("flowacct_process: no data\n")); atomic_add_64(&flowacct_data->epackets, 1); return (EINVAL); } } header = kmem_zalloc(FLOWACCT_HEADER_SZ, KM_NOSLEEP); if (header == NULL) { flowacct0dbg(("flowacct_process: error allocing mem")); atomic_add_64(&flowacct_data->epackets, 1); return (ENOMEM); } /* Get all the required information into header. */ if (flowacct_extract_header(mp, header) != 0) { kmem_free(header, FLOWACCT_HEADER_SZ); atomic_add_64(&flowacct_data->epackets, 1); return (EINVAL); } /* Updated the flow table with this entry */ if (flowacct_update_flows_tbl(header, flowacct_data) != 0) { kmem_free(header, FLOWACCT_HEADER_SZ); atomic_add_64(&flowacct_data->epackets, 1); return (ENOMEM); } /* Update global stats */ atomic_add_64(&flowacct_data->npackets, 1); atomic_add_64(&flowacct_data->nbytes, header->pktlen); kmem_free(header, FLOWACCT_HEADER_SZ); if (flowacct_data->flow_tid == 0) { flowacct_data->flow_tid = timeout(flowacct_timeout_flows, flowacct_data, drv_usectohz(flowacct_data->timer)); } return (0); }