1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! ieee80211 frames
16
17 // TODO: only allow the warnings for the included code
18 #![allow(clippy::all)]
19 #![allow(missing_docs)]
20 #![allow(unused)]
21 include!(concat!(env!("OUT_DIR"), "/ieee80211_packets.rs"));
22
23 use anyhow::anyhow;
24
25 /// A Ieee80211 MAC address
26
27 impl MacAddress {
to_vec(&self) -> [u8; 6]28 pub fn to_vec(&self) -> [u8; 6] {
29 u64::to_le_bytes(self.0)[0..6].try_into().expect("slice with incorrect length")
30 }
31 }
32
33 // TODO: Add unit tests.
34 impl fmt::Display for MacAddress {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 let bytes = u64::to_le_bytes(self.0);
37 write!(
38 f,
39 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
40 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5],
41 )
42 }
43 }
44
45 impl fmt::Display for Ieee80211 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(
48 f,
49 "{{ds: {}, src: {}, dst: {}}}",
50 self.get_ds(),
51 self.get_source(),
52 self.get_destination()
53 )
54 }
55 }
56
57 impl From<&[u8; 6]> for MacAddress {
from(bytes: &[u8; 6]) -> Self58 fn from(bytes: &[u8; 6]) -> Self {
59 Self(u64::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], 0, 0]))
60 }
61 }
62
63 impl From<MacAddress> for [u8; 6] {
from(MacAddress(addr): MacAddress) -> Self64 fn from(MacAddress(addr): MacAddress) -> Self {
65 let bytes = u64::to_le_bytes(addr);
66 bytes[0..6].try_into().unwrap()
67 }
68 }
69
70 impl MacAddress {
is_multicast(&self) -> bool71 pub fn is_multicast(&self) -> bool {
72 let addr = u64::to_le_bytes(self.0);
73 (addr[0] & 0x1) == 1
74 }
75
is_broadcast(&self) -> bool76 pub fn is_broadcast(&self) -> bool {
77 let addr = u64::to_le_bytes(self.0);
78 addr[0] == 0xff
79 }
80 }
81
82 impl Ieee80211 {
83 // Frame has addr4 field
has_a4(&self) -> bool84 pub fn has_a4(&self) -> bool {
85 self.to_ds == 1 || self.from_ds == 1
86 }
87
is_to_ap(&self) -> bool88 pub fn is_to_ap(&self) -> bool {
89 self.to_ds == 1 && self.from_ds == 0
90 }
91
92 // Frame type is management
is_mgmt(&self) -> bool93 pub fn is_mgmt(&self) -> bool {
94 self.ftype == FrameType::Mgmt
95 }
96
97 // Frame type is data
is_data(&self) -> bool98 pub fn is_data(&self) -> bool {
99 self.ftype == FrameType::Data
100 }
101
102 // Frame is probe request
is_probe_req(&self) -> bool103 pub fn is_probe_req(&self) -> bool {
104 self.ftype == FrameType::Ctl && self.stype == (ManagementSubType::ProbeReq as u8)
105 }
106
get_ds(&self) -> String107 pub fn get_ds(&self) -> String {
108 match self.specialize().unwrap() {
109 Ieee80211Child::Ieee80211ToAp(hdr) => "ToAp",
110 Ieee80211Child::Ieee80211FromAp(hdr) => "FromAp",
111 Ieee80211Child::Ieee80211Ibss(hdr) => "Ibss",
112 Ieee80211Child::Ieee80211Wds(hdr) => "Wds",
113 _ => panic!("unexpected specialized header"),
114 }
115 .to_string()
116 }
117
get_source(&self) -> MacAddress118 pub fn get_source(&self) -> MacAddress {
119 match self.specialize().unwrap() {
120 Ieee80211Child::Ieee80211ToAp(hdr) => hdr.source,
121 Ieee80211Child::Ieee80211FromAp(hdr) => hdr.source,
122 Ieee80211Child::Ieee80211Ibss(hdr) => hdr.source,
123 Ieee80211Child::Ieee80211Wds(hdr) => hdr.source,
124 _ => panic!("unexpected specialized header"),
125 }
126 }
127
128 /// Ieee80211 packets have 3-4 addresses in different positions based
129 /// on the FromDS and ToDS flags. This function gets the destination
130 /// address depending on the FromDS+ToDS packet subtypes.
get_destination(&self) -> MacAddress131 pub fn get_destination(&self) -> MacAddress {
132 match self.specialize().unwrap() {
133 Ieee80211Child::Ieee80211ToAp(hdr) => hdr.destination,
134 Ieee80211Child::Ieee80211FromAp(hdr) => hdr.destination,
135 Ieee80211Child::Ieee80211Ibss(hdr) => hdr.destination,
136 Ieee80211Child::Ieee80211Wds(hdr) => hdr.destination,
137 _ => panic!("unexpected specialized header"),
138 }
139 }
140
get_bssid(&self) -> Option<MacAddress>141 pub fn get_bssid(&self) -> Option<MacAddress> {
142 match self.specialize().unwrap() {
143 Ieee80211Child::Ieee80211ToAp(hdr) => Some(hdr.bssid),
144 Ieee80211Child::Ieee80211FromAp(hdr) => Some(hdr.bssid),
145 Ieee80211Child::Ieee80211Ibss(hdr) => Some(hdr.bssid),
146 Ieee80211Child::Ieee80211Wds(hdr) => None,
147 _ => panic!("unexpected specialized header"),
148 }
149 }
150
with_address( &self, source: Option<MacAddress>, destination: Option<MacAddress>, ) -> Ieee80211151 pub fn with_address(
152 &self,
153 source: Option<MacAddress>,
154 destination: Option<MacAddress>,
155 ) -> Ieee80211 {
156 match self.specialize().unwrap() {
157 Ieee80211Child::Ieee80211ToAp(frame) => {
158 frame.with_address(source, destination).try_into().unwrap()
159 }
160 Ieee80211Child::Ieee80211FromAp(frame) => {
161 frame.with_address(source, destination).try_into().unwrap()
162 }
163 Ieee80211Child::Ieee80211Ibss(frame) => {
164 frame.with_address(source, destination).try_into().unwrap()
165 }
166 Ieee80211Child::Ieee80211Wds(frame) => {
167 frame.with_address(source, destination).try_into().unwrap()
168 }
169 _ => panic!("Unknown Ieee80211Child type"),
170 }
171 }
172
173 /// Covert Ieee80211ToAp to Ieee80211FromAp packet.
into_from_ap(&self) -> anyhow::Result<Ieee80211FromAp>174 pub fn into_from_ap(&self) -> anyhow::Result<Ieee80211FromAp> {
175 match self.specialize().unwrap() {
176 Ieee80211Child::Ieee80211ToAp(frame_to_ap) => {
177 // Flip from_ap and to_ap bits.
178 // TODO: Investigate if there is a way to copy frame_control flags at once.
179 // The header struct only has 7 fields, not 15. Most fields come from le16 frame_control.
180 Ok(Ieee80211FromAp {
181 duration_id: frame_to_ap.duration_id,
182 ftype: frame_to_ap.ftype,
183 more_data: frame_to_ap.more_data,
184 more_frags: frame_to_ap.more_frags,
185 order: frame_to_ap.order,
186 pm: frame_to_ap.pm,
187 protected: frame_to_ap.protected,
188 retry: frame_to_ap.retry,
189 stype: frame_to_ap.stype,
190 version: frame_to_ap.version,
191 bssid: frame_to_ap.bssid,
192 source: frame_to_ap.source,
193 destination: frame_to_ap.destination,
194 seq_ctrl: frame_to_ap.seq_ctrl,
195 payload: frame_to_ap.payload.to_vec(),
196 })
197 }
198 _ => Err(anyhow!(
199 "Invalid Ieee80211Child packet. from_ds: {}, to_ds: {}",
200 self.from_ds,
201 self.to_ds
202 )),
203 }
204 }
205 }
206
207 impl Ieee80211FromAp {
with_address( &self, source: Option<MacAddress>, destination: Option<MacAddress>, ) -> Ieee80211FromAp208 pub fn with_address(
209 &self,
210 source: Option<MacAddress>,
211 destination: Option<MacAddress>,
212 ) -> Ieee80211FromAp {
213 Ieee80211FromAp {
214 source: source.unwrap_or(self.source),
215 destination: destination.unwrap_or(self.destination),
216 ..self.clone()
217 }
218 }
219 }
220
221 impl Ieee80211ToAp {
with_address( &self, source: Option<MacAddress>, destination: Option<MacAddress>, ) -> Ieee80211ToAp222 pub fn with_address(
223 &self,
224 source: Option<MacAddress>,
225 destination: Option<MacAddress>,
226 ) -> Ieee80211ToAp {
227 Ieee80211ToAp {
228 source: source.unwrap_or(self.source),
229 destination: destination.unwrap_or(self.destination),
230 ..self.clone()
231 }
232 }
233 }
234
235 impl Ieee80211Ibss {
with_address( &self, source: Option<MacAddress>, destination: Option<MacAddress>, ) -> Ieee80211Ibss236 pub fn with_address(
237 &self,
238 source: Option<MacAddress>,
239 destination: Option<MacAddress>,
240 ) -> Ieee80211Ibss {
241 Ieee80211Ibss {
242 source: source.unwrap_or(self.source),
243 destination: destination.unwrap_or(self.destination),
244 ..self.clone()
245 }
246 }
247 }
248
249 impl Ieee80211Wds {
with_address( &self, source: Option<MacAddress>, destination: Option<MacAddress>, ) -> Ieee80211Wds250 pub fn with_address(
251 &self,
252 source: Option<MacAddress>,
253 destination: Option<MacAddress>,
254 ) -> Ieee80211Wds {
255 Ieee80211Wds {
256 source: source.unwrap_or(self.source),
257 destination: destination.unwrap_or(self.destination),
258 ..self.clone()
259 }
260 }
261 }
262
parse_mac_address(s: &str) -> Option<MacAddress>263 pub fn parse_mac_address(s: &str) -> Option<MacAddress> {
264 let parts: Vec<&str> = s.split(':').collect();
265 if parts.len() != 6 {
266 return None;
267 }
268 let mut bytes = [0u8; 6];
269 for (i, part) in parts.iter().enumerate() {
270 match u8::from_str_radix(part, 16) {
271 Ok(n) => bytes[i] = n,
272 Err(e) => return None,
273 }
274 }
275 Some(MacAddress::from(&bytes))
276 }
277
278 #[cfg(test)]
279 mod tests {
280 use super::*;
281
282 #[test]
test_mad_address_to_vec()283 fn test_mad_address_to_vec() {
284 let mac_address: MacAddress = parse_mac_address("00:0b:85:71:20:ce").unwrap();
285 let mac_address_bytes = mac_address.to_vec();
286 let reconstructed_mac_address = MacAddress::from(&mac_address_bytes);
287 assert_eq!(mac_address, reconstructed_mac_address);
288 }
289
290 // These tests use the packets available here
291 // https://community.cisco.com/t5/wireless-mobility-knowledge-base/802-11-frames-a-starter-guide-to-learn-wireless-sniffer-traces/ta-p/3110019
292
293 #[test]
test_frame_qos()294 fn test_frame_qos() {
295 let frame: Vec<u8> = vec![
296 0x88, 0x02, 0x2c, 0x00, 0x00, 0x13, 0xe8, 0xeb, 0xd6, 0x03, 0x00, 0x0b, 0x85, 0x71,
297 0x20, 0xce, 0x00, 0x0b, 0x85, 0x71, 0x20, 0xce, 0x00, 0x26, 0x00, 0x00,
298 ];
299 let hdr = Ieee80211::decode_full(&frame).unwrap();
300 assert!(hdr.is_data());
301 assert_eq!(hdr.stype, DataSubType::Qos as u8);
302 assert_eq!(hdr.from_ds, 1);
303 assert_eq!(hdr.to_ds, 0);
304 assert_eq!(hdr.duration_id, 44);
305 // Source address: Cisco_71:20:ce (00:0b:85:71:20:ce)
306 let a = format!("{}", hdr.get_source());
307 let b = format!("{}", parse_mac_address("00:0b:85:71:20:ce").unwrap());
308 assert_eq!(a, b);
309 }
310
311 #[test]
test_is_multicast()312 fn test_is_multicast() {
313 // Multicast MAC address: 01:00:5E:00:00:FB
314 let mdns_mac_address = parse_mac_address("01:00:5e:00:00:fb").unwrap();
315 assert!(mdns_mac_address.is_multicast());
316 // Broadcast MAC address: ff:ff:ff:ff:ff:ff
317 let broadcast_mac_address = parse_mac_address("ff:ff:ff:ff:ff:ff").unwrap();
318 assert!(broadcast_mac_address.is_multicast());
319 // Source address: Cisco_71:20:ce (00:0b:85:71:20:ce)
320 let non_mdns_mac_address = parse_mac_address("00:0b:85:71:20:ce").unwrap();
321 assert!(!non_mdns_mac_address.is_multicast());
322 }
323
create_test_from_ap_ieee80211( source: MacAddress, destination: MacAddress, bssid: MacAddress, ) -> Ieee80211324 fn create_test_from_ap_ieee80211(
325 source: MacAddress,
326 destination: MacAddress,
327 bssid: MacAddress,
328 ) -> Ieee80211 {
329 Ieee80211FromAp {
330 duration_id: 0,
331 ftype: FrameType::Mgmt,
332 more_data: 0,
333 more_frags: 0,
334 order: 0,
335 pm: 0,
336 protected: 0,
337 retry: 0,
338 stype: 0,
339 version: 0,
340 bssid,
341 source,
342 destination,
343 seq_ctrl: 0,
344 payload: Vec::new(),
345 }
346 .try_into()
347 .unwrap()
348 }
349
create_test_ibss_ieee80211( source: MacAddress, destination: MacAddress, bssid: MacAddress, ) -> Ieee80211350 fn create_test_ibss_ieee80211(
351 source: MacAddress,
352 destination: MacAddress,
353 bssid: MacAddress,
354 ) -> Ieee80211 {
355 Ieee80211Ibss {
356 duration_id: 0,
357 ftype: FrameType::Mgmt,
358 more_data: 0,
359 more_frags: 0,
360 order: 0,
361 pm: 0,
362 protected: 0,
363 retry: 0,
364 stype: 0,
365 version: 0,
366 bssid,
367 source,
368 destination,
369 seq_ctrl: 0,
370 payload: Vec::new(),
371 }
372 .try_into()
373 .unwrap()
374 }
375
create_test_to_ap_ieee80211( source: MacAddress, destination: MacAddress, bssid: MacAddress, ) -> Ieee80211376 fn create_test_to_ap_ieee80211(
377 source: MacAddress,
378 destination: MacAddress,
379 bssid: MacAddress,
380 ) -> Ieee80211 {
381 Ieee80211ToAp {
382 duration_id: 0,
383 ftype: FrameType::Mgmt,
384 more_data: 0,
385 more_frags: 0,
386 order: 0,
387 pm: 0,
388 protected: 0,
389 retry: 0,
390 stype: 0,
391 version: 0,
392 bssid,
393 source,
394 destination,
395 seq_ctrl: 0,
396 payload: Vec::new(),
397 }
398 .try_into()
399 .unwrap()
400 }
401
test_with_address( create_test_ieee80211: fn(MacAddress, MacAddress, MacAddress) -> Ieee80211, )402 fn test_with_address(
403 create_test_ieee80211: fn(MacAddress, MacAddress, MacAddress) -> Ieee80211,
404 ) {
405 let source = parse_mac_address("01:02:03:00:00:01").unwrap();
406 let destination = parse_mac_address("01:02:03:00:00:02").unwrap();
407 let bssid = parse_mac_address("00:13:10:85:fe:01").unwrap();
408 let ieee80211 = create_test_ieee80211(source, destination, bssid);
409
410 let new_source = parse_mac_address("01:02:03:00:00:03").unwrap();
411 let new_destination = parse_mac_address("01:02:03:00:00:04").unwrap();
412
413 let new_ieee80211 = ieee80211.with_address(Some(new_source), Some(new_destination));
414 assert!(new_ieee80211.get_source() == new_source);
415 assert!(new_ieee80211.get_destination() == new_destination);
416
417 let new_ieee80211 = ieee80211.with_address(Some(new_source), None);
418 assert!(new_ieee80211.get_source() == new_source);
419 assert!(new_ieee80211.get_destination() == destination);
420
421 let new_ieee80211 = ieee80211.with_address(None, Some(new_destination));
422 assert!(new_ieee80211.get_source() == source);
423 assert!(new_ieee80211.get_destination() == new_destination);
424 }
425
426 #[test]
test_with_address_from_ap()427 fn test_with_address_from_ap() {
428 test_with_address(create_test_from_ap_ieee80211);
429 }
430
431 #[test]
test_with_address_to_ap()432 fn test_with_address_to_ap() {
433 test_with_address(create_test_to_ap_ieee80211);
434 }
435 #[test]
test_with_address_ibss()436 fn test_with_address_ibss() {
437 test_with_address(create_test_ibss_ieee80211);
438 }
439 }
440