#include <sys/cdefs.h>
#include "opt_inet6.h"
#include "opt_route.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/counter.h>
#include <sys/kernel.h>
#include <sys/ktr.h>
#include <sys/limits.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/syslog.h>
#include <sys/socket.h>
#include <vm/uma.h>
#include <net/if.h>
#include <net/route.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/netflow/netflow.h>
#include <netgraph/netflow/ng_netflow.h>
#include <netgraph/netflow/netflow_v9.h>
MALLOC_DECLARE(M_NETFLOW_GENERAL);
MALLOC_DEFINE(M_NETFLOW_GENERAL, "netflow_general", "plog, V9 templates data");
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}
};
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_TEMPLATE_SIZE(_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);
priv->flowsets_count = 1;
p = (uint16_t *)priv->v9_flowsets[0];
*p++ = 0;
*p++ = htons(flowset_size);
*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);
*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;
}
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;
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);
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);
}
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);
}
if (m_pktlen(m) != t->flow_header)
close_flowset(m, t);
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);
}
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);
}
fed = (struct flow_entry_data *)&fle->f;
#ifdef INET6
fed6 = (struct flow6_entry_data *)&fle->f;
#endif
flow_type = fed->r.flow_type;
switch (flow_type) {
case NETFLOW_V9_FLOW_V4_L4:
{
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;
rec->src_as = rec->dst_as = 0;
len = sizeof(struct netflow_v9_record_ipv4_tcp);
break;
}
#ifdef INET6
case NETFLOW_V9_FLOW_V6_L4:
{
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;
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);
}
}
if (flow_type != t->flow_type) {
if (t->flow_type != 0)
close_flowset(m, t);
t->flow_type = flow_type;
t->flow_header = m_pktlen(m);
fsh.id = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + flow_type);
fsh.length = 0;
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);
return (0);
}
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;
m = m_getm(NULL, mtu, M_NOWAIT, 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);
m->m_len = m->m_pkthdr.len = sizeof(struct netflow_v9_header);
t->count = 0;
t->mtu = mtu;
t->flow_header = m->m_len;
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);
}
void
return_export9_dgram(priv_p priv, fib_export_p fe, item_p item, struct netflow_v9_packet_opt *t, int flags)
{
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);
}
}
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;
}
void
ng_netflow_v9_cache_flush(priv_p priv)
{
int i;
for (i = 0; i < priv->flowsets_count; i++)
free(priv->v9_flowsets[i], M_NETFLOW_GENERAL);
}
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;
}