Hi,
 
I've figured it out, it has been a feature of the dissector code. Perhaps it might be useful for someone developing her own SPOA.
 
Kind regards,
 
      Danny
 
Gesendet: Donnerstag, 09. November 2017 um 16:07 Uhr
Von: "Frederic Lecaille" <flecai...@haproxy.com>
An: my.card....@web.de, haproxy@formilux.org
Betreff: Re: [RFC] Wireshark dissector for SPOP
On 11/05/2017 09:27 AM, my.card....@web.de wrote:
> Hi all,

Hi,

> I've implemented a very basic wireshark (https://www.wireshark.org)
> dissector for SPOP. I've stumbled over the following issue, that I
> couldn't figure out, yet.
> ACTION-ARGS should be multiple TYPED-DATA items, but the data sent by
> contrib/spoa_sample does not add type information:
> 73335    14123.613537866    127.0.0.1    127.0.0.1    SPOP    89    ACK
> STREAM-ID:35969 FRAME-ID:1[Malformed Packet]
> 0000   00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00  ..............E.
> 0010   00 4b 70 77 40 00 40 06 cc 33 7f 00 00 01 7f 00  .Kpw@.@..3......
> 0020   00 01 30 39 cd 4c e8 1d cd f2 05 90 45 92 80 18  ..09.L......E...
> 0030   01 5e fe 3f 00 00 01 01 08 0a 01 46 c7 f3 01 46  .^.?.......F...F
> 0040   c7 f3 67 01 00 00 00 f2 99 01 01 01 03 01 08 69  ..g............i
> 0050   70 5f 73 63 6f 72 65 03 47                       p_score.G
> Is this an implementation or documentation issues? What is haproxy
> expecting here?
> Kind regards,
>     Danny

Thank you for sharing this code.

It could be interesting to have such a dissector in the future.

I will have a look at your code asap.

Do not hesitate to update this thread if you managed to fix some issues.

Regards,

Fred.
/*
 * Wireshark dissector for SPOP
 *
 * Copyright 2017 Daniela Sonnenschein <my.card....@web.de>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *
 * Please see the protocol specification at
 *
 *   https://www.haproxy.org/download/1.7/doc/SPOE.txt
 *
 */
#include <stdint.h>
#include <stdio.h>

#include "config.h"

#include <epan/packet.h>
#include <epan/prefs.h>
#include "packet-tcp.h"

#define SPOP_PORT 12345

static int proto_spop = -1;

static int hf_spop_frame_length = -1;
static int hf_spop_frame_type = -1;
static int hf_spop_flags = -1;
static int hf_spop_flag_fin = -1;
static int hf_spop_frame_id = -1;
static int hf_spop_stream_id = -1;

static int hf_spop_kvlist = -1;
static int hf_spop_key_value = -1;
static int hf_spop_key_value_flags = -1;
static int hf_spop_messages = -1;
static int hf_spop_message = -1;
static int hf_spop_nbargs = -1;
static int hf_spop_actions = -1;
static int hf_spop_action = -1;
static int hf_spop_action_type = -1;
static int hf_spop_action_args = -1;
static int hf_spop_action_scope = -1;
static int hf_spop_typed_data = -1;
static int hf_spop_boolean = -1;
static int hf_spop_integer = -1;
static int hf_spop_ipv4 = -1;
static int hf_spop_ipv6 = -1;
static int hf_spop_string = -1;
static int hf_spop_string_length = -1;
static int hf_spop_string_value = -1;
static int hf_spop_binary = -1;

static gint ett_spop = -1;
static gint ett_spop_flags = -1;
static gint ett_spop_kvlist = -1;
static gint ett_spop_key_value = -1;
static gint ett_spop_messages = -1;
static gint ett_spop_message = -1;
static gint ett_spop_actions = -1;
static gint ett_spop_action = -1;
static gint ett_spop_action_args = -1;
static gint ett_spop_string = -1;
static gint ett_spop_typed_data = -1;

/* FLAGS :   0     1-31
            +---+-----------+
            | F|            |
            | I|  RESERVED  |
            | N|            |
            +--+------------+ */

/* FIN: Indicates that this is the final payload fragment. The first
        fragment may also be the final fragment. */
#define SPOP_FLAG_FIN  0x01

