From 19d073b752f45b9954da4aec9eda38b958f6b8ed Mon Sep 17 00:00:00 2001
From: Author Franck Baudin <franck.baudin@qosmos.com>
Date: Thu, 17 Apr 2014 17:21:52 +0200
Subject: [PATCH 1/2] DPI plugin addition proposal v2

Majors update since v1
======================
- DPI classification result stored in NXM_NX_REGs
  This is a temporary trick until AXM implementation.
  AXM stands for "Application eXtensible Matchers"
- Megaflows don't have to be disables, as the 5 tuple
  mandatory for DPI is unwilcared: this code should be
  moved in DPI OpenFlow action (see below)

Major TODO
==========
- DPI action trigerring the DPI procesing: all flows
  are analyzed in the current patch. Only flows sent via
  this action would be processed by DPI.
  Example if we want to run DPI on port 1 ond 2 only:
    ovs-ofctl add-flow br0 in_port=1,action=dpi,output:2
    ovs-ofctl add-flow br0 in_port=2,action=dpi,output:1
  This action would also limit the DPI processing cost only
  on targeted flows.

Design
======

DPI processing may require the first N packets of a given flow,
in order to get a final classification typically until the first
packet with application payload (right after the TCP handshake).

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
completely 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 79428250c2b2b51f233d2c3f74560190ff9fb325. Functional
tests details: http://youtu.be/jkbkvX2B_kI and http://youtu.be/QmnajvSsmHI

Screenshot
==========

root@vswitch:~# cat application-matching-tables-reg0.sh
ovs-ofctl del-flows br0
echo classification in progress 0 0x0
ovs-ofctl add-flow br0 in_port=1,reg0=0,action=output:2
ovs-ofctl add-flow br0 in_port=2,reg0=0,action=output:1
echo HTTP 67 0x43
ovs-ofctl add-flow br0 reg0=67,action="resubmit(,1)"
ovs-ofctl add-flow br0 table=1,in_port=2,action=output:1
ovs-ofctl add-flow br0 table=1,in_port=1,action=mod_vlan_vid:67,output:2
echo ICMP 70 0x46
ovs-ofctl add-flow br0 reg0=70,action="resubmit(,2)"
ovs-ofctl add-flow br0 table=2,in_port=2,action=output:1
ovs-ofctl add-flow br0 table=2,in_port=1,action=mod_vlan_vid:70,output:2
echo SSH 198 0xc6
ovs-ofctl add-flow br0 reg0=198,action="resubmit(,3)"
ovs-ofctl add-flow br0 table=3,in_port=2,action=output:1
ovs-ofctl add-flow br0 table=3,in_port=1,action=mod_vlan_vid:198,output:2

ovs-ofctl dump-flows br0
root@vswitch:~# . application-matching-tables-reg0.sh
classification in progress 0 0x0
HTTP 67 0x43
ICMP 70 0x46
SSH 198 0xc6
NXST_FLOW reply (xid=0x4):
 cookie=0x0, duration=0.026s, table=0, n_packets=0, n_bytes=0, idle_age=0, reg0=0x46 actions=resubmit(,2)
 cookie=0x0, duration=0.013s, table=0, n_packets=0, n_bytes=0, idle_age=0, reg0=0xc6 actions=resubmit(,3)
 cookie=0x0, duration=0.039s, table=0, n_packets=0, n_bytes=0, idle_age=0, reg0=0x43 actions=resubmit(,1)
 cookie=0x0, duration=0.048s, table=0, n_packets=0, n_bytes=0, idle_age=0, reg0=0,in_port=1 actions=output:2
 cookie=0x0, duration=0.044s, table=0, n_packets=0, n_bytes=0, idle_age=0, reg0=0,in_port=2 actions=output:1
 cookie=0x0, duration=0.031s, table=1, n_packets=0, n_bytes=0, idle_age=0, in_port=1 actions=mod_vlan_vid:67,output:2
 cookie=0x0, duration=0.035s, table=1, n_packets=0, n_bytes=0, idle_age=0, in_port=2 actions=output:1
 cookie=0x0, duration=0.017s, table=2, n_packets=0, n_bytes=0, idle_age=0, in_port=1 actions=mod_vlan_vid:70,output:2
 cookie=0x0, duration=0.022s, table=2, n_packets=0, n_bytes=0, idle_age=0, in_port=2 actions=output:1
 cookie=0x0, duration=0.005s, table=3, n_packets=0, n_bytes=0, idle_age=0, in_port=1 actions=mod_vlan_vid:198,output:2
 cookie=0x0, duration=0.009s, table=3, n_packets=0, n_bytes=0, idle_age=0, in_port=2 actions=output:1
root@vswitch:~#

