SNAT: IPFIX logging (VPP-445) 72/4672/2
authorMatus Fabian <matfabia@cisco.com>
Fri, 13 Jan 2017 12:15:54 +0000 (04:15 -0800)
committerDamjan Marion <dmarion.lists@gmail.com>
Sat, 14 Jan 2017 09:44:18 +0000 (09:44 +0000)
Change-Id: I8450217dd43a1cd9f510e40dfb22274ffc33a4c6
Signed-off-by: Matus Fabian <matfabia@cisco.com>
src/plugins/snat.am
src/plugins/snat/in2out.c
src/plugins/snat/out2in.c
src/plugins/snat/snat.api
src/plugins/snat/snat.c
src/plugins/snat/snat_ipfix_logging.c [new file with mode: 0644]
src/plugins/snat/snat_ipfix_logging.h [new file with mode: 0644]
src/plugins/snat/snat_test.c
test/ipfix.py [new file with mode: 0644]
test/test_snat.py
test/vpp_papi_provider.py

index 7ff2386..8611d15 100644 (file)
@@ -18,7 +18,8 @@ vppplugins_LTLIBRARIES += snat_plugin.la
 snat_plugin_la_SOURCES = snat/snat.c           \
         snat/in2out.c                          \
         snat/out2in.c                          \
-       snat/snat_plugin.api.h
+       snat/snat_plugin.api.h                  \
+        snat/snat_ipfix_logging.c
 
 API_FILES += snat/snat.api
 
index cd8f127..76a6a12 100644 (file)
@@ -22,6 +22,7 @@
 #include <vnet/ethernet/ethernet.h>
 #include <vnet/fib/ip4_fib.h>
 #include <snat/snat.h>
+#include <snat/snat_ipfix_logging.h>
 
 #include <vppinfra/hash.h>
 #include <vppinfra/error.h>
@@ -213,6 +214,14 @@ static u32 slow_path (snat_main_t *sm, vlib_buffer_t *b0,
       if (clib_bihash_add_del_8_8 (&sm->out2in, &kv0, 0 /* is_add */))
           clib_warning ("out2in key delete failed");
 
+      /* log NAT event */
+      snat_ipfix_logging_nat44_ses_delete(s->in2out.addr.as_u32,
+                                          s->out2in.addr.as_u32,
+                                          s->in2out.protocol,
+                                          s->in2out.port,
+                                          s->out2in.port,
+                                          s->in2out.fib_index);
+
       snat_free_outside_address_and_port 
         (sm, &s->out2in, s->outside_address_index);
       s->outside_address_index = ~0;
@@ -302,6 +311,14 @@ static u32 slow_path (snat_main_t *sm, vlib_buffer_t *b0,
   kv0.key = worker_by_out_key.as_u64;
   kv0.value = cpu_index;
   clib_bihash_add_del_8_8 (&sm->worker_by_out, &kv0, 1);
+
+  /* log NAT event */
+  snat_ipfix_logging_nat44_ses_create(s->in2out.addr.as_u32,
+                                      s->out2in.addr.as_u32,
+                                      s->in2out.protocol,
+                                      s->in2out.port,
+                                      s->out2in.port,
+                                      s->in2out.fib_index);
   return next0;
 }
                       
index 0c9c9cd..f132973 100644 (file)
@@ -22,6 +22,7 @@
 #include <vnet/ethernet/ethernet.h>
 #include <vnet/fib/ip4_fib.h>
 #include <snat/snat.h>
+#include <snat/snat_ipfix_logging.h>
 
 #include <vppinfra/hash.h>
 #include <vppinfra/error.h>
@@ -210,7 +211,14 @@ create_session_for_static_mapping (snat_main_t *sm,
   if (clib_bihash_add_del_8_8 (&sm->out2in, &kv0, 1 /* is_add */))
       clib_warning ("out2in key add failed");
 
-  return s;
+  /* log NAT event */
+  snat_ipfix_logging_nat44_ses_create(s->in2out.addr.as_u32,
+                                      s->out2in.addr.as_u32,
+                                      s->in2out.protocol,
+                                      s->in2out.port,
+                                      s->out2in.port,
+                                      s->in2out.fib_index);
+   return s;
 }
 
 static inline u32 icmp_out2in_slow_path (snat_main_t *sm,
index f046a96..ff1d9bc 100644 (file)
@@ -318,3 +318,27 @@ define snat_interface_addr_details {
   u32 context;
   u32 sw_if_index;
 };
+
+/** \brief Enable/disable S-NAT IPFIX logging
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param domain_id - observation domain ID
+    @param src_port - source port number
+    @param enable - 1 if enable, 0 if disable
+*/
+define snat_ipfix_enable_disable {
+  u32 client_index;
+  u32 context;
+  u32 domain_id;
+  u16 src_port;
+  u8 enable;
+};
+
+/** \brief Enable/disable S-NAT IPFIX logging reply
+    @param context - sender context, to match reply w/ request
+    @param retval - return code
+*/
+define snat_ipfix_enable_disable_reply {
+  u32 context;
+  i32 retval;
+};
index a1236cf..9edb0d5 100644 (file)
@@ -21,6 +21,7 @@
 #include <vnet/plugin/plugin.h>
 #include <vlibapi/api.h>
 #include <snat/snat.h>
+#include <snat/snat_ipfix_logging.h>
 
 #include <vlibapi/api.h>
 #include <vlibmemory/api.h>
@@ -278,6 +279,13 @@ int snat_del_address (snat_main_t *sm, ip4_address_t addr)
           pool_foreach (ses, tsm->sessions, ({
             if (ses->out2in.addr.as_u32 == addr.as_u32)
               {
+                /* log NAT event */
+                snat_ipfix_logging_nat44_ses_delete(ses->in2out.addr.as_u32,
+                                                    ses->out2in.addr.as_u32,
+                                                    ses->in2out.protocol,
+                                                    ses->in2out.port,
+                                                    ses->out2in.port,
+                                                    ses->in2out.fib_index);
                 vec_add1 (ses_to_be_removed, ses - tsm->sessions);
                 kv.key = ses->in2out.as_u64;
                 clib_bihash_add_del_8_8 (&sm->in2out, &kv, 0);
@@ -550,6 +558,14 @@ int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr,
                             continue;
                         }
 
+                      /* log NAT event */
+                      snat_ipfix_logging_nat44_ses_delete(s->in2out.addr.as_u32,
+                                                          s->out2in.addr.as_u32,
+                                                          s->in2out.protocol,
+                                                          s->in2out.port,
+                                                          s->out2in.port,
+                                                          s->in2out.fib_index);
+
                       value.key = s->in2out.as_u64;
                       clib_bihash_add_del_8_8 (&sm->in2out, &value, 0);
                       value.key = s->out2in.as_u64;
@@ -1172,6 +1188,37 @@ static void *vl_api_snat_interface_addr_dump_t_print
   FINISH;
 }
 
+static void
+vl_api_snat_ipfix_enable_disable_t_handler
+(vl_api_snat_ipfix_enable_disable_t * mp)
+{
+  snat_main_t * sm = &snat_main;
+  vl_api_snat_ipfix_enable_disable_reply_t * rmp;
+  int rv = 0;
+
+  rv = snat_ipfix_logging_enable_disable(mp->enable,
+                                         clib_host_to_net_u32 (mp->domain_id),
+                                         clib_host_to_net_u16 (mp->src_port));
+
+  REPLY_MACRO (VL_API_SNAT_IPFIX_ENABLE_DISABLE_REPLY);
+}
+
+static void *vl_api_snat_ipfix_enable_disable_t_print
+(vl_api_snat_ipfix_enable_disable_t *mp, void * handle)
+{
+  u8 * s;
+
+  s = format (0, "SCRIPT: snat_ipfix_enable_disable ");
+  if (mp->domain_id)
+    s = format (s, "domain %d ", clib_net_to_host_u32 (mp->domain_id));
+  if (mp->src_port)
+    s = format (s, "src_port %d ", clib_net_to_host_u16 (mp->src_port));
+  if (!mp->enable)
+    s = format (s, "disable ");
+
+  FINISH;
+}
+
 /* List of message types that this plugin understands */
 #define foreach_snat_plugin_api_msg                                     \
 _(SNAT_ADD_ADDRESS_RANGE, snat_add_address_range)                       \
@@ -1185,7 +1232,8 @@ _(SNAT_INTERFACE_DUMP, snat_interface_dump)                             \
 _(SNAT_SET_WORKERS, snat_set_workers)                                   \
 _(SNAT_WORKER_DUMP, snat_worker_dump)                                   \
 _(SNAT_ADD_DEL_INTERFACE_ADDR, snat_add_del_interface_addr)             \
-_(SNAT_INTERFACE_ADDR_DUMP, snat_interface_addr_dump)
+_(SNAT_INTERFACE_ADDR_DUMP, snat_interface_addr_dump)                   \
+_(SNAT_IPFIX_ENABLE_DISABLE, snat_ipfix_enable_disable)
 
 /* Set up the API message handling tables */
 static clib_error_t *
@@ -1303,6 +1351,9 @@ static clib_error_t * snat_init (vlib_main_t * vm)
 
   vec_add1 (im->add_del_interface_address_callbacks, cb4);
 
+  /* Init IPFIX logging */
+  snat_ipfix_logging_init(vm);
+
   return error;
 }
 
@@ -1420,6 +1471,7 @@ int snat_alloc_outside_address_and_port (snat_main_t * sm,
         }
     }
   /* Totally out of translations to use... */
+  snat_ipfix_logging_addresses_exhausted(0);
   return 1;
 }
 
@@ -1719,6 +1771,58 @@ VLIB_CLI_COMMAND (set_workers_command, static) = {
     "set snat workers <workers-list>",
 };
 
+static clib_error_t *
+snat_ipfix_logging_enable_disable_command_fn (vlib_main_t * vm,
+                                              unformat_input_t * input,
+                                              vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  u32 domain_id = 0;
+  u32 src_port = 0;
+  u8 enable = 1;
+  int rv = 0;
+
+  /* Get a line of input. */
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "domain %d", &domain_id))
+        ;
+      else if (unformat (line_input, "src-port %d", &src_port))
+        ;
+      else if (unformat (line_input, "disable"))
+        enable = 0;
+      else
+        return clib_error_return (0, "unknown input '%U'",
+          format_unformat_error, input);
+     }
+  unformat_free (line_input);
+
+  rv = snat_ipfix_logging_enable_disable (enable, domain_id, (u16) src_port);
+
+  if (rv)
+    return clib_error_return (0, "ipfix logging enable failed");
+
+  return 0;
+}
+
+/*?
+ * @cliexpar
+ * @cliexstart{snat ipfix logging}
+ * To enable SNAT IPFIX logging use:
+ *  vpp# snat ipfix logging
+ * To set IPFIX exporter use:
+ *  vpp# set ipfix exporter collector 10.10.10.3 src 10.10.10.1
+ * @cliexend
+?*/
+VLIB_CLI_COMMAND (snat_ipfix_logging_enable_disable_command, static) = {
+  .path = "snat ipfix logging",
+  .function = snat_ipfix_logging_enable_disable_command_fn,
+  .short_help = "snat ipfix logging [domain <domain-id>] [src-port <port>] [disable]",
+};
+
 static clib_error_t *
 snat_config (vlib_main_t * vm, unformat_input_t * input)
 {
@@ -1968,7 +2072,7 @@ show_snat_command_fn (vlib_main_t * vm,
             ({
               s = format (s, " %d", j);
             }));
-          vlib_cli_output (vm, "  %d busy ports:%v", ap->busy_ports, s);
+          vlib_cli_output (vm, "  %d busy ports:%s", ap->busy_ports, s);
         }
     }
 
