zebra: optionally use protobuf with FPM

Change zebra so that it can optionally use protobuf serialization when
communicating with a Forwarding Plane Manager component.

  * zebra/main.c

    Add the --fpm-format/-F command line option. This allows the user
    to control the format (protbuf|netlink) that is used to
    communicate with the FPM.

  * zebra/zebra_fpm.c

    - zebra_init_msg_format(),

      This new function is invoked on process startup to determine the
      FPM format that should be used.

    - zfpm_init()

      Change to accept any 'FPM message format' specified by the user
      (via the new command line flag).

    - zebra_encode_route()

      Tweak to use the selected FPM format.

  * zebra_fpm_protobuf.c

    New code to build protobuf messages to be sent to the FPM.

  * zebra/Makefile.am

    - Include common.am

    - Build new file zebra_fpm_protobuf.c when protobuf is available.

    - Link with the fpm_pb library.

Signed-off-by: Avneesh Sachdev <avneesh@sproute.com>
diff --git a/zebra/zebra_fpm.c b/zebra/zebra_fpm.c
index 9cbbf68..22fc6ca 100644
--- a/zebra/zebra_fpm.c
+++ b/zebra/zebra_fpm.c
@@ -139,6 +139,15 @@
 } zfpm_state_t;
 
 /*
+ * Message format to be used to communicate with the FPM.
+ */
+typedef enum
+{
+  ZFPM_MSG_FORMAT_NONE,
+  ZFPM_MSG_FORMAT_NETLINK,
+  ZFPM_MSG_FORMAT_PROTOBUF,
+} zfpm_msg_format_e;
+/*
  * Globals.
  */
 typedef struct zfpm_glob_t_
@@ -149,6 +158,11 @@
    */
   int enabled;
 
+  /*
+   * Message format to be used to communicate with the fpm.
+   */
+  zfpm_msg_format_e message_format;
+
   struct thread_master *master;
 
   zfpm_state_t state;
@@ -863,19 +877,40 @@
  */
 static inline int
 zfpm_encode_route (rib_dest_t *dest, struct rib *rib, char *in_buf,
-		   size_t in_buf_len)
+		   size_t in_buf_len, fpm_msg_type_e *msg_type)
 {
-#ifndef HAVE_NETLINK
-  return 0;
-#else
-
+  size_t len;
   int cmd;
+  len = 0;
 
-  cmd = rib ? RTM_NEWROUTE : RTM_DELROUTE;
+  *msg_type = FPM_MSG_TYPE_NONE;
 
-  return zfpm_netlink_encode_route (cmd, dest, rib, in_buf, in_buf_len);
+  switch (zfpm_g->message_format) {
 
+  case ZFPM_MSG_FORMAT_PROTOBUF:
+#ifdef HAVE_PROTOBUF
+    len = zfpm_protobuf_encode_route (dest, rib, (uint8_t *) in_buf,
+				      in_buf_len);
+    *msg_type = FPM_MSG_TYPE_PROTOBUF;
+#endif
+    break;
+
+  case ZFPM_MSG_FORMAT_NETLINK:
+#ifdef HAVE_NETLINK
+    *msg_type = FPM_MSG_TYPE_NETLINK;
+    cmd = rib ? RTM_NEWROUTE : RTM_DELROUTE;
+    len = zfpm_netlink_encode_route (cmd, dest, rib, in_buf, in_buf_len);
+    assert(fpm_msg_align(len) == len);
+    *msg_type = FPM_MSG_TYPE_NETLINK;
 #endif /* HAVE_NETLINK */
+    break;
+
+  default:
+    break;
+  }
+
+  return len;
+
 }
 
 /*
@@ -883,7 +918,7 @@
  *
  * Returns the rib that is to be sent to the FPM for a given dest.
  */
