Luajit API and some examples

Change-Id: Ia140c4750f06870c40b7058c4afb2e20ca633a49
Signed-off-by: Andrew Yourtchenko <ayourtch@gmail.com>
diff --git a/vpp-api/lua/README.md b/vpp-api/lua/README.md
new file mode 100644
index 0000000..3fc93b8
--- /dev/null
+++ b/vpp-api/lua/README.md
@@ -0,0 +1,55 @@
+This is the experimental version of Lua API, aimed for the luajit use.
+
+Please take a look and send the feedback to ayourtch@gmail.com.
+
+To run the examples here:
+
+1) install luajit - "sudo apt-get install luajit" on ubuntu
+
+2) make build-vpp-api in the top directory
+
+3) "make" in this directory to build libcough.so
+
+4) "make run" in a separate terminal window
+
+5) sudo luajit examples/example-cli.lua
+
+This will result in something like this:
+
+libcough detected
+
+Version:        17.01-rc0~37-g8b3191e
+00000000  31 37 2E 30 31 2D 72 63  30 7E 33 37 2D 67 38 62  17.01-rc0~37-g8b
+00000010  33 31 39 31 65 00 00 00  00 00 00 00 00 00 00 00  3191e...........
+
+{ [1] = { ["luaapi_message_name"] = show_version_reply,["program"] = vpe,["version"] = 17.01-rc0~37-g8b3191e,["build_date"] = Fri Nov 11 15:30:21 UTC 2016,["retval"] = 0,["build_directory"] = /home/ubuntu/vpp,["_vl_msg_id"] = 166,["context"] = 0,} ,}
+---
+{ [1] = { ["luaapi_message_name"] = cli_inband_reply,["_vl_msg_id"] = 90,["length"] = 94,["reply"] = vpp v17.01-rc0~37-g8b3191e built by ubuntu on vpp-lapi-commit at Fri Nov 11 15:30:21 UTC 2016
+,["retval"] = 0,["context"] = 0,} ,}
+---
+
+6) You can also run the performance test bench:
+
+$ sudo luajit bench.lua
+libcough detected
+
+10001 iterations, average speed 4108LL per second
+10001 iterations, average speed 4660LL per second
+10001 iterations, average speed 4095LL per second
+10001 iterations, average speed 4542LL per second
+10001 iterations, average speed 8048LL per second
+10001 iterations, average speed 6805LL per second
+10001 iterations, average speed 5170LL per second
+10001 iterations, average speed 6585LL per second
+10001 iterations, average speed 6714LL per second
+10001 iterations, average speed 6942LL per second
+Average tps across the tests: 5766LL
+
+Note: the above is run in an lxd container running inside 2-core
+xhyve VM on a Macbook Pro, so I would not take the performance numbers for granted :)
+
+The "examples" directory contains a few naive examples, as well as a couple of more 
+advanced ones - a tab-completing CLI for VPP that can call both the APIs and CLI,
+and also a small test utility which I use for automating some small tests using
+VPP.
+
diff --git a/vpp-api/lua/bench.lua b/vpp-api/lua/bench.lua
new file mode 100644
index 0000000..edff32b
--- /dev/null
+++ b/vpp-api/lua/bench.lua
@@ -0,0 +1,73 @@
+--[[
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+]]
+
+local vpp = require "vpp-lapi"
+
+local ffi = require "ffi"
+
+ffi.cdef([[
+   struct timespec {
+               long tv_sec;        /* seconds */
+               long tv_nsec;       /* nanoseconds */
+           };
+
+   int clock_gettime(int clk_id, struct timespec *tp);
+]])
+
+
+local time_cache = ffi.new("struct timespec[1]")
+local time_cache_1 = time_cache[0]
+function get_ns()
+  ffi.C.clock_gettime(0, time_cache)
+  return time_cache_1.tv_nsec + 1000000000 * time_cache_1.tv_sec
+end
+
+function do_bench()
+  local cycle_start = get_ns()
+  local n_iterations =  10000
+  local count = 1
+  for i = 1,n_iterations do
+    -- print(i)
+    vpp:api_call("show_version")
+    count = count + 1
+    -- print(i, "done")
+  end
+  cycle_end = get_ns()
+  local tps = n_iterations*1000000000LL/(cycle_end - cycle_start)
+  print (tostring(count) .. " iterations, average speed " .. tostring(tps) .. " per second")
+  return tps
+end
+
+root_dir = "/home/ubuntu/vpp"
+pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+
+vpp:init({ pneum_path = pneum_path })
+
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+
+vpp:connect("lua-bench")
+local n_tests = 10
+local tps_acc = 0LL
+for i=1,n_tests do
+  tps_acc = tps_acc + do_bench()
+end
+print("Average tps across the tests: " .. tostring(tps_acc/n_tests))
+
+vpp:disconnect()
+
+
diff --git a/vpp-api/lua/cough.c b/vpp-api/lua/cough.c
new file mode 100644
index 0000000..4251c73
--- /dev/null
+++ b/vpp-api/lua/cough.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+
+/* 
+ * This is a temporary helper library to seamlessly run against
+ * the current API as exposed by libpneum.
+ * In the future once the sync API is exposed as well as
+ * a way to free the received data, this can go away.
+ */
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+pthread_mutex_t mut;
+pthread_mutex_t *cb_lock = &mut;
+
+void *pneum_handle = NULL;
+
+typedef void* (*arbitrary)();
+
+int (*orig_pneum_connect)(char *name, char *chroot_prefix) = NULL;
+int (*orig_pneum_connect_sync)(char *name, char *chroot_prefix) = NULL;
+int (*orig_pneum_disconnect)(void) = NULL;
+int (*orig_pneum_read)(char **data, int *l) = NULL;
+int (*orig_pneum_write)(char *data, int len) = NULL;
+int (*orig_pneum_has_data)(void) = NULL;
+int (*orig_pneum_data_free)(char *data) = NULL;
+
+arbitrary my_function = NULL;
+
+typedef uint8_t u8;
+typedef uint32_t u32;
+
+u8 *cough_read_buffer;
+u32 cough_read_buffer_size = 1000000;
+u32 cough_read_head = 0;
+u32 cough_read_lock_start = 0; /* lock_start till head is busy memory */
+u32 cough_read_tail = 0;
+
+
+int wrap_pneum_callback(char *data, int len) {
+  // printf("Cough callback! with %d bytes\n", len);
+  pthread_mutex_lock(cb_lock);
+  if(cough_read_lock_start == cough_read_head) {
+    // printf("Reset read head!\n");
+    cough_read_head = 0;
+    cough_read_tail = 0;
+    cough_read_lock_start = 0;
+  }
+  u32 store_len = len;
+  memcpy(cough_read_buffer + cough_read_head, &store_len, sizeof(u32));
+  cough_read_head += sizeof(u32);
+  memcpy(cough_read_buffer + cough_read_head, data, len);
+  cough_read_head += len;
+  pthread_mutex_unlock(cb_lock);
+  return len;
+}
+
+int cough_pneum_attach(char *pneum_fname, char *cough_fname) {
+  /* this is needed to make the pneum aware of the wrap_pneum_callback */
+  pneum_handle = dlopen(cough_fname, RTLD_NOW|RTLD_GLOBAL);
+  /* now let's try to load pneum itself */
+  pneum_handle = dlopen(pneum_fname, RTLD_NOW|RTLD_GLOBAL);
+  if (pneum_handle) {
+    *(void**)(&orig_pneum_connect) = dlsym(pneum_handle,"pneum_connect");
+    *(void**)(&orig_pneum_connect_sync) = dlsym(pneum_handle,"pneum_connect_sync");
+    *(void**)(&orig_pneum_disconnect) = dlsym(pneum_handle,"pneum_disconnect");
+    *(void**)(&orig_pneum_read) = dlsym(pneum_handle,"pneum_read");
+    *(void**)(&orig_pneum_write) = dlsym(pneum_handle,"pneum_write");
+    *(void**)(&orig_pneum_has_data) = dlsym(pneum_handle,"pneum_has_data");
+    *(void**)(&orig_pneum_data_free) = dlsym(pneum_handle,"pneum_data_free");
+    // If you uncomment the below line we pretend we have an async-only libpneum
+    orig_pneum_connect_sync = NULL;
+    cough_read_buffer = malloc(cough_read_buffer_size);
+  } else {
+    printf("Could not get cough handle\n");
+    printf("Error: %s", dlerror());
+    return -1;
+  }
+
+  *(void**)(&my_function) = dlsym(pneum_handle,"something");
+}
+
+
+int pneum_connect(char *name, char *chroot_prefix) {
+  if(orig_pneum_connect) {
+    return(orig_pneum_connect(name, chroot_prefix));
+  } else {
+    printf("COUGH: pneum_connect\n");
+    return -1;
+  }
+}
+int pneum_connect_sync(char *name, char *chroot_prefix) {
+  if(orig_pneum_connect_sync) {
+    int ret = (orig_pneum_connect_sync(name, chroot_prefix));
+    return ret;
+  } else {
+    return(orig_pneum_connect(name, chroot_prefix));
+  }
+}
+
+
+int pneum_disconnect(void) {
+  if(orig_pneum_disconnect) {
+    return orig_pneum_disconnect();
+  } else {
+    printf("COUGH: pneum_disconnect\n");
+    return -1;
+  }
+}
+
+int pneum_has_data(void) {
+  if (orig_pneum_connect_sync) {
+    /* always return 1 in a pass-through case */
+    return 1;
+  } else {
+    // printf("COUGH: pneum_has_data\n");
+    return (cough_read_head != cough_read_tail);
+  }
+}
+
+
+int pneum_read(char **data, int *l) {
+  if(orig_pneum_connect_sync) {
+    return orig_pneum_read(data, l);
+  } else { 
+    while(!pneum_has_data());
+    u32 n_bytes;
+    pthread_mutex_lock(cb_lock);
+    memcpy(&n_bytes, cough_read_buffer + cough_read_tail, sizeof(u32));
+    cough_read_tail += sizeof(u32);
+    void * dataptr = (void *) (cough_read_buffer + cough_read_tail);
+    *data = dataptr;
+    cough_read_tail += n_bytes;
+    *l = n_bytes;
+    pthread_mutex_unlock(cb_lock);
+    return n_bytes; 
+  }
+}
+
+int pneum_write(char *data, int len) {
+  if(orig_pneum_write) {
+    return(orig_pneum_write(data, len));
+  } else {
+    printf("COUGH: pneum_write\n");
+    return -1;
+  }
+}
+
+void pneum_data_free(char *data) {
+  if (orig_pneum_connect_sync) {
+    if(orig_pneum_data_free) {
+      orig_pneum_data_free(data);
+    } else {
+      printf("COUGH: pneum_data_free\n");
+    }
+  } else {
+    u32 *len;
+    uint32_t index = ((u8*)data) - cough_read_buffer;
+    pthread_mutex_lock(cb_lock);
+    if ((index < cough_read_head) && (index > cough_read_lock_start)) {
+      len = (void *)(data - sizeof(u32));
+      cough_read_lock_start = index + *len;
+    }
+    pthread_mutex_unlock(cb_lock);
+  }
+}
diff --git a/vpp-api/lua/examples/cli/README.md b/vpp-api/lua/examples/cli/README.md
new file mode 100644
index 0000000..3a5f8ee
--- /dev/null
+++ b/vpp-api/lua/examples/cli/README.md
@@ -0,0 +1,5 @@
+This is a small experiment to have a wrapper CLI which can call both API functions as well as debug CLI.
+
+To facilitate tab completion and help, the API call names are broken up with spaces replacing the underscores.
+
+
diff --git a/vpp-api/lua/examples/cli/lua-cli.lua b/vpp-api/lua/examples/cli/lua-cli.lua
new file mode 100644
index 0000000..1ad5045
--- /dev/null
+++ b/vpp-api/lua/examples/cli/lua-cli.lua
@@ -0,0 +1,745 @@
+--[[
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+]]
+
+-- Experimental prototype CLI using API to VPP, with tab completion
+--
+-- Written by Andrew Yourtchenko (ayourtch@cisco.com) 2010,2016
+--
+
+vpp = require "vpp-lapi"
+
+
+local dotdotdot = "..."
+
+-- First the "readline" routine
+
+readln = {
+split = function(str, pat)
+  local t = {}  -- NOTE: use {n = 0} in Lua-5.0
+  local fpat = "(.-)" .. pat
+  local last_end = 1
+  if str then
+    local s, e, cap = str:find(fpat, 1)
+    while s do
+      if s ~= 1 or cap ~= "" then
+        table.insert(t,cap)
+      end
+      last_end = e+1
+      s, e, cap = str:find(fpat, last_end)
+    end
+    if last_end <= #str then
+      cap = str:sub(last_end)
+      table.insert(t, cap)
+    end
+  end
+  return t
+end,
+
+reader = function()
+  local rl = {}
+
+  rl.init = function()
+    os.execute("stty -icanon min 1 -echo")
+    rl.rawmode = true
+  end
+
+  rl.done = function()
+    os.execute("stty icanon echo")
+    rl.rawmode = false
+  end
+
+  rl.prompt = ">"
+  rl.history = { "" }
+  rl.history_index = 1
+  rl.history_length = 1
+
+  rl.hide_cmd = function()
+    local bs = string.char(8) .. " " .. string.char(8)
+    for i = 1, #rl.command do
+      io.stdout:write(bs)
+    end
+  end
+
+  rl.show_cmd = function()
+    if rl.command then
+      io.stdout:write(rl.command)
+    end
+  end
+
+  rl.store_history = function(cmd)
+    if cmd == "" then
+      return
+    end
+    rl.history[rl.history_length] = cmd
+    rl.history_length = rl.history_length + 1
+    rl.history_index = rl.history_length
+    rl.history[rl.history_length] = ""
+  end
+
+  rl.readln = function()
+    local done = false
+    local need_prompt = true
+    rl.command = ""
+
+    if not rl.rawmode then
+      rl.init()
+    end
+
+    while not done do
+      if need_prompt then
+        io.stdout:write(rl.prompt)
+	io.stdout:write(rl.command)
+        need_prompt = false
+      end
+
+      local ch = io.stdin:read(1)
+      if ch:byte(1) == 27 then
+        -- CONTROL
+        local ch2 = io.stdin:read(1)
+        -- arrows
+        if ch2:byte(1) == 91 then
+          local ch3 = io.stdin:read(1)
+          local b = ch3:byte(1)
+          if b == 65 then
+            ch = "UP"
+          elseif b == 66 then
+            ch = "DOWN"
+          elseif b == 67 then
+            ch = "RIGHT"
+          elseif b == 68 then
+            ch = "LEFT"
+          end
+          -- print("Byte: " .. ch3:byte(1))
+          -- if ch3:byte(1)
+        end
+      end
+
+      if ch == "?" then
+        io.stdout:write(ch)
+        io.stdout:write("\n")
+        if rl.help then
+          rl.help(rl)
+        end
+        need_prompt = true
+      elseif ch == "\t" then
+        if rl.tab_complete then
+          rl.tab_complete(rl)
+        end
+        io.stdout:write("\n")
+        need_prompt = true
+      elseif ch == "\n" then
+        io.stdout:write(ch)
+        done = true
+      elseif ch == "\004" then
+        io.stdout:write("\n")
+        rl.command = nil
+	done = true
+      elseif ch == string.char(127) then
+        if rl.command ~= "" then
+          io.stdout:write(string.char(8) .. " " .. string.char(8))
+          rl.command = string.sub(rl.command, 1, -2)
+        end
+      elseif #ch > 1 then
+        -- control char
+        if ch == "UP" then
+          rl.hide_cmd()
+          if rl.history_index == #rl.history then
+            rl.history[rl.history_index] = rl.command
+          end
+          if rl.history_index > 1 then
+            rl.history_index = rl.history_index - 1
+            rl.command = rl.history[rl.history_index]
+          end
+          rl.show_cmd()
+        elseif ch == "DOWN" then
+          rl.hide_cmd()
+          if rl.history_index < rl.history_length then
+            rl.history_index = rl.history_index + 1
+            rl.command = rl.history[rl.history_index]
+          end
+          rl.show_cmd()
+        end
+      else
+        io.stdout:write(ch)
+        rl.command = rl.command .. ch
+      end
+    end
+    if rl.command then
+      rl.store_history(rl.command)
+    end
+    return rl.command
+  end
+  return rl
+end
+
+}
+
+--[[
+
+r = reader()
+
+local done = false
+
+while not done do
+  local cmd = r.readln()
+  print("Command: " .. tostring(cmd))
+  if not cmd or cmd == "quit" then
+    done = true
+  end
+end
+
+r.done()
+
+]]
+
+--------- MDS show tech parser
+
+local print_section = nil
+local list_sections = false
+
+local curr_section = "---"
+local curr_parser = nil
+
+-- by default operate in batch mode
+local batch_mode = true
+
+local db = {}
+local device = {}
+device.output = {}
+local seen_section = {}
+
+function start_collection(name)
+  device = {}
+  seen_section = {}
+end
+
+function print_error(errmsg)
+  print("@#$:" .. errmsg)
+end
+
+function keys(tbl)
+  local t = {}
+  for k, v in pairs(tbl) do
+    table.insert(t, k)
+  end
+  return t
+end
+
+function tset (parent, ...)
+
+  -- print ('set', ...)
+
+  local len = select ('#', ...)
+  local key, value = select (len-1, ...)
+  local cutpoint, cutkey
+
+  for i=1,len-2 do
+
+    local key = select (i, ...)
+    local child = parent[key]
+
+    if value == nil then
+      if child == nil then  return
+      elseif next (child, next (child)) then  cutpoint = nil  cutkey = nil
+      elseif cutpoint == nil then  cutpoint = parent  cutkey = key  end
+
+    elseif child == nil then  child = {}  parent[key] = child  end
+
+    parent = child
+    end
+
+  if value == nil and cutpoint then  cutpoint[cutkey] = nil
+  else  parent[key] = value  return value  end
+  end
+
+
+function tget (parent, ...)
+  local len = select ('#', ...)
+  for i=1,len do
+    parent = parent[select (i, ...)]
+    if parent == nil then  break  end
+    end
+  return parent
+  end
+
+
+local pager_lines = 23
+local pager_printed = 0
+local pager_skipping = false
+local pager_filter_pipe = nil
+
+function pager_reset()
+  pager_printed = 0
+  pager_skipping = false
+  if pager_filter_pipe then
+    pager_filter_pipe:close()
+    pager_filter_pipe = nil
+  end
+end
+
+
+function print_more()
+  io.stdout:write(" --More-- ")
+end
+
+function print_nomore()
+  local bs = string.char(8)
+  local bs10 = bs ..  bs ..  bs ..  bs ..  bs ..  bs ..  bs ..  bs ..  bs ..  bs
+  io.stdout:write(bs10 .. "          " .. bs10)
+end
+
+function print_line(txt)
+  if pager_filter_pipe then
+    pager_filter_pipe:write(txt .. "\n")
+    return
+  end
+  if pager_printed >= pager_lines then
+    print_more()
+    local ch = io.stdin:read(1)
+    if ch == " " then
+      pager_printed = 0
+    elseif ch == "\n" then
+      pager_printed = pager_printed - 1
+    elseif ch == "q" then
+      pager_printed = 0
+      pager_skipping = true
+    end
+    print_nomore()
+  end
+  if not pager_skipping then
+    print(txt)
+    pager_printed = pager_printed + 1
+  else
+    -- skip printing
+  end
+end
+
+function paged_write(text)
+  local t = readln.split(text, "[\n]")
+  if string.sub(text, -1) == "\n" then
+    table.insert(t, "")
+  end
+  for i, v in ipairs(t) do
+    if i < #t then
+      print_line(v)
+    else
+      if pager_filter_pipe then
+        pager_filter_pipe:write(v)
+      else
+        io.stdout:write(v)
+      end
+    end
+  end
+end
+
+
+
+
+
+function get_choices(tbl, key)
+  local res = {}
+  for k, v in pairs(tbl) do
+    if string.sub(k, 1, #key) == key then
+      table.insert(res, k)
+    elseif 0 < #key and dotdotdot == k then
+      table.insert(res, k)
+    end
+  end
+  return res
+end
+
+function get_exact_choice(choices, val)
+  local exact_idx = nil
+  local substr_idx = nil
+  local substr_seen = false
+
+  if #choices == 1 then
+    if choices[1] == dotdotdot then
+      return 1
+    elseif string.sub(choices[1], 1, #val) == val then
+      return 1
+    else
+      return nil
+    end
+  else
+    for i, v in ipairs(choices) do
+      if v == val then
+        exact_idx = i
+        substr_seen = true
+      elseif choices[i] ~= dotdotdot and string.sub(choices[i], 1, #val) == val then
+        if substr_seen then
+          substr_idx = nil
+        else
+          substr_idx = i
+          substr_seen = true
+        end
+      elseif choices[i] == dotdotdot then
+        if substr_seen then
+          substr_idx = nil
+        else
+          substr_idx = i
+          substr_seen = true
+        end
+      end
+    end
+  end
+  return exact_idx or substr_idx
+end
+
+function device_cli_help(rl)
+  local key = readln.split(rl.command, "[ ]+")
+  local tree = rl.tree
+  local keylen = #key
+  local fullcmd = ""
+  local error = false
+  local terse = true
+
+  if ((#rl.command >= 1) and (string.sub(rl.command, -1) == " ")) or (#rl.command == 0) then
+    table.insert(key, "")
+    terse = false
+  end
+
+  for i, v in ipairs(key) do
+    local choices = get_choices(tree, v)
+    local idx = get_exact_choice(choices, v)
+    if idx then
+      local choice = choices[idx]
+      tree = tree[choice]
+      fullcmd = fullcmd .. choice .. " "
+    else
+      if i < #key then
+        error = true
+      end
+    end
+
+    if i == #key and not error then
+      for j, w in ipairs(choices) do
+        if terse then
+          paged_write(w .. "\t")
+        else
+          paged_write("  " .. w .. "\n")
+        end
+      end
+      paged_write("\n")
+      if terse then
+        paged_write(" \n")
+      end
+    end
+  end
+  pager_reset()
+end
+
+function device_cli_tab_complete(rl)
+  local key = readln.split(rl.command, "[ ]+")
+  local tree = rl.tree
+  local keylen = #key
+  local fullcmd = ""
+  local error = false
+
+  for i, v in ipairs(key) do
+    local choices = get_choices(tree, v)
+    local idx = get_exact_choice(choices, v)
+    if idx and choices[idx] ~= dotdotdot then
+      local choice = choices[idx]
+      tree = tree[choice]
+      -- print("level " .. i .. " '" .. choice .. "'")
+      fullcmd = fullcmd .. choice .. " "
+    else
+      -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ")
+      error = true
+    end
+  end
+  if not error then
+    rl.command = fullcmd
+  else
+    -- print("\n\nerror\n")
+  end
+  pager_reset()
+end
+
+function device_cli_exec(rl)
+
+  local cmd_nopipe = rl.command
+  local cmd_pipe = nil
+
+  local pipe1, pipe2 = string.find(rl.command, "[|]")
+  if pipe1 then
+    cmd_nopipe = string.sub(rl.command, 1, pipe1-1)
+    cmd_pipe = string.sub(rl.command, pipe2+1, -1)
+  end
+
+  local key = readln.split(cmd_nopipe .. " <cr>", "[ ]+")
+  local tree = rl.tree
+  local keylen = #key
+  local fullcmd = ""
+  local error = false
+  local func = nil
+
+  if cmd_pipe then
+    pager_filter_pipe = io.popen(cmd_pipe, "w")
+  end
+
+
+  rl.choices = {}
+
+  for i, v in ipairs(key) do
+    local choices = get_choices(tree, v)
+    local idx = get_exact_choice(choices, v)
+    if idx then
+      local choice = choices[idx]
+      if i == #key then
+        func = tree[choice]
+      else
+        if choice == dotdotdot then
+          -- keep the tree the same, update the choice value to match the input string
+          choices[idx] = v
+          choice = v
+        else
+          tree = tree[choice]
+        end
+      end
+      -- print("level " .. i .. " '" .. choice .. "'")
+      table.insert(rl.choices, choice)
+    else
+      -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ")
+      error = true
+      return nil
+    end
+  end
+  return func
+end
+
+function populate_tree(commands)
+  local tree = {}
+
+  for k, v in pairs(commands) do
+    local key = readln.split(k .. " <cr>", "[ ]+")
+    local xtree = tree
+    for i, kk in ipairs(key) do
+      if i == 1 and kk == "sh" then
+        kk = "show"
+      end
+      if i == #key then
+        if type(v) == "function" then
+          xtree[kk] = v
+        else
+          xtree[kk] = function(rl) paged_write(table.concat(v, "\n") .. "\n") end
+        end
+      else
+        if not xtree[kk] then
+          xtree[kk] = {}
+        end
+        xtree = xtree[kk]
+      end
+    end
+  end
+  return tree
+end
+
+function trim (s)
+  return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
+end
+
+
+function init_vpp(vpp)
+  local root_dir = "/home/ubuntu/vpp"
+  local pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+
+  vpp:init({ pneum_path = pneum_path })
+
+  vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+  vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+
+  vpp:connect("lua_cli")
+end
+
+function run_cli(vpp, cli)
+  local reply = vpp:api_call("cli_inband", { cmd = cli })
+  if reply and #reply == 1 then
+    local rep = reply[1]
+    if 0 == rep.retval then
+      return rep.reply
+    else
+      return "XXXXXLUACLI: API RETVAL ERROR : " .. tostring(rep.retval)
+    end
+  else
+    return "XXXXXLUACLI ERROR, RAW REPLY: " .. vpp.dump(reply)
+  end
+end
+
+
+function toprintablestring(s)
+  if type(s) == "string" then
+    return "\n"..vpp.hex_dump(s)
+  else
+    return tostring(s)
+  end
+end
+
+function interactive_cli(r)
+  while not done do
+    pager_reset()
+    local cmd = r.readln()
+    if not cmd then
+      done = true
+    elseif cmd == "quit" or cmd == "exit" then
+      done = true
+    else
+      local func = device_cli_exec(r)
+      if func then
+	func(r)
+      else
+	if trim(cmd) == "" then
+	else
+	  for i = 1, #r.prompt do
+	    paged_write(" ")
+	  end
+	  paged_write("^\n% Invalid input detected at '^' marker.\n\n")
+	end
+      end
+    end
+  end
+end
+
+device = {}
+device.output = {}
+
+init_vpp(vpp)
+cmds_str = run_cli(vpp, "?")
+vpp_cmds = readln.split(cmds_str, "\n")
+vpp_clis = {}
+
+for linenum, line in ipairs(vpp_cmds) do
+  local m,h = string.match(line, "^  (.-)  (.*)$")
+  if m and #m > 0 then
+    table.insert(vpp_clis, m)
+    device.output["vpp debug cli " .. m] = function(rl)
+      -- print("ARBITRARY CLI" .. vpp.dump(rl.choices))
+      print("LUACLI command: " .. table.concat(rl.choices, " "))
+      local sub = {}
+      --
+      for i=4, #rl.choices -1 do
+        table.insert(sub, rl.choices[i])
+      end
+      local cli = table.concat(sub, " ")
+      print("Running CLI: " .. tostring(cli))
+      paged_write(run_cli(vpp, cli))
+    end
+    device.output["vpp debug cli " .. m .. " " .. dotdotdot] = function(rl)
+      print("ARGH")
+    end
+
+    local ret = run_cli(vpp, "help " .. m)
+    device.output["help vpp debug cli " .. m] = { ret }
+  end
+end
+
+for linenum, line in ipairs(vpp_clis) do
+  -- print(line, ret)
+end
+
+for msgnum, msgname in ipairs(vpp.msg_number_to_name) do
+  local cli, numspaces = string.gsub(msgname, "_", " ")
+  device.output["call " .. cli .. " " .. dotdotdot] = function(rl)
+    print("ARGH")
+  end
+  device.output["call " .. cli] = function(rl)
+    print("LUACLI command: " .. table.concat(rl.choices, " "))
+    print("Running API: " .. msgname) -- vpp.dump(rl.choices))
+    local out = {}
+    local args = {}
+    local ntaken = 0
+    local argname = ""
+    for i=(1+1+numspaces+1), #rl.choices-1 do
+      -- print(i, rl.choices[i])
+      if ntaken > 0 then
+        ntaken = ntaken -1
+      else
+        local fieldname = rl.choices[i]
+        local field = vpp.msg_name_to_fields[msgname][fieldname]
+        if field then
+          local s = rl.choices[i+1]
+          s=s:gsub("\\x(%x%x)",function (x) return string.char(tonumber(x,16)) end)
+          args[fieldname] = s
+          ntaken = 1
+        end
+      end
+    end
+    -- print("ARGS: ", vpp.dump(args))
+    local ret = vpp:api_call(msgname, args)
+    for i, reply in ipairs(ret) do
+      table.insert(out, "=================== Entry #" .. tostring(i))
+      for k, v in pairs(reply) do
+        table.insert(out, "   " .. tostring(k) .. " : " .. toprintablestring(v))
+      end
+    end
+    -- paged_write(vpp.dump(ret) .. "\n\n")
+    paged_write(table.concat(out, "\n").."\n\n")
+  end
+  device.output["call " .. cli .. " help"] = function(rl)
+    local out = {}
+    for k, v in pairs(vpp.msg_name_to_fields[msgname]) do
+      table.insert(out, tostring(k) .. " : " .. v["ctype"] .. " ; " .. tostring(vpp.dump(v)) )
+    end
+    -- paged_write(vpp.dump(vpp.msg_name_to_fields[msgname]) .. "\n\n")
+    paged_write(table.concat(out, "\n").."\n\n")
+  end
+-- vpp.msg_name_to_number = {}
+end
+
+
+
+local r = readln.reader()
+local done = false
+
+r.prompt = "VPP(luaCLI)#"
+
+r.help = device_cli_help
+r.tab_complete = device_cli_tab_complete
+print("===== CLI view, use ^D to end =====")
+
+r.tree = populate_tree(device.output)
+-- readln.pretty("xxxx", r.tree)
+
+
+for idx, an_arg in ipairs(arg) do
+  local fname = an_arg
+  if fname == "-i" then
+    pager_lines = 23
+    interactive_cli(r)
+  else
+    pager_lines = 100000000
+    for line in io.lines(fname) do
+      r.command = line
+      local func = device_cli_exec(r)
+      if func then
+	func(r)
+      end
+    end
+  end
+end
+
+if #arg == 0 then
+  print("You should specify '-i' as an argument for the interactive session,")
+  print("but with no other sources of commands, we start interactive session now anyway")
+   interactive_cli(r)
+end
+
+vpp:disconnect()
+r.done()
+
+
diff --git a/vpp-api/lua/examples/example-acl-plugin.lua b/vpp-api/lua/examples/example-acl-plugin.lua
new file mode 100644
index 0000000..ca01f18
--- /dev/null
+++ b/vpp-api/lua/examples/example-acl-plugin.lua
@@ -0,0 +1,110 @@
+--[[
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+]]
+
+
+vpp = require "vpp-lapi"
+
+root_dir = "/home/ubuntu/vpp"
+pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+
+vpp:init({ pneum_path = pneum_path })
+
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+vpp:connect("aytest")
+vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl")
+
+-- api calls
+reply = vpp:api_call("show_version")
+print("Version: ", reply[1].version)
+print(vpp.hex_dump(reply[1].version))
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add", { context = 42, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add", { context = 42, count = 0 })
+print(vpp.dump(reply))
+print("---")
+
+acl_index_to_delete = reply[1].acl_index
+print("Deleting " .. tostring(acl_index_to_delete))
+reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+for ri, rv in ipairs(reply) do 
+  print("Reply message #" .. tostring(ri))
+  print(vpp.dump(rv))
+  for ai, av in ipairs(rv.r) do
+    print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av))
+  end
+   
+end
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+print(vpp.dump(reply))
+print("---")
+
+
+vpp:disconnect()
+
+
diff --git a/vpp-api/lua/examples/example-classifier.lua b/vpp-api/lua/examples/example-classifier.lua
new file mode 100644
index 0000000..a3fa45f
--- /dev/null
+++ b/vpp-api/lua/examples/example-classifier.lua
@@ -0,0 +1,51 @@
+--[[
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+]]
+
+
+local vpp = require "vpp-lapi"
+local bit = require("bit")
+
+root_dir = "/home/ubuntu/vpp"
+pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+
+vpp:init({ pneum_path = pneum_path })
+
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+
+vpp:connect("aytest")
+
+-- api calls
+
+print("Calling API to add a new classifier table")
+reply = vpp:api_call("classify_add_del_table", {
+  context = 43,
+  memory_size = bit.lshift(2, 20),
+  client_index = 42,
+  is_add = 1,
+  nbuckets = 32,
+  skip_n_vectors = 0,
+  match_n_vectors = 1,
+  mask = "\255\255\255\255\255\255\255\255" .. "\255\255\255\255\255\255\255\255"
+})
+print(vpp.dump(reply))
+print("---")
+
+
+vpp:disconnect()
+
+
diff --git a/vpp-api/lua/examples/example-cli.lua b/vpp-api/lua/examples/example-cli.lua
new file mode 100644
index 0000000..656feae
--- /dev/null
+++ b/vpp-api/lua/examples/example-cli.lua
@@ -0,0 +1,45 @@
+--[[
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+]]
+
+vpp = require "vpp-lapi"
+
+root_dir = "/home/ubuntu/vpp"
+pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+
+vpp:init({ pneum_path = pneum_path })
+
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+
+vpp:connect("aytest")
+
+-- api calls
+reply = vpp:api_call("show_version")
+print("Version: ", reply[1].version)
+print(vpp.hex_dump(reply[1].version))
+print(vpp.dump(reply))
+print("---")
+
+
+reply = vpp:api_call("cli_inband", { cmd = "show vers" }) 
+print(vpp.dump(reply))
+print("---")
+
+
+vpp:disconnect()
+
+
diff --git a/vpp-api/lua/examples/lute/README.md b/vpp-api/lua/examples/lute/README.md
new file mode 100644
index 0000000..8d37250
--- /dev/null
+++ b/vpp-api/lua/examples/lute/README.md
@@ -0,0 +1,66 @@
+LUTE: Lua Unit Test Environment
+
+This is a small helper utility to automate some simple tests
+that one might need to do.
+
+Think of it as a hybrid of a screen and expect who
+also took some habits from HTML inline code.
+
+It is quite probably useless for building anything serious,
+but practice shows it is quite efficient at allowing
+convenient temporary quick tests, and for something
+that was written over a course of a couple of evenings it
+is quite a nice little helper tool.
+
+It allows do launch and drive multiple shell sessions,
+and by virtue of having been written in Lua, it of course
+also allows to add the business logic using the Lua code.
+
+If you launch the lute without parameters, it gives you
+the interactive shell to execute the commands in.
+
+If you launch it with an argument, it will attempt to
+read and execute the commands from the file.
+
+Commands:
+
+shell FOO
+
+  spawn a shell in a new PTY under the label FOO.
+
+run FOO bar
+
+  Send "bar" keystrokes followed by "ENTER" to the session FOO
+
+  Special case: "break" word on its own gets translated into ^C being sent.
+
+cd FOO
+
+  "change domain" into session FOO. All subsequent inputs will go,
+  line-buffered, into the session FOO. To jump back up, use ^D (Control-D),
+  or within the file, use ^D^D^D (caret D caret D caret D on its own line)
+
+expect FOO blablabla
+
+  Pause further interpretation of the batch mode until you see "blablabla"
+  in the output of session FOO, or until timeout happens.
+
+sleep N
+
+  Sleep an integer N seconds, if you are in batch mode.
+
+echo blabla
+
+  Echo the remainder of the line to standard output.
+
+For Lua code, there is a pre-existing pseudo-session called "lua",
+which accepts "run lua" command which does what you would expect
+(evaluate the rest of the string in Lua context - being the same
+as lute itself). Also you can do "cd lua" and get into a
+multiline-enabled interpreter shell.
+
+This way for the VPP case you can automate some of the things in your routine
+that you would have to have done manually, and test drive API as well
+as use the realistic native OS components to create the environment around it.
+
+
diff --git a/vpp-api/lua/examples/lute/lute.lua b/vpp-api/lua/examples/lute/lute.lua
new file mode 100644
index 0000000..89b9924
--- /dev/null
+++ b/vpp-api/lua/examples/lute/lute.lua
@@ -0,0 +1,777 @@
+--[[
+version = 1
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+]]
+
+-- LUTE: Lua Unit Test Environment
+-- AKA what happens when screen tries to marry with lua and expect,
+-- but escapes mid-ceremony.
+--
+-- comments: @ayourtch
+
+ffi = require("ffi")
+
+vpp = {}
+function vpp.dump(o)
+   if type(o) == 'table' then
+      local s = '{ '
+      for k,v in pairs(o) do
+         if type(k) ~= 'number' then k = '"'..k..'"' end
+         s = s .. '['..k..'] = ' .. vpp.dump(v) .. ','
+      end
+      return s .. '} '
+   else
+      return tostring(o)
+   end
+end
+
+
+ffi.cdef([[
+
+int posix_openpt(int flags);
+int grantpt(int fd);
+int unlockpt(int fd);
+char *ptsname(int fd);
+
+typedef long pid_t;
+typedef long ssize_t;
+typedef long size_t;
+typedef int nfds_t;
+typedef long time_t;
+typedef long suseconds_t;
+
+pid_t fork(void);
+pid_t setsid(void);
+
+int close(int fd);
+int open(char *pathname, int flags);
+
+int dup2(int oldfd, int newfd);
+
+ssize_t read(int fd, void *buf, size_t count);
+ssize_t write(int fd, const void *buf, size_t count);
+
+struct pollfd {
+               int   fd;         /* file descriptor */
+               short events;     /* requested events */
+               short revents;    /* returned events */
+           };
+
+int poll(struct pollfd *fds, nfds_t nfds, int timeout);
+
+struct timeval {
+               time_t      tv_sec;     /* seconds */
+               suseconds_t tv_usec;    /* microseconds */
+           };
+
+int gettimeofday(struct timeval *tv, struct timezone *tz);
+
+int inet_pton(int af, const char *src, void *dst);
+
+]])
+
+ffi.cdef([[
+void *memset(void *s, int c, size_t n);
+void *memcpy(void *dest, void *src, size_t n);
+void *memmove(void *dest, const void *src, size_t n);
+void *memmem(const void *haystack, size_t haystacklen,
+        const void *needle, size_t needlelen);
+]])
+
+
+
+local O_RDWR = 2
+
+
+function os_time()
+  local tv = ffi.new("struct timeval[1]")
+  local ret = ffi.C.gettimeofday(tv, nil)
+  return tonumber(tv[0].tv_sec) + (tonumber(tv[0].tv_usec)/1000000.0)
+end
+
+function sleep(n)
+  local when_wakeup = os_time() + n
+  while os_time() <= when_wakeup do
+    ffi.C.poll(nil, 0, 10)
+  end
+end
+
+
+function c_str(text_in)
+  local text = text_in 
+  local c_str = ffi.new("char[?]", #text+1)
+  ffi.copy(c_str, text)
+  return c_str
+end
+
+function ip46(addr_text)
+  local out = ffi.new("char [200]")
+  local AF_INET6 = 10
+  local AF_INET = 2
+  local is_ip6 = ffi.C.inet_pton(AF_INET6, c_str(addr_text), out)
+  if is_ip6 == 1 then
+    return ffi.string(out, 16), true
+  end
+  local is_ip4 = ffi.C.inet_pton(AF_INET, c_str(addr_text), out)
+  if is_ip4 then
+    return (string.rep("4", 12).. ffi.string(out, 4)), false
+  end
+end
+
+function pty_master_open()
+  local fd = ffi.C.posix_openpt(O_RDWR)
+  ffi.C.grantpt(fd)
+  ffi.C.unlockpt(fd)
+  local p = ffi.C.ptsname(fd)
+  print("PTS:" .. ffi.string(p))
+  return fd, ffi.string(p)
+end
+
+function pty_run(cmd)
+  local master_fd, pts_name = pty_master_open()
+  local child_pid = ffi.C.fork()
+  if (child_pid == -1) then
+    print("Error fork()ing")
+    return -1
+  end 
+ 
+  if child_pid ~= 0 then
+    -- print("Parent")
+    return master_fd, child_pid
+  end
+
+  -- print("Child")
+  if (ffi.C.setsid() == -1) then
+    print("Child error setsid")
+    os.exit(-1)
+  end
+
+  ffi.C.close(master_fd)
+
+  local slave_fd = ffi.C.open(c_str(pts_name), O_RDWR)
+  if slave_fd == -1 then
+    print("Child can not open slave fd")
+    os.exit(-2)
+  end
+
+  ffi.C.dup2(slave_fd, 0)
+  ffi.C.dup2(slave_fd, 1)
+  ffi.C.dup2(slave_fd, 2)
+  os.execute(cmd)
+end
+
+function readch()
+  local buf = ffi.new("char[1]")
+  local nread= ffi.C.read(0, buf, 1)
+  -- print("\nREADCH : " .. string.char(buf[0]))
+  return string.char(buf[0])
+end
+
+function stdout_write(str)
+  ffi.C.write(1, c_str(str), #str)
+end
+
+
+readln = {
+split = function(str, pat)
+  local t = {}  -- NOTE: use {n = 0} in Lua-5.0
+  local fpat = "(.-)" .. pat
+  local last_end = 1
+  if str then
+    local s, e, cap = str:find(fpat, 1)
+    while s do
+      if s ~= 1 or cap ~= "" then
+        table.insert(t,cap)
+      end
+      last_end = e+1
+      s, e, cap = str:find(fpat, last_end)
+    end
+    if last_end <= #str then
+      cap = str:sub(last_end)
+      table.insert(t, cap)
+    end
+  end
+  return t
+end,
+
+reader = function()
+  local rl = {}
+
+  rl.init = function()
+    os.execute("stty -icanon min 1 -echo")
+    rl.rawmode = true
+  end
+
+  rl.done = function()
+    os.execute("stty icanon echo")
+    rl.rawmode = false
+  end
+
+  rl.prompt = ">"
+  rl.history = { "" }
+  rl.history_index = 1
+  rl.history_length = 1
+
+  rl.hide_cmd = function()
+    local bs = string.char(8) .. " " .. string.char(8)
+    for i = 1, #rl.command do
+      stdout_write(bs)
+    end
+  end
+
+  rl.show_cmd = function()
+    if rl.command then
+      stdout_write(rl.command)
+    end
+  end
+
+  rl.store_history = function(cmd)
+    if cmd == "" then
+      return
+    end
+    rl.history[rl.history_length] = cmd
+    rl.history_length = rl.history_length + 1
+    rl.history_index = rl.history_length
+    rl.history[rl.history_length] = ""
+  end
+
+  rl.readln = function(stdin_select_fn, batch_cmd, batch_when, batch_expect)
+    local done = false
+    local need_prompt = true
+    rl.command = ""
+
+    if not rl.rawmode then
+      rl.init()
+    end
+
+    while not done do
+      local indent_value = #rl.prompt + #rl.command
+      if need_prompt then
+        stdout_write(rl.prompt)
+        stdout_write(rl.command)
+        need_prompt = false
+      end
+      if type(stdin_select_fn) == "function" then
+        while not stdin_select_fn(indent_value, batch_cmd, batch_when, batch_expect) do
+          stdout_write(rl.prompt)
+          stdout_write(rl.command)
+          indent_value = #rl.prompt + #rl.command
+        end
+        if batch_cmd and ((os_time() > batch_when) or (batch_expect and expect_success(batch_expect, buf, 0))) then
+          stdout_write("\n" .. rl.prompt .. batch_cmd .. "\n")
+          if batch_expect then
+            expect_done(batch_expect)
+          end
+          return batch_cmd, batch_expect
+        end
+      end
+      local ch = readch()
+      if ch:byte(1) == 27 then
+        -- CONTROL
+        local ch2 = readch()
+        -- arrows
+        if ch2:byte(1) == 91 then
+          local ch3 = readch()
+          local b = ch3:byte(1)
+          if b == 65 then
+            ch = "UP"
+          elseif b == 66 then
+            ch = "DOWN"
+          elseif b == 67 then
+            ch = "RIGHT"
+          elseif b == 68 then
+            ch = "LEFT"
+          end
+          -- print("Byte: " .. ch3:byte(1))
+          -- if ch3:byte(1)
+        end
+      end
+
+      if ch == "?" then
+        stdout_write(ch)
+        stdout_write("\n")
+        if rl.help then
+          rl.help(rl)
+        end
+        need_prompt = true
+      elseif ch == "\t" then
+        if rl.tab_complete then
+          rl.tab_complete(rl)
+        end
+        stdout_write("\n")
+        need_prompt = true
+      elseif ch == "\n" then
+        stdout_write(ch)
+        done = true
+      elseif ch == "\004" then
+        stdout_write("\n")
+        rl.command = nil
+        done = true
+      elseif ch == string.char(127) then
+        if rl.command ~= "" then
+          stdout_write(string.char(8) .. " " .. string.char(8))
+          rl.command = string.sub(rl.command, 1, -2)
+        end
+      elseif #ch > 1 then
+        -- control char
+        if ch == "UP" then
+          rl.hide_cmd()
+          if rl.history_index == #rl.history then
+            rl.history[rl.history_index] = rl.command
+          end
+          if rl.history_index > 1 then
+            rl.history_index = rl.history_index - 1
+            rl.command = rl.history[rl.history_index]
+          end
+          rl.show_cmd()
+        elseif ch == "DOWN" then
+          rl.hide_cmd()
+          if rl.history_index < rl.history_length then
+            rl.history_index = rl.history_index + 1
+            rl.command = rl.history[rl.history_index]
+          end
+          rl.show_cmd()
+        end
+      else
+        stdout_write(ch)
+        rl.command = rl.command .. ch
+      end
+    end
+    if rl.command then
+      rl.store_history(rl.command)
+    end
+    return rl.command
+  end
+  return rl
+end
+
+}
+
+local select_fds = {}
+local sessions = {}
+
+local line_erased = false
+
+function erase_line(indent)
+  if not line_erased then
+    line_erased = true
+    stdout_write(string.rep(string.char(8), indent)..string.rep(" ", indent)..string.rep(string.char(8), indent))
+  end
+end
+
+function do_select_stdin(indent, batch_cmd, batch_when, batch_expect)
+  while true do
+    local nfds = 1+#select_fds
+    local pfds = ffi.new("struct pollfd[?]", nfds)
+    pfds[0].fd = 0;
+    pfds[0].events = 1;
+    pfds[0].revents = 0;
+    for i = 1,#select_fds do
+      pfds[i].fd = select_fds[i].fd
+      pfds[i].events = 1
+      pfds[i].revents = 0
+    end
+    if batch_cmd and ((os_time() > batch_when) or (batch_expect and expect_success(batch_expect, buf, 0))) then
+      return true
+    end
+    while ffi.C.poll(pfds, nfds, 10) == 0 do
+      if batch_cmd and ((os_time() > batch_when) or (batch_expect and expect_success(batch_expect, buf, 0))) then
+        return true
+      end
+      if line_erased then
+        line_erased = false
+        return false
+      end
+    end
+    if pfds[0].revents == 1 then
+      return true
+    end
+    for i = 1,#select_fds do
+      if(pfds[i].revents > 0) then
+        if pfds[i].fd ~= select_fds[i].fd then
+          print("File descriptors unequal", pfds[i].fd, select_fds[i].fd)
+        end
+        select_fds[i].cb(select_fds[i], pfds[i].revents, indent)
+      end
+    end
+  end
+end
+
+local buf = ffi.new("char [32768]")
+
+function session_stdout_write(prefix, data)
+  data = prefix .. data:gsub("\n", "\n"..prefix):gsub("\n"..prefix.."$", "\n")
+  
+  stdout_write(data)
+end
+
+function expect_success(sok, buf, nread)
+  local expect_buf_sz = ffi.sizeof(sok.expect_buf) - 128
+  local expect_buf_avail = expect_buf_sz - sok.expect_buf_idx
+  -- print("EXPECT_SUCCESS: nread ".. tostring(nread).. " expect_buf_idx: " .. tostring(sok.expect_buf_idx) .. " expect_buf_avail: " .. tostring(expect_buf_avail) )
+  if expect_buf_avail < 0 then
+    print "EXPECT BUFFER OVERRUN ALREADY"
+    os.exit(1)
+  end
+  if expect_buf_avail < nread then
+    if (nread >= ffi.sizeof(sok.expect_buf)) then
+      print("Read too large of a chunk to fit into expect buffer")
+      return nil
+    end
+    local delta = nread - expect_buf_avail
+
+    ffi.C.memmove(sok.expect_buf, sok.expect_buf + delta, expect_buf_sz - delta)
+    sok.expect_buf_idx = sok.expect_buf_idx - delta
+    expect_buf_avail = nread 
+  end
+  if sok.expect_buf_idx + nread > expect_buf_sz then
+    print("ERROR, I have just overrun the buffer !")
+    os.exit(1)
+  end
+  ffi.C.memcpy(sok.expect_buf + sok.expect_buf_idx, buf, nread)
+  sok.expect_buf_idx = sok.expect_buf_idx + nread
+  if sok.expect_str == nil then
+    return true
+  end
+  local match_p = ffi.C.memmem(sok.expect_buf, sok.expect_buf_idx, sok.expect_str, sok.expect_str_len)
+  if match_p ~= nil then
+    return true
+  end
+  return false
+end
+
+function expect_done(sok)
+  local expect_buf_sz = ffi.sizeof(sok.expect_buf) - 128
+  if not sok.expect_str then 
+    return false
+  end
+  local match_p = ffi.C.memmem(sok.expect_buf, sok.expect_buf_idx, sok.expect_str, sok.expect_str_len)
+  if match_p ~= nil then
+    if sok.expect_cb then
+      sok.expect_cb(sok)
+    end
+    local match_idx = ffi.cast("char *", match_p) - ffi.cast("char *", sok.expect_buf)
+    ffi.C.memmove(sok.expect_buf, ffi.cast("char *", match_p) + sok.expect_str_len, expect_buf_sz - match_idx - sok.expect_str_len)
+    sok.expect_buf_idx = match_idx + sok.expect_str_len
+    sok.expect_success = true
+
+    sok.expect_str = nil
+    sok.expect_str_len = 0
+    return true
+  end
+end
+
+function slave_events(sok, revents, indent)
+  local fd = sok.fd
+  local nread = ffi.C.read(fd, buf, ffi.sizeof(buf)-128)
+  local idx = nread - 1
+  while idx >= 0 and buf[idx] ~= 10 do
+    idx = idx - 1
+  end
+  if idx >= 0 then
+    erase_line(indent)
+    session_stdout_write(sok.prefix, sok.buf .. ffi.string(buf, idx+1))
+    sok.buf = ""
+  end
+  sok.buf = sok.buf .. ffi.string(buf+idx+1, nread-idx-1)
+  -- print("\nRead: " .. tostring(nread))
+  -- stdout_write(ffi.string(buf, nread))
+  if expect_success(sok, buf, nread) then
+    return true
+  end
+  return false
+end
+
+
+function start_session(name)
+  local mfd, cpid = pty_run("/bin/bash")
+  local sok =  { ["fd"] = mfd, ["cb"] = slave_events, ["buf"] = "", ["prefix"] = name .. ":", ["expect_buf"] = ffi.new("char [165536]"), ["expect_buf_idx"] = 0, ["expect_str"] = nil }
+  table.insert(select_fds, sok)
+  sessions[name] = sok
+end
+
+function command_transform(exe)
+  if exe == "break" then
+    exe = string.char(3)
+  end
+  return exe
+end
+
+function session_write(a_session, a_str)
+  if has_session(a_session) then
+    return tonumber(ffi.C.write(sessions[a_session].fd, c_str(a_str), #a_str))
+  else
+    return 0
+  end
+end
+
+function session_exec(a_session, a_cmd)
+  local exe = command_transform(a_cmd) .. "\n"
+  session_write(a_session, exe)
+end
+
+function session_cmd(ui, a_session, a_cmd)
+  if not has_session(a_session) then
+    stdout_write("ERR: No such session '" .. tostring(a_session) .. "'\n")
+    return nil
+  end
+  if a_session == "lua" then
+    local func, msg = loadstring(ui.lua_acc .. a_cmd)
+    -- stdout_write("LOADSTR: " .. vpp.dump({ ret, msg }) .. "\n")
+    if not func and string.match(msg, "<eof>") then
+      if a_session ~= ui.in_session then
+         stdout_write("ERR LOADSTR: " .. tostring(msg) .. "\n")
+         return nil
+      end
+      ui.lua_acc = ui.lua_acc .. a_cmd  .. "\n"
+      return true
+    end
+    ui.lua_acc = ""
+    local ret, msg = pcall(func)
+    if ret then
+      return true
+    else
+      stdout_write("ERR: " .. msg .. "\n") 
+      return nil
+    end
+  else
+    session_exec(a_session, a_cmd)
+    if ui.session_cmd_delay then
+      return { "delay", ui.session_cmd_delay }
+    end
+    return true
+  end
+end
+
+function has_session(a_session)
+  if a_session == "lua" then
+    return true
+  end
+  return (sessions[a_session] ~= nil)
+end
+
+function command_match(list, input, output)
+  for i, v in ipairs(list) do
+    local m = {}
+    m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9] = string.match(input, v[1])
+    -- print("MATCH: ", vpp.dump(m))
+    if m[1] then
+       output["result"] = m
+       output["result_index"] = i
+       return m
+    end 
+  end
+  return nil
+end
+
+function cmd_spawn_shell(ui, a_arg)
+  start_session(a_arg[1])
+  return true
+end
+
+function cmd_run_cmd(ui, a_arg)
+  local a_sess = a_arg[1]
+  local a_cmd = a_arg[2]
+  return session_cmd(ui, a_sess, a_cmd)
+end
+
+function cmd_cd(ui, a_arg)
+  local a_sess = a_arg[1]
+  if has_session(a_sess) then
+    ui.in_session = a_sess
+    return true
+  else
+    stdout_write("ERR: Unknown session '".. tostring(a_sess) .. "'\n")
+    return nil
+  end
+end
+
+function cmd_sleep(ui, a_arg)
+  return { "delay", tonumber(a_arg[1]) }
+end
+
+function cmd_expect(ui, a_arg)
+  local a_sess = a_arg[1]
+  local a_expect = a_arg[2]
+  local sok = sessions[a_sess]
+  if not sok then
+    stdout_write("ERR: unknown session '" .. tostring(a_sess) .. "'\n")
+    return nil
+  end
+  sok.expect_str = c_str(a_expect)
+  sok.expect_str_len = #a_expect
+  return { "expect", a_sess }
+end
+
+function cmd_info(ui, a_arg)
+  local a_sess = a_arg[1]
+  local sok = sessions[a_sess]
+  if not sok then
+    stdout_write("ERR: unknown session '" .. tostring(a_sess) .. "'\n")
+    return nil
+  end
+  print("Info for session " .. tostring(a_sess) .. "\n")
+  print("Expect buffer index: " .. tostring(sok.expect_buf_idx))
+  print("Expect buffer: '" .. tostring(ffi.string(sok.expect_buf, sok.expect_buf_idx)) .. "'\n")
+  if sok.expect_str then
+    print("Expect string: '" .. tostring(ffi.string(sok.expect_str, sok.expect_str_len)) .. "'\n")
+  else
+    print("Expect string not set\n")
+  end
+end
+
+function cmd_echo(ui, a_arg)
+  local a_data = a_arg[1]
+  print("ECHO: " .. tostring(a_data))
+end
+
+main_command_table = {
+  { "^shell ([a-zA-Z0-9_]+)$", cmd_spawn_shell },
+  { "^run ([a-zA-Z0-9_]+) (.+)$", cmd_run_cmd },
+  { "^cd ([a-zA-Z0-9_]+)$", cmd_cd  },
+  { "^sleep ([0-9]+)$", cmd_sleep },
+  { "^expect ([a-zA-Z0-9_]+) (.-)$", cmd_expect },
+  { "^info ([a-zA-Z0-9_]+)$", cmd_info },
+  { "^echo (.-)$", cmd_echo }
+}
+
+
+
+function ui_set_prompt(ui)
+  if ui.in_session then 
+    if ui.in_session == "lua" then
+      if #ui.lua_acc > 0 then
+        ui.r.prompt = ui.in_session .. ">>"
+      else
+        ui.r.prompt = ui.in_session .. ">"
+      end 
+    else
+      ui.r.prompt = ui.in_session .. "> "
+    end
+  else
+    ui.r.prompt = "> "
+  end
+  return ui.r.prompt
+end
+
+function ui_run_command(ui, cmd)
+  -- stdout_write("Command: " .. tostring(cmd) .. "\n")
+  local ret = false
+  if ui.in_session then
+    if cmd then
+      if cmd == "^D^D^D" then
+        ui.in_session = nil
+        ret = true
+      else
+        ret = session_cmd(ui, ui.in_session, cmd)
+      end
+    else
+      ui.in_session = nil
+      ret = true
+    end
+  else  
+    if cmd then
+      local out = {}
+      if cmd == "" then
+        ret = true
+      end
+      if command_match(main_command_table, cmd, out) then
+        local i = out.result_index
+        local m = out.result
+        if main_command_table[i][2] then
+          ret = main_command_table[i][2](ui, m)
+        end
+      end
+    end
+    if not cmd or cmd == "quit" then
+      return "quit"
+    end
+  end
+  return ret
+end
+
+local ui = {}
+ui.in_session = nil
+ui.r = readln.reader() 
+ui.lua_acc = ""
+ui.session_cmd_delay = 0.3
+
+local lines = ""
+
+local done = false
+-- a helper function which always returns nil
+local no_next_line = function() return nil end
+
+-- a function which returns the next batch line
+local next_line = no_next_line
+
+local batchfile = arg[1]
+
+if batchfile then
+  local f = io.lines(batchfile)
+  next_line = function() 
+    local line = f()
+    if line then 
+      return line
+    else 
+      next_line = no_next_line
+      session_stdout_write(batchfile .. ":", "End of batch\n")
+      return nil
+    end
+  end
+end
+
+
+local batch_when = 0
+local batch_expect = nil
+while not done do
+  local prompt = ui_set_prompt(ui)
+  local batch_cmd = next_line() 
+  local cmd, expect_sok = ui.r.readln(do_select_stdin, batch_cmd, batch_when, batch_expect)
+  if expect_sok and not expect_success(expect_sok, buf, 0) then
+    if not cmd_ret and next_line ~= no_next_line then
+      print("ERR: expect timeout\n")
+      next_line = no_next_line
+    end
+  else 
+    local cmd_ret = ui_run_command(ui, cmd)
+    if not cmd_ret and next_line ~= no_next_line then
+      print("ERR: Error during batch execution\n")
+      next_line = no_next_line
+    end
+
+    if cmd_ret  == "quit" then
+      done = true
+    end
+    batch_expect = nil
+    batch_when = 0
+    if type(cmd_ret) == "table" then
+      if cmd_ret[1] == "delay" then
+	batch_when = os_time() + tonumber(cmd_ret[2])
+      end
+      if cmd_ret[1] == "expect" then
+	batch_expect = sessions[cmd_ret[2]]
+	batch_when = os_time() + 15
+      end
+    end
+  end
+end
+ui.r.done()
+
+os.exit(1)
+
+
+
diff --git a/vpp-api/lua/examples/lute/script-inout-acl-noacl.lute b/vpp-api/lua/examples/lute/script-inout-acl-noacl.lute
new file mode 100644
index 0000000..a24d04b
--- /dev/null
+++ b/vpp-api/lua/examples/lute/script-inout-acl-noacl.lute
@@ -0,0 +1,329 @@
+shell vppbuild
+run vppbuild stty -echo
+run vppbuild sudo -u ubuntu -i bash -c "(cd vpp && make plugins && echo ALLGOOD)"
+expect vppbuild ALLGOOD
+
+shell s0
+shell s1
+shell s2
+
+
+cd s1
+unshare -n /bin/bash
+/sbin/ifconfig -a
+^D^D^D
+
+cd s2
+unshare -n /bin/bash
+/sbin/ifconfig -a
+^D^D^D
+
+
+cd lua
+
+function session_get_bash_pid(s)
+  if not has_session(s) then
+    return nil
+  end
+  local fname = "/tmp/lute-"..s.."-pid.txt"
+
+  session_exec(s, "echo $$ >" .. fname)
+  -- it's a dirty hack but it's quick
+  sleep(0.5)
+  local pid = io.lines(fname)()
+  print("Got pid for " .. s .. " : " .. tostring(pid))
+  return(tonumber(pid))
+end
+
+function session_connect_with(s0, s1)
+  -- local pid0 = tostring(session_get_bash_pid(s0))
+  local pid1 = tostring(session_get_bash_pid(s1))
+  local eth_options = { "rx", "tx",  "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "rxhash" }
+  local this_end = s0 .. "_" .. s1
+  local other_end = s1 .. "_" .. s0
+  session_exec(s0, "ip link add name " .. this_end .. " type veth peer name " .. other_end)
+  session_exec(s0, "ip link set dev " .. this_end .. " up promisc on")
+  for i, option in ipairs(eth_options) do
+    session_exec(s0, "/sbin/ethtool --offload " .. this_end .. " " .. option .. " off")
+    session_exec(s0, "/sbin/ethtool --offload " .. other_end .. " " .. option .. " off")
+  end
+  session_exec(s0, "ip link set dev " .. other_end .. " up promisc on netns /proc/" .. pid1 .. "/ns/net")
+  sleep(0.5)
+end
+
+^D^D^D
+run lua session_connect_with("s0", "s1")
+run lua session_connect_with("s0", "s2")
+
+cd s1
+ip -6 addr add dev s1_s0 2001:db8:1::1/64
+ip -4 addr add dev s1_s0 192.0.2.1/24
+ip link set dev s1_s0 up promisc on
+^D^D^D
+
+cd s2
+ip -6 addr add dev s2_s0 2001:db8:1::2/64
+ip -6 addr add dev s2_s0 2001:db8:1::3/64
+ip -6 addr add dev s2_s0 2001:db8:1::4/64
+ip -4 addr add dev s2_s0 192.0.2.2/24
+ip -4 addr add dev s2_s0:1 192.0.2.3/24
+ip -4 addr add dev s2_s0:2 192.0.2.4/24
+ip link set dev s2_s0 up promisc on
+^D^D^D
+
+run s1 ip addr
+run s2 ip addr
+shell VPP
+cd VPP
+cd /home/ubuntu/vpp
+make debug 
+r
+^D^D^D
+expect VPP DBGvpp#
+
+cd lua
+-- Initialization of the Lua environment for talking to VPP
+vpp = require("vpp-lapi")
+root_dir = "/home/ubuntu/vpp"
+pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+vpp:init({ pneum_path = pneum_path })
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+vpp:connect("aytest")
+vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl")
+
+^D^D^D
+
+cd lua
+
+reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s1", hw_addr = "AAAAAA" })
+vpp_if_to_s1 = reply[1].sw_if_index
+
+reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s2", hw_addr = "AAAAAA" })
+vpp_if_to_s2 = reply[1].sw_if_index
+
+ifaces = { vpp_if_to_s1, vpp_if_to_s2 }
+
+reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s1, admin_up_down = 1, link_up_down = 1 })
+print(vpp.dump(reply))
+reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s2, admin_up_down = 1, link_up_down = 1 })
+print(vpp.dump(reply))
+
+bd_id = 42
+
+reply = vpp:api_call("bridge_domain_add_del", { bd_id = bd_id, flood = 1, uu_flood = 1, forward = 1, learn = 1, arp_term = 0, is_add = 1 })
+print(vpp.dump(reply))
+
+for i, v in ipairs(ifaces) do
+  reply = vpp:api_call("sw_interface_set_l2_bridge", { rx_sw_if_index = v, bd_id = bd_id, shg = 0, bvi = 0, enable = 1 } )
+  print(vpp.dump(reply))
+end
+
+^D^D^D
+
+run s1 ping -c 3 192.0.2.2
+expect s1 packet loss
+run s1 ping -c 3 192.0.2.3
+expect s1 packet loss
+run s1 ping -c 3 192.0.2.4
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+
+
+cd lua
+--- ACL testing
+
+--[[ temporary comment out
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add_replace", { context = 42, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add_replace", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add_replace", { context = 42, count = 0 })
+print(vpp.dump(reply))
+print("---")
+
+acl_index_to_delete = reply[1].acl_index
+print("Deleting " .. tostring(acl_index_to_delete))
+reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+for ri, rv in ipairs(reply) do
+  print("Reply message #" .. tostring(ri))
+  print(vpp.dump(rv))
+  for ai, av in ipairs(rv.r) do
+    print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av))
+  end
+
+end
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 4294967295 })
+print(vpp.dump(reply))
+print("---")
+
+
+]] -- end of comment out
+
+---- Should be nothing ^^
+r = {
+  { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 },
+  { is_permit = 0, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::3"), dst_ip_prefix_len = 128 },
+  { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8::"), dst_ip_prefix_len = 32  },
+  { is_permit = 1, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.2"), dst_ip_prefix_len = 32},
+  { is_permit = 0, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.3"), dst_ip_prefix_len = 32 },
+}
+
+reply = vpp:api_call("acl_add_replace", { context = 42, count = 5, r = r })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add_replace", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_in
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s1, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+--reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+-- print(vpp.dump(reply))
+--print("---")
+
+^D^D^D
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 0
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run VPP show trace 
+expect VPP match: inacl 0 rule 1
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 2
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping -c 3 192.0.2.2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 3
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping -c 3 192.0.2.3
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 4
+
+
+cd lua
+
+--- TEST OUTBOUND ACL
+
+r1 = {
+  { is_permit = 1, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 },
+  { is_permit = 0, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::4"), dst_ip_prefix_len = 128 }
+}
+
+reply = vpp:api_call("acl_add_replace", { context = 42, count = 3, r = r1 })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+
+^D^D^D
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: outacl 2 rule 0
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run VPP show trace 
+expect VPP match: inacl 0 rule 1
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+run VPP show trace
+expect VPP match: outacl 2 rule 1
+
+run lua print("ALL GOOD!")
+
diff --git a/vpp-api/lua/examples/lute/script-inout-acl-old.lute b/vpp-api/lua/examples/lute/script-inout-acl-old.lute
new file mode 100644
index 0000000..9edebf0
--- /dev/null
+++ b/vpp-api/lua/examples/lute/script-inout-acl-old.lute
@@ -0,0 +1,329 @@
+shell vppbuild
+run vppbuild stty -echo
+run vppbuild sudo -u ubuntu -i bash -c "(cd vpp && make plugins && echo ALLGOOD)"
+expect vppbuild ALLGOOD
+
+shell s0
+shell s1
+shell s2
+
+
+cd s1
+unshare -n /bin/bash
+/sbin/ifconfig -a
+^D^D^D
+
+cd s2
+unshare -n /bin/bash
+/sbin/ifconfig -a
+^D^D^D
+
+
+cd lua
+
+function session_get_bash_pid(s)
+  if not has_session(s) then
+    return nil
+  end
+  local fname = "/tmp/lute-"..s.."-pid.txt"
+
+  session_exec(s, "echo $$ >" .. fname)
+  -- it's a dirty hack but it's quick
+  sleep(0.5)
+  local pid = io.lines(fname)()
+  print("Got pid for " .. s .. " : " .. tostring(pid))
+  return(tonumber(pid))
+end
+
+function session_connect_with(s0, s1)
+  -- local pid0 = tostring(session_get_bash_pid(s0))
+  local pid1 = tostring(session_get_bash_pid(s1))
+  local eth_options = { "rx", "tx",  "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "rxhash" }
+  local this_end = s0 .. "_" .. s1
+  local other_end = s1 .. "_" .. s0
+  session_exec(s0, "ip link add name " .. this_end .. " type veth peer name " .. other_end)
+  session_exec(s0, "ip link set dev " .. this_end .. " up promisc on")
+  for i, option in ipairs(eth_options) do
+    session_exec(s0, "/sbin/ethtool --offload " .. this_end .. " " .. option .. " off")
+    session_exec(s0, "/sbin/ethtool --offload " .. other_end .. " " .. option .. " off")
+  end
+  session_exec(s0, "ip link set dev " .. other_end .. " up promisc on netns /proc/" .. pid1 .. "/ns/net")
+  sleep(0.5)
+end
+
+^D^D^D
+run lua session_connect_with("s0", "s1")
+run lua session_connect_with("s0", "s2")
+
+cd s1
+ip -6 addr add dev s1_s0 2001:db8:1::1/64
+ip -4 addr add dev s1_s0 192.0.2.1/24
+ip link set dev s1_s0 up promisc on
+^D^D^D
+
+cd s2
+ip -6 addr add dev s2_s0 2001:db8:1::2/64
+ip -6 addr add dev s2_s0 2001:db8:1::3/64
+ip -6 addr add dev s2_s0 2001:db8:1::4/64
+ip -4 addr add dev s2_s0 192.0.2.2/24
+ip -4 addr add dev s2_s0:1 192.0.2.3/24
+ip -4 addr add dev s2_s0:2 192.0.2.4/24
+ip link set dev s2_s0 up promisc on
+^D^D^D
+
+run s1 ip addr
+run s2 ip addr
+shell VPP
+cd VPP
+cd /home/ubuntu/vpp
+make debug 
+r
+^D^D^D
+expect VPP DBGvpp#
+
+cd lua
+-- Initialization of the Lua environment for talking to VPP
+vpp = require("vpp-lapi")
+root_dir = "/home/ubuntu/vpp"
+pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+vpp:init({ pneum_path = pneum_path })
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+vpp:connect("aytest")
+vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl")
+
+^D^D^D
+
+cd lua
+
+reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s1", hw_addr = "AAAAAA" })
+vpp_if_to_s1 = reply[1].sw_if_index
+
+reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s2", hw_addr = "AAAAAA" })
+vpp_if_to_s2 = reply[1].sw_if_index
+
+ifaces = { vpp_if_to_s1, vpp_if_to_s2 }
+
+reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s1, admin_up_down = 1, link_up_down = 1 })
+print(vpp.dump(reply))
+reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s2, admin_up_down = 1, link_up_down = 1 })
+print(vpp.dump(reply))
+
+bd_id = 42
+
+reply = vpp:api_call("bridge_domain_add_del", { bd_id = bd_id, flood = 1, uu_flood = 1, forward = 1, learn = 1, arp_term = 0, is_add = 1 })
+print(vpp.dump(reply))
+
+for i, v in ipairs(ifaces) do
+  reply = vpp:api_call("sw_interface_set_l2_bridge", { rx_sw_if_index = v, bd_id = bd_id, shg = 0, bvi = 0, enable = 1 } )
+  print(vpp.dump(reply))
+end
+
+^D^D^D
+
+run s1 ping -c 3 192.0.2.2
+expect s1 packet loss
+run s1 ping -c 3 192.0.2.3
+expect s1 packet loss
+run s1 ping -c 3 192.0.2.4
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+
+
+cd lua
+--- ACL testing
+
+--[[ temporary comment out
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add", { context = 42, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add", { context = 42, count = 0 })
+print(vpp.dump(reply))
+print("---")
+
+acl_index_to_delete = reply[1].acl_index
+print("Deleting " .. tostring(acl_index_to_delete))
+reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+for ri, rv in ipairs(reply) do
+  print("Reply message #" .. tostring(ri))
+  print(vpp.dump(rv))
+  for ai, av in ipairs(rv.r) do
+    print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av))
+  end
+
+end
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 4294967295 })
+print(vpp.dump(reply))
+print("---")
+
+
+]] -- end of comment out
+
+---- Should be nothing ^^
+r = {
+  { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 },
+  { is_permit = 0, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::3"), dst_ip_prefix_len = 128 },
+  { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8::"), dst_ip_prefix_len = 32  },
+  { is_permit = 1, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.2"), dst_ip_prefix_len = 32},
+  { is_permit = 0, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.3"), dst_ip_prefix_len = 32 },
+}
+
+reply = vpp:api_call("acl_add", { context = 42, count = 5, r = r })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_in
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s1, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+--reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+-- print(vpp.dump(reply))
+--print("---")
+
+^D^D^D
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 0
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run VPP show trace 
+expect VPP match: inacl 0 rule 1
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 2
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping -c 3 192.0.2.2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 3
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping -c 3 192.0.2.3
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 4
+
+
+cd lua
+
+--- TEST OUTBOUND ACL
+
+r1 = {
+  { is_permit = 1, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 },
+  { is_permit = 0, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::4"), dst_ip_prefix_len = 128 }
+}
+
+reply = vpp:api_call("acl_add", { context = 42, count = 3, r = r1 })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+
+^D^D^D
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: outacl 2 rule 0
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run VPP show trace 
+expect VPP match: inacl 0 rule 1
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+run VPP show trace
+expect VPP match: outacl 2 rule 1
+
+run lua print("ALL GOOD!")
+
diff --git a/vpp-api/lua/examples/lute/script-inout-acl.lute b/vpp-api/lua/examples/lute/script-inout-acl.lute
new file mode 100644
index 0000000..d7e7423
--- /dev/null
+++ b/vpp-api/lua/examples/lute/script-inout-acl.lute
@@ -0,0 +1,329 @@
+shell vppbuild
+run vppbuild stty -echo
+run vppbuild sudo -u ubuntu -i bash -c "(cd vpp && make plugins && echo ALLGOOD)"
+expect vppbuild ALLGOOD
+
+shell s0
+shell s1
+shell s2
+
+
+cd s1
+unshare -n /bin/bash
+/sbin/ifconfig -a
+^D^D^D
+
+cd s2
+unshare -n /bin/bash
+/sbin/ifconfig -a
+^D^D^D
+
+
+cd lua
+
+function session_get_bash_pid(s)
+  if not has_session(s) then
+    return nil
+  end
+  local fname = "/tmp/lute-"..s.."-pid.txt"
+
+  session_exec(s, "echo $$ >" .. fname)
+  -- it's a dirty hack but it's quick
+  sleep(0.5)
+  local pid = io.lines(fname)()
+  print("Got pid for " .. s .. " : " .. tostring(pid))
+  return(tonumber(pid))
+end
+
+function session_connect_with(s0, s1)
+  -- local pid0 = tostring(session_get_bash_pid(s0))
+  local pid1 = tostring(session_get_bash_pid(s1))
+  local eth_options = { "rx", "tx",  "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "rxhash" }
+  local this_end = s0 .. "_" .. s1
+  local other_end = s1 .. "_" .. s0
+  session_exec(s0, "ip link add name " .. this_end .. " type veth peer name " .. other_end)
+  session_exec(s0, "ip link set dev " .. this_end .. " up promisc on")
+  for i, option in ipairs(eth_options) do
+    session_exec(s0, "/sbin/ethtool --offload " .. this_end .. " " .. option .. " off")
+    session_exec(s0, "/sbin/ethtool --offload " .. other_end .. " " .. option .. " off")
+  end
+  session_exec(s0, "ip link set dev " .. other_end .. " up promisc on netns /proc/" .. pid1 .. "/ns/net")
+  sleep(0.5)
+end
+
+^D^D^D
+run lua session_connect_with("s0", "s1")
+run lua session_connect_with("s0", "s2")
+
+cd s1
+ip -6 addr add dev s1_s0 2001:db8:1::1/64
+ip -4 addr add dev s1_s0 192.0.2.1/24
+ip link set dev s1_s0 up promisc on
+^D^D^D
+
+cd s2
+ip -6 addr add dev s2_s0 2001:db8:1::2/64
+ip -6 addr add dev s2_s0 2001:db8:1::3/64
+ip -6 addr add dev s2_s0 2001:db8:1::4/64
+ip -4 addr add dev s2_s0 192.0.2.2/24
+ip -4 addr add dev s2_s0:1 192.0.2.3/24
+ip -4 addr add dev s2_s0:2 192.0.2.4/24
+ip link set dev s2_s0 up promisc on
+^D^D^D
+
+run s1 ip addr
+run s2 ip addr
+shell VPP
+cd VPP
+cd /home/ubuntu/vpp
+make debug 
+r
+^D^D^D
+expect VPP DBGvpp#
+
+cd lua
+-- Initialization of the Lua environment for talking to VPP
+vpp = require("vpp-lapi")
+root_dir = "/home/ubuntu/vpp"
+pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+vpp:init({ pneum_path = pneum_path })
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+vpp:connect("aytest")
+vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl")
+
+^D^D^D
+
+cd lua
+
+reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s1", hw_addr = "AAAAAA" })
+vpp_if_to_s1 = reply[1].sw_if_index
+
+reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s2", hw_addr = "AAAAAA" })
+vpp_if_to_s2 = reply[1].sw_if_index
+
+ifaces = { vpp_if_to_s1, vpp_if_to_s2 }
+
+reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s1, admin_up_down = 1, link_up_down = 1 })
+print(vpp.dump(reply))
+reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s2, admin_up_down = 1, link_up_down = 1 })
+print(vpp.dump(reply))
+
+bd_id = 42
+
+reply = vpp:api_call("bridge_domain_add_del", { bd_id = bd_id, flood = 1, uu_flood = 1, forward = 1, learn = 1, arp_term = 0, is_add = 1 })
+print(vpp.dump(reply))
+
+for i, v in ipairs(ifaces) do
+  reply = vpp:api_call("sw_interface_set_l2_bridge", { rx_sw_if_index = v, bd_id = bd_id, shg = 0, bvi = 0, enable = 1 } )
+  print(vpp.dump(reply))
+end
+
+^D^D^D
+
+run s1 ping -c 3 192.0.2.2
+expect s1 packet loss
+run s1 ping -c 3 192.0.2.3
+expect s1 packet loss
+run s1 ping -c 3 192.0.2.4
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+
+
+cd lua
+--- ACL testing
+
+--[[ temporary comment out
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 0 })
+print(vpp.dump(reply))
+print("---")
+
+acl_index_to_delete = reply[1].acl_index
+print("Deleting " .. tostring(acl_index_to_delete))
+reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+for ri, rv in ipairs(reply) do
+  print("Reply message #" .. tostring(ri))
+  print(vpp.dump(rv))
+  for ai, av in ipairs(rv.r) do
+    print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av))
+  end
+
+end
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 4294967295 })
+print(vpp.dump(reply))
+print("---")
+
+
+]] -- end of comment out
+
+---- Should be nothing ^^
+r = {
+  { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 },
+  { is_permit = 0, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::3"), dst_ip_prefix_len = 128 },
+  { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8::"), dst_ip_prefix_len = 32  },
+  { is_permit = 1, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.2"), dst_ip_prefix_len = 32},
+  { is_permit = 0, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.3"), dst_ip_prefix_len = 32 },
+}
+
+reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 5, r = r })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_in
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s1, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+--reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+-- print(vpp.dump(reply))
+--print("---")
+
+^D^D^D
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 0
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run VPP show trace 
+expect VPP match: inacl 0 rule 1
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 2
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping -c 3 192.0.2.2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 3
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping -c 3 192.0.2.3
+expect s1 packet loss
+run VPP show trace
+expect VPP match: inacl 0 rule 4
+
+
+cd lua
+
+--- TEST OUTBOUND ACL
+
+r1 = {
+  { is_permit = 1, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 },
+  { is_permit = 0, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::4"), dst_ip_prefix_len = 128 }
+}
+
+reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 3, r = r1 })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+
+^D^D^D
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run VPP show trace
+expect VPP match: outacl 2 rule 0
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run VPP show trace 
+expect VPP match: inacl 0 rule 1
+
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+run VPP show trace
+expect VPP match: outacl 2 rule 1
+
+run lua print("ALL GOOD!")
+
diff --git a/vpp-api/lua/examples/lute/script.lute b/vpp-api/lua/examples/lute/script.lute
new file mode 100644
index 0000000..c3dd90f
--- /dev/null
+++ b/vpp-api/lua/examples/lute/script.lute
@@ -0,0 +1,7 @@
+shell s1
+expect s1 $
+run s1 echo testing123
+expect s1 $
+run s1 echo done
+quit
+
diff --git a/vpp-api/lua/examples/lute/sessions-acl.lute b/vpp-api/lua/examples/lute/sessions-acl.lute
new file mode 100644
index 0000000..ac237ef
--- /dev/null
+++ b/vpp-api/lua/examples/lute/sessions-acl.lute
@@ -0,0 +1,308 @@
+run lua -- collectgarbage("stop")
+
+shell vppbuild
+run vppbuild stty -echo
+run vppbuild sudo -u ubuntu -i bash -c "(cd vpp && make plugins && echo ALLGOOD)"
+expect vppbuild ALLGOOD
+
+shell s0
+shell s1
+shell s2
+
+
+cd s1
+unshare -n /bin/bash
+/sbin/ifconfig -a
+^D^D^D
+
+cd s2
+unshare -n /bin/bash
+/sbin/ifconfig -a
+^D^D^D
+
+
+cd lua
+
+function session_get_bash_pid(s)
+  if not has_session(s) then
+    return nil
+  end
+  local fname = "/tmp/lute-"..s.."-pid.txt"
+
+  session_exec(s, "echo $$ >" .. fname)
+  -- it's a dirty hack but it's quick
+  sleep(0.5)
+  local pid = io.lines(fname)()
+  print("Got pid for " .. s .. " : " .. tostring(pid))
+  return(tonumber(pid))
+end
+
+function session_connect_with(s0, s1)
+  -- local pid0 = tostring(session_get_bash_pid(s0))
+  local pid1 = tostring(session_get_bash_pid(s1))
+  local eth_options = { "rx", "tx",  "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "rxhash" }
+  local this_end = s0 .. "_" .. s1
+  local other_end = s1 .. "_" .. s0
+  session_exec(s0, "ip link add name " .. this_end .. " type veth peer name " .. other_end)
+  session_exec(s0, "ip link set dev " .. this_end .. " up promisc on")
+  for i, option in ipairs(eth_options) do
+    session_exec(s0, "/sbin/ethtool --offload " .. this_end .. " " .. option .. " off")
+    session_exec(s0, "/sbin/ethtool --offload " .. other_end .. " " .. option .. " off")
+  end
+  session_exec(s0, "ip link set dev " .. other_end .. " up promisc on netns /proc/" .. pid1 .. "/ns/net")
+  sleep(0.5)
+end
+
+^D^D^D
+run lua session_connect_with("s0", "s1")
+run lua session_connect_with("s0", "s2")
+
+cd s1
+ip -6 addr add dev s1_s0 2001:db8:1::1/64
+ip -4 addr add dev s1_s0 192.0.2.1/24
+ip link set dev s1_s0 up promisc on
+^D^D^D
+
+cd s2
+ip -6 addr add dev s2_s0 2001:db8:1::2/64
+ip -6 addr add dev s2_s0 2001:db8:1::3/64
+ip -6 addr add dev s2_s0 2001:db8:1::4/64
+ip -4 addr add dev s2_s0 192.0.2.2/24
+ip -4 addr add dev s2_s0:1 192.0.2.3/24
+ip -4 addr add dev s2_s0:2 192.0.2.4/24
+ip link set dev s2_s0 up promisc on
+^D^D^D
+
+run s1 ip addr
+run s2 ip addr
+shell VPP
+cd VPP
+cd /home/ubuntu/vpp
+make debug 
+r
+^D^D^D
+expect VPP DBGvpp#
+
+cd lua
+-- Initialization of the Lua environment for talking to VPP
+vpp = require("vpp-lapi")
+root_dir = "/home/ubuntu/vpp"
+pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so"
+vpp:init({ pneum_path = pneum_path })
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api")
+vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api")
+vpp:connect("aytest")
+vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl")
+
+^D^D^D
+
+cd lua
+
+reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s1", hw_addr = "AAAAAA" })
+vpp_if_to_s1 = reply[1].sw_if_index
+
+reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s2", hw_addr = "AAAAAA" })
+vpp_if_to_s2 = reply[1].sw_if_index
+
+ifaces = { vpp_if_to_s1, vpp_if_to_s2 }
+
+reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s1, admin_up_down = 1, link_up_down = 1 })
+print(vpp.dump(reply))
+reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s2, admin_up_down = 1, link_up_down = 1 })
+print(vpp.dump(reply))
+
+bd_id = 42
+
+reply = vpp:api_call("bridge_domain_add_del", { bd_id = bd_id, flood = 1, uu_flood = 1, forward = 1, learn = 1, arp_term = 0, is_add = 1 })
+print(vpp.dump(reply))
+
+for i, v in ipairs(ifaces) do
+  reply = vpp:api_call("sw_interface_set_l2_bridge", { rx_sw_if_index = v, bd_id = bd_id, shg = 0, bvi = 0, enable = 1 } )
+  print(vpp.dump(reply))
+end
+
+^D^D^D
+
+run s1 ping -c 3 192.0.2.2
+expect s1 packet loss
+run s1 ping -c 3 192.0.2.3
+expect s1 packet loss
+run s1 ping -c 3 192.0.2.4
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::2
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::3
+expect s1 packet loss
+run s1 ping6 -c 3 2001:db8:1::4
+expect s1 packet loss
+
+
+cd lua
+--- ACL testing
+
+--[[ temporary comment out
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add", { context = 42, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_add", { context = 42, count = 0 })
+print(vpp.dump(reply))
+print("---")
+
+acl_index_to_delete = reply[1].acl_index
+print("Deleting " .. tostring(acl_index_to_delete))
+reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+for ri, rv in ipairs(reply) do
+  print("Reply message #" .. tostring(ri))
+  print(vpp.dump(rv))
+  for ai, av in ipairs(rv.r) do
+    print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av))
+  end
+
+end
+print("---")
+
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0})
+print(vpp.dump(reply))
+print("---")
+
+reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 4294967295 })
+print(vpp.dump(reply))
+print("---")
+
+
+]] -- end of comment out
+
+---- Should be nothing ^^
+r = {
+  { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 },
+  { is_permit = 0, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::3"), dst_ip_prefix_len = 128 },
+  { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8::"), dst_ip_prefix_len = 32  },
+  { is_permit = 1, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.2"), dst_ip_prefix_len = 32},
+  { is_permit = 0, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.3"), dst_ip_prefix_len = 32 },
+}
+
+reply = vpp:api_call("acl_add", { context = 42, count = 5, r = r })
+print(vpp.dump(reply))
+print("---")
+interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_in
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s1, is_add = 1, is_input = 1, acl_index = interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+--- TEST OUTBOUND ACL
+
+r1 = {
+  { is_permit = 1, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 },
+  { is_permit = 0, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::4"), dst_ip_prefix_len = 128 },
+  { is_permit = 2, is_ipv6 = 0 }
+}
+
+reply = vpp:api_call("acl_add", { context = 42, count = 3, r = r1 })
+print(vpp.dump(reply))
+print("---")
+interface_acl_out = reply[1].acl_index
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out })
+print(vpp.dump(reply))
+print("---")
+
+r2 = {
+  { is_permit = 1, is_ipv6 = 1 },
+  { is_permit = 0, is_ipv6 = 0 }
+}
+
+reply = vpp:api_call("acl_add", { context = 42, count = 2, r = r2 })
+print(vpp.dump(reply))
+print("---")
+second_interface_acl_in = reply[1].acl_index
+
+reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 1, acl_index = second_interface_acl_in })
+print(vpp.dump(reply))
+print("---")
+
+^D^D^D
+
+run VPP show classify tables
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s2 nc -v -l -p 22
+run s1 nc 192.0.2.2 22
+run s1 echo
+sleep 1
+run s1 break
+sleep 1
+run VPP show trace
+expect VPP match: outacl 2 rule 2
+run VPP show classify tables
+
+
+run VPP show classify tables
+run VPP clear trace
+run VPP trace add af-packet-input 100
+run s2 nc -v -l -p 22
+run s1 nc 192.0.2.2 22
+run s1 echo
+sleep 1
+run s1 break
+sleep 1
+run VPP show trace
+expect VPP match: outacl 2 rule 2
+run VPP show classify tables
+
+
+run lua print("ALL GOOD!")
+
diff --git a/vpp-api/lua/vpp-lapi.lua b/vpp-api/lua/vpp-lapi.lua
new file mode 100644
index 0000000..4f2e3f5
--- /dev/null
+++ b/vpp-api/lua/vpp-lapi.lua
@@ -0,0 +1,752 @@
+--[[
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+]]
+
+local vpp = {}
+
+local ffi = require("ffi")
+
+--[[
+
+The basic type definitions. A bit of weird gymnastic with
+unionization of the hton* and ntoh* functions results
+is to make handling of signed and unsigned types a bit cleaner,
+essentially building typecasting into a C union.
+
+The vl_api_opaque_message_t is a synthetic type assumed to have
+enough storage to hold the entire API message regardless of the type.
+During the operation it is casted to the specific message struct types.
+
+]]
+
+
+ffi.cdef([[
+
+typedef uint8_t u8;
+typedef int8_t i8;
+typedef uint16_t u16;
+typedef int16_t i16;
+typedef uint32_t u32;
+typedef int32_t i32;
+typedef uint64_t u64;
+typedef int64_t i64;
+typedef double f64;
+typedef float f32;
+
+#pragma pack(1)
+typedef union {
+  u16 u16;
+  i16 i16;
+} lua_ui16t;
+
+#pragma pack(1)
+typedef union {
+  u32 u32;
+  i32 i32;
+} lua_ui32t;
+
+u16 ntohs(uint16_t hostshort);
+u16 htons(uint16_t hostshort);
+u32 htonl(uint32_t along);
+u32 ntohl(uint32_t along);
+void *memset(void *s, int c, size_t n);
+void *memcpy(void *dest, void *src, size_t n);
+
+#pragma pack(1)
+typedef struct _vl_api_opaque_message {
+  u16 _vl_msg_id;
+  u8  data[65536];
+} vl_api_opaque_message_t;
+]])
+
+
+-- CRC-based version stuff
+
+local crc32c_table = ffi.new('const uint32_t[256]',
+  { 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4,
+  0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
+  0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B,
+  0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
+  0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B,
+  0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
+  0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54,
+  0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
+  0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
+  0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
+  0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5,
+  0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
+  0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45,
+  0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
+  0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
+  0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
+  0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48,
+  0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
+  0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687,
+  0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
+  0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927,
+  0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
+  0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8,
+  0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
+  0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096,
+  0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
+  0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
+  0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
+  0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9,
+  0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
+  0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36,
+  0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
+  0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C,
+  0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
+  0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043,
+  0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
+  0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3,
+  0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
+  0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C,
+  0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
+  0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652,
+  0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
+  0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D,
+  0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
+  0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
+  0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
+  0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2,
+  0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
+  0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530,
+  0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
+  0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF,
+  0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
+  0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F,
+  0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
+  0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90,
+  0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
+  0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE,
+  0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
+  0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321,
+  0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
+  0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81,
+  0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
+  0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
+  0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 }
+);
+
+local function CRC8(crc, d)
+  return bit.bxor(bit.rshift(crc, 8), crc32c_table[bit.band(0xff, bit.bxor(crc, d))])
+end
+
+local function CRC16(crc, d)
+  crc = CRC8(crc, bit.band(d, 0xFF))
+  d = bit.rshift(d, 8)
+  crc = CRC8(crc, bit.band(d, 0xFF))
+  return crc
+end
+
+local function string_crc(str, crc)
+  for i=1,#str do
+    -- print("S", i, string.byte(str, i), string.char(string.byte(str, i)))
+    crc = CRC8(crc, string.byte(str, i))
+  end
+  return crc
+end
+
+local tokens = {
+  { ["match"] =' ', ["act"]             = { }  },
+  { ["match"] ='\n', ["act"]             = { }  },
+  { ["match"] ="manual_endian", ["act"]  = { "NODE_MANUAL_ENDIAN", "MANUAL_ENDIAN",    276 } },
+  { ["match"] ="define", ["act"]         = { "NODE_DEFINE",        "DEFINE",           267 } },
+  { ["match"] ="dont_trace", ["act"]     = { "NODE_DONT_TRACE",    "DONT_TRACE",       279 } },
+  { ["match"] ="f64", ["act"]            = { "NODE_F64",           "PRIMTYPE",         string_crc } },
+  { ["match"] ="i16", ["act"]            = { "NODE_I16",           "PRIMTYPE",         string_crc } },
+  { ["match"] ="i32", ["act"]            = { "NODE_I32",           "PRIMTYPE",         string_crc } },
+  { ["match"] ="i64", ["act"]            = { "NODE_I64",           "PRIMTYPE",         string_crc } },
+  { ["match"] ="i8", ["act"]             = { "NODE_I8",            "PRIMTYPE",         string_crc } },
+  { ["match"] ="manual_print", ["act"]   = { "NODE_MANUAL_PRINT",  "MANUAL_PRINT",     275 } },
+  { ["match"] ="noversion", ["act"]      = { "NODE_NOVERSION",     "NOVERSION",        274 } },
+  { ["match"] ="packed", ["act"]         = { "NODE_PACKED",        "TPACKED",          266 } },
+  { ["match"] ="typeonly", ["act"]       = { "NODE_TYPEONLY",      "TYPEONLY",         278 } },
+  { ["match"] ="u16", ["act"]            = { "NODE_U16",           "PRIMTYPE",         string_crc } },
+  { ["match"] ="u32", ["act"]            = { "NODE_U32",           "PRIMTYPE",         string_crc } },
+  { ["match"] ="u64", ["act"]            = { "NODE_U64",           "PRIMTYPE",         string_crc } },
+  { ["match"] ="u8", ["act"]             = { "NODE_U8",            "PRIMTYPE",         string_crc } },
+  { ["match"] ="union", ["act"]          = { "NODE_UNION",         "UNION",            271 } },
+  { ["match"] ="uword", ["act"]          = { "NODE_UWORD",         "PRIMTYPE",         string_crc } },
+  { ["match"] ="%(", ["act"]             = { "NODE_LPAR",          "LPAR",             259 } },
+  { ["match"] ="%)", ["act"]             = { "NODE_RPAR",          "RPAR",             258 } },
+  { ["match"] =";", ["act"]              = { "NODE_SEMI",          "SEMI",             260 } },
+  { ["match"] ="%[", ["act"]             = { "NODE_LBRACK",        "LBRACK",           261 } },
+  { ["match"] ="%]", ["act"]             = { "NODE_RBRACK",        "RBRACK",           262 } },
+  { ["match"] ="%{", ["act"]             = { "NODE_LCURLY",        "LCURLY",           268 } },
+  { ["match"] ="%}", ["act"]             = { "NODE_RCURLY",        "RCURLY",           269 } },
+  { ["match"] ='%b""', ["act"]           = { "NODE_STRING",        "STRING",           string_crc } },
+  { ["match"] ='%b@@', ["act"]           = { "NODE_HELPER",        "HELPER_STRING",    string_crc } },
+  -- TODO: \ must be consumed
+  { ["match"] ='[_a-zA-Z][_a-zA-Z0-9]*',
+                       ["act"]           = { "NODE_NAME",          "NAME",             string_crc } },
+  { ["match"] ='[0-9]+', ["act"]         = { "NODE_NUMBER",        "NUMBER",           string_crc } },
+  { ["match"] ='#[^\n]+', ["act"]            = { "NODE_PRAGMA",        "PRAGMA",           nil } },
+}
+
+
+function vpp.crc_version_string(data)
+  local input_crc = 0
+  -- Get rid of comments
+  data = data:gsub("/%*.-%*/", "")
+  data = data:gsub("//[^\n]+", "")
+  -- print(data)
+  idx = 1
+  while (true) do
+    local matched = nil
+    for k, v in ipairs(tokens) do
+      if not matched then
+        local x, y, cap = string.find(data, v["match"], idx)
+        if x == idx then
+          matched = { ["node"] = v["act"], ["x"] = x, ["y"] = y, ["cap"] = cap, ["chars"] = string.sub(data, x, y)  }
+          -- print(k, v, x, y, cap, matched.chars, matched.node[0] )
+        end
+      end
+    end
+    if matched then
+      idx = idx + (matched.y - matched.x + 1)
+      if matched.node[1] then
+        local act = matched.node[3]
+        if type(act) == "function" then
+          input_crc = act(matched.chars, input_crc)
+        elseif type(act) == "number" then
+          input_crc = CRC16(input_crc, act)
+        end
+        -- print(vpp.dump(matched))
+      end
+    else
+      -- print("NOT MATCHED!")
+      local crc = CRC16(input_crc, 0xFFFFFFFF)
+      return string.sub(string.format("%x", crc), -8)
+    end
+  end
+end
+
+
+function vpp.dump(o)
+   if type(o) == 'table' then
+      local s = '{ '
+      for k,v in pairs(o) do
+         if type(k) ~= 'number' then k = '"'..k..'"' end
+         s = s .. '['..k..'] = ' .. vpp.dump(v) .. ','
+      end
+      return s .. '} '
+   else
+      return tostring(o)
+   end
+end
+
+function vpp.hex_dump(buf)
+  local ret = {}
+  for i=1,math.ceil(#buf/16) * 16 do
+    if (i-1) % 16 == 0 then table.insert(ret, string.format('%08X  ', i-1)) end
+    table.insert(ret, ( i > #buf and '   ' or string.format('%02X ', buf:byte(i)) ))
+    if i %  8 == 0 then table.insert(ret, ' ') end
+    if i % 16 == 0 then table.insert(ret, buf:sub(i-16+1, i):gsub('%c','.')..'\n' ) end
+  end
+  return table.concat(ret)
+end
+
+
+function vpp.c_str(text_in)
+  local text = text_in -- \000 will be helpfully added by ffi.copy
+  local c_str = ffi.new("char[?]", #text+1)
+  ffi.copy(c_str, text)
+  return c_str
+end
+
+
+function vpp.init(vpp, args)
+  local pneum_api = args.pneum_api or [[
+ int cough_pneum_attach(char *pneum_path, char *cough_path);
+ int pneum_connect(char *name, char *chroot_prefix);
+ int pneum_connect_sync(char *name, char *chroot_prefix);
+ int pneum_disconnect(void);
+ int pneum_read(char **data, int *l);
+ int pneum_write(char *data, int len);
+
+void pneum_data_free(char *data);
+]]
+
+  vpp.pneum_path = args.pneum_path
+  ffi.cdef(pneum_api)
+  local init_res = 0
+  if pcall(function()
+		     vpp.cough_path = args.cough_path or "./libcough.so"
+                     vpp.cough = ffi.load(vpp.cough_path)
+           end) then
+    pcall(function()
+      if(vpp.cough.cough_pneum_attach) then
+        vpp.pneum_is_cough = true
+        print("libcough detected\n")
+        init_res = vpp.cough.cough_pneum_attach(vpp.c_str(vpp.pneum_path), vpp.c_str(vpp.cough_path))
+        vpp.pneum = vpp.cough
+      end
+    end)
+  else
+    vpp.pneum = ffi.load(vpp.pneum_path)
+  end
+  if (init_res < 0) then
+    return nil
+  end
+
+  vpp.next_msg_num = 1
+  vpp.msg_name_to_number = {}
+  vpp.msg_name_to_fields = {}
+  vpp.msg_number_to_name = {}
+  vpp.msg_number_to_type = {}
+  vpp.msg_number_to_pointer_type = {}
+  vpp.c_type_to_fields = {}
+  vpp.events = {}
+  vpp.plugin_version = {}
+  vpp.is_connected = false
+
+
+  vpp.t_lua2c = {}
+  vpp.t_c2lua = {}
+  vpp.t_lua2c["u8"] = function(c_type, src, dst_c_ptr)
+    if type(src) == "string" then
+      -- ffi.copy adds a zero byte at the end. Grrr.
+      -- ffi.copy(dst_c_ptr, src)
+      ffi.C.memcpy(dst_c_ptr, vpp.c_str(src), #src)
+      return(#src)
+    elseif type(src) == "table" then
+      for i,v in ipairs(src) do
+        ffi.cast("u8 *", dst_c_ptr)[i-1] = v
+      end
+      return(#src)
+    else
+      return 1, src -- ffi.cast("u8", src)
+    end
+  end
+  vpp.t_c2lua["u8"] = function(c_type, src_ptr, src_len)
+    if src_len then
+      return ffi.string(src_ptr, src_len)
+    else
+      return (tonumber(src_ptr))
+    end
+  end
+
+  vpp.t_lua2c["u16"] = function(c_type, src, dst_c_ptr)
+    if type(src) == "table" then
+      for i,v in ipairs(src) do
+        ffi.cast("u16 *", dst_c_ptr)[i-1] = ffi.C.htons(v)
+      end
+      return(2 * #src)
+    else
+      return 2, (ffi.C.htons(src))
+    end
+  end
+  vpp.t_c2lua["u16"] = function(c_type, src_ptr, src_len)
+    if src_len then
+      local out = {}
+      for i = 0,src_len-1 do
+        out[i+1] = tonumber(ffi.C.ntohs(src_ptr[i]))
+      end
+      return out
+    else
+      return (tonumber(ffi.C.ntohs(src_ptr)))
+    end
+  end
+
+  vpp.t_lua2c["u32"] = function(c_type, src, dst_c_ptr)
+    if type(src) == "table" then
+      for i,v in ipairs(src) do
+        ffi.cast("u32 *", dst_c_ptr)[i-1] = ffi.C.htonl(v)
+      end
+      return(4 * #src)
+    else
+      return 4, (ffi.C.htonl(src))
+    end
+  end
+  vpp.t_c2lua["u32"] = function(c_type, src_ptr, src_len)
+    if src_len then
+      local out = {}
+      for i = 0,src_len-1 do
+        out[i+1] = tonumber(ffi.C.ntohl(src_ptr[i]))
+      end
+      return out
+    else
+      return (tonumber(ffi.C.ntohl(src_ptr)))
+    end
+  end
+  vpp.t_lua2c["i32"] = function(c_type, src, dst_c_ptr)
+    if type(src) == "table" then
+      for i,v in ipairs(src) do
+        ffi.cast("i32 *", dst_c_ptr)[i-1] = ffi.C.htonl(v)
+      end
+      return(4 * #src)
+    else
+      return 4, (ffi.C.htonl(src))
+    end
+  end
+  vpp.t_c2lua["i32"] = function(c_type, src_ptr, src_len)
+    local ntohl = function(src)
+      local u32val = ffi.cast("u32", src)
+      local ntohlval = (ffi.C.ntohl(u32val))
+      local out = tonumber(ffi.cast("i32", ntohlval + 0LL))
+      return out
+    end
+    if src_len then
+      local out = {}
+      for i = 0,src_len-1 do
+        out[i+1] = tonumber(ntohl(src_ptr[i]))
+      end
+    else
+      return (tonumber(ntohl(src_ptr)))
+    end
+  end
+
+  vpp.t_lua2c["u64"] = function(c_type, src, dst_c_ptr)
+    if type(src) == "table" then
+      for i,v in ipairs(src) do
+        ffi.cast("u64 *", dst_c_ptr)[i-1] = v --- FIXME ENDIAN
+      end
+      return(8 * #src)
+    else
+      return 8, ffi.cast("u64", src) --- FIXME ENDIAN
+    end
+  end
+  vpp.t_c2lua["u64"] = function(c_type, src_ptr, src_len)
+    if src_len then
+      local out = {}
+      for i = 0,src_len-1 do
+        out[i+1] = tonumber(src_ptr[i]) -- FIXME ENDIAN
+      end
+      return out
+    else
+      return (tonumber(src_ptr)) --FIXME ENDIAN
+    end
+  end
+
+
+
+
+  vpp.t_lua2c["__MSG__"] = function(c_type, src, dst_c_ptr)
+    local dst = ffi.cast(c_type .. " *", dst_c_ptr)
+    local additional_len = 0
+    local fields_info = vpp.c_type_to_fields[c_type]
+    -- print("__MSG__ type: " .. tostring(c_type))
+    ffi.C.memset(dst_c_ptr, 0, ffi.sizeof(dst[0]))
+    -- print(vpp.dump(fields_info))
+    -- print(vpp.dump(src))
+    for k,v in pairs(src) do
+      local field = fields_info[k]
+      if not field then
+        print("ERROR: field " .. tostring(k) .. " in message " .. tostring(c_type) .. " is unknown")
+      end
+      local lua2c = vpp.t_lua2c[field.c_type]
+      -- print("__MSG__ field " .. tostring(k) .. " : " .. vpp.dump(field))
+      -- if the field is not an array type, try to coerce the argument to a number
+      if not field.array and type(v) == "string" then
+        v = tonumber(v)
+      end
+      if not lua2c then
+        print("__MSG__ " .. tostring(c_type) .. " t_lua2c: can not store field " .. field.name ..
+              " type " .. field.c_type .. " dst " .. tostring(dst[k]))
+        return 0
+      end
+      local len = 0
+      local val = nil
+      if field.array and (type(v) == "table") then
+        print("NTFY: field " .. tostring(k) .. " in message " .. tostring(c_type) .. " is an array")
+        for field_i, field_v in ipairs(v) do
+          print("NTFY: setting member#" .. tostring(field_i) .. " to value " .. vpp.dump(field_v))
+          local field_len, field_val = lua2c(field.c_type, field_v, dst[k][field_i-1])
+          len = len + field_len
+        end
+      else
+        len, val = lua2c(field.c_type, v, dst[k])
+      end
+      if not field.array then
+        dst[k] = val
+      else
+        if 0 == field.array then
+          additional_len = additional_len + len
+          -- print("Adding " .. tostring(len) .. " bytes due to field " .. tostring(field.name))
+          -- If there is a variable storing the length
+          -- and the input table does not set it, do magic
+          if field.array_size and not src[field.array_size] then
+            local size_field = fields_info[field.array_size]
+            if size_field then
+              dst[field.array_size] = vpp.t_c2lua[size_field.c_type](size_field.c_type, len)
+            end
+          end
+        end
+      end
+      -- print("Full message:\n" .. vpp.hex_dump(ffi.string(ffi.cast('void *', req_store_cache), 64)))
+    end
+    return (ffi.sizeof(dst[0])+additional_len)
+  end
+
+  vpp.t_c2lua["__MSG__"] = function(c_type, src_ptr, src_len)
+    local out = {}
+    local reply_typed_ptr = ffi.cast(c_type .. " *", src_ptr)
+    local field_desc = vpp.c_type_to_fields[c_type]
+    if src_len then
+      for i = 0,src_len-1 do
+        out[i+1] = vpp.t_c2lua[c_type](c_type, src_ptr[i])
+      end
+      return out
+    end
+
+    for k, v in pairs(field_desc) do
+      local v_c2lua = vpp.t_c2lua[v.c_type]
+      if v_c2lua then
+        local len = v.array
+        -- print(dump(v))
+        if len then
+          local len_field_name = k .. "_length"
+          local len_field = field_desc[len_field_name]
+          if (len_field) then
+            local real_len = vpp.t_c2lua[len_field.c_type](len_field.c_type, reply_typed_ptr[len_field_name])
+            out[k] =  v_c2lua(v.c_type, reply_typed_ptr[k], real_len)
+          elseif len == 0 then
+            -- check if len = 0, then must be a field which contains the size
+            len_field =  field_desc[v.array_size]
+            local real_len = vpp.t_c2lua[len_field.c_type](len_field.c_type, reply_typed_ptr[v.array_size])
+            -- print("REAL length: " .. vpp.dump(v) .. " : " .. tostring(real_len))
+            out[k] = v_c2lua(v.c_type, reply_typed_ptr[k], real_len)
+          else
+            -- alas, just stuff the entire array
+            out[k] = v_c2lua(v.c_type, reply_typed_ptr[k], len)
+          end
+        else
+          out[k] =  v_c2lua(v.c_type, reply_typed_ptr[k])
+        end
+      else
+        out[k] = "<no accessor function for type " .. tostring(v.c_type) .. ">"
+      end
+      -- print(k, out[k])
+    end
+    return out
+  end
+
+  return vpp
+end
+
+function vpp.connect(vpp, client_name)
+    local name = "lua_client"
+    if client_name then
+      name = client_name
+    end
+    local ret = vpp.pneum.pneum_connect_sync(vpp.c_str(client_name), nil)
+    if tonumber(ret) == 0 then
+      vpp.is_connected = true
+    end
+  end
+
+function vpp.disconnect(vpp)
+    vpp.pneum.pneum_disconnect()
+  end
+
+function vpp.consume_api(vpp, path, plugin_name)
+    -- print("Consuming the VPP api from "..path)
+    local ffii = {}
+    local f = io.open(path, "r")
+    if not f then
+      print("Could not open " .. path)
+      return nil
+    end
+    local data = f:read("*all")
+    -- Remove all C comments
+    data = data:gsub("/%*.-%*/", "")
+    if vpp.is_connected and not plugin_name then
+      print(path .. ": must specify plugin name!")
+      return
+    end
+    if plugin_name then
+      vpp.plugin_version[plugin_name] = vpp.crc_version_string(data)
+      local full_plugin_name = plugin_name .. "_" .. vpp.plugin_version[plugin_name]
+      local reply = vpp:api_call("get_first_msg_id", { name = full_plugin_name } )
+      vpp.next_msg_num = tonumber(reply[1].first_msg_id)
+      print("Plugin " .. full_plugin_name .. " first message is " .. tostring(vpp.next_msg_num))
+    end
+    -- print ("data len: ", #data)
+    data = data:gsub("\n(.-)(%S+)%s*{([^}]*)}", function (preamble, name, members)
+      local _, typeonly = preamble:gsub("typeonly", "")
+      local maybe_msg_id_field = { [0] = "u16 _vl_msg_id;", "" }
+      local onedef = "\n\n#pragma pack(1)\ntypedef struct _vl_api_"..name.. " {\n" ..
+	   -- "   u16 _vl_msg_id;" ..
+           maybe_msg_id_field[typeonly] ..
+	   members:gsub("%[[a-zA-Z_]+]", "[0]") ..
+	   "} vl_api_" .. name .. "_t;"
+
+      local c_type = "vl_api_" .. name .. "_t"
+
+      local fields = {}
+      -- vpp.msg_name_to_fields[name] = fields
+      -- print("CTYPE " .. c_type)
+      vpp.c_type_to_fields[c_type] = fields
+      vpp.t_lua2c[c_type] = vpp.t_lua2c["__MSG__"]
+      vpp.t_c2lua[c_type] = vpp.t_c2lua["__MSG__"]
+      local mirec = { name = "_vl_msg_id", c_type = "u16", array = nil, array_size = nil }
+      if typeonly == 0 then
+        fields[mirec.name] = mirec
+      end
+
+      -- populate the field reflection table for the message
+      -- sets the various type information as well as the accessors for lua<->C conversion
+      members:gsub("(%S+)%s+(%S+);", function (fieldtype, fieldname)
+          local fieldcount = nil
+          local fieldcountvar = nil
+          -- data = data:gsub("%[[a-zA-Z_]+]", "[0]")
+          fieldname = fieldname:gsub("(%b[])", function(cnt)
+              fieldcount = tonumber(cnt:sub(2, -2));
+              if not fieldcount then
+                fieldcount = 0
+                fieldcountvar = cnt:sub(2, -2)
+              end
+              return ""
+            end)
+	  local fieldrec = { name = fieldname, c_type = fieldtype, array = fieldcount, array_size = fieldcountvar }
+          if fieldcount then
+            if fieldtype == "u8" then
+              -- any array of bytes is treated as a string
+            elseif vpp.t_lua2c[fieldtype] then
+              -- print("Array of " .. fieldtype .. " is ok!")
+            else
+              print("Unknown array type: ", name,  " : " , fieldname, " : ", fieldtype, ":", fieldcount, ":", fieldcountvar)
+            end
+          end
+	  fields[fieldname] = fieldrec
+	end)
+
+      -- print(dump(fields))
+
+      if typeonly == 0 then
+	local this_message_number = vpp.next_msg_num
+	vpp.next_msg_num = vpp.next_msg_num + 1
+	vpp.msg_name_to_number[name] = this_message_number
+	vpp.msg_number_to_name[this_message_number] = name
+	vpp.msg_number_to_type[this_message_number] = "vl_api_" .. name .. "_t"
+	vpp.msg_number_to_pointer_type[this_message_number] = vpp.msg_number_to_type[this_message_number] .. " *"
+	onedef = onedef .. "\n\n enum { vl_msg_" .. name .. " = " .. this_message_number .. " };\n\n"
+      end
+      table.insert(ffii, onedef);
+      return "";
+      end)
+    local cdef = table.concat(ffii)
+    -- print(cdef)
+    ffi.cdef(cdef)
+  end
+
+
+function vpp.lua2c(vpp, c_type, src, dst_c_ptr)
+  -- returns the number of bytes written to memory pointed by dst
+  local lua2c = vpp.t_lua2c[c_type]
+  if lua2c then
+    return(lua2c(c_type, src, dst_c_ptr))
+  else
+    print("vpp.lua2c: do not know how to store type " .. c_type)
+    return 0
+  end
+end
+
+function vpp.c2lua(vpp, c_type, src_ptr, src_len)
+  -- returns the lua data structure
+  local c2lua = vpp.t_c2lua[c_type]
+  if c2lua then
+    return(c2lua(c_type, src_ptr, src_len))
+  else
+    print("vpp.c2lua: do not know how to load type " .. c_type)
+    return nil
+  end
+end
+
+local req_store_cache = ffi.new("vl_api_opaque_message_t[1]")
+
+function vpp.api_write(vpp, api_name, req_table)
+    local msg_num = vpp.msg_name_to_number[api_name]
+    if not msg_num then
+      print ("API call "..api_name.." is not known")
+      return nil
+    end
+
+    if not req_table then
+      req_table = {}
+    end
+    req_table._vl_msg_id = msg_num
+
+    local packed_len = vpp:lua2c(vpp.msg_number_to_type[msg_num], req_table, req_store_cache)
+    if vpp.debug_dump then
+      print("Write Message length: " .. tostring(packed_len) .. "\n" .. vpp.hex_dump(ffi.string(ffi.cast('void *', req_store_cache), packed_len)))
+    end
+
+    res = vpp.pneum.pneum_write(ffi.cast('void *', req_store_cache), packed_len)
+    return res
+  end
+
+local rep_store_cache = ffi.new("vl_api_opaque_message_t *[1]")
+local rep_len_cache = ffi.new("int[1]")
+
+function vpp.api_read(vpp)
+    local rep_type = "vl_api_opaque_message_t"
+    local rep = rep_store_cache
+    local replen = rep_len_cache
+    res = vpp.pneum.pneum_read(ffi.cast("void *", rep), replen)
+    if vpp.debug_dump then
+      print("Read Message length: " .. tostring(replen[0]) .. "\n" .. vpp.hex_dump(ffi.string(ffi.cast('void *', rep[0]), replen[0])))
+    end
+
+    local reply_msg_num = ffi.C.ntohs(rep[0]._vl_msg_id)
+    local reply_msg_name = vpp.msg_number_to_name[reply_msg_num]
+
+    local reply_typed_ptr = ffi.cast(vpp.msg_number_to_pointer_type[reply_msg_num], rep[0])
+    local out = vpp:c2lua(vpp.msg_number_to_type[reply_msg_num], rep[0], nil, replen[0])
+    if type(out) == "table" then
+      out["luaapi_message_name"] = reply_msg_name
+    end
+
+    vpp.pneum.pneum_data_free(ffi.cast('void *',rep[0]))
+
+    return reply_msg_name, out
+  end
+
+function vpp.api_call(vpp, api_name, req_table, options_in)
+    local msg_num = vpp.msg_name_to_number[api_name]
+    local end_message_name = api_name .."_reply"
+    local replies = {}
+    local cstruct = ""
+    local options = options_in or {}
+    if msg_num then
+      vpp:api_write(api_name, req_table)
+      if not vpp.msg_name_to_number[end_message_name] or options.force_ping then
+        end_message_name = "control_ping_reply"
+        vpp:api_write("control_ping")
+      end
+      repeat
+        reply_message_name, reply = vpp:api_read()
+        if reply and not reply.context then
+          -- there may be async events inbetween
+          table.insert(vpp.events, reply)
+        else
+          if reply_message_name ~= "control_ping_reply" then
+            -- do not insert the control ping encapsulation
+            table.insert(replies, reply)
+          end
+        end
+        -- print(reply)
+      until reply_message_name == end_message_name
+    else
+      print(api_name .. " is an unknown API call")
+      return nil
+    end
+    return replies
+  end
+
+return vpp