ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
promisc.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file promisc.cpp
6 * @brief promisc implementation: the pure 802.11 header parser + PCAP framing, and the ESP32
7 * esp_wifi promiscuous binding. The parser / PCAP builders are host-identical.
8 */
9
12
13#if PC_ENABLE_PROMISC
14
15#include <string.h>
16
17#if defined(ARDUINO)
18#include <esp_wifi.h>
19#endif
20bool wifi_frame_parse(const uint8_t *frame, uint16_t len, WifiFrameInfo *out)
21{
22 if (!frame || !out || len < 10) // FC(2) + Duration(2) + Addr1(6) - the shortest control frame
23 {
24 return false;
25 }
26 memset(out, 0, sizeof(*out));
27
28 const uint8_t fc0 = frame[0];
29 const uint8_t fc1 = frame[1];
30 out->type = (WifiFrameType)((fc0 >> 2) & 0x3);
31 out->subtype = (uint8_t)((fc0 >> 4) & 0xF);
32 out->to_ds = (fc1 & 0x01) != 0;
33 out->from_ds = (fc1 & 0x02) != 0;
34 out->protected_frame = (fc1 & 0x40) != 0;
35
36 if (out->type == WifiFrameType::WIFI_FT_CTRL)
37 {
38 // Control frames carry only Addr1 (the receiver); the rest vary by subtype.
39 out->dst = frame + 4;
40 out->hdr_len = 10;
41 return true;
42 }
43
44 // Management / data / extension frames carry the full 3-address header + sequence control.
45 if (len < 24)
46 {
47 return false;
48 }
49 out->seq = (uint16_t)(((uint16_t)frame[22] | ((uint16_t)frame[23] << 8)) >> 4);
50 out->is_qos = (out->type == WifiFrameType::WIFI_FT_DATA) && (out->subtype & 0x08) != 0;
51
52 const bool has_addr4 = out->to_ds && out->from_ds; // WDS
53 uint16_t hlen = 24;
54 if (has_addr4)
55 {
56 hlen += 6;
57 }
58 if (out->is_qos)
59 {
60 hlen += 2;
61 }
62 if (out->is_qos && (fc1 & 0x80)) // Order bit on a QoS data frame -> HT Control field
63 {
64 hlen += 4;
65 }
66 if (len < hlen)
67 {
68 return false;
69 }
70 out->hdr_len = hlen;
71
72 const uint8_t *a1 = frame + 4;
73 const uint8_t *a2 = frame + 10;
74 const uint8_t *a3 = frame + 16;
75 if (!out->to_ds && !out->from_ds) // IBSS / management
76 {
77 out->dst = a1;
78 out->src = a2;
79 out->bssid = a3;
80 }
81 // !to_ds && !from_ds is unreachable below: the `if` above already caught that combination.
82 else if (!out->to_ds && out->from_ds) // from the AP // GCOVR_EXCL_BR_LINE
83 {
84 out->dst = a1;
85 out->bssid = a2;
86 out->src = a3;
87 }
88 // to_ds == false is unreachable below: ruled out by the two branches above.
89 else if (out->to_ds && !out->from_ds) // to the AP // GCOVR_EXCL_BR_LINE
90 {
91 out->bssid = a1;
92 out->src = a2;
93 out->dst = a3;
94 }
95 else // WDS (4-address): A1=RA, A2=TA, A3=DA, A4=SA
96 {
97 out->dst = a3;
98 out->src = frame + 24;
99 out->bssid = nullptr;
100 }
101 return true;
102}
103
104// libpcap framing (pc_pcap_global_header / pc_pcap_record_header) is in
105// shared_primitives/pcap.h - shared with the other capture features.
106
107// --- ESP32 radio binding -----------------------------------------------------------------
108#ifdef ARDUINO
109
110namespace
111{
112// All promiscuous-capture state, owned by one instance (internal linkage): the frame sink.
113// One named owner, unreachable from any other translation unit.
114struct PromiscCtx
115{
116 pc_promisc_sink_fn sink = nullptr;
117};
118PromiscCtx s_promisc;
119
120} // namespace
121
122bool pc_promisc_begin(uint8_t channel, pc_promisc_sink_fn sink)
123{
124 if (!sink)
125 {
126 return false;
127 }
128 s_promisc.sink = sink;
129 // pc_promisc_sink_fn and pc_phy_frame_fn are the same neutral shape, so the sink goes
130 // straight down; the vendor packet struct is unwrapped in the backend.
131 if (!pc_phy_monitor_begin(channel, sink))
132 {
133 s_promisc.sink = nullptr;
134 return false;
135 }
136 return true;
137}
138
139void pc_promisc_set_channel(uint8_t channel)
140{
142}
143
144void pc_promisc_end(void)
145{
147 s_promisc.sink = nullptr;
148}
149
150#else // host build - no radio
151
152bool pc_promisc_begin(uint8_t, pc_promisc_sink_fn)
153{
154 return false;
155}
156void pc_promisc_set_channel(uint8_t)
157{
158 // host build: no radio, no channel to set
159}
160void pc_promisc_end(void)
161{
162 // host build: no radio, nothing to stop
163}
164
165#endif // ARDUINO
166
167#endif // PC_ENABLE_PROMISC
bool pc_phy_monitor_begin(uint8_t, pc_phy_frame_fn)
Enter monitor mode on channel, delivering frames to cb.
Definition physical.cpp:116
void pc_phy_monitor_end(void)
Leave monitor mode.
Definition physical.cpp:123
void pc_phy_monitor_set_channel(uint8_t)
Retune monitor mode to channel.
Definition physical.cpp:120
Layer 1 (Physical) - link bring-up and live egress-interface reporting.
Wi-Fi promiscuous (monitor) capture (PC_ENABLE_PROMISC) - passive 802.11 sniffing.