/src/ndpi/src/lib/protocols/protobuf.c
Line | Count | Source |
1 | | /* |
2 | | * protobuf.c |
3 | | * |
4 | | * Copyright (C) 2023 by ntop.org |
5 | | * |
6 | | * This file is part of nDPI, an open source deep packet inspection |
7 | | * library based on the OpenDPI and PACE technology by ipoque GmbH |
8 | | * |
9 | | * nDPI is free software: you can redistribute it and/or modify |
10 | | * it under the terms of the GNU Lesser General Public License as published by |
11 | | * the Free Software Foundation, either version 3 of the License, or |
12 | | * (at your option) any later version. |
13 | | * |
14 | | * nDPI is distributed in the hope that it will be useful, |
15 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | | * GNU Lesser General Public License for more details. |
18 | | * |
19 | | * You should have received a copy of the GNU Lesser General Public License |
20 | | * along with nDPI. If not, see <http://www.gnu.org/licenses/>. |
21 | | * |
22 | | */ |
23 | | |
24 | | #include "ndpi_protocol_ids.h" |
25 | | |
26 | | #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_PROTOBUF |
27 | | //#define DEBUG_PROTOBUF 1 |
28 | 3.85k | #define PROTOBUF_MIN_ELEMENTS 2 |
29 | 2.41M | #define PROTOBUF_MAX_ELEMENTS 32 |
30 | 642k | #define PROTOBUF_REQUIRED_ELEMENTS 8 |
31 | 637k | #define PROTOBUF_MIN_PACKETS 4 |
32 | 123k | #define PROTOBUF_MAX_PACKETS 8 |
33 | | |
34 | | #include "ndpi_api.h" |
35 | | #include "ndpi_private.h" |
36 | | |
37 | | enum protobuf_type { |
38 | | PT_INVALID = -1, |
39 | | PT_VARINT = 0, |
40 | | PT_I64, |
41 | | PT_LEN, |
42 | | PT_SGROUP, // deprecated |
43 | | PT_EGROUP, // deprecated |
44 | | PT_I32 |
45 | | }; |
46 | | |
47 | | size_t protobuf_dissect(unsigned char const * const buffer, size_t const size, |
48 | | size_t * const protobuf_elements, |
49 | | size_t * const protobuf_len_elements); |
50 | | |
51 | | static void ndpi_int_protobuf_add_connection(struct ndpi_detection_module_struct *ndpi_struct, |
52 | | struct ndpi_flow_struct *flow) |
53 | 2.89k | { |
54 | 2.89k | NDPI_LOG_INFO(ndpi_struct, "found Protobuf\n"); |
55 | 2.89k | ndpi_set_detected_protocol(ndpi_struct, &flow->core, NDPI_PROTOCOL_PROTOBUF, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI); |
56 | 2.89k | } |
57 | | |
58 | | static enum protobuf_type |
59 | | protobuf_dissect_tag(uint64_t tag, uint64_t * const field_number) |
60 | 6.13M | { |
61 | 6.13M | uint8_t const wire_type = tag & 0x07; |
62 | 6.13M | *field_number = tag >> 3; |
63 | | |
64 | 6.13M | switch (wire_type) |
65 | 6.13M | { |
66 | 2.06M | case PT_VARINT: |
67 | 2.76M | case PT_I64: |
68 | 3.36M | case PT_LEN: |
69 | 3.80M | case PT_SGROUP: |
70 | 4.22M | case PT_EGROUP: |
71 | 4.96M | case PT_I32: |
72 | 4.96M | return wire_type; |
73 | 6.13M | } |
74 | | |
75 | 1.17M | return PT_INVALID; |
76 | 6.13M | } |
77 | | |
78 | | static int |
79 | | protobuf_dissect_varint(unsigned char const * const buffer, size_t size, |
80 | | size_t * const offset, uint64_t * const value) |
81 | 8.17M | { |
82 | 8.17M | size_t i; |
83 | 8.17M | *value = 0; |
84 | | |
85 | 14.9M | for (i = 0; i < 9; ++i) |
86 | 14.5M | { |
87 | 14.5M | if (size < *offset + i + 1) |
88 | 570k | { |
89 | 570k | return -1; |
90 | 570k | } |
91 | | |
92 | 13.9M | *value |= ((uint64_t)(buffer[*offset + i] & 0x7F)) << (i * 8 - i); |
93 | 13.9M | if ((buffer[*offset + i] & 0x80) == 0) |
94 | 7.13M | { |
95 | 7.13M | break; |
96 | 7.13M | } |
97 | 13.9M | } |
98 | | |
99 | 7.60M | *offset += i + 1; |
100 | 7.60M | return 0; |
101 | 8.17M | } |
102 | | |
103 | | size_t protobuf_dissect(unsigned char const * const buffer, size_t const size, |
104 | | size_t * const protobuf_elements, |
105 | | size_t * const protobuf_len_elements) |
106 | 4.19M | { |
107 | 4.19M | *protobuf_elements = 0; |
108 | 4.19M | *protobuf_len_elements = 0; |
109 | 4.19M | size_t offset = 0; |
110 | | |
111 | | #ifdef DEBUG_PROTOBUF |
112 | | printf("Protobuf:"); |
113 | | #endif |
114 | 6.59M | do { |
115 | | #ifdef DEBUG_PROTOBUF |
116 | | printf(" "); |
117 | | #endif |
118 | 6.59M | uint64_t tag; |
119 | | // A Protobuf tag has a type and a field number stored as u32 varint. |
120 | 6.59M | if (protobuf_dissect_varint(buffer, size, &offset, &tag) != 0) |
121 | 462k | { |
122 | 462k | break; |
123 | 462k | } |
124 | | |
125 | 6.13M | uint64_t field_number; |
126 | 6.13M | enum protobuf_type type = protobuf_dissect_tag(tag, &field_number); |
127 | 6.13M | if (type == PT_INVALID || field_number == 0 || field_number > (UINT_MAX >> 3)) |
128 | 2.69M | { |
129 | 2.69M | return 0; |
130 | 2.69M | } |
131 | | |
132 | | #ifdef DEBUG_PROTOBUF |
133 | | printf("[id: %llu]", (unsigned long long int)field_number); |
134 | | #endif |
135 | 3.43M | switch (type) |
136 | 3.43M | { |
137 | 1.15M | case PT_VARINT: |
138 | 1.15M | { |
139 | 1.15M | uint64_t value; |
140 | 1.15M | if (protobuf_dissect_varint(buffer, size, &offset, &value) != 0) |
141 | 52.2k | { |
142 | 52.2k | return 0; |
143 | 52.2k | } |
144 | | #ifdef DEBUG_PROTOBUF |
145 | | printf("[VARINT: %llu / %llx]", (unsigned long long int)value, |
146 | | (unsigned long long int)value); |
147 | | #endif |
148 | 1.10M | break; |
149 | 1.15M | } |
150 | 1.10M | case PT_I64: { |
151 | 498k | if (size < offset + sizeof(uint64_t)) |
152 | 111k | { |
153 | 111k | return 0; |
154 | 111k | } |
155 | | #ifdef DEBUG_PROTOBUF |
156 | | union { |
157 | | int64_t as_i64; |
158 | | uint64_t as_u64; |
159 | | double as_double; |
160 | | } value; |
161 | | value.as_u64 = le64toh(*(uint64_t *)&buffer[offset]); |
162 | | printf("[I64: %lld / %llu / %lf]", (long long int)value.as_i64, |
163 | | (unsigned long long int)value.as_u64, value.as_double); |
164 | | #endif |
165 | 386k | offset += 8; |
166 | 386k | break; |
167 | 498k | } |
168 | 419k | case PT_LEN: |
169 | 419k | { |
170 | 419k | uint64_t length; |
171 | 419k | if (protobuf_dissect_varint(buffer, size, &offset, &length) != 0) |
172 | 55.8k | { |
173 | 55.8k | if (size >= offset) |
174 | 55.7k | { |
175 | 55.7k | break; // We are not excluding the protocol immediately. Let's wait for more packets to arrive.. |
176 | 55.7k | } else { |
177 | 53 | return 0; |
178 | 53 | } |
179 | 55.8k | } |
180 | 363k | if (length == 0 || length > INT_MAX) |
181 | 56.4k | { |
182 | 56.4k | return 0; |
183 | 56.4k | } |
184 | 307k | offset += length; |
185 | 307k | (*protobuf_len_elements)++; |
186 | | #ifdef DEBUG_PROTOBUF |
187 | | printf("[LEN length: %llu]", (unsigned long long int)length); |
188 | | #endif |
189 | 307k | break; |
190 | 363k | } |
191 | 364k | case PT_SGROUP: |
192 | 711k | case PT_EGROUP: |
193 | | // Start/End groups are deprecated and therefor ignored to reduce false positives. |
194 | 711k | return 0; |
195 | 644k | case PT_I32: { |
196 | 644k | if (size < offset + sizeof(uint32_t)) |
197 | 86.7k | { |
198 | 86.7k | return 0; |
199 | 86.7k | } |
200 | | #ifdef DEBUG_PROTOBUF |
201 | | union { |
202 | | int32_t as_i32; |
203 | | uint32_t as_u32; |
204 | | float as_float; |
205 | | } value; |
206 | | value.as_u32 = le32toh(*(uint32_t *)&buffer[offset]); |
207 | | printf("[I32: %d / %u / %f]", value.as_i32, value.as_u32, value.as_float); |
208 | | #endif |
209 | 558k | offset += 4; |
210 | 558k | break; |
211 | 644k | } |
212 | 0 | case PT_INVALID: |
213 | 0 | break; |
214 | 3.43M | } |
215 | 3.43M | } while (++(*protobuf_elements) < PROTOBUF_MAX_ELEMENTS); |
216 | | |
217 | | #ifdef DEBUG_PROTOBUF |
218 | | printf(" [offset: %llu][length: %zu][elems: %llu][len_elems: %llu]\n", |
219 | | (unsigned long long int)offset, size, |
220 | | (unsigned long long int)protobuf_elements, |
221 | | (unsigned long long int)protobuf_len_elements); |
222 | | #endif |
223 | 475k | return offset; |
224 | 4.19M | } |
225 | | |
226 | | static void ndpi_search_protobuf(struct ndpi_detection_module_struct *ndpi_struct, |
227 | | struct ndpi_flow_struct *flow) |
228 | 4.19M | { |
229 | 4.19M | struct ndpi_packet_struct const * const packet = &ndpi_struct->packet; |
230 | | |
231 | 4.19M | NDPI_LOG_DBG(ndpi_struct, "search Protobuf\n"); |
232 | | |
233 | 4.19M | size_t protobuf_elements = 0; |
234 | 4.19M | size_t protobuf_len_elements = 0; |
235 | 4.19M | size_t bytes_parsed = protobuf_dissect(packet->payload, packet->payload_packet_len, |
236 | 4.19M | &protobuf_elements, &protobuf_len_elements); |
237 | | |
238 | 4.19M | if (bytes_parsed == 0) { |
239 | 3.87M | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
240 | 3.87M | return; |
241 | 3.87M | } |
242 | | |
243 | 321k | if ((protobuf_elements >= PROTOBUF_REQUIRED_ELEMENTS && protobuf_len_elements > 0 && |
244 | | /* (On UDP) this packet might be also a RTP/RTCP one. Wait for the next one */ |
245 | 2.61k | (flow->core.packet_counter > 1 || flow->core.l4_proto == IPPROTO_TCP || flow->metadata.rtp.rtp_stage == 0)) |
246 | 318k | || (flow->core.packet_counter >= PROTOBUF_MIN_PACKETS && protobuf_elements >= PROTOBUF_MIN_ELEMENTS)) |
247 | 2.89k | { |
248 | | #ifdef DEBUG_PROTOBUF |
249 | | printf("Protobuf found after %u packets.\n", flow->core.packet_counter); |
250 | | #endif |
251 | 2.89k | ndpi_int_protobuf_add_connection(ndpi_struct, flow); |
252 | 2.89k | return; |
253 | 2.89k | } |
254 | | |
255 | 318k | if (packet->payload_packet_len >= bytes_parsed |
256 | 123k | && protobuf_elements > 0 |
257 | 123k | && flow->core.packet_counter <= PROTOBUF_MAX_PACKETS) |
258 | 123k | { |
259 | 123k | return; // We probably need more packets to dissect. |
260 | 123k | } |
261 | | |
262 | 194k | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
263 | 194k | } |
264 | | |
265 | | |
266 | | void init_protobuf_dissector(struct ndpi_detection_module_struct *ndpi_struct) |
267 | 16.5k | { |
268 | 16.5k | ndpi_register_dissector("Protobuf", ndpi_struct, |
269 | 16.5k | ndpi_search_protobuf, |
270 | 16.5k | NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, |
271 | 16.5k | DISSECTOR_LICENSE_LGPL, |
272 | 16.5k | 1, NDPI_PROTOCOL_PROTOBUF); |
273 | 16.5k | } |