@@ -1981,7 +2085,7 @@ show_snat_command_fn (vlib_main_t * vm,
             {
               vlib_worker_thread_t *w =
                 vlib_worker_threads + *worker + sm->first_worker_index;
-              vlib_cli_output (vm, "  %v", w->name);
+              vlib_cli_output (vm, "  %s", w->name);
             }
         }
     }
@@ -2032,7 +2136,7 @@ show_snat_command_fn (vlib_main_t * vm,
                 continue;
 
               vlib_worker_thread_t *w = vlib_worker_threads + j;
-              vlib_cli_output (vm, "Thread %d (%v at lcore %u):", j, w->name,
+              vlib_cli_output (vm, "Thread %d (%s at lcore %u):", j, w->name,
                                w->lcore_id);
               vlib_cli_output (vm, "  %d list pool elements",
                                pool_elts (tsm->list_pool));
diff --git a/src/plugins/snat/snat_ipfix_logging.c b/src/plugins/snat/snat_ipfix_logging.c
new file mode 100644 (file)
index 0000000..d72eb22
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+ * snat_ipfix_logging.c - NAT Events IPFIX logging
+ *
+ * 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.
+ */
+
+#include <vnet/flow/flow_report.h>
+#include <vlibmemory/api.h>
+#include <snat/snat.h>
+#include <snat/snat_ipfix_logging.h>
+
+snat_ipfix_logging_main_t snat_ipfix_logging_main;
+
+#define NAT44_SESSION_CREATE_LEN 26
+#define NAT_ADDRESSES_EXHAUTED_LEN 13
+
+#define NAT44_SESSION_CREATE_FIELD_COUNT 8
+#define NAT_ADDRESSES_EXHAUTED_FIELD_COUNT 3
+
+typedef struct {
+  u8 nat_event;
+  u32 src_ip;
+  u32 nat_src_ip;
+  snat_protocol_t snat_proto;
+  u16 src_port;
+  u16 nat_src_port;
+  u32 vrf_id;
+} snat_ipfix_logging_nat44_ses_args_t;
+
+typedef struct {
+  u32 pool_id;
+} snat_ipfix_logging_addr_exhausted_args_t;
+
+/**
+ * @brief Create an IPFIX template packet rewrite string
+ *
+ * @param frm               flow report main
+ * @param fr                flow report
+ * @param collector_address collector address
+ * @param src_address       source address
+ * @param collector_port    collector
+ * @param event             NAT event ID
+ *
+ * @returns template packet
+ */
+static inline u8 *
+snat_template_rewrite (flow_report_main_t * frm,
+                       flow_report_t * fr,
+                       ip4_address_t * collector_address,
+                       ip4_address_t * src_address,
+                       u16 collector_port,
+                       nat_event_t event)
+{
+  snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main;
+  ip4_header_t *ip;
+  udp_header_t *udp;
+  ipfix_message_header_t *h;
+  ipfix_set_header_t *s;
+  ipfix_template_header_t *t;
+  ipfix_field_specifier_t *f;
+  ipfix_field_specifier_t *first_field;
+  u8 *rewrite = 0;
+  ip4_ipfix_template_packet_t *tp;
+  u32 field_count = 0;
+  flow_report_stream_t *stream;
+
+  stream = &frm->streams[fr->stream_index];
+  silm->stream_index = fr->stream_index;
+
+  if (event == NAT_ADDRESSES_EXHAUTED)
+    {
+      field_count = NAT_ADDRESSES_EXHAUTED_FIELD_COUNT;
+      silm->addr_exhausted_template_id = fr->template_id;
+    }
+  else if (event == NAT44_SESSION_CREATE)
+    {
+      field_count = NAT44_SESSION_CREATE_FIELD_COUNT;
+      silm->nat44_session_template_id = fr->template_id;
+    }
+
+  /* allocate rewrite space */
+  vec_validate_aligned (rewrite,
+                       sizeof (ip4_ipfix_template_packet_t)
+                       + field_count * sizeof (ipfix_field_specifier_t) - 1,
+                       CLIB_CACHE_LINE_BYTES);
+
+  tp = (ip4_ipfix_template_packet_t *) rewrite;
+  ip = (ip4_header_t *) & tp->ip4;
+  udp = (udp_header_t *) (ip + 1);
+  h = (ipfix_message_header_t *) (udp + 1);
+  s = (ipfix_set_header_t *) (h + 1);
+  t = (ipfix_template_header_t *) (s + 1);
+  first_field = f = (ipfix_field_specifier_t *) (t + 1);
+
+  ip->ip_version_and_header_length = 0x45;
+  ip->ttl = 254;
+  ip->protocol = IP_PROTOCOL_UDP;
+  ip->src_address.as_u32 = src_address->as_u32;
+  ip->dst_address.as_u32 = collector_address->as_u32;
+  udp->src_port = clib_host_to_net_u16 (stream->src_port);
+  udp->dst_port = clib_host_to_net_u16 (collector_port);
+  udp->length = clib_host_to_net_u16 (vec_len (rewrite) - sizeof (*ip));
+
+  /* FIXUP: message header export_time */
+  h->domain_id = clib_host_to_net_u32 (stream->domain_id);
+
+  /* Add TLVs to the template */
+  if (event == NAT_ADDRESSES_EXHAUTED)
+    {
+      f->e_id_length = ipfix_e_id_length (0, observationTimeMilliseconds, 8);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, natEvent, 1);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, natPoolId, 4);
+      f++;
+    }
+  else if (event == NAT44_SESSION_CREATE)
+    {
+      f->e_id_length = ipfix_e_id_length (0, observationTimeMilliseconds, 8);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, natEvent, 1);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, sourceIPv4Address, 4);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, postNATSourceIPv4Address, 4);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, protocolIdentifier, 1);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, sourceTransportPort, 2);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, postNAPTSourceTransportPort, 2);
+      f++;
+      f->e_id_length = ipfix_e_id_length (0, ingressVRFID, 4);
+      f++;
+    }
+
+  /* Back to the template packet... */
+  ip = (ip4_header_t *) & tp->ip4;
+  udp = (udp_header_t *) (ip + 1);
+
+  ASSERT (f - first_field);
+  /* Field count in this template */
+  t->id_count = ipfix_id_count (fr->template_id, f - first_field);
+
+  /* set length in octets */
+  s->set_id_length =
+    ipfix_set_id_length (2 /* set_id */ , (u8 *) f - (u8 *) s);
+
+  /* message length in octets */
+  h->version_length = version_length ((u8 *) f - (u8 *) h);
+
+  ip->length = clib_host_to_net_u16 ((u8 *) f - (u8 *) ip);
+  ip->checksum = ip4_header_checksum (ip);
+
+  return rewrite;
+}
+
+u8 *
+snat_template_rewrite_addr_exhausted (flow_report_main_t * frm,
+                                      flow_report_t * fr,
+                                      ip4_address_t * collector_address,
+                                      ip4_address_t * src_address,
+                                      u16 collector_port)
+{
+  return snat_template_rewrite (frm, fr, collector_address, src_address,
+                                collector_port, NAT_ADDRESSES_EXHAUTED);
+}
+
+u8 *
+snat_template_rewrite_nat44_session (flow_report_main_t * frm,
+                                     flow_report_t * fr,
+                                     ip4_address_t * collector_address,
+                                     ip4_address_t * src_address,
+                                     u16 collector_port)
+{
+  return snat_template_rewrite (frm, fr, collector_address, src_address,
+                                collector_port, NAT44_SESSION_CREATE);
+}
+
+static inline void
+snat_ipfix_header_create (flow_report_main_t * frm,
+                          vlib_buffer_t * b0,
+                          u32 * offset)
+{
+  snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main;
+  flow_report_stream_t *stream;
+  ip4_ipfix_template_packet_t * tp;
+  ipfix_message_header_t * h = 0;
+  ipfix_set_header_t * s = 0;
+  ip4_header_t * ip;
+  udp_header_t * udp;
+  stream = &frm->streams[silm->stream_index];
+
+  b0->current_data = 0;
+  b0->current_length = sizeof (*ip) + sizeof (*udp) + sizeof (*h) +
+                       sizeof (*s);
+  b0->flags |= (VLIB_BUFFER_TOTAL_LENGTH_VALID | VLIB_BUFFER_FLOW_REPORT);
+  vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0;
+  vnet_buffer (b0)->sw_if_index[VLIB_TX] = frm->fib_index;
+  tp = vlib_buffer_get_current (b0);
+  ip = (ip4_header_t *) &tp->ip4;
+  udp = (udp_header_t *) (ip+1);
+  h = (ipfix_message_header_t *)(udp+1);
+  s = (ipfix_set_header_t *)(h+1);
+
+  ip->ip_version_and_header_length = 0x45;
+  ip->ttl = 254;
+  ip->protocol = IP_PROTOCOL_UDP;
+  ip->flags_and_fragment_offset = 0;
+  ip->src_address.as_u32 = frm->src_address.as_u32;
+  ip->dst_address.as_u32 = frm->ipfix_collector.as_u32;
+  udp->src_port = clib_host_to_net_u16 (UDP_DST_PORT_ipfix);
+  udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_ipfix);
+  udp->checksum = 0;
+
+  h->export_time = clib_host_to_net_u32 (
+    (u32) (((f64)frm->unix_time_0) + (vlib_time_now(frm->vlib_main) -
+    frm->vlib_time_0)));
+  h->sequence_number = clib_host_to_net_u32 (stream->sequence_number++);
+  h->domain_id = clib_host_to_net_u32 (stream->domain_id);
+
+  *offset = (u32) (((u8 *)(s+1)) - (u8 *)tp);
+}
+
+static inline void
+snat_ipfix_send (flow_report_main_t * frm,
+                 vlib_frame_t * f,
+                 vlib_buffer_t * b0,
+                 u16 template_id)
+{
+  ip4_ipfix_template_packet_t * tp;
+  ipfix_message_header_t * h = 0;
+  ipfix_set_header_t * s = 0;
+  ip4_header_t * ip;
+  udp_header_t * udp;
+  vlib_main_t * vm = frm->vlib_main;
+
+  tp = vlib_buffer_get_current (b0);
+  ip = (ip4_header_t *) & tp->ip4;
+  udp = (udp_header_t *) (ip + 1);
+  h = (ipfix_message_header_t *) (udp + 1);
+  s = (ipfix_set_header_t *) (h + 1);
+
+  s->set_id_length = ipfix_set_id_length (template_id,
+                                          b0->current_length -
+                                          (sizeof (*ip) + sizeof (*udp) +
+                                           sizeof (*h)));
+  h->version_length = version_length (b0->current_length -
+                                      (sizeof (*ip) + sizeof (*udp)));
+
+  ip->length = clib_host_to_net_u16 (b0->current_length);
+  ip->checksum = ip4_header_checksum (ip);
+  udp->length = clib_host_to_net_u16 (b0->current_length - sizeof (*ip));
+
+  if (frm->udp_checksum)
+    {
+      udp->checksum = ip4_tcp_udp_compute_checksum (vm, b0, ip);
+      if (udp->checksum == 0)
+        udp->checksum = 0xffff;
+    }
+
+  ASSERT (ip->checksum == ip4_header_checksum (ip));
+
+  vlib_put_frame_to_node (vm, ip4_lookup_node.index, f);
+}
+
+static void
+snat_ipfix_logging_nat44_ses (u8 nat_event, u32 src_ip, u32 nat_src_ip,
+                              snat_protocol_t snat_proto, u16 src_port,
+                              u16 nat_src_port, u32 vrf_id, int do_flush)
+{
+  snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main;
+  flow_report_main_t *frm = &flow_report_main;
+  vlib_frame_t *f;
+  vlib_buffer_t *b0 = 0;
+  u32 bi0 = ~0;
+  u32 offset;
+  vlib_main_t * vm = frm->vlib_main;
+  u64 now;
+  vlib_buffer_free_list_t *fl;
+  u8 proto = ~0;
+
+  if (!silm->enabled)
+    return;
+
+  proto = (snat_proto == SNAT_PROTOCOL_UDP) ? IP_PROTOCOL_UDP : proto;
+  proto = (snat_proto == SNAT_PROTOCOL_TCP) ? IP_PROTOCOL_TCP : proto;
+  proto = (snat_proto == SNAT_PROTOCOL_ICMP) ? IP_PROTOCOL_ICMP : proto;
+
+  now = (u64) ((vlib_time_now (vm) - silm->vlib_time_0) * 1e3);
+  now += silm->milisecond_time_0;
+
+  b0 = silm->nat44_session_buffer;
+
+  if (PREDICT_FALSE (b0 == 0))
+    {
+      if (do_flush)
+        return;
+
+      if (vlib_buffer_alloc (vm, &bi0, 1) != 1)
+        {
+          clib_warning ("can't allocate buffer for NAT IPFIX event");
+          return;
+        }
+
+      b0 = silm->nat44_session_buffer =
+        vlib_get_buffer (vm, bi0);
+      fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
+      vlib_buffer_init_for_free_list (b0, fl);
+      VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0);
+      offset = 0;
+    }
+  else
+    {
+      bi0 = vlib_get_buffer_index (vm, b0);
+      offset = silm->nat44_session_next_record_offset;
+    }
+
+  f = silm->nat44_session_frame;
+  if (PREDICT_FALSE (f == 0))
+    {
+      u32 * to_next;
+      f = vlib_get_frame_to_node (vm, ip4_lookup_node.index);
+      silm->nat44_session_frame = f;
+      to_next = vlib_frame_vector_args (f);
+      to_next[0] = bi0;
+      f->n_vectors = 1;
+    }
+
+  if (PREDICT_FALSE (offset == 0))
+    snat_ipfix_header_create (frm, b0, &offset);
+
+  if (PREDICT_TRUE (do_flush == 0))
+    {
+      u64 time_stamp = clib_host_to_net_u64 (now);
+      clib_memcpy (b0->data + offset, &time_stamp, sizeof (time_stamp));
+      offset += sizeof (time_stamp);
+
+      clib_memcpy (b0->data + offset, &nat_event, sizeof (nat_event));
+      offset += sizeof (nat_event);
+
+      clib_memcpy (b0->data + offset, &src_ip, sizeof (src_ip));
+      offset += sizeof (src_ip);
+
+      clib_memcpy (b0->data + offset, &nat_src_ip, sizeof (nat_src_ip));
+      offset += sizeof (nat_src_ip);
+
+      clib_memcpy (b0->data + offset, &proto, sizeof (proto));
+      offset += sizeof (proto);
+
+      clib_memcpy (b0->data + offset, &src_port, sizeof (src_port));
+      offset += sizeof (src_port);
+
+      clib_memcpy (b0->data + offset, &nat_src_port, sizeof (nat_src_port));
+      offset += sizeof (nat_src_port);
+
+      clib_memcpy (b0->data + offset, &vrf_id, sizeof(vrf_id));
+      offset += sizeof (vrf_id);
+
+      b0->current_length += NAT44_SESSION_CREATE_LEN;
+    }
+
+  if (PREDICT_FALSE (do_flush || (offset + NAT44_SESSION_CREATE_LEN) > frm->path_mtu))
+    {
+      snat_ipfix_send (frm, f, b0, silm->nat44_session_template_id);
+      silm->nat44_session_frame = 0;
+      silm->nat44_session_buffer = 0;
+      offset = 0;
+    }
+  silm->nat44_session_next_record_offset = offset;
+ }
+
+static void
+snat_ipfix_logging_addr_exhausted (u32 pool_id, int do_flush)
+{
+  snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main;
+  flow_report_main_t *frm = &flow_report_main;
+  vlib_frame_t *f;
+  vlib_buffer_t *b0 = 0;
+  u32 bi0 = ~0;
+  u32 offset;
+  vlib_main_t * vm = frm->vlib_main;
+  u64 now;
+  vlib_buffer_free_list_t *fl;
+  u8 nat_event = NAT_ADDRESSES_EXHAUTED;
+
+  if (!silm->enabled)
+    return;
+
+  now = (u64) ((vlib_time_now (vm) - silm->vlib_time_0) * 1e3);
+  now += silm->milisecond_time_0;
+
+  b0 = silm->addr_exhausted_buffer;
+
+  if (PREDICT_FALSE (b0 == 0))
+    {
+      if (do_flush)
+        return;
+
+      if (vlib_buffer_alloc (vm, &bi0, 1) != 1)
+        {
+          clib_warning ("can't allocate buffer for NAT IPFIX event");
+          return;
+        }
+
+      b0 = silm->addr_exhausted_buffer =
+        vlib_get_buffer (vm, bi0);
+      fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
+      vlib_buffer_init_for_free_list (b0, fl);
+      VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0);
+      offset = 0;
+    }
+  else
+    {
+      bi0 = vlib_get_buffer_index (vm, b0);
+      offset = silm->addr_exhausted_next_record_offset;
+    }
+
+  f = silm->addr_exhausted_frame;
+  if (PREDICT_FALSE (f == 0))
+    {
+      u32 * to_next;
+      f = vlib_get_frame_to_node (vm, ip4_lookup_node.index);
+      silm->addr_exhausted_frame = f;
+      to_next = vlib_frame_vector_args (f);
+      to_next[0] = bi0;
+      f->n_vectors = 1;
+    }
+
+  if (PREDICT_FALSE (offset == 0))
+    snat_ipfix_header_create (frm, b0, &offset);
+
+  if (PREDICT_TRUE (do_flush == 0))
+    {
+      u64 time_stamp = clib_host_to_net_u64 (now);
+      clib_memcpy (b0->data + offset, &time_stamp, sizeof (time_stamp));
+      offset += sizeof (time_stamp);
+
+      clib_memcpy (b0->data + offset, &nat_event, sizeof (nat_event));
+      offset += sizeof (nat_event);
+
+      clib_memcpy (b0->data + offset, &pool_id, sizeof(pool_id));
+      offset += sizeof (pool_id);
+
+      b0->current_length += NAT_ADDRESSES_EXHAUTED_LEN;
+    }
+
+  if (PREDICT_FALSE (do_flush || (offset + NAT_ADDRESSES_EXHAUTED_LEN) > frm->path_mtu))
+    {
+      snat_ipfix_send (frm, f, b0, silm->addr_exhausted_template_id);
+      silm->addr_exhausted_frame = 0;
+      silm->addr_exhausted_buffer = 0;
+      offset = 0;
+    }
+  silm->addr_exhausted_next_record_offset = offset;
+ }
+
+static void
+snat_ipfix_logging_nat44_ses_rpc_cb (snat_ipfix_logging_nat44_ses_args_t *a)
+{
+  snat_ipfix_logging_nat44_ses(a->nat_event, a->src_ip, a->nat_src_ip,
+                               a->snat_proto, a->src_port, a->nat_src_port,
+                               a->vrf_id, 0);
+}
+
+/**
+ * @brief Generate NAT44 session create event
+ *
+ * @param src_ip       source IPv4 address
+ * @param nat_src_ip   transaltes source IPv4 address
+ * @param snat_proto   SNAT transport protocol
+ * @param src_port     source port
+ * @param nat_src_port translated source port
+ * @param vrf_id       VRF ID
+ */
+void
+snat_ipfix_logging_nat44_ses_create (u32 src_ip,
+                                     u32 nat_src_ip,
+                                     snat_protocol_t snat_proto,
+                                     u16 src_port,
+                                     u16 nat_src_port,
+                                     u32 vrf_id)
+{
+  snat_ipfix_logging_nat44_ses_args_t a;
+
+  a.nat_event = NAT44_SESSION_CREATE;
+  a.src_ip = src_ip;
+  a.nat_src_ip = nat_src_ip;
+  a.snat_proto = snat_proto;
+  a.src_port = src_port;
+  a.nat_src_port = nat_src_port;
+  a.vrf_id = vrf_id;
+
+  vl_api_rpc_call_main_thread (snat_ipfix_logging_nat44_ses_rpc_cb, (u8 *) &a,
+                               sizeof (a));
+}
+
+/**
+ * @brief Generate NAT44 session delete event
+ *
+ * @param src_ip       source IPv4 address
+ * @param nat_src_ip   transaltes source IPv4 address
+ * @param snat_proto   SNAT transport protocol
+ * @param src_port     source port
+ * @param nat_src_port translated source port
+ * @param vrf_id       VRF ID
+ */
+void
+snat_ipfix_logging_nat44_ses_delete (u32 src_ip,
+                                     u32 nat_src_ip,
+                                     snat_protocol_t snat_proto,
+                                     u16 src_port,
+                                     u16 nat_src_port,
+                                     u32 vrf_id)
+{
+  snat_ipfix_logging_nat44_ses_args_t a;
+
+  a.nat_event = NAT44_SESSION_DELETE;
+  a.src_ip = src_ip;
+  a.nat_src_ip = nat_src_ip;
+  a.snat_proto = snat_proto;
+  a.src_port = src_port;
+  a.nat_src_port = nat_src_port;
+  a.vrf_id = vrf_id;
+
+  vl_api_rpc_call_main_thread (snat_ipfix_logging_nat44_ses_rpc_cb, (u8 *) &a,
+                               sizeof (a));
+}
+
+vlib_frame_t *
+snat_data_callback_nat44_session (flow_report_main_t * frm,
+                                  flow_report_t * fr,
+                                  vlib_frame_t * f,
+                                  u32 * to_next,
+                                  u32 node_index)
+{
+  snat_ipfix_logging_nat44_ses(0, 0, 0, 0, 0, 0, 0, 1);
+  return f;
+}
+
+static void
+snat_ipfix_logging_addr_exhausted_rpc_cb
+ (snat_ipfix_logging_addr_exhausted_args_t * a)
+{
+  snat_ipfix_logging_addr_exhausted(a->pool_id, 0);
+}
+
+/**
+ * @brief Generate NAT addresses exhausted event
+ *
+ * @param pool_id NAT pool ID
+ */
+void
+snat_ipfix_logging_addresses_exhausted(u32 pool_id)
+{
+  //TODO: This event SHOULD be rate limited
+  snat_ipfix_logging_addr_exhausted_args_t a;
+
+  a.pool_id = pool_id;
+
+  vl_api_rpc_call_main_thread (snat_ipfix_logging_addr_exhausted_rpc_cb,
+                               (u8 *) &a, sizeof (a));
+}
+
+vlib_frame_t *
+snat_data_callback_addr_exhausted (flow_report_main_t * frm,
+                                   flow_report_t * fr,
+                                   vlib_frame_t * f,
+                                   u32 * to_next,
+                                   u32 node_index)
+{
+  snat_ipfix_logging_addr_exhausted(0, 1);
+  return f;
+}
+
+/**
+ * @brief Enable/disable SNAT IPFIX logging
+ *
+ * @param enable    1 if enable, 0 if disable
+ * @param domain_id observation domain ID
+ * @param src_port  source port number
+ *
+ * @returns 0 if success
+ */
+int
+snat_ipfix_logging_enable_disable (int enable, u32 domain_id, u16 src_port)
+{
+  snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main;
+  flow_report_main_t *frm = &flow_report_main;
+  vnet_flow_report_add_del_args_t a;
+  int rv;
+  u8 e = enable ? 1 : 0;
+
+  if (silm->enabled == e)
+    return 0;
+
+  silm->enabled = e;
+
+  memset (&a, 0, sizeof (a));
+  a.rewrite_callback = snat_template_rewrite_nat44_session;
+  a.flow_data_callback = snat_data_callback_nat44_session;
+  a.is_add = enable;
+  a.domain_id = domain_id ? domain_id : 1;
+  a.src_port = src_port ? src_port : UDP_DST_PORT_ipfix;
+
+  rv = vnet_flow_report_add_del (frm, &a);
+  if (rv)
+    {
+      clib_warning ("vnet_flow_report_add_del returned %d", rv);
+      return -1;
+    }
+
+  a.rewrite_callback = snat_template_rewrite_addr_exhausted;
+  a.flow_data_callback = snat_data_callback_addr_exhausted;
+
+  rv = vnet_flow_report_add_del (frm, &a);
+  if (rv)
+    {
+      clib_warning ("vnet_flow_report_add_del returned %d", rv);
+      return -1;
+    }
+
+  return 0;
+}
+
+/**
+ * @brief Initialize SNAT IPFIX logging
+ *
+ * @param vm vlib main
+ */
+void
+snat_ipfix_logging_init (vlib_main_t * vm)
+{
+  snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main;
+
+  silm->enabled = 0;
+
+  /* Set up time reference pair */
+  silm->vlib_time_0 = vlib_time_now (vm);
+  silm->milisecond_time_0 = unix_time_now_nsec () * 1e-6;
+}
diff --git a/src/plugins/snat/snat_ipfix_logging.h b/src/plugins/snat/snat_ipfix_logging.h
new file mode 100644 (file)
index 0000000..b968ee2
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * snat_ipfix_logging.h - NAT Events IPFIX logging
+ *
+ * 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.
+ */
+#ifndef __included_snat_ipfix_logging_h__
+#define __included_snat_ipfix_logging_h__
+
+typedef enum {
+  NAT_ADDRESSES_EXHAUTED = 3,
+  NAT44_SESSION_CREATE = 4,
+  NAT44_SESSION_DELETE = 5,
+  NAT_PORTS_EXHAUSTED = 12,
+} nat_event_t;
+
+typedef struct {
+  /** S-NAT IPFIX logging enabled */
+  u8 enabled;
+
+  /** ipfix buffers under construction */
+  vlib_buffer_t *nat44_session_buffer;
+  vlib_buffer_t *addr_exhausted_buffer;
+
+  /** frames containing ipfix buffers */
+  vlib_frame_t *nat44_session_frame;
+  vlib_frame_t *addr_exhausted_frame;
+
+  /** next record offset */
+  u32 nat44_session_next_record_offset;
+  u32 addr_exhausted_next_record_offset;
+
+  /** Time reference pair */
+  u64 milisecond_time_0;
+  f64 vlib_time_0;
+
+  /** template IDs */
+  u16 nat44_session_template_id;
+  u16 addr_exhausted_template_id;
+
+  /** stream index */
+  u32 stream_index;
+} snat_ipfix_logging_main_t;
+
+extern snat_ipfix_logging_main_t snat_ipfix_logging_main;
+
+void snat_ipfix_logging_init (vlib_main_t * vm);
+int snat_ipfix_logging_enable_disable (int enable, u32 domain_id, u16 src_port);
+void snat_ipfix_logging_nat44_ses_create (u32 src_ip, u32 nat_src_ip,
+                                          snat_protocol_t snat_proto,
+                                          u16 src_port, u16 nat_src_port,
+                                          u32 vrf_id);
+void snat_ipfix_logging_nat44_ses_delete (u32 src_ip, u32 nat_src_ip,
+                                          snat_protocol_t snat_proto,
+                                          u16 src_port, u16 nat_src_port,
+                                          u32 vrf_id);
+void snat_ipfix_logging_addresses_exhausted(u32 pool_id);
+#endif /* __included_snat_ipfix_logging_h__ */
index 6f87d80..013d7d9 100644 (file)
@@ -62,7 +62,8 @@ _(snat_add_address_range_reply)                 \
 _(snat_interface_add_del_feature_reply)         \
 _(snat_add_static_mapping_reply)                \
 _(snat_set_workers_reply)                       \
