add route6 option to route ipv6 nets to a specific address

This commit is contained in:
2014-08-02 23:23:16 +02:00
parent d242e3e65e
commit bcb22604a4

View File

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