diff --git a/pcap_anonymize/layers/mac.py b/pcap_anonymize/layers/mac.py index 1ac3924f9bbef716f7f68e4a33c58bcf556d63fa..edf72e4b4d313db60601004fbdd2c086de67ffad 100644 --- a/pcap_anonymize/layers/mac.py +++ b/pcap_anonymize/layers/mac.py @@ -2,6 +2,7 @@ Anonymize MAC addresses. """ +import random import secrets from scapy.layers.l2 import Ether, ARP @@ -14,6 +15,36 @@ special_macs = [ ] +def get_ig_bit(mac: str) -> int: + """ + Get the I/G bit of a given MAC address. + + Args: + mac (str): MAC address to get the I/G bit from + Returns: + int: 8-bit integer with the I/G bit set to its corresponding value, + and all other bits set to 0 + """ + first_byte = int(mac.split(":")[0], BASE_HEX) + ig_mask = 0b00000001 + return first_byte & ig_mask + + +def get_ul_bit(mac: str) -> int: + """ + Get the U/L bit of a given MAC address. + + Args: + mac (str): MAC address to get the U/L bit from + Returns: + int: 8-bit integer with the U/L bit set to its corresponding value, + and all other bits set to 0 + """ + first_byte = int(mac.split(":")[0], BASE_HEX) + ul_mask = 0b00000010 + return first_byte & ul_mask + + def anonymize_mac(mac: str) -> str: """ Anonymize a given MAC address. @@ -33,9 +64,7 @@ def anonymize_mac(mac: str) -> str: ## I/G bit: first byte, least-significant bit # I/G bit = 0 ==> Unicast address # I/G bit = 1 ==> Multicast address - first_byte = int(mac_split[0], BASE_HEX) - ig_mask = 0b00000001 - ig_bit = first_byte & ig_mask + ig_bit = get_ig_bit(mac) is_multicast = bool(ig_bit) # True ==> Multicast, False ==> Unicast # Multicast address: @@ -46,14 +75,14 @@ def anonymize_mac(mac: str) -> str: ## U/L bit: first byte, second least-significant bit # U/L bit = 0 ==> Universally administered address (UAA) # U/L bit = 1 ==> Locally administered address (LAA) - ul_mask = 0b00000010 - ul_bit = first_byte & ul_mask + ul_bit = get_ul_bit(mac) is_local = bool(ul_bit) # True ==> LAA, False ==> UAA # Locally administered address if is_local: - first_byte = (secrets.token_hex(1) & ig_bit) & ul_bit # Keep I/G and U/L bits - return f"{first_byte:x}" + ':'.join(secrets.token_hex(1) for _ in range(5)) + bit_mask = ig_bit | ul_bit + first_byte = (random.getrandbits(6) << 2) | bit_mask # Keep I/G and U/L bits + return f"{first_byte:02x}:" + ':'.join(secrets.token_hex(1) for _ in range(5)) # Universally administered address return ( diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..125f31fb5d4e4192ce1f5977d27060b43b1fb062 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,3 @@ +""" +Unit tests for the package `pcap-anonymize`. +""" diff --git a/test/test_mac.py b/test/test_mac.py new file mode 100644 index 0000000000000000000000000000000000000000..8d28d63f4aade3463f7684ac9c0737cf03d4e8dd --- /dev/null +++ b/test/test_mac.py @@ -0,0 +1,52 @@ +from pcap_anonymize.layers.mac import get_ig_bit, get_ul_bit, anonymize_mac + +# Number of random MAC addresses to generate per unit test +N_TESTS = 5 + + +### TEST FUNCTIONS ### + +def test_get_ig_bit(): + """ + Test the function `get_ig_bit`. + """ + assert get_ig_bit("00:00:00:00:00:00") == 0b00000000 + assert get_ig_bit("01:00:00:00:00:00") == 0b00000001 + assert get_ig_bit("12:34:56:78:9a:bc") == 0b00000000 + + +def test_get_ul_bit(): + """ + Test the function `get_ul_bit`. + """ + assert get_ul_bit("00:00:00:00:00:00") == 0b00000000 + assert get_ul_bit("02:00:00:00:00:00") == 0b00000010 + assert get_ul_bit("12:34:56:78:9a:bc") == 0b00000010 + + +def test_anonymize_mac_multicast(): + """ + Test the function `anonymize_mac` + with a multicast MAC address. + The MAC address should not be anonymized. + """ + mac_multicast = "01:00:00:00:00:00" + assert anonymize_mac(mac_multicast) == mac_multicast + + +def test_anonymize_mac_laa(): + """ + Test the function `anonymize_mac` + with a locally administered MAC address. + All bits should be anonymized except the I/G and U/L bits. + """ + mac_laa = "02:00:00:00:00:00" + + # Generate N anonymized MAC addresses, + # and verify they are correct + for _ in range(N_TESTS): + mac_laa_anon = anonymize_mac(mac_laa) + assert mac_laa_anon != mac_laa + # Verify I/G and U/L bits + assert get_ig_bit(mac_laa) == get_ig_bit(mac_laa_anon) + assert get_ul_bit(mac_laa) == get_ul_bit(mac_laa_anon)