From 688090c33a9fcadc98db8142a6d0ce15f3d2442a Mon Sep 17 00:00:00 2001
From: root <root@vswitch.(none)>
Date: Fri, 7 Mar 2014 11:36:31 +0100
Subject: [PATCH] DPI plugin addition proposal.

Design
======

DPI processing may require the first N packets of a given flow,
in order to get a final classification or to extract metadatas.

The idea behind this patch is to hook on new flow processing
and avoid to add a datapath entry until N packets have been
processed by the DPI engine. From a DPI perspective, a flow is
identified by its 5-tuple (IPs/PROTO/PORTs).

If megaflows are enabled, once a megaflow entry is added
in the datapath, any new flow from a DPI perspective
(5-tuple) won't be analyzed: we can see this as a feature.
Moreover on this DPI entry gate, we could add a new non-terminal
OpenFlow action: "dpi". First as a proprietary extention.

When a datapath flow expire (timeout), meaning the flow has been
completly classified, the DPI engine has kept track of the flow
status (classified) and will return without any further analysis.

In term of scalability, the DPI engine is run synchronously
by the n_handlers threads.

Tests
=====

Tested with Qosmos iXengine version b043672e4c54529e4b38c974e0c520045370eb59
and openvswitch version 6d56c1f19fe9facea0eabc4a39ad01cfc8d7971d, with and
without megaflows. This is a prototype, corner cases have been ignored.

Screenshot
==========

ovs-vswitchd --pidfile -- /root/sample_flow.so --offload-mask=all --hook base:upacket --hook http:.
2014-03-19T10:20:19Z|00001|dpi|INFO|dpi_init_once
2014-03-19T10:20:22Z|00002|reconnect|INFO|unix:/usr/local/var/run/openvswitch/db.sock: connecting...
2014-03-19T10:20:22Z|00003|reconnect|INFO|unix:/usr/local/var/run/openvswitch/db.sock: connected
2014-03-19T10:20:22Z|00004|dpif|WARN|system@ovs-system: failed to put[create][modify] (Invalid argument) skb_priority(0),skb_mark(0),in_port(0),eth(src=00:00:00:00:00:00,dst=00:00:00:00:00:00),eth_type(0x8847),mpls(label=0,tc=0,ttl=0,bos=1)
2014-03-19T10:20:22Z|00005|ofproto_dpif|INFO|system@ovs-system: MPLS label stack length probed as 0
2014-03-19T10:20:22Z|00001|dpi(handler_1)|INFO|dpi_init_perthread
2014-03-19T10:20:22Z|00001|dpi(handler_2)|INFO|dpi_init_perthread
2014-03-19T10:20:22Z|00006|bridge|INFO|bridge br0: added interface br0 on port 65534
2014-03-19T10:20:22Z|00007|bridge|INFO|bridge br0: added interface eth2 on port 2
2014-03-19T10:20:22Z|00008|bridge|INFO|bridge br0: added interface eth1 on port 1
2014-03-19T10:20:22Z|00009|bridge|INFO|bridge br0: using datapath ID 0000eaa4cba3c344
2014-03-19T10:20:22Z|00010|connmgr|INFO|br0: added service controller "punix:/usr/local/var/run/openvswitch/br0.mgmt"
2014-03-19T10:20:22Z|00011|bridge|INFO|ovs-vswitchd (Open vSwitch) 2.1.90
2014-03-19T10:20:29Z|00012|memory|INFO|47332 kB peak resident set size after 10.1 seconds
2014-03-19T10:20:29Z|00013|memory|INFO|dispatchers:1 flow_dumpers:1 handlers:2 ports:3 revalidators:2 rules:4
0 s(0x2d5d878)/base/upacket=0x7f5da752d980
0 s(0x7f5da00276e8)/base/upacket=0x7f5da7d2e980
0 s(0x2d5d878)/base/upacket=0x7f5da752d980
0 s(0x2d63288)/http/request/method=GET
0 s(0x2d63288)/http/request/is_webdav=0
0 s(0x2d63288)/http/request/request_ts=1395224565.124765
0 s(0x2d63288)/http/request/uri_start_offset=4
0 s(0x2d63288)/http/request/header_statusline=GET / HTTP/1.1
0 s(0x2d63288)/http/request/uri_end_offset=5
0 s(0x2d63288)/http/request/uri_full=/
0 s(0x2d63288)/http/declassify_override=0
0 s(0x2d63288)/http/request/uri=/
0 s(0x2d63288)/http/request/uri_decoded=/
0 s(0x2d63288)/http/request/uri_get_decoded=/
0 s(0x2d63288)/http/request/uri_path_decoded=/
0 s(0x2d63288)/http/request/uri_path=/
0 s(0x2d63288)/http/request/directory=/
0 s(0x2d63288)/http/request/version=1.1
0 s(0x2d63288)/http/request/user_agent_start_offset=28
0 s(0x2d63288)/http/request/header=
0 s(0x2d63288)/http/request/header/header_name=User-Agent
0 s(0x2d63288)/http/request/user_agent_end_offset=51
0 s(0x2d63288)/http/request/user_agent=Wget/1.13.4 (linux-gnu)
../..

