Index: head/sys/netgraph/netflow/netflow_v9.c =================================================================== --- head/sys/netgraph/netflow/netflow_v9.c (revision 241445) +++ head/sys/netgraph/netflow/netflow_v9.c (revision 241446) @@ -1,482 +1,493 @@ /*- * Copyright (c) 2010 Alexander V. Chernikov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ static const char rcs_id[] = "@(#) $FreeBSD$"; #include "opt_inet6.h" #include "opt_route.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DECLARE(M_NETFLOW_GENERAL); MALLOC_DEFINE(M_NETFLOW_GENERAL, "netflow_general", "plog, V9 templates data"); /* * Base V9 templates for L4+ IPv4/IPv6 protocols */ struct netflow_v9_template _netflow_v9_record_ipv4_tcp[] = { { NETFLOW_V9_FIELD_IPV4_SRC_ADDR, 4}, { NETFLOW_V9_FIELD_IPV4_DST_ADDR, 4}, { NETFLOW_V9_FIELD_IPV4_NEXT_HOP, 4}, { NETFLOW_V9_FIELD_INPUT_SNMP, 2}, { NETFLOW_V9_FIELD_OUTPUT_SNMP, 2}, { NETFLOW_V9_FIELD_IN_PKTS, sizeof(CNTR)}, { NETFLOW_V9_FIELD_IN_BYTES, sizeof(CNTR)}, { NETFLOW_V9_FIELD_OUT_PKTS, sizeof(CNTR)}, { NETFLOW_V9_FIELD_OUT_BYTES, sizeof(CNTR)}, { NETFLOW_V9_FIELD_FIRST_SWITCHED, 4}, { NETFLOW_V9_FIELD_LAST_SWITCHED, 4}, { NETFLOW_V9_FIELD_L4_SRC_PORT, 2}, { NETFLOW_V9_FIELD_L4_DST_PORT, 2}, { NETFLOW_V9_FIELD_TCP_FLAGS, 1}, { NETFLOW_V9_FIELD_PROTOCOL, 1}, { NETFLOW_V9_FIELD_TOS, 1}, { NETFLOW_V9_FIELD_SRC_AS, 4}, { NETFLOW_V9_FIELD_DST_AS, 4}, { NETFLOW_V9_FIELD_SRC_MASK, 1}, { NETFLOW_V9_FIELD_DST_MASK, 1}, {0, 0} }; struct netflow_v9_template _netflow_v9_record_ipv6_tcp[] = { { NETFLOW_V9_FIELD_IPV6_SRC_ADDR, 16}, { NETFLOW_V9_FIELD_IPV6_DST_ADDR, 16}, { NETFLOW_V9_FIELD_IPV6_NEXT_HOP, 16}, { NETFLOW_V9_FIELD_INPUT_SNMP, 2}, { NETFLOW_V9_FIELD_OUTPUT_SNMP, 2}, { NETFLOW_V9_FIELD_IN_PKTS, sizeof(CNTR)}, { NETFLOW_V9_FIELD_IN_BYTES, sizeof(CNTR)}, { NETFLOW_V9_FIELD_OUT_PKTS, sizeof(CNTR)}, { NETFLOW_V9_FIELD_OUT_BYTES, sizeof(CNTR)}, { NETFLOW_V9_FIELD_FIRST_SWITCHED, 4}, { NETFLOW_V9_FIELD_LAST_SWITCHED, 4}, { NETFLOW_V9_FIELD_L4_SRC_PORT, 2}, { NETFLOW_V9_FIELD_L4_DST_PORT, 2}, { NETFLOW_V9_FIELD_TCP_FLAGS, 1}, { NETFLOW_V9_FIELD_PROTOCOL, 1}, { NETFLOW_V9_FIELD_TOS, 1}, { NETFLOW_V9_FIELD_SRC_AS, 4}, { NETFLOW_V9_FIELD_DST_AS, 4}, { NETFLOW_V9_FIELD_SRC_MASK, 1}, { NETFLOW_V9_FIELD_DST_MASK, 1}, {0, 0} }; /* * Pre-compiles flow exporter for all possible FlowSets * so we can add flowset to packet via simple memcpy() */ static void generate_v9_templates(priv_p priv) { uint16_t *p, *template_fields_cnt; int cnt; int flowset_size = sizeof(struct netflow_v9_flowset_header) + _NETFLOW_V9_TEMPLATE_SIZE(_netflow_v9_record_ipv4_tcp) + /* netflow_v9_record_ipv4_tcp */ _NETFLOW_V9_TEMPLATE_SIZE(_netflow_v9_record_ipv6_tcp); /* netflow_v9_record_ipv6_tcp */ priv->v9_flowsets[0] = malloc(flowset_size, M_NETFLOW_GENERAL, M_WAITOK | M_ZERO); if (flowset_size % 4) flowset_size += 4 - (flowset_size % 4); /* Padding to 4-byte boundary */ priv->flowsets_count = 1; p = (uint16_t *)priv->v9_flowsets[0]; *p++ = 0; /* Flowset ID, 0 is reserved for Template FlowSets */ *p++ = htons(flowset_size); /* Total FlowSet length */ /* * Most common TCP/UDP IPv4 template, ID = 256 */ *p++ = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + NETFLOW_V9_FLOW_V4_L4); template_fields_cnt = p++; for (cnt = 0; _netflow_v9_record_ipv4_tcp[cnt].field_id != 0; cnt++) { *p++ = htons(_netflow_v9_record_ipv4_tcp[cnt].field_id); *p++ = htons(_netflow_v9_record_ipv4_tcp[cnt].field_length); } *template_fields_cnt = htons(cnt); /* * TCP/UDP IPv6 template, ID = 257 */ *p++ = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + NETFLOW_V9_FLOW_V6_L4); template_fields_cnt = p++; for (cnt = 0; _netflow_v9_record_ipv6_tcp[cnt].field_id != 0; cnt++) { *p++ = htons(_netflow_v9_record_ipv6_tcp[cnt].field_id); *p++ = htons(_netflow_v9_record_ipv6_tcp[cnt].field_length); } *template_fields_cnt = htons(cnt); priv->flowset_records[0] = 2; } /* Closes current data flowset */ static void inline close_flowset(struct mbuf *m, struct netflow_v9_packet_opt *t) { struct mbuf *m_old; uint32_t zero = 0; int offset = 0; uint16_t *flowset_length, len; /* Hack to ensure we are not crossing mbuf boundary, length is uint16_t */ m_old = m_getptr(m, t->flow_header + offsetof(struct netflow_v9_flowset_header, length), &offset); flowset_length = (uint16_t *)(mtod(m_old, char *) + offset); len = (uint16_t)(m_pktlen(m) - t->flow_header); /* Align on 4-byte boundary (RFC 3954, Clause 5.3) */ if (len % 4) { if (m_append(m, 4 - (len % 4), (void *)&zero) != 1) panic("ng_netflow: m_append() failed!"); len += 4 - (len % 4); } *flowset_length = htons(len); } /* * Non-static functions called from ng_netflow.c */ /* We have full datagram in fib data. Send it to export hook. */ int export9_send(priv_p priv, fib_export_p fe, item_p item, struct netflow_v9_packet_opt *t, int flags) { struct mbuf *m = NGI_M(item); struct netflow_v9_export_dgram *dgram = mtod(m, struct netflow_v9_export_dgram *); struct netflow_v9_header *header = &dgram->header; struct timespec ts; int error = 0; if (t == NULL) { CTR0(KTR_NET, "export9_send(): V9 export packet without tag"); NG_FREE_ITEM(item); return (0); } /* Close flowset if not closed already */ if (m_pktlen(m) != t->flow_header) close_flowset(m, t); /* Fill export header. */ header->count = t->count; header->sys_uptime = htonl(MILLIUPTIME(time_uptime)); getnanotime(&ts); header->unix_secs = htonl(ts.tv_sec); header->seq_num = htonl(atomic_fetchadd_32(&fe->flow9_seq, 1)); header->count = htons(t->count); header->source_id = htonl(fe->domain_id); if (priv->export9 != NULL) NG_FWD_ITEM_HOOK_FLAGS(error, item, priv->export9, flags); else NG_FREE_ITEM(item); free(t, M_NETFLOW_GENERAL); return (error); } /* Add V9 record to dgram. */ int export9_add(item_p item, struct netflow_v9_packet_opt *t, struct flow_entry *fle) { size_t len = 0; struct netflow_v9_flowset_header fsh; struct netflow_v9_record_general rg; struct mbuf *m = NGI_M(item); uint16_t flow_type; struct flow_entry_data *fed; #ifdef INET6 struct flow6_entry_data *fed6; #endif if (t == NULL) { CTR0(KTR_NET, "ng_netflow: V9 export packet without tag!"); return (0); } /* Prepare flow record */ fed = (struct flow_entry_data *)&fle->f; #ifdef INET6 fed6 = (struct flow6_entry_data *)&fle->f; #endif /* We can use flow_type field since fle6 offset is equal to fle */ flow_type = fed->r.flow_type; switch (flow_type) { case NETFLOW_V9_FLOW_V4_L4: { /* IPv4 TCP/UDP/[SCTP] */ struct netflow_v9_record_ipv4_tcp *rec = &rg.rec.v4_tcp; rec->src_addr = fed->r.r_src.s_addr; rec->dst_addr = fed->r.r_dst.s_addr; rec->next_hop = fed->next_hop.s_addr; rec->i_ifx = htons(fed->fle_i_ifx); rec->o_ifx = htons(fed->fle_o_ifx); rec->i_packets = htonl(fed->packets); rec->i_octets = htonl(fed->bytes); rec->o_packets = htonl(0); rec->o_octets = htonl(0); rec->first = htonl(MILLIUPTIME(fed->first)); rec->last = htonl(MILLIUPTIME(fed->last)); rec->s_port = fed->r.r_sport; rec->d_port = fed->r.r_dport; rec->flags = fed->tcp_flags; rec->prot = fed->r.r_ip_p; rec->tos = fed->r.r_tos; rec->dst_mask = fed->dst_mask; rec->src_mask = fed->src_mask; /* Not supported fields. */ rec->src_as = rec->dst_as = 0; len = sizeof(struct netflow_v9_record_ipv4_tcp); break; } #ifdef INET6 case NETFLOW_V9_FLOW_V6_L4: { /* IPv6 TCP/UDP/[SCTP] */ struct netflow_v9_record_ipv6_tcp *rec = &rg.rec.v6_tcp; rec->src_addr = fed6->r.src.r_src6; rec->dst_addr = fed6->r.dst.r_dst6; rec->next_hop = fed6->n.next_hop6; rec->i_ifx = htons(fed6->fle_i_ifx); rec->o_ifx = htons(fed6->fle_o_ifx); rec->i_packets = htonl(fed6->packets); rec->i_octets = htonl(fed6->bytes); rec->o_packets = htonl(0); rec->o_octets = htonl(0); rec->first = htonl(MILLIUPTIME(fed6->first)); rec->last = htonl(MILLIUPTIME(fed6->last)); rec->s_port = fed6->r.r_sport; rec->d_port = fed6->r.r_dport; rec->flags = fed6->tcp_flags; rec->prot = fed6->r.r_ip_p; rec->tos = fed6->r.r_tos; rec->dst_mask = fed6->dst_mask; rec->src_mask = fed6->src_mask; /* Not supported fields. */ rec->src_as = rec->dst_as = 0; len = sizeof(struct netflow_v9_record_ipv6_tcp); break; } #endif default: { CTR1(KTR_NET, "export9_add(): Don't know what to do with %d flow type!", flow_type); return (0); } } /* Check if new records has the same template */ if (flow_type != t->flow_type) { /* close old flowset */ if (t->flow_type != 0) close_flowset(m, t); t->flow_type = flow_type; t->flow_header = m_pktlen(m); /* Generate data flowset ID */ fsh.id = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + flow_type); fsh.length = 0; /* m_append should not fail since all data is already allocated */ if (m_append(m, sizeof(fsh), (void *)&fsh) != 1) panic("ng_netflow: m_append() failed"); } if (m_append(m, len, (void *)&rg.rec) != 1) panic("ng_netflow: m_append() failed"); t->count++; if (m_pktlen(m) + sizeof(struct netflow_v9_record_general) + sizeof(struct netflow_v9_flowset_header) >= _NETFLOW_V9_MAX_SIZE(t->mtu)) return (1); /* end of datagram */ return (0); } /* * Detach export datagram from fib instance, if there is any. * If there is no, allocate a new one. */ item_p get_export9_dgram(priv_p priv, fib_export_p fe, struct netflow_v9_packet_opt **tt) { item_p item = NULL; struct netflow_v9_packet_opt *t = NULL; mtx_lock(&fe->export9_mtx); if (fe->exp.item9 != NULL) { item = fe->exp.item9; fe->exp.item9 = NULL; t = fe->exp.item9_opt; fe->exp.item9_opt = NULL; } mtx_unlock(&fe->export9_mtx); if (item == NULL) { struct netflow_v9_export_dgram *dgram; struct mbuf *m; uint16_t mtu = priv->mtu; /* Allocate entire packet at once, allowing easy m_append() calls */ m = m_getm(NULL, mtu, M_DONTWAIT, MT_DATA); if (m == NULL) return (NULL); t = malloc(sizeof(struct netflow_v9_packet_opt), M_NETFLOW_GENERAL, M_NOWAIT | M_ZERO); if (t == NULL) { m_free(m); return (NULL); } item = ng_package_data(m, NG_NOFLAGS); if (item == NULL) { free(t, M_NETFLOW_GENERAL); return (NULL); } dgram = mtod(m, struct netflow_v9_export_dgram *); dgram->header.count = 0; dgram->header.version = htons(NETFLOW_V9); /* Set mbuf current data length */ m->m_len = m->m_pkthdr.len = sizeof(struct netflow_v9_header); t->count = 0; t->mtu = mtu; t->flow_header = m->m_len; /* * Check if we need to insert templates into packet */ struct netflow_v9_flowset_header *fl; if ((time_uptime >= priv->templ_time + fe->templ_last_ts) || (fe->sent_packets >= priv->templ_packets + fe->templ_last_pkt)) { fe->templ_last_ts = time_uptime; fe->templ_last_pkt = fe->sent_packets; fl = priv->v9_flowsets[0]; m_append(m, ntohs(fl->length), (void *)fl); t->flow_header = m->m_len; t->count += priv->flowset_records[0]; } } *tt = t; return (item); } /* * Re-attach incomplete datagram back to fib instance. * If there is already another one, then send incomplete. */ void return_export9_dgram(priv_p priv, fib_export_p fe, item_p item, struct netflow_v9_packet_opt *t, int flags) { /* * It may happen on SMP, that some thread has already * put its item there, in this case we bail out and * send what we have to collector. */ mtx_lock(&fe->export9_mtx); if (fe->exp.item9 == NULL) { fe->exp.item9 = item; fe->exp.item9_opt = t; mtx_unlock(&fe->export9_mtx); } else { mtx_unlock(&fe->export9_mtx); export9_send(priv, fe, item, t, flags); } } /* Allocate memory and set up flow cache */ void ng_netflow_v9_cache_init(priv_p priv) { generate_v9_templates(priv); priv->templ_time = NETFLOW_V9_MAX_TIME_TEMPL; priv->templ_packets = NETFLOW_V9_MAX_PACKETS_TEMPL; priv->mtu = BASE_MTU; } /* Free all flow cache memory. Called from ng_netflow_cache_flush() */ void ng_netflow_v9_cache_flush(priv_p priv) { int i; /* Free flowsets*/ for (i = 0; i < priv->flowsets_count; i++) free(priv->v9_flowsets[i], M_NETFLOW_GENERAL); } + +/* Get a snapshot of NetFlow v9 settings */ +void +ng_netflow_copyv9info(priv_p priv, struct ng_netflow_v9info *i) +{ + + i->templ_time = priv->templ_time; + i->templ_packets = priv->templ_packets; + i->mtu = priv->mtu; +} + Index: head/sys/netgraph/netflow/ng_netflow.c =================================================================== --- head/sys/netgraph/netflow/ng_netflow.c (revision 241445) +++ head/sys/netgraph/netflow/ng_netflow.c (revision 241446) @@ -1,987 +1,1013 @@ /*- * Copyright (c) 2010-2011 Alexander V. Chernikov * Copyright (c) 2004-2005 Gleb Smirnoff * Copyright (c) 2001-2003 Roman V. Palagin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $ */ static const char rcs_id[] = "@(#) $FreeBSD$"; #include "opt_inet6.h" #include "opt_route.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Netgraph methods */ static ng_constructor_t ng_netflow_constructor; static ng_rcvmsg_t ng_netflow_rcvmsg; static ng_close_t ng_netflow_close; static ng_shutdown_t ng_netflow_rmnode; static ng_newhook_t ng_netflow_newhook; static ng_rcvdata_t ng_netflow_rcvdata; static ng_disconnect_t ng_netflow_disconnect; /* Parse type for struct ng_netflow_info */ static const struct ng_parse_struct_field ng_netflow_info_type_fields[] = NG_NETFLOW_INFO_TYPE; static const struct ng_parse_type ng_netflow_info_type = { &ng_parse_struct_type, &ng_netflow_info_type_fields }; /* Parse type for struct ng_netflow_ifinfo */ static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[] = NG_NETFLOW_IFINFO_TYPE; static const struct ng_parse_type ng_netflow_ifinfo_type = { &ng_parse_struct_type, &ng_netflow_ifinfo_type_fields }; /* Parse type for struct ng_netflow_setdlt */ static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[] = NG_NETFLOW_SETDLT_TYPE; static const struct ng_parse_type ng_netflow_setdlt_type = { &ng_parse_struct_type, &ng_netflow_setdlt_type_fields }; /* Parse type for ng_netflow_setifindex */ static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[] = NG_NETFLOW_SETIFINDEX_TYPE; static const struct ng_parse_type ng_netflow_setifindex_type = { &ng_parse_struct_type, &ng_netflow_setifindex_type_fields }; /* Parse type for ng_netflow_settimeouts */ static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[] = NG_NETFLOW_SETTIMEOUTS_TYPE; static const struct ng_parse_type ng_netflow_settimeouts_type = { &ng_parse_struct_type, &ng_netflow_settimeouts_type_fields }; /* Parse type for ng_netflow_setconfig */ static const struct ng_parse_struct_field ng_netflow_setconfig_type_fields[] = NG_NETFLOW_SETCONFIG_TYPE; static const struct ng_parse_type ng_netflow_setconfig_type = { &ng_parse_struct_type, &ng_netflow_setconfig_type_fields }; /* Parse type for ng_netflow_settemplate */ static const struct ng_parse_struct_field ng_netflow_settemplate_type_fields[] = NG_NETFLOW_SETTEMPLATE_TYPE; static const struct ng_parse_type ng_netflow_settemplate_type = { &ng_parse_struct_type, &ng_netflow_settemplate_type_fields }; /* Parse type for ng_netflow_setmtu */ static const struct ng_parse_struct_field ng_netflow_setmtu_type_fields[] = NG_NETFLOW_SETMTU_TYPE; static const struct ng_parse_type ng_netflow_setmtu_type = { &ng_parse_struct_type, &ng_netflow_setmtu_type_fields }; +/* Parse type for struct ng_netflow_v9info */ +static const struct ng_parse_struct_field ng_netflow_v9info_type_fields[] + = NG_NETFLOW_V9INFO_TYPE; +static const struct ng_parse_type ng_netflow_v9info_type = { + &ng_parse_struct_type, + &ng_netflow_v9info_type_fields +}; + /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_netflow_cmds[] = { { NGM_NETFLOW_COOKIE, NGM_NETFLOW_INFO, "info", NULL, &ng_netflow_info_type }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_IFINFO, "ifinfo", &ng_parse_uint16_type, &ng_netflow_ifinfo_type }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETDLT, "setdlt", &ng_netflow_setdlt_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETIFINDEX, "setifindex", &ng_netflow_setifindex_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETTIMEOUTS, "settimeouts", &ng_netflow_settimeouts_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETCONFIG, "setconfig", &ng_netflow_setconfig_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETTEMPLATE, "settemplate", &ng_netflow_settemplate_type, NULL }, { NGM_NETFLOW_COOKIE, NGM_NETFLOW_SETMTU, "setmtu", &ng_netflow_setmtu_type, NULL }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_V9INFO, + "v9info", + NULL, + &ng_netflow_v9info_type + }, { 0 } }; /* Netgraph node type descriptor */ static struct ng_type ng_netflow_typestruct = { .version = NG_ABI_VERSION, .name = NG_NETFLOW_NODE_TYPE, .constructor = ng_netflow_constructor, .rcvmsg = ng_netflow_rcvmsg, .close = ng_netflow_close, .shutdown = ng_netflow_rmnode, .newhook = ng_netflow_newhook, .rcvdata = ng_netflow_rcvdata, .disconnect = ng_netflow_disconnect, .cmdlist = ng_netflow_cmds, }; NETGRAPH_INIT(netflow, &ng_netflow_typestruct); /* Called at node creation */ static int ng_netflow_constructor(node_p node) { priv_p priv; int i; /* Initialize private data */ priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); /* Initialize fib data */ priv->maxfibs = rt_numfibs; priv->fib_data = malloc(sizeof(fib_export_p) * priv->maxfibs, M_NETGRAPH, M_WAITOK | M_ZERO); /* Make node and its data point at each other */ NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Initialize timeouts to default values */ priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT; priv->info.nfinfo_act_t = ACTIVE_TIMEOUT; /* Set default config */ for (i = 0; i < NG_NETFLOW_MAXIFACES; i++) priv->ifaces[i].info.conf = NG_NETFLOW_CONF_INGRESS; /* Initialize callout handle */ callout_init(&priv->exp_callout, CALLOUT_MPSAFE); /* Allocate memory and set up flow cache */ ng_netflow_cache_init(priv); return (0); } /* * ng_netflow supports two hooks: data and export. * Incoming traffic is expected on data, and expired * netflow datagrams are sent to export. */ static int ng_netflow_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */ strlen(NG_NETFLOW_HOOK_DATA)) == 0) { iface_p iface; int ifnum = -1; const char *cp; char *eptr; cp = name + strlen(NG_NETFLOW_HOOK_DATA); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); ifnum = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) return (EINVAL); /* See if hook is already connected */ if (priv->ifaces[ifnum].hook != NULL) return (EISCONN); iface = &priv->ifaces[ifnum]; /* Link private info and hook together */ NG_HOOK_SET_PRIVATE(hook, iface); iface->hook = hook; /* * In most cases traffic accounting is done on an * Ethernet interface, so default data link type * will be DLT_EN10MB. */ iface->info.ifinfo_dlt = DLT_EN10MB; } else if (strncmp(name, NG_NETFLOW_HOOK_OUT, strlen(NG_NETFLOW_HOOK_OUT)) == 0) { iface_p iface; int ifnum = -1; const char *cp; char *eptr; cp = name + strlen(NG_NETFLOW_HOOK_OUT); if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) return (EINVAL); ifnum = (int)strtoul(cp, &eptr, 10); if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) return (EINVAL); /* See if hook is already connected */ if (priv->ifaces[ifnum].out != NULL) return (EISCONN); iface = &priv->ifaces[ifnum]; /* Link private info and hook together */ NG_HOOK_SET_PRIVATE(hook, iface); iface->out = hook; } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { if (priv->export != NULL) return (EISCONN); /* Netflow version 5 supports 32-bit counters only */ if (CNTR_MAX == UINT64_MAX) return (EINVAL); priv->export = hook; /* Exporter is ready. Let's schedule expiry. */ callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, (void *)priv); } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT9) == 0) { if (priv->export9 != NULL) return (EISCONN); priv->export9 = hook; /* Exporter is ready. Let's schedule expiry. */ callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, (void *)priv); } else return (EINVAL); return (0); } /* Get a netgraph control message. */ static int ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command */ switch (msg->header.typecookie) { case NGM_NETFLOW_COOKIE: switch (msg->header.cmd) { case NGM_NETFLOW_INFO: { struct ng_netflow_info *i; NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), M_NOWAIT); i = (struct ng_netflow_info *)resp->data; ng_netflow_copyinfo(priv, i); break; } case NGM_NETFLOW_IFINFO: { struct ng_netflow_ifinfo *i; const uint16_t *index; if (msg->header.arglen != sizeof(uint16_t)) ERROUT(EINVAL); index = (uint16_t *)msg->data; if (*index >= NG_NETFLOW_MAXIFACES) ERROUT(EINVAL); /* connected iface? */ if (priv->ifaces[*index].hook == NULL) ERROUT(EINVAL); NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_ifinfo), M_NOWAIT); i = (struct ng_netflow_ifinfo *)resp->data; memcpy((void *)i, (void *)&priv->ifaces[*index].info, sizeof(priv->ifaces[*index].info)); break; } case NGM_NETFLOW_SETDLT: { struct ng_netflow_setdlt *set; struct ng_netflow_iface *iface; if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) ERROUT(EINVAL); set = (struct ng_netflow_setdlt *)msg->data; if (set->iface >= NG_NETFLOW_MAXIFACES) ERROUT(EINVAL); iface = &priv->ifaces[set->iface]; /* connected iface? */ if (iface->hook == NULL) ERROUT(EINVAL); switch (set->dlt) { case DLT_EN10MB: iface->info.ifinfo_dlt = DLT_EN10MB; break; case DLT_RAW: iface->info.ifinfo_dlt = DLT_RAW; break; default: ERROUT(EINVAL); } break; } case NGM_NETFLOW_SETIFINDEX: { struct ng_netflow_setifindex *set; struct ng_netflow_iface *iface; if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) ERROUT(EINVAL); set = (struct ng_netflow_setifindex *)msg->data; if (set->iface >= NG_NETFLOW_MAXIFACES) ERROUT(EINVAL); iface = &priv->ifaces[set->iface]; /* connected iface? */ if (iface->hook == NULL) ERROUT(EINVAL); iface->info.ifinfo_index = set->index; break; } case NGM_NETFLOW_SETTIMEOUTS: { struct ng_netflow_settimeouts *set; if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) ERROUT(EINVAL); set = (struct ng_netflow_settimeouts *)msg->data; priv->info.nfinfo_inact_t = set->inactive_timeout; priv->info.nfinfo_act_t = set->active_timeout; break; } case NGM_NETFLOW_SETCONFIG: { struct ng_netflow_setconfig *set; if (msg->header.arglen != sizeof(struct ng_netflow_setconfig)) ERROUT(EINVAL); set = (struct ng_netflow_setconfig *)msg->data; if (set->iface >= NG_NETFLOW_MAXIFACES) ERROUT(EINVAL); priv->ifaces[set->iface].info.conf = set->conf; break; } case NGM_NETFLOW_SETTEMPLATE: { struct ng_netflow_settemplate *set; if (msg->header.arglen != sizeof(struct ng_netflow_settemplate)) ERROUT(EINVAL); set = (struct ng_netflow_settemplate *)msg->data; priv->templ_packets = set->packets; priv->templ_time = set->time; break; } case NGM_NETFLOW_SETMTU: { struct ng_netflow_setmtu *set; if (msg->header.arglen != sizeof(struct ng_netflow_setmtu)) ERROUT(EINVAL); set = (struct ng_netflow_setmtu *)msg->data; if ((set->mtu < MIN_MTU) || (set->mtu > MAX_MTU)) ERROUT(EINVAL); priv->mtu = set->mtu; break; } case NGM_NETFLOW_SHOW: { if (msg->header.arglen != sizeof(struct ngnf_show_header)) ERROUT(EINVAL); NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT); if (!resp) ERROUT(ENOMEM); error = ng_netflow_flow_show(priv, (struct ngnf_show_header *)msg->data, (struct ngnf_show_header *)resp->data); if (error) NG_FREE_MSG(resp); + + break; + } + case NGM_NETFLOW_V9INFO: + { + struct ng_netflow_v9info *i; + + NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_v9info), + M_NOWAIT); + i = (struct ng_netflow_v9info *)resp->data; + ng_netflow_copyv9info(priv, i); break; } default: ERROUT(EINVAL); /* unknown command */ break; } break; default: ERROUT(EINVAL); /* incorrect cookie */ break; } /* * Take care of synchronous response, if any. * Free memory and return. */ done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* Receive data on hook. */ static int ng_netflow_rcvdata (hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); const iface_p iface = NG_HOOK_PRIVATE(hook); hook_p out; struct mbuf *m = NULL, *m_old = NULL; struct ip *ip = NULL; struct ip6_hdr *ip6 = NULL; struct m_tag *mtag; int pullup_len = 0, off; uint8_t acct = 0, bypass = 0, flags = 0, upper_proto = 0; int error = 0, l3_off = 0; unsigned int src_if_index; caddr_t upper_ptr = NULL; fib_export_p fe; uint32_t fib; if ((hook == priv->export) || (hook == priv->export9)) { /* * Data arrived on export hook. * This must not happen. */ log(LOG_ERR, "ng_netflow: incoming data on export hook!\n"); ERROUT(EINVAL); }; if (hook == iface->hook) { if ((iface->info.conf & NG_NETFLOW_CONF_INGRESS) == 0) bypass = 1; out = iface->out; } else if (hook == iface->out) { if ((iface->info.conf & NG_NETFLOW_CONF_EGRESS) == 0) bypass = 1; out = iface->hook; } else ERROUT(EINVAL); if ((!bypass) && (iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE))) { mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW, MTAG_NETFLOW_CALLED, NULL); while (mtag != NULL) { if ((iface->info.conf & NG_NETFLOW_CONF_ONCE) || ((ng_ID_t *)(mtag + 1))[0] == NG_NODE_ID(node)) { bypass = 1; break; } mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW, MTAG_NETFLOW_CALLED, mtag); } } if (bypass) { if (out == NULL) ERROUT(ENOTCONN); NG_FWD_ITEM_HOOK(error, item, out); return (error); } if (iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE)) { mtag = m_tag_alloc(MTAG_NETFLOW, MTAG_NETFLOW_CALLED, sizeof(ng_ID_t), M_NOWAIT); if (mtag) { ((ng_ID_t *)(mtag + 1))[0] = NG_NODE_ID(node); m_tag_prepend(NGI_M(item), mtag); } } /* Import configuration flags related to flow creation */ flags = iface->info.conf & NG_NETFLOW_FLOW_FLAGS; NGI_GET_M(item, m); m_old = m; /* Increase counters. */ iface->info.ifinfo_packets++; /* * Depending on interface data link type and packet contents * we pullup enough data, so that ng_netflow_flow_add() does not * need to know about mbuf at all. We keep current length of data * needed to be contiguous in pullup_len. mtod() is done at the * very end one more time, since m can had changed after pulluping. * * In case of unrecognized data we don't return error, but just * pass data to downstream hook, if it is available. */ #define M_CHECK(length) do { \ pullup_len += length; \ if (((m)->m_pkthdr.len < (pullup_len)) || \ ((pullup_len) > MHLEN)) { \ error = EINVAL; \ goto bypass; \ } \ if ((m)->m_len < (pullup_len) && \ (((m) = m_pullup((m),(pullup_len))) == NULL)) { \ error = ENOBUFS; \ goto done; \ } \ } while (0) switch (iface->info.ifinfo_dlt) { case DLT_EN10MB: /* Ethernet */ { struct ether_header *eh; uint16_t etype; M_CHECK(sizeof(struct ether_header)); eh = mtod(m, struct ether_header *); /* Make sure this is IP frame. */ etype = ntohs(eh->ether_type); switch (etype) { case ETHERTYPE_IP: M_CHECK(sizeof(struct ip)); eh = mtod(m, struct ether_header *); ip = (struct ip *)(eh + 1); l3_off = sizeof(struct ether_header); break; #ifdef INET6 case ETHERTYPE_IPV6: /* * m_pullup() called by M_CHECK() pullups * kern.ipc.max_protohdr (default 60 bytes) which is enough */ M_CHECK(sizeof(struct ip6_hdr)); eh = mtod(m, struct ether_header *); ip6 = (struct ip6_hdr *)(eh + 1); l3_off = sizeof(struct ether_header); break; #endif case ETHERTYPE_VLAN: { struct ether_vlan_header *evh; M_CHECK(sizeof(struct ether_vlan_header) - sizeof(struct ether_header)); evh = mtod(m, struct ether_vlan_header *); etype = ntohs(evh->evl_proto); l3_off = sizeof(struct ether_vlan_header); if (etype == ETHERTYPE_IP) { M_CHECK(sizeof(struct ip)); ip = (struct ip *)(evh + 1); break; #ifdef INET6 } else if (etype == ETHERTYPE_IPV6) { M_CHECK(sizeof(struct ip6_hdr)); ip6 = (struct ip6_hdr *)(evh + 1); break; #endif } } default: goto bypass; /* pass this frame */ } break; } case DLT_RAW: /* IP packets */ M_CHECK(sizeof(struct ip)); ip = mtod(m, struct ip *); /* l3_off is already zero */ #ifdef INET6 /* If INET6 is not defined IPv6 packets will be discarded in ng_netflow_flow_add() */ if (ip->ip_v == IP6VERSION) { /* IPv6 packet */ ip = NULL; M_CHECK(sizeof(struct ip6_hdr) - sizeof(struct ip)); ip6 = mtod(m, struct ip6_hdr *); } #endif break; default: goto bypass; break; } off = pullup_len; if ((ip != NULL) && ((ip->ip_off & htons(IP_OFFMASK)) == 0)) { if ((ip->ip_v != IPVERSION) || ((ip->ip_hl << 2) < sizeof(struct ip))) goto bypass; /* * In case of IPv4 header with options, we haven't pulled * up enough, yet. */ M_CHECK((ip->ip_hl << 2) - sizeof(struct ip)); /* Save upper layer offset and proto */ off = pullup_len; upper_proto = ip->ip_p; /* * XXX: in case of wrong upper layer header we will forward this packet * but skip this record in netflow */ switch (ip->ip_p) { case IPPROTO_TCP: M_CHECK(sizeof(struct tcphdr)); break; case IPPROTO_UDP: M_CHECK(sizeof(struct udphdr)); break; case IPPROTO_SCTP: M_CHECK(sizeof(struct sctphdr)); break; } } else if (ip != NULL) { /* Nothing to save except upper layer proto, since this is packet fragment */ flags |= NG_NETFLOW_IS_FRAG; upper_proto = ip->ip_p; if ((ip->ip_v != IPVERSION) || ((ip->ip_hl << 2) < sizeof(struct ip))) goto bypass; #ifdef INET6 } else if (ip6 != NULL) { /* Check if we can export */ if (priv->export9 == NULL) goto bypass; /* Loop thru IPv6 extended headers to get upper layer header / frag */ int cur = ip6->ip6_nxt, hdr_off = 0; struct ip6_ext *ip6e; struct ip6_frag *ip6f; /* Save upper layer info */ off = pullup_len; upper_proto = cur; if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) goto bypass; while (42) { switch (cur) { /* * Same as in IPv4, we can forward 'bad' packet without accounting */ case IPPROTO_TCP: M_CHECK(sizeof(struct tcphdr)); goto loopend; case IPPROTO_UDP: M_CHECK(sizeof(struct udphdr)); goto loopend; case IPPROTO_SCTP: M_CHECK(sizeof(struct sctphdr)); goto loopend; /* Loop until 'real' upper layer headers */ case IPPROTO_HOPOPTS: case IPPROTO_ROUTING: case IPPROTO_DSTOPTS: M_CHECK(sizeof(struct ip6_ext)); ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + off); upper_proto = ip6e->ip6e_nxt; hdr_off = (ip6e->ip6e_len + 1) << 3; break; /* RFC4302, can be before DSTOPTS */ case IPPROTO_AH: M_CHECK(sizeof(struct ip6_ext)); ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + off); upper_proto = ip6e->ip6e_nxt; hdr_off = (ip6e->ip6e_len + 2) << 2; break; case IPPROTO_FRAGMENT: M_CHECK(sizeof(struct ip6_frag)); ip6f = (struct ip6_frag *)(mtod(m, caddr_t) + off); upper_proto = ip6f->ip6f_nxt; hdr_off = sizeof(struct ip6_frag); off += hdr_off; flags |= NG_NETFLOW_IS_FRAG; goto loopend; #if 0 case IPPROTO_NONE: goto loopend; #endif /* * Any unknown header (new extension or IPv6/IPv4 * header for tunnels) ends loop. */ default: goto loopend; } off += hdr_off; cur = upper_proto; } #endif } #undef M_CHECK #ifdef INET6 loopend: #endif /* Just in case of real reallocation in M_CHECK() / m_pullup() */ if (m != m_old) { atomic_fetchadd_32(&priv->info.nfinfo_realloc_mbuf, 1); /* Restore ip/ipv6 pointer */ if (ip != NULL) ip = (struct ip *)(mtod(m, caddr_t) + l3_off); else if (ip6 != NULL) ip6 = (struct ip6_hdr *)(mtod(m, caddr_t) + l3_off); } upper_ptr = (caddr_t)(mtod(m, caddr_t) + off); /* Determine packet input interface. Prefer configured. */ src_if_index = 0; if (hook == iface->out || iface->info.ifinfo_index == 0) { if (m->m_pkthdr.rcvif != NULL) src_if_index = m->m_pkthdr.rcvif->if_index; } else src_if_index = iface->info.ifinfo_index; /* Check packet FIB */ fib = M_GETFIB(m); if (fib >= priv->maxfibs) { CTR2(KTR_NET, "ng_netflow_rcvdata(): packet fib %d is out of " "range of available fibs: 0 .. %d", fib, priv->maxfibs); goto bypass; } if ((fe = priv_to_fib(priv, fib)) == NULL) { /* Setup new FIB */ if (ng_netflow_fib_init(priv, fib) != 0) { /* malloc() failed */ goto bypass; } fe = priv_to_fib(priv, fib); } if (ip != NULL) error = ng_netflow_flow_add(priv, fe, ip, upper_ptr, upper_proto, flags, src_if_index); #ifdef INET6 else if (ip6 != NULL) error = ng_netflow_flow6_add(priv, fe, ip6, upper_ptr, upper_proto, flags, src_if_index); #endif else goto bypass; acct = 1; bypass: if (out != NULL) { if (acct == 0) { /* Accounting failure */ if (ip != NULL) { atomic_fetchadd_32(&priv->info.nfinfo_spackets, 1); priv->info.nfinfo_sbytes += m_length(m, NULL); } else if (ip6 != NULL) { atomic_fetchadd_32(&priv->info.nfinfo_spackets6, 1); priv->info.nfinfo_sbytes6 += m_length(m, NULL); } } /* XXX: error gets overwritten here */ NG_FWD_NEW_DATA(error, item, out, m); return (error); } done: if (item) NG_FREE_ITEM(item); if (m) NG_FREE_M(m); return (error); } /* We will be shut down in a moment */ static int ng_netflow_close(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); callout_drain(&priv->exp_callout); ng_netflow_cache_flush(priv); return (0); } /* Do local shutdown processing. */ static int ng_netflow_rmnode(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(priv->node); free(priv->fib_data, M_NETGRAPH); free(priv, M_NETGRAPH); return (0); } /* Hook disconnection. */ static int ng_netflow_disconnect(hook_p hook) { node_p node = NG_HOOK_NODE(hook); priv_p priv = NG_NODE_PRIVATE(node); iface_p iface = NG_HOOK_PRIVATE(hook); if (iface != NULL) { if (iface->hook == hook) iface->hook = NULL; if (iface->out == hook) iface->out = NULL; } /* if export hook disconnected stop running expire(). */ if (hook == priv->export) { if (priv->export9 == NULL) callout_drain(&priv->exp_callout); priv->export = NULL; } if (hook == priv->export9) { if (priv->export == NULL) callout_drain(&priv->exp_callout); priv->export9 = NULL; } /* Removal of the last link destroys the node. */ if (NG_NODE_NUMHOOKS(node) == 0) ng_rmnode_self(node); return (0); } Index: head/sys/netgraph/netflow/ng_netflow.h =================================================================== --- head/sys/netgraph/netflow/ng_netflow.h (revision 241445) +++ head/sys/netgraph/netflow/ng_netflow.h (revision 241446) @@ -1,490 +1,509 @@ /*- * Copyright (c) 2010-2011 Alexander V. Chernikov * Copyright (c) 2004-2005 Gleb Smirnoff * Copyright (c) 2001-2003 Roman V. Palagin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $SourceForge: ng_netflow.h,v 1.26 2004/09/04 15:44:55 glebius Exp $ * $FreeBSD$ */ #ifndef _NG_NETFLOW_H_ #define _NG_NETFLOW_H_ #define NG_NETFLOW_NODE_TYPE "netflow" #define NGM_NETFLOW_COOKIE 1309868867 +#define NGM_NETFLOW_V9_COOKIE 1349865386 #define NG_NETFLOW_MAXIFACES USHRT_MAX /* Hook names */ #define NG_NETFLOW_HOOK_DATA "iface" #define NG_NETFLOW_HOOK_OUT "out" #define NG_NETFLOW_HOOK_EXPORT "export" #define NG_NETFLOW_HOOK_EXPORT9 "export9" /* This define effectively disable (v5) netflow export hook! */ /* #define COUNTERS_64 */ /* Netgraph commands understood by netflow node */ enum { NGM_NETFLOW_INFO = 1|NGM_READONLY|NGM_HASREPLY, /* get node info */ NGM_NETFLOW_IFINFO = 2|NGM_READONLY|NGM_HASREPLY, /* get iface info */ NGM_NETFLOW_SHOW = 3|NGM_READONLY|NGM_HASREPLY, /* show ip cache flow */ NGM_NETFLOW_SETDLT = 4, /* set data-link type */ NGM_NETFLOW_SETIFINDEX = 5, /* set interface index */ NGM_NETFLOW_SETTIMEOUTS = 6, /* set active/inactive flow timeouts */ NGM_NETFLOW_SETCONFIG = 7, /* set flow generation options */ NGM_NETFLOW_SETTEMPLATE = 8, /* set v9 flow template periodic */ NGM_NETFLOW_SETMTU = 9, /* set outgoing interface MTU */ + NGM_NETFLOW_V9INFO = 10|NGM_READONLY|NGM_HASREPLY, /* get v9 info */ }; /* This structure is returned by the NGM_NETFLOW_INFO message */ struct ng_netflow_info { uint64_t nfinfo_bytes; /* accounted IPv4 bytes */ uint32_t nfinfo_packets; /* accounted IPv4 packets */ uint64_t nfinfo_bytes6; /* accounted IPv6 bytes */ uint32_t nfinfo_packets6; /* accounted IPv6 packets */ uint64_t nfinfo_sbytes; /* skipped IPv4 bytes */ uint32_t nfinfo_spackets; /* skipped IPv4 packets */ uint64_t nfinfo_sbytes6; /* skipped IPv6 bytes */ uint32_t nfinfo_spackets6; /* skipped IPv6 packets */ uint32_t nfinfo_used; /* used cache records */ uint32_t nfinfo_used6; /* used IPv6 cache records */ uint32_t nfinfo_alloc_failed; /* failed allocations */ uint32_t nfinfo_export_failed; /* failed exports */ uint32_t nfinfo_export9_failed; /* failed exports */ uint32_t nfinfo_realloc_mbuf; /* reallocated mbufs */ uint32_t nfinfo_alloc_fibs; /* fibs allocated */ uint32_t nfinfo_act_exp; /* active expiries */ uint32_t nfinfo_inact_exp; /* inactive expiries */ uint32_t nfinfo_inact_t; /* flow inactive timeout */ uint32_t nfinfo_act_t; /* flow active timeout */ }; /* This structure is returned by the NGM_NETFLOW_IFINFO message */ struct ng_netflow_ifinfo { uint32_t ifinfo_packets; /* number of packets for this iface */ uint8_t ifinfo_dlt; /* Data Link Type, DLT_XXX */ #define MAXDLTNAMELEN 20 u_int16_t ifinfo_index; /* connected iface index */ uint32_t conf; }; /* This structure is passed to NGM_NETFLOW_SETDLT message */ struct ng_netflow_setdlt { uint16_t iface; /* which iface dlt change */ uint8_t dlt; /* DLT_XXX from bpf.h */ }; /* This structure is passed to NGM_NETFLOW_SETIFINDEX */ struct ng_netflow_setifindex { u_int16_t iface; /* which iface index change */ u_int16_t index; /* new index */ }; /* This structure is passed to NGM_NETFLOW_SETTIMEOUTS */ struct ng_netflow_settimeouts { uint32_t inactive_timeout; /* flow inactive timeout */ uint32_t active_timeout; /* flow active timeout */ }; #define NG_NETFLOW_CONF_INGRESS 0x01 /* Account on ingress */ #define NG_NETFLOW_CONF_EGRESS 0x02 /* Account on egress */ #define NG_NETFLOW_CONF_ONCE 0x04 /* Add tag to account only once */ #define NG_NETFLOW_CONF_THISONCE 0x08 /* Account once in current node */ #define NG_NETFLOW_CONF_NOSRCLOOKUP 0x10 /* No radix lookup on src */ #define NG_NETFLOW_CONF_NODSTLOOKUP 0x20 /* No radix lookup on dst */ #define NG_NETFLOW_IS_FRAG 0x01 #define NG_NETFLOW_FLOW_FLAGS (NG_NETFLOW_CONF_NOSRCLOOKUP|\ NG_NETFLOW_CONF_NODSTLOOKUP) /* This structure is passed to NGM_NETFLOW_SETCONFIG */ struct ng_netflow_setconfig { u_int16_t iface; /* which iface config change */ u_int32_t conf; /* new config */ }; /* This structure is passed to NGM_NETFLOW_SETTEMPLATE */ struct ng_netflow_settemplate { uint16_t time; /* max time between announce */ uint16_t packets; /* max packets between announce */ }; /* This structure is passed to NGM_NETFLOW_SETMTU */ struct ng_netflow_setmtu { uint16_t mtu; /* MTU for packet */ }; /* This structure is used in NGM_NETFLOW_SHOW request/responce */ struct ngnf_show_header { u_char version; /* IPv4 or IPv6 */ uint32_t hash_id; /* current hash index */ uint32_t list_id; /* current record number in given hash */ uint32_t nentries; /* number of records in response */ }; +/* This structure is used in NGM_NETFLOW_V9INFO message */ +struct ng_netflow_v9info { + uint16_t templ_packets; /* v9 template packets */ + uint16_t templ_time; /* v9 template time */ + uint16_t mtu; /* v9 MTU */ +}; + /* XXXGL * Somewhere flow_rec6 is casted to flow_rec, and flow6_entry_data is * casted to flow_entry_data. After casting, fle->r.fib is accessed. * So beginning of these structs up to fib should be kept common. */ /* This is unique data, which identifies flow */ struct flow_rec { uint16_t flow_type; /* IPv4 L4/L3 flow, see NETFLOW_V9_FLOW* */ uint16_t fib; struct in_addr r_src; struct in_addr r_dst; union { struct { uint16_t s_port; /* source TCP/UDP port */ uint16_t d_port; /* destination TCP/UDP port */ } dir; uint32_t both; } ports; union { struct { u_char prot; /* IP protocol */ u_char tos; /* IP TOS */ uint16_t i_ifx; /* input interface index */ } i; uint32_t all; } misc; }; /* This is unique data, which identifies flow */ struct flow6_rec { uint16_t flow_type; /* IPv4 L4/L3 Ipv6 L4/L3 flow, see NETFLOW_V9_FLOW* */ uint16_t fib; union { struct in_addr r_src; struct in6_addr r_src6; } src; union { struct in_addr r_dst; struct in6_addr r_dst6; } dst; union { struct { uint16_t s_port; /* source TCP/UDP port */ uint16_t d_port; /* destination TCP/UDP port */ } dir; uint32_t both; } ports; union { struct { u_char prot; /* IP protocol */ u_char tos; /* IP TOS */ uint16_t i_ifx; /* input interface index */ } i; uint32_t all; } misc; }; #define r_ip_p misc.i.prot #define r_tos misc.i.tos #define r_i_ifx misc.i.i_ifx #define r_misc misc.all #define r_ports ports.both #define r_sport ports.dir.s_port #define r_dport ports.dir.d_port /* A flow entry which accumulates statistics */ struct flow_entry_data { uint16_t version; /* Protocol version */ struct flow_rec r; struct in_addr next_hop; uint16_t fle_o_ifx; /* output interface index */ #define fle_i_ifx r.misc.i.i_ifx uint8_t dst_mask; /* destination route mask bits */ uint8_t src_mask; /* source route mask bits */ u_long packets; u_long bytes; long first; /* uptime on first packet */ long last; /* uptime on last packet */ u_char tcp_flags; /* cumulative OR */ }; struct flow6_entry_data { uint16_t version; /* Protocol version */ struct flow6_rec r; union { struct in_addr next_hop; struct in6_addr next_hop6; } n; uint16_t fle_o_ifx; /* output interface index */ #define fle_i_ifx r.misc.i.i_ifx uint8_t dst_mask; /* destination route mask bits */ uint8_t src_mask; /* source route mask bits */ u_long packets; u_long bytes; long first; /* uptime on first packet */ long last; /* uptime on last packet */ u_char tcp_flags; /* cumulative OR */ }; /* * How many flow records we will transfer at once * without overflowing socket receive buffer */ #define NREC_AT_ONCE 1000 #define NREC6_AT_ONCE (NREC_AT_ONCE * sizeof(struct flow_entry_data) / \ sizeof(struct flow6_entry_data)) #define NGRESP_SIZE (sizeof(struct ngnf_show_header) + (NREC_AT_ONCE * \ sizeof(struct flow_entry_data))) #define SORCVBUF_SIZE (NGRESP_SIZE + 2 * sizeof(struct ng_mesg)) /* Everything below is for kernel */ #ifdef _KERNEL struct flow_entry { TAILQ_ENTRY(flow_entry) fle_hash; /* entries in hash slot */ struct flow_entry_data f; }; struct flow6_entry { TAILQ_ENTRY(flow_entry) fle_hash; /* entries in hash slot */ struct flow6_entry_data f; }; /* Parsing declarations */ /* Parse the info structure */ #define NG_NETFLOW_INFO_TYPE { \ { "IPv4 bytes", &ng_parse_uint64_type }, \ { "IPv4 packets", &ng_parse_uint32_type }, \ { "IPv6 bytes", &ng_parse_uint64_type }, \ { "IPv6 packets", &ng_parse_uint32_type }, \ { "IPv4 skipped bytes", &ng_parse_uint64_type }, \ { "IPv4 skipped packets", &ng_parse_uint32_type }, \ { "IPv6 skipped bytes", &ng_parse_uint64_type }, \ { "IPv6 skipped packets", &ng_parse_uint32_type }, \ { "IPv4 records used", &ng_parse_uint32_type },\ { "IPv6 records used", &ng_parse_uint32_type },\ { "Failed allocations", &ng_parse_uint32_type },\ { "V5 failed exports", &ng_parse_uint32_type },\ { "V9 failed exports", &ng_parse_uint32_type },\ { "mbuf reallocations", &ng_parse_uint32_type },\ { "fibs allocated", &ng_parse_uint32_type },\ { "Active expiries", &ng_parse_uint32_type },\ { "Inactive expiries", &ng_parse_uint32_type },\ { "Inactive timeout", &ng_parse_uint32_type },\ { "Active timeout", &ng_parse_uint32_type },\ { NULL } \ } /* Parse the ifinfo structure */ #define NG_NETFLOW_IFINFO_TYPE { \ { "packets", &ng_parse_uint32_type }, \ { "data link type", &ng_parse_uint8_type }, \ { "index", &ng_parse_uint16_type }, \ { "conf", &ng_parse_uint32_type }, \ { NULL } \ } /* Parse the setdlt structure */ #define NG_NETFLOW_SETDLT_TYPE { \ { "iface", &ng_parse_uint16_type }, \ { "dlt", &ng_parse_uint8_type }, \ { NULL } \ } /* Parse the setifindex structure */ #define NG_NETFLOW_SETIFINDEX_TYPE { \ { "iface", &ng_parse_uint16_type }, \ { "index", &ng_parse_uint16_type }, \ { NULL } \ } /* Parse the settimeouts structure */ #define NG_NETFLOW_SETTIMEOUTS_TYPE { \ { "inactive", &ng_parse_uint32_type }, \ { "active", &ng_parse_uint32_type }, \ { NULL } \ } /* Parse the setifindex structure */ #define NG_NETFLOW_SETCONFIG_TYPE { \ { "iface", &ng_parse_uint16_type }, \ { "conf", &ng_parse_uint32_type }, \ { NULL } \ } /* Parse the settemplate structure */ #define NG_NETFLOW_SETTEMPLATE_TYPE { \ { "time", &ng_parse_uint16_type }, \ { "packets", &ng_parse_uint16_type }, \ { NULL } \ } /* Parse the setmtu structure */ #define NG_NETFLOW_SETMTU_TYPE { \ { "mtu", &ng_parse_uint16_type }, \ { NULL } \ } +/* Parse the v9info structure */ +#define NG_NETFLOW_V9INFO_TYPE { \ + { "v9 template packets", &ng_parse_uint16_type },\ + { "v9 template time", &ng_parse_uint16_type },\ + { "v9 MTU", &ng_parse_uint16_type },\ + { NULL } \ +} + /* Private hook data */ struct ng_netflow_iface { hook_p hook; /* NULL when disconnected */ hook_p out; /* NULL when no bypass hook */ struct ng_netflow_ifinfo info; }; typedef struct ng_netflow_iface *iface_p; typedef struct ng_netflow_ifinfo *ifinfo_p; struct netflow_export_item { item_p item; item_p item9; struct netflow_v9_packet_opt *item9_opt; }; /* Structure contatining fib-specific data */ struct fib_export { uint32_t fib; /* kernel fib id */ struct netflow_export_item exp; /* Various data used for export */ struct mtx export_mtx; /* exp.item mutex */ struct mtx export9_mtx; /* exp.item9 mutex */ uint32_t flow_seq; /* current V5 flow sequence */ uint32_t flow9_seq; /* current V9 flow sequence */ uint32_t domain_id; /* Observartion domain id */ /* Netflow V9 counters */ uint32_t templ_last_ts; /* unixtime of last template announce */ uint32_t templ_last_pkt; /* packets count on last template announce */ uint32_t sent_packets; /* packets sent by exporter; */ struct netflow_v9_packet_opt *export9_opt; /* current packet specific options */ }; typedef struct fib_export *fib_export_p; /* Structure describing our flow engine */ struct netflow { node_p node; /* link to the node itself */ hook_p export; /* export data goes there */ hook_p export9; /* Netflow V9 export data goes there */ struct ng_netflow_info info; struct callout exp_callout; /* expiry periodic job */ /* * Flow entries are allocated in uma(9) zone zone. They are * indexed by hash hash. Each hash element consist of tailqueue * head and mutex to protect this element. */ #define CACHESIZE (65536*4) #define CACHELOWAT (CACHESIZE * 3/4) #define CACHEHIGHWAT (CACHESIZE * 9/10) uma_zone_t zone; struct flow_hash_entry *hash; /* * NetFlow data export * * export_item is a data item, it has an mbuf with cluster * attached to it. A thread detaches export_item from priv * and works with it. If the export is full it is sent, and * a new one is allocated. Before exiting thread re-attaches * its current item back to priv. If there is item already, * current incomplete datagram is sent. * export_mtx is used for attaching/detaching. */ /* IPv6 support */ #ifdef INET6 uma_zone_t zone6; struct flow_hash_entry *hash6; #endif /* Multiple FIB support */ fib_export_p *fib_data; /* array of pointers to per-fib data */ uint16_t maxfibs; /* number of allocated fibs */ + /* Netflow v9 configuration options */ /* * RFC 3954 clause 7.3 * "Both options MUST be configurable by the user on the Exporter." */ uint16_t templ_time; /* time between sending templates */ uint16_t templ_packets; /* packets between sending templates */ #define NETFLOW_V9_MAX_FLOWSETS 2 u_char flowsets_count; /* current flowsets used */ u_char flowset_records[NETFLOW_V9_MAX_FLOWSETS - 1]; /* Count of records in each flowset */ uint16_t mtu; /* export interface MTU */ struct netflow_v9_flowset_header *v9_flowsets[NETFLOW_V9_MAX_FLOWSETS - 1]; /* Pointers to pre-compiled flowsets */ struct ng_netflow_iface ifaces[NG_NETFLOW_MAXIFACES]; }; typedef struct netflow *priv_p; /* Header of a small list in hash cell */ struct flow_hash_entry { struct mtx mtx; TAILQ_HEAD(fhead, flow_entry) head; }; #define ERROUT(x) { error = (x); goto done; } #define MTAG_NETFLOW 1221656444 #define MTAG_NETFLOW_CALLED 0 #define m_pktlen(m) ((m)->m_pkthdr.len) #define IP6VERSION 6 #define priv_to_fib(priv, fib) (priv)->fib_data[(fib)] /* * Cisco uses milliseconds for uptime. Bad idea, since it overflows * every 48+ days. But we will do same to keep compatibility. This macro * does overflowable multiplication to 1000. */ #define MILLIUPTIME(t) (((t) << 9) + /* 512 */ \ ((t) << 8) + /* 256 */ \ ((t) << 7) + /* 128 */ \ ((t) << 6) + /* 64 */ \ ((t) << 5) + /* 32 */ \ ((t) << 3)) /* 8 */ /* Prototypes for netflow.c */ void ng_netflow_cache_init(priv_p); void ng_netflow_cache_flush(priv_p); int ng_netflow_fib_init(priv_p priv, int fib); void ng_netflow_copyinfo(priv_p, struct ng_netflow_info *); +void ng_netflow_copyv9info(priv_p, struct ng_netflow_v9info *); timeout_t ng_netflow_expire; int ng_netflow_flow_add(priv_p, fib_export_p, struct ip *, caddr_t, uint8_t, uint8_t, unsigned int); int ng_netflow_flow6_add(priv_p, fib_export_p, struct ip6_hdr *, caddr_t , uint8_t, uint8_t, unsigned int); int ng_netflow_flow_show(priv_p, struct ngnf_show_header *req, struct ngnf_show_header *resp); void ng_netflow_v9_cache_init(priv_p); void ng_netflow_v9_cache_flush(priv_p); item_p get_export9_dgram(priv_p, fib_export_p, struct netflow_v9_packet_opt **); void return_export9_dgram(priv_p, fib_export_p, item_p, struct netflow_v9_packet_opt *, int); int export9_add(item_p, struct netflow_v9_packet_opt *, struct flow_entry *); int export9_send(priv_p, fib_export_p, item_p, struct netflow_v9_packet_opt *, int); #endif /* _KERNEL */ #endif /* _NG_NETFLOW_H_ */