blob: a6be0af0d6b91eceb37b75ca2b20df1aa8b5d23b [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (c) 2013-2017 Nest Labs, Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# @file
# This file implements a Python script for making unique
# pseudo-random global identifiers (Global IDs) for IPv6 unique
# local addresses (ULAs) according to Section 3.2.2 of RFC 4193.
#
"""Tool for making unique pseudo-random global identifiers (Global IDs) for IPv6 unique local addresses (ULAs) according to Section 3.2.2 of RFC 4193.
"""
import sys
import os
import re
import hashlib
import binascii
import time
import struct
import math
from datetime import datetime
__all__ = [ 'MakeULAGlobalId' ]
def NormalizeMAC(macAddr):
macAddr = macAddr.replace(':', '')
macAddr = macAddr.replace('-', '')
macAddr = macAddr.upper()
return macAddr
def MakeULAGlobalId(macAddr, timeStamp=datetime.utcnow()):
# Normalized the string representation of the MAC.
macAddr = NormalizeMAC(macAddr)
# If the MAC address is a MAC-48 convert it to a EUI-64 as specified in RFC-4291.
if len(macAddr) == 12:
macAddr = macAddr[:6] + "FFFE" + macAddr[6:]
# Throw an error if an invalid MAC was specified.
if len(macAddr) != 16:
raise ValueError('Invalid MAC address')
# Convert the MAC to binary.
macAddr = binascii.unhexlify(macAddr)
# Convert the time stamp to a TimeDelta since the NTP epoch.
timeStamp = timeStamp - datetime(1900, 1, 1)
# Convert the the TimeDelta to integral and fractional seconds in 64-bit NTP format.
timeStampSecs = timeStamp.days * 86400 + timeStamp.seconds
timeStampFractSecs = int(timeStamp.microseconds * (2**32) // 1000000)
# Create a byte string containing the 32-bit seconds value, the 32-bit
# fractional seconds value and the binary MAC address in network order.
hashInput = struct.pack('!LL8s', timeStampSecs, timeStampFractSecs, macAddr)
# Hash the byte string using SHA1.
hash = hashlib.sha1(hashInput).digest()
# Extract the lower 40 bits as an 8 bit integer and 2 16 bit integers.
(field1, field2, field3) = struct.unpack('!15xBHH', hash)
# Combine the field values with the ULA prefix (FD00::/8) to create the
# ULA prefix.
return "%x:%x:%x" % (field1 + 0xfd00, field2, field3)
# Testing code
#
if __name__ == "__main__":
usage = """
make-ula-gloabl-id.py <mac-addr> [ <date-time> | <date> <time> ]
where:
<mac-addr> is a 48 or 64-bit MAC address in hex (XX:XX:XX:XX:XX:XX)
<date-time> is a date and time (YYYY/MM/DD HH:MM:SS)
"""
if len(sys.argv) < 2:
print usage
sys.exit(-1)
if len(sys.argv) > 4:
print "Unexpected argument: %s" % (sys.argv[3])
sys.exit(-1)
macAddr = sys.argv[1]
# Handle either the date-time argument as a single, quoted
# argument (e.g. "2010/08/30 12:34:04") or as two, separate
# arguments (e.g. 2010/08/30 12:34:04).
if len(sys.argv) == 3:
timeStamp = datetime.strptime(sys.argv[2], '%Y/%m/%d %H:%M:%S')
elif len(sys.argv) == 4:
timeStamp = datetime.strptime(sys.argv[2] + ' ' + sys.argv[3], '%Y/%m/%d %H:%M:%S')
else:
timeStamp = datetime.utcnow()
print ("%s::/48") % MakeULAGlobalId(macAddr, timeStamp)