ovs-dpctl dump-flows system@ovs-system
skb_priority(0),in_port(2),eth(src=52:54:00:12:34:88,dst=52:54:00:12:34:44),eth_type(0x0800),ipv4(src=1.1.1.2/0.0.0.0,dst=1.1.1.1/0.0.0.0,proto=6/0,tos=0/0,ttl=64/0,frag=no/0xff), packets:13, bytes:2970, used:1.504s, actions:3
skb_priority(0),in_port(3),eth(src=52:54:00:12:34:44,dst=52:54:00:12:34:88),eth_type(0x0800),ipv4(src=1.1.1.1/0.0.0.0,dst=1.1.1.2/0.0.0.0,proto=6/0,tos=0/0,ttl=64/0,frag=no/0xff), packets:10, bytes:3356, used:1.461s, actions:2

Signed-off-by: Franck Baudin <franck.baudin@qosmos.com>
---
 lib/automake.mk               |    5 +-
 lib/dpi.c                     |  142 +++++++++++++++++++++++++++++++++++++++++
 lib/dpi.h                     |   38 +++++++++++
 ofproto/ofproto-dpif-upcall.c |   24 ++++++-
 vswitchd/ovs-vswitchd.c       |   10 +++
 5 files changed, 214 insertions(+), 5 deletions(-)
 create mode 100644 lib/dpi.c
 create mode 100644 lib/dpi.h

diff --git a/lib/automake.mk b/lib/automake.mk
index b1688ef..94fbf9b 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -7,7 +7,8 @@
 
 lib_LTLIBRARIES += lib/libopenvswitch.la
 
-lib_libopenvswitch_la_LIBADD = $(SSL_LIBS)
+DPI_LIBS = -ldl
+lib_libopenvswitch_la_LIBADD = $(SSL_LIBS) $(DPI_LIBS)
 lib_libopenvswitch_la_LDFLAGS = -release $(VERSION)
 
 lib_libopenvswitch_la_SOURCES = \
@@ -48,6 +49,8 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/dirs.h \
 	lib/dpif-netdev.c \
 	lib/dpif-provider.h \
+	lib/dpi.c \
+	lib/dpi.h \
 	lib/dpif.c \
 	lib/dpif.h \
 	lib/heap.c \
