Add support for exporting flow information in NetFlow v5 format.
[sliver-openvswitch.git] / secchan / flow-end.c
diff --git a/secchan/flow-end.c b/secchan/flow-end.c
new file mode 100644 (file)
index 0000000..56d1776
--- /dev/null
@@ -0,0 +1,321 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include <config.h>
+#include "flow-end.h"
+#include <errno.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <time.h>
+#include "openflow/nicira-ext.h"
+#include "openflow/openflow.h"
+#include "secchan.h"
+#include "ofpbuf.h"
+#include "vconn.h"
+#include "rconn.h"
+#include "socket-util.h"
+#include "xtoxll.h"
+#include "netflow.h"
+
+#define THIS_MODULE VLM_flow_end
+#include "vlog.h"
+
+struct flow_end_data {
+    struct rconn *remote_rconn;
+    struct rconn *local_rconn;
+
+    bool send_ofp_exp;         /* Send OpenFlow 'flow expired' messages? */
+
+    int netflow_fd;            /* Socket for NetFlow collector. */
+    uint32_t netflow_cnt;      /* Flow sequence number for NetFlow. */
+};
+
+static int
+udp_open(char *dst)
+{
+    char *save_ptr;
+    const char *host_name;
+    const char *port_string;
+    struct sockaddr_in sin;
+    int retval;
+    int fd;
+
+    /* Glibc 2.7 has a bug in strtok_r when compiling with optimization that
+     * can cause segfaults here:
+     * http://sources.redhat.com/bugzilla/show_bug.cgi?id=5614.
+     * Using "::" instead of the obvious ":" works around it. */
+    host_name = strtok_r(dst, "::", &save_ptr);
+    port_string = strtok_r(NULL, "::", &save_ptr);
+    if (!host_name) {
+        ofp_error(0, "%s: bad peer name format", dst);
+        return -EAFNOSUPPORT;
+    }
+    if (!port_string) {
+        ofp_error(0, "%s: bad port format", dst);
+        return -EAFNOSUPPORT;
+    }
+
+    memset(&sin, 0, sizeof sin);
+    sin.sin_family = AF_INET;
+    if (lookup_ip(host_name, &sin.sin_addr)) {
+        return -ENOENT;
+    }
+    sin.sin_port = htons(atoi(port_string));
+
+    fd = socket(AF_INET, SOCK_DGRAM, 0);
+    if (fd < 0) {
+        VLOG_ERR("%s: socket: %s", dst, strerror(errno));
+        return -errno;
+    }
+
+    retval = set_nonblocking(fd);
+    if (retval) {
+        close(fd);
+        return -retval;
+    }
+
+    retval = connect(fd, (struct sockaddr *) &sin, sizeof sin);
+    if (retval < 0) {
+        int error = errno;
+        VLOG_ERR("%s: connect: %s", dst, strerror(error));
+        close(fd);
+        return -error;
+    }
+
+    return fd;
+}
+
+static void
+send_netflow_msg(const struct nx_flow_end *nfe, struct flow_end_data *fe)
+{
+    struct netflow_v5_header *nf_hdr;
+    struct netflow_v5_record *nf_rec;
+    uint8_t buf[sizeof(*nf_hdr) + sizeof(*nf_rec)];
+    uint8_t *p = buf;
+    struct timeval now;
+
+    /* We only send NetFlow messages for fully specified IP flows; any 
+     * entry with a wildcard is ignored. */
+    if ((nfe->match.wildcards != 0) 
+            || (nfe->match.dl_type != htons(ETH_TYPE_IP))) {
+        return;
+    }
+
+    memset(&buf, 0, sizeof(buf));
+    gettimeofday(&now, NULL);
+
+    nf_hdr = (struct netflow_v5_header *)p;
+    p += sizeof(*nf_hdr);
+    nf_rec = (struct netflow_v5_record *)p;
+
+    nf_hdr->version = htons(NETFLOW_V5_VERSION);
+    nf_hdr->count = htons(1);
+    nf_hdr->sysuptime = htonl((uint32_t)ntohll(nfe->end_time));
+    nf_hdr->unix_secs = htonl(now.tv_sec);
+    nf_hdr->unix_nsecs = htonl(now.tv_usec * 1000);
+    nf_hdr->flow_seq = htonl(fe->netflow_cnt);
+    nf_hdr->engine_type = 0;
+    nf_hdr->engine_id = 0;
+    nf_hdr->sampling_interval = htons(0);
+
+    nf_rec->src_addr = nfe->match.nw_src;
+    nf_rec->dst_addr = nfe->match.nw_dst;
+    nf_rec->nexthop = htons(0);
+    nf_rec->input = nfe->match.in_port;
+    nf_rec->output = htons(0);
+    nf_rec->packet_count = htonl((uint32_t)ntohll(nfe->packet_count));
+    nf_rec->byte_count = htonl((uint32_t)ntohll(nfe->byte_count));
+    nf_rec->init_time = htonl((uint32_t)ntohll(nfe->init_time));
+    nf_rec->used_time = htonl((uint32_t)ntohll(nfe->used_time));
+
+    if (nfe->match.nw_proto == IP_TYPE_ICMP) {
+        /* In NetFlow, the ICMP type and code are concatenated and
+         * placed in the 'dst_port' field. */
+        uint8_t type = (uint8_t)ntohs(nfe->match.tp_src);
+        uint8_t code = (uint8_t)ntohs(nfe->match.tp_dst);
+        nf_rec->src_port = htons(0);
+        nf_rec->dst_port = htons((type << 8) | code);
+    } else {
+        nf_rec->src_port = nfe->match.tp_src;
+        nf_rec->dst_port = nfe->match.tp_dst;
+    }
+
+    nf_rec->tcp_flags = nfe->tcp_flags;
+    nf_rec->ip_proto = nfe->match.nw_proto;
+    nf_rec->ip_tos = nfe->ip_tos;
+
+    nf_rec->src_as = htons(0);
+    nf_rec->dst_as = htons(0);
+    nf_rec->src_mask = 0;
+    nf_rec->dst_mask = 0;
+
+    send(fe->netflow_fd, buf, sizeof(buf), 0);
+    fe->netflow_cnt++;
+}
+
+static void 
+send_ofp_expired(const struct nx_flow_end *nfe, const struct flow_end_data *fe)
+{
+    struct ofp_flow_expired *ofe;
+    struct ofpbuf *b;
+
+    if ((nfe->reason != NXFER_IDLE_TIMEOUT) 
+            && (nfe->reason != NXFER_HARD_TIMEOUT)) {
+        return;
+    }
+
+    ofe = make_openflow(sizeof(*ofe), OFPT_FLOW_EXPIRED, &b);
+    ofe->match = nfe->match;
+    ofe->priority = nfe->priority;
+    if (nfe->reason == NXFER_IDLE_TIMEOUT) {
+        ofe->reason = OFPER_IDLE_TIMEOUT;
+    } else {
+        ofe->reason = OFPER_HARD_TIMEOUT;
+    }
+    /* 'duration' is in seconds, but we keeping track of milliseconds. */
+    ofe->duration = htonl((ntohll(nfe->end_time)-ntohll(nfe->init_time))/1000);
+    ofe->packet_count = nfe->packet_count;
+    ofe->byte_count = nfe->byte_count;
+
+    rconn_send(fe->remote_rconn, b, NULL);
+}
+
+static void 
+send_nx_flow_end_config(const struct flow_end_data *fe)
+{
+    struct nx_flow_end_config *nfec;
+    struct ofpbuf *b;
+
+    nfec = make_openflow(sizeof(*nfec), OFPT_VENDOR, &b);
+    nfec->header.vendor  = htonl(NX_VENDOR_ID);
+    nfec->header.subtype = htonl(NXT_FLOW_END_CONFIG);
+    nfec->enable = fe->send_ofp_exp ? 1 : 0;
+
+    rconn_send(fe->local_rconn, b, NULL);
+}
+
+static bool
+flow_end_local_packet_cb(struct relay *r, void *flow_end_)
+{
+    struct flow_end_data *fe = flow_end_;
+    struct ofpbuf *msg = r->halves[HALF_LOCAL].rxbuf;
+    struct nicira_header *request = msg->data;
+    struct nx_flow_end *nfe = msg->data;
+
+
+    if (msg->size < sizeof(*nfe)) {
+        return false;
+    }
+    request = msg->data;
+    if (request->header.type != OFPT_VENDOR
+        || request->vendor != htonl(NX_VENDOR_ID)
+        || request->subtype != htonl(NXT_FLOW_END)) {
+        return false;
+    }
+
+    if (fe->netflow_fd >= 0) {
+        send_netflow_msg(nfe, fe);
+    }
+
+    if (fe->send_ofp_exp) {
+        send_ofp_expired(nfe, fe);
+    }
+
+    /* We always consume these Flow End messages. */
+    return true;
+}
+
+static bool
+flow_end_remote_packet_cb(struct relay *r, void *flow_end_)
+{
+    struct flow_end_data *fe = flow_end_;
+    struct ofpbuf *msg = r->halves[HALF_REMOTE].rxbuf;
+    struct ofp_switch_config *osc = msg->data;
+
+    /* Check for OFPT_SET_CONFIG messages to see if the controller wants
+     * to receive 'flow expired' messages.  If so, we need to intercept
+     * the datapath's 'flow end' meta-messages and convert. */
+
+    if ((msg->size < sizeof(*osc)) 
+            || (osc->header.type != OFPT_SET_CONFIG)) {
+        return false;
+    }
+
+    if (osc->flags & htons(OFPC_SEND_FLOW_EXP)) {
+        fe->send_ofp_exp = true;
+    } else {
+        fe->send_ofp_exp = false;
+    }
+
+    send_nx_flow_end_config(fe);
+
+    return false;
+}
+
+static struct hook_class flow_end_hook_class = {
+    flow_end_local_packet_cb,   /* local_packet_cb */
+    flow_end_remote_packet_cb,  /* remote_packet_cb */
+    NULL,                       /* periodic_cb */
+    NULL,                       /* wait_cb */
+    NULL,                       /* closing_cb */
+};
+
+void
+flow_end_start(struct secchan *secchan, char *netflow_dst,
+               struct rconn *local, struct rconn *remote)
+{
+    struct flow_end_data *fe;
+
+    fe = xcalloc(1, sizeof *fe);
+
+    fe->remote_rconn = remote;
+    fe->local_rconn = local;
+
+    if (netflow_dst) {
+        fe->netflow_fd = udp_open(netflow_dst);
+        if (fe->netflow_fd < 0) {
+            ofp_fatal(0, "NetFlow setup failed");
+        }
+        fe->send_ofp_exp = true;
+    } else {
+        fe->netflow_fd = -1;
+        fe->send_ofp_exp = false;
+    }
+
+    add_hook(secchan, &flow_end_hook_class, fe);
+
+    send_nx_flow_end_config(fe);
+}