Coverage Report

Created: 2026-05-14 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ntopng/src/Mac.cpp
Line
Count
Source
1
/*
2
 *
3
 * (C) 2013-26 - ntop.org
4
 *
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software Foundation,
18
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
 *
20
 */
21
22
#include "ntop_includes.h"
23
24
/* *************************************** */
25
26
Mac::Mac(NetworkInterface* _iface, u_int8_t _mac[6])
27
18.1k
    : GenericHashEntry(_iface) {
28
18.1k
  if (trace_new_delete)
29
0
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[new] %s", __FILE__);
30
18.1k
  memcpy(mac, _mac, 6);
31
32
18.1k
  broadcast_mac = Utils::isBroadcastMac(mac);
33
18.1k
  special_mac = Utils::isSpecialMac(mac);
34
18.1k
  empty_mac = Utils::isEmptyMac(mac);
35
18.1k
  source_mac = false;
36
18.1k
  bridge_seen_iface_id = 0, lockDeviceTypeChanges = false;
37
18.1k
  memset(&names, 0, sizeof(names));
38
18.1k
  device_type = device_unknown, asset_map_updated = true;
39
18.1k
  host_pool_id = NO_HOST_POOL_ID;
40
18.1k
  device_os = ndpi_os_unknown;
41
#ifdef NTOPNG_PRO
42
  captive_portal_notified = 0;
43
#endif
44
18.1k
  model = NULL, ssid = NULL, dhcpv4_fingerprint = NULL;
45
18.1k
  stats_reset_requested = data_delete_requested = false;
46
18.1k
  stats = new (std::nothrow) MacStats(_iface);
47
18.1k
  stats_shadow = NULL;
48
18.1k
  last_stats_reset = ntop->getLastStatsReset(); /* assume fresh stats, may be
49
                                                   changed by deserialize */
50
#ifdef HAVE_NEDGE
51
  last_counter_reset = 0;
52
#endif
53
54
18.1k
  if (ntop->getMacManufacturers()) {
55
0
    manuf = ntop->getMacManufacturers()->getManufacturer(mac);
56
0
    if (manuf) checkDeviceTypeFromManufacturer();
57
0
  } else
58
18.1k
    manuf = NULL;
59
60
#ifdef MANUF_DEBUG
61
  ntop->getTrace()->traceEvent(
62
      TRACE_NORMAL,
63
      "Assigned manufacturer [mac: %02x:%02x:%02x:%02x:%02x:%02x] "
64
      "[manufacturer: %s]",
65
      mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
66
      manuf ? manuf : "- not available -");
67
#endif
68
69
#ifdef DEBUG
70
  char buf[32];
71
72
  ntop->getTrace()->traceEvent(TRACE_NORMAL, "Created %s [total %u]",
73
                               Utils::formatMac(mac, buf, sizeof(buf)),
74
                               iface->getNumL2Devices());
75
#endif
76
77
18.1k
  updateHostPool(true /* inline with packet processing */,
78
18.1k
                 true /* first inc */);
79
80
18.1k
  readDHCPCache();
81
82
18.1k
  if (device_type == device_unknown) guessDeviceType();
83
84
#ifdef NTOPNG_PRO
85
  dumpMacInfo(false /* Do not add last seen */);
86
#endif
87
18.1k
}
88
89
/* *************************************** */
90
91
18.1k
Mac::~Mac() {
92
18.1k
  if (trace_new_delete)
93
0
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[delete] %s", __FILE__);
94
95
#ifdef NTOPNG_PRO
96
  dumpMacInfo(true /* Add last seen */);
97
#endif
98
99
18.1k
  if (model) free(model);
100
18.1k
  if (ssid) free(ssid);
101
18.1k
  if (dhcpv4_fingerprint) free(dhcpv4_fingerprint);
102
18.1k
  freeMacData();
103
18.1k
  if (stats) delete (stats);
104
18.1k
  if (stats_shadow) delete (stats_shadow);
105
106
#ifdef DEBUG
107
  ntop->getTrace()->traceEvent(TRACE_NORMAL, "Deleted %s [total %u][%s]",
108
                               Utils::formatMac(mac, buf, sizeof(buf)),
109
                               iface->getNumL2Devices(),
110
                               source_mac ? "Host" : "Special");
111
#endif
112
18.1k
}
113
114
/* *************************************** */
115
116
1.36k
void Mac::set_hash_entry_state_idle() {
117
1.36k
  if (source_mac) iface->decNumL2Devices();
118
119
  /* Pool counters are updated both in and outside the datapath.
120
     So decPoolNumHosts must stay in the destructor to preserve counters
121
     consistency (no thread outside the datapath will change the last pool id)
122
   */
123
#ifdef HOST_POOLS_DEBUG
124
  char buf[32];
125
  ntop->getTrace()->traceEvent(
126
      TRACE_NORMAL,
127
      "Going to decrease the number of pool l2 devices for %s "
128
      "[num pool l2 devices: %u]...",
129
      Utils::formatMac(mac, buf, sizeof(buf)),
130
      iface->getHostPools()->getNumPoolL2Devices(get_host_pool()));
131
#endif
132
133
1.36k
  iface->decPoolNumL2Devices(get_host_pool(), true /* Mac is deleted inline */);
134
135
#ifdef HOST_POOLS_DEBUG
136
  ntop->getTrace()->traceEvent(
137
      TRACE_NORMAL,
138
      "Number of pool l2 devices decreased."
139
      "[num pool l2 devices: %u]",
140
      iface->getHostPools()->getNumPoolL2Devices(get_host_pool()));
141
#endif
142
143
1.36k
  GenericHashEntry::set_hash_entry_state_idle();
144
1.36k
}
145
146
/* *************************************** */
147
148
#ifdef HAVE_NEDGE
149
static const char* location2str(MacLocation location) {
150
  switch (location) {
151
    case located_on_lan_interface:
152
      return "lan";
153
    case located_on_wan_interface:
154
      return "wan";
155
    default:
156
      return "unknown";
157
  }
158
}
159
#endif
160
161
/* *************************************** */
162
163
0
void Mac::lua(lua_State* vm, bool show_details, bool asListElement) {
164
0
  char buf[32], *m;
165
166
0
  lua_newtable(vm);
167
168
0
  lua_push_str_table_entry(vm, "mac",
169
0
                           m = Utils::formatMac(mac, buf, sizeof(buf)));
170
0
  lua_push_uint64_table_entry(vm, "bridge_seen_iface_id", bridge_seen_iface_id);
171
172
0
  if (show_details) {
173
0
    if (manuf) lua_push_str_table_entry(vm, "manufacturer", (char*)manuf);
174
175
0
    lua_push_bool_table_entry(vm, "source_mac", source_mac);
176
0
    lua_push_bool_table_entry(vm, "special_mac", special_mac);
177
#ifdef HAVE_NEDGE
178
    lua_push_str_table_entry(vm, "location", (char*)location2str(locate()));
179
#endif
180
0
    lua_push_uint64_table_entry(vm, "devtype", device_type);
181
0
    if (model) lua_push_str_table_entry(vm, "model", (char*)model);
182
0
    if (ssid) lua_push_str_table_entry(vm, "ssid", (char*)ssid);
183
0
  }
184
185
0
  if (stats) stats->lua(vm, show_details);
186
187
0
  if (dhcpv4_fingerprint)
188
0
    lua_push_str_table_entry(vm, "dhcp_fingerprint", dhcpv4_fingerprint);
189
190
0
  lua_push_uint64_table_entry(vm, "seen.first", first_seen);
191
0
  lua_push_uint64_table_entry(vm, "seen.last", last_seen);
192
0
  lua_push_uint64_table_entry(vm, "duration", get_duration());
193
0
  lua_push_uint64_table_entry(vm, "num_hosts", getNumHosts());
194
0
  lua_push_uint64_table_entry(vm, "pool", get_host_pool());
195
#ifdef HAVE_NEDGE
196
  lua_push_uint32_table_entry(vm, "last_counter_reset", last_counter_reset);
197
198
  if (events.size() > 0) {
199
    u_int i = 1;
200
201
    lua_newtable(vm);
202
203
    for (std::vector<std::string>::iterator it = events.begin();
204
         it != events.end(); ++it, i++) {
205
      lua_pushstring(vm, it->c_str());
206
      lua_rawseti(vm, -2, i);
207
    }
208
209
    lua_pushstring(vm, "events");
210
    lua_insert(vm, -2);
211
    lua_settable(vm, -3);
212
  }
213
#endif
214
215
0
  if (asListElement) {
216
0
    lua_pushstring(vm, m);
217
0
    lua_insert(vm, -2);
218
0
    lua_settable(vm, -3);
219
0
  }
220
0
}
221
/* *************************************** */
222
223
1.63M
bool Mac::isNull() const {
224
1.63M
  u_int8_t zero_mac[6] = {0};
225
226
1.63M
  return (memcmp(mac, zero_mac, sizeof(zero_mac)) == 0 ? true : false);
227
1.63M
}
228
229
/* *************************************** */
230
231
203k
bool Mac::equal(const u_int8_t _mac[6]) {
232
203k
  if (!_mac) return (false);
233
203k
  if (memcmp(mac, _mac, 6) == 0)
234
203k
    return (true);
235
739
  else
236
739
    return (false);
237
203k
}
238
239
/* *************************************** */
240
241
0
char* Mac::getSerializationKey(char* buf, u_int bufsize, bool short_format) {
242
0
  char buf1[32];
243
0
  char* mac_ptr = Utils::formatMac(mac, buf1, sizeof(buf1));
244
245
0
  snprintf(buf, bufsize,
246
0
           short_format ? MAC_SERIALIZED_SHORT_KEY : MAC_SERIALIZED_KEY,
247
0
           iface->get_id(), mac_ptr);
248
0
  return (buf);
249
0
}
250
251
/* *************************************** */
252
253
2.98k
bool Mac::statsResetRequested() {
254
2.98k
  return (stats_reset_requested ||
255
2.98k
          (last_stats_reset < ntop->getLastStatsReset()));
256
2.98k
}
257
258
/* *************************************** */
259
260
#ifdef HAVE_NEDGE
261
MacLocation Mac::locate() {
262
  if (iface->is_bridge_interface()) {
263
    InterfaceLocation location =
264
        iface->getInterfaceLocation(bridge_seen_iface_id);
265
    if (location == lan_interface)
266
      return (located_on_lan_interface);
267
    else if (location == wan_interface)
268
      return (located_on_wan_interface);
269
  } else {
270
    if (bridge_seen_iface_id == DUMMY_BRIDGE_INTERFACE_ID)
271
      return (located_on_lan_interface);
272
  }
273
274
  return (located_on_unknown_interface);
275
}
276
#endif
277
278
/* *************************************** */
279
280
18.1k
void Mac::updateHostPool(bool isInlineCall, bool firstUpdate) {
281
18.1k
  if (!iface) return;
282
283
#ifdef HOST_POOLS_DEBUG
284
  char buf[24];
285
  u_int16_t cur_pool_id = get_host_pool();
286
287
  ntop->getTrace()->traceEvent(
288
      TRACE_NORMAL,
289
      "Going to refresh pool for %s "
290
      "[pool id: %u]"
291
      "[pool num devices: %u]...",
292
      Utils::formatMac(get_mac(), buf, sizeof(buf)), cur_pool_id,
293
      iface->getHostPools()->getNumPoolL2Devices(get_host_pool()));
294
#endif
295
296
18.1k
  if (!firstUpdate) iface->decPoolNumL2Devices(get_host_pool(), isInlineCall);
297
18.1k
  host_pool_id = iface->getHostPool(this);
298
18.1k
  iface->incPoolNumL2Devices(get_host_pool(), isInlineCall);
299
300
#ifdef HOST_POOLS_DEBUG
301
  ntop->getTrace()->traceEvent(
302
      TRACE_NORMAL,
303
      "Refresh done. "
304
      "[old pool id: %u]"
305
      "[new pool id: %u]"
306
      "[old pool num devices: %u]"
307
      "[new pool num devices: %u]",
308
      cur_pool_id, get_host_pool(),
309
      iface->getHostPools()->getNumPoolL2Devices(cur_pool_id),
310
      iface->getHostPools()->getNumPoolL2Devices(get_host_pool()));
311
#endif
312
18.1k
}
313
314
/* *************************************** */
315
316
0
char* Mac::getDHCPName(char* const buf, ssize_t buf_size) {
317
0
  if (buf && buf_size) {
318
0
    m.lock(__FILE__, __LINE__);
319
0
    snprintf(buf, buf_size, "%s", names.dhcp ? names.dhcp : "");
320
0
    m.unlock(__FILE__, __LINE__);
321
0
  }
322
323
0
  return Utils::stringtolower(buf);
324
0
}
325
326
/* *************************************** */
327
328
281
char* Mac::getDHCPNameNotLowerCase(char* const buf, ssize_t buf_size) {
329
281
  if (buf && buf_size) {
330
281
    m.lock(__FILE__, __LINE__);
331
281
    snprintf(buf, buf_size, "%s", names.dhcp ? names.dhcp : "");
332
281
    m.unlock(__FILE__, __LINE__);
333
281
  }
334
335
281
  return ((char*)buf);
336
281
}
337
338
/* *************************************** */
339
340
0
void Mac::checkDeviceTypeFromManufacturer() {
341
0
  if (isNull()) return;
342
343
0
  if (strstr(manuf, "Networks") /* Arista, Juniper... */
344
0
      || strstr(manuf, "Brocade") || strstr(manuf, "Routerboard") ||
345
0
      strstr(manuf, "Alcatel-Lucent") || strstr(manuf, "AVM"))
346
0
    setDeviceType(device_networking);
347
0
  else if (strstr(manuf, "Xerox"))
348
0
    setDeviceType(device_printer);
349
0
  else if (strstr(manuf, "Raspberry Pi") ||
350
0
           strstr(manuf, "PCS Computer Systems") /* VirtualBox */
351
0
  )
352
0
    setDeviceType(device_workstation);
353
0
  else {
354
    /* https://www.techrepublic.com/blog/data-center/mac-address-scorecard-for-common-virtual-machine-platforms/
355
     */
356
357
0
    if ((!memcmp(mac, "\x00\x50\x56", 3)) ||
358
0
        (!memcmp(mac, "\x00\x0C\x29", 3)) ||
359
0
        (!memcmp(mac, "\x00\x05\x69", 3)) ||
360
0
        (!memcmp(mac, "\x00\x03\xFF", 3)) ||
361
0
        (!memcmp(mac, "\x00\x1C\x42", 3)) ||
362
0
        (!memcmp(mac, "\x00\x0F\x4B", 3)) ||
363
0
        (!memcmp(mac, "\x00\x16\x3E", 3)) || (!memcmp(mac, "\x08\x00\x27", 3)))
364
0
      setDeviceType(device_workstation); /* VM */
365
0
  }
366
0
}
367
368
/* *************************************** */
369
370
562k
void Mac::inlineSetModel(const char* the_model) {
371
562k
  if (!model && the_model && (model = strdup(the_model))) {
372
34
    if (strstr(model, "AppleTV") != NULL)
373
1
      setDeviceType(device_multimedia);
374
33
    else if (strstr(model, "MacBook") != NULL)
375
1
      setDeviceType(device_laptop);
376
32
    else if (strstr(model, "AirPort") != NULL)
377
1
      setDeviceType(device_wifi);
378
31
    else if (strstr(model, "Mac") != NULL)
379
5
      setDeviceType(device_workstation);
380
26
    else if (strstr(model, "TimeCapsule") != NULL)
381
1
      setDeviceType(device_nas);
382
34
  }
383
562k
}
384
/* *************************************** */
385
386
92.8k
void Mac::inlineSetSSID(const char* s) {
387
92.8k
  if (!ssid && s && (ssid = strdup(s))) setDeviceType(device_wifi);
388
92.8k
}
389
390
/* *************************************** */
391
392
241
void Mac::inlineSetDHCPName(const char* dhcp_name) {
393
241
  if (!names.dhcp && dhcp_name && (names.dhcp = strdup(dhcp_name)))
394
98
    asset_map_updated = true;
395
241
}
396
397
/* *************************************** */
398
399
2.98k
void Mac::checkDataReset() {
400
2.98k
  if (data_delete_requested) {
401
0
    deleteMacData();
402
0
    data_delete_requested = false;
403
0
  }
404
2.98k
}
405
406
/* *************************************** */
407
408
2.98k
void Mac::checkStatsReset() {
409
2.98k
  if (statsResetRequested()) {
410
0
    MacStats* new_stats = new (std::nothrow) MacStats(iface);
411
412
0
    stats_shadow = stats;
413
0
    stats = new_stats;
414
0
    last_stats_reset = ntop->getLastStatsReset();
415
0
    stats_reset_requested = false;
416
417
#ifdef HAVE_NEDGE
418
    char buf[32];
419
420
    ntop->getTrace()->traceEvent(TRACE_INFO, "Reset stats for MAC %s",
421
                                 print(buf, sizeof(buf)));
422
    last_counter_reset = time(NULL);
423
#endif
424
0
  }
425
2.98k
}
426
427
/* *************************************** */
428
429
2.98k
void Mac::periodic_stats_update(const struct timeval* tv, bool force_update) {
430
2.98k
  checkDataReset();
431
2.98k
  checkStatsReset();
432
2.98k
  if (stats) stats->updateStats(tv);
433
2.98k
}
434
435
/* *************************************** */
436
437
18.1k
void Mac::readDHCPCache() {
438
  /* Check DHCP cache */
439
18.1k
  char mac_str[24], buf[64], key[CONST_MAX_LEN_REDIS_KEY];
440
441
18.1k
  if (!names.dhcp && !isNull()) {
442
5.74k
    Utils::formatMac(get_mac(), mac_str, sizeof(mac_str));
443
444
5.74k
    snprintf(key, sizeof(key), DHCP_CACHE, iface->get_id(), mac_str);
445
446
5.74k
    if (ntop->getRedis()->get(key, buf, sizeof(buf)) == 0) {
447
84
      names.dhcp = strdup(buf);
448
84
    }
449
5.74k
  }
450
18.1k
}
451
452
/* *************************************** */
453
454
18.1k
void Mac::freeMacData() {
455
  // TODO: allow fingerprint, ssid, and model to be resettable
456
18.1k
  if (names.dhcp) {
457
182
    free(names.dhcp);
458
182
    names.dhcp = NULL;
459
182
  }
460
18.1k
}
461
462
/* *************************************** */
463
464
0
void Mac::deleteMacData() {
465
0
  m.lock(__FILE__, __LINE__);
466
0
  freeMacData();
467
0
  m.unlock(__FILE__, __LINE__);
468
0
  source_mac = false;
469
0
  device_type = device_unknown;
470
#ifdef NTOPNG_PRO
471
  captive_portal_notified = false;
472
#endif
473
0
  first_seen = last_seen;
474
0
}
475
476
/* *************************************** */
477
478
0
u_int64_t Mac::get_mac64() { return Utils::encodeMacTo64(mac); }
479
480
/* *************************************** */
481
482
2.98k
bool Mac::is_hash_entry_state_idle_transition_ready() {
483
  /*  ntop->getTrace()->traceEvent(TRACE_NORMAL,
484
        "Is idle, current time, last seen, configured expiration: "
485
        "[ %s | %d | %d | %d ]",
486
        is_active_entry_now_idle(ntop->getPrefs()->macAddressCacheDuration())
487
            ? "true"
488
            : "false",
489
        time(NULL), last_seen, ntop->getPrefs()->macAddressCacheDuration());
490
  */
491
2.98k
  return ((getUses() == 0) && is_active_entry_now_idle(
492
1.40k
                                  ntop->getPrefs()->macAddressCacheDuration()));
493
2.98k
}
494
495
/* *************************************** */
496
497
0
void Mac::setDHCPFingerprint(const char* f) {
498
0
  if ((f == NULL) || (f[0] == '\0')) return;
499
500
0
  if (dhcpv4_fingerprint != NULL) free(dhcpv4_fingerprint);
501
502
0
  dhcpv4_fingerprint = strdup(f);
503
504
#ifdef NTOPNG_PRO
505
  analyzeDevice();
506
#endif
507
0
}
508
509
/* *************************************** */
510
511
18.1k
void Mac::guessDeviceType() {
512
18.1k
  if (manuf == NULL) return;
513
514
  /* ntop->getTrace()->traceEvent(TRACE_ERROR, "*** %s", manuf); */
515
516
0
  if (strncasecmp(manuf, "Sonos", 5) == 0)
517
0
    device_type = device_wifi;
518
0
  else if ((strncasecmp(manuf, "Tp-Link", 7) == 0) ||
519
0
           (strncasecmp(manuf, "Technicolor", 11) == 0))
520
0
    device_type = device_networking;
521
0
  else if (strncasecmp(manuf, "ASUSTek", 7) == 0)
522
0
    device_type = device_workstation;
523
0
}
524
525
/* *************************************** */
526
527
1.60M
void Mac::setDeviceType(DeviceType devtype) {
528
1.60M
  if (isNull() || (device_type == devtype)) return;
529
530
  /* Called by ntopng when it can guess a device type during normal packet
531
   * processing */
532
58
  if (!lockDeviceTypeChanges) {
533
58
    device_type = devtype;
534
58
    asset_map_updated = true;
535
58
    ntop->trackAssetChange("MAC", "setDeviceType", this, NULL, NULL, NULL,
536
58
                           (char*)Utils::deviceType2str(devtype));
537
58
  }
538
58
}
539
540
/* *************************************** */
541
542
0
void Mac::setDeviceOS(ndpi_os _os) {
543
0
  if (device_os == _os) return;
544
545
0
  device_os = _os, asset_map_updated = true;
546
0
  ntop->trackAssetChange("MAC", "setDeviceOS", this, NULL, NULL, NULL,
547
0
                         (char*)Utils::OS2Str(_os));
548
0
}
549
550
/* *************************************** */
551
552
#ifdef HAVE_NEDGE
553
void Mac::logMacEvent(char* msg) {
554
  char buf[512], theDate[32];
555
  time_t theTime = time(NULL);
556
  struct tm result;
557
558
  strftime(theDate, sizeof(theDate), "%d/%b/%Y %H:%M:%S",
559
           localtime_r(&theTime, &result));
560
  snprintf(buf, sizeof(buf), "%s %s", theDate, msg);
561
562
  events.insert(events.begin(), buf); /* Asdds a message at the beginning */
563
564
  if (events.size() > 25 /* max number of events */)
565
    events.pop_back(); /* Deletes last element */
566
}
567
#endif