-static struct rib *
+struct rib *
 zfpm_route_for_update (rib_dest_t *dest)
 {
   struct rib *rib;
@@ -919,6 +954,7 @@
   fpm_msg_hdr_t *hdr;
   struct rib *rib;
   int is_add, write_msg;
+  fpm_msg_type_e msg_type;
 
   s = zfpm_g->obuf;
 
@@ -943,7 +979,6 @@
 
     hdr = (fpm_msg_hdr_t *) buf;
     hdr->version = FPM_PROTO_VERSION;
-    hdr->msg_type = FPM_MSG_TYPE_NETLINK;
 
     data = fpm_msg_data (hdr);
 
@@ -963,11 +998,13 @@
       }
 
     if (write_msg) {
-      data_len = zfpm_encode_route (dest, rib, (char *) data, buf_end - data);
+      data_len = zfpm_encode_route (dest, rib, (char *) data, buf_end - data,
+				    &msg_type);
 
       assert (data_len);
       if (data_len)
 	{
+	  hdr->msg_type = msg_type;
 	  msg_len = fpm_data_len_to_msg_len (data_len);
 	  hdr->msg_len = htons (msg_len);
 	  stream_forward_endp (s, msg_len);
@@ -1569,6 +1606,64 @@
 }
 
 
+/*
+ * zfpm_init_message_format
+ */
+static inline void
+zfpm_init_message_format (const char *format)
+{
+  int have_netlink, have_protobuf;
+
+  have_netlink = have_protobuf = 0;
+
+#ifdef HAVE_NETLINK
+  have_netlink = 1;
+#endif
+
+#ifdef HAVE_PROTOBUF
+  have_protobuf = 1;
+#endif
+
+  zfpm_g->message_format = ZFPM_MSG_FORMAT_NONE;
+
+  if (!format)
+    {
+      if (have_netlink)
+	{
+	  zfpm_g->message_format = ZFPM_MSG_FORMAT_NETLINK;
+	}
+      else if (have_protobuf)
+	{
+	  zfpm_g->message_format = ZFPM_MSG_FORMAT_PROTOBUF;
+	}
+      return;
+    }
+
+  if (!strcmp ("netlink", format))
+    {
+      if (!have_netlink)
+	{
+	  zlog_err ("FPM netlink message format is not available");
+	  return;
+	}
+      zfpm_g->message_format = ZFPM_MSG_FORMAT_NETLINK;
+      return;
+    }
+
+  if (!strcmp ("protobuf", format))
+    {
+      if (!have_protobuf)
+	{
+	  zlog_err ("FPM protobuf message format is not available");
+	  return;
+	}
+      zfpm_g->message_format = ZFPM_MSG_FORMAT_PROTOBUF;
+      return;
+    }
+
+  zlog_warn ("Unknown fpm format '%s'", format);
+}
+
 /**
  * fpm_remote_srv_write 
  *
@@ -1598,11 +1693,13 @@
  *
  * @param[in] port port at which FPM is running.
  * @param[in] enable TRUE if the zebra FPM module should be enabled
+ * @param[in] format to use to talk to the FPM. Can be 'netink' or 'protobuf'.
  *
  * Returns TRUE on success.
  */
 int
-zfpm_init (struct thread_master *master, int enable, uint16_t port)
+zfpm_init (struct thread_master *master, int enable, uint16_t port,
+	   const char *format)
 {
   static int initialized = 0;
 
@@ -1618,16 +1715,6 @@
   zfpm_g->sock = -1;
   zfpm_g->state = ZFPM_STATE_IDLE;
 
-  /*
-   * Netlink must currently be available for the Zebra-FPM interface
-   * to be enabled.
-   */
-#ifndef HAVE_NETLINK
-  enable = 0;
-#endif
-
-  zfpm_g->enabled = enable;
-
   zfpm_stats_init (&zfpm_g->stats);
   zfpm_stats_init (&zfpm_g->last_ivl_stats);
   zfpm_stats_init (&zfpm_g->cumulative_stats);
@@ -1637,6 +1724,16 @@
   install_element (CONFIG_NODE, &fpm_remote_ip_cmd);
   install_element (CONFIG_NODE, &no_fpm_remote_ip_cmd);
 
+  zfpm_init_message_format(format);
+
+  /*
+   * Disable FPM interface if no suitable format is available.
+   */
+  if (zfpm_g->message_format == ZFPM_MSG_FORMAT_NONE)
+      enable = 0;
+
+  zfpm_g->enabled = enable;
+
   if (!enable) {
     return 1;
   }