Add RFC5424 syslog protocol support (VPP-1139) 21/16021/2
authorMatus Fabian <matfabia@cisco.com>
Mon, 19 Nov 2018 12:25:32 +0000 (04:25 -0800)
committerOle Trøan <otroan@employees.org>
Thu, 22 Nov 2018 06:31:53 +0000 (06:31 +0000)
Syslog protocol logging transport event messages across network over UDP
protocol based on RFC5426.

Change-Id: Ica74b40bcc2e6d0fbd41e9bf78e76395fbabab3c
Signed-off-by: Matus Fabian <matfabia@cisco.com>
14 files changed:
doxygen/dev_doc.md
src/vnet/CMakeLists.txt
src/vnet/syslog/dir.dox [new file with mode: 0644]
src/vnet/syslog/sylog_doc.md [new file with mode: 0644]
src/vnet/syslog/syslog.api [new file with mode: 0644]
src/vnet/syslog/syslog.c [new file with mode: 0644]
src/vnet/syslog/syslog.h [new file with mode: 0644]
src/vnet/syslog/syslog_api.c [new file with mode: 0644]
src/vnet/syslog/syslog_udp.c [new file with mode: 0644]
src/vnet/syslog/syslog_udp.h [new file with mode: 0644]
src/vnet/vnet_all_api_h.h
test/Makefile
test/test_syslog.py [new file with mode: 0644]
test/vpp_papi_provider.py

index 690a8d8..83c11ed 100644 (file)
@@ -11,3 +11,4 @@ Programming notes for developers.
 - @subpage acl_multicore
 - @subpage acl_lookup_context
 - @subpage libmemif_doc
