Welcome! Log In Create A New Profile

Advanced

[RFC] Wireshark dissector for SPOP

Posted by Anonymous User 
Anonymous User
[RFC] Wireshark dissector for SPOP
November 05, 2017 09:40AM
/*
* Wireshark dissector for SPOP
*
* Copyright 2017 Daniela Sonnenschein <my.card.god@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_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_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 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_BOOLEAN_TRUE 0x08

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_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, NULL, 0x0, NULL, HFILL }
},
{ &hf_spop_action_args,
{ "ACTION-ARGS", "spop.payload.action.args",
FT_NONE, BASE_NONE, NULL, 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_BOOLEAN_TRUE, 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)
{
size_t length;
uint64_t string_length;
proto_tree *item;

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);

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)
{
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);
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;

/* <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);

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

proto_item_set_len(item, length);

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);

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);

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 nbargs;
proto_tree *item;

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);
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;

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);
}
Frederic Lecaille
Re: [RFC] Wireshark dissector for SPOP
November 09, 2017 04:20PM
On 11/05/2017 09:27 AM, My.Card.God@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.
Anonymous User
Aw: Re: [RFC] Wireshark dissector for SPOP
November 12, 2017 07:50PM
/*
* Wireshark dissector for SPOP
*
* Copyright 2017 Daniela Sonnenschein <my.card.god@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);
}
Frederic Lecaille
Re: Aw: Re: [RFC] Wireshark dissector for SPOP
November 19, 2017 12:30PM
Hi Danny,

So I had a look at this issue which is easily reproducible.

See my answer below.

On 11/12/2017 07:43 PM, My.Card.God@web.de wrote:
> 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" <flecaille@haproxy.com>
> *An:* My.Card.God@web.de, haproxy@formilux.org
> *Betreff:* Re: [RFC] Wireshark dissector for SPOP
> On 11/05/2017 09:27 AM, My.Card.God@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

"Malformed Packet"s are announced by wireshark when parsing ACK SPOP frames.

According to 3.4 paragraph of SPOP documentation, such frames are made
of actions, with 3 arguments here when using ACTION-SET-VAR action only:

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

The hexadecimal dump of this action here is:

01 03 01 08 69 70 5f 73 63 6f 72 65 03 47

which must be decomposed as follows:

01 -> SET-VAR
03 -> NB-ARGS
01 -> VAR-SCOPE (1st argument)
08 69 70 5f 73 63 6f 72 65 -> VAR-NAME (2nd argument)
03 47 -> VAR-VALUE (3rd argument)

Here VAR-NAME is a STRING field made of an encoded length -> 0x08
followed by the non null terminated string -> 69 70 5f 73 63 6f 72 65
(ip_score).

*But* note that these arguments are not 3 TYPED-DATA fields.
Only the last one VAR-VALUE argument is typed, so prefixed by a unique
byte for the type (0x03 here -> UINT32).

So dissect_action_args() should not be made of a loop like this:

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

At this time, your dissector considers VAR-SCOPE argument as a BOOL
field (false) and VAR-NAME as a 0x69 bytes long STRING field (with 0x08
as TYPED-DATA field ID).

I hope this will help to finalize your dissector which could be added to
"contrib/wireshark-dissectors/spop" directory.


Regards.

Fred.
Anonymous User
Re: [RFC] Wireshark dissector for SPOP
November 20, 2017 10:20PM
/*
* Wireshark dissector for SPOP
*
* Copyright 2017 Daniela Sonnenschein <my.card.god@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 <epan/expert.h>
#include <epan/reassemble.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_flag_abort = -1;
static int hf_spop_flag_reserved = -1;
static int hf_spop_frame_id = -1;
static int hf_spop_stream_id = -1;
static int hf_spop_frame_payload = -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 int hf_spop_fragments = -1;
static int hf_spop_fragment = -1;
static int hf_spop_fragment_overlap = -1;
static int hf_spop_fragment_overlap_conflicts = -1;
static int hf_spop_fragment_multiple_tails = -1;
static int hf_spop_fragment_too_long_fragment = -1;
static int hf_spop_fragment_error = -1;
static int hf_spop_fragment_count = -1;
static int hf_spop_reassembled_in = -1;
static int hf_spop_reassembled_length = -1;
static int hf_spop_reassembled_data = -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;

static gint ett_spop_fragment = -1;
static gint ett_spop_fragments = -1;

static expert_field ei_spop_invalid_fragmentation = EI_INIT;

static const fragment_items spop_frag_items = {
/* Fragment subtrees */
&ett_spop_fragment,
&ett_spop_fragments,
/* Fragment fields */
&hf_spop_fragments,
&hf_spop_fragment,
&hf_spop_fragment_overlap,
&hf_spop_fragment_overlap_conflicts,
&hf_spop_fragment_multiple_tails,
&hf_spop_fragment_too_long_fragment,
&hf_spop_fragment_error,
&hf_spop_fragment_count,
/* Reassembled in field */
&hf_spop_reassembled_in,
/* Reassemled length field */
&hf_spop_reassembled_length,
&hf_spop_reassembled_data,
/* Tag */
"Message fragments"
};

