From 261c2f24cc08fa49504e93ca03381bb1aa9e19e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markku=20Leini=C3=B6?= Date: Sat, 8 Jul 2023 22:59:21 +0300 Subject: Add Zabbix protocol dissector Features: - Supports also compressed and TLS-encrypted Zabbix connections as well as TCP desegmenting - Dissects both passive agent connections (10050/tcp, plaintext-based) and active agent, proxy and sender/trapper connections (10051/tcp, JSON-based), ports are configurable - Detects passive agent conversations by checking the request being non-JSON (not depending on the well-known TCP ports) - Calculates response times using protocol data saved in conversations - Detects the connection type (proxy, agent, sender/trapper) and shows tree and Info column information accordingly - Dissects protocols up to Zabbix version 6.4 (currently latest) and 7.0 (currently in alpha) - Does not support passive agent connections in Zabbix 3.x or earlier (it does not have the normal Zabbix header; note that Zabbix 4.0 was released in 2018) --- epan/dissectors/CMakeLists.txt | 1 + epan/dissectors/packet-zabbix.c | 1107 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1108 insertions(+) create mode 100644 epan/dissectors/packet-zabbix.c (limited to 'epan') diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 1bf4cebc45..2880485207 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -2087,6 +2087,7 @@ set(DISSECTOR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/packet-ypserv.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-ypxfr.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-z3950.c + ${CMAKE_CURRENT_SOURCE_DIR}/packet-zabbix.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-zbee-aps.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-zbee-nwk.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-zbee-nwk-gp.c diff --git a/epan/dissectors/packet-zabbix.c b/epan/dissectors/packet-zabbix.c new file mode 100644 index 0000000000..ccc2c1b799 --- /dev/null +++ b/epan/dissectors/packet-zabbix.c @@ -0,0 +1,1107 @@ +/* packet-zabbix.c + * Routines for Zabbix protocol dissection + * Copyright 2023, Markku Leiniƶ + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Zabbix protocol specifications can be found in Zabbix documentation: + * https://www.zabbix.com/documentation/current/en/manual/appendix/protocols + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include "packet-tcp.h" + +void proto_register_zabbix(void); +void proto_reg_handoff_zabbix(void); + +static dissector_handle_t zabbix_handle; + +/* Desegmentation of Zabbix protocol over TCP */ +static bool zabbix_desegment = true; + +/* Initialize the protocol and registered fields */ +static int proto_zabbix = -1; +static int hf_zabbix_header = -1; +static int hf_zabbix_flags = -1; +static int hf_zabbix_flag_zabbix_communications = -1; +static int hf_zabbix_flag_compressed = -1; +static int hf_zabbix_flag_largepacket = -1; +static int hf_zabbix_flag_reserved = -1; +static int hf_zabbix_length = -1; +static int hf_zabbix_reserved = -1; +static int hf_zabbix_uncompressed_length = -1; +static int hf_zabbix_large_length = -1; +static int hf_zabbix_large_reserved = -1; +static int hf_zabbix_large_uncompressed_length = -1; +static int hf_zabbix_data = -1; +static int hf_zabbix_time = -1; +static int hf_zabbix_agent = -1; +static int hf_zabbix_agent_config = -1; +static int hf_zabbix_agent_data = -1; +static int hf_zabbix_agent_passive = -1; +static int hf_zabbix_agent_name = -1; +static int hf_zabbix_agent_hb = -1; +static int hf_zabbix_agent_hb_freq = -1; +static int hf_zabbix_agent_hostmetadata = -1; +static int hf_zabbix_agent_hostinterface = -1; +static int hf_zabbix_agent_listenipv4 = -1; +static int hf_zabbix_agent_listenipv6 = -1; +static int hf_zabbix_agent_listenport = -1; +static int hf_zabbix_proxy = -1; +static int hf_zabbix_proxy_hb = -1; +static int hf_zabbix_proxy_name = -1; +static int hf_zabbix_proxy_data = -1; +static int hf_zabbix_proxy_config = -1; +static int hf_zabbix_proxy_fullsync = -1; +static int hf_zabbix_proxy_incr_config = -1; +static int hf_zabbix_proxy_no_config_change = -1; +static int hf_zabbix_sender = -1; +static int hf_zabbix_sender_name = -1; +static int hf_zabbix_request = -1; +static int hf_zabbix_response = -1; +static int hf_zabbix_success = -1; +static int hf_zabbix_failed = -1; +static int hf_zabbix_config_revision = -1; +static int hf_zabbix_session = -1; +static int hf_zabbix_version = -1; + +/* Initialize the subtree pointers */ +static int ett_zabbix = -1; + +/* Initialize expert fields */ +static expert_field ei_zabbix_packet_too_large = EI_INIT; +static expert_field ei_zabbix_json_error = EI_INIT; + +/* Other dissector-specifics */ +static range_t *zabbix_port_range; + +static const guint8 ZABBIX_HDR_SIGNATURE[] = "ZBXD"; +static const char ZABBIX_UNKNOWN[] = ""; + +typedef struct _zabbix_conv_info_t { + guint32 req_framenum; + nstime_t req_timestamp; + uint16_t oper_flags; /* ZABBIX_T_XXX macros below */ + const guint8 *host_name; +} zabbix_conv_info_t; + +#define ZABBIX_HDR_MIN_LEN 13 /* When not large packet */ +#define ZABBIX_HDR_MAX_LEN 21 /* When large packet */ +#define ZABBIX_MAX_LENGTH_ALLOWED 1024*1024*1024 /* 1 GB */ +#define ZABBIX_TCP_PORTS "10050,10051" /* IANA registered ports */ + +#define ZABBIX_FLAG_ZABBIX_COMMUNICATIONS 0x01 +#define ZABBIX_FLAG_COMPRESSED 0x02 +#define ZABBIX_FLAG_LARGEPACKET 0x04 +#define ZABBIX_FLAG_RESERVED 0xf8 + +/* Response flags are not saved in the conversations */ +#define ZABBIX_RESPONSE_SUCCESS 0x01 +#define ZABBIX_RESPONSE_FAILED 0x02 +#define ZABBIX_RESPONSE_FULLSYNC 0x04 +#define ZABBIX_RESPONSE_INCREMENTAL 0x08 +#define ZABBIX_RESPONSE_NOCHANGE 0x10 + +/* Flags for saving and comparing operation types, + * max 16 bits as defined in zabbix_conv_info_t above */ +#define ZABBIX_T_REQUEST 0x00000001 /* Not set for heartbeats */ +#define ZABBIX_T_RESPONSE 0x00000002 +#define ZABBIX_T_ACTIVE 0x00000004 +#define ZABBIX_T_PASSIVE 0x00000008 +#define ZABBIX_T_AGENT 0x00000010 +#define ZABBIX_T_PROXY 0x00000020 +#define ZABBIX_T_SENDER 0x00000040 +#define ZABBIX_T_CONFIG 0x00000080 +#define ZABBIX_T_DATA 0x00000100 +#define ZABBIX_T_HEARTBEAT 0x00000200 + +#define ADD_ZABBIX_T_FLAGS(flags) (zabbix_info->oper_flags |= (flags)) +#define CLEAR_ZABBIX_T_FLAGS(flags) (zabbix_info->oper_flags &= (0xffff-(flags))) +#define IS_ZABBIX_T_FLAGS(flags) ((zabbix_info->oper_flags & (flags)) == (flags)) + +#define CONV_IS_ZABBIX_REQUEST(zabbix_info,pinfo) ((zabbix_info)->req_framenum == (pinfo)->fd->num) +#define CONV_IS_ZABBIX_RESPONSE(zabbix_info,pinfo) ((zabbix_info)->req_framenum != (pinfo)->fd->num) + + +static zabbix_conv_info_t* +zabbix_find_conversation_and_get_conv_data(packet_info *pinfo) +{ + conversation_t *conversation; + zabbix_conv_info_t *zabbix_info = NULL; + + conversation = find_conversation_pinfo(pinfo, 0); + if (conversation) { + zabbix_info = (zabbix_conv_info_t *)conversation_get_proto_data(conversation, proto_zabbix); + } else { + conversation = conversation_new(pinfo->num, &pinfo->src, &pinfo->dst, + conversation_pt_to_conversation_type(pinfo->ptype), + pinfo->srcport, pinfo->destport, 0); + } + if (!zabbix_info) { + /* New conversation, or there was no Zabbix data yet in the existing conv */ + zabbix_info = wmem_alloc(wmem_file_scope(), sizeof(zabbix_conv_info_t)); + if (value_is_in_range(zabbix_port_range, pinfo->destport)) { + /* Let's assume this is the first Zabbix packet (request) */ + zabbix_info->req_framenum = pinfo->fd->num; + zabbix_info->req_timestamp = pinfo->abs_ts; + } + else { + /* For any reason we didn't have Zabbix data yet but this is not + * the first packet for the connection, so don't save it as a request + */ + zabbix_info->req_framenum = 0; + nstime_set_unset(&zabbix_info->req_timestamp); + /* For some reason this produces "syntax error: '{'" when compiling: + zabbix_info->req_timestamp = NSTIME_INIT_UNSET; + */ + } + zabbix_info->oper_flags = 0; + zabbix_info->host_name = NULL; + conversation_add_proto_data(conversation, proto_zabbix, (void *)zabbix_info); + } + return zabbix_info; +} + +static void +zabbix_add_expert_info_if_too_large(packet_info *pinfo, proto_tree *tree_item, + uint64_t length, bool *is_too_large) +{ + if (length > ZABBIX_MAX_LENGTH_ALLOWED) { + expert_add_info(pinfo, tree_item, &ei_zabbix_packet_too_large); + *is_too_large = true; + } + return; +} + +static int +dissect_zabbix_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) +{ + int offset = 0; + int agent_hb_freq = 0; + unsigned oper_response = 0; + proto_item *ti; + proto_item *pi; + proto_tree *temp_ti; + proto_tree *zabbix_tree = NULL; + uint8_t flags; + uint16_t agent_listenport = 0; + uint64_t length; + uint64_t uncompressed_length; + uint64_t datalen; + int64_t config_revision = -1; + bool is_compressed; + bool is_large_packet; + bool is_too_large = false; + char *json_str; + jsmntok_t *data_array = NULL; + jsmntok_t *data_object = NULL; + const char *agent_name = NULL; + const char *agent_hostmetadata = NULL; + const char *agent_hostinterface = NULL; + const char *agent_listenip = NULL; + const char *proxy_name = NULL; + const char *sender_name = NULL; + const char *session = NULL; + const char *request_type = NULL; + const char *response_status = NULL; + const char *version = NULL; + gdouble temp_double; + tvbuff_t *next_tvb; + zabbix_conv_info_t *zabbix_info; + static int* const flagbits[] = { + &hf_zabbix_flag_reserved, + &hf_zabbix_flag_largepacket, + &hf_zabbix_flag_compressed, + &hf_zabbix_flag_zabbix_communications, + NULL + }; + + /* Make entries in Protocol column and Info column on summary display */ + col_set_str(pinfo->cinfo, COL_PROTOCOL, "Zabbix"); + col_clear(pinfo->cinfo, COL_INFO); + + if ((tvb_reported_length(tvb) < ZABBIX_HDR_MIN_LEN) || + (tvb_memeql(tvb, offset, ZABBIX_HDR_SIGNATURE, 4) == -1)) { + /* Encrypted or not Zabbix at all */ + return 0; + } + flags = tvb_get_guint8(tvb, offset+4); + if (!(flags & ZABBIX_FLAG_ZABBIX_COMMUNICATIONS)) { + return 0; + } + + zabbix_info = zabbix_find_conversation_and_get_conv_data(pinfo); + + is_compressed = (flags & ZABBIX_FLAG_COMPRESSED) > 0; + is_large_packet = (flags & ZABBIX_FLAG_LARGEPACKET) > 0; + + /* create display subtree for the protocol */ + ti = proto_tree_add_item(tree, proto_zabbix, tvb, 0, -1, ENC_NA); + zabbix_tree = proto_item_add_subtree(ti, ett_zabbix); + proto_tree_add_item(zabbix_tree, hf_zabbix_header, tvb, offset, 4, ENC_UTF_8); + offset += 4; + proto_tree_add_bitmask(zabbix_tree, tvb, offset, hf_zabbix_flags, ett_zabbix, flagbits, ENC_BIG_ENDIAN); + offset += 1; + if (is_large_packet) { + /* 8-byte values */ + temp_ti = proto_tree_add_item_ret_uint64(zabbix_tree, + hf_zabbix_large_length, tvb, offset, 8, ENC_LITTLE_ENDIAN, &length); + zabbix_add_expert_info_if_too_large(pinfo, temp_ti, length, &is_too_large); + offset += 8; + if (is_compressed) { + temp_ti = proto_tree_add_item_ret_uint64(zabbix_tree, + hf_zabbix_large_uncompressed_length, tvb, offset, 8, ENC_LITTLE_ENDIAN, &uncompressed_length); + zabbix_add_expert_info_if_too_large(pinfo, temp_ti, uncompressed_length, &is_too_large); + } else { + proto_tree_add_item(zabbix_tree, hf_zabbix_large_reserved, tvb, offset, 8, ENC_LITTLE_ENDIAN); + } + offset += 8; + } else { + /* 4-byte values */ + uint32_t temp_uint32; + temp_ti = proto_tree_add_item_ret_uint(zabbix_tree, + hf_zabbix_length, tvb, offset, 4, ENC_LITTLE_ENDIAN, &temp_uint32); + length = (uint64_t)temp_uint32; + zabbix_add_expert_info_if_too_large(pinfo, temp_ti, length, &is_too_large); + offset += 4; + if (is_compressed) { + temp_ti = proto_tree_add_item_ret_uint(zabbix_tree, + hf_zabbix_uncompressed_length, tvb, offset, 4, ENC_LITTLE_ENDIAN, &temp_uint32); + uncompressed_length = (uint64_t)temp_uint32; + zabbix_add_expert_info_if_too_large(pinfo, temp_ti, uncompressed_length, &is_too_large); + } else { + proto_tree_add_item(zabbix_tree, hf_zabbix_reserved, tvb, offset, 4, ENC_LITTLE_ENDIAN); + } + offset += 4; + } + if (is_too_large) { + /* Set next_tvb for response time calculation to work later */ + next_tvb = tvb_new_subset_remaining(tvb, offset); + /* ... but don't do any content-based inspection, just skip to the end */ + goto final_outputs; + } else if (is_compressed) { + next_tvb = tvb_uncompress(tvb, offset, tvb_reported_length_remaining(tvb, offset)); + if (next_tvb) { + tvb_set_child_real_data_tvbuff(tvb, next_tvb); + add_new_data_source(pinfo, next_tvb, "Uncompressed data"); + datalen = uncompressed_length; + } else { + /* Handle uncompressed */ + next_tvb = tvb_new_subset_remaining(tvb, offset); + datalen = length; + } + } else { + next_tvb = tvb_new_subset_remaining(tvb, offset); + datalen = length; + } + + /* Use only next_tvb and datalen for data extraction from here on! */ + offset = 0; + + /* Rewrite the default texts in the protocol tree and initialize request/response flags */ + if (CONV_IS_ZABBIX_REQUEST(zabbix_info, pinfo)) { + proto_item_set_text(ti, "Zabbix Protocol request"); + ADD_ZABBIX_T_FLAGS(ZABBIX_T_REQUEST); + CLEAR_ZABBIX_T_FLAGS(ZABBIX_T_RESPONSE); + } + else if (CONV_IS_ZABBIX_RESPONSE(zabbix_info, pinfo)) { + proto_item_set_text(ti, "Zabbix Protocol response"); + ADD_ZABBIX_T_FLAGS(ZABBIX_T_RESPONSE); + CLEAR_ZABBIX_T_FLAGS(ZABBIX_T_REQUEST); + } + + /* + * Note that json_str is modified when using json_get_xxx() functions below! + * So don't use it to anything else (make a wmem_strdup() if needed) + */ + json_str = tvb_get_string_enc(pinfo->pool, next_tvb, offset, (int)datalen, ENC_UTF_8); + if (CONV_IS_ZABBIX_REQUEST(zabbix_info, pinfo) && !json_validate(json_str, datalen)) { + /* The only non-JSON Zabbix request is passive agent, update the conversation data */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_AGENT | ZABBIX_T_PASSIVE); + } + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_AGENT | ZABBIX_T_PASSIVE)) { + if (CONV_IS_ZABBIX_REQUEST(zabbix_info, pinfo)) { + proto_item_set_text(ti, "Zabbix Passive agent request"); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Passive agent request"); + } else if (CONV_IS_ZABBIX_RESPONSE(zabbix_info, pinfo)) { + proto_item_set_text(ti, "Zabbix Passive agent response"); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Passive agent response"); + } + /* Don't do content-based searches for passive agents */ + goto show_agent_outputs; + } + /* Parse JSON, first get the token count */ + int token_count = json_parse(json_str, NULL, 0); + if (token_count <= 0) { + temp_ti = proto_tree_add_item(zabbix_tree, hf_zabbix_data, next_tvb, 0, (int)datalen, ENC_UTF_8); + expert_add_info_format(pinfo, temp_ti, &ei_zabbix_json_error, "Error in initial JSON parse"); + goto final_outputs; + } + jsmntok_t *tokens = wmem_alloc_array(pinfo->pool, jsmntok_t, token_count); + int ret = json_parse(json_str, tokens, token_count); + if (ret <= 0) { + temp_ti = proto_tree_add_item(zabbix_tree, hf_zabbix_data, next_tvb, 0, (int)datalen, ENC_UTF_8); + expert_add_info_format(pinfo, temp_ti, &ei_zabbix_json_error, "Error parsing JSON tokens"); + goto final_outputs; + } + + /* + * Now we have JSON tokens analyzed, let's do all the logic to populate the fields. + * Also set Zabbix tree item and Info column texts, Len= and ports will be added later below. + */ + + /* First populate common fields */ + version = json_get_string(json_str, tokens, "version"); + session = json_get_string(json_str, tokens, "session"); + if (json_get_double(json_str, tokens, "config_revision", &temp_double)) { + config_revision = (int64_t)temp_double; + } + request_type = json_get_string(json_str, tokens, "request"); + response_status = json_get_string(json_str, tokens, "response"); + data_array = json_get_array(json_str, tokens, "data"); + data_object = json_get_object(json_str, tokens, "data"); + if (request_type) { + if (strcmp(request_type, "active checks") == 0) { + /* Active agent requesting configs */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_AGENT | ZABBIX_T_CONFIG | ZABBIX_T_ACTIVE); + agent_name = json_get_string(json_str, tokens, "host"); + zabbix_info->host_name = wmem_strdup(wmem_file_scope(), agent_name); + proto_item_set_text(ti, + "Zabbix Request for active checks for \"%s\"", (agent_name ? agent_name : ZABBIX_UNKNOWN)); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Request for active checks for \"%s\"", (agent_name ? agent_name : ZABBIX_UNKNOWN)); + agent_hostmetadata = json_get_string(json_str, tokens, "host_metadata"); + agent_hostinterface = json_get_string(json_str, tokens, "interface"); + agent_listenip = json_get_string(json_str, tokens, "ip"); + if (json_get_double(json_str, tokens, "port", &temp_double)) { + agent_listenport = (uint16_t)temp_double; + } + } + else if (strcmp(request_type, "agent data") == 0) { + /* Active agent sending data */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_AGENT | ZABBIX_T_DATA | ZABBIX_T_ACTIVE); + /* Zabbix Agent 2 has Host in the top level */ + agent_name = json_get_string(json_str, tokens, "host"); + if (!agent_name) { + /* For Zabbix Agent try parsing agent name inside data array */ + jsmntok_t *tok = json_get_array(json_str, tokens, "data"); + if (tok && json_get_array_len(tok) > 0) { + jsmntok_t *datatok = json_get_array_index(tok, 0); + agent_name = json_get_string(json_str, datatok, "host"); + } + } + zabbix_info->host_name = wmem_strdup(wmem_file_scope(), agent_name); + proto_item_set_text(ti, + "Zabbix Send agent data from \"%s\"", (agent_name ? agent_name : ZABBIX_UNKNOWN)); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Send agent data from \"%s\"", (agent_name ? agent_name : ZABBIX_UNKNOWN)); + } + else if (strcmp(request_type, "active check heartbeat") == 0) { + /* Active agent sending heartbeat */ + /* Clear the request flag first */ + zabbix_info->oper_flags = 0; + ADD_ZABBIX_T_FLAGS(ZABBIX_T_AGENT | ZABBIX_T_HEARTBEAT | ZABBIX_T_ACTIVE); + agent_name = json_get_string(json_str, tokens, "host"); + if (json_get_double(json_str, tokens, "heartbeat_freq", &temp_double)) { + agent_hb_freq = (int)temp_double; + } + proto_item_set_text(ti, + "Zabbix Agent heartbeat from \"%s\"", (agent_name ? agent_name : ZABBIX_UNKNOWN)); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Agent heartbeat from \"%s\"", (agent_name ? agent_name : ZABBIX_UNKNOWN)); + } + else if (strcmp(request_type, "sender data") == 0) { + /* Sender/trapper */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_SENDER); + /* Try to get the sender name from the first data array item */ + jsmntok_t *tok = json_get_array(json_str, tokens, "data"); + if (tok && json_get_array_len(tok) > 0) { + jsmntok_t *datatok = json_get_array_index(tok, 0); + sender_name = json_get_string(json_str, datatok, "host"); + } + zabbix_info->host_name = wmem_strdup(wmem_file_scope(), sender_name); + proto_item_set_text(ti, + "Zabbix Sender data from \"%s\"", (sender_name ? sender_name : ZABBIX_UNKNOWN)); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Sender data from \"%s\"", (sender_name ? sender_name : ZABBIX_UNKNOWN)); + } + else if ((strcmp(request_type, "proxy data") == 0) || + (strcmp(request_type, "host availability") == 0) || + (strcmp(request_type, "history data") == 0) || + (strcmp(request_type, "discovery data") == 0) || + (strcmp(request_type, "auto registration") == 0)) { + /* Either active or passive proxy; "proxy data" = Zabbix 3.4+, + * others = Zabbix 3.2 or older */ + proxy_name = json_get_string(json_str, tokens, "host"); + if (token_count == 3) { /* Only '{"request":"xxx"}' */ + /* This is Zabbix server connecting to passive proxy */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_DATA | ZABBIX_T_PASSIVE); + proto_item_set_text(ti, "Zabbix Request for passive proxy data"); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Request for passive proxy data"); + } + else if (proxy_name) { + /* This is an active proxy connecting to server */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_DATA | ZABBIX_T_ACTIVE); + zabbix_info->host_name = wmem_strdup(wmem_file_scope(), proxy_name); + proto_item_set_text(ti, "Zabbix Proxy data from \"%s\"", proxy_name); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Proxy data from \"%s\"", proxy_name); + } + } + else if (strcmp(request_type, "proxy config") == 0) { + /* Either active or passive proxy */ + proxy_name = json_get_string(json_str, tokens, "host"); + if (token_count == 3) { /* Only '{"request":"proxy config"}' */ + /* This is Zabbix 6.4+ server connecting to passive proxy */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_CONFIG | ZABBIX_T_PASSIVE); + proto_item_set_text(ti, "Zabbix Start send proxy config to passive proxy"); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Start send proxy config to passive proxy"); + } + else if (proxy_name) { + /* This is an active proxy connecting to server */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_CONFIG | ZABBIX_T_ACTIVE); + zabbix_info->host_name = wmem_strdup(wmem_file_scope(), proxy_name); + proto_item_set_text(ti, "Zabbix Request proxy config for \"%s\"", proxy_name); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Request proxy config for \"%s\"", proxy_name); + } + } + else if (strcmp(request_type, "proxy heartbeat") == 0) { + /* Heartbeat from active proxy, not used in Zabbix 6.4+ */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_HEARTBEAT | ZABBIX_T_ACTIVE); + proxy_name = json_get_string(json_str, tokens, "host"); + zabbix_info->host_name = wmem_strdup(wmem_file_scope(), proxy_name); + proto_item_set_text(ti, "Zabbix Proxy heartbeat from \"%s\"", proxy_name); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Proxy heartbeat from \"%s\"", proxy_name); + } + } + else if (json_get_object(json_str, tokens, "globalmacro")) { + /* This is Zabbix server before 6.4 sending configurations to active proxy */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_CONFIG | ZABBIX_T_ACTIVE); + proxy_name = zabbix_info->host_name; + proto_item_set_text(ti, + "Zabbix Response for proxy config for \"%s\"", (proxy_name ? proxy_name : ZABBIX_UNKNOWN)); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for proxy config for \"%s\"", (proxy_name ? proxy_name : ZABBIX_UNKNOWN)); + } + else if (json_get_double(json_str, tokens, "full_sync", &temp_double)) { + /* This is Zabbix 6.4+ server sending proxy config to active or passive proxy */ + /* Only present when value is 1 */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_CONFIG); + oper_response |= ZABBIX_RESPONSE_FULLSYNC; + /* Active/passive flag was set in the earlier packet */ + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_PASSIVE)) { + /* There is no proxy name anywhere to use */ + proto_item_set_text(ti, "Zabbix Passive proxy config"); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Passive proxy config"); + } + else { + proxy_name = zabbix_info->host_name; + proto_item_set_text(ti, + "Zabbix Response for proxy config for \"%s\"", (proxy_name ? proxy_name : ZABBIX_UNKNOWN)); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for proxy config for \"%s\"", (proxy_name ? proxy_name : ZABBIX_UNKNOWN)); + } + } + else if (response_status) { + if (strcmp(response_status, "success") == 0) { + oper_response |= ZABBIX_RESPONSE_SUCCESS; + } + else if (strcmp(response_status, "failed") == 0) { + oper_response |= ZABBIX_RESPONSE_FAILED; + } + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_AGENT)) { + agent_name = zabbix_info->host_name; + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_CONFIG | ZABBIX_T_ACTIVE)) { + proto_item_set_text(ti, + "Zabbix Response for active checks for \"%s\" (%s)", (agent_name ? agent_name : ZABBIX_UNKNOWN), response_status); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for active checks for \"%s\" (%s)", (agent_name ? agent_name : ZABBIX_UNKNOWN), response_status); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_DATA | ZABBIX_T_ACTIVE)) { + proto_item_set_text(ti, + "Zabbix Response for agent data for \"%s\" (%s)", (agent_name ? agent_name : ZABBIX_UNKNOWN), response_status); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for agent data for \"%s\" (%s)", (agent_name ? agent_name : ZABBIX_UNKNOWN), response_status); + } + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_PROXY)) { + proxy_name = zabbix_info->host_name; + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_CONFIG | ZABBIX_T_ACTIVE)) { + proto_item_set_text(ti, + "Zabbix Response for active proxy config request for \"%s\" (%s)", (proxy_name ? proxy_name : ZABBIX_UNKNOWN), response_status); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for active proxy config request for \"%s\" (%s)", (proxy_name ? proxy_name : ZABBIX_UNKNOWN), response_status); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_DATA | ZABBIX_T_ACTIVE)) { + proto_item_set_text(ti, + "Zabbix Response for active proxy data for \"%s\" (%s)", (proxy_name ? proxy_name : ZABBIX_UNKNOWN), response_status); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for active proxy data for \"%s\" (%s)", (proxy_name ? proxy_name : ZABBIX_UNKNOWN), response_status); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_CONFIG | ZABBIX_T_PASSIVE)) { + proto_item_set_text(ti, + "Zabbix Response for passive proxy config (%s)", response_status); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for passive proxy config (%s)", response_status); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_DATA | ZABBIX_T_PASSIVE)) { + proto_item_set_text(ti, + "Zabbix Response for passive proxy data (%s)", response_status); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for passive proxy data (%s)", response_status); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_HEARTBEAT | ZABBIX_T_ACTIVE)) { + proto_item_set_text(ti, + "Zabbix Response for active proxy heartbeat for \"%s\" (%s)", (proxy_name ? proxy_name : ZABBIX_UNKNOWN), response_status); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for active proxy heartbeat for \"%s\" (%s)", (proxy_name ? proxy_name : ZABBIX_UNKNOWN), response_status); + } + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_SENDER)) { + sender_name = zabbix_info->host_name; + proto_item_set_text(ti, + "Zabbix Response for sender data for \"%s\" (%s)", (sender_name ? sender_name : ZABBIX_UNKNOWN), response_status); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for sender data for \"%s\" (%s)", (sender_name ? sender_name : ZABBIX_UNKNOWN), response_status); + } + } + else if (data_object || data_array) { + /* No other match above, let's assume this is server sending incremental + * configuration to a proxy + */ + ADD_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_CONFIG); + if (data_object && (data_object->size == 0)) { + /* Empty data object */ + oper_response |= ZABBIX_RESPONSE_NOCHANGE; + } + else if (data_array) { + /* This was not a "full_sync" but data array exists */ + oper_response |= ZABBIX_RESPONSE_INCREMENTAL; + } + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_PASSIVE)) { + /* There is no proxy name anywhere to use */ + proto_item_set_text(ti, "Zabbix Passive proxy config"); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Passive proxy config"); + } + else { + proxy_name = zabbix_info->host_name; + proto_item_set_text(ti, + "Zabbix Response for proxy config for \"%s\"", (proxy_name ? proxy_name : ZABBIX_UNKNOWN)); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Zabbix Response for proxy config for \"%s\"", (proxy_name ? proxy_name : ZABBIX_UNKNOWN)); + } + } + else if (session && version) { + /* Last guesses: responses from passive proxy */ + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_CONFIG | ZABBIX_T_PASSIVE)) { + proto_item_set_text(ti, "Zabbix Passive proxy response for config push"); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Passive proxy response for config push"); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_PROXY | ZABBIX_T_DATA | ZABBIX_T_PASSIVE)) { + proto_item_set_text(ti, "Zabbix Passive proxy data response"); + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Passive proxy data response"); + } + } + + + /* Add all relevant fields to the tree */ + +show_agent_outputs: + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_AGENT)) { + temp_ti = proto_tree_add_boolean(zabbix_tree, hf_zabbix_agent, NULL, 0, 0, true); + proto_item_set_text(temp_ti, "This is an agent connection"); + proto_item_set_generated(temp_ti); + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_DATA)) { + temp_ti = proto_tree_add_boolean(zabbix_tree, hf_zabbix_agent_data, NULL, 0, 0, true); + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_RESPONSE)) { + /* Set as generated, not seen in data */ + proto_item_set_generated(temp_ti); + } + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_CONFIG)) { + temp_ti = proto_tree_add_boolean(zabbix_tree, hf_zabbix_agent_config, NULL, 0, 0, true); + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_RESPONSE)) { + /* Set as generated, not seen in data */ + proto_item_set_generated(temp_ti); + } + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_HEARTBEAT)) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_agent_hb, NULL, 0, 0, true); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_PASSIVE)) { + temp_ti = proto_tree_add_boolean(zabbix_tree, hf_zabbix_agent_passive, NULL, 0, 0, true); + proto_item_set_text(temp_ti, "Agent is in passive mode"); + proto_item_set_generated(temp_ti); + } + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_PROXY)) { + temp_ti = proto_tree_add_boolean(zabbix_tree, hf_zabbix_proxy, NULL, 0, 0, true); + proto_item_set_text(temp_ti, "This is a proxy connection"); + proto_item_set_generated(temp_ti); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_SENDER)) { + temp_ti = proto_tree_add_boolean(zabbix_tree, hf_zabbix_sender, NULL, 0, 0, true); + proto_item_set_text(temp_ti, "This is a sender connection"); + proto_item_set_generated(temp_ti); + } + if (oper_response & ZABBIX_RESPONSE_SUCCESS) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_success, NULL, 0, 0, true); + } + else if (oper_response & ZABBIX_RESPONSE_FAILED) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_failed, NULL, 0, 0, true); + } + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_AGENT)) { + if (agent_name) { + temp_ti = proto_tree_add_string(zabbix_tree, hf_zabbix_agent_name, NULL, 0, 0, agent_name); + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_RESPONSE)) { + /* agent_name was populated from the conversation */ + proto_item_set_generated(temp_ti); + proto_item_append_text(temp_ti, " (from the request)"); + } + } + if (agent_hb_freq) { + proto_tree_add_int(zabbix_tree, hf_zabbix_agent_hb_freq, NULL, 0, 0, agent_hb_freq); + } + if (agent_hostmetadata) { + proto_tree_add_string(zabbix_tree, hf_zabbix_agent_hostmetadata, NULL, 0, 0, agent_hostmetadata); + } + if (agent_hostinterface) { + proto_tree_add_string(zabbix_tree, hf_zabbix_agent_hostinterface, NULL, 0, 0, agent_hostinterface); + } + if (agent_listenip) { + if (strstr(agent_listenip, ":") != NULL) { + ws_in6_addr addr6; + if (ws_inet_pton6(agent_listenip, &addr6)) { + proto_tree_add_ipv6(zabbix_tree, hf_zabbix_agent_listenipv6, NULL, 0, 0, &addr6); + } + } + else { + ws_in4_addr addr4; + if (ws_inet_pton4(agent_listenip, &addr4)) { + proto_tree_add_ipv4(zabbix_tree, hf_zabbix_agent_listenipv4, NULL, 0, 0, addr4); + } + } + } + if (agent_listenport) { + proto_tree_add_uint(zabbix_tree, hf_zabbix_agent_listenport, NULL, 0, 0, agent_listenport); + } + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_PROXY)) { + if (proxy_name) { + temp_ti = proto_tree_add_string(zabbix_tree, hf_zabbix_proxy_name, NULL, 0, 0, proxy_name); + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_RESPONSE)) { + /* proxy_name was populated from the conversation */ + proto_item_set_generated(temp_ti); + proto_item_append_text(temp_ti, " (from the request)"); + } + } + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_DATA)) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_proxy_data, NULL, 0, 0, true); + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_CONFIG)) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_proxy_config, NULL, 0, 0, true); + if (oper_response & ZABBIX_RESPONSE_FULLSYNC) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_proxy_fullsync, NULL, 0, 0, true); + } + else if (oper_response & ZABBIX_RESPONSE_INCREMENTAL) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_proxy_incr_config, NULL, 0, 0, true); + } + else if (oper_response & ZABBIX_RESPONSE_NOCHANGE) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_proxy_no_config_change, NULL, 0, 0, true); + } + } + else if (IS_ZABBIX_T_FLAGS(ZABBIX_T_HEARTBEAT)) { + proto_tree_add_boolean(zabbix_tree, hf_zabbix_proxy_hb, NULL, 0, 0, true); + } + } + else if (sender_name) { + temp_ti = proto_tree_add_string(zabbix_tree, hf_zabbix_sender_name, NULL, 0, 0, sender_name); + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_RESPONSE)) { + /* sender_name was populated from the conversation */ + proto_item_set_generated(temp_ti); + proto_item_append_text(temp_ti, " (from the request)"); + } + } + if (version) { + proto_tree_add_string(zabbix_tree, hf_zabbix_version, NULL, 0, 0, version); + } + if (config_revision > -1) { + proto_tree_add_int64(zabbix_tree, hf_zabbix_config_revision, NULL, 0, 0, config_revision); + } + if (session) { + proto_tree_add_string(zabbix_tree, hf_zabbix_session, NULL, 0, 0, session); + } + /* Show also the full JSON (or passive agent request/response) */ + proto_tree_add_item(zabbix_tree, hf_zabbix_data, next_tvb, 0, (int)datalen, ENC_UTF_8); + +final_outputs: + + /* These are common for all cases, too large or not */ + + /* Check the ZABBIX_T_REQUEST flag (and not CONV_IS_ZABBIX_REQUEST macro) because + * heartbeats are not marked as requests */ + if (IS_ZABBIX_T_FLAGS(ZABBIX_T_REQUEST)) { + temp_ti = proto_tree_add_boolean(zabbix_tree, hf_zabbix_request, NULL, 0, 0, true); + proto_item_set_text(temp_ti, "This is Zabbix request"); + } else if (CONV_IS_ZABBIX_RESPONSE(zabbix_info, pinfo)) { + temp_ti = proto_tree_add_boolean(zabbix_tree, hf_zabbix_response, NULL, 0, 0, true); + proto_item_set_text(temp_ti, "This is Zabbix response"); + if (!nstime_is_unset(&zabbix_info->req_timestamp)) { + nstime_t delta; + nstime_delta(&delta, &pinfo->abs_ts, &zabbix_info->req_timestamp); + pi = proto_tree_add_time(zabbix_tree, hf_zabbix_time, next_tvb, 0, 0, &delta); + proto_item_set_generated(pi); + } + } + + /* Add length to the Zabbix tree text */ + proto_item_append_text(ti, ", Len=%u", (unsigned)length); + /* Add/set Info column texts */ + const gchar *info_text = col_get_text(pinfo->cinfo, COL_INFO); + if (!info_text || !strlen(info_text)) { + /* Info column is still empty, set the default text */ + if (CONV_IS_ZABBIX_REQUEST(zabbix_info, pinfo)) { + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Protocol request, Flags=0x%02x", flags); + } else if (CONV_IS_ZABBIX_RESPONSE(zabbix_info, pinfo)) { + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Protocol response, Flags=0x%02x", flags); + } else { + col_add_fstr(pinfo->cinfo, COL_INFO, "Zabbix Protocol, Flags=0x%02x", flags); + } + } + col_append_fstr(pinfo->cinfo, COL_INFO, ", Len=%u (", (unsigned)length); + col_append_ports(pinfo->cinfo, COL_INFO, PT_TCP, pinfo->srcport, pinfo->destport); + col_append_str(pinfo->cinfo, COL_INFO, ")"); + + return tvb_reported_length(tvb); +} + +/* Determine PDU length of Zabbix protocol */ +static unsigned +get_zabbix_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) +{ + uint8_t flags; + uint64_t length; + + flags = tvb_get_guint8(tvb, offset+4); + if (flags & ZABBIX_FLAG_LARGEPACKET) { + /* 8-byte length field + * Note that ZABBIX_HDR_MIN_LEN check (in dissect_zabbix()) is still enough + * due to the header structure (there are reserved bytes) + */ + length = tvb_get_guint64(tvb, offset+5, ENC_LITTLE_ENDIAN) + ZABBIX_HDR_MAX_LEN; + } else { + /* 4-byte length */ + length = tvb_get_guint32(tvb, offset+5, ENC_LITTLE_ENDIAN) + ZABBIX_HDR_MIN_LEN; + } + return (unsigned)length; +} + +/* The main dissecting routine */ +static int +dissect_zabbix(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) +{ + uint8_t flags; + + if (tvb_captured_length(tvb) < ZABBIX_HDR_MIN_LEN) { + /* Not enough data */ + return 0; + } + if (tvb_memeql(tvb, 0, ZABBIX_HDR_SIGNATURE, 4)) { + /* Encrypted or not Zabbix at all */ + return 0; + } + flags = tvb_get_guint8(tvb, 4); + if (!(flags & ZABBIX_FLAG_ZABBIX_COMMUNICATIONS)) { + return 0; + } + /* This is unencrypted Zabbix protocol, continue with dissecting it */ + tcp_dissect_pdus(tvb, pinfo, tree, zabbix_desegment, ZABBIX_HDR_MIN_LEN, + get_zabbix_pdu_len, dissect_zabbix_pdu, data); + return tvb_reported_length(tvb); +} + +/* Register the protocol with Wireshark */ + +void +proto_register_zabbix(void) +{ + static hf_register_info hf[] = { + { &hf_zabbix_header, + { "Header", "zabbix.header", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_flags, + { "Flags", "zabbix.flags", + FT_UINT8, BASE_HEX, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_flag_zabbix_communications, + { "Zabbix communications protocol", "zabbix.flags.zabbix", + FT_BOOLEAN, 8, TFS(&tfs_yes_no), ZABBIX_FLAG_ZABBIX_COMMUNICATIONS, + NULL, HFILL } + }, + { &hf_zabbix_flag_compressed, + { "Compressed", "zabbix.flags.compressed", + FT_BOOLEAN, 8, TFS(&tfs_yes_no), ZABBIX_FLAG_COMPRESSED, + NULL, HFILL } + }, + { &hf_zabbix_flag_largepacket, + { "Large packet", "zabbix.flags.large_packet", + FT_BOOLEAN, 8, TFS(&tfs_yes_no), ZABBIX_FLAG_LARGEPACKET, + NULL, HFILL } + }, + { &hf_zabbix_flag_reserved, + { "Reserved bits", "zabbix.flags.reserved", + FT_UINT8, BASE_DEC, NULL, ZABBIX_FLAG_RESERVED, + NULL, HFILL } + }, + { &hf_zabbix_length, + { "Length", "zabbix.len", + FT_UINT32, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_reserved, + { "Reserved", "zabbix.reserved", + FT_UINT32, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_uncompressed_length, + { "Uncompressed length", "zabbix.uncompressed_len", + FT_UINT32, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_large_length, + { "Large length", "zabbix.large.len", + FT_UINT64, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_large_reserved, + { "Large reserved", "zabbix.large.reserved", + FT_UINT64, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_large_uncompressed_length, + { "Large uncompressed length", "zabbix.large.uncompressed_len", + FT_UINT64, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_data, + { "Data", "zabbix.data", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_time, + { "Response time", "zabbix.time", + FT_RELATIVE_TIME, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_request, + { "Zabbix protocol request", "zabbix.request", + FT_BOOLEAN, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_response, + { "Zabbix protocol response", "zabbix.response", + FT_BOOLEAN, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_success, + { "Success", "zabbix.success", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_failed, + { "Failed", "zabbix.failed", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_agent, + { "Zabbix agent connection", "zabbix.agent", + FT_BOOLEAN, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_config, + { "Zabbix agent config", "zabbix.agent.config", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_data, + { "Zabbix agent data", "zabbix.agent.data", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_passive, + { "Passive agent", "zabbix.agent.passive", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_name, + { "Agent name", "zabbix.agent.name", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_hb, + { "Agent heartbeat", "zabbix.agent.heartbeat", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_hb_freq, + { "Agent heartbeat frequency", "zabbix.agent.heartbeat_freq", + FT_INT32, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_hostmetadata, + { "Agent host metadata", "zabbix.agent.host_metadata", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_hostinterface, + { "Agent host interface", "zabbix.agent.host_interface", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_listenipv4, + { "Agent listen IPv4", "zabbix.agent.listen_ipv4", + FT_IPv4, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_listenipv6, + { "Agent listen IPv6", "zabbix.agent.listen_ipv6", + FT_IPv6, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_agent_listenport, + { "Agent listen port", "zabbix.agent.listen_port", + FT_UINT16, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_proxy, + { "Proxy connection", "zabbix.proxy", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_proxy_name, + { "Proxy name", "zabbix.proxy.name", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_proxy_hb, + { "Proxy heartbeat", "zabbix.proxy.heartbeat", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_proxy_data, + { "Proxy data", "zabbix.proxy.data", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_proxy_config, + { "Proxy config", "zabbix.proxy.config", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_proxy_fullsync, + { "Proxy config full sync", "zabbix.proxy.full_sync", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_proxy_incr_config, + { "Proxy incremental config", "zabbix.proxy.incremental_config", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_proxy_no_config_change, + { "Proxy no config changes", "zabbix.proxy.no_config_changes", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_sender, + { "Sender connection", "zabbix.sender", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0, + NULL, HFILL } + }, + { &hf_zabbix_sender_name, + { "Sender name", "zabbix.sender.name", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_version, + { "Version", "zabbix.version", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_session, + { "Session", "zabbix.session", + FT_STRING, BASE_NONE, NULL, 0, + NULL, HFILL } + }, + { &hf_zabbix_config_revision, + { "Config revision", "zabbix.config_revision", + FT_INT64, BASE_DEC, NULL, 0, + NULL, HFILL } + }, + }; + + static ei_register_info ei[] = { + { + &ei_zabbix_packet_too_large, + { "zabbix.packet_too_large", PI_UNDECODED, PI_WARN, + "Packet is too large for detailed dissection", EXPFILL } + }, + { + &ei_zabbix_json_error, + { "zabbix.json_error", PI_PROTOCOL, PI_ERROR, + "Cannot parse JSON", EXPFILL } + }, + }; + + /* Setup protocol subtree array */ + static gint *ett[] = { + &ett_zabbix, + }; + + module_t *zabbix_module; + expert_module_t *expert_zabbix; + + /* Register the protocol name and description */ + proto_zabbix = proto_register_protocol("Zabbix Protocol", "Zabbix", "zabbix"); + + /* Required function calls to register the header fields and subtrees used */ + proto_register_field_array(proto_zabbix, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + + zabbix_module = prefs_register_protocol(proto_zabbix, NULL); + + prefs_register_bool_preference(zabbix_module, "desegment", + "Reassemble Zabbix messages spanning multiple TCP segments", + "Whether the Zabbix protocol dissector should reassemble messages spanning multiple TCP segments." + " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", + (gboolean *)&zabbix_desegment); + + zabbix_handle = register_dissector("zabbix", dissect_zabbix, proto_zabbix); + + expert_zabbix = expert_register_protocol(proto_zabbix); + expert_register_field_array(expert_zabbix, ei, array_length(ei)); +} + +void +proto_reg_handoff_zabbix(void) +{ + dissector_add_uint_range_with_preference("tcp.port", ZABBIX_TCP_PORTS, zabbix_handle); + zabbix_port_range = prefs_get_range_value("Zabbix", "tcp.port"); + dissector_add_uint_range("tls.port", zabbix_port_range, zabbix_handle); +} -- cgit v1.2.3