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