-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmaclib.py
More file actions
255 lines (220 loc) · 8.72 KB
/
Copy pathmaclib.py
File metadata and controls
255 lines (220 loc) · 8.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
"""Operations on MAC addresses."""
import os
import uuid
import random
import numbers
import subprocess
import distutils.spawn
def get_mac():
"""Get your own device's MAC address using uuid.getnode().
Returns a Mac object, or None on failure."""
#TODO: uuid.getnode() arbitrarily chooses a MAC when the device has more than one. May have to use
# another method to make sure it's the MAC of the NIC in use. Probably have to create a
# dummy socket using a public IP.
# uuid.getnode() returns the MAC as an integer.
uuid_mac = Mac(uuid.getnode())
# On failure, uuid.getnode() returns a random MAC, with the eight bit set:
# https://docs.python.org/2/library/uuid.html#uuid.getnode
# Check the eigth bit to determine whether it failed.
if uuid_mac.byte_ints[0] & 0b00000001:
# Try harder. (Use the "ip" command.)
device = get_default_device()
mac_str = get_mac_of_device(device)
if mac_str:
return Mac(mac_str)
else:
return None
else:
return uuid_mac
def get_mac_of_device(device):
output = execute_cmd(['ip', 'link', 'show', 'dev', device], bin_path='/sbin/ip')
line_num = 0
for line in output.splitlines():
line_num += 1
if line_num == 2:
fields = line.split()
if len(fields) > 2:
return fields[1]
return None
def get_default_device():
output = execute_cmd(['ip', 'route', 'show', '0/0'], bin_path='/sbin/ip')
fields = output.split()
if len(fields) < 6:
return None
else:
return fields[4]
def execute_cmd(cmd, bin_path=None):
if bin_path:
if 'PATH' not in os.environ or not distutils.spawn.find_executable(cmd[0]):
cmd[0] = bin_path
devnull = open(os.devnull, 'w')
try:
output = subprocess.check_output(cmd, stderr=devnull)
except (OSError, subprocess.CalledProcessError):
return None
finally:
devnull.close()
return output
def get_random_mac():
"""Generate a valid, random MAC address."""
# In the first byte, the two least-significant bits must be 10:
# The 1 means it's a local MAC address (not globally assigned and unique).
# The 0 means it's not a broadcast address.
# https://superuser.com/questions/725467/set-mac-address-fails-rtnetlink-answers-cannot-assign-requested-address/725472#725472
# 0-63 * 4 gives a number with 00 as the last binary digits.
number = random.randint(0, 63) * 4
# Add the following 5 bytes.
for i in range(5):
number *= random.randint(0, 255)
return Mac(number)
def eui64_to_mac(eui64):
"""Convert an EUI-64 to a MAC address by removing the middle two bytes."""
_bytes = eui64.split(':')
return Mac(_bytes[:3] + _bytes[5:])
# Subclass tuple to make Mac immutable.
class Mac(tuple):
"""An object representing a MAC address.
Initialize with one argument: a MAC address in one of 4 representations:
1. a string of the colon-delimited hexadecimal bytes
2. an iterable of the bytes as hex strings
3. an iterable of the bytes as integers
4. a single integer representing the 48-bit value of the address
When a representation is requested that wasn't given initially, it is computed and cached.
Mac objects are immutable."""
# Need to override tuple's __new__().
def __new__(cls, mac):
return tuple.__new__(cls, ())
def __init__(self, mac):
self._string = None
self._number = None
self._bytes = None
self._byte_ints = None
if isinstance(mac, basestring):
assert len(mac) == 17, 'Mac string must be 17 characters (6 colon-delimited hex bytes).'
self._string = mac.upper()
elif isinstance(mac, numbers.Integral):
self._number = mac
else:
try:
_bytes = tuple(mac)
except TypeError:
raise AssertionError('Mac object must be initialized with a string, integer, or iterable.')
assert len(_bytes) == 6, 'Mac must consist of 6 bytes.'
if isinstance(_bytes[0], basestring):
self._bytes = _bytes
elif isinstance(_bytes[0], numbers.Integral):
self._byte_ints = _bytes
else:
raise AssertionError('Mac bytes must be numbers or strings.')
@property
def string(self):
"""A string representing the MAC address as the standard colon-delimited hex bytes.
If it doesn't exist, derive it from the bytes."""
if self._string is None:
self._string = ':'.join(self.bytes)
return self._string
@property
def number(self):
"""An int representing the MAC address value as a number.
If it doesn't exist, derive it from the bytes."""
if self._number is None:
hexadecimal = ''.join(self.bytes)
self._number = int(hexadecimal, 16)
return self._number
@property
def byte_ints(self):
"""A tuple representing the MAC address as a series of bytes (ints).
If it doesn't exist, derive it from the bytes."""
if self._byte_ints is None:
self._byte_ints = tuple(int(o, 16) for o in self.bytes)
return self._byte_ints
@property
def bytes(self):
"""An tuple representing the MAC address as a series of hex bytes (strings).
If it doesn't exist, try deriving it from one of the other representations."""
if self._bytes is None:
if self._string is not None:
self._bytes = tuple(self._string.split(':'))
elif self._byte_ints is not None:
self._bytes = tuple('{:02X}'.format(o) for o in self._byte_ints)
elif self._number is not None:
hexadecimal = '{:012X}'.format(self._number)
self._bytes = tuple(hexadecimal[i:i+2] for i in range(0, 12, 2))
else:
raise AssertionError('Mac object is uninitialized.')
return self._bytes
def __str__(self):
return self.string
def __repr__(self):
return "{}.{}('{}')".format(type(self).__module__, type(self).__name__, self.string)
def __eq__(self, mac2):
return self.string == mac2.string
def __ne__(self, mac2):
return self.string != mac2.string
def to_eui64(self, is_mac48=False):
"""Convert the MAC address to an EUI-64 address.
This expands the address to 64 bits by adding 'FF:FE' as the middle two bytes, by default.
This is the procedure when the MAC address is considered an EUI-48 (as is done when creating an
IPv6 address from a MAC address). If the MAC address should be considered a MAC-48 instead, so
that 'FF:FF' is used as the middle bytes, set 'is_mac48' to True.
N.B.: This is part 1 of how IPv6 generates addresses from MAC addresses. The second part is
flipping the locally administered bit."""
if is_mac48:
middle = 'FF:FF'
else:
middle = 'FF:FE'
return self.bytes[:3] + middle + self.bytes[3:]
def is_broadcast(self):
"""Check whether the MAC address is the broadcast FF:FF:FF:FF:FF:FF address."""
return self.string == 'FF:FF:FF:FF:FF:FF'
def is_local(self):
"""Check whether the "locally administered" bit in a MAC address is set to 1."""
return bool(self.byte_ints[0] & 0b00000010)
def is_global(self):
"""Check whether the "locally administered" bit in a MAC address is set to 0.
This means that the MAC address should be "globally unique"."""
return not bool(self.byte_ints[0] & 0b00000010)
def is_multicast(self):
"""Check whether the "multicast" bit in a MAC address is set to 1."""
return bool(self.byte_ints[0] & 0b00000001)
def is_unicast(self):
"""Check whether the "multicast" bit in a MAC address is set to 0.
This means the MAC address is unicast."""
return not bool(self.byte_ints[0] & 0b00000001)
def is_normal(self):
"""Check whether the MAC address is the common type used by networking hardware.
Returns false if it's a locally administered, multicast, or broadcast address."""
# Is broadcast address?
if self.is_broadcast():
return False
# Is locally administered bit set?
if self.is_local():
return False
# Is multicast bit set?
if self.is_multicast():
return False
return True
def to_local(self):
"""Set the "locally administered" bit to 1 and return the result."""
byte_ints = list(self.byte_ints)
# OR the first byte with 00000010.
byte_ints[0] = byte_ints[0] | 0b00000010
return Mac(byte_ints)
def to_global(self):
"""Set the "locally administered" bit to 0 and return the result."""
byte_ints = list(self.byte_ints)
# AND the first byte with 11111101.
byte_ints[0] = byte_ints[0] & 0b11111101
return Mac(byte_ints)
def to_multicast(self):
"""Set the "multicast" bit to 1 and return the result."""
byte_ints = list(self.byte_ints)
# OR the first byte with 00000001.
byte_ints[0] = byte_ints[0] | 0b00000001
return Mac(byte_ints)
def to_unicast(self):
"""Set the "multicast" bit to 0 and return the result."""
byte_ints = list(self.byte_ints)
# AND the first byte with 11111110.
byte_ints[0] = byte_ints[0] & 0b11111110
return Mac(byte_ints)