Coverage Report

Created: 2026-09-19 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}