-_(snat_add_del_interface_addr_reply)
+_(snat_add_del_interface_addr_reply)            \
+_(snat_ipfix_enable_disable_reply)
 
 #define _(n)                                            \
     static void vl_api_##n##_t_handler                  \
@@ -98,7 +99,9 @@ _(SNAT_SET_WORKERS_REPLY, snat_set_workers_reply)               \
 _(SNAT_WORKER_DETAILS, snat_worker_details)                     \
 _(SNAT_ADD_DEL_INTERFACE_ADDR_REPLY,                            \
   snat_add_del_interface_addr_reply)                            \
-_(SNAT_INTERFACE_ADDR_DETAILS, snat_interface_addr_details)
+_(SNAT_INTERFACE_ADDR_DETAILS, snat_interface_addr_details)     \
+_(SNAT_IPFIX_ENABLE_DISABLE_REPLY,                              \
+  snat_ipfix_enable_disable_reply)
 
 /* M: construct, but don't yet send a message */
 #define M(T,t)                                                  \
@@ -543,7 +546,7 @@ static int api_snat_worker_dump(vat_main_t * vam)
   return 0;
 }
 
-static int api_snat_add_del_interface_addr (vat_main_t * vam)
+static int api_snat_ipfix_enable_disable (vat_main_t * vam)
 {
   snat_test_main_t * sm = &snat_test_main;
   unformat_input_t * i = vam->input;
@@ -617,6 +620,41 @@ static int api_snat_interface_addr_dump(vat_main_t * vam)
   return 0;
 }
 