static const value_string frame_types[] = {
    { 1, "HAPROXY-HELLO" }, /* Sent by HAproxy when it opens a connection
                               to an agent */
    { 2, "HAPROXY-DISCONNECT" }, /* Sent by HAproxy when it want to close
                                    the connection or in reply to an
                                    AGENT-DISCONNECT frame */
    { 3, "NOTIFY" }, /* Sent by HAproxy to pass information to an agent */
    { 101, "AGENT-HELLO" }, /* Reply to a HAPROXY-HELLO frame, when the
                               connection is established */
    { 102, "AGENT-DISCONNECT" }, /* Sent by an agent just before closing
                                    the connection */
    { 103, "ACK" }, /* Sent to acknowledge a NOTIFY frame */
    { 0, NULL }
};

static const value_string action_types[] = {
    { 1, "set-var" },    /* <1> */
    { 2, "unset-var" },  /* <2> */
    { 0, NULL }
};

static const value_string action_scopes[] = {
    { 0, "PROCESS" },    /* <0> */
    { 1, "SESSION" },    /* <1> */
    { 2, "TRANSATION" }, /* <2> */
    { 3, "REQUEST" },    /* <3> */
    { 4, "RESERVED" },   /* <4> */
    { 0, NULL }
};

static const value_string data_type[] = {
    { 0, "NULL"   }, /* <0> */
    { 1, "BOOL"   }, /* <1+FLAG> */
    { 2, "INT32"  }, /* <2><VALUE:varint> */
    { 3, "UINT32" }, /* <3><VALUE:varint> */
    { 4, "INT64"  }, /* <4><VALUE:varint> */
    { 5, "UINT64" }, /* <5><VALUE:varint> */
    { 6, "IPV4"   }, /* <6><STRUCT IN_ADDR:4 bytes> */
    { 7, "IPV6"   }, /* <7><STRUCT IN_ADDR6:16 bytes> */
    { 8, "String" }, /* <8><LENGTH:varint><BYTES> */
    { 9, "Binary" }, /* <9><LENGTH:varint><BYTES> */
    { 0, NULL     }
};

/* For booleans, the value (true or false) is the first bit in the
   FLAGS bitfield. if this bit is set to 0, then the boolean is evaluated
   as false, otherwise, the boolean is evaluated as true. */

#define SPOP_KEY_VALUE_FLAGS_MASK   0xf0
#define SPOP_KEY_VALUE_FLAG_BOOLEAN 0x10

