# # CORE # Copyright (c)2010-2012 the Boeing Company. # See the LICENSE file included in this distribution. # # authors: core-dev@pf.itd.nrl.navy.mil # """ vnode.py: SimpleJailNode and JailNode classes that implement the FreeBSD jail-based virtual node. """ import os import subprocess import threading from core import constants from core.bsd.netgraph import createngnode from core.bsd.netgraph import destroyngnode from core.coreobj import PyCoreNetIf from core.coreobj import PyCoreNode from core.misc import utils utils.checkexec([constants.IFCONFIG_BIN, constants.VIMAGE_BIN]) class VEth(PyCoreNetIf): def __init__(self, node, name, localname, mtu=1500, net=None, start=True): PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu) # name is the device name (e.g. ngeth0, ngeth1, etc.) before it is # installed in a node; the Netgraph name is renamed to localname # e.g. before install: name = ngeth0 localname = n0_0_123 # after install: name = eth0 localname = n0_0_123 self.localname = localname self.ngid = None self.net = None self.pipe = None self.addrlist = [] self.hwaddr = None self.up = False self.hook = "ether" if start: self.startup() def startup(self): hookstr = "%s %s" % (self.hook, self.hook) ngname, ngid = createngnode(type="eiface", hookstr=hookstr, name=self.localname) self.name = ngname self.ngid = ngid utils.check_call([constants.IFCONFIG_BIN, ngname, "up"]) self.up = True def shutdown(self): if not self.up: return destroyngnode(self.localname) self.up = False def attachnet(self, net): if self.net: self.detachnet() self.net = None net.attach(self) self.net = net def detachnet(self): if self.net is not None: self.net.detach(self) def addaddr(self, addr): self.addrlist.append(addr) def deladdr(self, addr): self.addrlist.remove(addr) def sethwaddr(self, addr): self.hwaddr = addr class TunTap(PyCoreNetIf): """TUN/TAP virtual device in TAP mode""" def __init__(self, node, name, localname, mtu=None, net=None, start=True): raise NotImplementedError class SimpleJailNode(PyCoreNode): def __init__(self, session, objid=None, name=None, nodedir=None, verbose=False): PyCoreNode.__init__(self, session, objid, name) self.nodedir = nodedir self.verbose = verbose self.pid = None self.up = False self.lock = threading.RLock() self._mounts = [] def startup(self): if self.up: raise Exception, "already up" vimg = [constants.VIMAGE_BIN, "-c", self.name] try: os.spawnlp(os.P_WAIT, constants.VIMAGE_BIN, *vimg) except OSError: raise Exception, ("vimage command not found while running: %s" % \ vimg) self.info("bringing up loopback interface") self.cmd([constants.IFCONFIG_BIN, "lo0", "127.0.0.1"]) self.info("setting hostname: %s" % self.name) self.cmd(["hostname", self.name]) self.cmd([constants.SYSCTL_BIN, "vfs.morphing_symlinks=1"]) self.up = True def shutdown(self): if not self.up: return for netif in self.netifs(): netif.shutdown() self._netif.clear() del self.session vimg = [constants.VIMAGE_BIN, "-d", self.name] try: os.spawnlp(os.P_WAIT, constants.VIMAGE_BIN, *vimg) except OSError: raise Exception("vimage command not found while running: %s" % vimg) self.up = False def cmd(self, args, wait=True): if wait: mode = os.P_WAIT else: mode = os.P_NOWAIT tmp = utils.call([constants.VIMAGE_BIN, self.name] + args, cwd=self.nodedir) if not wait: tmp = None if tmp: self.warn("cmd exited with status %s: %s" % (tmp, str(args))) return tmp def cmdresult(self, args, wait=True): cmdid, cmdin, cmdout, cmderr = self.popen(args) result = cmdout.read() result += cmderr.read() cmdin.close() cmdout.close() cmderr.close() if wait: status = cmdid.wait() else: status = 0 return status, result def popen(self, args): cmd = [constants.VIMAGE_BIN, self.name] cmd.extend(args) tmp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.nodedir) return tmp, tmp.stdin, tmp.stdout, tmp.stderr def icmd(self, args): return os.spawnlp(os.P_WAIT, constants.VIMAGE_BIN, constants.VIMAGE_BIN, self.name, *args) def term(self, sh="/bin/sh"): return os.spawnlp(os.P_WAIT, "xterm", "xterm", "-ut", "-title", self.name, "-e", constants.VIMAGE_BIN, self.name, sh) def termcmdstring(self, sh="/bin/sh"): """ We add "sudo" to the command string because the GUI runs as a normal user. """ return "cd %s && sudo %s %s %s" % (self.nodedir, constants.VIMAGE_BIN, self.name, sh) def shcmd(self, cmdstr, sh="/bin/sh"): return self.cmd([sh, "-c", cmdstr]) def boot(self): pass def mount(self, source, target): source = os.path.abspath(source) self.info("mounting %s at %s" % (source, target)) self.addsymlink(path=target, file=None) def umount(self, target): self.info("unmounting %s" % target) def newveth(self, ifindex=None, ifname=None, net=None): self.lock.acquire() try: if ifindex is None: ifindex = self.newifindex() if ifname is None: ifname = "eth%d" % ifindex sessionid = self.session.shortsessionid() name = "n%s_%s_%s" % (self.objid, ifindex, sessionid) localname = name ifclass = VEth veth = ifclass(node=self, name=name, localname=localname, mtu=1500, net=net, start=self.up) if self.up: # install into jail utils.check_call([constants.IFCONFIG_BIN, veth.name, "vnet", self.name]) # rename from "ngeth0" to "eth0" self.cmd([constants.IFCONFIG_BIN, veth.name, "name", ifname]) veth.name = ifname try: self.addnetif(veth, ifindex) except: veth.shutdown() del veth raise return ifindex finally: self.lock.release() def sethwaddr(self, ifindex, addr): self._netif[ifindex].sethwaddr(addr) if self.up: self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), "link", str(addr)]) def addaddr(self, ifindex, addr): if self.up: if ":" in addr: family = "inet6" else: family = "inet" self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), family, "alias", str(addr)]) self._netif[ifindex].addaddr(addr) def deladdr(self, ifindex, addr): try: self._netif[ifindex].deladdr(addr) except ValueError: self.warn("trying to delete unknown address: %s" % addr) if self.up: if ":" in addr: family = "inet6" else: family = "inet" self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), family, "-alias", str(addr)]) valid_deladdrtype = ("inet", "inet6", "inet6link") def delalladdr(self, ifindex, addrtypes=valid_deladdrtype): addr = self.getaddr(self.ifname(ifindex), rescan=True) for t in addrtypes: if t not in self.valid_deladdrtype: raise ValueError, "addr type must be in: " + \ " ".join(self.valid_deladdrtype) for a in addr[t]: self.deladdr(ifindex, a) # update cached information self.getaddr(self.ifname(ifindex), rescan=True) def ifup(self, ifindex): if self.up: self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), "up"]) def newnetif(self, net=None, addrlist=[], hwaddr=None, ifindex=None, ifname=None): self.lock.acquire() try: ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net) if net is not None: self.attachnet(ifindex, net) if hwaddr: self.sethwaddr(ifindex, hwaddr) for addr in utils.maketuple(addrlist): self.addaddr(ifindex, addr) self.ifup(ifindex) return ifindex finally: self.lock.release() def attachnet(self, ifindex, net): self._netif[ifindex].attachnet(net) def detachnet(self, ifindex): self._netif[ifindex].detachnet() def addfile(self, srcname, filename): shcmd = 'mkdir -p $(dirname "%s") && mv "%s" "%s" && sync' % (filename, srcname, filename) self.shcmd(shcmd) def getaddr(self, ifname, rescan=False): return None # return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan) def addsymlink(self, path, file): """ Create a symbolic link from /path/name/file -> /tmp/pycore.nnnnn/@.conf/path.name/file """ dirname = path if dirname and dirname[0] == "/": dirname = dirname[1:] dirname = dirname.replace("/", ".") if file: pathname = os.path.join(path, file) sym = os.path.join(self.session.sessiondir, "@.conf", dirname, file) else: pathname = path sym = os.path.join(self.session.sessiondir, "@.conf", dirname) if os.path.islink(pathname): if os.readlink(pathname) == sym: # this link already exists - silently return return os.unlink(pathname) else: if os.path.exists(pathname): self.warn("did not create symlink for %s since path " \ "exists on host" % pathname) return self.info("creating symlink %s -> %s" % (pathname, sym)) os.symlink(sym, pathname) class JailNode(SimpleJailNode): def __init__(self, session, objid=None, name=None, nodedir=None, bootsh="boot.sh", verbose=False, start=True): super(JailNode, self).__init__(session=session, objid=objid, name=name, nodedir=nodedir, verbose=verbose) self.bootsh = bootsh if not start: return # below here is considered node startup/instantiation code self.makenodedir() self.startup() def boot(self): self.session.services.bootnodeservices(self) def validate(self): self.session.services.validatenodeservices(self) def startup(self): self.lock.acquire() try: super(JailNode, self).startup() # self.privatedir("/var/run") # self.privatedir("/var/log") finally: self.lock.release() def shutdown(self): if not self.up: return self.lock.acquire() # services are instead stopped when session enters datacollect state # self.session.services.stopnodeservices(self) try: super(JailNode, self).shutdown() finally: self.rmnodedir() self.lock.release() def privatedir(self, path): if path[0] != "/": raise ValueError, "path not fully qualified: " + path hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip("/").replace("/", ".")) try: os.mkdir(hostpath) except OSError: pass except Exception, e: raise Exception, e self.mount(hostpath, path) def opennodefile(self, filename, mode="w"): dirname, basename = os.path.split(filename) # self.addsymlink(path=dirname, file=basename) if not basename: raise ValueError, "no basename for filename: " + filename if dirname and dirname[0] == "/": dirname = dirname[1:] dirname = dirname.replace("/", ".") dirname = os.path.join(self.nodedir, dirname) if not os.path.isdir(dirname): os.makedirs(dirname, mode=0755) hostfilename = os.path.join(dirname, basename) return open(hostfilename, mode) def nodefile(self, filename, contents, mode=0644): f = self.opennodefile(filename, "w") f.write(contents) os.chmod(f.name, mode) f.close() self.info("created nodefile: %s; mode: 0%o" % (f.name, mode))