+static int api_snat_add_del_interface_addr (vat_main_t * vam)
+{
+  snat_test_main_t * sm = &snat_test_main;
+  unformat_input_t * i = vam->input;
+  f64 timeout;
+  vl_api_snat_ipfix_enable_disable_t * mp;
+  u32 domain_id = 0;
+  u32 src_port = 0;
+  u8 enable = 1;
+
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "domain %d", &domain_id))
+        ;
+      else if (unformat (i, "src_port %d", &src_port))
+        ;
+      else if (unformat (i, "disable"))
+        enable = 0;
+      else
+        {
+          clib_warning("unknown input '%U'", format_unformat_error, i);
+          return -99;
+        }
+    }
+
+  M(SNAT_IPFIX_ENABLE_DISABLE, snat_ipfix_enable_disable);
+  mp->domain_id = htonl(domain_id);
+  mp->src_port = htons((u16) src_port);
+  mp->enable = enable;
+
+  S; W;
+  /* NOTREACHED */
+  return 0;
+}
+
 /* 
  * List of messages that the api test plugin sends,
  * and that the data plane plugin processes
@@ -635,7 +673,9 @@ _(snat_interface_dump, "")                                       \
 _(snat_worker_dump, "")                                          \
 _(snat_add_del_interface_addr,                                   \
   "<intfc> | sw_if_index <id> [del]")                            \
-_(snat_interface_addr_dump, "")
+_(snat_interface_addr_dump, "")                                  \
+_(snat_ipfix_enable_disable, "[domain <id>] [src_port <n>] "     \
+  "[disable]")
 
 void vat_api_hookup (vat_main_t *vam)
 {
diff --git a/test/ipfix.py b/test/ipfix.py
new file mode 100644 (file)
index 0000000..deaff67
--- /dev/null
@@ -0,0 +1,539 @@
+#!/usr/bin/env python
+# IPFIX support for Scapy (RFC7011)
+
+from scapy.all import *
+
+
+# IPFIX Information Elements http://www.iana.org/assignments/ipfix/ipfix.xhtml
+information_elements = {
+    1:   "octetDeltaCount",
+    2:   "packetDeltaCount",
+    3:   "deltaFlowCount",
+    4:   "protocolIdentifier",
+    5:   "ipClassOfService",
+    6:   "tcpControlBits",
+    7:   "sourceTransportPort",
+    8:   "sourceIPv4Address",
+    9:   "sourceIPv4PrefixLength",
+    10:  "ingressInterface",
+    11:  "destinationTransportPort",
+    12:  "destinationIPv4Address",
+    13:  "destinationIPv4PrefixLength",
+    14:  "egressInterface",
+    15:  "ipNextHopIPv4Address",
+    16:  "bgpSourceAsNumber",
+    17:  "bgpDestinationAsNumber",
+    18:  "bgpNextHopIPv4Address",
+    19:  "postMCastPacketDeltaCount",
+    20:  "postMCastOctetDeltaCount",
+    21:  "flowEndSysUpTime",
+    22:  "flowStartSysUpTime",
+    23:  "postOctetDeltaCount",
+    24:  "postPacketDeltaCount",
+    25:  "minimumIpTotalLength",
+    26:  "maximumIpTotalLength",
+    27:  "sourceIPv6Address",
+    28:  "destinationIPv6Address",
+    29:  "sourceIPv6PrefixLength",
+    30:  "destinationIPv6PrefixLength",
+    31:  "flowLabelIPv6",
+    32:  "icmpTypeCodeIPv4",
+    33:  "igmpType",
+    34:  "samplingInterval",
+    35:  "samplingAlgorithm",
+    36:  "flowActiveTimeout",
+    37:  "flowIdleTimeout",
+    38:  "engineType",
+    39:  "engineId",
+    40:  "exportedOctetTotalCount",
+    41:  "exportedMessageTotalCount",
+    42:  "exportedFlowRecordTotalCount",
+    43:  "ipv4RouterSc",
+    44:  "sourceIPv4Prefix",
+    45:  "destinationIPv4Prefix",
+    46:  "mplsTopLabelType",
+    47:  "mplsTopLabelIPv4Address",
+    48:  "samplerId",
+    49:  "samplerMode",
+    50:  "samplerRandomInterval",
+    51:  "classId",
+    52:  "minimumTTL",
+    53:  "maximumTTL",
+    54:  "fragmentIdentification",
+    55:  "postIpClassOfService",
+    56:  "sourceMacAddress",
+    57:  "postDestinationMacAddress",
+    58:  "vlanId",
+    59:  "postVlanId",
+    60:  "ipVersion",
+    61:  "flowDirection",
+    62:  "ipNextHopIPv6Address",
+    63:  "bgpNextHopIPv6Address",
+    64:  "ipv6ExtensionHeaders",
+    70:  "mplsTopLabelStackSection",
+    71:  "mplsLabelStackSection2",
+    72:  "mplsLabelStackSection3",
+    73:  "mplsLabelStackSection4",
+    74:  "mplsLabelStackSection5",
+    75:  "mplsLabelStackSection6",
+    76:  "mplsLabelStackSection7",
+    77:  "mplsLabelStackSection8",
+    78:  "mplsLabelStackSection9",
+    79:  "mplsLabelStackSection10",
+    80:  "destinationMacAddress",
+    81:  "postSourceMacAddress",
+    82:  "interfaceName",
+    83:  "interfaceDescription",
+    84:  "samplerName",
+    85:  "octetTotalCount",
+    86:  "packetTotalCount",
+    87:  "flagsAndSamplerId",
+    88:  "fragmentOffset",
+    89:  "forwardingStatus",
+    90:  "mplsVpnRouteDistinguisher",
+    91:  "mplsTopLabelPrefixLength",
+    92:  "srcTrafficIndex",
+    93:  "dstTrafficIndex",
+    94:  "applicationDescription",
+    95:  "applicationId",
+    96:  "applicationName",
+    98:  "postIpDiffServCodePoint",
+    99:  "multicastReplicationFactor",
+    100: "className",
+    101: "classificationEngineId",
+    102: "layer2packetSectionOffset",
+    103: "layer2packetSectionSize",
+    104: "layer2packetSectionData",
+    128: "bgpNextAdjacentAsNumber",
+    129: "bgpPrevAdjacentAsNumber",
+    130: "exporterIPv4Address",
+    131: "exporterIPv6Address",
+    132: "droppedOctetDeltaCount",
+    133: "droppedPacketDeltaCount",
+    134: "droppedOctetTotalCount",
+    135: "droppedPacketTotalCount",
+    136: "flowEndReason",
+    137: "commonPropertiesId",
+    138: "observationPointId",
+    139: "icmpTypeCodeIPv6",
+    140: "mplsTopLabelIPv6Address",
+    141: "lineCardId",
+    142: "portId",
+    143: "meteringProcessId",
+    144: "exportingProcessId",
+    145: "templateId",
+    146: "wlanChannelId",
+    147: "wlanSSID",
+    148: "flowId",
+    149: "observationDomainId",
+    150: "flowStartSeconds",
+    151: "flowEndSeconds",
+    152: "flowStartMilliseconds",
+    153: "flowEndMilliseconds",
+    154: "flowStartMicroseconds",
+    155: "flowEndMicroseconds",
+    156: "flowStartNanoseconds",
+    157: "flowEndNanoseconds",
+    158: "flowStartDeltaMicroseconds",
+    159: "flowEndDeltaMicroseconds",
+    160: "systemInitTimeMilliseconds",
+    161: "flowDurationMilliseconds",
+    162: "flowDurationMicroseconds",
+    163: "observedFlowTotalCount",
+    164: "ignoredPacketTotalCount",
+    165: "ignoredOctetTotalCount",
+    166: "notSentFlowTotalCount",
+    167: "notSentPacketTotalCount",
+    168: "notSentOctetTotalCount",
+    169: "destinationIPv6Prefix",
+    170: "sourceIPv6Prefix",
+    171: "postOctetTotalCount",
+    172: "postPacketTotalCount",
+    173: "flowKeyIndicator",
+    174: "postMCastPacketTotalCount",
+    175: "postMCastOctetTotalCount",
+    176: "icmpTypeIPv4",
+    177: "icmpCodeIPv4",
+    178: "icmpTypeIPv6",
+    179: "icmpCodeIPv6",
+    180: "udpSourcePort",
+    181: "udpDestinationPort",
+    182: "tcpSourcePort",
+    183: "tcpDestinationPort",
+    184: "tcpSequenceNumber",
+    185: "tcpAcknowledgementNumber",
+    186: "tcpWindowSize",
+    187: "tcpUrgentPointer",
+    188: "tcpHeaderLength",
+    189: "ipHeaderLength",
+    190: "totalLengthIPv4",
+    191: "payloadLengthIPv6",
+    192: "ipTTL",
+    193: "nextHeaderIPv6",
+    194: "mplsPayloadLength",
+    195: "ipDiffServCodePoint",
+    196: "ipPrecedence",
+    197: "fragmentFlags",
+    198: "octetDeltaSumOfSquares",
+    199: "octetTotalSumOfSquares",
+    200: "mplsTopLabelTTL",
+    201: "mplsLabelStackLength",
+    202: "mplsLabelStackDepth",
+    203: "mplsTopLabelExp",
+    204: "ipPayloadLength",
+    205: "udpMessageLength",
+    206: "isMulticast",
+    207: "ipv4IHL",
+    208: "ipv4Options",
+    209: "tcpOptions",
+    210: "paddingOctets",
+    211: "collectorIPv4Address",
+    212: "collectorIPv6Address",
+    213: "exportInterface",
+    214: "exportProtocolVersion",
+    215: "exportTransportProtocol",
+    216: "collectorTransportPort",
+    217: "exporterTransportPort",
+    218: "tcpSynTotalCount",
+    219: "tcpFinTotalCount",
+    220: "tcpRstTotalCount",
+    221: "tcpPshTotalCount",
+    222: "tcpAckTotalCount",
+    223: "tcpUrgTotalCount",
+    224: "ipTotalLength",
+    225: "postNATSourceIPv4Address",
+    226: "postNATDestinationIPv4Address",
+    227: "postNAPTSourceTransportPort",
+    228: "postNAPTDestinationTransportPort",
+    229: "natOriginatingAddressRealm",
+    230: "natEvent",
+    231: "initiatorOctets",
+    232: "responderOctets",
+    233: "firewallEvent",
+    234: "ingressVRFID",
+    235: "egressVRFID",
+    236: "VRFname",
+    237: "postMplsTopLabelExp",
+    238: "tcpWindowScale",
+    239: "biflowDirection",
+    240: "ethernetHeaderLength",
+    241: "ethernetPayloadLength",
+    242: "ethernetTotalLength",
+    243: "dot1qVlanId",
+    244: "dot1qPriority",
+    245: "dot1qCustomerVlanId",
+    246: "dot1qCustomerPriority",
+    247: "metroEvcId",
+    248: "metroEvcType",
+    249: "pseudoWireId",
+    250: "pseudoWireType",
+    251: "pseudoWireControlWord",
+    252: "ingressPhysicalInterface",
+    253: "egressPhysicalInterface",
+    254: "postDot1qVlanId",
+    255: "postDot1qCustomerVlanId",
+    256: "ethernetType",
+    257: "postIpPrecedence",
+    258: "collectionTimeMilliseconds",
+    259: "exportSctpStreamId",
+    260: "maxExportSeconds",
+    261: "maxFlowEndSeconds",
+    262: "messageMD5Checksum",
+    263: "messageScope",
+    264: "minExportSeconds",
+    265: "minFlowStartSeconds",
+    266: "opaqueOctets",
+    267: "sessionScope",
+    268: "maxFlowEndMicroseconds",
+    269: "maxFlowEndMilliseconds",
+    270: "maxFlowEndNanoseconds",
+    271: "minFlowStartMicroseconds",
+    272: "minFlowStartMilliseconds",
+    273: "minFlowStartNanoseconds",
+    274: "collectorCertificate",
+    275: "exporterCertificate",
+    276: "dataRecordsReliability",
+    277: "observationPointType",
+    278: "newConnectionDeltaCount",
+    279: "connectionSumDurationSeconds",
+    280: "connectionTransactionId",
+    281: "postNATSourceIPv6Address",
+    282: "postNATDestinationIPv6Address",
+    283: "natPoolId",
+    284: "natPoolName",
+    285: "anonymizationFlags",
+    286: "anonymizationTechnique",
+    287: "informationElementIndex",
+    288: "p2pTechnology",
+    289: "tunnelTechnology",
+    290: "encryptedTechnology",
+    291: "basicList",
+    292: "subTemplateList",
+    293: "subTemplateMultiList",
+    294: "bgpValidityState",
+    295: "IPSecSPI",
+    296: "greKey",
+    297: "natType",
+    298: "initiatorPackets",
+    299: "responderPackets",
+    300: "observationDomainName",
+    301: "selectionSequenceId",
+    302: "selectorId",
+    303: "informationElementId",
+    304: "selectorAlgorithm",
+    305: "samplingPacketInterval",
+    306: "samplingPacketSpace",
+    307: "samplingTimeInterval",
+    308: "samplingTimeSpace",
+    309: "samplingSize",
+    310: "samplingPopulation",
+    311: "samplingProbability",
+    312: "dataLinkFrameSize",
+    313: "ipHeaderPacketSection",
+    314: "ipPayloadPacketSection",
+    315: "dataLinkFrameSection",
+    316: "mplsLabelStackSection",
+    317: "mplsPayloadPacketSection",
+    318: "selectorIdTotalPktsObserved",
+    319: "selectorIdTotalPktsSelected",
+    320: "absoluteError",
+    321: "relativeError",
+    322: "observationTimeSeconds",
+    323: "observationTimeMilliseconds",
+    324: "observationTimeMicroseconds",
+    325: "observationTimeNanoseconds",
+    326: "digestHashValue",
+    327: "hashIPPayloadOffset",
+    328: "hashIPPayloadSize",
+    329: "hashOutputRangeMin",
+    330: "hashOutputRangeMax",
+    331: "hashSelectedRangeMin",
+    332: "hashSelectedRangeMax",
+    333: "hashDigestOutput",
+    334: "hashInitialiserValue",
+    335: "selectorName",
+    336: "upperCILimit",
+    337: "lowerCILimit",
+    338: "confidenceLevel",
+    339: "informationElementDataType",
+    340: "informationElementDescription",
+    341: "informationElementName",
+    342: "informationElementRangeBegin",
+    343: "informationElementRangeEnd",
+    344: "informationElementSemantics",
+    345: "informationElementUnits",
+    346: "privateEnterpriseNumber",
+    347: "virtualStationInterfaceId",
+    348: "virtualStationInterfaceName",
+    349: "virtualStationUUID",
+    350: "virtualStationName",
+    351: "layer2SegmentId",
+    352: "layer2OctetDeltaCount",
+    353: "layer2OctetTotalCount",
+    354: "ingressUnicastPacketTotalCount",
+    355: "ingressMulticastPacketTotalCount",
+    356: "ingressBroadcastPacketTotalCount",
+    357: "egressUnicastPacketTotalCount",
+    358: "egressBroadcastPacketTotalCount",
+    359: "monitoringIntervalStartMilliSeconds",
+    360: "monitoringIntervalEndMilliSeconds",
+    361: "portRangeStart",
+    362: "portRangeEnd",
+    363: "portRangeStepSize",
+    364: "portRangeNumPorts",
+    365: "staMacAddress",
+    366: "staIPv4Address",
+    367: "wtpMacAddress",
+    368: "ingressInterfaceType",
+    369: "egressInterfaceType",
+    370: "rtpSequenceNumber",
+    371: "userName",
+    372: "applicationCategoryName",
+    373: "applicationSubCategoryName",
+    374: "applicationGroupName",
+    375: "originalFlowsPresent",
+    376: "originalFlowsInitiated",
+    377: "originalFlowsCompleted",
+    378: "distinctCountOfSourceIPAddress",
+    379: "distinctCountOfDestinationIPAddress",
+    380: "distinctCountOfSourceIPv4Address",
+    381: "distinctCountOfDestinationIPv4Address",
+    382: "distinctCountOfSourceIPv6Address",
+    383: "distinctCountOfDestinationIPv6Address",
+    384: "valueDistributionMethod",
+    385: "rfc3550JitterMilliseconds",
+    386: "rfc3550JitterMicroseconds",
+    387: "rfc3550JitterNanoseconds",
+    388: "dot1qDEI",
+    389: "dot1qCustomerDEI",
+    390: "flowSelectorAlgorithm",
+    391: "flowSelectedOctetDeltaCount",
+    392: "flowSelectedPacketDeltaCount",
+    393: "flowSelectedFlowDeltaCount",
+    394: "selectorIDTotalFlowsObserved",
+    395: "selectorIDTotalFlowsSelected",
+    396: "samplingFlowInterval",
+    397: "samplingFlowSpacing",
+    398: "flowSamplingTimeInterval",
+    399: "flowSamplingTimeSpacing",
+    400: "hashFlowDomain",
+    401: "transportOctetDeltaCount",
+    402: "transportPacketDeltaCount",
+    403: "originalExporterIPv4Address",
+    404: "originalExporterIPv6Address",
+    405: "originalObservationDomainId",
+    406: "intermediateProcessId",
+    407: "ignoredDataRecordTotalCount",
+    408: "dataLinkFrameType",
+    409: "sectionOffset",
+    410: "sectionExportedOctets",
+    411: "dot1qServiceInstanceTag",
+    412: "dot1qServiceInstanceId",
+    413: "dot1qServiceInstancePriority",
+    414: "dot1qCustomerSourceMacAddress",
+    415: "dot1qCustomerDestinationMacAddress",
+    417: "postLayer2OctetDeltaCount",
+    418: "postMCastLayer2OctetDeltaCount",
+    420: "postLayer2OctetTotalCount",
+    421: "postMCastLayer2OctetTotalCount",
+    422: "minimumLayer2TotalLength",
+    423: "maximumLayer2TotalLength",
+    424: "droppedLayer2OctetDeltaCount",
+    425: "droppedLayer2OctetTotalCount",
+    426: "ignoredLayer2OctetTotalCount",
+    427: "notSentLayer2OctetTotalCount",
+    428: "layer2OctetDeltaSumOfSquares",
+    429: "layer2OctetTotalSumOfSquares",
+    430: "layer2FrameDeltaCount",
+    431: "layer2FrameTotalCount",
+    432: "pseudoWireDestinationIPv4Address",
+    433: "ignoredLayer2FrameTotalCount",
+    434: "mibObjectValueInteger",
+    435: "mibObjectValueOctetString",
+    436: "mibObjectValueOID",
+    437: "mibObjectValueBits",
+    438: "mibObjectValueIPAddress",
+    439: "mibObjectValueCounter",
+    440: "mibObjectValueGauge",
+    441: "mibObjectValueTimeTicks",
+    442: "mibObjectValueUnsigned",
+    443: "mibObjectValueTable",
+    444: "mibObjectValueRow",
+    445: "mibObjectIdentifier",
+    446: "mibSubIdentifier",
+    447: "mibIndexIndicator",
+    448: "mibCaptureTimeSemantics",
+    449: "mibContextEngineID",
+    450: "mibContextName",
+    451: "mibObjectName",
+    452: "mibObjectDescription",
+    453: "mibObjectSyntax",
+    454: "mibModuleName",
+    455: "mobileIMSI",
+    456: "mobileMSISDN",
+    457: "httpStatusCode",
+    458: "sourceTransportPortsLimit",
+    459: "httpRequestMethod",
+    460: "httpRequestHost",
+    461: "httpRequestTarget",
+    462: "httpMessageVersion"
+}
+
+
+class IPFIX(Packet):
+    name = "IPFIX"
+    fields_desc = [ShortField("version", 10),
+                   ShortField("length", None),
+                   IntField("exportTime", None),
+                   IntField("sequenceNumber", 1),
+                   IntField("observationDomainID", 1)]
+
+
+class FieldSpecifier(Packet):
+    name = "Field Specifier"
+    fields_desc = [ShortEnumField(
+        "informationElement", None, information_elements),
+        ShortField("fieldLength", None)]
+
+    def extract_padding(self, s):
+        return "", s
+
+
+class Template(Packet):
+    name = "Template"
+    fields_desc = [ShortField("templateID", 256),
+                   FieldLenField("fieldCount", None, count_of="fields"),
+                   PacketListField("templateFields", [], FieldSpecifier,
+                                   count_from=lambda p: p.fieldCount)]
+
+
+class Data(Packet):
+    name = "Data"
+    fields_desc = [
+        StrLenField("data", "", length_from=lambda p: p.underlayer.length - 4)]
+
+    def extract_padding(self, s):
+        return "", s
+
+
+class Set(Packet):
+    name = "Set"
+    fields_desc = [ShortField("setID", 256),
+                   ShortField("length", None)]
+
+    def guess_payload_class(self, payload):
+        if self.setID == 2:
+            return Template
+        elif self.setID > 255:
+            return Data
+        else:
+            return Packet.guess_payload_class(self, payload)
+
+
+bind_layers(IPFIX, Set)
+bind_layers(UDP, IPFIX, dport=4739)
+
+
+class IPFIXDecoder(object):
+    """ IPFIX data set decoder """
+
+    def __init__(self):
+        self._templates = []
+
+    def add_template(self, template):
+        """
+        Add IPFIX tempalte
+
+        :param template: IPFIX template
+        """
+        templateID = template.templateID
+        fields = []
+        rec_len = 0
+        for field in template.templateFields:
+            fields.append(
+                {'name': field.informationElement, 'len': field.fieldLength})
+            rec_len += field.fieldLength
+        self._templates.append(
+            {'id': templateID, 'fields': fields, 'rec_len': rec_len})
+
+    def decode_data_set(self, data_set):
+        """
+        Decode IPFIX data
+
+        :param data_set: IPFIX data set
+        :returns: List of decoded data records.
+        """
+        data = []
+        for template in self._templates:
+            if template['id'] == data_set.setID:
+                offset = 0
+                d = data_set[Data].data
+                for i in range(len(d) / template['rec_len']):
+                    record = {}
+                    for field in template['fields']:
+                        f = d[offset:offset + field['len']]
+                        offset += field['len']
+                        record.update({field['name']: f})
+                    data.append(record)
+                break
+        return data
index 653496e..1abb3da 100644 (file)
@@ -2,12 +2,14 @@
 
 import socket
 import unittest
+import struct
 
 from framework import VppTestCase, VppTestRunner
-
 from scapy.layers.inet import IP, TCP, UDP, ICMP
 from scapy.layers.l2 import Ether
+from scapy.data import IP_PROTOS
 from util import ppp
+from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder
 
 
 class TestSNAT(VppTestCase):
@@ -176,6 +178,62 @@ class TestSNAT(VppTestCase):
                                       "(inside network):", packet))
                 raise
 
+    def verify_ipfix_nat44_ses(self, data):
+        """
+        Verify IPFIX NAT44 session create/delete event
+
+        :param data: Decoded IPFIX data records
+        """
+        nat44_ses_create_num = 0
+        nat44_ses_delete_num = 0
+        self.assertEqual(6, len(data))
+        for record in data:
+            # natEvent
+            self.assertIn(ord(record[230]), [4, 5])
+            if ord(record[230]) == 4:
+                nat44_ses_create_num += 1
+            else:
+                nat44_ses_delete_num += 1
+            # sourceIPv4Address
+            self.assertEqual(self.pg0.remote_ip4n, record[8])
+            # postNATSourceIPv4Address
+            self.assertEqual(socket.inet_pton(socket.AF_INET, self.snat_addr),
+                             record[225])
+            # ingressVRFID
+            self.assertEqual(struct.pack("!I", 0), record[234])
+            # protocolIdentifier/sourceTransportPort/postNAPTSourceTransportPort
+            if IP_PROTOS.icmp == ord(record[4]):
+                self.assertEqual(struct.pack("!H", self.icmp_id_in), record[7])
+                self.assertEqual(struct.pack("!H", self.icmp_id_out),
+                                 record[227])
+            elif IP_PROTOS.tcp == ord(record[4]):
+                self.assertEqual(struct.pack("!H", self.tcp_port_in),
+                                 record[7])
+                self.assertEqual(struct.pack("!H", self.tcp_port_out),
+                                 record[227])
+            elif IP_PROTOS.udp == ord(record[4]):
+                self.assertEqual(struct.pack("!H", self.udp_port_in),
+                                 record[7])
+                self.assertEqual(struct.pack("!H", self.udp_port_out),
+                                 record[227])
+            else:
+                self.fail("Invalid protocol")
+        self.assertEqual(3, nat44_ses_create_num)
+        self.assertEqual(3, nat44_ses_delete_num)
+
+    def verify_ipfix_addr_exhausted(self, data):
+        """
+        Verify IPFIX NAT addresses event
+
+        :param data: Decoded IPFIX data records
+        """
+        self.assertEqual(1, len(data))
+        record = data[0]
+        # natEvent
+        self.assertEqual(ord(record[230]), 3)
+        # natPoolID
+        self.assertEqual(struct.pack("!I", 0), record[283])
+
     def clear_snat(self):
         """
         Clear SNAT configuration.
@@ -184,6 +242,8 @@ class TestSNAT(VppTestCase):
         for intf in interfaces:
             self.vapi.snat_add_interface_addr(intf.sw_if_index, is_add=0)
 
+        self.vapi.snat_ipfix(enable=0)
+
         interfaces = self.vapi.snat_interface_dump()
         for intf in interfaces:
             self.vapi.snat_interface_add_del_feature(intf.sw_if_index,
@@ -647,11 +707,77 @@ class TestSNAT(VppTestCase):
         adresses = self.vapi.snat_address_dump()
         self.assertEqual(0, len(adresses))
 
+    def test_ipfix_nat44_sess(self):
+        """ S-NAT IPFIX logging NAT44 session created/delted """
+        self.snat_add_address(self.snat_addr)
+        self.vapi.snat_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.snat_interface_add_del_feature(self.pg1.sw_if_index,
+                                                 is_inside=0)
+        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+                                     src_address=self.pg3.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.snat_ipfix()
+
+        pkts = self.create_stream_in(self.pg0, self.pg1)
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(len(pkts))
+        self.verify_capture_out(capture)
+        self.snat_add_address(self.snat_addr, is_add=0)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(3)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_nat44_ses(data)
+
+    def test_ipfix_addr_exhausted(self):
+        """ S-NAT IPFIX logging NAT addresses exhausted """
+        self.vapi.snat_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.snat_interface_add_del_feature(self.pg1.sw_if_index,
+                                                 is_inside=0)
+        self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+                                     src_address=self.pg3.local_ip4n,
+                                     path_mtu=512,
+                                     template_interval=10)
+        self.vapi.snat_ipfix()
+
+        p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+             IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+             TCP(sport=3025))
+        self.pg0.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(0)
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
+        capture = self.pg3.get_capture(3)
+        ipfix = IPFIXDecoder()
+        # first load template
+        for p in capture:
+            self.assertTrue(p.haslayer(IPFIX))
+            if p.haslayer(Template):
+                ipfix.add_template(p.getlayer(Template))
+        # verify events in data set
+        for p in capture:
+            if p.haslayer(Data):
+                data = ipfix.decode_data_set(p.getlayer(Set))
+                self.verify_ipfix_addr_exhausted(data)
+
     def tearDown(self):
         super(TestSNAT, self).tearDown()
         if not self.vpp_dead:
             self.logger.info(self.vapi.cli("show snat verbose"))
             self.clear_snat()
 
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
index 73d3b56..96f3ddc 100644 (file)
@@ -934,6 +934,23 @@ class VppPapiProvider(object):
         """
         return self.api(self.papi.snat_interface_addr_dump, {})
 
+    def snat_ipfix(
+            self,
+            domain_id=1,
+            src_port=4739,
+            enable=1):
+        """Enable/disable S-NAT IPFIX logging
+
+        :param domain_id: Observation domain ID (Default value = 1)
+        :param src_port: Source port number (Default value = 4739)
+        :param enable: 1 if enable, 0 if disable (Default value = 1)
+        """
+        return self.api(
+            self.papi.snat_ipfix_enable_disable,
+            {'domain_id': domain_id,
+             'src_port': src_port,
+             'enable': enable})
+
     def control_ping(self):
         self.api(self.papi.control_ping)