From bcb22604a4bc06be39dd50168ea9238c133a38f4 Mon Sep 17 00:00:00 2001 From: Peter Dahlberg Date: Sat, 2 Aug 2014 23:23:16 +0200 Subject: [PATCH] add route6 option to route ipv6 nets to a specific address --- VMHelper.py | 555 ++++++++++++++++++++++++++-------------------------- 1 file changed, 281 insertions(+), 274 deletions(-) diff --git a/VMHelper.py b/VMHelper.py index 61d1457..e6ddbff 100644 --- a/VMHelper.py +++ b/VMHelper.py @@ -9,318 +9,325 @@ from qmp import QEMUMonitorProtocol from VMdhcpd import VMdhcpd class VMHelper: - def __init__(self, filename): - self.config = json.load(open(filename)) + def __init__(self, filename): + self.config = json.load(open(filename)) - def getVmIds(self): - if ('VMs' in self.config): - return [x for x , y in self.config['VMs'].items()] - else: - return [] + def getVmIds(self): + if ('VMs' in self.config): + return [x for x , y in self.config['VMs'].items()] + else: + return [] - def humanMonitorCommand(self, vmid: str, command: str, args: list) -> str: - proto = QEMUMonitorProtocol(self.config['kvm']['qmpsocket'].replace("$VMID", vmid)) - proto.connect() - commandline = command + " " + " ".join(args) - result = proto.cmd("human-monitor-command", { "command-line" : commandline }) + def humanMonitorCommand(self, vmid: str, command: str, args: list) -> str: + proto = QEMUMonitorProtocol(self.config['kvm']['qmpsocket'].replace("$VMID", vmid)) + proto.connect() + commandline = command + " " + " ".join(args) + result = proto.cmd("human-monitor-command", { "command-line" : commandline }) - proto.close() - return result["return"] + proto.close() + return result["return"] - def getPid(self,vmid) -> "int or None": - result = None - try: - with open(self.config['kvm']['pidfile'].replace("$VMID", vmid)) as pidfd: - firstline = pidfd.readline().strip() - result = int(firstline) if firstline.isdigit() else None - except IOError: - pass - return result - + def getPid(self,vmid) -> "int or None": + result = None + try: + with open(self.config['kvm']['pidfile'].replace("$VMID", vmid)) as pidfd: + firstline = pidfd.readline().strip() + result = int(firstline) if firstline.isdigit() else None + except IOError: + pass + return result + - def process_running(self,vmid) -> bool: + def process_running(self,vmid) -> bool: - pid = self.getPid(vmid) - - if pid is None: - return False + pid = self.getPid(vmid) + + if pid is None: + return False - try: - os.kill(pid,0) - except OSError as e: - return e.errno != errno.ESRCH - else: - return True + try: + os.kill(pid,0) + except OSError as e: + return e.errno != errno.ESRCH + else: + return True - def getQMPStatus(self,vmid): - # cmd doku http://git.qemu.org/?p=qemu.git;a=blob;f=qmp-commands.hx;h=1e0e11ee32571209e2dfce41b5c18f01d6ad3880;hb=HEAD - proto = QEMUMonitorProtocol(self.config['kvm']['qmpsocket'].replace("$VMID", vmid)) - proto.connect() - - result = {} - - queryKvm = proto.command("query-kvm") - result["kvm-enabled"] = queryKvm["enabled"] - result["kvm-present"] = queryKvm["present"] + def getQMPStatus(self,vmid): + # cmd doku http://git.qemu.org/?p=qemu.git;a=blob;f=qmp-commands.hx;h=1e0e11ee32571209e2dfce41b5c18f01d6ad3880;hb=HEAD + proto = QEMUMonitorProtocol(self.config['kvm']['qmpsocket'].replace("$VMID", vmid)) + proto.connect() + + result = {} + + queryKvm = proto.command("query-kvm") + result["kvm-enabled"] = queryKvm["enabled"] + result["kvm-present"] = queryKvm["present"] - result["status"] = proto.command("query-status")["status"] + result["status"] = proto.command("query-status")["status"] - proto.close() - return result + proto.close() + return result - def autostartVMs(self,managerpath): - if ('VMs' in self.config): - for vmid, vmcfg in self.config['VMs'].items(): - if "autostart" in vmcfg and vmcfg["autostart"] and not self.process_running(vmid): - self.startVM(vmid,managerpath) - else: - raise Exception("Missing VMs config section!") + def autostartVMs(self,managerpath): + if ('VMs' in self.config): + for vmid, vmcfg in self.config['VMs'].items(): + if "autostart" in vmcfg and vmcfg["autostart"] and not self.process_running(vmid): + self.startVM(vmid,managerpath) + else: + raise Exception("Missing VMs config section!") - def startVM(self, vmid, managerpath, hibernate_ignore=False): - self.setupNetwork(vmid) - cmd = [] + def startVM(self, vmid, managerpath, hibernate_ignore=False): + self.setupNetwork(vmid) + cmd = [] - fullpath_helper = os.path.realpath(__file__) - pathname_helper = os.path.dirname(fullpath_helper) + fullpath_helper = os.path.realpath(__file__) + pathname_helper = os.path.dirname(fullpath_helper) - cmd.append(pathname_helper + "/qemu-wrapper.sh") - cmd.append(sys.executable) - cmd.append(managerpath) - cmd.append(self.config['kvm']['executable']) - cmd.append(vmid) + cmd.append(pathname_helper + "/qemu-wrapper.sh") + cmd.append(sys.executable) + cmd.append(managerpath) + cmd.append(self.config['kvm']['executable']) + cmd.append(vmid) - cmd.append("-pidfile") - cmd.append(self.config['kvm']['pidfile'].replace("$VMID", vmid)) + cmd.append("-pidfile") + cmd.append(self.config['kvm']['pidfile'].replace("$VMID", vmid)) - cmd.append("-qmp") - cmd.append("unix:" + self.config['kvm']['qmpsocket'].replace("$VMID", vmid) + ",server,nowait") + cmd.append("-qmp") + cmd.append("unix:" + self.config['kvm']['qmpsocket'].replace("$VMID", vmid) + ",server,nowait") - hibernate_file = self.config['kvm']['hibernatefile'].replace("$VMID", vmid) - # check if ignore hibernate file flag is set - if (not hibernate_ignore): - if (os.path.isfile(hibernate_file)): - cmd.append("-incoming") - cmd.append("exec: cat \'{}\' && rm \'{}\'".format(hibernate_file, hibernate_file)) + hibernate_file = self.config['kvm']['hibernatefile'].replace("$VMID", vmid) + # check if ignore hibernate file flag is set + if (not hibernate_ignore): + if (os.path.isfile(hibernate_file)): + cmd.append("-incoming") + cmd.append("exec: cat \'{}\' && rm \'{}\'".format(hibernate_file, hibernate_file)) - if "runas" in self.config["kvm"]: - cmd.append("-runas") - cmd.append(self.config["kvm"]["runas"]) + if "runas" in self.config["kvm"]: + cmd.append("-runas") + cmd.append(self.config["kvm"]["runas"]) - cmd += ["-name", vmid] + cmd += ["-name", vmid] - default_args = self.config['kvm']['default_args'].replace("$VMID", vmid) - cmd += default_args.split() - cmd += self.createArguments(vmid).split() - #print(" ".join(cmd)) - subprocess.Popen(cmd, stdout=open("/dev/null"), stderr=open("/dev/null")) - #subprocess.call(cmd + default_args = self.config['kvm']['default_args'].replace("$VMID", vmid) + cmd += default_args.split() + cmd += self.createArguments(vmid).split() + #print(" ".join(cmd)) + subprocess.Popen(cmd, stdout=open("/dev/null"), stderr=open("/dev/null")) + #subprocess.call(cmd - def shutdownVMs(self,timeout,parallel=True, statusCallback=lambda vmid, st : None): - threads = [] - for vmid in self.getVmIds(): - if self.process_running(vmid): - # i=vmid: strange workaround for strange problem the it uses the wrong vmid... - def stopCallback(st, i=vmid): statusCallback(i, st) + def shutdownVMs(self,timeout,parallel=True, statusCallback=lambda vmid, st : None): + threads = [] + for vmid in self.getVmIds(): + if self.process_running(vmid): + # i=vmid: strange workaround for strange problem the it uses the wrong vmid... + def stopCallback(st, i=vmid): statusCallback(i, st) - thread = threading.Thread(target=lambda : self.stopVM(vmid, timeout, stopCallback)) - thread.start() - if parallel: - threads.append(thread) - else: - thread.join() + thread = threading.Thread(target=lambda : self.stopVM(vmid, timeout, stopCallback)) + thread.start() + if parallel: + threads.append(thread) + else: + thread.join() - for thread in threads: - thread.join() - - def stopVM(self, vmid, timeout=None, statusCallback=lambda st : None, wait=False): - proto = QEMUMonitorProtocol(self.config['kvm']['qmpsocket'].replace("$VMID", vmid)) - proto.connect() - - statusCallback("send_powerdown") - proto.cmd("system_powerdown") - - if timeout is None and not wait: - proto.close() - return + for thread in threads: + thread.join() + + def stopVM(self, vmid, timeout=None, statusCallback=lambda st : None, wait=False): + proto = QEMUMonitorProtocol(self.config['kvm']['qmpsocket'].replace("$VMID", vmid)) + proto.connect() + + statusCallback("send_powerdown") + proto.cmd("system_powerdown") + + if timeout is None and not wait: + proto.close() + return - timeoutEvent = threading.Event() + timeoutEvent = threading.Event() - def waitForShutdown(): - shutDown = False - while not timeoutEvent.is_set() and not shutDown : - event = proto.pull_event(wait=True) - shutDown = event is not None and event["event"] == 'SHUTDOWN' - - eventWaitThread = threading.Thread(target=waitForShutdown) - eventWaitThread.start() - - if(timeout is None and wait): - eventWaitThread.join() - proto.close() - return + def waitForShutdown(): + shutDown = False + while not timeoutEvent.is_set() and not shutDown : + event = proto.pull_event(wait=True) + shutDown = event is not None and event["event"] == 'SHUTDOWN' + + eventWaitThread = threading.Thread(target=waitForShutdown) + eventWaitThread.start() + + if(timeout is None and wait): + eventWaitThread.join() + proto.close() + return - eventWaitThread.join(timeout) - timeoutEvent.set() - if eventWaitThread.is_alive(): - statusCallback("send_quit") - proto.cmd("quit") - time.sleep(1) - if(self.process_running(vmid)): - statusCallback("kill_vm") - self.killVm(vmid, 9) #kill it with fire! + eventWaitThread.join(timeout) + timeoutEvent.set() + if eventWaitThread.is_alive(): + statusCallback("send_quit") + proto.cmd("quit") + time.sleep(1) + if(self.process_running(vmid)): + statusCallback("kill_vm") + self.killVm(vmid, 9) #kill it with fire! - proto.close() + proto.close() - def killVm(self, vmid, signal=15): - pid = self.getPid(vmid) - - if pid is not None: - os.kill(pid,signal) + def killVm(self, vmid, signal=15): + pid = self.getPid(vmid) + + if pid is not None: + os.kill(pid,signal) - - def createArguments(self, vmid): - if ('VMs' in self.config) and (vmid in self.config['VMs']): - config = self.config['VMs'][vmid] - else: - raise Exception("No such VM configuration") + + def createArguments(self, vmid): + if ('VMs' in self.config) and (vmid in self.config['VMs']): + config = self.config['VMs'][vmid] + else: + raise Exception("No such VM configuration") - args = "" - if ('cpu' in config): - args += " -cpu " + config['cpu'] - if ('smp' in config): - args += " -smp " + str(config['smp']) - if ('memory' in config): - args += " -m " + str(config['memory']) - if ('disk' in config): - disk = config['disk'] - if ('file' in disk): - args += " -drive file=" + disk['file'] - if ('cache' in disk): - args += ",cache=" + disk['cache'] - if ('hw' in disk): - args += ",if=" + disk['hw'] - if ('cdrom' in config): - args += " -cdrom " + config['cdrom'] - if ('network' in config): - net = config['network'] - args += " -net nic,vlan=1" - if ('mac' in net): - args += ",macaddr=" + net['mac'] - if ('hw' in net): - args += ",model=" + net['hw'] - args += " -net tap,vlan=1" - if ('dev' in net): - args += ",ifname=" + net['dev'] - args += ",script=no,downscript=no" - if ('vnc' in config): - vnc = config['vnc'] - if ('display' in vnc): - args += " -vnc 127.0.0.1:" + str(vnc['display']) - if ('keyboard' in config): - args += " -k " + config['keyboard'] - if ('kernel' in config): - args += " -kernel " + config['kernel'] - if ('append' in config): - args += " -append \"" + config['append'] + "\"" - return args + args = "" + if ('cpu' in config): + args += " -cpu " + config['cpu'] + if ('smp' in config): + args += " -smp " + str(config['smp']) + if ('memory' in config): + args += " -m " + str(config['memory']) + if ('disk' in config): + disk = config['disk'] + if ('file' in disk): + args += " -drive file=" + disk['file'] + if ('cache' in disk): + args += ",cache=" + disk['cache'] + if ('hw' in disk): + args += ",if=" + disk['hw'] + if ('cdrom' in config): + args += " -cdrom " + config['cdrom'] + if ('network' in config): + net = config['network'] + args += " -net nic,vlan=1" + if ('mac' in net): + args += ",macaddr=" + net['mac'] + if ('hw' in net): + args += ",model=" + net['hw'] + args += " -net tap,vlan=1" + if ('dev' in net): + args += ",ifname=" + net['dev'] + args += ",script=no,downscript=no" + if ('vnc' in config): + vnc = config['vnc'] + if ('display' in vnc): + args += " -vnc 127.0.0.1:" + str(vnc['display']) + if ('keyboard' in config): + args += " -k " + config['keyboard'] + if ('kernel' in config): + args += " -kernel " + config['kernel'] + if ('append' in config): + args += " -append \"" + config['append'] + "\"" + return args - def setupNetwork(self, vmid): - if ('VMs' in self.config) and (vmid in self.config['VMs']): - config = self.config['VMs'][vmid] - else: - raise Exception("No such VM configuration") - - commands = [] - if ('network' in config): - net = config['network'] - if ('dev' in net): - commands.append(["tunctl", "-t", net['dev']]) - commands.append(["ip", "link", "set", "dev", net['dev'], "up"]) - commands.append(["ip", "addr", "add", "dev", net['dev'], "0.0.0.0"]) - commands.append(["ip", "-6" , "addr", "add", "dev", net['dev'], "fe80::1/64"]) + def setupNetwork(self, vmid): + if ('VMs' in self.config) and (vmid in self.config['VMs']): + config = self.config['VMs'][vmid] + else: + raise Exception("No such VM configuration") + + commands = [] + if ('network' in config): + net = config['network'] + if ('dev' in net): + commands.append(["tunctl", "-t", net['dev']]) + commands.append(["ip", "link", "set", "dev", net['dev'], "up"]) + commands.append(["ip", "addr", "add", "dev", net['dev'], "0.0.0.0"]) + commands.append(["ip", "-6" , "addr", "add", "dev", net['dev'], "fe80::1/64"]) - chain = "VMNET-FWD-" + vmid.upper() + chain = "VMNET-FWD-" + vmid.upper() - commands.append(["iptables", "--new-chain", chain]) - if ('ip' in net): - for ip in net['ip']: - commands.append(["ip", "route", "add", ip, "dev", net['dev']]) - commands.append(["iptables", "-A", chain, "-i", net['dev'], "-s", ip, "-j", "ACCEPT"]) - commands.append(["iptables", "-A", chain, "-i", net['dev'], "-j", "REJECT", "--reject-with", "icmp-admin-prohibited"]) - commands.append(["iptables", "-A", "FORWARD", "-j", chain]) - - commands.append(["ip6tables", "--new-chain", chain]) - if ('ipv6' in net): - for ipv6 in net['ipv6']: - commands.append( ["ip", "-6", "route", "add", ipv6, "dev", net['dev']]) - commands.append(["ip6tables", "-A", chain, "-i", net['dev'], "-s", ipv6, "-j", "ACCEPT"]) - commands.append(["ip6tables", "-A", chain, "-i", net['dev'], "-j", "REJECT", "--reject-with", "icmp6-adm-prohibited"]) - commands.append(["ip6tables", "-A", "FORWARD", "-j", chain]) - - for cmd in commands: - subprocess.call(cmd, stdout=open("/dev/null") - - ) - - VMdhcpd(vmid, self.config).start() + commands.append(["iptables", "--new-chain", chain]) + if ('ip' in net): + for ip in net['ip']: + commands.append(["ip", "route", "add", ip, "dev", net['dev']]) + commands.append(["iptables", "-A", chain, "-i", net['dev'], "-s", ip, "-j", "ACCEPT"]) + commands.append(["iptables", "-A", chain, "-i", net['dev'], "-j", "REJECT", "--reject-with", "icmp-admin-prohibited"]) + commands.append(["iptables", "-A", "FORWARD", "-j", chain]) + + commands.append(["ip6tables", "--new-chain", chain]) + if ('ipv6' in net): + for ipv6 in net['ipv6']: + commands.append( ["ip", "-6", "route", "add", ipv6, "dev", net['dev']]) + commands.append(["ip6tables", "-A", chain, "-i", net['dev'], "-s", ipv6, "-j", "ACCEPT"]) - def teardownNetwork(self, vmid): - if ('VMs' in self.config) and (vmid in self.config['VMs']): - config = self.config['VMs'][vmid] - else: - raise Exception("No such VM configuration") - - commands = [] - if ('network' in config): - net = config['network'] - if ('dev' in net): - commands.append(["tunctl", "-d", net['dev']]) + # routes networks onto a specific ipv6 address belonging to the VM + if ('route6' in net): + for dstnet, viaadd in net['route6'].items(): + commands.append( ["ip", "-6", "route", "add", dstnet, "via", viaadd ]) + commands.append(["ip6tables", "-A", chain, "-i", net['dev'], "-s", dstnet, "-j", "ACCEPT"]) - chain = "VMNET-FWD-" + vmid.upper() + commands.append(["ip6tables", "-A", chain, "-i", net['dev'], "-j", "REJECT", "--reject-with", "icmp6-adm-prohibited"]) + commands.append(["ip6tables", "-A", "FORWARD", "-j", chain]) + + for cmd in commands: + subprocess.call(cmd, stdout=open("/dev/null") + + ) + + VMdhcpd(vmid, self.config).start() - commands.append(["iptables", "-F", chain]) - commands.append(["iptables", "-D", "FORWARD", "-j", chain]) - commands.append(["iptables", "--delete-chain", chain]) - - commands.append(["ip6tables", "-F", chain]) - commands.append(["ip6tables", "-D", "FORWARD", "-j", chain]) - commands.append(["ip6tables", "--delete-chain", chain]) - - for cmd in commands: - subprocess.call(cmd, stdout=open("/dev/null")) + def teardownNetwork(self, vmid): + if ('VMs' in self.config) and (vmid in self.config['VMs']): + config = self.config['VMs'][vmid] + else: + raise Exception("No such VM configuration") + + commands = [] + if ('network' in config): + net = config['network'] + if ('dev' in net): + commands.append(["tunctl", "-d", net['dev']]) - VMdhcpd(vmid, self.config).stop() + chain = "VMNET-FWD-" + vmid.upper() - def generateAuthorizedKeys(self): - userkeys = {} - keydir = os.path.join(self.config["ssh"]["homedir"], self.config["ssh"]["keydir"]) - for filename in os.listdir(keydir): - fnsplit = filename.split("@") - if len(fnsplit) == 2: - user = fnsplit[0] - with open(os.path.join(keydir,filename)) as f: - keystring = f.readline().rstrip('\n') - if user in userkeys: - userkeys[user].append(keystring) - else: - userkeys[user] = [keystring] + commands.append(["iptables", "-F", chain]) + commands.append(["iptables", "-D", "FORWARD", "-j", chain]) + commands.append(["iptables", "--delete-chain", chain]) + + commands.append(["ip6tables", "-F", chain]) + commands.append(["ip6tables", "-D", "FORWARD", "-j", chain]) + commands.append(["ip6tables", "--delete-chain", chain]) + + for cmd in commands: + subprocess.call(cmd, stdout=open("/dev/null")) - authorized_keys = "" - for user, keys in userkeys.items(): - prepend = 'no-agent-forwarding,no-user-rc,no-X11-forwarding,command="read",' - for vm, vals in self.config["VMs"].items(): - if vals["owner"] == user: - prepend += 'permitopen="localhost:{0}",'.format(vals["vnc"]["display"] + 5900) - prepend += 'permitopen="127.0.0.1:{0}",'.format(vals["vnc"]["display"] + 5900) - prepend += 'permitopen="[::1]:{0}",'.format(vals["vnc"]["display"] + 5900) - prepend = prepend[:-1] + VMdhcpd(vmid, self.config).stop() - for key in keys: - authorized_keys += prepend + " " + key + "\n" + def generateAuthorizedKeys(self): + userkeys = {} + keydir = os.path.join(self.config["ssh"]["homedir"], self.config["ssh"]["keydir"]) + for filename in os.listdir(keydir): + fnsplit = filename.split("@") + if len(fnsplit) == 2: + user = fnsplit[0] + with open(os.path.join(keydir,filename)) as f: + keystring = f.readline().rstrip('\n') + if user in userkeys: + userkeys[user].append(keystring) + else: + userkeys[user] = [keystring] - authorized_key_file = os.path.join(self.config["ssh"]["homedir"], ".ssh/authorized_keys") - with open(authorized_key_file, mode="w") as f: - f.write(authorized_keys) - - os.chmod(authorized_key_file ,0o600) - os.chown(authorized_key_file, pwd.getpwnam(self.config["ssh"]["user"]).pw_uid, pwd.getpwnam(self.config["ssh"]["user"]).pw_gid) + authorized_keys = "" + for user, keys in userkeys.items(): + prepend = 'no-agent-forwarding,no-user-rc,no-X11-forwarding,command="read",' + for vm, vals in self.config["VMs"].items(): + if vals["owner"] == user: + prepend += 'permitopen="localhost:{0}",'.format(vals["vnc"]["display"] + 5900) + prepend += 'permitopen="127.0.0.1:{0}",'.format(vals["vnc"]["display"] + 5900) + prepend += 'permitopen="[::1]:{0}",'.format(vals["vnc"]["display"] + 5900) + prepend = prepend[:-1] + + for key in keys: + authorized_keys += prepend + " " + key + "\n" + + authorized_key_file = os.path.join(self.config["ssh"]["homedir"], ".ssh/authorized_keys") + with open(authorized_key_file, mode="w") as f: + f.write(authorized_keys) + + os.chmod(authorized_key_file ,0o600) + os.chown(authorized_key_file, pwd.getpwnam(self.config["ssh"]["user"]).pw_uid, pwd.getpwnam(self.config["ssh"]["user"]).pw_gid)