393 lines
13 KiB
Python
393 lines
13 KiB
Python
#
|
|
# 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, signal, sys, subprocess, threading, string
|
|
import random, time
|
|
from core.misc.utils import *
|
|
from core.constants import *
|
|
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position
|
|
from core.emane.nodes import EmaneNode
|
|
from core.bsd.netgraph import *
|
|
|
|
checkexec([IFCONFIG_BIN, 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
|
|
check_call([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 = [VIMAGE_BIN, "-c", self.name]
|
|
try:
|
|
os.spawnlp(os.P_WAIT, VIMAGE_BIN, *vimg)
|
|
except OSError:
|
|
raise Exception, ("vimage command not found while running: %s" % \
|
|
vimg)
|
|
self.info("bringing up loopback interface")
|
|
self.cmd([IFCONFIG_BIN, "lo0", "127.0.0.1"])
|
|
self.info("setting hostname: %s" % self.name)
|
|
self.cmd(["hostname", self.name])
|
|
self.cmd([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 = [VIMAGE_BIN, "-d", self.name]
|
|
try:
|
|
os.spawnlp(os.P_WAIT, 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 = call([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 = [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, VIMAGE_BIN, VIMAGE_BIN, self.name, *args)
|
|
|
|
def term(self, sh = "/bin/sh"):
|
|
return os.spawnlp(os.P_WAIT, "xterm", "xterm", "-ut",
|
|
"-title", self.name, "-e", 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, 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
|
|
check_call([IFCONFIG_BIN, veth.name, "vnet", self.name])
|
|
# rename from "ngeth0" to "eth0"
|
|
self.cmd([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([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([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([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([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 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, path[1:].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))
|
|
|