diff --git a/lib/dpi.c b/lib/dpi.c
new file mode 100644
index 0000000..57871d8
--- /dev/null
+++ b/lib/dpi.c
@@ -0,0 +1,142 @@
+/*
+ *   Copyright  Qosmos 2000-2014 - All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/time.h>
+#include "dpi.h"
+
+VLOG_DEFINE_THIS_MODULE(dpi);
+
+static int (*engine_init_once)(int , char **) = NULL;
+static void (*engine_exit_once)(void) = NULL;
+static void *(*engine_init_perthread)(void) = NULL;
+static void (*engine_exit_perthread)(void *) = NULL;
+static bool (*engine_inject_packet)(void *, char *, size_t , struct timeval *) = NULL;
+
+static void *dpi_lib = NULL;
+
+/*
+ * argv[0] => lib name
+ * argv[1..argc-1] => lib parameters
+ */
+int 
+dpi_init_once(int argc, char *argv[])
+{
+
+	VLOG_INFO(__FUNCTION__);
+    dpi_lib = dlopen(argv[0], RTLD_NOW|RTLD_GLOBAL);
+    if (!dpi_lib) {
+        VLOG_ERR("failed to open DPI library");
+        return -1; 
+    }
+    engine_init_once = dlsym(dpi_lib, "dpi_engine_init_once");
+    if (!engine_init_once) {
+        goto error;
+    }
+    engine_exit_once = dlsym(dpi_lib, "dpi_engine_exit_once");
+    if (!engine_exit_once) {
+        goto error;
+    }
+    engine_init_perthread = dlsym(dpi_lib, "dpi_engine_init_perthread");
+    if (!engine_init_perthread) {
+        goto error;
+    }
+    engine_exit_perthread = dlsym(dpi_lib, "dpi_engine_exit_perthread");
+    if (!engine_exit_perthread) {
+        goto error;
+    }
+    engine_inject_packet = dlsym(dpi_lib, "dpi_engine_inject_packet");
+    if (!engine_inject_packet) {
+        goto error;
+    }
+
+    optind = 0;
+    return engine_init_once(argc, argv);
+
+error:
+    VLOG_ERR("missing symbol in DPI library");
+    dlclose(dpi_lib);
+    return -1;
+}
+
+
+void
+dpi_exit_once()
+{
+	VLOG_INFO(__FUNCTION__);
+    if (engine_exit_once) {
+        engine_exit_once();
+    }
+    dlclose(dpi_lib);
+	return;
+}
+
+
+void *
+dpi_init_perthread(void)
+{
+	VLOG_INFO(__FUNCTION__);
+    if (engine_init_perthread) {
+        return engine_init_perthread();
+    }
+    return NULL;
+}
+
+
+void
+dpi_exit_perthread(void *opaque)
+{
+	VLOG_INFO(__FUNCTION__);
+    if (engine_exit_perthread) {
+        engine_exit_perthread(opaque);
+    }
+    return;
+}
+
+/*
+ * function output: DPI classification status
+ *  *flow_need_dpi == 1: flow classification in progress (incomplete so far)
+ *  *flow_need_dpi == 0: flow classified, no need to inject more packets
+ */
+int
+dpi_process(void *perthread_opaque, struct ofpbuf * packet, bool *flow_need_dpi)
+{
+    int i;
+    struct timeval tv;
+    char p[256];
+
+    if (!engine_inject_packet) {
+        return 0;
+    }
+
+    if (gettimeofday(&tv, NULL)) { /* XXX: performance killer */
+	    VLOG_ERR("%s: gettimeofday failed(errno=%rds), skipping DPI",
+            __FUNCTION__, errno);
+        return -1;
+    }
+    *flow_need_dpi = engine_inject_packet(perthread_opaque, (char*)packet->data, packet->size , &tv);
+    /* XXX: debug to be removed */
+    for (i=0; i<40; i++) {
+        sprintf(p + 2 * i,"%02x", *((uint8_t *)packet->data + i));
+    }
+    VLOG_DBG("%s: <%s> %s", __FUNCTION__, p, *flow_need_dpi ? "need DPI" : "offloaded");
+	return 0;
+}
+
+
diff --git a/lib/dpi.h b/lib/dpi.h
new file mode 100644
index 0000000..884aab3
--- /dev/null
+++ b/lib/dpi.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright  Qosmos 2000-2014 - All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DPI_H
+#define DPI_H 1
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+#include "vlog.h"
+#include "dpif.h"
+
+int dpi_init_once(int, char **);
+void dpi_exit_once(void);
+void *dpi_init_perthread(void);
+void dpi_exit_perthread(void *);
+int dpi_process(void *, struct ofpbuf *, bool *);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif /* dpi.h */
+
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index 0d5b251..1fa4bc8 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -21,6 +21,7 @@
 
 #include "connmgr.h"
 #include "coverage.h"
+#include "dpi.h"
 #include "dpif.h"
 #include "dynamic-string.h"
 #include "fail-open.h"