+- @subpage syslog_doc
index ac448e2..290a4ee 100644 (file)
@@ -1354,6 +1354,23 @@ list(APPEND VNET_HEADERS
 
 list(APPEND VNET_API_FILES bier/bier.api)
 
+##############################################################################
+# SYSLOG
+##############################################################################
+
+list (APPEND VNET_SOURCES
+  syslog/syslog_api.c
+  syslog/syslog_udp.c
+  syslog/syslog.c
+)
+
+list(APPEND VNET_HEADERS
+  syslog/syslog_udp.h
+  syslog/syslog.h
+)
+
+list(APPEND VNET_API_FILES syslog/syslog.api)
+
 ##############################################################################
 # VNET Library
 ##############################################################################
diff --git a/src/vnet/syslog/dir.dox b/src/vnet/syslog/dir.dox
new file mode 100644 (file)
index 0000000..ea64053
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+/**
+ @dir
+ @brief RFC5424 syslog protocol implementation
+*/
diff --git a/src/vnet/syslog/sylog_doc.md b/src/vnet/syslog/sylog_doc.md
new file mode 100644 (file)
index 0000000..0b48d4d
--- /dev/null
@@ -0,0 +1,65 @@
+# Syslog protocol support {#syslog_doc}
+
+VPP provides [RFC5424](https://tools.ietf.org/html/rfc5424) syslog protocol
+logging, which is used to transport event messages across network. VPP
+currently suports UDP transport based on
+[RFC5426](https://tools.ietf.org/html/rfc5426).
+
+The syslog message has the following format:
+* header
+* structured data
+* free-form message
+
+The header contains, priority, version, timestamp, hostname, application,
+process id and message id. It is followed by structured data, which  provides
+a mechanism to express event data in easily parsable format. Structured data
+can contain zero, one or multiple structured data elements. Structured data
+element contains name-value pairs. Structured data can by followed by free-form
+message.
+
+Following example explains how to use the internal APIs to genrate syslog
+message:
+```{.c}
+   #include <vnet/syslog/syslog.h>
+
+   ...
+
+   syslog_msg_t syslog_msg;
+
+   /* Check if syslog logging is enabled */
+   if (!syslog_is_enabled ())
+     return;
+
+   /* Severity filer test */
+   if (syslog_severity_filter_block (severity))
+     return;
+
+   /* Initialize syslog message header */
+   syslog_msg_init (&syslog_msg, facility, severity, "NAT", "SADD");
+
+   /* Create structured data element */
+   syslog_msg_sd_init (&syslog_msg, "nsess");
+   /* Add structured data element parameters (name-value pairs) */
+   syslog_msg_add_sd_param (&syslog_msg, "SSUBIX", "%d", ssubix);
+   syslog_msg_add_sd_param (&syslog_msg, "SVLAN", "%d", svlan);
+   syslog_msg_add_sd_param (&syslog_msg, "IATYP", "IPv4");
+   syslog_msg_add_sd_param (&syslog_msg, "ISADDR", "%U",
+                            format_ip4_address, isaddr);
+   syslog_msg_add_sd_param (&syslog_msg, "ISPORT", "%d", isport);
+   syslog_msg_add_sd_param (&syslog_msg, "XATYP", "IPv4");
+   syslog_msg_add_sd_param (&syslog_msg, "XSADDR", "%U",
+                            format_ip4_address, xsaddr);
+   syslog_msg_add_sd_param (&syslog_msg, "XSPORT", "%d", xsport);
+   syslog_msg_add_sd_param (&syslog_msg, "PROTO", "%d", proto);
+
+   /* Send syslog message */
+   syslog_msg_send (&syslog_msg);
+```
+
+Example above produces following syslog message:
+   <134>1 2018-11-12T11:25:30.252715Z 172.16.4.1 NAT 5901 SADD [nsess SSUBIX="0" SVLAN="0" IATYP="IPv4" ISADDR="172.16.1.2" ISPORT="6303" XATYP="IPv4" XSADDR="10.0.0.3" XSPORT="16253" PROTO="6"]
+
+To add free-form message use:
+```{.c}
+   syslog_msg_add_msg (&syslog_msg, "event log entry");
+```
diff --git a/src/vnet/syslog/syslog.api b/src/vnet/syslog/syslog.api
new file mode 100644 (file)
index 0000000..3ba5d69
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+
+option version = "1.0.0";
+import "vnet/ip/ip_types.api";
+
+enum syslog_severity
+{
+  SYSLOG_API_SEVERITY_EMERG = 0,
+  SYSLOG_API_SEVERITY_ALERT = 1,
+  SYSLOG_API_SEVERITY_CRIT = 2,
+  SYSLOG_API_SEVERITY_ERR = 3,
+  SYSLOG_API_SEVERITY_WARN = 4,
+  SYSLOG_API_SEVERITY_NOTICE = 5,
+  SYSLOG_API_SEVERITY_INFO = 6,
+  SYSLOG_API_SEVERITY_DBG = 7,
+};
+
+/** \brief Set syslog sender configuration
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param src_address - IPv4 address of syslog sender (source)
+    @param collector_address - IPv4 address of syslog collector (destination)
+    @param collector_port - UDP port of syslog colector (destination)
+    @param vrf_id - VRF/FIB table ID
+    @param max_msg_size - maximum message length
+*/
+autoreply define syslog_set_sender
+{
+  u32 client_index;
+  u32 context;
+  vl_api_ip4_address_t src_address;
+  vl_api_ip4_address_t collector_address;
+  u16 collector_port;
+  u32 vrf_id;
+  u32 max_msg_size;
+};
+
+/** \brief Get syslog sender configuration
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+define syslog_get_sender
+{
+  u32 client_index;
+  u32 context;
+};
+
+/** \brief Get syslog sender configuration reply
+    @param context - sender context, to match reply w/ request
+    @param retval - return code for the request
+    @param src_address - IPv4 address of syslog sender (source)
+    @param collector_address - IPv4 address of syslog collector (destination)
+    @param collector_port - UDP port of syslog colector (destination)
+    @param vrf_id - VRF/FIB table ID
+    @param max_msg_size - maximum message length
+*/
+define syslog_get_sender_reply
+{
+  u32 context;
+  i32 retval;
+  vl_api_ip4_address_t src_address;
+  vl_api_ip4_address_t collector_address;
+  u16 collector_port;
+  u32 vrf_id;
+  u32 max_msg_size;
+};
+
+/** \brief Set syslog filter
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param severity - severity filter (specified severity and greater match)
+*/
+autoreply define syslog_set_filter
+{
+  u32 client_index;
+  u32 context;
+  vl_api_syslog_severity_t severity;
+ };
+
+/** \brief Get syslog filter
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+define syslog_get_filter
+{
+  u32 client_index;
+  u32 context;
+};
+
+/** \brief Get syslog filter reply
+    @param context - sender context, to match reply w/ request
+    @param retval - return code for the request
+    @param severity - severity filter (specified severity and greater match)
+*/
+define syslog_get_filter_reply
+{
+  u32 context;
+  i32 retval;
+  vl_api_syslog_severity_t severity;
+};
+
+/*
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/syslog/syslog.c b/src/vnet/syslog/syslog.c
new file mode 100644 (file)
index 0000000..1cffe03
--- /dev/null
@@ -0,0 +1,646 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+/**
+ * @file RFC5424 syslog protocol implementation
+ */
+
+#include <unistd.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/ip/format.h>
+#include <vnet/syslog/syslog.h>
+#include <vnet/syslog/syslog_udp.h>
+
+#define SYSLOG_VERSION "1"
+#define NILVALUE "-"
+#define DEFAULT_UDP_PORT 514
+#define DEFAULT_MAX_MSG_SIZE 480
+
+#define encode_priority(f, p) ((f << 3) | p)
+
+syslog_main_t syslog_main;
+
+/* format timestamp RFC5424 6.2.3. */
+static u8 *
+format_syslog_timestamp (u8 * s, va_list * args)
+{
+  f64 timestamp = va_arg (*args, f64);
+  struct tm *tm;
+  word msec;
+
+  time_t t = timestamp;
+  tm = gmtime (&t);
+  msec = 1e6 * (timestamp - t);
+  return format (s, "%4d-%02d-%02dT%02d:%02d:%02d.%06dZ", 1900 + tm->tm_year,
+                1 + tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min,
+                tm->tm_sec, msec);
+}
+
+/* format header RFC5424 6.2. */
+static u8 *
+format_syslog_header (u8 * s, va_list * args)
+{
+  syslog_main_t *sm = &syslog_main;
+  syslog_header_t *h = va_arg (*args, syslog_header_t *);
+  u32 pri = encode_priority (h->facility, h->severity);
+
+  return format (s, "<%d>%s %U %U %s %d %s", pri, SYSLOG_VERSION,
+                format_syslog_timestamp, h->timestamp + sm->time_offset,
+                format_ip4_address, &sm->src_address,
+                h->app_name ? h->app_name : NILVALUE, sm->procid,
+                h->msgid ? h->msgid : NILVALUE);
+}
+
+/* format strucured data elements RFC5424 6.3. */
+static u8 *
+format_syslog_structured_data (u8 * s, va_list * args)
+{
+  u8 **sds = va_arg (*args, u8 **);
+  int i;
+
+  if (vec_len (sds))
+    {
+      for (i = 0; i < vec_len (sds); i++)
+       s = format (s, "[%s]", sds[i]);
+    }
+  /* if zero structured data elemts field must contain NILVALUE */
+  else
+    s = format (s, "%s", NILVALUE);
+
+  return s;
+}
+
+static u8 *
+format_syslog_msg (u8 * s, va_list * args)
+{
+  syslog_msg_t *m = va_arg (*args, syslog_msg_t *);
+
+  s =
+    format (s, "%U %U", format_syslog_header, &m->header,
+           format_syslog_structured_data, m->structured_data);
+  /* free-form message is optional */
+  if (m->msg)
+    s = format (s, " %s", m->msg);
+
+  return s;
+}
+
+void
+syslog_msg_sd_init (syslog_msg_t * syslog_msg, char *sd_id)
+{
+  u8 *sd;
+
+  sd = format (0, "%s", sd_id);
+  vec_add1 (syslog_msg->structured_data, sd);
+  syslog_msg->curr_sd_index++;
+}
+
+void
+syslog_msg_add_sd_param (syslog_msg_t * syslog_msg, char *name, char *fmt,
+                        ...)
+{
+  va_list va;
+  u8 *value;
+
+  va_start (va, fmt);
+  value = va_format (0, fmt, &va);
+  va_end (va);
+  vec_terminate_c_string (value);
+
+  syslog_msg->structured_data[syslog_msg->curr_sd_index] =
+    format (syslog_msg->structured_data[syslog_msg->curr_sd_index],
+           " %s=\"%s\"", name, value);
+  vec_free (value);
+}
+
+void
+syslog_msg_add_msg (syslog_msg_t * syslog_msg, char *fmt, ...)
+{
+  va_list va;
+  u8 *msg;
+
+  va_start (va, fmt);
+  msg = va_format (0, fmt, &va);
+  va_end (va);
+  vec_terminate_c_string (msg);
+
+  syslog_msg->msg = msg;
+}
+
+void
+syslog_msg_init (syslog_msg_t * syslog_msg, syslog_facility_t facility,
+                syslog_severity_t severity, char *app_name, char *msgid)
+{
+  syslog_main_t *sm = &syslog_main;
+  vlib_main_t *vm = sm->vlib_main;
+
+  syslog_msg->header.facility = facility;
+  syslog_msg->header.severity = severity;
+  syslog_msg->header.timestamp = vlib_time_now (vm);
+  syslog_msg->header.app_name = app_name;
+  syslog_msg->header.msgid = msgid;
+  syslog_msg->structured_data = 0;
+  syslog_msg->curr_sd_index = ~0;
+  syslog_msg->msg = 0;
+}
+
+int
+syslog_msg_send (syslog_msg_t * syslog_msg)
+{
+  syslog_main_t *sm = &syslog_main;
+  vlib_main_t *vm = sm->vlib_main;
+  u32 bi, msg_len, *to_next;
+  u8 *tmp;
+  vlib_buffer_t *b;
+  vlib_buffer_free_list_t *fl;
+  vlib_frame_t *f;
+  int i;
+
+  if (vlib_buffer_alloc (vm, &bi, 1) != 1)
+    return -1;
+
+  b = vlib_get_buffer (vm, bi);
+  clib_memset (vnet_buffer (b), 0, sizeof (*vnet_buffer (b)));
+  fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
+  vlib_buffer_init_for_free_list (b, fl);
+  VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b);
+
+  /* one message per UDP datagram RFC5426 3.1. */
+  tmp = format (0, "%U", format_syslog_msg, syslog_msg);
+  msg_len = vec_len (tmp) - (vec_c_string_is_terminated (tmp) ? 1 : 0);
+  msg_len = msg_len < sm->max_msg_size ? msg_len : sm->max_msg_size;
+  clib_memcpy_fast (b->data, tmp, msg_len);
+  b->current_length = msg_len;
+  vec_free (tmp);
+
+  vec_free (syslog_msg->msg);
+  for (i = 0; i < vec_len (syslog_msg->structured_data); i++)
+    vec_free (syslog_msg->structured_data[i]);
+  vec_free (syslog_msg->structured_data);
+
+  syslog_add_udp_transport (vm, bi);
+
+  f = vlib_get_frame_to_node (vm, sm->ip4_lookup_node_index);
+  to_next = vlib_frame_vector_args (f);
+  to_next[0] = bi;
+  f->n_vectors = 1;
+  vlib_put_frame_to_node (vm, sm->ip4_lookup_node_index, f);
+
+  return 0;
+}
+
+static uword
+unformat_syslog_facility (unformat_input_t * input, va_list * args)
+{
+  u32 *r = va_arg (*args, u32 *);
+
+  if (0);
+#define _(v,f,s) else if (unformat (input, s)) *r = SYSLOG_FACILITY_##f;
+  foreach_syslog_facility
+#undef _
+    else
+    return 0;
+
+  return 1;
+}
+
+static uword
+unformat_syslog_severity (unformat_input_t * input, va_list * args)
+{
+  u32 *r = va_arg (*args, u32 *);
+
+  if (0);
+#define _(v,f,s) else if (unformat (input, s)) *r = SYSLOG_SEVERITY_##f;
+  foreach_syslog_severity
+#undef _
+    else
+    return 0;
+
+  return 1;
+}
+
+static u8 *
+format_syslog_severity (u8 * s, va_list * args)
+{
+  u32 i = va_arg (*args, u32);
+  u8 *t = 0;
+
+  switch (i)
+    {
+#define _(v,f,str) case SYSLOG_SEVERITY_##f: t = (u8 *) str; break;
+      foreach_syslog_severity
+#undef _
+    default:
+      return format (s, "unknown");
+    }
+
+  return format (s, "%s", t);
+}
+
+vnet_api_error_t
+set_syslog_sender (ip4_address_t * collector, u16 collector_port,
+                  ip4_address_t * src, u32 vrf_id, u32 max_msg_size)
+{
+  syslog_main_t *sm = &syslog_main;
+  u32 fib_index;
+
+  if (max_msg_size < DEFAULT_MAX_MSG_SIZE)
+    return VNET_API_ERROR_INVALID_VALUE;
+
+  if (collector->as_u32 == 0 || collector_port == 0 || src->as_u32 == 0)
+    return VNET_API_ERROR_INVALID_VALUE;
+
+  if (vrf_id == ~0)
+    {
+      fib_index = ~0;
+    }
+  else
+    {
+      fib_index = fib_table_find (FIB_PROTOCOL_IP4, vrf_id);
+      if (fib_index == ~0)
+       return VNET_API_ERROR_NO_SUCH_FIB;
+    }
+
+  sm->fib_index = fib_index;
+
+  sm->collector.as_u32 = collector->as_u32;
+  sm->collector_port = (u16) collector_port;
+  sm->src_address.as_u32 = src->as_u32;
+  sm->max_msg_size = max_msg_size;
+
+  return 0;
+}
+
+static clib_error_t *
+set_syslog_sender_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                             vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  ip4_address_t collector, src;
+  u32 collector_port = DEFAULT_UDP_PORT;
+  u32 vrf_id = ~0;
+  u32 max_msg_size = DEFAULT_MAX_MSG_SIZE;
+  clib_error_t *ret = 0;
+
+  collector.as_u32 = 0;
+  src.as_u32 = 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, "collector %U", unformat_ip4_address, &collector))
+       ;
+      else if (unformat (line_input, "port %u", &collector_port))
+       ;
+      else if (unformat (line_input, "src %U", unformat_ip4_address, &src))
+       ;
+      else if (unformat (line_input, "vrf-id %u", &vrf_id))
+       ;
+      else if (unformat (line_input, "max-msg-size %u", &max_msg_size))
+       ;
+      else
+       {
+         ret = clib_error_return (0, "Unknown input `%U'",
+                                  format_unformat_error, line_input);
+         goto done;
+       }
+    }
+
+  if (collector.as_u32 == 0)
+    {
+      ret = clib_error_return (0, "collector address required");
+      goto done;
+    }
+
+  if (src.as_u32 == 0)
+    {
+      ret = clib_error_return (0, "src address required");
+      goto done;
+    }
+
+  if (max_msg_size < DEFAULT_MAX_MSG_SIZE)
+    {
+      ret =
+       clib_error_return (0, "too small max-msg-size value, minimum is %u",
+                          DEFAULT_MAX_MSG_SIZE);
+      goto done;
+    }
+
+  vnet_api_error_t rv =
+    set_syslog_sender (&collector, collector_port, &src, vrf_id,
+                      max_msg_size);
+
+  if (rv)
+    ret =
+      clib_error_return (0, "set syslog sender failed rv=%d:%U", (int) rv,
+                        format_vnet_api_errno, rv);
+
+done:
+  unformat_free (line_input);
+  return ret;
+}
+
+static clib_error_t *
+show_syslog_sender_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                              vlib_cli_command_t * cmd)
+{
+  syslog_main_t *sm = &syslog_main;
+  u32 vrf_id = ~0;
+
+  if (sm->fib_index != ~0)
+    vrf_id = fib_table_get_table_id (sm->fib_index, FIB_PROTOCOL_IP4);
+
+  if (syslog_is_enabled ())
+    vlib_cli_output (vm, "collector %U:%u, src address %U, VRF ID %d, "
+                    "max-msg-size %u",
+                    format_ip4_address, &sm->collector, sm->collector_port,
+                    format_ip4_address, &sm->src_address,
+                    vrf_id, sm->max_msg_size);
+  else
+    vlib_cli_output (vm, "syslog sender is disabled");
+
+  return 0;
+}
+
+static clib_error_t *
+test_syslog_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                       vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  syslog_msg_t syslog_msg;
+  syslog_facility_t facility;
+  syslog_severity_t severity;
+  clib_error_t *ret = 0;
+  u8 *app_name = 0, *msgid = 0, *sd_id = 0, *param_name = 0, *param_value = 0;
+
+  if (!syslog_is_enabled ())
+    return 0;
+
+  /* Get a line of input. */
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  if (unformat (line_input, "%U", unformat_syslog_facility, &facility))
+    {
+      if (unformat (line_input, "%U", unformat_syslog_severity, &severity))
+       {
+         if (syslog_severity_filter_block (severity))
+           goto done;
+
+         if (unformat (line_input, "%s", &app_name))
+           {
+             if (unformat (line_input, "%s", &msgid))
+               {
+                 syslog_msg_init (&syslog_msg, facility, severity,
+                                  (char *) app_name, (char *) msgid);
+                 while (unformat (line_input, "sd-id %s", &sd_id))
+                   {
+                     syslog_msg_sd_init (&syslog_msg, (char *) sd_id);
+                     while (unformat
+                            (line_input, "sd-param %s %s", &param_name,
+                             &param_value))
+                       {
+                         syslog_msg_add_sd_param (&syslog_msg,
+                                                  (char *) param_name,
+                                                  (char *) param_value);
+                         vec_free (param_name);
+                         vec_free (param_value);
+                       }
+                     vec_free (sd_id);
+                   }
+                 if (unformat_check_input (line_input) !=
+                     UNFORMAT_END_OF_INPUT)
+                   syslog_msg_add_msg (&syslog_msg, "%U",
+                                       format_unformat_input, line_input);
+                 syslog_msg_send (&syslog_msg);
+               }
+             else
+               {
+                 ret =
+                   clib_error_return (0, "Unknown input `%U'",
+                                      format_unformat_error, line_input);
+                 goto done;
+               }
+           }
+         else
+           {
+             ret =
+               clib_error_return (0, "Unknown input `%U'",
+                                  format_unformat_error, line_input);
+             goto done;
+           }
+       }
+      else
+       {
+         ret =
+           clib_error_return (0, "Unknown input `%U'", format_unformat_error,
+                              line_input);
+         goto done;
+       }
+    }
+  else
+    {
+      ret =
+       clib_error_return (0, "Unknown input `%U'", format_unformat_error,
+                          line_input);
+      goto done;
+    }
+
+done:
+  vec_free (app_name);
+  vec_free (msgid);
+  unformat_free (line_input);
+  return ret;
+}
+
+static clib_error_t *
+set_syslog_filter_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                             vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  syslog_main_t *sm = &syslog_main;
+  clib_error_t *ret = 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, "severity %U", unformat_syslog_severity,
+          &sm->severity_filter))
+       ;
+      else
+       {
+         ret = clib_error_return (0, "Unknown input `%U'",
+                                  format_unformat_error, line_input);
+         goto done;
+       }
+    }
+
+done:
+  unformat_free (line_input);
+  return ret;
+}
+
+static clib_error_t *
+show_syslog_filter_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                              vlib_cli_command_t * cmd)
+{
+  syslog_main_t *sm = &syslog_main;
+
+  vlib_cli_output (vm, "severity-filter: %U", format_syslog_severity,
+                  sm->severity_filter);
+
+  return 0;
+}
+
+/* *INDENT-OFF* */
+/*?
+ * Set syslog sender configuration.
+ *
+ * @cliexpar
+ * @parblock
+ *
+ * Example of how to configure syslog sender:
+ * @cliexcmd{set syslog sender collector 10.10.10.10 port 514 src 172.16.2.2}
+ * @endparblock
+?*/
+VLIB_CLI_COMMAND (set_syslog_sender_command, static) = {
+    .path = "set syslog sender",
+    .short_help = "set syslog sender "
+                  "collector <ip4-address> [port <port>] "
+                  "src <ip4-address> [vrf-id <vrf-id>] "
+                  "[max-msg-size <max-msg-size>]",
+    .function = set_syslog_sender_command_fn,
+};
+
+/*?
+ * Show syslog sender configuration.
+ *
+ * @cliexpar
+ * @parblock
+ *
+ * Example of how to display syslog sender configuration:
+ * @cliexstart{show syslog sender}
+ * collector 10.10.10.10:514, src address 172.16.2.2, VRF ID 0, max-msg-size 480
+ * @cliexend
+ * @endparblock
+?*/
+VLIB_CLI_COMMAND (show_syslog_sender_command, static) = {
+    .path = "show syslog sender",
+    .short_help = "show syslog sender",
+    .function = show_syslog_sender_command_fn,
+};
+
+/*?
+ * This command generate test syslog message.
+ *
+ * @cliexpar
+ * @parblock
+ *
+ * Example of how to generate following syslog message
+ * '<em><180>1 2018-11-07T11:36:41.231759Z 172.16.1.1 test 10484 testMsg
+ * [exampleSDID@32473 eventID="1011" eventSource="App" iut="3"]
+ * this is message</em>'
+ * @cliexcmd{test syslog local6 warning test testMsg sd-id <!--
+ * --> exampleSDID@32473 sd-param eventID 1011 sd-param eventSource App <!--
+ * --> sd-param iut 3 this is message}
+ * @endparblock
+?*/
+VLIB_CLI_COMMAND (test_syslog_command, static) = {
+    .path = "test syslog",
+    .short_help = "test syslog <facility> <severity> <app-name> <msgid> "
+                  "[sd-id <sd-id> sd-param <name> <value>] [<message]",
+    .function = test_syslog_command_fn,
+};
+
+/*?
+ * Set syslog severity filter, specified severity and greater match.
+ *
+ * @cliexpar
+ * @parblock
+ *
+ * Example of how to configure syslog severity filter:
+ * @cliexcmd{set syslog filter severity warning}
+ * @endparblock
+?*/
+VLIB_CLI_COMMAND (set_syslog_filter_command, static) = {
+    .path = "set syslog filter",
+    .short_help = "set syslog filter severity <severity>",
+    .function = set_syslog_filter_command_fn,
+};
+
+/*?
+ * Show syslog severity filter.
+ *
+ * @cliexpar
+ * @parblock
+ *
+ * Example of how to display syslog severity filter:
+ * @cliexstart{show syslog filter}
+ * severity-filter: warning
+ * @cliexend
+ * @endparblock
+?*/
+VLIB_CLI_COMMAND (show_syslog_filter_command, static) = {
+    .path = "show syslog filter",
+    .short_help = "show syslog filter",
+    .function = show_syslog_filter_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+syslog_init (vlib_main_t * vm)
+{
+  syslog_main_t *sm = &syslog_main;
+  f64 vlib_time_0 = vlib_time_now (vm);
+  struct timeval timeval_0;
+  vlib_node_t *ip4_lookup_node;
+
+  sm->vlib_main = vm;
+  sm->vnet_main = vnet_get_main ();
+
+  sm->procid = getpid ();
+  gettimeofday (&timeval_0, 0);
+  sm->time_offset =
+    (f64) timeval_0.tv_sec + (((f64) timeval_0.tv_usec) * 1e-6) - vlib_time_0;
+
+  sm->collector.as_u32 = 0;
+  sm->src_address.as_u32 = 0;
+  sm->collector_port = DEFAULT_UDP_PORT;
+  sm->max_msg_size = DEFAULT_MAX_MSG_SIZE;
+  sm->fib_index = ~0;
+  sm->severity_filter = SYSLOG_SEVERITY_INFORMATIONAL;
+
+  ip4_lookup_node = vlib_get_node_by_name (vm, (u8 *) "ip4-lookup");
+  sm->ip4_lookup_node_index = ip4_lookup_node->index;
+
+  return 0;
+}
+
+VLIB_INIT_FUNCTION (syslog_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/syslog/syslog.h b/src/vnet/syslog/syslog.h
new file mode 100644 (file)
index 0000000..ed91587
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+/**
+ * @file RFC5424 syslog protocol declarations
+ */
+#ifndef __included_syslog_h__
+#define __included_syslog_h__
+
+#include <vlib/vlib.h>
+#include <vnet/vnet.h>
+#include <vnet/ip/ip4_packet.h>
+
+/* syslog message facilities */
+#define foreach_syslog_facility                 \
+  _(0, KERNEL, "kernel")                        \
+  _(1, USER_LEVEL, "user-level")                \
+  _(2, MAIL_SYSTEM, "mail-system")              \
+  _(3, SYSTEM_DAEMONS, "system-daemons")        \
+  _(4, SEC_AUTH, "security-authorization")      \
+  _(5, SYSLOGD, "syslogd")                      \
+  _(6, LINE_PRINTER, "line-printer")            \
+  _(7, NETWORK_NEWS, "network-news")            \
+  _(8, UUCP, "uucp")                            \
+  _(9, CLOCK, "clock-daemon")                   \
+  _(11, FTP, "ftp-daemon")                      \
+  _(12, NTP, "ntp-subsystem")                   \
+  _(13, LOG_AUDIT, "log-audit")                 \
+  _(14, LOG_ALERT, "log-alert")                 \
+  _(16, LOCAL0, "local0")                       \
+  _(17, LOCAL1, "local1")                       \
+  _(18, LOCAL2, "local2")                       \
+  _(19, LOCAL3, "local3")                       \
+  _(20, LOCAL4, "local4")                       \
+  _(21, LOCAL5, "local5")                       \
+  _(22, LOCAL6, "local6")                       \
+  _(23, LOCAL7, "local7")
+
+typedef enum
+{
+#define _(v, N, s) SYSLOG_FACILITY_##N = v,
+  foreach_syslog_facility
+#undef _
+} syslog_facility_t;
+
+/* syslog message severities */
+#define foreach_syslog_severity        \
+  _(0, EMERGENCY, "emergency")         \
+  _(1, ALERT, "alert")                 \
+  _(2, CRITICAL, "critical")           \
+  _(3, ERROR, "error")                 \
+  _(4, WARNING, "warning")             \
+  _(5, NOTICE, "notice")               \
+  _(6, INFORMATIONAL, "informational") \
+  _(7, DEBUG, "debug")
+
+typedef enum
+{
+#define _(v, N, s) SYSLOG_SEVERITY_##N = v,
+  foreach_syslog_severity
+#undef _
+} syslog_severity_t;
+
+/** syslog header */
+typedef struct
+{
+  /** facility value, part of priority */
+  syslog_facility_t facility;
+
+  /** severity value, part of priority */
+  syslog_severity_t severity;
+
+  /** message timestamp */
+  f64 timestamp;
+
+  /** application that originated the message RFC5424 6.2.5. */
+  char *app_name;
+
+  /** identify the type of message RFC5424 6.2.7. */
+  char *msgid;
+} syslog_header_t;
+
+/** syslog message */
+typedef struct
+{
+  /** header */
+  syslog_header_t header;
+
+  /** structured data RFC5424 6.3. */
+  u8 **structured_data;
+  u32 curr_sd_index;
+
+  /** free-form message RFC5424 6.4. */
+  u8 *msg;
+} syslog_msg_t;
+
+typedef struct
+{
+  /** process ID RFC5424 6.2.6. */
+  u32 procid;
+
+  /** time offset */
+  f64 time_offset;
+
+  /** IPv4 address of remote host (destination) */
+  ip4_address_t collector;
+
+  /** UDP port number of remote host (destination) */
+  u16 collector_port;
+
+  /** IPv4 address of sender (source) */
+  ip4_address_t src_address;
+
+  /** FIB table index */
+  u32 fib_index;
+
+  /** message size limit */
+  u32 max_msg_size;
+
+  /** severity filter (specified severity and greater match) */
+  syslog_severity_t severity_filter;
+
+  /** ip4-lookup node index */
+  u32 ip4_lookup_node_index;
+
+  /** convenience variables */
+  vlib_main_t *vlib_main;
+  vnet_main_t *vnet_main;
+} syslog_main_t;
+
+extern syslog_main_t syslog_main;
+
+/**
+ * @brief Initialize syslog message header
+ *
+ * @param facility facility value
+ * @param severity severity level
+ * @param app_name application that originated message RFC424 6.2.5. (optional)
+ * @param msgid identify the type of message RFC5424 6.2.7. (optional)
+ */
+void syslog_msg_init (syslog_msg_t * syslog_msg, syslog_facility_t facility,
+                     syslog_severity_t severity, char *app_name,
+                     char *msgid);
+/**
+ * @brief Initialize structured data element
+ *
+ * @param sd_id structured data element name RFC5424 6.3.2.
+ */
+void syslog_msg_sd_init (syslog_msg_t * syslog_msg, char *sd_id);
+
+/**
+ * @brief Add structured data elemnt parameter name-value pair RFC5424 6.3.3.
+ */
+void syslog_msg_add_sd_param (syslog_msg_t * syslog_msg, char *name,
+                             char *fmt, ...);
+
+/**
+ * @brief Add free-form message RFC5424 6.4.
+ */
+void syslog_msg_add_msg (syslog_msg_t * syslog_msg, char *fmt, ...);
+
+/**
+ * @brief Send syslog message
+ */
+int syslog_msg_send (syslog_msg_t * syslog_msg);
+
+/**
+ * @brief Set syslog sender configuration
+ *
+ * @param collector IPv4 address of syslog collector (destination)
+ * @param collector_port UDP port of syslog colector (destination)
+ * @param src IPv4 address of syslog sender (source)
+ * @param vrf_id VRF/FIB table ID
+ * @param max_msg_size maximum message length
+ */
+vnet_api_error_t set_syslog_sender (ip4_address_t * collector,
+                                   u16 collector_port, ip4_address_t * src,
+                                   u32 vrf_id, u32 max_msg_size);
+
+/**
+ * @brief Check if syslog logging is enabled
+ *
+ * @return 1 if syslog logging is enabled, 0 otherwise
+ */
+always_inline int
+syslog_is_enabled (void)
+{
+  syslog_main_t *sm = &syslog_main;
+
+  return sm->collector.as_u32 ? 1 : 0;
+}
+
+/**
+ * @brief Severity filter test
+ *
+ * @return 1 if message with specified severity is not selected to be logged
+ */
+always_inline int
+syslog_severity_filter_block (syslog_severity_t s)
+{
+  syslog_main_t *sm = &syslog_main;
+
+  return (sm->severity_filter < s);
+}
+
+#endif /* __included_syslog_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/syslog/syslog_api.c b/src/vnet/syslog/syslog_api.c
new file mode 100644 (file)
index 0000000..8f94c72
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2018 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/vnet.h>
+#include <vlibmemory/api.h>
+
+#include <vnet/interface.h>
+#include <vnet/api_errno.h>
+
+#include <vnet/fib/fib_table.h>
+#include <vnet/syslog/syslog.h>
+
+#include <vnet/vnet_msg_enum.h>
+
+#define vl_typedefs            /* define message structures */
+#include <vnet/vnet_all_api_h.h>
+#undef vl_typedefs
+
+#define vl_endianfun           /* define message structures */
+#include <vnet/vnet_all_api_h.h>
+#undef vl_endianfun
+
+/* instantiate all the print functions we know about */
+#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
+#define vl_printfun
+#include <vnet/vnet_all_api_h.h>
+#undef vl_printfun
+
+#include <vlibapi/api_helper_macros.h>
+
+#define foreach_vpe_api_msg             \
+_(SYSLOG_SET_SENDER, syslog_set_sender) \
+_(SYSLOG_GET_SENDER, syslog_get_sender) \
+_(SYSLOG_SET_FILTER, syslog_set_filter) \
+_(SYSLOG_GET_FILTER, syslog_get_filter)
+
+static int
+syslog_severity_decode (vl_api_syslog_severity_t v, syslog_severity_t * s)
+{
+  v = ntohl (v);
+  int rv = 0;
+
+  switch (v)
+    {
+    case SYSLOG_API_SEVERITY_EMERG:
+      *s = SYSLOG_SEVERITY_EMERGENCY;
+      break;
+    case SYSLOG_API_SEVERITY_ALERT:
+      *s = SYSLOG_SEVERITY_ALERT;
+      break;
+    case SYSLOG_API_SEVERITY_CRIT:
+      *s = SYSLOG_SEVERITY_CRITICAL;
+      break;
+    case SYSLOG_API_SEVERITY_ERR:
+      *s = SYSLOG_SEVERITY_ERROR;
+      break;
+    case SYSLOG_API_SEVERITY_WARN:
+      *s = SYSLOG_SEVERITY_WARNING;
+      break;
+    case SYSLOG_API_SEVERITY_NOTICE:
+      *s = SYSLOG_SEVERITY_NOTICE;
+      break;
+    case SYSLOG_API_SEVERITY_INFO:
+      *s = SYSLOG_SEVERITY_INFORMATIONAL;
+      break;
+    case SYSLOG_API_SEVERITY_DBG:
+      *s = SYSLOG_SEVERITY_DEBUG;
+      break;
+    default:
+      rv = VNET_API_ERROR_INVALID_VALUE;
+    }
+
+  return rv;
+}
+
+static int
+syslog_severity_encode (syslog_severity_t v, vl_api_syslog_severity_t * s)
+{
+  int rv = 0;
+  switch (v)
+    {
+    case SYSLOG_SEVERITY_EMERGENCY:
+      *s = SYSLOG_API_SEVERITY_EMERG;
+      break;
+    case SYSLOG_SEVERITY_ALERT:
+      *s = SYSLOG_API_SEVERITY_ALERT;
+      break;
+    case SYSLOG_SEVERITY_CRITICAL:
+      *s = SYSLOG_API_SEVERITY_CRIT;
+      break;
+    case SYSLOG_SEVERITY_ERROR:
+      *s = SYSLOG_API_SEVERITY_ERR;
+      break;
+    case SYSLOG_SEVERITY_WARNING:
+      *s = SYSLOG_API_SEVERITY_WARN;
+      break;
+    case SYSLOG_SEVERITY_NOTICE:
+      *s = SYSLOG_API_SEVERITY_NOTICE;
+      break;
+    case SYSLOG_SEVERITY_INFORMATIONAL:
+      *s = SYSLOG_API_SEVERITY_INFO;
+      break;
+    case SYSLOG_SEVERITY_DEBUG:
+      *s = SYSLOG_API_SEVERITY_DBG;
+      break;
+    default:
+      rv = VNET_API_ERROR_INVALID_VALUE;
+    }
+
+  *s = htonl (*s);
+  return rv;
+}
+
+static void
+vl_api_syslog_set_sender_t_handler (vl_api_syslog_set_sender_t * mp)
+{
+  vl_api_syslog_set_sender_reply_t *rmp;
+  ip4_address_t collector, src;
+
+  clib_memcpy (&collector, &mp->collector_address, sizeof (collector));
+  clib_memcpy (&src, &mp->src_address, sizeof (src));
+
+  int rv = set_syslog_sender (&collector, ntohs (mp->collector_port), &src,
+                             ntohl (mp->vrf_id), ntohl (mp->max_msg_size));
+
+  REPLY_MACRO (VL_API_SYSLOG_SET_SENDER_REPLY);
+}
+
+static void
+vl_api_syslog_get_sender_t_handler (vl_api_syslog_get_sender_t * mp)
+{
+  int rv = 0;
+  vl_api_syslog_get_sender_reply_t *rmp;
+  syslog_main_t *sm = &syslog_main;
+  u32 vrf_id;
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO2 (VL_API_SYSLOG_GET_SENDER_REPLY,
+  ({
+    clib_memcpy (rmp->collector_address.address, &(sm->collector),
+                 sizeof(ip4_address_t));
+    clib_memcpy (rmp->src_address.address, &(sm->src_address),
+                 sizeof(ip4_address_t));
+    rmp->collector_port = htons (sm->collector_port);
+    if (sm->fib_index == ~0)
+      vrf_id = ~0;
+    else
+      vrf_id = htonl (fib_table_get_table_id (sm->fib_index, FIB_PROTOCOL_IP4));
+    rmp->vrf_id = vrf_id;
+    rmp->max_msg_size = htonl (sm->max_msg_size);
+  }))
+  /* *INDENT-ON* */
+}
+
+static void
+vl_api_syslog_set_filter_t_handler (vl_api_syslog_set_filter_t * mp)
+{
+  vl_api_syslog_set_filter_reply_t *rmp;
+  syslog_main_t *sm = &syslog_main;
+  int rv = 0;
+  syslog_severity_t s;
+
+  rv = syslog_severity_decode (mp->severity, &s);
+  if (rv)
+    goto send_reply;
+
+  sm->severity_filter = s;
+
+send_reply:
+  REPLY_MACRO (VL_API_SYSLOG_SET_FILTER_REPLY);
+}
+
+static void
+vl_api_syslog_get_filter_t_handler (vl_api_syslog_get_filter_t * mp)
+{
+  int rv = 0;
+  vl_api_syslog_get_filter_reply_t *rmp;
+  syslog_main_t *sm = &syslog_main;
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO2 (VL_API_SYSLOG_GET_FILTER_REPLY,
+  ({
+     rv = syslog_severity_encode (sm->severity_filter, &rmp->severity);
+  }))
+  /* *INDENT-ON* */
+}
+
+#define vl_msg_name_crc_list
+#include <vnet/vnet_all_api_h.h>
+#undef vl_msg_name_crc_list
+
+static void
+setup_message_id_table (api_main_t * am)
+{
+#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id);
+  foreach_vl_msg_name_crc_syslog;
+#undef _
+}
+
+static clib_error_t *
+syslog_api_hookup (vlib_main_t * vm)
+{
+  api_main_t *am = &api_main;
+
+#define _(N,n)                                                  \
+    vl_msg_api_set_handlers(VL_API_##N, #n,                     \
+                           vl_api_##n##_t_handler,              \
+                           vl_noop_handler,                     \
+                           vl_api_##n##_t_endian,               \
+                           vl_api_##n##_t_print,                \
+                           sizeof(vl_api_##n##_t), 1);
+  foreach_vpe_api_msg;
+#undef _
+
+  /*
+   * Set up the (msg_name, crc, message-id) table
+   */
+  setup_message_id_table (am);
+
+  return 0;
+}
+
+VLIB_API_INIT_FUNCTION (syslog_api_hookup);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/syslog/syslog_udp.c b/src/vnet/syslog/syslog_udp.c
new file mode 100644 (file)
index 0000000..f4fa1d0
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+/**
+ * @file syslog protocol UDP transport layer implementation (RFC5426)
+ */
+
+#include <vnet/syslog/syslog_udp.h>
+#include <vnet/ip/ip4.h>
+#include <vnet/udp/udp_packet.h>
+
+void
+syslog_add_udp_transport (vlib_main_t * vm, u32 bi)
+{
+  syslog_main_t *sm = &syslog_main;
+  vlib_buffer_t *b = vlib_get_buffer (vm, bi);
+  ip4_header_t *ip;
+  udp_header_t *udp;
+
+  vlib_buffer_advance (b, -(sizeof (ip4_header_t) + sizeof (udp_header_t)));
+
+  b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
+  vnet_buffer (b)->sw_if_index[VLIB_RX] = 0;
+  vnet_buffer (b)->sw_if_index[VLIB_TX] = sm->fib_index;
+
+  ip = vlib_buffer_get_current (b);
+  clib_memset (ip, 0, sizeof (*ip));
+  udp = (udp_header_t *) (ip + 1);
+  clib_memset (udp, 0, sizeof (*udp));
+
+  ip->ip_version_and_header_length = 0x45;
+  ip->flags_and_fragment_offset =
+    clib_host_to_net_u16 (IP4_HEADER_FLAG_DONT_FRAGMENT);
+  ip->ttl = 255;
+  ip->protocol = IP_PROTOCOL_UDP;
+  ip->src_address.as_u32 = sm->src_address.as_u32;
+  ip->dst_address.as_u32 = sm->collector.as_u32;
+
+  udp->src_port = udp->dst_port = clib_host_to_net_u16 (sm->collector_port);
+
+  const u16 ip_length = vlib_buffer_length_in_chain (vm, b);
+  ip->length = clib_host_to_net_u16 (ip_length);
+  ip->checksum = ip4_header_checksum (ip);
+
+  const u16 udp_length = ip_length - (sizeof (ip4_header_t));
+  udp->length = clib_host_to_net_u16 (udp_length);
+  /* RFC5426 3.6. */
+  udp->checksum = ip4_tcp_udp_compute_checksum (vm, b, ip);
+  if (udp->checksum == 0)
+    udp->checksum = 0xffff;
+
+  b->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/syslog/syslog_udp.h b/src/vnet/syslog/syslog_udp.h
new file mode 100644 (file)
index 0000000..008fed7
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+/**
+ * @file syslog protocol UDP transport layer declaration (RFC5426)
+ */
+#ifndef __included_syslog_udp_h__
+#define __included_syslog_udp_h__
+
+#include <vnet/syslog/syslog.h>
+
+/**
+ * @brief Add UDP/IP transport layer by prepending it to existing data
+ */
+void syslog_add_udp_transport (vlib_main_t * vm, u32 bi);
+
+#endif /* __included_syslog_udp_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index 157006b..927bcab 100644 (file)
@@ -76,6 +76,7 @@
 #include <vnet/dhcp/dhcp6_ia_na_client_cp.api.h>
 #include <vnet/devices/pipe/pipe.api.h>
 #include <vnet/vxlan-gbp/vxlan_gbp.api.h>
+#include <vnet/syslog/syslog.api.h>
 
 /*
  * fd.io coding-style-patch-verification: ON
index 0338062..5f456e1 100644 (file)
@@ -73,7 +73,7 @@ PYTHON_EXTRA_DEPENDS=
 endif
 
 PYTHON_VENV_PATH=$(VPP_PYTHON_PREFIX)/virtualenv
-PYTHON_DEPENDS=$(PYTHON_EXTRA_DEPENDS) psutil faulthandler six scapy==2.4.0 pexpect cryptography subprocess32 cffi git+https://github.com/vpp-dev/py-lispnetworking
+PYTHON_DEPENDS=$(PYTHON_EXTRA_DEPENDS) psutil faulthandler six scapy==2.4.0 pexpect cryptography subprocess32 cffi syslog-rfc5424-parser git+https://github.com/vpp-dev/py-lispnetworking
 SCAPY_SOURCE=$(shell find $(PYTHON_VENV_PATH) -name site-packages)
 BUILD_COV_DIR=$(BR)/test-cov
 
diff --git a/test/test_syslog.py b/test/test_syslog.py
new file mode 100644 (file)
index 0000000..db7d7be
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/bin/env python
+
+from framework import VppTestCase, VppTestRunner
+from util import ppp
+from scapy.packet import Raw
+from scapy.layers.inet import IP, UDP
+from vpp_papi_provider import SYSLOG_SEVERITY
+from syslog_rfc5424_parser import SyslogMessage, ParseError
+from syslog_rfc5424_parser.constants import SyslogFacility, SyslogSeverity
+
+
+class TestSyslog(VppTestCase):
+    """ Syslog Protocol Test Cases """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestSyslog, cls).setUpClass()
+
+        try:
+            cls.create_pg_interfaces(range(1))
+            cls.pg0.admin_up()
+            cls.pg0.config_ip4()
+            cls.pg0.resolve_arp()
+
+        except Exception:
+            super(TestSyslog, cls).tearDownClass()
+            raise
+
+    def syslog_generate(self, facility, severity, appname, msgid, sd=None,
+                        msg=None):
+        """
+        Generate syslog message
+
+        :param facility: facility value
+        :param severity: severity level
+        :param appname: application name that originate message
+        :param msgid: message indetifier
+        :param sd: structured data (optional)
+        :param msg: free-form message (optional)
+        """
+        facility_str = ['kernel', 'user-level', 'mail-system',
+                        'system-daemons', 'security-authorization', 'syslogd',
+                        'line-printer', 'network-news', 'uucp', 'clock-daemon',
+                        '', 'ftp-daemon', 'ntp-subsystem', 'log-audit',
+                        'log-alert', '', 'local0', 'local1', 'local2',
+                        'local3', 'local4', 'local5', 'local6', 'local7']
+
+        severity_str = ['emergency', 'alert', 'critical', 'error', 'warning',
+                        'notice', 'informational', 'debug']
+
+        cli_str = "test syslog %s %s %s %s" % (facility_str[facility],
+                                               severity_str[severity],
+                                               appname,
+                                               msgid)
+        if sd is not None:
+            for sd_id, sd_params in sd.items():
+                cli_str += " sd-id %s" % (sd_id)
+                for name, value in sd_params.items():
+                    cli_str += " sd-param %s %s" % (name, value)
+        if msg is not None:
+            cli_str += " %s" % (msg)
+        self.vapi.cli(cli_str)
+
+    def syslog_verify(self, data, facility, severity, appname, msgid, sd=None,
+                      msg=None):
+        """
+        Verify syslog message
+
+        :param data: syslog message
+        :param facility: facility value
+        :param severity: severity level
+        :param appname: application name that originate message
+        :param msgid: message indetifier
+        :param sd: structured data (optional)
+        :param msg: free-form message (optional)
+        """
+        message = data.decode('utf-8')
+        if sd is None:
+            sd = {}
+        try:
+            message = SyslogMessage.parse(message)
+            self.assertEqual(message.facility, facility)
+            self.assertEqual(message.severity, severity)
+            self.assertEqual(message.appname, appname)
+            self.assertEqual(message.msgid, msgid)
+            self.assertEqual(message.msg, msg)
+            self.assertEqual(message.sd, sd)
+            self.assertEqual(message.version, 1)
+            self.assertEqual(message.hostname, self.pg0.local_ip4)
+        except ParseError as e:
+            self.logger.error(e)
+
+    def test_syslog(self):
+        """ Syslog Protocol test """
+        self.vapi.syslog_set_sender(self.pg0.remote_ip4n, self.pg0.local_ip4n)
+        config = self.vapi.syslog_get_sender()
+        self.assertEqual(config.collector_address.address,
+                         self.pg0.remote_ip4n)
+        self.assertEqual(config.collector_port, 514)
+        self.assertEqual(config.src_address.address, self.pg0.local_ip4n)
+        self.assertEqual(config.vrf_id, 0)
+        self.assertEqual(config.max_msg_size, 480)
+
+        appname = 'test'
+        msgid = 'testMsg'
+        msg = 'this is message'
+        sd1 = {'exampleSDID@32473': {'iut': '3',
+                                     'eventSource': 'App',
+                                     'eventID': '1011'}}
+        sd2 = {'exampleSDID@32473': {'iut': '3',
+                                     'eventSource': 'App',
+                                     'eventID': '1011'},
+               'examplePriority@32473': {'class': 'high'}}
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.syslog_generate(SyslogFacility.local7,
+                             SyslogSeverity.info,
+                             appname,
+                             msgid,
+                             None,
+                             msg)
+        capture = self.pg0.get_capture(1)
+        try:
+            self.assertEqual(capture[0][IP].src, self.pg0.local_ip4)
+            self.assertEqual(capture[0][IP].dst, self.pg0.remote_ip4)
+            self.assertEqual(capture[0][UDP].dport, 514)
+            self.assert_packet_checksums_valid(capture[0], False)
+        except:
+                self.logger.error(ppp("invalid packet:", capture[0]))
+                raise
+        self.syslog_verify(capture[0][Raw].load,
+                           SyslogFacility.local7,
+                           SyslogSeverity.info,
+                           appname,
+                           msgid,
+                           None,
+                           msg)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.vapi.syslog_set_filter(SYSLOG_SEVERITY.WARN)
+        filter = self.vapi.syslog_get_filter()
+        self.assertEqual(filter.severity, SYSLOG_SEVERITY.WARN)
+        self.syslog_generate(SyslogFacility.local7,
+                             SyslogSeverity.info,
+                             appname,
+                             msgid,
+                             None,
+                             msg)
+        self.pg0.assert_nothing_captured()
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.syslog_generate(SyslogFacility.local6,
+                             SyslogSeverity.warning,
+                             appname,
+                             msgid,
+                             sd1,
+                             msg)
+        capture = self.pg0.get_capture(1)
+        self.syslog_verify(capture[0][Raw].load,
+                           SyslogFacility.local6,
+                           SyslogSeverity.warning,
+                           appname,
+                           msgid,
+                           sd1,
+                           msg)
+
+        self.vapi.syslog_set_sender(self.pg0.remote_ip4n,
+                                    self.pg0.local_ip4n,
+                                    collector_port=12345)
+        config = self.vapi.syslog_get_sender()
+        self.assertEqual(config.collector_port, 12345)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.syslog_generate(SyslogFacility.local5,
+                             SyslogSeverity.err,
+                             appname,
+                             msgid,
+                             sd2,
+                             None)
+        capture = self.pg0.get_capture(1)
+        try:
+            self.assertEqual(capture[0][UDP].dport, 12345)
+        except:
+                self.logger.error(ppp("invalid packet:", capture[0]))
+                raise
+        self.syslog_verify(capture[0][Raw].load,
+                           SyslogFacility.local5,
+                           SyslogSeverity.err,
+                           appname,
+                           msgid,
+                           sd2,
+                           None)
+
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)
index 8ed870e..f8d0e6c 100644 (file)
@@ -44,6 +44,17 @@ class QOS_SOURCE:
     IP = 3
 
 
+class SYSLOG_SEVERITY:
+    EMERG = 0
+    ALERT = 1
+    CRIT = 2
+    ERR = 3
+    WARN = 4
+    NOTICE = 5
+    INFO = 6
+    DBG = 7
+
+
 class UnexpectedApiReturnValueError(Exception):
     """ exception raised when the API return value is unexpected """
     pass
@@ -4026,3 +4037,42 @@ class VppPapiProvider(object):
 
     def svs_dump(self):
         return self.api(self.papi.svs_dump, {})
+
+    def syslog_set_sender(
+            self,
+            collector,
+            src,
+            collector_port=514,
+            vrf_id=0,
+            max_msg_size=480):
+        """Set syslog sender configuration
+
+        :param collector: colector IP address
+        :param src: source IP address
+        :param collector_port: collector UDP port (Default value = 514)
+        :param vrf_id: VRF id (Default value = 0)
+        :param max_msg_size: maximum message length (Default value = 480)
+        """
+        return self.api(self.papi.syslog_set_sender,
+                        {'collector_address': {
+                             'address': collector},
+                         'src_address': {
+                             'address': src},
+                         'collector_port': collector_port,
+                         'vrf_id': vrf_id,
+                         'max_msg_size': max_msg_size})
+
+    def syslog_get_sender(self):
+        """Return syslog sender configuration"""
+        return self.api(self.papi.syslog_get_sender, {})
+
+    def syslog_set_filter(self, severity):
+        """Set syslog filter parameters
+
+        :param severity: severity filter (specified severity and greater match)
+        """
+        return self.api(self.papi.syslog_set_filter, {'severity': severity})
+
+    def syslog_get_filter(self):
+        """Return syslog filter parameters"""
+        return self.api(self.papi.syslog_get_filter, {})