diff --git a/.gitignore b/.gitignore index b6e4761..2d56172 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# configuration file +config.json diff --git a/ip/ipv4.py b/ip/ipv4.py new file mode 100644 index 0000000..47e5944 --- /dev/null +++ b/ip/ipv4.py @@ -0,0 +1,47 @@ +from ipaddress import IPv4Network, IPv4Address, AddressValueError, NetmaskValueError +from randmac import RandMac + + +def ipv4(prefix: str, ipl: [IPv4Address] = None, macl: [str] = None) -> [(IPv4Address, str, IPv4Address, int)]: + """ + This function generate a list of IPs and MACs to insert. + + If a list is given, they avoid duplicated entry with list, for duplicated IP the MAC will be set to None. + + :param prefix: The IPs prefix + :type prefix: IPv4Address + :param ipl: A list of IPs + :type ipl: [IPv4Address] + :param macl: A list of MACs + :type macl: [str] + :return: list of tuple with IP, MAC, subnet mask and cidr + :rtype: [(IPv4Address, str, IPv4Address, int)] + """ + + if ipl is None: + ipl = [] + if macl is None: + macl = [] + out = [] + + # Check if prefix is valid + try: + ips = IPv4Network(prefix) + except (ValueError, AddressValueError, NetmaskValueError): + raise ValueError("Invalid prefix !") + subnet_mask = ips.netmask + cidr = ips.prefixlen + + # For all ip in prefix + for ip in ips.hosts(): + mac = None + ip = str(ip) + if ip not in ipl: + ipl.append(ip) + mac = str(RandMac("00:00:00:00:00:00", True)) + while mac in macl: + mac = str(RandMac("00:00:00:00:00:00", True)) + macl.append(mac) + + out.append((ip, mac, subnet_mask, cidr)) + return out diff --git a/mac.py b/mac.py deleted file mode 100644 index 35d3783..0000000 --- a/mac.py +++ /dev/null @@ -1,213 +0,0 @@ -from pymysql import connect -from ipaddress import IPv4Network -from randmac import RandMac -from sys import stderr, argv -from subprocess import run, PIPE - - -DB_HOST = "" -DB_USER = "" -DB_PASS = "" -DB_NAME = "" - -SSH_HOST = "" -SSH_PORT = "" -SSH_KEY = "" -SSH_USER = "" - -DEFAULT_INTERFACE = "" - -debug = False - -if debug: - print("DEBUG MOD ACTICATED !") - -def main(args): - """ - args: list - List of IP prefix - - This function get from DB and routeur IPs and MACs, create insertion list and sent it - """ - - # DB connection - try: - print("Connect to DB...") - db = connect(DB_HOST, DB_USER, DB_PASS, DB_NAME) - cursor = db.cursor() - except: - print("Can't connect to DB !", file=stderr) - exit(1) - else: - print("Connected to DB") - - # Get IPs from DB - try: - print("Get IPs...") - cursor.execute("SELECT ip FROM mg_proxmox_addon_ip") - ipRow = cursor.fetchall() - ipRow = [i[0] for i in ipRow] - except: - print("Failt to get IPs !", file=stderr) - exit(1) - else: - print("IPs get") - - # Get MACs from DB - try: - print("Get MACs...") - cursor.execute("SELECT mac_address FROM mg_proxmox_addon_ip") - macRow = cursor.fetchall() - macRow = [i[0] for i in macRow] - except: - print("Fail to get MACs !", file=stderr) - exit(1) - else: - print("MACs get") - - # Get IPs and MACs from routeur - try: - print("Get routeur IPs and MACs...") - routeur = run(["ssh", "-i", SSH_KEY, "-o", "StrictHostKeyChecking no", f"{SSH_USER}@{SSH_HOST}", "-p", SSH_PORT, "/ip arp print"], stdout=PIPE).stdout.decode() - except: - print("Failt to get reouteur IPs and MACs !", file=stderr) - exit(1) - else: - print("Routeur IPs and MACs get") - - cursor.close() - - # Get vlan give on the first arg - vlan = args[0] - if vlan != "null": - try: - vlan = int(vlan) - except: - print("Invalid vlan !", file=stderr) - exit(1) - del args[0] - - # Get list of ip, mac and subnet_mask to add - out = [] - for arg in args: - out+=addIPs(arg, ipRow, macRow) - - # Insert the list - print(f"Start insert of {len(out)} IPs") - insertIPs(out, vlan, db, routeur) - - -def addIPs(prefix: str, ipRow: [str], macRow: [str]) -> [(str, str, str, int)]: - """ - prefix: str - The IPs prefix - ipRow: list of str - A list of IPs in the DB - macRow: list of string - A list of MACs in the DB - return: List of tupple with IP, MAC, subnet_mask and cidr - - This function generate a lits of IPs and MACs to insert - """ - out = [] - - # Check if prefix is valid - try: - print(f"Get IPs for {prefix}") - ips = IPv4Network(prefix) - subnet_mask = ips.netmask - cidr = ips.prefixlen - except: - print("Invalid IP prefix !", file=stderr) - else: - # For all ip in prefix - for ip in ips.hosts(): - # Check DB - if str(ip) not in ipRow: - # Random until not in DB - mac = RandMac("00:00:00:00:00:00", True) - while mac in macRow: - mac = RandMac() - # Append to shared list to avoid colisions - ipRow.append(ip) - macRow.append(mac) - - out.append((str(ip), str(mac), str(subnet_mask), int(cidr))) - - else: - print(f"IP: {ip} already on DB !", file=stderr) - print(f"Got {len(out)} IPs") - return out - - -def insertIPs(insert: [(str, str, str, int)], vlan, db, routeur): - """ - insert: List of tupple with IP, MAC and subnet_mask - The list of IPs, MACs to insert - db: MySQL DB object - The DB to insert - routeur: str - A string content MACs and IPs content in routeur - - This function insert given IPs and MACs to SB and routeur (if necessary) - """ - cursor = db.cursor() - - # For every IP to insert - gateway = insert[0][0] - del insert[0] - print(f"Use gateway: {gateway}") - for i in insert: - # Try insert into DB - try: - cmd = f"INSERT INTO mg_proxmox_addon_ip (ip, type, mac_address, subnet_mask, cidr, gateway, tag) VALUES ('{i[0]}', 'IPv4', {i[1]}, '{i[2]}', {i[3]}, '{gateway}', {vlan})" - if debug: - print(cmd) - cursor.execute(cmd) - except Exception as e: - raise e - print(f"Fail to insert values !\nIP: {i[0]}, Type: IPv4, MAC: {i[1]}, subnet_mask: {i[2]}", file=stderr) - else: - # Optional log - #print(f"IP: {i[0]} of type IPv4 with MAC: {i[1]} on subnet_mask: {i[2]} add") - - # If IP and MAC ar not in the routeur, add them - if ((i[0] not in routeur) or not (routeur[routeur.find(i[0]):5].replace(" ", ""))) and (i[1] not in routeur): - if vlan != "null": - interface = f"vlan{vlan}" - else: - interface = DEFAULT_INTERFACE - - cmd = ["ssh", "-i", SSH_KEY, "-o", "StrictHostKeyChecking no", f"{SSH_USER}@{SSH_HOST}", "-p", SSH_PORT, f"/ip arp add address={i[0]} mac-address={i[1]} interface={interface}".replace("'", "")] - if debug: - print(cmd) - else: - run(cmd) - elif debug: - print(f"Already on the routeur !\nIP: {i[0]}, Type: IPv4, MAC: {i[1]}, subnet_mask: {i[2]}", file=stderr) - - cursor.close() - - # Commit to the DB - if debug: - print("Commit DB...") - else: - try: - print("Commit to DB...") - db.commit() - except: - print("Fail to commit !", file=stderr) - exit(1) - else: - print("Commited to DB") - - - -# Execution as a script handler -if __name__ == "__main__": - # Avoid file name on args - if argv[0] == __file__: - del argv[0] - - # Start main function - main(argv) diff --git a/main.py b/main.py new file mode 100644 index 0000000..ce883c6 --- /dev/null +++ b/main.py @@ -0,0 +1,52 @@ +from pymysql import connect +from argparse import ArgumentParser +from os.path import isfile +from json import dump, load +from whmcs.get_whmcs import get_whmcs_ipv4, get_whmcs_mac +from whmcs.insert_whmcs import insert_whmcs_ipv4 +from router.insert_router import insert_router_ipv4 +from ip.ipv4 import ipv4 + +if not isfile("config.json"): + with open("config.json", "w") as config: + data = {"database": {"host": "", "user": "", "password": "", "name": ""}, "ssh": {"host": "", "port": 22, "user": "", "key": ""}, "interface": {"default": ""}} + dump(data, config) + print("Config file created, please fill it") + exit() +with open("config.json", "r") as config: + conf = load(config) +DB_HOST = conf["database"]["host"] +DB_USER = conf["database"]["user"] +DB_PASS = conf["database"]["password"] +DB_NAME = conf["database"]["name"] + +SSH_HOST = conf["ssh"]["host"] +SSH_PORT = conf["ssh"]["port"] +SSH_USER = conf["ssh"]["user"] +SSH_KEY = conf["ssh"]["key"] + +DEFAULT_INTERFACE = conf["interface"]["default"] + +pars = ArgumentParser() +pars.add_argument("interface", help="Interface of IPs") +pars.add_argument("prefix", help="IPs prefix") +pars.add_argument("-d", "--debug", help="Any consequence", action="store_true") +args = pars.parse_args() + +debug = False +if args.debug: + debug = True + print("DEBUG MOD ACTICATED !") + +# DB connection +db = connect(DB_HOST, DB_USER, DB_PASS, DB_NAME) + +ipl = get_whmcs_ipv4(db) +macl = get_whmcs_mac(db) + +# Get list of ip, mac and subnet_mask to add +out = ipv4(args.prefix, ipl, macl) + +# Insert the list +insert_whmcs_ipv4(out, args.interface, db, debug) +insert_router_ipv4(out, "", SSH_HOST, SSH_PORT, SSH_USER, SSH_KEY, debug) diff --git a/router/get_router.py b/router/get_router.py new file mode 100644 index 0000000..c5dc00a --- /dev/null +++ b/router/get_router.py @@ -0,0 +1,49 @@ +from subprocess import run, PIPE +from ipaddress import IPv4Address +import re + +rip = re.compile(r"[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}") # IP regex +rmac = re.compile(r"(?:[A-F]|[0-9]){1,3}:(?:[A-F]|[0-9]){1,3}:(?:[A-F]|[0-9]){1,3}:(?:[A-F]|[0-9]){1,3}:(?:[A-F]|[0-9])" + r"{1,3}:(?:[A-F]|[0-9]){1,3}") # MAC regex + + +def get_router_ipv4(host: str, port: int, user: str, key: str) -> [IPv4Address]: + """ + Gets IPv4 list of the router + + :param host: The SSH host of the router + :type host: str + :param port: The SSH port of the router + :type port: int + :param user: The SSH user of the router + :type port: str + :param key: The SSH key of the router + :type key: str + :return: List of IPv4 in the router + :rtype: [IPv4Address] + """ + + out = run(["ssh", "-i", key, "-o", "StrictHostKeyChecking no", f"{user}@{host}", "-p", str(port), "/ip arp print"], + stdout=PIPE).stdout.decode() + return [IPv4Address(i) for i in rip.findall(out)] + + +def get_router_mac(host: str, port: int, user: str, key: str) -> [str]: + """ + Gets MAC list of the router + + :param host: The SSH host of the router + :type host: str + :param port: The SSH port of the router + :type port: int + :param user: The SSH user of the router + :type port: str + :param key: The SSH key of the router + :type key: str + :return: List of MAC in the router + :rtype: [str] + """ + + out = run(["ssh", "-i", key, "-o", "StrictHostKeyChecking no", f"{user}@{host}", "-p", port, "/ip arp print"], + stdout=PIPE).stdout.decode() + return rmac.findall(out) diff --git a/router/insert_router.py b/router/insert_router.py new file mode 100644 index 0000000..d965842 --- /dev/null +++ b/router/insert_router.py @@ -0,0 +1,35 @@ +from ipaddress import IPv4Address +from router.get_router import get_router_ipv4 +from subprocess import run + + +def insert_router_ipv4(insert: [(IPv4Address, str, IPv4Address, int)], interface: str, host: str, port: int, user: str, + key: str, debug: bool = False): + """ + This function insert IPv4 on the router + + :param insert: The list of IPs, MACs to insert + :type insert: [(IPv4Address, str, IPv4Address, int)] + :param interface: The interface of IPs + :type interface: str + :param host: The SSH host of the router + :type host: str + :param port: The SSH port of the router + :type port: int + :param user: The SSH user of the router + :type port: str + :param key: The SSH key of the router + :type key: str + :param debug: Disable commit on database + :type debug: bool + """ + + ipl = get_router_ipv4(host, port, user, key) + + for i in insert: + if ((i[0] not in ipl) or not (ipl[ipl.find(i[0]):5].replace(" ", ""))) and (i[1] not in ipl): + cmd = ["ssh", "-i", key, "-o", "StrictHostKeyChecking no", f"{user}@{host}", "-p", port, f"/ip arp add address={i[0]} mac-address={i[1]} interface={interface}".replace("'", "")] + if not debug: + run(cmd) + else: + print(cmd) diff --git a/whmcs/get_whmcs.py b/whmcs/get_whmcs.py new file mode 100644 index 0000000..aacf470 --- /dev/null +++ b/whmcs/get_whmcs.py @@ -0,0 +1,36 @@ +from pymysql import Connect +from ipaddress import IPv4Address + + +def get_whmcs_ipv4(db: Connect) -> [IPv4Address]: + """ + Get all IPs of WHMCS + + :param db: The database connection of WHMCS + :type db: pymysql.Connect + :return: The list of WHMCS IP + :rtype: [IPv4Address] + """ + + cursor = db.cursor() + cursor.execute("SELECT ip FROM mg_proxmox_addon_ip") + ips = [IPv4Address(i[0]) for i in cursor.fetchall()] + cursor.close() + return ips + + +def get_whmcs_mac(db: Connect) -> [str]: + """ + Get all MACs of WHMCS + + :param db: The database connection of WHMCS + :type db: pymysql.Connect + :return: The list of WHMCS MAC + :rtype: [str] + """ + + cursor = db.cursor() + cursor.execute("SELECT mac_address FROM mg_proxmox_addon_ip") + macs = [i[0] for i in cursor.fetchall()] + cursor.close() + return macs diff --git a/whmcs/insert_whmcs.py b/whmcs/insert_whmcs.py new file mode 100644 index 0000000..ce4bf2f --- /dev/null +++ b/whmcs/insert_whmcs.py @@ -0,0 +1,52 @@ +from ipaddress import IPv4Address +from sys import stderr +from pymysql import Connect + + +def insert_whmcs_ipv4(insert: [(IPv4Address, str, IPv4Address, int)], interface: str, db: Connect, debug: bool = False): + """ + This function insert given IPs and MACs to WHMCS + + :param insert: The list of IPs, MACs to insert + :type insert: [(IPv4Address, str, IPv4Address, int)] + :param interface: The interface of IPs + :type interface: str + :param db: The database connection of WHMCS + :type db: pymysql.Connect + :param debug: Disable commit on database + :type debug: bool + """ + cursor = db.cursor() + # Get gateway + gateway = insert[0][0] + del insert[0] + # Get vlan if given + if interface[:5] == "vlan": + try: + vlan = int(interface[4:]) + except ValueError: + raise ValueError("Invalid vlan !") + else: + vlan = "null" + + # For every IP to insert + for i in insert: + cmd = f"INSERT INTO mg_proxmox_addon_ip (ip, type, mac_address, subnet_mask, cidr, gateway, tag) " \ + f"VALUES ('{i[0]}', 'IPv4', {i[1]}, '{i[2]}', {i[3]}, '{gateway}', {vlan})" + try: + cursor.execute(cmd) + except Exception as e: + print(cmd, file=stderr) + raise e + + cursor.close() + + # Commit to the DB + if not debug: + try: + print("Commit to DB...") + db.commit() + except Exception as e: + raise e + else: + print("Commited to DB")