void
proto_register_spop(void)
{
    static hf_register_info hf[] = {
        { &hf_spop_frame_length,
            { "Frame Length", "spop.frame.length",
            FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_frame_type,
            { "Frame Type", "spop.frame.type",
            FT_UINT8, BASE_DEC, VALS(frame_types), 0x0, NULL, HFILL }
        },
        { &hf_spop_flags,
            { "Flags", "spop.flags",
            FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_flag_fin,
            { "FIN Flag", "spop.flags.fin",
            FT_BOOLEAN, 8, NULL, SPOP_FLAG_FIN, NULL, HFILL }
        },
        { &hf_spop_stream_id,
            { "Stream ID", "spop.stream.id",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_frame_id,
            { "Frame ID", "spop.frame.id",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_kvlist,
            { "KV-LIST", "spop.payload.kv-list",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_key_value,
            { "KEY-VALUE", "spop.payload.key-value",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_key_value_flags,
            { "KEY-VALUE-FLAGS", "spop.payload.key-value-flags",
            FT_UINT8, 4, NULL, SPOP_KEY_VALUE_FLAGS_MASK, NULL, HFILL }
        },
        { &hf_spop_messages,
            { "MESSAGES", "spop.payload.messages",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_message,
            { "MESSAGE", "spop.payload.message",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_nbargs,
            { "NBARGS", "spop.payload.nbargs",
            FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_actions,
            { "ACTIONS", "spop.payload.actions",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_action,
            { "ACTION", "spop.payload.action",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_action_type,
            { "ACTION-TYPE", "spop.payload.action.type",
            FT_UINT8, BASE_DEC, VALS(action_types), 0x0, NULL, HFILL }
        },
        { &hf_spop_action_args,
            { "ACTION-ARGS", "spop.payload.action.args",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_action_scope,
            { "ACTION-SCOPE", "spop.payload.action.scope",
            FT_UINT8, BASE_DEC, VALS(action_scopes), 0x0, NULL, HFILL }
        },
        { &hf_spop_typed_data,
            { "TYPED-DATA", "spop.payload.typed-data",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_boolean,
            { "BOOLEAN", "spop.value.boolean",
            FT_BOOLEAN, 8, NULL, SPOP_KEY_VALUE_FLAG_BOOLEAN, NULL, HFILL }
        },
        { &hf_spop_integer,
            { "INTEGER", "spop.value.integer",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_ipv4,
            { "IPv4", "spop.value.ipv4",
            FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_ipv6,
            { "IPv6", "spop.value.ipv6",
            FT_IPv6, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_string,
            { "STRING", "spop.value.string",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_string_length,
            { "LENGTH", "spop.value.string.length",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_string_value,
            { "VALUE", "spop.value.string.value",
            FT_STRING, STR_ASCII, NULL, 0x0, NULL, HFILL }
        },
        { &hf_spop_binary,
            { "BINARY", "spop.value.binary",
            FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }
        }
    };

    static gint *ett[] = {
        &ett_spop,
        &ett_spop_flags,
        &ett_spop_kvlist,
        &ett_spop_typed_data,
        &ett_spop_key_value,
        &ett_spop_messages,
        &ett_spop_message,
        &ett_spop_actions,
        &ett_spop_action,
        &ett_spop_action_args,
        &ett_spop_string,
    };

    proto_spop = proto_register_protocol (
        "SPOP Protocol", /* name */
        "SPOP",          /* short name */
        "spop"           /* abbrev */
        );

    proto_register_field_array(proto_spop, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
}

/* Variable-length integer (varint) are encoded using Peers encoding:
 *
 *       0  <= X < 240        : 1 byte  (7.875 bits)  [ XXXX XXXX ]
 *      240 <= X < 2288       : 2 bytes (11 bits)     [ 1111 XXXX ] [ 0XXX XXXX 
]
 *     2288 <= X < 264432     : 3 bytes (18 bits)     [ 1111 XXXX ] [ 1XXX XXXX 
]   [ 0XXX XXXX ]
 *   264432 <= X < 33818864   : 4 bytes (25 bits)     [ 1111 XXXX ] [ 1XXX XXXX 
]*2 [ 0XXX XXXX ]
 * 33818864 <= X < 4328786160 : 5 bytes (32 bits)     [ 1111 XXXX ] [ 1XXX XXXX 
]*3 [ 0XXX XXXX ]
 * ...
 */
static int
dissect_varint(tvbuff_t *tvb, proto_tree *tree _U_, int id, size_t offset, 
uint64_t *valuep)
{
  size_t length;
  uint64_t value = 0;
  uint8_t tmp;

  tmp = tvb_get_guint8(tvb, offset);
  length = 1;

  if ((tmp & 0xf0) == 0xf0)
  {
    value = (tmp & 0x0f);
    do
    {
      tmp = tvb_get_guint8(tvb, offset + length);
      length++; 

      value <<= 7;
      value += (tmp & 0x7f);
    }
    while (tmp & 0x80);
  }
  else /* if (tmp < 240) */
  {
    value = tmp;
  }

  {
    proto_item *ti = proto_tree_add_item(tree, id, tvb, offset, length, ENC_NA);
    proto_item_append_text(ti, ": %lu (varint %lu octets)", value, length);
  }

  if (valuep)
    *valuep = value;

  return length;
}

static int
dissect_string(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset, char **strp)
{
  size_t length;
  uint64_t string_length;
  proto_tree *item;
  char *string_value;

  item = proto_tree_add_item(tree, hf_spop_string, tvb, offset, 0, ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_string);

  length = dissect_varint(tvb, tree, hf_spop_string_length, offset, 
&string_length);
  proto_tree_add_item(tree, hf_spop_string_value, tvb, offset + length, 
string_length, ENC_STRING);
 
  proto_item_set_len(item, length + string_length);

  if ((string_value = (char *)wmem_alloc(wmem_packet_scope(), string_length+1)))
  {
    tvb_memcpy(tvb, (guint8 *)string_value, offset + length, string_length);
    string_value[string_length] = '\0';
    proto_item_append_text(item, ": \"%s\"", string_value);
  }
  if (strp)
    *strp = string_value;

  return length + string_length;
}

static int
dissect_binary(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset)
{
  size_t length;
  uint64_t binary_length;

  length = dissect_varint(tvb, tree, hf_spop_integer, offset, &binary_length);

  proto_tree_add_item(tree, hf_spop_binary, tvb, offset + length, 
binary_length, ENC_NA);

  return length + binary_length;
}

static int
dissect_typed_data(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset, char 
**value)
{
  size_t length = 0;
  proto_tree *item;

  item = proto_tree_add_item(tree, hf_spop_typed_data, tvb, offset, 1, ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_typed_data);
  switch (tvb_get_guint8(tvb, offset))
  {
    case 9: /* Binary */
      length = dissect_binary(tvb, tree, offset + 1);
      break;
    case 8: /* String */
      length = dissect_string(tvb, tree, offset + 1, value);
      break;
    case 7: /* IPV6 */
      proto_tree_add_item(tree, hf_spop_ipv6, tvb, offset + 1, 16, ENC_NA);
      length = 16;
      break;
    case 6: /* IPV4 */
      proto_tree_add_item(tree, hf_spop_ipv4, tvb, offset + 1, 4, ENC_NA);
      length = 4;
      break;
    case 5: /* UINT64 */
    case 4: /* INT64 */
    case 3: /* UINT32 */
    case 2: /* INT32 */
      length = dissect_varint(tvb, tree, hf_spop_integer, offset + 1, NULL);
      break;
    case 1: /* Boolean */
      proto_tree_add_item(tree, hf_spop_boolean, tvb, offset, 1, ENC_NA);
      length = 0;
      break;
    case 0: /* NULL */
    default:
      break;
  }

  proto_item_set_len(item, length + 1);

  return length + 1;
}

static int
dissect_key_value(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset)
{
  size_t length;
  proto_tree *item;
  char *key = "-", *value = "-";

  /* <STRING> <TYPED-DATA> */

  item = proto_tree_add_item(tree, hf_spop_key_value, tvb, offset, 0, ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_key_value);

  proto_tree_add_item(tree, hf_spop_key_value_flags, tvb, offset, 1, ENC_NA);

  length = dissect_string(tvb, tree, offset, &key);
  length += dissect_typed_data(tvb, tree, offset + length, &value);

  proto_item_set_len(item, length);

  proto_item_append_text(item, ": \"%s\" = \"%s\"", key, value);

  return length;
}

static int
dissect_kvlist(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset)
{
  size_t length;
  size_t kvlist_length;
  proto_tree *item;
  int pairs;

  /* KV-LIST: [ <KV-NAME> <KV-VALUE> ... ] */

  length = tvb_captured_length(tvb);
  kvlist_length = length - offset;

  item = proto_tree_add_item(tree, hf_spop_kvlist, tvb, offset, kvlist_length, 
ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_kvlist);

  for (pairs=0; offset<length; pairs++)
    offset += dissect_key_value(tvb, tree, offset);

  proto_item_append_text(item, ": %d key value pairs", pairs);
  
  return kvlist_length;
}

static int
dissect_message(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset)
{
  size_t length;
  proto_tree *item;
  guint8 nbargs, i;

  /* <MESSAGE-NAME> <NB-ARGS:1 byte> <KV-LIST> */

  item = proto_tree_add_item(tree, hf_spop_message, tvb, offset, 0, ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_message);

  length = dissect_string(tvb, tree, offset, NULL);

  nbargs = tvb_get_guint8(tvb, offset + length);
  proto_tree_add_item(tree, hf_spop_nbargs, tvb, offset + length, 1, ENC_NA);
  length += 1;

  for (i=0; i<nbargs; i++)
    length += dissect_kvlist(tvb, tree, offset + length);

  proto_item_set_len(item, length);

  return length;
}

static int
dissect_messages(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset)
{
  size_t length;
  size_t messages_length;
  proto_tree *item;

  /* LIST-OF-MESSAGES: [ <MESSAGE-NAME> <NB-ARGS:1 byte> <KV-LIST> ... ] */
  length = tvb_captured_length(tvb);
  messages_length = length - offset;

  item = proto_tree_add_item(tree, hf_spop_messages, tvb, offset, 
messages_length, ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_messages);

  while (offset < length) 
    offset += dissect_message(tvb, tree, offset);

  return messages_length;
}

static int
dissect_action_args(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset, guint8 
nbargs)
{
  proto_tree *item;
  size_t length = 0;
  int i;

  /* ACTION-ARGS: [ <TYPED-DATA>... ] */

  item = proto_tree_add_item(tree, hf_spop_action_args, tvb, offset, 0, ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_action_args);

  proto_item_append_text(item, ": %d action args", nbargs);

  for (i=0; i<nbargs; i++)
    length += dissect_typed_data(tvb, tree, offset + length, NULL);

  proto_item_set_len(item, length);

  return length;
}

static int
dissect_action(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset)
{
  size_t length;
  guint8 type;
  guint8 nbargs;
  proto_tree *item, *args;

  /* An agent must acknowledge each NOTIFY frame by sending the
     corresponding ACK frame. Actions can be added in these frames to
     dynamically take action on the processing of a stream. */

  item = proto_tree_add_item(tree, hf_spop_action, tvb, offset, 0, ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_action);

  /* ACTION-TYPE:1 byte> <NB-ARGS:1 byte> <ACTION-ARGS> */

  proto_tree_add_item(tree, hf_spop_action_type, tvb, offset, 1, ENC_NA);
  type = tvb_get_guint8(tvb, offset);
  length = 1;

  proto_tree_add_item(tree, hf_spop_nbargs, tvb, offset + length, 1, ENC_NA);
  nbargs = tvb_get_guint8(tvb, offset + length);
  length += 1;

  args = proto_tree_add_item(tree, hf_spop_action_args, tvb, offset + length, 
0, ENC_NA);
  tree = proto_item_add_subtree(args, ett_spop_action_args);

  /* Here is the list of supported actions: */
  switch (type)
  {
    case 0x01:
      /* set-var - set the value for an existing variable. 3 arguments
         must be attached to this action: the variable scope (proc, sess,
         txn, req or req), the variable name (a string) and its value.

         ACTION-SET-VAR  : <SET-VAR:1 byte><NB-ARGS:1 byte>
                           <VAR-SCOPE:1 byte><VAR-NAME><VAR-VALUE>

         SET-VAR     : <1>
         NB-ARGS     : <3>
         VAR-SCOPE   : <PROCESS> | <SESSION> | <TRANSACTION> | <REQUEST> | 
<RESPONSE>
         VAR-NAME    : <STRING>
         VAR-VALUE   : <TYPED-DATA> */
      if (nbargs == 3)
      {
        // VAR-SCOPE
        proto_tree_add_item(tree, hf_spop_action_scope, tvb, offset + length, 
1, ENC_NA);
        length += 1;
        // VAR-NAME
        length += dissect_string(tvb, tree, offset + length, NULL);
        // VAR-VALUE
        length += dissect_typed_data(tvb, tree, offset + length, NULL);
      }
      
      break;
    case 0x02:
      /* unset-var - unset the value for an existing variable. 2
         arguments must be attached to this action: the variable scope
         (proc, sess, txn, req or req) and the variable name (a string).

         ACTION-UNSET-VAR  : <UNSET-VAR:1 byte><NB-ARGS:1 byte>
                             <VAR-SCOPE:1 byte><VAR-NAME>

         UNSET-VAR   : <2>
         NB-ARGS     : <2>
         VAR-SCOPE   : <PROCESS> | <SESSION> | <TRANSACTION> | <REQUEST> | 
<RESPONSE>
         VAR-NAME    : <STRING> */

      if (nbargs == 2)
      {
        // VAR-SCOPE
        proto_tree_add_item(tree, hf_spop_action_scope, tvb, offset + length, 
1, ENC_NA);
        length += 1;
        // VAR-NAME
        length += dissect_string(tvb, tree, offset + length, NULL);
      }
      break;
    /* default:
      error */
  }

  // length += dissect_action_args(tvb, tree, offset + length, nbargs);

  proto_item_set_len(item, length);

  return length;
}

static int
dissect_actions(tvbuff_t *tvb, proto_tree *tree _U_, size_t offset)
{
  size_t length;
  size_t actions_length;
  proto_tree *item;
  int actions;

  /* LIST-OF-ACTIONS: [ <ACTION> ... ] */

  length = tvb_captured_length(tvb);
  actions_length = length - offset;

  item = proto_tree_add_item(tree, hf_spop_actions, tvb, offset, 
actions_length, ENC_NA);
  tree = proto_item_add_subtree(item, ett_spop_actions);

  for (actions=0; offset < length; actions++)
    offset += dissect_action(tvb, tree, offset);

  proto_item_append_text(item, ": %d actions", actions);

  return actions_length;
}

static int
dissect_spop_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, 
void *data _U_)
{
  size_t offset = 0;
  proto_tree *spop_tree;
  proto_item *item;
  uint8_t frame_type;
  uint64_t stream_id;
  uint64_t frame_id;
  proto_tree *flags_tree, *ft;

  col_set_str(pinfo->cinfo, COL_PROTOCOL, "SPOP");
  /* Clear out stuff in the info column */
  col_clear(pinfo->cinfo, COL_INFO);

  item = proto_tree_add_item(tree, proto_spop, tvb, 0, -1, ENC_NA);
  spop_tree = proto_item_add_subtree(item, ett_spop);

  /* Exchange between HAProxy and agents are made using FRAME packets.
     All frames must be prefixed with their size encoded on 4 bytes in
     network byte order:

        <FRAME-LENGTH:4 bytes> <FRAME> */

  proto_tree_add_item(spop_tree, hf_spop_frame_length, tvb, offset, 4, 
ENC_BIG_ENDIAN);
  offset += 4;

  /* A frame always starts with its type, on one byte, followed by
     metadata containing flags, on 4 bytes and a two variable-length
     integer representing the stream identifier and the frame identifier
     inside the stream:

    FRAME       : <FRAME-TYPE:1 byte> <METADATA> <FRAME-PAYLOAD>
    METADATA    : <FLAGS:4 bytes> <STREAM-ID:varint> <FRAME-ID:varint> */

  frame_type = tvb_get_guint8(tvb, offset);
  proto_tree_add_item(spop_tree, hf_spop_frame_type, tvb, offset, 1, 
ENC_BIG_ENDIAN);
  offset += 1;

  proto_item_append_text(item, ": %s", val_to_str(frame_type, frame_types, 
"Unknown (0x%02x)"));
 
  ft = proto_tree_add_item(spop_tree, hf_spop_flags, tvb, offset, 4, 
ENC_BIG_ENDIAN);

  flags_tree = proto_item_add_subtree(ft, ett_spop_flags);
  proto_tree_add_item(flags_tree, hf_spop_flag_fin, tvb, offset, 1, 
ENC_BIG_ENDIAN);
  offset += 4;

  offset += dissect_varint(tvb, spop_tree, hf_spop_stream_id, offset, 
&stream_id);
  offset += dissect_varint(tvb, spop_tree, hf_spop_frame_id, offset, &frame_id);
  col_add_fstr(pinfo->cinfo, COL_INFO, /* "FRAME-TYPE: */ "%s STREAM-ID:%lu 
FRAME-ID:%lu", val_to_str(frame_type, frame_types, "0x%02x"), stream_id, 
frame_id);

  /* Then comes the frame payload. Depending on the frame type, the
     payload can be of three types: a simple key/value list, a list of
     messages or a list of actions.

    FRAME-PAYLOAD    : <LIST-OF-MESSAGES> | <LIST-OF-ACTIONS> | <KV-LIST>

    LIST-OF-MESSAGES : [ <MESSAGE-NAME> <NB-ARGS:1 byte> <KV-LIST> ... ]
    MESSAGE-NAME     : <STRING>

    LIST-OF-ACTIONS  : [ <ACTION-TYPE:1 byte> <NB-ARGS:1 byte> <ACTION-ARGS> 
... ]
    ACTION-ARGS      : [ <TYPED-DATA>... ]

    KV-LIST          : [ <KV-NAME> <KV-VALUE> ... ]
    KV-NAME          : <STRING>
    KV-VALUE         : <TYPED-DATA> */

  switch (frame_type)
  {
    case   1: /* "HAPROXY-HELLO" */
    case   2: /* "HAPROXY-DISCONNECT" */
    case 101: /* "AGENT-HELLO" */
    case 102: /* "AGENT-DISCONNECT" */
      offset += dissect_kvlist(tvb, spop_tree, offset);
      break;

    case   3: /* "NOTIFY" */
      offset += dissect_messages(tvb, spop_tree, offset);
      break;

    case 103: /* "ACK" */
      offset += dissect_actions(tvb, spop_tree, offset);
      break;

    default:
                fprintf(stderr, "%s: What?! %i\n", __func__, frame_type);
      /* Panic and run! */
      break;
  }

  return tvb_captured_length(tvb);
}

/* determine PDU length of spo protocol */
static guint
get_spop_frame_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void 
*data _U_)
{
    return (guint)tvb_get_ntohl(tvb, offset) + 4;
}

/* The main dissecting routine */
static int
dissect_spop(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
    tcp_dissect_pdus(tvb, pinfo, tree, TRUE, 4,
                     get_spop_frame_len, dissect_spop_frame, data);
    return tvb_captured_length(tvb);
}

void
proto_reg_handoff_spop(void)
{
    static dissector_handle_t spop_handle;

    spop_handle = create_dissector_handle(dissect_spop, proto_spop);
    dissector_add_uint("tcp.port", SPOP_PORT, spop_handle);
}

Reply via email to