/* * thrift.c * * Copyright (C) 2023 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nDPI is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with nDPI. If not, see . * */ #include "ndpi_protocol_ids.h" #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_APACHE_THRIFT #include "ndpi_api.h" #include "ndpi_private.h" #include // References: https://thrift.apache.org AND https://github.com/apache/thrift // Not Implemented (sub)protocols: TJSONProtocol, TSimpleJSONProtocol and TDebugProtocol // TBinaryProtocol PACK_ON struct thrift_strict_hdr { uint8_t protocol_id; uint8_t version; uint8_t unused_byte_pad; uint8_t message_type; uint32_t method_length; char method[0]; } PACK_OFF; // TCompactProtocol PACK_ON struct thrift_compact_hdr { uint8_t protocol_id; #if defined(__BIG_ENDIAN__) uint8_t message_type : 3; uint8_t version : 5; #elif defined(__LITTLE_ENDIAN__) uint8_t version : 5; uint8_t message_type : 3; #else #error "Missing endian macro definitions." #endif uint8_t sequence_id[3]; uint8_t method_length; char method[0]; } PACK_OFF; enum thrift_message_type { TMT_INVALID_TMESSAGE_TYPE = 0, TMT_CALL = 1, TMT_REPLY = 2, TMT_EXCEPTION = 3, TMT_ONEWAY = 4, TMT_TYPE_MAX }; static void ndpi_int_thrift_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, uint16_t master_protocol) { switch (master_protocol) { case NDPI_PROTOCOL_UNKNOWN: NDPI_LOG_DBG(ndpi_struct, "found Apache Thrift TCP/UDP\n"); break; case NDPI_PROTOCOL_HTTP: NDPI_LOG_DBG(ndpi_struct, "found Apache Thrift HTTP\n"); break; } ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_APACHE_THRIFT, master_protocol, NDPI_CONFIDENCE_DPI); } static int thrift_validate_method(char const * const method, size_t method_length) { const union { uint8_t const * const ptr; char const * const str; } m = { .str = method }; return ndpi_is_printable_buffer(m.ptr, method_length); } static int thrift_validate_version(uint8_t version) { return version <= 0x01; } static int thrift_validate_type(uint8_t message_type) { return message_type < TMT_TYPE_MAX; } static void thrift_set_method(struct ndpi_flow_struct *flow, char const * const method, size_t method_length) { if (thrift_validate_method(method, method_length) == 0) { ndpi_set_risk(flow, NDPI_INVALID_CHARACTERS, "Invalid method name"); flow->protos.thrift.method[0] = '\0'; } else { strncpy(flow->protos.thrift.method, method, ndpi_min(sizeof(flow->protos.thrift.method), method_length)); } } static void thrift_set_type(struct ndpi_flow_struct *flow, uint8_t message_type) { if (message_type == TMT_INVALID_TMESSAGE_TYPE) { ndpi_set_risk(flow, NDPI_MALFORMED_PACKET, "Invalid message type"); } flow->protos.thrift.message_type = message_type; if (message_type == TMT_EXCEPTION) { ndpi_set_risk(flow, NDPI_ERROR_CODE_DETECTED, "Apache Thrift Exception"); } } static void ndpi_dissect_strict_hdr(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, struct thrift_strict_hdr const * const strict_hdr) { struct ndpi_packet_struct const * const packet = &ndpi_struct->packet; const size_t method_length = ntohl(strict_hdr->method_length); if (packet->tcp == NULL) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } if (packet->payload_packet_len < sizeof(*strict_hdr) + method_length) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } if (thrift_validate_version(strict_hdr->version) == 0) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } if (thrift_validate_type(strict_hdr->message_type) == 0) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } ndpi_int_thrift_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); thrift_set_method(flow, strict_hdr->method, method_length); thrift_set_type(flow, strict_hdr->message_type); } static void ndpi_dissect_compact_hdr(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, struct thrift_compact_hdr const * const compact_hdr) { struct ndpi_packet_struct const * const packet = &ndpi_struct->packet; if (packet->udp == NULL) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } if (packet->payload_packet_len < sizeof(*compact_hdr) + compact_hdr->method_length) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } if (thrift_validate_version(compact_hdr->version) == 0) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } if (thrift_validate_type(compact_hdr->message_type) == 0) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } ndpi_int_thrift_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); thrift_set_method(flow, compact_hdr->method, compact_hdr->method_length); thrift_set_type(flow, compact_hdr->message_type); } static void ndpi_search_thrift_tcp_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct const * const packet = &ndpi_struct->packet; NDPI_LOG_DBG(ndpi_struct, "search Apache Thrift\n"); if (flow->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP || flow->detected_protocol_stack[1] == NDPI_PROTOCOL_HTTP) { /* Check Thrift over HTTP */ if (packet->content_line.ptr != NULL) { if ((LINE_ENDS(packet->content_line, "application/vnd.apache.thrift.binary") != 0) || (LINE_ENDS(packet->content_line, "application/vnd.apache.thrift.compact") != 0) || (LINE_ENDS(packet->content_line, "application/vnd.apache.thrift.json") != 0)) { NDPI_LOG_INFO(ndpi_struct, "found Apache Thrift over HTTP\n"); ndpi_int_thrift_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP); return; } } } else if (packet->payload_packet_len >= sizeof(struct thrift_compact_hdr)) { const union { uint8_t const * const raw_ptr; struct thrift_strict_hdr const * const strict_hdr; struct thrift_compact_hdr const * const compact_hdr; } thrift_data = { .raw_ptr = &packet->payload[0] }; if (thrift_data.raw_ptr[0] == 0x80) { /* Strict Binary Protocol */ if (packet->payload_packet_len < sizeof(*thrift_data.strict_hdr)) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } ndpi_dissect_strict_hdr(ndpi_struct, flow, thrift_data.strict_hdr); return; } else if (thrift_data.raw_ptr[0] == 0x82) { /* Compact Protocol */ ndpi_dissect_compact_hdr(ndpi_struct, flow, thrift_data.compact_hdr); return; } else { /* Probably not Apache Thrift. */ NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } } NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void init_apache_thrift_dissector(struct ndpi_detection_module_struct *ndpi_struct, uint32_t *id) { ndpi_set_bitmask_protocol_detection("Thrift", ndpi_struct, *id, NDPI_PROTOCOL_APACHE_THRIFT, ndpi_search_thrift_tcp_udp, NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); *id += 1; }