Coverage Report

Created: 2026-02-21 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ndpi/src/lib/protocols/tftp.c
Line
Count
Source
1
/*
2
 * tftp.c
3
 *
4
 * Copyright (C) 2009-11 - ipoque GmbH
5
 * Copyright (C) 2011-26 - ntop.org
6
 *
7
 * This file is part of nDPI, an open source deep packet inspection
8
 * library based on the OpenDPI and PACE technology by ipoque GmbH
9
 *
10
 * nDPI is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Lesser General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * nDPI is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public License
21
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
25
#include "ndpi_protocol_ids.h"
26
27
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_TFTP
28
29
#include "ndpi_api.h"
30
#include "ndpi_private.h"
31
32
static void ndpi_int_tftp_add_connection(struct ndpi_detection_module_struct
33
           *ndpi_struct, struct ndpi_flow_struct *flow)
34
0
{
35
0
  ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TFTP, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
36
0
}
37
38
static size_t tftp_dissect_szstr(struct ndpi_packet_struct const * const packet,
39
                                 size_t * const offset,
40
                                 char const ** const string_start)
41
21
{
42
21
  if (packet->payload_packet_len <= *offset)
43
0
  {
44
0
    return 0;
45
0
  }
46
47
21
  const union {
48
21
    uint8_t const * const as_ptr;
49
21
    char const * const as_str;
50
21
  } payload = { .as_ptr = packet->payload + *offset };
51
52
21
  size_t len = strnlen(payload.as_str, packet->payload_packet_len - *offset);
53
21
  if (len == 0 ||
54
17
      packet->payload_packet_len <= *offset + len ||
55
15
      payload.as_str[len] != '\0')
56
6
  {
57
6
    return 0;
58
6
  }
59
60
15
  if (string_start != NULL)
61
12
  {
62
12
    *string_start = payload.as_str;
63
12
  }
64
15
  *offset += len + 1;
65
15
  return len;
66
21
}
67
68
static int tftp_dissect_mode(struct ndpi_packet_struct const * const packet,
69
                             size_t * const offset)
70
1
{
71
1
  static char const * const valid_modes[] = {
72
1
    "netascii", "octet", "mail"
73
1
  };
74
1
  char const * string_start;
75
1
  size_t string_length = tftp_dissect_szstr(packet, offset, &string_start);
76
1
  size_t i;
77
78
1
  if (string_length == 0)
79
0
  {
80
0
    return 1;
81
0
  }
82
83
4
  for (i = 0; i < NDPI_ARRAY_LENGTH(valid_modes); ++i)
84
3
  {
85
3
    if (strncasecmp(string_start, valid_modes[i], string_length) == 0)
86
0
    {
87
0
      break;
88
0
    }
89
3
  }
90
91
1
  return i == NDPI_ARRAY_LENGTH(valid_modes);
92
1
}
93
94
static int tftp_dissect_options(struct ndpi_packet_struct const * const packet,
95
                                size_t * const offset)
96
8
{
97
8
  static char const * const valid_options[] = {
98
8
    "blksize", "tsize"
99
8
  };
100
8
  uint8_t options_used[NDPI_ARRAY_LENGTH(valid_options)] = {0, 0};
101
8
  size_t i;
102
103
8
  do {
104
8
    char const * string_start;
105
8
    size_t string_length = tftp_dissect_szstr(packet, offset, &string_start);
106
107
8
    if (string_length == 0 ||
108
5
        tftp_dissect_szstr(packet, offset, NULL) == 0 /* value, not interested */)
109
5
    {
110
5
      break;
111
5
    }
112
113
9
    for (i = 0; i < NDPI_ARRAY_LENGTH(valid_options); ++i)
114
6
    {
115
6
      if (strncasecmp(string_start, valid_options[i], string_length) == 0)
116
0
      {
117
0
        break;
118
0
      }
119
6
    }
120
121
3
    if (i == NDPI_ARRAY_LENGTH(valid_options) /* option not found in valid_options */ ||
122
0
        options_used[i] != 0 /* duplicate options are not allowed */)
123
3
    {
124
3
      break;
125
3
    }
126
127
0
    options_used[i] = 1;
128
0
  } while (1);
129
130
8
  return *offset != packet->payload_packet_len;
131
8
}
132
133
static void ndpi_search_tftp(struct ndpi_detection_module_struct *ndpi_struct,
134
           struct ndpi_flow_struct *flow)