root@vswitch:~/openvswitch#  vswitchd/ovs-vswitchd --pidfile -- /root/sample_flow.so  --hook http:server --hook base:application_end --hook base:application_id_end
2014-04-17T14:52:19Z|00001|dpi|INFO|dpi_init_once
base:application_id_end hooked!

2014-04-17T14:52:22Z|00002|reconnect|INFO|unix:/usr/local/var/run/openvswitch/db.sock: connecting...
2014-04-17T14:52:22Z|00003|reconnect|INFO|unix:/usr/local/var/run/openvswitch/db.sock: connected
2014-04-17T14:52:22Z|00004|dpif|WARN|system@ovs-system: failed to put[create][modify] (Invalid argument) skb_priority(0),skb_mark(0),recirc_id(0x1),dp_hash(0x1),in_port(0),eth(src=00:00:00:00:00:00,dst=00:00:00:00:00:00)
2014-04-17T14:52:22Z|00005|ofproto_dpif|INFO|system@ovs-system: Datapath does not support recirculation
2014-04-17T14:52:22Z|00006|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-04-17T14:52:22Z|00007|ofproto_dpif|INFO|system@ovs-system: MPLS label stack length probed as 0
2014-04-17T14:52:22Z|00001|dpi(handler_1)|INFO|dpi_init_perthread
2014-04-17T14:52:22Z|00001|dpi(handler_2)|INFO|dpi_init_perthread
2014-04-17T14:52:22Z|00008|bridge|INFO|bridge br0: added interface br0 on port 65534
2014-04-17T14:52:22Z|00009|bridge|INFO|bridge br0: added interface eth1 on port 1
2014-04-17T14:52:22Z|00010|bridge|INFO|bridge br0: added interface eth2 on port 2
2014-04-17T14:52:22Z|00011|bridge|INFO|bridge br0: using datapath ID 0000465dd7ed224e
2014-04-17T14:52:22Z|00012|connmgr|INFO|br0: added service controller "punix:/usr/local/var/run/openvswitch/br0.mgmt"
2014-04-17T14:52:22Z|00002|dpi(handler_1)|INFO|dpi_exit_perthread
2014-04-17T14:52:22Z|00002|dpi(handler_2)|INFO|dpi_exit_perthread
2014-04-17T14:52:22Z|00001|dpi(handler_8)|INFO|dpi_init_perthread
2014-04-17T14:52:22Z|00001|dpi(handler_9)|INFO|dpi_init_perthread
2014-04-17T14:52:22Z|00013|bridge|INFO|ovs-vswitchd (Open vSwitch) 2.1.90
2014-04-17T14:52:29Z|00014|memory|INFO|47640 kB peak resident set size after 10.1 seconds
2014-04-17T14:52:29Z|00015|memory|INFO|dispatchers:1 flow_dumpers:1 handlers:2 ports:3 revalidators:2 rules:16
0 s(0x2e9ce78)/base/protocol_end/application_end=icmp
0 s(0x2e9ce78)/base/protocol_end/application_id_end=70
0 s(0x2e9c418)/base/protocol_end/application_end=icmp
0 s(0x2e9c418)/base/protocol_end/application_id_end=70
2014-04-17T14:52:38Z|00016|ofproto|INFO|br0: 12 flow_mods 10 s ago (11 adds, 1 deletes)
0 s(0x2e952d8)/base/protocol_end/application_end=icmp
0 s(0x2e952d8)/base/protocol_end/application_id_end=70
0 s(0x2ea1a78)/base/protocol_end/application_end=icmp
0 s(0x2ea1a78)/base/protocol_end/application_id_end=70
0 s(0x2e952d8)/base/protocol_end/application_end=icmp
0 s(0x2e952d8)/base/protocol_end/application_id_end=70
0 s(0x2e952d8)/base/protocol_end/application_end=icmp
0 s(0x2e952d8)/base/protocol_end/application_id_end=70
0 s(0x2ea1a78)/base/protocol_end/application_end=ssh
0 s(0x2ea1a78)/base/protocol_end/application_id_end=198

root@vswitch:~# ovs-dpctl dump-flows system@ovs-system
skb_priority(0),in_port(2),eth_type(0x0800),ipv4(src=1.1.1.1/255.255.255.255,dst=1.1.1.2/255.255.255.255,proto=6/0xff,tos=0x10/0,ttl=64/0,frag=no/0xff),tcp(src=52157,dst=5022), packets:4773, bytes:316602, used:0.000s, flags:P., actions:push_vlan(vid=198,pcp=0),3
skb_priority(0),in_port(3),eth_type(0x0800),ipv4(src=1.1.1.2/255.255.255.255,dst=1.1.1.1/255.255.255.255,proto=6/0xff,tos=0x10/0,ttl=64/0,frag=no/0xff),tcp(src=5022,dst=52157), packets:20591, bytes:3862734, used:0.000s, flags:P., actions:2