/* FLAGS : 0 1 2-31
+---+--------------+
| | A | |
| F | B | |
| I | O | RESERVED |
| N | R | |
| | T | |
+---+--------------+ */

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

/* ABORT: Indicates that the processing of the current frame must be
cancelled. This bit should be set on frames with a fragmented
payload. It can be ignore for frames with an unfragmented
payload. When it is set, the FIN bit must also be set. */
#define SPOP_FLAG_ABORT 0x00000002

#define SPOP_FLAG_RESERVED 0xfffffffc

/* Used for all frames but the firest when a payload is fragmented. */
#define SPOP_FRAME_TYPE_UNSET 0

/* Sent by HAproxy when it opens a connection to an agent */
#define SPOP_FRAME_TYPE_HAPROXY_HELLO 1

/* Sent by HAproxy when it want to close the connection or in reply to
an AGENT-DISCONNECT frame */
#define SPOP_FRAME_TYPE_HAPROXY_DISCONNECT 2

/* Sent by HAproxy to pass information to an agent */
#define SPOP_FRAME_TYPE_NOTIFY 3

/* Reply to a HAPROXY-HELLO frame, when the connection is established */
#define SPOP_FRAME_TYPE_AGENT_HELLO 101

/* Sent by an agent just before closing the connection */
#define SPOP_FRAME_TYPE_AGENT_DISCONNECT 102

/* Sent to acknowledge a NOTIFY frame */
#define SPOP_FRAME_TYPE_ACK 103