135
525
{
136
525
  struct ndpi_packet_struct const * const packet = &ndpi_struct->packet;
137
525
  u_int16_t block_num;
138
525
  u_int16_t prev_num;
139
140
525
  NDPI_LOG_DBG(ndpi_struct, "search TFTP\n");
141
142
525
  if (packet->payload_packet_len < 4 /* min. header size */ ||
143
514
      get_u_int8_t(packet->payload, 0) != 0x00)
144
404
  {
145
404
    NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
146
404
    return;
147
404
  }
148
149
  /* parse TFTP opcode */
150
121
  switch (get_u_int8_t(packet->payload, 1))
151
121
  {
152
7
    case 0x01:
153
        /* Read request (RRQ) */
154
10
    case 0x02:
155
        /* Write request (WWQ) */
156
157
10
        if (packet->payload[packet->payload_packet_len - 1] != 0x00 /* last pdu element is a nul terminated string */)
158
3
        {
159
3
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
160
3
          return;
161
3
        }
162
163
7
        {
164
7
          size_t filename_len = 0;
165
7
          size_t offset = 2;
166
7
          char const * filename_start;
167
168
7
          filename_len = tftp_dissect_szstr(packet, &offset, &filename_start);
169
170
          /* Exclude the flow as TFPT if there was no filename and mode in the first two strings. */
171
7
          if (filename_len == 0 || ndpi_is_printable_buffer((uint8_t *)filename_start, filename_len) == 0)
172
6
          {
173
6
            NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
174
6
            return;
175
6
          }
176
177
1
          if (tftp_dissect_mode(packet, &offset) != 0)
178
1
          {
179
1
            NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
180
1
            return;
181
1
          }
182
183
0
          if (tftp_dissect_options(packet, &offset) != 0)
184
0
          {
185
0
            NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
186
0
            return;
187
0
          }
188
189
          /* Dissect RRQ/WWQ filename. */
190
0
          filename_len = ndpi_min(filename_len, sizeof(flow->protos.tftp.filename) - 1);
191
0
          memcpy(flow->protos.tftp.filename, filename_start, filename_len);
192
0
          flow->protos.tftp.filename[filename_len] = '\0';
193
194
          /* We have seen enough and do not need any more TFTP packets. */
195
0
          NDPI_LOG_INFO(ndpi_struct, "found tftp (RRQ/WWQ)\n");
196
0
          ndpi_int_tftp_add_connection(ndpi_struct, flow);
197
0
        }
198
0
        return;
199
200
3
    case 0x03:
201
        /* Data (DATA) */
202
3
        if (packet->payload_packet_len <= 4 /* min DATA header size */)
203
1
        {
204
1
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
205
1
          return;
206
1
        }
207
        /* First 2 bytes were opcode so next 16 bits are the block number.
208
         * This should increment every packet but give some leeway for midstream and packet loss. */
209
2
        block_num = ntohs(get_u_int16_t(packet->payload, 2));
210
2
        prev_num = flow->l4.udp.tftp_data_num;
211
2
        flow->l4.udp.tftp_data_num = block_num;
212
2
        if (!(block_num == prev_num + 1 || (prev_num != 0 && block_num == prev_num)))
213
1
        {
214
1
          return;
215
1
        }
216
1
        break;
217
218
3
    case 0x04:
219
        /* Acknowledgment (ACK) */
220
221
3
        if (packet->payload_packet_len != 4 /* ACK has a fixed packet size */)
222
2
        {
223
2
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
224
2
          return;
225
2
        }
226
        /* First 2 bytes were opcode so next 16 bits are the block number.
227
         * This should increment every packet but give some leeway for midstream and packet loss. */
228
1
        block_num = ntohs(get_u_int16_t(packet->payload, 2));
229
1
        prev_num = flow->l4.udp.tftp_ack_num;
230
1
        flow->l4.udp.tftp_ack_num = block_num;
231
1
        if (!(block_num == prev_num + 1 || (block_num == prev_num)))
232
1
        {
233
1
          return;
234
1
        }
235
0
        break;
236
237
3
    case 0x05:
238
        /* Error (ERROR) */
239
240
3
        if (packet->payload_packet_len < 5 ||
241
2
            packet->payload[packet->payload_packet_len - 1] != 0x00 ||
242
1
            packet->payload[2] != 0x00 || packet->payload[3] > 0x07)
243
3
        {
244
3
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
245
3
          return;
246
3
        }
247
0
        break;
248
249
8
    case 0x06:
250
        /* Option Acknowledgment (OACK) */
251
252
8
        {
253
8
          size_t offset = 2;
254
255
8
          if (tftp_dissect_options(packet, &offset) != 0)
256
8
          {
257
8
            NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
258
8
            return;
259
8
          }
260
8
        }
261
262
        /* We have seen enough and do not need any more TFTP packets. */
263
0
        NDPI_LOG_INFO(ndpi_struct, "found tftp (OACK)\n");
264
0
        ndpi_int_tftp_add_connection(ndpi_struct, flow);
265
0
        break;
266
267
94
    default:
268
94
        NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
269
94
        return;
270
121
  }
271
272
1
  if (flow->l4.udp.tftp_stage < 3)
273
1
  {
274
1
    NDPI_LOG_DBG2(ndpi_struct, "maybe tftp. need next packet\n");
275
1
    flow->l4.udp.tftp_stage++;
276
1
    return;
277
1
  }
278
279
0
  NDPI_LOG_INFO(ndpi_struct, "found tftp\n");
280
0
  ndpi_int_tftp_add_connection(ndpi_struct, flow);
281
0
}
282
283
284
void init_tftp_dissector(struct ndpi_detection_module_struct *ndpi_struct)
285
6.05k
{
286
6.05k
  ndpi_register_dissector("TFTP", ndpi_struct,
287
6.05k
                     ndpi_search_tftp,
288
6.05k
                     NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD,
289
6.05k
                     1, NDPI_PROTOCOL_TFTP);
290
6.05k
}
291