Signed-off-by: Franck Baudin <franck.baudin@qosmos.com>
---
 lib/automake.mk               |    5 +-
 lib/dpi.c                     |  145 +++++++++++++++++++++++++++++++++++++++++
 lib/dpi.h                     |   38 +++++++++++
 ofproto/ofproto-dpif-upcall.c |   29 +++++++-
 ofproto/ofproto-dpif-xlate.c  |    5 ++
 vswitchd/ovs-vswitchd.c       |   10 +++
 6 files changed, 227 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 fcd2c5b..9fade78 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)
 
 if WIN32
 lib_libopenvswitch_la_LIBADD += ${PTHREAD_LIBS}
@@ -54,6 +55,8 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/dpif-netdev.c \
 	lib/dpif-netdev.h \
 	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..95f17fb
--- /dev/null
+++ b/lib/dpi.c
@@ -0,0 +1,145 @@
+/*
+ *   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 *,
+                void *, size_t) = 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,
+    uint32_t classif[],
+    size_t len) /* classif[] len (bytes, not elements nb!) */
+{
+    int i;
+    struct timeval tv;
+
+    if (!engine_inject_packet) {
+        return 0;
+    }
+    if (gettimeofday(&tv, NULL)) { /* XXX: performance killer */
+	    VLOG_ERR("%s: gettimeofday failed(errno=%d), skipping DPI",
+            __FUNCTION__, errno);
+        return -1;
+    }
+    *flow_need_dpi = engine_inject_packet(perthread_opaque, 
+                        (char*)ofpbuf_data(packet), ofpbuf_size(packet), &tv,
+                        (void *)classif, len);
+    VLOG_DBG("%s: app_id=<%d> tags=<0x%x> <%s>", __FUNCTION__, classif[0], classif[1], 
+            *flow_need_dpi ? "need DPI" : "offloaded");
+	return 0;
+}
+
+
diff --git a/lib/dpi.h b/lib/dpi.h
new file mode 100644
index 0000000..3b3d4be
--- /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 *, uint32_t *, size_t);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif /* dpi.h */
+
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index 938cfde..1179fb8 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"
@@ -54,6 +55,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. */
 
@@ -213,7 +215,7 @@ struct flow_miss {
 
     struct xlate_out xout;
 
-    bool put;
+    bool put_or_skip;
 };
 
 static void upcall_destroy(struct upcall *);
@@ -689,6 +691,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);
@@ -717,6 +724,7 @@ udpif_upcall_handler(void *arg)
         coverage_clear();
     }
 
+    dpi_exit_perthread(handler->dpi_opaque);
     return NULL;
 }
 
@@ -1036,7 +1044,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 {
@@ -1047,6 +1055,16 @@ handle_upcalls(struct handler *handler, struct list *upcalls)
             miss->stats.n_packets++;
 
             upcall->flow_miss = miss;
+            /* flow->regs[] updated based on DPI classification */
+            error = dpi_process(handler->dpi_opaque,
+                        packet, 
+                        &miss->put_or_skip,
+                        &miss->flow.regs[0],
+                        FLOW_N_REGS);
+            if (error) {
+                VLOG_ERR("dpi_process");
+                /* XXX: continue anyway or drop? */
+            }
             continue;
         }
 
@@ -1178,16 +1196,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/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 1ba5462..5eb264b 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -3100,6 +3100,11 @@ xlate_actions__(struct xlate_in *xin, struct xlate_out *xout)
     memset(&wc->masks.in_port, 0xff, sizeof wc->masks.in_port);
     memset(&wc->masks.skb_priority, 0xff, sizeof wc->masks.skb_priority);
     memset(&wc->masks.dl_type, 0xff, sizeof wc->masks.dl_type);
+    memset(&wc->masks.nw_src, 0xffffffff, sizeof wc->masks.nw_src);
+    memset(&wc->masks.nw_dst, 0xffffffff, sizeof wc->masks.nw_dst);
+    memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
+    memset(&wc->masks.tp_src, 0xffff, sizeof wc->masks.tp_src);
+    memset(&wc->masks.tp_dst, 0xffff, sizeof wc->masks.tp_dst);
     if (is_ip_any(flow)) {
         wc->masks.nw_frag |= FLOW_NW_FRAG_MASK;
     }
diff --git a/vswitchd/ovs-vswitchd.c b/vswitchd/ovs-vswitchd.c
index 2f3086d..9fd44ad 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"
@@ -132,6 +133,7 @@ main(int argc, char *argv[])
     bridge_exit();
     unixctl_server_destroy(unixctl);
     service_stop();
+    dpi_exit_once();
 
     return 0;
 }
@@ -231,7 +233,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());
@@ -243,6 +252,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

