Hi Fred,
thanks for looking into this, I've fixed this issue (and introduced some new :-))
The attached dissector code parses all SPOP frames sent from contrib/spoa_sample and haproxy and handles some fragmentation scenarios.
Kind regards,
Danny
Gesendet: Sonntag, 19. November 2017 um 12:25 Uhr
Von: "Frederic Lecaille" <[email protected]>
An: [email protected], [email protected]
Betreff: Re: Aw: Re: [RFC] Wireshark dissector for SPOP
Von: "Frederic Lecaille" <[email protected]>
An: [email protected], [email protected]
Betreff: Re: Aw: Re: [RFC] Wireshark dissector for SPOP
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, [email protected] 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" <[email protected]>
> *An:* [email protected], [email protected]
> *Betreff:* Re: [RFC] Wireshark dissector for SPOP
> On 11/05/2017 09:27 AM, [email protected] 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@[email protected]......
> > 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.
So I had a look at this issue which is easily reproducible.
See my answer below.
On 11/12/2017 07:43 PM, [email protected] 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" <[email protected]>
> *An:* [email protected], [email protected]
> *Betreff:* Re: [RFC] Wireshark dissector for SPOP
> On 11/05/2017 09:27 AM, [email protected] 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@[email protected]......
> > 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.
/* * Wireshark dissector for SPOP * * Copyright 2017 Daniela Sonnenschein <[email protected]> * * 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);
}

