summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatus Fabian <matfabia@cisco.com>2017-01-13 04:15:54 -0800
committerDamjan Marion <dmarion.lists@gmail.com>2017-01-14 09:44:18 +0000
commiteea28d78a3173341727aafee4c414bcb01001339 (patch)
treefe657b651619b62b7b33cbfabe999e770728f650
parent2d0b6e380be3acec05f4acea9e4f1b9bb730ee88 (diff)
SNAT: IPFIX logging (VPP-445)
Change-Id: I8450217dd43a1cd9f510e40dfb22274ffc33a4c6 Signed-off-by: Matus Fabian <matfabia@cisco.com>
-rw-r--r--src/plugins/snat.am3
-rw-r--r--src/plugins/snat/in2out.c17
-rw-r--r--src/plugins/snat/out2in.c10
-rw-r--r--src/plugins/snat/snat.api24
-rw-r--r--src/plugins/snat/snat.c112
-rw-r--r--src/plugins/snat/snat_ipfix_logging.c653
-rw-r--r--src/plugins/snat/snat_ipfix_logging.h68
-rw-r--r--src/plugins/snat/snat_test.c48
-rw-r--r--test/ipfix.py539
-rw-r--r--test/test_snat.py128
-rw-r--r--test/vpp_papi_provider.py17
11 files changed, 1608 insertions, 11 deletions
diff --git a/src/plugins/snat.am b/src/plugins/snat.am
index 7ff2386ec83..8611d15c740 100644
--- a/src/plugins/snat.am
+++ b/src/plugins/snat.am
@@ -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
diff --git a/src/plugins/snat/in2out.c b/src/plugins/snat/in2out.c
index cd8f1271808..76a6a12cec5 100644
--- a/src/plugins/snat/in2out.c
+++ b/src/plugins/snat/in2out.c
@@ -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;
}
diff --git a/src/plugins/snat/out2in.c b/src/plugins/snat/out2in.c
index 0c9c9cd12b6..f1329733ecf 100644
--- a/src/plugins/snat/out2in.c
+++ b/src/plugins/snat/out2in.c
@@ -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,
diff --git a/src/plugins/snat/snat.api b/src/plugins/snat/snat.api
index f046a9657e2..ff1d9bc1b8a 100644
--- a/src/plugins/snat/snat.api
+++ b/src/plugins/snat/snat.api
@@ -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;
+};
diff --git a/src/plugins/snat/snat.c b/src/plugins/snat/snat.c
index a1236cf74f4..9edb0d5699b 100644
--- a/src/plugins/snat/snat.c
+++ b/src/plugins/snat/snat.c
@@ -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;
}
@@ -1720,6 +1772,58 @@ VLIB_CLI_COMMAND (set_workers_command, static) = {
};
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)
{
snat_main_t * sm = &snat_main;
@@ -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
index 00000000000..d72eb226675
--- /dev/null
+++ b/src/plugins/snat/snat_ipfix_logging.c
@@ -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
index 00000000000..b968ee21e9c
--- /dev/null
+++ b/src/plugins/snat/snat_ipfix_logging.h
@@ -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__ */
diff --git a/src/plugins/snat/snat_test.c b/src/plugins/snat/snat_test.c
index 6f87d80379b..013d7d9b700 100644
--- a/src/plugins/snat/snat_test.c
+++ b/src/plugins/snat/snat_test.c
@@ -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
index 00000000000..deaff67ba42
--- /dev/null
+++ b/test/ipfix.py
@@ -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
diff --git a/test/test_snat.py b/test/test_snat.py
index 653496e2880..1abb3daaa40 100644
--- a/test/test_snat.py
+++ b/test/test_snat.py
@@ -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)
diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py
index 73d3b564001..96f3ddc5dae 100644
--- a/test/vpp_papi_provider.py
+++ b/test/vpp_papi_provider.py
@@ -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)