#!/usr/bin/env python3
import argparse
import ipaddress
import json
import os

def mac_from_string(address):
    return bytes([int(x, 16) for x in address.split(":")])

def get_iface_mac(ifname):
    with os.popen(f"ip -j link ls dev '{ifname}'", "r") as fh:
        buf = json.load(fh)
        return mac_from_string(buf[0]["address"])

parser = argparse.ArgumentParser()
parser.add_argument("-l", "--link-local", action="store_true",
                        help="generate a random IPv6 link-local address")
parser.add_argument("-i", "--from-iface", metavar="IFNAME",
                        help="translate MAC to EUI64-based IPv6LL address")
parser.add_argument("-m", "--from-mac", metavar="MAC",
                        help="translate MAC to EUI64-based IPv6LL address")
parser.add_argument("-u", "--ula", action="store_true",
                        help="generate a random ULA /48 prefix")
parser.add_argument("-U", "--ula64", action="store_true",
                        help="generate a random ULA /64 prefix")
parser.add_argument("-M", "--mac", action="store_true",
                        help="generate a random MAC address")
args = parser.parse_args()

if (args.link_local + args.mac + args.ula + args.ula64) > 1:
    exit("error: Conflicting modes specified")

elif (args.from_mac or args.from_iface) and (args.mac + args.ula + args.ula64):
    exit("error: Conflicting options (--from-mac is ignored in this mode)")

elif args.mac:
    buf = bytearray(os.urandom(6))
    buf[0] &= ~1    # Clear "unicast/group" bit
    buf[0] |= 2     # Set "global/local" bit to locally administered
    mac = ":".join(["%02x" % b for b in buf])
    print(mac)

elif args.ula:
    buf = bytes([0xfd, *os.urandom(5),               0x00, 0x00,
                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    addr = ipaddress.IPv6Address(buf)
    net = ipaddress.IPv6Network("%s/48" % addr)
    print(net)

elif args.ula64:
    buf = bytes([0xfd, *os.urandom(7),
                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
    addr = ipaddress.IPv6Address(buf)
    net = ipaddress.IPv6Network("%s/64" % addr)
    print(net)

else:
    if args.from_mac:
        mac = bytearray(mac_from_string(args.from_mac))
        assert len(mac) == 6
        iid = mac[0:3] + bytearray([0xFF, 0xFE]) + mac[3:6]
        # Flip the U/L bit per specification
        iid[0] ^= 0x02
    elif args.from_iface:
        mac = bytearray(get_iface_mac(args.from_iface))
        assert len(mac) == 6
        iid = mac[0:3] + bytearray([0xFF, 0xFE]) + mac[3:6]
        # Flip the U/L bit per specification
        iid[0] ^= 0x02
    else:
        iid = bytearray(os.urandom(8))
        # Clear the U/L bit to always indicate "Local"
        iid[0] &= ~0x02
    buf = bytes([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 *iid])
    addr = ipaddress.IPv6Address(buf)
    print(addr)
