📄 softflowd.c
字号:
/* * Copyright 2002 Damien Miller <djm@mindrot.org> 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 ``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 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. *//* $Id: softflowd.c,v 1.93 2006/11/02 06:23:29 djm Exp $ *//* * This is software implementation of Cisco's NetFlow(tm) traffic * reporting system. It operates by listening (via libpcap) on a * promiscuous interface and tracking traffic flows. * * Traffic flows are recorded by source/destination/protocol IP address or, in the * case of TCP and UDP, by src_addr:src_port/dest_addr:dest_port/protocol * * Flows expire automatically after a period of inactivity (default: 1 hour) * They may also be evicted (in order of age) in situations where there are * more flows than slots available. * * Netflow version 1 compatible packets are sent to a specified target * host upon flow expiry. * * As this implementation watches traffic promiscuously, it is likely to * place significant load on hosts or gateways on which it is installed. */#include "common.h"#include "sys-tree.h"#include "convtime.h"#include "softflowd.h"#include "treetype.h"#include "log.h"#include <pcap.h>RCSID("$Id: softflowd.c,v 1.93 2006/11/02 06:23:29 djm Exp $");/* Global variables */static int verbose_flag = 0; /* Debugging flag *//* Signal handler flags */static int graceful_shutdown_request = 0; /* Context for libpcap callback functions */struct CB_CTXT { struct FLOWTRACK *ft; int linktype; int fatal; int want_v6;};/* Describes a datalink header and how to extract v4/v6 frames from it */struct DATALINK { int dlt; /* BPF datalink type */ int skiplen; /* Number of bytes to skip datalink header */ int ft_off; /* Datalink frametype offset */ int ft_len; /* Datalink frametype length */ int ft_is_be; /* Set if frametype is big-endian */ u_int32_t ft_mask; /* Mask applied to frametype */ u_int32_t ft_v4; /* IPv4 frametype */ u_int32_t ft_v6; /* IPv6 frametype */};/* Datalink types that we know about */static const struct DATALINK lt[] = { { DLT_EN10MB, 14, 12, 2, 1, 0xffffffff, 0x0800, 0x86dd }, { DLT_PPP, 5, 3, 2, 1, 0xffffffff, 0x0021, 0x0057 },#ifdef DLT_LINUX_SLL { DLT_LINUX_SLL,16, 14, 2, 1, 0xffffffff, 0x0800, 0x86dd },#endif { DLT_RAW, 0, 0, 1, 1, 0x000000f0, 0x0040, 0x0060 }, { DLT_NULL, 4, 0, 4, 0, 0xffffffff, AF_INET, AF_INET6 },#ifdef DLT_LOOP { DLT_LOOP, 4, 0, 4, 1, 0xffffffff, AF_INET, AF_INET6 },#endif { -1, -1, -1, -1, -1, 0x00000000, 0xffff, 0xffff },};/* Netflow send functions */typedef int (netflow_send_func_t)(struct FLOW **, int, int, u_int64_t *, struct timeval *, int);struct NETFLOW_SENDER { int version; netflow_send_func_t *func; int v6_capable;};/* Array of NetFlow export function that we know of. NB. nf[0] is default */static const struct NETFLOW_SENDER nf[] = { { 5, send_netflow_v5, 0 }, { 1, send_netflow_v1, 0 }, { 9, send_netflow_v9, 1 }, { -1, NULL, 0 },};/* Describes a location where we send NetFlow packets to */struct NETFLOW_TARGET { int fd; const struct NETFLOW_SENDER *dialect;};/* Signal handlers */static void sighand_graceful_shutdown(int signum){ graceful_shutdown_request = signum;}static void sighand_other(int signum){ /* XXX: this may not be completely safe */ logit(LOG_WARNING, "Exiting immediately on unexpected signal %d", signum); _exit(0);}/* * This is the flow comparison function. */static intflow_compare(struct FLOW *a, struct FLOW *b){ /* Be careful to avoid signed vs unsigned issues here */ int r; if (a->af != b->af) return (a->af > b->af ? 1 : -1); if ((r = memcmp(&a->addr[0], &b->addr[0], sizeof(a->addr[0]))) != 0) return (r > 0 ? 1 : -1); if ((r = memcmp(&a->addr[1], &b->addr[1], sizeof(a->addr[1]))) != 0) return (r > 0 ? 1 : -1);#ifdef notyet if (a->ip6_flowlabel[0] != 0 && b->ip6_flowlabel[0] != 0 && a->ip6_flowlabel[0] != b->ip6_flowlabel[0]) return (a->ip6_flowlabel[0] > b->ip6_flowlabel[0] ? 1 : -1); if (a->ip6_flowlabel[1] != 0 && b->ip6_flowlabel[1] != 0 && a->ip6_flowlabel[1] != b->ip6_flowlabel[1]) return (a->ip6_flowlabel[1] > b->ip6_flowlabel[1] ? 1 : -1);#endif if (a->protocol != b->protocol) return (a->protocol > b->protocol ? 1 : -1); if (a->port[0] != b->port[0]) return (ntohs(a->port[0]) > ntohs(b->port[0]) ? 1 : -1); if (a->port[1] != b->port[1]) return (ntohs(a->port[1]) > ntohs(b->port[1]) ? 1 : -1); return (0);}/* Generate functions for flow tree */FLOW_PROTOTYPE(FLOWS, FLOW, trp, flow_compare);FLOW_GENERATE(FLOWS, FLOW, trp, flow_compare);/* * This is the expiry comparison function. */static intexpiry_compare(struct EXPIRY *a, struct EXPIRY *b){ if (a->expires_at != b->expires_at) return (a->expires_at > b->expires_at ? 1 : -1); /* Make expiry entries unique by comparing flow sequence */ if (a->flow->flow_seq != b->flow->flow_seq) return (a->flow->flow_seq > b->flow->flow_seq ? 1 : -1); return (0);}/* Generate functions for flow tree */EXPIRY_PROTOTYPE(EXPIRIES, EXPIRY, trp, expiry_compare);EXPIRY_GENERATE(EXPIRIES, EXPIRY, trp, expiry_compare);#if 0/* Dump a packet */static voiddump_packet(const u_int8_t *p, int len){ char buf[1024], tmp[3]; int i; for (*buf = '\0', i = 0; i < len; i++) { snprintf(tmp, sizeof(tmp), "%02x%s", p[i], i % 2 ? " " : ""); if (strlcat(buf, tmp, sizeof(buf) - 4) >= sizeof(buf) - 4) { strlcat(buf, "...", sizeof(buf)); break; } } logit(LOG_INFO, "packet len %d: %s", len, buf);}#endif/* Format a time in an ISOish format */static const char *format_time(time_t t){ struct tm *tm; static char buf[20]; tm = localtime(&t); strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", tm); return (buf);}/* Format a flow in a verbose and ugly way */static const char *format_flow(struct FLOW *flow){ char addr1[64], addr2[64], stime[20], ftime[20]; static char buf[1024]; inet_ntop(flow->af, &flow->addr[0], addr1, sizeof(addr1)); inet_ntop(flow->af, &flow->addr[1], addr2, sizeof(addr2)); snprintf(stime, sizeof(ftime), "%s", format_time(flow->flow_start.tv_sec)); snprintf(ftime, sizeof(ftime), "%s", format_time(flow->flow_last.tv_sec)); snprintf(buf, sizeof(buf), "seq:%llu [%s]:%hu <> [%s]:%hu proto:%u " "octets>:%u packets>:%u octets<:%u packets<:%u " "start:%s.%03ld finish:%s.%03ld tcp>:%02x tcp<:%02x " "flowlabel>:%08x flowlabel<:%08x ", flow->flow_seq, addr1, ntohs(flow->port[0]), addr2, ntohs(flow->port[1]), (int)flow->protocol, flow->octets[0], flow->packets[0], flow->octets[1], flow->packets[1], stime, (flow->flow_start.tv_usec + 500) / 1000, ftime, (flow->flow_last.tv_usec + 500) / 1000, flow->tcp_flags[0], flow->tcp_flags[1], flow->ip6_flowlabel[0], flow->ip6_flowlabel[1]); return (buf);}/* Format a flow in a brief way */static const char *format_flow_brief(struct FLOW *flow){ char addr1[64], addr2[64]; static char buf[1024]; inet_ntop(flow->af, &flow->addr[0], addr1, sizeof(addr1)); inet_ntop(flow->af, &flow->addr[1], addr2, sizeof(addr2)); snprintf(buf, sizeof(buf), "seq:%llu [%s]:%hu <> [%s]:%hu proto:%u", flow->flow_seq, addr1, ntohs(flow->port[0]), addr2, ntohs(flow->port[1]), (int)flow->protocol); return (buf);}/* Fill in transport-layer (tcp/udp) portions of flow record */static inttransport_to_flowrec(struct FLOW *flow, const u_int8_t *pkt, const size_t caplen, int isfrag, int protocol, int ndx){ const struct tcphdr *tcp = (const struct tcphdr *)pkt; const struct udphdr *udp = (const struct udphdr *)pkt; const struct icmp *icmp = (const struct icmp *)pkt; /* * XXX to keep flow in proper canonical format, it may be necessary * to swap the array slots based on the order of the port numbers * does this matter in practice??? I don't think so - return flows will * always match, because of their symmetrical addr/ports */ switch (protocol) { case IPPROTO_TCP: /* Check for runt packet, but don't error out on short frags */ if (caplen < sizeof(*tcp)) return (isfrag ? 0 : 1); flow->port[ndx] = tcp->th_sport; flow->port[ndx ^ 1] = tcp->th_dport; flow->tcp_flags[ndx] |= tcp->th_flags; break; case IPPROTO_UDP: /* Check for runt packet, but don't error out on short frags */ if (caplen < sizeof(*udp)) return (isfrag ? 0 : 1); flow->port[ndx] = udp->uh_sport; flow->port[ndx ^ 1] = udp->uh_dport; break; case IPPROTO_ICMP: /* * Encode ICMP type * 256 + code into dest port like * Cisco routers */ flow->port[ndx] = 0; flow->port[ndx ^ 1] = htons(icmp->icmp_type * 256 + icmp->icmp_code); break; } return (0);}/* Convert a IPv4 packet to a partial flow record (used for comparison) */static intipv4_to_flowrec(struct FLOW *flow, const u_int8_t *pkt, size_t caplen, size_t len, int *isfrag, int af){ const struct ip *ip = (const struct ip *)pkt; int ndx; if (caplen < 20 || caplen < ip->ip_hl * 4) return (-1); /* Runt packet */ if (ip->ip_v != 4) return (-1); /* Unsupported IP version */ /* Prepare to store flow in canonical format */ ndx = memcmp(&ip->ip_src, &ip->ip_dst, sizeof(ip->ip_src)) > 0 ? 1 : 0; flow->af = af; flow->addr[ndx].v4 = ip->ip_src; flow->addr[ndx ^ 1].v4 = ip->ip_dst; flow->protocol = ip->ip_p; flow->octets[ndx] = len; flow->packets[ndx] = 1; *isfrag = (ntohs(ip->ip_off) & (IP_OFFMASK|IP_MF)) ? 1 : 0; /* Don't try to examine higher level headers if not first fragment */ if (*isfrag && (ntohs(ip->ip_off) & IP_OFFMASK) != 0) return (0); return (transport_to_flowrec(flow, pkt + (ip->ip_hl * 4), caplen - (ip->ip_hl * 4), *isfrag, ip->ip_p, ndx));}/* Convert a IPv6 packet to a partial flow record (used for comparison) */static intipv6_to_flowrec(struct FLOW *flow, const u_int8_t *pkt, size_t caplen, size_t len, int *isfrag, int af){ const struct ip6_hdr *ip6 = (const struct ip6_hdr *)pkt; const struct ip6_ext *eh6; const struct ip6_frag *fh6; int ndx, nxt; if (caplen < sizeof(*ip6)) return (-1); /* Runt packet */ if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) return (-1); /* Unsupported IPv6 version */ /* Prepare to store flow in canonical format */ ndx = memcmp(&ip6->ip6_src, &ip6->ip6_dst, sizeof(ip6->ip6_src)) > 0 ? 1 : 0; flow->af = af; flow->ip6_flowlabel[ndx] = ip6->ip6_flow & IPV6_FLOWLABEL_MASK; flow->addr[ndx].v6 = ip6->ip6_src; flow->addr[ndx ^ 1].v6 = ip6->ip6_dst; flow->octets[ndx] = len; flow->packets[ndx] = 1; *isfrag = 0; nxt = ip6->ip6_nxt; pkt += sizeof(*ip6); caplen -= sizeof(*ip6); /* Now loop through headers, looking for transport header */ for (;;) { eh6 = (const struct ip6_ext *)pkt; if (nxt == IPPROTO_HOPOPTS || nxt == IPPROTO_ROUTING || nxt == IPPROTO_DSTOPTS) { if (caplen < sizeof(*eh6) || caplen < (eh6->ip6e_len + 1) << 3) return (1); /* Runt */ nxt = eh6->ip6e_nxt; pkt += (eh6->ip6e_len + 1) << 3; caplen -= (eh6->ip6e_len + 1) << 3; } else if (nxt == IPPROTO_FRAGMENT) { *isfrag = 1; fh6 = (const struct ip6_frag *)eh6; if (caplen < sizeof(*fh6)) return (1); /* Runt */ /* * Don't try to examine higher level headers if * not first fragment */ if ((fh6->ip6f_offlg & IP6F_OFF_MASK) != 0) return (0); nxt = fh6->ip6f_nxt; pkt += sizeof(*fh6); caplen -= sizeof(*fh6); } else break; } flow->protocol = nxt; return (transport_to_flowrec(flow, pkt, caplen, *isfrag, nxt, ndx));}static voidflow_update_expiry(struct FLOWTRACK *ft, struct FLOW *flow){ EXPIRY_REMOVE(EXPIRIES, &ft->expiries, flow->expiry); /* Flows over 2Gb traffic */ if (flow->octets[0] > (1U << 31) || flow->octets[1] > (1U << 31)) { flow->expiry->expires_at = 0; flow->expiry->reason = R_OVERBYTES; goto out; } /* Flows over maximum life seconds */ if (ft->maximum_lifetime != 0 && flow->flow_last.tv_sec - flow->flow_start.tv_sec > ft->maximum_lifetime) { flow->expiry->expires_at = 0; flow->expiry->reason = R_MAXLIFE; goto out; } if (flow->protocol == IPPROTO_TCP) { /* Reset TCP flows */ if (ft->tcp_rst_timeout != 0 && ((flow->tcp_flags[0] & TH_RST) || (flow->tcp_flags[1] & TH_RST))) { flow->expiry->expires_at = flow->flow_last.tv_sec + ft->tcp_rst_timeout; flow->expiry->reason = R_TCP_RST; goto out; } /* Finished TCP flows */ if (ft->tcp_fin_timeout != 0 && ((flow->tcp_flags[0] & TH_FIN) && (flow->tcp_flags[1] & TH_FIN))) { flow->expiry->expires_at = flow->flow_last.tv_sec + ft->tcp_fin_timeout; flow->expiry->reason = R_TCP_FIN; goto out; } /* TCP flows */ if (ft->tcp_timeout != 0) { flow->expiry->expires_at = flow->flow_last.tv_sec + ft->tcp_timeout; flow->expiry->reason = R_TCP; goto out; } } if (ft->udp_timeout != 0 && flow->protocol == IPPROTO_UDP) { /* UDP flows */ flow->expiry->expires_at = flow->flow_last.tv_sec + ft->udp_timeout; flow->expiry->reason = R_UDP; goto out; } if (ft->icmp_timeout != 0 && ((flow->af == AF_INET && flow->protocol == IPPROTO_ICMP) ||
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -