summaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-cl3dcw.c
blob: bc2f123d23d29f8725b7d9d9960a983ef78a4d35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
/* packet-cl3dcw.c
 * Routines for CableLabs Dual-Channel Wi-Fi Messaging Protocol Dissection
 * Copyright 2019 Jon Dennis <j.dennis[at]cablelabs.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * CableLabs Specifications Can Be Found At:
 *  https://www.cablelabs.com/specs
 */


#include "config.h"
#include <epan/packet.h>
#include <epan/expert.h>

void proto_register_cl3dcw(void);
void proto_reg_handoff_cl3dcw(void);

static dissector_handle_t cl3dcw_handle;
#define SSID_MAX_LENGTH 32

/* persistent handles for this dissector */
static int           proto_cl3dcw               = -1;
static gint          ett_cl3dcw                 = -1;
static int           hf_cl3dcw_type             = -1;
static int           hf_cl3dcw_dccount          = -1;
static int           hf_cl3dcw_datamacaddrcount = -1;
static int           hf_cl3dcw_datassidcount    = -1;
static int           hf_cl3dcw_dcmacaddr        = -1;
static int           hf_cl3dcw_dcssid           = -1;
static int           hf_cl3dcw_dcbond           = -1;
static gint          ett_cl3dcw_dcbond          = -1;
static expert_field  ei_cl3dcw_unknown_type     = EI_INIT;
static expert_field  ei_cl3dcw_nodc             = EI_INIT;
static expert_field  ei_cl3dcw_ssid_too_big     = EI_INIT;


/* message id types */
#define DCWMSG_STA_JOIN           0x01
#define DCWMSG_STA_UNJOIN         0x02
#define DCWMSG_STA_ACK            0x11
#define DCWMSG_STA_NACK           0x12
#define DCWMSG_AP_ACCEPT_STA      0x21
#define DCWMSG_AP_REJECT_STA      0x22
#define DCWMSG_AP_ACK_DISCONNECT  0x41
#define DCWMSG_AP_QUIT            0x99

/* message type strings */
static const value_string cl3dcw_msg_types[] = {
  {DCWMSG_STA_JOIN,          "Station Join"        },
  {DCWMSG_STA_UNJOIN,        "Station Unjoin"      },
  {DCWMSG_STA_ACK,           "Station Ack"         },
  {DCWMSG_STA_NACK,          "Station Nack"        },
  {DCWMSG_AP_ACCEPT_STA,     "AP Accept Station"   },
  {DCWMSG_AP_REJECT_STA,     "AP Reject Station"   },
  {DCWMSG_AP_ACK_DISCONNECT, "AP Ack Disconnect"   },
  {DCWMSG_AP_QUIT,           "AQ Quit"             },
  {0, NULL}
};


static gint
dissect_sta_join(tvbuff_t * const tvb, packet_info * const pinfo, proto_tree * const tree _U_, proto_item * const ti) {
  guint32 data_macaddr_count;
  gint    offset;

  proto_tree_add_item_ret_uint(tree, hf_cl3dcw_datamacaddrcount, tvb, 0, 1, ENC_NA, &data_macaddr_count);
  if (data_macaddr_count < 1) {
    expert_add_info(pinfo, ti, &ei_cl3dcw_nodc);
  }

  offset = 1;
  while(data_macaddr_count--) {
    proto_tree_add_item(tree, hf_cl3dcw_dcmacaddr, tvb, offset, 6, ENC_NA);
    offset += 6;
  }

  return offset;
}

static gint
dissect_sta_unjoin(tvbuff_t * const tvb, packet_info * const pinfo, proto_tree * const tree _U_, proto_item * const ti) {
  guint32 data_macaddr_count;
  gint    offset;

  proto_tree_add_item_ret_uint(tree, hf_cl3dcw_datamacaddrcount, tvb, 0, 1, ENC_NA, &data_macaddr_count);
  if (data_macaddr_count < 1) {
    expert_add_info(pinfo, ti, &ei_cl3dcw_nodc);
  }

  offset = 1;
  while (data_macaddr_count--) {
    proto_tree_add_item(tree, hf_cl3dcw_dcmacaddr, tvb, offset, 6, ENC_NA);
    offset += 6;
  }

  return offset;
}

static gint
dissect_sta_ack(tvbuff_t * const tvb, packet_info * const pinfo, proto_tree * const tree _U_, proto_item * const ti) {

  proto_item *bond_item;
  proto_tree *bond_tree;

  guint32  data_channel_count;
  guint8   ssid_len;
  guint8  *ssidbuf;

  gint     offset;

  proto_tree_add_item_ret_uint(tree, hf_cl3dcw_dccount, tvb, 0, 1, ENC_NA, &data_channel_count);
  if (data_channel_count < 1) {
    expert_add_info(pinfo, ti, &ei_cl3dcw_nodc);
  }

  offset = 1;
  while (data_channel_count--) {
    /* parse each data channel bond...
     * format is 6-byte mac addr + 1 byte ssid string length + ssid string
     */
    ssid_len = tvb_get_guint8(tvb, offset + 6); /* +6 = skip over mac address */
    if (ssid_len > SSID_MAX_LENGTH) {
      expert_add_info(pinfo, ti, &ei_cl3dcw_ssid_too_big);
    }
    ssidbuf = tvb_get_string_enc(pinfo->pool, tvb, offset + 6 + 1, ssid_len, ENC_ASCII); /* +6+1 = skip over mac address and length field */

    /* add the data channel bond sub-tree item */
    bond_item = proto_tree_add_item(tree, hf_cl3dcw_dcbond, tvb, offset, 6, ENC_NA);
    proto_item_append_text(bond_item, " -> \"%s\"", ssidbuf);
    proto_item_set_len(bond_item, 6 + 1 + ssid_len);
    bond_tree = proto_item_add_subtree(bond_item, ett_cl3dcw_dcbond);

    /* add the MAC address... */
    proto_tree_add_item(bond_tree, hf_cl3dcw_dcmacaddr, tvb, offset, 6, ENC_NA);
    offset += 6;

    /* add the SSID string
     * XXX the intent here is to highlight the leading length byte in the hex dump
     *     without printing it in the string... i suspect there is a better way of doing this
     */
    proto_tree_add_string_format(bond_tree, hf_cl3dcw_dcssid, tvb, offset, 1 + ssid_len,
                                 "", "Data Channel SSID: %s", ssidbuf);
    offset += 1 + ssid_len;
  }

  return offset;
}

static gint
dissect_sta_nack(tvbuff_t * const tvb, packet_info * const pinfo, proto_tree * const tree _U_, proto_item * const ti) {
  guint32  data_macaddr_count;
  gint     offset;

  proto_tree_add_item_ret_uint(tree, hf_cl3dcw_datamacaddrcount, tvb, 0, 1, ENC_NA, &data_macaddr_count);
  if (data_macaddr_count < 1) {
    expert_add_info(pinfo, ti, &ei_cl3dcw_nodc);
  }

  offset = 1;
  while (data_macaddr_count--) {
    proto_tree_add_item(tree, hf_cl3dcw_dcmacaddr, tvb, offset, 6, ENC_NA);
    offset += 6;
  }

  return offset;
}

static gint
dissect_ap_accept_sta(tvbuff_t * const tvb, packet_info * const pinfo, proto_tree * const tree _U_, proto_item * const ti) {

  guint32  data_ssid_count;
  guint8   ssid_len;
  guint8  *ssidbuf;

  gint     offset;

  proto_tree_add_item_ret_uint(tree, hf_cl3dcw_datassidcount, tvb, 0, 1, ENC_NA, &data_ssid_count);
  if (data_ssid_count < 1) {
    expert_add_info(pinfo, ti, &ei_cl3dcw_nodc);
  }

  offset = 1;
  while (data_ssid_count--) {
    ssid_len = tvb_get_guint8(tvb, offset);
    if (ssid_len > SSID_MAX_LENGTH) {
      expert_add_info(pinfo, ti, &ei_cl3dcw_ssid_too_big);
    }
    ssidbuf = tvb_get_string_enc(pinfo->pool, tvb, offset + 1, ssid_len, ENC_ASCII); /* +1 = skip over length field */

    /* add the SSID string
     * XXX the intent here is to highlight the leading length byte in the hex dump
     *     without printing it in the string... i suspect there is a better way of doing this
     */
    proto_tree_add_string_format(tree, hf_cl3dcw_dcssid, tvb, offset, 1 + ssid_len,
                                 "", "Data Channel SSID: %s", ssidbuf);
    offset += 1 + ssid_len;
  }

  return offset;
}

static gint
dissect_ap_reject_sta(tvbuff_t * const tvb, packet_info * const pinfo, proto_tree * const tree _U_, proto_item * const ti) {
  guint32 data_macaddr_count;
  gint    offset;

  proto_tree_add_item_ret_uint(tree, hf_cl3dcw_datamacaddrcount, tvb, 0, 1, ENC_NA, &data_macaddr_count);
  if (data_macaddr_count < 1) {
    expert_add_info(pinfo, ti, &ei_cl3dcw_nodc);
  }

  offset = 1;
  while (data_macaddr_count--) {
    proto_tree_add_item(tree, hf_cl3dcw_dcmacaddr, tvb, offset, 6, ENC_NA);
    offset += 6;
  }

  return offset;
}

/* called for each incomming framing matching our CL3 (sub-)protocol id: */
static int
dissect_cl3dcw(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_) {

  proto_item   *ti;
  proto_tree   *cl3dcw_tree;
  tvbuff_t     *tvb_msg;
  gint          total_dcw_message_len;

  guint8 type;

  /* parse the header fields */
  total_dcw_message_len = 1;
  type = tvb_get_guint8(tvb, 0);

  /* setup the "packet summary view" fields */
  col_set_str(pinfo->cinfo, COL_PROTOCOL, "CL3-DCW");
  col_clear(pinfo->cinfo, COL_INFO);
  col_add_fstr(pinfo->cinfo, COL_INFO, "Dual-Channel Wi-Fi %s [Type 0x%02X]", val_to_str_const(type, cl3dcw_msg_types, "Unknown"), (guint)type);

  /* create a tree node for us... */
  ti = proto_tree_add_protocol_format(tree, proto_cl3dcw, tvb, 0, tvb_captured_length(tvb), "Dual-Channel Wi-Fi Control Message");
  cl3dcw_tree = proto_item_add_subtree(ti, ett_cl3dcw);
  tvb_msg = tvb_new_subset_remaining(tvb, 1);

  /* display dcw fields: */
  proto_tree_add_uint(cl3dcw_tree, hf_cl3dcw_type, tvb, 0, 1, type);

  /* parse the message by type... */
  switch (type) {
  case DCWMSG_STA_JOIN:          total_dcw_message_len += dissect_sta_join(tvb_msg, pinfo, cl3dcw_tree, ti);        break;
  case DCWMSG_STA_UNJOIN:        total_dcw_message_len += dissect_sta_unjoin(tvb_msg, pinfo, cl3dcw_tree, ti);      break;
  case DCWMSG_STA_ACK:           total_dcw_message_len += dissect_sta_ack(tvb_msg, pinfo, cl3dcw_tree, ti);         break;
  case DCWMSG_STA_NACK:          total_dcw_message_len += dissect_sta_nack(tvb_msg, pinfo, cl3dcw_tree, ti);        break;
  case DCWMSG_AP_ACCEPT_STA:     total_dcw_message_len += dissect_ap_accept_sta(tvb_msg, pinfo, cl3dcw_tree, ti);   break;
  case DCWMSG_AP_REJECT_STA:     total_dcw_message_len += dissect_ap_reject_sta(tvb_msg, pinfo, cl3dcw_tree, ti);   break;
  case DCWMSG_AP_ACK_DISCONNECT: /* nothing to really dissect */ break;
  case DCWMSG_AP_QUIT:           /* nothing to really dissect */ break;
  default:
    expert_add_info(pinfo, ti, &ei_cl3dcw_unknown_type);
    return tvb_captured_length(tvb);
  }

  /* now that the individual message dissection functions have ran,
     update the tree item length so that the hex dissection dieplay
     highlighting does not include any ethernet frame padding */
  proto_item_set_len(ti, total_dcw_message_len);
  return total_dcw_message_len; /* is this correct ? */
}

/* initializes this dissector */
void
proto_register_cl3dcw(void) {
  static hf_register_info hf[] = {
    { &hf_cl3dcw_type,
      { "Type",                            "cl3dcw.type",
        FT_UINT8,      BASE_HEX,           VALS(cl3dcw_msg_types), 0x0,
        NULL, HFILL }},
    { &hf_cl3dcw_dccount,
      { "Data Channel Count",             "cl3dcw.dccount",
        FT_UINT8,      BASE_DEC,           NULL, 0x0,
        NULL, HFILL }},
    { &hf_cl3dcw_datamacaddrcount,
      { "Data MAC Address Count",          "cl3dcw.datamacaddrcount",
        FT_UINT8,      BASE_DEC,           NULL, 0x0,
        NULL, HFILL }},
    { &hf_cl3dcw_datassidcount,
      { "Data SSID Count",                 "cl3dcw.datassidcount",
        FT_UINT8,      BASE_DEC,           NULL, 0x0,
        NULL, HFILL }},
    { &hf_cl3dcw_dcmacaddr,
      { "Data Channel MAC Address",        "cl3dcw.dcmacaddr",
        FT_ETHER,      BASE_NONE,          NULL, 0x0,
        NULL, HFILL }},
    { &hf_cl3dcw_dcssid,
      { "Data Channel SSID",               "cl3dcw.dcssid",
        FT_STRING,     BASE_NONE,          NULL, 0x0,
        NULL, HFILL }},
    { &hf_cl3dcw_dcbond,
      { "Data Channel Bond",               "cl3dcw.dcbond",
        FT_BYTES,      SEP_COLON,          NULL, 0x0,
        NULL, HFILL }},
  };
  static gint *ett[] = {
    &ett_cl3dcw,
    &ett_cl3dcw_dcbond,
  };
  static ei_register_info ei[] = {
     { &ei_cl3dcw_unknown_type,   { "cl3dcw.unknown_type",       PI_MALFORMED, PI_ERROR, "Unknown DCW message type", EXPFILL }},
     { &ei_cl3dcw_nodc,           { "cl3dcw.no_data_channels",   PI_MALFORMED, PI_WARN,  "No data-channels provided", EXPFILL }},
     { &ei_cl3dcw_ssid_too_big,   { "cl3dcw.ssid_too_big",       PI_MALFORMED, PI_WARN,  "Data channel SSID too big (expecting 32-byte maximum SSID)", EXPFILL }},
  };

  expert_module_t* expert_cl3dcw;

  proto_cl3dcw = proto_register_protocol("CableLabs Dual-Channel Wi-Fi", "cl3dcw", "cl3dcw");

  proto_register_field_array(proto_cl3dcw, hf, array_length(hf));
  proto_register_subtree_array(ett, array_length(ett));
  expert_cl3dcw = expert_register_protocol(proto_cl3dcw);
  expert_register_field_array(expert_cl3dcw, ei, array_length(ei));

  cl3dcw_handle = register_dissector("cl3dcw", &dissect_cl3dcw, proto_cl3dcw);
}

/* hooks in our dissector to be called on matching CL3 (sub-)protocol id */
void
proto_reg_handoff_cl3dcw(void) {
  dissector_add_uint("cl3.subprotocol", 0x00DC, cl3dcw_handle);
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 2
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=2 tabstop=8 expandtab:
 * :indentSize=2:tabSize=8:noTabs=true:
 */