static const value_string frame_types[] = {
{ SPOP_FRAME_TYPE_UNSET, "UNSET" },
{ SPOP_FRAME_TYPE_HAPROXY_HELLO, "HAPROXY-HELLO" },
{ SPOP_FRAME_TYPE_HAPROXY_DISCONNECT, "HAPROXY-DISCONNECT" },
{ SPOP_FRAME_TYPE_NOTIFY, "NOTIFY" },
{ SPOP_FRAME_TYPE_AGENT_HELLO, "AGENT-HELLO" },
{ SPOP_FRAME_TYPE_AGENT_DISCONNECT, "AGENT-DISCONNECT" },
{ SPOP_FRAME_TYPE_ACK, "ACK" },
{ 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

static reassembly_table spop_reassembly_table;

static void
spop_init(void)
{
reassembly_table_init(&spop_reassembly_table,
&addresses_ports_reassembly_table_functions);
//&addresses_reassembly_table_functions);
}

static void
spop_cleanup(void)
{
reassembly_table_destroy(&spop_reassembly_table);
}

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, 32, NULL, SPOP_FLAG_FIN, NULL, HFILL }
},
{ &hf_spop_flag_abort,
{ "ABORT Flag", "spop.flags.abort",
FT_BOOLEAN, 32, NULL, SPOP_FLAG_ABORT, NULL, HFILL }
},
{ &hf_spop_flag_reserved,
{ "RESERVED", "spop.flags.reserved",
FT_BOOLEAN, 32, NULL, SPOP_FLAG_RESERVED, 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_frame_payload,
{ "Frame Payload", "spop.frame.payload",
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 }
},
/* Fragmentation */
{ &hf_spop_fragments,
{ "Message fragments", "spop.fragments",
FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_fragment,
{ "Message fragment", "spop.fragment",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_fragment_overlap,
{ "Message fragment overlap", "spop.fragment.overlap",
FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_fragment_overlap_conflicts,
{ "Message fragment overlapping with conflicting data", "spop.fragment.overlap.conflicts",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_fragment_multiple_tails,
{ "Message has multiple tail fragments", "spop.fragment.multiple_tails",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_fragment_too_long_fragment,
{ "Message fragment too long", "spop.fragment.too_long_fragment",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_fragment_error,
{ "Message defragmentation error", "spop.fragment.error",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_fragment_count,
{ "Message fragment count", "spop.fragment.count",
FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_reassembled_in,
{ "Reassembled in", "spop.reassembled.in",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_reassembled_length,
{ "Reassembled length", "spop.reassembled.length",
FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL }
},
{ &hf_spop_reassembled_data,
{ "Reassembled data", "spop.reassembled.data",
FT_BYTES, BASE_NONE, NULL, 0x00, 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,
&ett_spop_fragment,
&ett_spop_fragments
};

static ei_register_info ei[] = {
{ &ei_spop_invalid_fragmentation, { "spop.fragmentation.invalid", PI_REASSEMBLE, PI_ERROR, "Fragmentation invalid", EXPFILL }}
};
expert_module_t *expert_spop;

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));

register_init_routine(spop_init);
register_cleanup_routine(spop_cleanup);

expert_spop = expert_register_protocol(proto_spop);
expert_register_field_array(expert_spop, ei, array_length(ei));
}

/* 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;
}

if (tree)
{
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, guint8 nbargs)
{
size_t length;
proto_tree *item;
guint8 pairs;

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

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

if (nbargs != 0)
{
for (length=0, pairs=0; pairs<nbargs; pairs++)
length += dissect_key_value(tvb, tree, offset + length);
}
else
{
length = tvb_captured_length(tvb) - offset;
for (pairs=0; offset<length; pairs++)
offset += dissect_key_value(tvb, tree, offset);
}

proto_item_append_text(item, ": %d key value pairs", pairs);
proto_item_set_len(item, length);

return length;
}

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

/* <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;

length += dissect_kvlist(tvb, tree, offset + length, nbargs);

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> ... ] */
// ??? messages_length = tvb_reported_length_remaining(tvb, offset);
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> ... ] */

// ??? actions_length = tvb_reported_length_remaining(tvb, offset);
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 gint
dissect_spop_payload(tvbuff_t *tvb, guint8 frame_type, gint offset, proto_tree *tree _U_)
{
// size_t offset = 0;

/* 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 SPOP_FRAME_TYPE_HAPROXY_HELLO:
case SPOP_FRAME_TYPE_HAPROXY_DISCONNECT:
case SPOP_FRAME_TYPE_AGENT_HELLO:
case SPOP_FRAME_TYPE_AGENT_DISCONNECT:
offset += dissect_kvlist(tvb, tree, offset, 0);
break;

case SPOP_FRAME_TYPE_NOTIFY:
offset += dissect_messages(tvb, tree, offset);
break;

case SPOP_FRAME_TYPE_ACK:
offset += dissect_actions(tvb, tree, offset);
break;

case SPOP_FRAME_TYPE_UNSET:
/* frame_type from first frame wasn't preserved correctly */

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

static proto_tree *
dissect_spop_header(tvbuff_t *tvb, packet_info *pinfo, guint8 *frame_type_out, gboolean reassembled, proto_tree *tree _U_, void *data _U_)
{
gint offset = 0;
proto_tree *spop_tree;
proto_item *item;
guint8 frame_type;
uint64_t stream_id;
uint64_t frame_id;
gboolean last_fragment;
guint8 flags_0_value;

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> */

if (!reassembled)
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);
if (frame_type_out)
*frame_type_out = frame_type;

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)"));

flags_0_value = tvb_get_guint8(tvb, offset);
last_fragment = (flags_0_value & SPOP_FLAG_FIN);

if (!reassembled)
{
proto_tree *flags_tree, *ft;
ft = proto_tree_add_item(spop_tree, hf_spop_flags, tvb, offset, 4, ENC_LITTLE_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_LITTLE_ENDIAN);
proto_tree_add_item(flags_tree, hf_spop_flag_abort, tvb, offset, 1, ENC_LITTLE_ENDIAN);
proto_tree_add_item(flags_tree, hf_spop_flag_reserved, tvb, offset, 4, ENC_LITTLE_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);

if (!last_fragment && !reassembled)
{
switch (frame_type)
{
case SPOP_FRAME_TYPE_AGENT_HELLO:
case SPOP_FRAME_TYPE_AGENT_DISCONNECT:
case SPOP_FRAME_TYPE_HAPROXY_HELLO:
case SPOP_FRAME_TYPE_HAPROXY_DISCONNECT:
expert_add_info_format(pinfo, spop_tree, &ei_spop_invalid_fragmentation, "Frame must not be fragmented");
break;
}
}
proto_tree_add_item(spop_tree, hf_spop_frame_payload, tvb, offset, tvb_reported_length_remaining(tvb, offset), ENC_BIG_ENDIAN);

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);

return spop_tree;
}

static int
dissect_spop_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_)
{
size_t offset = 0;
guint8 frame_type;
guint8 flags_0_value;
proto_tree *spop_tree = NULL;
uint64_t stream_id, frame_id;
fragment_head *fd_head = NULL;
tvbuff_t *next_tvb = NULL;
gboolean last_fragment;
guint len, start;

offset += 4; /* Skip length */

frame_type = tvb_get_guint8(tvb, offset);
offset += 1;

flags_0_value = tvb_get_guint8(tvb, offset);
last_fragment = (flags_0_value & SPOP_FLAG_FIN);
offset += 4;

/* Frames cannot exceed a maximum size negociated between HAProxy and
agents during the HELLO handshake. Most of time, payload will be
small enough to send it in one frame. But when supported by the
peer, it will be possible to fragment huge payload on many frames.
This ability is announced during the HELLO handshake and it can be
asynmetric (supported by agents but not by HAProxy or the opposite).
The following rules apply to fragmentation:

* An unfragemnted payload consists of a single frame with the
FIN bit set.
* A fragemented payload consists of several frames with the FIN
bit clear and terminated by a single frame with the FIN bit set.
All these frames must share the same STREAM-ID and FRAME-ID.
The first frame must set the right FRAME-TYPE (e.g, NOTIFY).
The following frames must have an unset type (0).

Beside the support of fragmented payload by a peer, some payload
must not be fragmented. See below for details.

IMPORTANT : The maximum size supported by peers for a frame must
be greater than or equal to 256 bytes. */

offset += dissect_varint(tvb, NULL, -1, offset, &stream_id);
offset += dissect_varint(tvb, NULL, -1, offset, &frame_id);

if (frame_type == SPOP_FRAME_TYPE_UNSET)
start = offset;
else
start = 0;
len = tvb_reported_length_remaining(tvb, start);

spop_tree = dissect_spop_header(tvb, pinfo, NULL, FALSE, tree, data);

fd_head = fragment_add_seq_next(&spop_reassembly_table,
tvb, start, pinfo, frame_id,
NULL, len, !last_fragment);
if (fd_head)
{
// spop_tree = dissect_spop_header(tvb, pinfo, NULL, FALSE, tree, data);
if (!last_fragment || frame_type == SPOP_FRAME_TYPE_UNSET)
col_append_fstr(pinfo->cinfo, COL_INFO, " (Message fragment)");

next_tvb = process_reassembled_data(tvb, 0, pinfo,
"Reassembled SPOP", fd_head,
&spop_frag_items, NULL, tree);
if (next_tvb != NULL)
{
if (frame_type == SPOP_FRAME_TYPE_UNSET)
{
spop_tree = dissect_spop_header(next_tvb, pinfo, &frame_type, TRUE, tree, data);
col_append_fstr(pinfo->cinfo, COL_INFO, " (Message reassembled)");
}
dissect_spop_payload(next_tvb, frame_type, offset, spop_tree);
}
}
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);
}
Sorry, only registered users may post in this forum.

Click here to login