@@ -53,6 +54,7 @@ struct handler {
     struct udpif *udpif;               /* Parent udpif. */
     pthread_t thread;                  /* Thread ID. */
     char *name;                        /* Thread name. */
+    void *dpi_opaque;                  /* per thread DPI opaque data */    
 
     struct ovs_mutex mutex;            /* Mutex guarding the following. */
 
@@ -212,7 +214,7 @@ struct flow_miss {
 
     struct xlate_out xout;
 
-    bool put;
+    bool put_or_skip;
 };
 
 static void upcall_destroy(struct upcall *);
@@ -664,6 +666,11 @@ udpif_upcall_handler(void *arg)
 
     handler->name = xasprintf("handler_%u", ovsthread_id_self());
     set_subprogram_name("%s", handler->name);
+    handler->dpi_opaque = dpi_init_perthread();
+    if (!handler->dpi_opaque) {
+        VLOG_ERR("%s: dpi_init_perthread failed", handler->name);
+        return NULL;
+    }
 
     while (!latch_is_set(&handler->udpif->exit_latch)) {
         struct list misses = LIST_INITIALIZER(&misses);
@@ -689,6 +696,7 @@ udpif_upcall_handler(void *arg)
         coverage_clear();
     }
 
+    dpi_exit_perthread(handler->dpi_opaque);
     return NULL;
 }
 
@@ -1010,7 +1018,7 @@ handle_upcalls(struct handler *handler, struct list *upcalls)
                 miss->stats.used = time_msec();
                 miss->stats.tcp_flags = 0;
                 miss->odp_in_port = odp_in_port;
-                miss->put = false;
+                miss->put_or_skip = false;
 
                 n_misses++;
             } else {
@@ -1021,6 +1029,11 @@ handle_upcalls(struct handler *handler, struct list *upcalls)
             miss->stats.n_packets++;
 
             upcall->flow_miss = miss;
+            error = dpi_process(handler->dpi_opaque, packet, &miss->put_or_skip);
+            if (error) {
+                VLOG_ERR("dpi_process");
+                /* XXX: continue anyway or drop? */
+            }
             continue;
         }
 
@@ -1152,16 +1165,19 @@ handle_upcalls(struct handler *handler, struct list *upcalls)
          *    - The datapath already has too many flows.
          *
          *    - An earlier iteration of this loop already put the same flow.
+         *      Or the DPI asked not to put the flow in the datapath as it
+         *      needs the following packets to achieve the classification or
+         *      proceeed to metadata extraction.
          *
          *    - We received this packet via some flow installed in the kernel
          *      already. */
         if (may_put
-            && !miss->put
+            && !miss->put_or_skip
             && upcall->dpif_upcall.type == DPIF_UC_MISS) {
             struct ofpbuf mask;
             bool megaflow;
 
-            miss->put = true;
+            miss->put_or_skip = true;
 
             atomic_read(&enable_megaflows, &megaflow);
             ofpbuf_use_stack(&mask, &miss->mask_buf, sizeof miss->mask_buf);
diff --git a/vswitchd/ovs-vswitchd.c b/vswitchd/ovs-vswitchd.c
index ca76aef..7c318e8 100644
--- a/vswitchd/ovs-vswitchd.c
+++ b/vswitchd/ovs-vswitchd.c
@@ -30,6 +30,7 @@
 #include "compiler.h"
 #include "daemon.h"
 #include "dirs.h"
+#include "dpi.h"
 #include "dpif.h"
 #include "dummy.h"
 #include "fatal-signal.h"
@@ -127,6 +128,7 @@ main(int argc, char *argv[])
     bridge_exit();
     unixctl_server_destroy(unixctl);
     service_stop();
+    dpi_exit_once();
 
     return 0;
 }
@@ -215,7 +217,14 @@ parse_options(int argc, char *argv[], char **unixctl_pathp)
 
     argc -= optind;
     argv += optind;
+    if (dpi_init_once(argc, argv)) {
+        VLOG_FATAL("dpi init failed, continuing anyway");
+    }
 
+    return xasprintf("unix:%s/db.sock", ovs_rundir());
+/* XXX restore the following code or simply transform this 
+ * non-option argument into an option-argument (my ppreference) */
+#if 0
     switch (argc) {
     case 0:
         return xasprintf("unix:%s/db.sock", ovs_rundir());
@@ -227,6 +236,7 @@ parse_options(int argc, char *argv[], char **unixctl_pathp)
         VLOG_FATAL("at most one non-option argument accepted; "
                    "use --help for usage");
     }
+#endif
 }
 
 static void
-- 
1.7.1

