initial commit after bringing over cleaned up code and testing some examples
This commit is contained in:
parent
c4858e6e0d
commit
00f4ebf5a9
93 changed files with 15189 additions and 13083 deletions
|
@ -1,80 +1,89 @@
|
|||
#
|
||||
# CORE
|
||||
# Copyright (c)2010-2013 the Boeing Company.
|
||||
# See the LICENSE file included in this distribution.
|
||||
#
|
||||
# authors: Tom Goff <thomas.goff@boeing.com>
|
||||
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
||||
#
|
||||
'''
|
||||
"""
|
||||
nodes.py: definition of an LxcNode and CoreNode classes, and other node classes
|
||||
that inherit from the CoreNode, implementing specific node types.
|
||||
'''
|
||||
"""
|
||||
|
||||
from vnode import *
|
||||
from vnet import *
|
||||
from core.misc.ipaddr import *
|
||||
from core.api import coreapi
|
||||
import socket
|
||||
import subprocess
|
||||
import threading
|
||||
from socket import AF_INET
|
||||
from socket import AF_INET6
|
||||
|
||||
from core import constants
|
||||
from core.coreobj import PyCoreNetIf
|
||||
from core.coreobj import PyCoreNode
|
||||
from core.coreobj import PyCoreObj
|
||||
from core.data import LinkData
|
||||
from core.enumerations import LinkTypes
|
||||
from core.enumerations import NodeTypes
|
||||
from core.enumerations import RegisterTlvs
|
||||
from core.misc import ipaddress
|
||||
from core.misc import log
|
||||
from core.misc import utils
|
||||
from core.netns.vnet import GreTapBridge
|
||||
from core.netns.vnet import LxBrNet
|
||||
from core.netns.vnode import LxcNode
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class CtrlNet(LxBrNet):
|
||||
policy = "ACCEPT"
|
||||
CTRLIF_IDX_BASE = 99 # base control interface index
|
||||
DEFAULT_PREFIX_LIST = ["172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
|
||||
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
|
||||
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
|
||||
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24"]
|
||||
|
||||
def __init__(self, session, objid = "ctrlnet", name = None,
|
||||
verbose = False, prefix = None,
|
||||
hostid = None, start = True, assign_address = True,
|
||||
updown_script = None, serverintf = None):
|
||||
self.prefix = IPv4Prefix(prefix)
|
||||
# base control interface index
|
||||
CTRLIF_IDX_BASE = 99
|
||||
DEFAULT_PREFIX_LIST = [
|
||||
"172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
|
||||
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
|
||||
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
|
||||
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24"
|
||||
]
|
||||
|
||||
def __init__(self, session, objid="ctrlnet", name=None, prefix=None,
|
||||
hostid=None, start=True, assign_address=True,
|
||||
updown_script=None, serverintf=None):
|
||||
self.prefix = ipaddress.Ipv4Prefix(prefix)
|
||||
self.hostid = hostid
|
||||
self.assign_address = assign_address
|
||||
self.updown_script = updown_script
|
||||
self.serverintf = serverintf
|
||||
LxBrNet.__init__(self, session, objid = objid, name = name,
|
||||
verbose = verbose, start = start)
|
||||
LxBrNet.__init__(self, session, objid=objid, name=name, start=start)
|
||||
|
||||
def startup(self):
|
||||
if self.detectoldbridge():
|
||||
return
|
||||
|
||||
|
||||
LxBrNet.startup(self)
|
||||
if self.hostid:
|
||||
addr = self.prefix.addr(self.hostid)
|
||||
else:
|
||||
addr = self.prefix.maxaddr()
|
||||
addr = self.prefix.max_addr()
|
||||
msg = "Added control network bridge: %s %s" % \
|
||||
(self.brname, self.prefix)
|
||||
(self.brname, self.prefix)
|
||||
addrlist = ["%s/%s" % (addr, self.prefix.prefixlen)]
|
||||
if self.assign_address:
|
||||
self.addrconfig(addrlist = addrlist)
|
||||
self.addrconfig(addrlist=addrlist)
|
||||
msg += " address %s" % addr
|
||||
self.session.info(msg)
|
||||
logger.info(msg)
|
||||
if self.updown_script is not None:
|
||||
self.info("interface %s updown script '%s startup' called" % \
|
||||
(self.brname, self.updown_script))
|
||||
check_call([self.updown_script, self.brname, "startup"])
|
||||
logger.info("interface %s updown script '%s startup' called" % \
|
||||
(self.brname, self.updown_script))
|
||||
subprocess.check_call([self.updown_script, self.brname, "startup"])
|
||||
if self.serverintf is not None:
|
||||
try:
|
||||
check_call([BRCTL_BIN, "addif", self.brname, self.serverintf])
|
||||
check_call([IP_BIN, "link", "set", self.serverintf, "up"])
|
||||
except Exception, e:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, self.brname,
|
||||
"Error joining server interface %s to controlnet bridge %s: %s" % \
|
||||
(self.serverintf, self.brname, e))
|
||||
|
||||
subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, self.serverintf])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.serverintf, "up"])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error joining server interface %s to controlnet bridge %s",
|
||||
self.serverintf, self.brname)
|
||||
|
||||
def detectoldbridge(self):
|
||||
''' Occassionally, control net bridges from previously closed sessions are not cleaned up.
|
||||
"""
|
||||
Occassionally, control net bridges from previously closed sessions are not cleaned up.
|
||||
Check if there are old control net bridges and delete them
|
||||
'''
|
||||
retstat, retstr = cmdresult([BRCTL_BIN,'show'])
|
||||
"""
|
||||
retstat, retstr = utils.cmdresult([constants.BRCTL_BIN, 'show'])
|
||||
if retstat != 0:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, None,
|
||||
"Unable to retrieve list of installed bridges")
|
||||
logger.error("Unable to retrieve list of installed bridges")
|
||||
lines = retstr.split('\n')
|
||||
for line in lines[1:]:
|
||||
cols = line.split('\t')
|
||||
|
@ -82,46 +91,47 @@ class CtrlNet(LxBrNet):
|
|||
flds = cols[0].split('.')
|
||||
if len(flds) == 3:
|
||||
if flds[0] == 'b' and flds[1] == self.objid:
|
||||
self.session.exception(coreapi.CORE_EXCP_LEVEL_FATAL, "CtrlNet.startup()", None,
|
||||
"Error: An active control net bridge (%s) found. "\
|
||||
"An older session might still be running. " \
|
||||
"Stop all sessions and, if needed, delete %s to continue." % \
|
||||
(oldbr, oldbr))
|
||||
logger.error(
|
||||
"Error: An active control net bridge (%s) found. " \
|
||||
"An older session might still be running. " \
|
||||
"Stop all sessions and, if needed, delete %s to continue." % \
|
||||
(oldbr, oldbr)
|
||||
)
|
||||
return True
|
||||
'''
|
||||
"""
|
||||
# Do this if we want to delete the old bridge
|
||||
self.warn("Warning: Old %s bridge found: %s" % (self.objid, oldbr))
|
||||
logger.warn("Warning: Old %s bridge found: %s" % (self.objid, oldbr))
|
||||
try:
|
||||
check_call([BRCTL_BIN, 'delbr', oldbr])
|
||||
except Exception, e:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, oldbr,
|
||||
"Error deleting old bridge %s" % oldbr)
|
||||
self.info("Deleted %s" % oldbr)
|
||||
'''
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.exception("Error deleting old bridge %s", oldbr, e)
|
||||
logger.info("Deleted %s", oldbr)
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
def shutdown(self):
|
||||
if self.serverintf is not None:
|
||||
try:
|
||||
check_call([BRCTL_BIN, "delif", self.brname, self.serverintf])
|
||||
except Exception, e:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
|
||||
"Error deleting server interface %s to controlnet bridge %s: %s" % \
|
||||
(self.serverintf, self.brname, e))
|
||||
|
||||
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, self.serverintf])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error deleting server interface %s to controlnet bridge %s",
|
||||
self.serverintf, self.brname)
|
||||
|
||||
if self.updown_script is not None:
|
||||
self.info("interface %s updown script '%s shutdown' called" % \
|
||||
(self.brname, self.updown_script))
|
||||
check_call([self.updown_script, self.brname, "shutdown"])
|
||||
logger.info("interface %s updown script '%s shutdown' called" % (self.brname, self.updown_script))
|
||||
subprocess.check_call([self.updown_script, self.brname, "shutdown"])
|
||||
LxBrNet.shutdown(self)
|
||||
|
||||
def tolinkmsgs(self, flags):
|
||||
''' Do not include CtrlNet in link messages describing this session.
|
||||
'''
|
||||
def all_link_data(self, flags):
|
||||
"""
|
||||
Do not include CtrlNet in link messages describing this session.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class CoreNode(LxcNode):
|
||||
apitype = coreapi.CORE_NODE_DEF
|
||||
apitype = NodeTypes.DEFAULT.value
|
||||
|
||||
|
||||
class PtpNet(LxBrNet):
|
||||
policy = "ACCEPT"
|
||||
|
@ -129,127 +139,148 @@ class PtpNet(LxBrNet):
|
|||
def attach(self, netif):
|
||||
if len(self._netif) > 1:
|
||||
raise ValueError, \
|
||||
"Point-to-point links support at most 2 network interfaces"
|
||||
"Point-to-point links support at most 2 network interfaces"
|
||||
LxBrNet.attach(self, netif)
|
||||
|
||||
def tonodemsg(self, flags):
|
||||
''' Do not generate a Node Message for point-to-point links. They are
|
||||
built using a link message instead.
|
||||
'''
|
||||
def data(self, message_type):
|
||||
"""
|
||||
Do not generate a Node Message for point-to-point links. They are
|
||||
built using a link message instead.
|
||||
"""
|
||||
pass
|
||||
|
||||
def tolinkmsgs(self, flags):
|
||||
''' Build CORE API TLVs for a point-to-point link. One Link message
|
||||
describes this network.
|
||||
'''
|
||||
tlvdata = ""
|
||||
def all_link_data(self, flags):
|
||||
"""
|
||||
Build CORE API TLVs for a point-to-point link. One Link message
|
||||
describes this network.
|
||||
"""
|
||||
|
||||
all_links = []
|
||||
|
||||
if len(self._netif) != 2:
|
||||
return tlvdata
|
||||
(if1, if2) = self._netif.items()
|
||||
return all_links
|
||||
|
||||
if1, if2 = self._netif.items()
|
||||
if1 = if1[1]
|
||||
if2 = if2[1]
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
|
||||
if1.node.objid)
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
|
||||
if2.node.objid)
|
||||
uni = False
|
||||
|
||||
unidirectional = 0
|
||||
if if1.getparams() != if2.getparams():
|
||||
uni = True
|
||||
tlvdata += self.netifparamstolink(if1)
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_TYPE,
|
||||
self.linktype)
|
||||
if uni:
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1)
|
||||
unidirectional = 1
|
||||
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \
|
||||
if1.node.getifindex(if1))
|
||||
if if1.hwaddr:
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1MAC,
|
||||
if1.hwaddr)
|
||||
for addr in if1.addrlist:
|
||||
(ip, sep, mask) = addr.partition('/')
|
||||
interface1_ip4 = None
|
||||
interface1_ip4_mask = None
|
||||
interface1_ip6 = None
|
||||
interface1_ip6_mask = None
|
||||
for address in if1.addrlist:
|
||||
ip, sep, mask = address.partition('/')
|
||||
mask = int(mask)
|
||||
if isIPv4Address(ip):
|
||||
if ipaddress.is_ipv4_address(ip):
|
||||
family = AF_INET
|
||||
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP4
|
||||
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP4MASK
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface1_ip4_mask = mask
|
||||
else:
|
||||
family = AF_INET6
|
||||
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP6
|
||||
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP6MASK
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip,
|
||||
IPAddr(af=family, addr=ipl))
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface1_ip6_mask = mask
|
||||
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \
|
||||
if2.node.getifindex(if2))
|
||||
if if2.hwaddr:
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2MAC,
|
||||
if2.hwaddr)
|
||||
for addr in if2.addrlist:
|
||||
(ip, sep, mask) = addr.partition('/')
|
||||
interface2_ip4 = None
|
||||
interface2_ip4_mask = None
|
||||
interface2_ip6 = None
|
||||
interface2_ip6_mask = None
|
||||
for address in if2.addrlist:
|
||||
ip, sep, mask = address.partition('/')
|
||||
mask = int(mask)
|
||||
if isIPv4Address(ip):
|
||||
if ipaddress.is_ipv4_address(ip):
|
||||
family = AF_INET
|
||||
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP4
|
||||
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP4MASK
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface2_ip4_mask = mask
|
||||
else:
|
||||
family = AF_INET6
|
||||
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP6
|
||||
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP6MASK
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip,
|
||||
IPAddr(af=family, addr=ipl))
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
|
||||
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
|
||||
if not uni:
|
||||
return [msg,]
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface2_ip6_mask = mask
|
||||
|
||||
# TODO: not currently used
|
||||
# loss=netif.getparam('loss')
|
||||
link_data = LinkData(
|
||||
message_type=flags,
|
||||
node1_id=if1.node.objid,
|
||||
node2_id=if2.node.objid,
|
||||
link_type=self.linktype,
|
||||
unidirectional=unidirectional,
|
||||
delay=if1.getparam("delay"),
|
||||
bandwidth=if1.getparam("bw"),
|
||||
dup=if1.getparam("duplicate"),
|
||||
jitter=if1.getparam("jitter"),
|
||||
interface1_id=if1.node.getifindex(if1),
|
||||
interface1_mac=if1.hwaddr,
|
||||
interface1_ip4=interface1_ip4,
|
||||
interface1_ip4_mask=interface1_ip4_mask,
|
||||
interface1_ip6=interface1_ip6,
|
||||
interface1_ip6_mask=interface1_ip6_mask,
|
||||
interface2_id=if2.node.getifindex(if2),
|
||||
interface2_mac=if2.hwaddr,
|
||||
interface2_ip4=interface2_ip4,
|
||||
interface2_ip4_mask=interface2_ip4_mask,
|
||||
interface2_ip6=interface2_ip6,
|
||||
interface2_ip6_mask=interface2_ip6_mask,
|
||||
)
|
||||
|
||||
all_links.append(link_data)
|
||||
|
||||
# build a 2nd link message for the upstream link parameters
|
||||
# (swap if1 and if2)
|
||||
tlvdata = ""
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
|
||||
if2.node.objid)
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
|
||||
if1.node.objid)
|
||||
tlvdata += self.netifparamstolink(if2)
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1)
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \
|
||||
if2.node.getifindex(if2))
|
||||
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \
|
||||
if1.node.getifindex(if1))
|
||||
msg2 = coreapi.CoreLinkMessage.pack(0, tlvdata)
|
||||
return [msg, msg2]
|
||||
if unidirectional:
|
||||
link_data = LinkData(
|
||||
message_type=0,
|
||||
node1_id=if2.node.objid,
|
||||
node2_id=if1.node.objid,
|
||||
delay=if1.getparam("delay"),
|
||||
bandwidth=if1.getparam("bw"),
|
||||
dup=if1.getparam("duplicate"),
|
||||
jitter=if1.getparam("jitter"),
|
||||
unidirectional=1,
|
||||
interface1_id=if2.node.getifindex(if2),
|
||||
interface2_id=if1.node.getifindex(if1)
|
||||
)
|
||||
all_links.append(link_data)
|
||||
|
||||
return all_links
|
||||
|
||||
|
||||
class SwitchNode(LxBrNet):
|
||||
apitype = coreapi.CORE_NODE_SWITCH
|
||||
apitype = NodeTypes.SWITCH.value
|
||||
policy = "ACCEPT"
|
||||
type = "lanswitch"
|
||||
|
||||
|
||||
class HubNode(LxBrNet):
|
||||
apitype = coreapi.CORE_NODE_HUB
|
||||
apitype = NodeTypes.HUB.value
|
||||
policy = "ACCEPT"
|
||||
type = "hub"
|
||||
|
||||
def __init__(self, session, objid = None, name = None, verbose = False,
|
||||
start = True):
|
||||
''' the Hub node forwards packets to all bridge ports by turning off
|
||||
the MAC address learning
|
||||
'''
|
||||
LxBrNet.__init__(self, session, objid, name, verbose, start)
|
||||
def __init__(self, session, objid=None, name=None, start=True):
|
||||
"""
|
||||
the Hub node forwards packets to all bridge ports by turning off
|
||||
the MAC address learning
|
||||
"""
|
||||
LxBrNet.__init__(self, session, objid, name, start)
|
||||
if start:
|
||||
check_call([BRCTL_BIN, "setageing", self.brname, "0"])
|
||||
subprocess.check_call([constants.BRCTL_BIN, "setageing", self.brname, "0"])
|
||||
|
||||
|
||||
class WlanNode(LxBrNet):
|
||||
apitype = coreapi.CORE_NODE_WLAN
|
||||
linktype = coreapi.CORE_LINK_WIRELESS
|
||||
apitype = NodeTypes.WIRELESS_LAN.value
|
||||
linktype = LinkTypes.WIRELESS.value
|
||||
policy = "DROP"
|
||||
type = "wlan"
|
||||
|
||||
def __init__(self, session, objid = None, name = None, verbose = False,
|
||||
start = True, policy = None):
|
||||
LxBrNet.__init__(self, session, objid, name, verbose, start, policy)
|
||||
def __init__(self, session, objid=None, name=None, start=True, policy=None):
|
||||
LxBrNet.__init__(self, session, objid, name, start, policy)
|
||||
# wireless model such as basic range
|
||||
self.model = None
|
||||
# mobility model such as scripted
|
||||
|
@ -258,72 +289,75 @@ class WlanNode(LxBrNet):
|
|||
def attach(self, netif):
|
||||
LxBrNet.attach(self, netif)
|
||||
if self.model:
|
||||
netif.poshook = self.model._positioncallback
|
||||
netif.poshook = self.model.position_callback
|
||||
if netif.node is None:
|
||||
return
|
||||
(x,y,z) = netif.node.position.get()
|
||||
(x, y, z) = netif.node.position.get()
|
||||
# invokes any netif.poshook
|
||||
netif.setposition(x, y, z)
|
||||
#self.model.setlinkparams()
|
||||
# self.model.setlinkparams()
|
||||
|
||||
def setmodel(self, model, config):
|
||||
''' Mobility and wireless model.
|
||||
'''
|
||||
if (self.verbose):
|
||||
self.info("adding model %s" % model._name)
|
||||
if model._type == coreapi.CORE_TLV_REG_WIRELESS:
|
||||
self.model = model(session=self.session, objid=self.objid,
|
||||
verbose=self.verbose, values=config)
|
||||
if self.model._positioncallback:
|
||||
"""
|
||||
Sets the mobility and wireless model.
|
||||
|
||||
:param core.mobility.WirelessModel.cls model: wireless model to set to
|
||||
:param config:
|
||||
:return:
|
||||
"""
|
||||
logger.info("adding model %s" % model.name)
|
||||
if model.config_type == RegisterTlvs.WIRELESS.value:
|
||||
self.model = model(session=self.session, object_id=self.objid, values=config)
|
||||
if self.model.position_callback:
|
||||
for netif in self.netifs():
|
||||
netif.poshook = self.model._positioncallback
|
||||
netif.poshook = self.model.position_callback
|
||||
if netif.node is not None:
|
||||
(x,y,z) = netif.node.position.get()
|
||||
(x, y, z) = netif.node.position.get()
|
||||
netif.poshook(netif, x, y, z)
|
||||
self.model.setlinkparams()
|
||||
elif model._type == coreapi.CORE_TLV_REG_MOBILITY:
|
||||
self.mobility = model(session=self.session, objid=self.objid,
|
||||
verbose=self.verbose, values=config)
|
||||
elif model.config_type == RegisterTlvs.MOBILITY.value:
|
||||
self.mobility = model(session=self.session, object_id=self.objid, values=config)
|
||||
|
||||
def updatemodel(self, model_name, values):
|
||||
''' Allow for model updates during runtime (similar to setmodel().)
|
||||
'''
|
||||
if (self.verbose):
|
||||
self.info("updating model %s" % model_name)
|
||||
if self.model is None or self.model._name != model_name:
|
||||
"""
|
||||
Allow for model updates during runtime (similar to setmodel().)
|
||||
"""
|
||||
logger.info("updating model %s" % model_name)
|
||||
if self.model is None or self.model.name != model_name:
|
||||
return
|
||||
model = self.model
|
||||
if model._type == coreapi.CORE_TLV_REG_WIRELESS:
|
||||
if model.config_type == RegisterTlvs.WIRELESS.value:
|
||||
if not model.updateconfig(values):
|
||||
return
|
||||
if self.model._positioncallback:
|
||||
if self.model.position_callback:
|
||||
for netif in self.netifs():
|
||||
netif.poshook = self.model._positioncallback
|
||||
netif.poshook = self.model.position_callback
|
||||
if netif.node is not None:
|
||||
(x,y,z) = netif.node.position.get()
|
||||
(x, y, z) = netif.node.position.get()
|
||||
netif.poshook(netif, x, y, z)
|
||||
self.model.setlinkparams()
|
||||
|
||||
def tolinkmsgs(self, flags):
|
||||
msgs = LxBrNet.tolinkmsgs(self, flags)
|
||||
def all_link_data(self, flags):
|
||||
all_links = LxBrNet.all_link_data(self, flags)
|
||||
|
||||
if self.model:
|
||||
msgs += self.model.tolinkmsgs(flags)
|
||||
return msgs
|
||||
all_links.extend(self.model.all_link_data(flags))
|
||||
|
||||
return all_links
|
||||
|
||||
|
||||
class RJ45Node(PyCoreNode, PyCoreNetIf):
|
||||
''' RJ45Node is a physical interface on the host linked to the emulated
|
||||
network.
|
||||
'''
|
||||
apitype = coreapi.CORE_NODE_RJ45
|
||||
"""
|
||||
RJ45Node is a physical interface on the host linked to the emulated
|
||||
network.
|
||||
"""
|
||||
apitype = NodeTypes.RJ45.value
|
||||
type = "rj45"
|
||||
|
||||
def __init__(self, session, objid = None, name = None, mtu = 1500,
|
||||
verbose = False, start = True):
|
||||
PyCoreNode.__init__(self, session, objid, name, verbose=verbose,
|
||||
start=start)
|
||||
def __init__(self, session, objid=None, name=None, mtu=1500, start=True):
|
||||
PyCoreNode.__init__(self, session, objid, name, start=start)
|
||||
# this initializes net, params, poshook
|
||||
PyCoreNetIf.__init__(self, node=self, name=name, mtu = mtu)
|
||||
PyCoreNetIf.__init__(self, node=self, name=name, mtu=mtu)
|
||||
self.up = False
|
||||
self.lock = threading.RLock()
|
||||
self.ifindex = None
|
||||
|
@ -334,27 +368,28 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
self.startup()
|
||||
|
||||
def startup(self):
|
||||
''' Set the interface in the up state.
|
||||
'''
|
||||
"""
|
||||
Set the interface in the up state.
|
||||
"""
|
||||
# interface will also be marked up during net.attach()
|
||||
self.savestate()
|
||||
try:
|
||||
check_call([IP_BIN, "link", "set", self.localname, "up"])
|
||||
except:
|
||||
self.warn("Failed to run command: %s link set %s up" % \
|
||||
(IP_BIN, self.localname))
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("failed to run command: %s link set %s up", constants.IP_BIN, self.localname)
|
||||
return
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
''' Bring the interface down. Remove any addresses and queuing
|
||||
disciplines.
|
||||
'''
|
||||
"""
|
||||
Bring the interface down. Remove any addresses and queuing
|
||||
disciplines.
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
check_call([IP_BIN, "link", "set", self.localname, "down"])
|
||||
check_call([IP_BIN, "addr", "flush", "dev", self.localname])
|
||||
mutecall([TC_BIN, "qdisc", "del", "dev", self.localname, "root"])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "down"])
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "flush", "dev", self.localname])
|
||||
utils.mutecall([constants.TC_BIN, "qdisc", "del", "dev", self.localname, "root"])
|
||||
self.up = False
|
||||
self.restorestate()
|
||||
|
||||
|
@ -364,25 +399,27 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
def detachnet(self):
|
||||
PyCoreNetIf.detachnet(self)
|
||||
|
||||
def newnetif(self, net = None, addrlist = [], hwaddr = None,
|
||||
ifindex = None, ifname = None):
|
||||
''' This is called when linking with another node. Since this node
|
||||
represents an interface, we do not create another object here,
|
||||
but attach ourselves to the given network.
|
||||
'''
|
||||
def newnetif(self, net=None, addrlist=[], hwaddr=None,
|
||||
ifindex=None, ifname=None):
|
||||
"""
|
||||
This is called when linking with another node. Since this node
|
||||
represents an interface, we do not create another object here,
|
||||
but attach ourselves to the given network.
|
||||
"""
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if ifindex is None:
|
||||
ifindex = 0
|
||||
if self.net is not None:
|
||||
raise ValueError, \
|
||||
"RJ45 nodes support at most 1 network interface"
|
||||
"RJ45 nodes support at most 1 network interface"
|
||||
self._netif[ifindex] = self
|
||||
self.node = self # PyCoreNetIf.node is self
|
||||
# PyCoreNetIf.node is self
|
||||
self.node = self
|
||||
self.ifindex = ifindex
|
||||
if net is not None:
|
||||
self.attachnet(net)
|
||||
for addr in maketuple(addrlist):
|
||||
for addr in utils.maketuple(addrlist):
|
||||
self.addaddr(addr)
|
||||
return ifindex
|
||||
finally:
|
||||
|
@ -400,10 +437,11 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
raise ValueError, "ifindex %s does not exist" % ifindex
|
||||
|
||||
def netif(self, ifindex, net=None):
|
||||
''' This object is considered the network interface, so we only
|
||||
return self here. This keeps the RJ45Node compatible with
|
||||
real nodes.
|
||||
'''
|
||||
"""
|
||||
This object is considered the network interface, so we only
|
||||
return self here. This keeps the RJ45Node compatible with
|
||||
real nodes.
|
||||
"""
|
||||
if net is not None and net == self.net:
|
||||
return self
|
||||
if ifindex is None:
|
||||
|
@ -419,27 +457,28 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
|
||||
def addaddr(self, addr):
|
||||
if self.up:
|
||||
check_call([IP_BIN, "addr", "add", str(addr), "dev", self.name])
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name])
|
||||
PyCoreNetIf.addaddr(self, addr)
|
||||
|
||||
def deladdr(self, addr):
|
||||
if self.up:
|
||||
check_call([IP_BIN, "addr", "del", str(addr), "dev", self.name])
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "del", str(addr), "dev", self.name])
|
||||
PyCoreNetIf.deladdr(self, addr)
|
||||
|
||||
def savestate(self):
|
||||
''' Save the addresses and other interface state before using the
|
||||
"""
|
||||
Save the addresses and other interface state before using the
|
||||
interface for emulation purposes. TODO: save/restore the PROMISC flag
|
||||
'''
|
||||
"""
|
||||
self.old_up = False
|
||||
self.old_addrs = []
|
||||
cmd = [IP_BIN, "addr", "show", "dev", self.localname]
|
||||
cmd = [constants.IP_BIN, "addr", "show", "dev", self.localname]
|
||||
try:
|
||||
tmp = subprocess.Popen(cmd, stdout = subprocess.PIPE)
|
||||
tmp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
except OSError:
|
||||
self.warn("Failed to run %s command: %s" % (IP_BIN, cmd))
|
||||
logger.exception("Failed to run %s command: %s", constants.IP_BIN, cmd)
|
||||
if tmp.wait():
|
||||
self.warn("Command failed: %s" % cmd)
|
||||
logger.warn("Command failed: %s", cmd)
|
||||
return
|
||||
lines = tmp.stdout.read()
|
||||
tmp.stdout.close()
|
||||
|
@ -459,31 +498,27 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
self.old_addrs.append((items[1], None))
|
||||
|
||||
def restorestate(self):
|
||||
''' Restore the addresses and other interface state after using it.
|
||||
'''
|
||||
"""
|
||||
Restore the addresses and other interface state after using it.
|
||||
"""
|
||||
for addr in self.old_addrs:
|
||||
if addr[1] is None:
|
||||
check_call([IP_BIN, "addr", "add", addr[0], "dev",
|
||||
self.localname])
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "add", addr[0], "dev", self.localname])
|
||||
else:
|
||||
check_call([IP_BIN, "addr", "add", addr[0], "brd", addr[1],
|
||||
"dev", self.localname])
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "add", addr[0], "brd", addr[1], "dev", self.localname])
|
||||
if self.old_up:
|
||||
check_call([IP_BIN, "link", "set", self.localname, "up"])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
|
||||
|
||||
def setposition(self, x=None, y=None, z=None):
|
||||
''' Use setposition() from both parent classes.
|
||||
'''
|
||||
"""
|
||||
Use setposition() from both parent classes.
|
||||
"""
|
||||
PyCoreObj.setposition(self, x, y, z)
|
||||
# invoke any poshook
|
||||
PyCoreNetIf.setposition(self, x, y, z)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TunnelNode(GreTapBridge):
|
||||
apitype = coreapi.CORE_NODE_TUNNEL
|
||||
apitype = NodeTypes.TUNNEL.value
|
||||
policy = "ACCEPT"
|
||||
type = "tunnel"
|
||||
|
||||
|
|
741
daemon/core/netns/openvswitch.py
Normal file
741
daemon/core/netns/openvswitch.py
Normal file
|
@ -0,0 +1,741 @@
|
|||
"""
|
||||
TODO: probably goes away, or implement the usage of "unshare", or docker formal.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import subprocess
|
||||
import threading
|
||||
from socket import AF_INET
|
||||
from socket import AF_INET6
|
||||
|
||||
from core import constants
|
||||
from core.coreobj import PyCoreNet
|
||||
from core.data import LinkData
|
||||
from core.enumerations import LinkTypes
|
||||
from core.enumerations import NodeTypes
|
||||
from core.enumerations import RegisterTlvs
|
||||
from core.misc import ipaddress
|
||||
from core.misc import log
|
||||
from core.misc import utils
|
||||
from core.netns.vif import GreTap
|
||||
from core.netns.vif import VEth
|
||||
from core.netns.vnet import EbtablesQueue
|
||||
from core.netns.vnet import GreTapBridge
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
# a global object because all WLANs share the same queue
|
||||
# cannot have multiple threads invoking the ebtables commnd
|
||||
ebtables_queue = EbtablesQueue()
|
||||
|
||||
ebtables_lock = threading.Lock()
|
||||
|
||||
utils.check_executables([
|
||||
constants.IP_BIN,
|
||||
constants.EBTABLES_BIN,
|
||||
constants.TC_BIN
|
||||
])
|
||||
|
||||
|
||||
def ebtables_commands(call, commands):
|
||||
ebtables_lock.acquire()
|
||||
try:
|
||||
for command in commands:
|
||||
call(command)
|
||||
finally:
|
||||
ebtables_lock.release()
|
||||
|
||||
|
||||
class OvsNet(PyCoreNet):
|
||||
"""
|
||||
Used to be LxBrNet.
|
||||
|
||||
Base class for providing Openvswitch functionality to objects that create bridges.
|
||||
"""
|
||||
|
||||
policy = "DROP"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True, policy=None):
|
||||
"""
|
||||
Creates an OvsNet instance.
|
||||
|
||||
:param core.session.Session session: session this object is a part of
|
||||
:param objid:
|
||||
:param name:
|
||||
:param start:
|
||||
:param policy:
|
||||
:return:
|
||||
"""
|
||||
|
||||
PyCoreNet.__init__(self, session, objid, name, start)
|
||||
|
||||
if policy:
|
||||
self.policy = policy
|
||||
else:
|
||||
self.policy = self.__class__.policy
|
||||
|
||||
session_id = self.session.short_session_id()
|
||||
self.bridge_name = "b.%s.%s" % (str(self.objid), session_id)
|
||||
self.up = False
|
||||
|
||||
if start:
|
||||
self.startup()
|
||||
ebtables_queue.startupdateloop(self)
|
||||
|
||||
def startup(self):
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "add-br", self.bridge_name])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error adding bridge")
|
||||
|
||||
try:
|
||||
# turn off spanning tree protocol and forwarding delay
|
||||
# TODO: appears stp and rstp are off by default, make sure this always holds true
|
||||
# TODO: apears ovs only supports rstp forward delay and again it's off by default
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.bridge_name, "up"])
|
||||
|
||||
# create a new ebtables chain for this bridge
|
||||
ebtables_commands(subprocess.check_call, [
|
||||
[constants.EBTABLES_BIN, "-N", self.bridge_name, "-P", self.policy],
|
||||
[constants.EBTABLES_BIN, "-A", "FORWARD", "--logical-in", self.bridge_name, "-j", self.bridge_name]
|
||||
])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error setting bridge parameters")
|
||||
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
if not self.up:
|
||||
logger.info("exiting shutdown, object is not up")
|
||||
return
|
||||
|
||||
ebtables_queue.stopupdateloop(self)
|
||||
|
||||
utils.mutecall([constants.IP_BIN, "link", "set", self.bridge_name, "down"])
|
||||
utils.mutecall([constants.OVS_BIN, "del-br", self.bridge_name])
|
||||
|
||||
ebtables_commands(utils.mutecall, [
|
||||
[constants.EBTABLES_BIN, "-D", "FORWARD", "--logical-in", self.bridge_name, "-j", self.bridge_name],
|
||||
[constants.EBTABLES_BIN, "-X", self.bridge_name]
|
||||
])
|
||||
|
||||
for interface in self.netifs():
|
||||
# removes veth pairs used for bridge-to-bridge connections
|
||||
interface.shutdown()
|
||||
|
||||
self._netif.clear()
|
||||
self._linked.clear()
|
||||
del self.session
|
||||
self.up = False
|
||||
|
||||
def attach(self, interface):
|
||||
if self.up:
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "add-port", self.bridge_name, interface.localname])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", interface.localname, "up"])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error joining interface %s to bridge %s", interface.localname, self.bridge_name)
|
||||
return
|
||||
|
||||
PyCoreNet.attach(self, interface)
|
||||
|
||||
def detach(self, interface):
|
||||
if self.up:
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "del-port", self.bridge_name, interface.localname])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error removing interface %s from bridge %s", interface.localname, self.bridge_name)
|
||||
return
|
||||
|
||||
PyCoreNet.detach(self, interface)
|
||||
|
||||
def linked(self, interface_one, interface_two):
|
||||
# check if the network interfaces are attached to this network
|
||||
if self._netif[interface_one.netifi] != interface_one:
|
||||
raise ValueError("inconsistency for interface %s" % interface_one.name)
|
||||
|
||||
if self._netif[interface_two.netifi] != interface_two:
|
||||
raise ValueError("inconsistency for interface %s" % interface_two.name)
|
||||
|
||||
try:
|
||||
linked = self._linked[interface_one][interface_two]
|
||||
except KeyError:
|
||||
if self.policy == "ACCEPT":
|
||||
linked = True
|
||||
elif self.policy == "DROP":
|
||||
linked = False
|
||||
else:
|
||||
raise ValueError("unknown policy: %s" % self.policy)
|
||||
|
||||
self._linked[interface_one][interface_two] = linked
|
||||
|
||||
return linked
|
||||
|
||||
def unlink(self, interface_one, interface_two):
|
||||
"""
|
||||
Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
|
||||
filtering rules.
|
||||
"""
|
||||
with self._linked_lock:
|
||||
if not self.linked(interface_one, interface_two):
|
||||
return
|
||||
|
||||
self._linked[interface_one][interface_two] = False
|
||||
|
||||
ebtables_queue.ebchange(self)
|
||||
|
||||
def link(self, interface_one, interface_two):
|
||||
"""
|
||||
Link two PyCoreNetIfs together, resulting in adding or removing
|
||||
ebtables filtering rules.
|
||||
"""
|
||||
with self._linked_lock:
|
||||
if self.linked(interface_one, interface_two):
|
||||
return
|
||||
|
||||
self._linked[interface_one][interface_two] = True
|
||||
|
||||
ebtables_queue.ebchange(self)
|
||||
|
||||
def linkconfig(self, interface, bw=None, delay=None, loss=None, duplicate=None,
|
||||
jitter=None, netif2=None, devname=None):
|
||||
"""
|
||||
Configure link parameters by applying tc queuing disciplines on the
|
||||
interface.
|
||||
"""
|
||||
if not devname:
|
||||
devname = interface.localname
|
||||
|
||||
tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname]
|
||||
parent = ["root"]
|
||||
|
||||
# attempt to set bandwidth and update as needed if value changed
|
||||
bandwidth_changed = interface.setparam("bw", bw)
|
||||
if bandwidth_changed:
|
||||
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
|
||||
if bw > 0:
|
||||
if self.up:
|
||||
burst = max(2 * interface.mtu, bw / 1000)
|
||||
limit = 0xffff # max IP payload
|
||||
tbf = ["tbf", "rate", str(bw), "burst", str(burst), "limit", str(limit)]
|
||||
logger.info("linkconfig: %s" % [tc + parent + ["handle", "1:"] + tbf])
|
||||
subprocess.check_call(tc + parent + ["handle", "1:"] + tbf)
|
||||
interface.setparam("has_tbf", True)
|
||||
elif interface.getparam("has_tbf") and bw <= 0:
|
||||
tcd = [] + tc
|
||||
tcd[2] = "delete"
|
||||
|
||||
if self.up:
|
||||
subprocess.check_call(tcd + parent)
|
||||
|
||||
interface.setparam("has_tbf", False)
|
||||
# removing the parent removes the child
|
||||
interface.setparam("has_netem", False)
|
||||
|
||||
if interface.getparam("has_tbf"):
|
||||
parent = ["parent", "1:1"]
|
||||
|
||||
netem = ["netem"]
|
||||
delay_changed = interface.setparam("delay", delay)
|
||||
|
||||
if loss is not None:
|
||||
loss = float(loss)
|
||||
loss_changed = interface.setparam("loss", loss)
|
||||
|
||||
if duplicate is not None:
|
||||
duplicate = float(duplicate)
|
||||
duplicate_changed = interface.setparam("duplicate", duplicate)
|
||||
jitter_changed = interface.setparam("jitter", jitter)
|
||||
|
||||
# if nothing changed return
|
||||
if not any([bandwidth_changed, delay_changed, loss_changed, duplicate_changed, jitter_changed]):
|
||||
return
|
||||
|
||||
# jitter and delay use the same delay statement
|
||||
if delay is not None:
|
||||
netem += ["delay", "%sus" % delay]
|
||||
else:
|
||||
netem += ["delay", "0us"]
|
||||
|
||||
if jitter is not None:
|
||||
netem += ["%sus" % jitter, "25%"]
|
||||
|
||||
if loss is not None:
|
||||
netem += ["loss", "%s%%" % min(loss, 100)]
|
||||
|
||||
if duplicate is not None:
|
||||
netem += ["duplicate", "%s%%" % min(duplicate, 100)]
|
||||
|
||||
if delay <= 0 and jitter <= 0 and loss <= 0 and duplicate <= 0:
|
||||
# possibly remove netem if it exists and parent queue wasn"t removed
|
||||
if not interface.getparam("has_netem"):
|
||||
return
|
||||
|
||||
tc[2] = "delete"
|
||||
|
||||
if self.up:
|
||||
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"]],))
|
||||
subprocess.check_call(tc + parent + ["handle", "10:"])
|
||||
interface.setparam("has_netem", False)
|
||||
elif len(netem) > 1:
|
||||
if self.up:
|
||||
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem],))
|
||||
subprocess.check_call(tc + parent + ["handle", "10:"] + netem)
|
||||
interface.setparam("has_netem", True)
|
||||
|
||||
def linknet(self, network):
|
||||
"""
|
||||
Link this bridge with another by creating a veth pair and installing
|
||||
each device into each bridge.
|
||||
"""
|
||||
session_id = self.session.short_session_id()
|
||||
|
||||
try:
|
||||
self_objid = "%x" % self.objid
|
||||
except TypeError:
|
||||
self_objid = "%s" % self.objid
|
||||
|
||||
try:
|
||||
net_objid = "%x" % network.objid
|
||||
except TypeError:
|
||||
net_objid = "%s" % network.objid
|
||||
|
||||
localname = "veth%s.%s.%s" % (self_objid, net_objid, session_id)
|
||||
|
||||
if len(localname) >= 16:
|
||||
raise ValueError("interface local name %s too long" % localname)
|
||||
|
||||
name = "veth%s.%s.%s" % (net_objid, self_objid, session_id)
|
||||
if len(name) >= 16:
|
||||
raise ValueError("interface name %s too long" % name)
|
||||
|
||||
interface = VEth(node=None, name=name, localname=localname, mtu=1500, net=self, start=self.up)
|
||||
self.attach(interface)
|
||||
if network.up:
|
||||
# this is similar to net.attach() but uses netif.name instead
|
||||
# of localname
|
||||
subprocess.check_call([constants.OVS_BIN, "add-port", network.brname, interface.name])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", interface.name, "up"])
|
||||
|
||||
# TODO: is there a native method for this? see if this causes issues
|
||||
# i = network.newifindex()
|
||||
# network._netif[i] = interface
|
||||
# with network._linked_lock:
|
||||
# network._linked[interface] = {}
|
||||
# this method call is equal to the above, with a interface.netifi = call
|
||||
network.attach(interface)
|
||||
|
||||
interface.net = self
|
||||
interface.othernet = network
|
||||
return interface
|
||||
|
||||
def getlinknetif(self, network):
|
||||
"""
|
||||
Return the interface of that links this net with another net
|
||||
(that were linked using linknet()).
|
||||
"""
|
||||
for interface in self.netifs():
|
||||
if hasattr(interface, "othernet") and interface.othernet == network:
|
||||
return interface
|
||||
|
||||
return None
|
||||
|
||||
def addrconfig(self, addresses):
|
||||
"""
|
||||
Set addresses on the bridge.
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
|
||||
for address in addresses:
|
||||
try:
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "add", str(address), "dev", self.bridge_name])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error adding IP address")
|
||||
|
||||
|
||||
class OvsCtrlNet(OvsNet):
|
||||
policy = "ACCEPT"
|
||||
CTRLIF_IDX_BASE = 99 # base control interface index
|
||||
DEFAULT_PREFIX_LIST = [
|
||||
"172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
|
||||
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
|
||||
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
|
||||
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24"
|
||||
]
|
||||
|
||||
def __init__(self, session, objid="ctrlnet", name=None, prefix=None, hostid=None,
|
||||
start=True, assign_address=True, updown_script=None, serverintf=None):
|
||||
OvsNet.__init__(self, session, objid=objid, name=name, start=start)
|
||||
self.prefix = ipaddress.Ipv4Prefix(prefix)
|
||||
self.hostid = hostid
|
||||
self.assign_address = assign_address
|
||||
self.updown_script = updown_script
|
||||
self.serverintf = serverintf
|
||||
|
||||
def startup(self):
|
||||
if self.detectoldbridge():
|
||||
return
|
||||
|
||||
OvsNet.startup(self)
|
||||
if self.hostid:
|
||||
addr = self.prefix.addr(self.hostid)
|
||||
else:
|
||||
addr = self.prefix.max_addr()
|
||||
|
||||
message = "Added control network bridge: %s %s" % (self.bridge_name, self.prefix)
|
||||
addresses = ["%s/%s" % (addr, self.prefix.prefixlen)]
|
||||
if self.assign_address:
|
||||
self.addrconfig(addresses=addresses)
|
||||
message += " address %s" % addr
|
||||
logger.info(message)
|
||||
|
||||
if self.updown_script:
|
||||
logger.info("interface %s updown script %s startup called" % (self.bridge_name, self.updown_script))
|
||||
subprocess.check_call([self.updown_script, self.bridge_name, "startup"])
|
||||
|
||||
if self.serverintf:
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "add-port", self.bridge_name, self.serverintf])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.serverintf, "up"])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error joining server interface %s to controlnet bridge %s",
|
||||
self.serverintf, self.bridge_name)
|
||||
|
||||
def detectoldbridge(self):
|
||||
"""
|
||||
Occassionally, control net bridges from previously closed sessions are not cleaned up.
|
||||
Check if there are old control net bridges and delete them
|
||||
"""
|
||||
|
||||
status, output = utils.cmdresult([constants.OVS_BIN, "list-br"])
|
||||
output = output.strip()
|
||||
if output:
|
||||
for line in output.split("\n"):
|
||||
bride_name = line.split(".")
|
||||
if bride_name[0] == "b" and bride_name[1] == self.objid:
|
||||
logger.error("older session may still be running with conflicting id for bridge: %s", line)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def shutdown(self):
|
||||
if self.serverintf:
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "del-port", self.bridge_name, self.serverintf])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error deleting server interface %s to controlnet bridge %s",
|
||||
self.serverintf, self.bridge_name)
|
||||
|
||||
if self.updown_script:
|
||||
logger.info("interface %s updown script '%s shutdown' called", self.bridge_name, self.updown_script)
|
||||
subprocess.check_call([self.updown_script, self.bridge_name, "shutdown"])
|
||||
|
||||
OvsNet.shutdown(self)
|
||||
|
||||
def all_link_data(self, flags):
|
||||
"""
|
||||
Do not include CtrlNet in link messages describing this session.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class OvsPtpNet(OvsNet):
|
||||
policy = "ACCEPT"
|
||||
|
||||
def attach(self, interface):
|
||||
if len(self._netif) >= 2:
|
||||
raise ValueError("point-to-point links support at most 2 network interfaces")
|
||||
OvsNet.attach(self, interface)
|
||||
|
||||
def data(self, message_type):
|
||||
"""
|
||||
Do not generate a Node Message for point-to-point links. They are
|
||||
built using a link message instead.
|
||||
"""
|
||||
pass
|
||||
|
||||
def all_link_data(self, flags):
|
||||
"""
|
||||
Build CORE API TLVs for a point-to-point link. One Link message describes this network.
|
||||
"""
|
||||
|
||||
all_links = []
|
||||
|
||||
if len(self._netif) != 2:
|
||||
return all_links
|
||||
|
||||
if1, if2 = self._netif.items()
|
||||
if1 = if1[1]
|
||||
if2 = if2[1]
|
||||
|
||||
unidirectional = 0
|
||||
if if1.getparams() != if2.getparams():
|
||||
unidirectional = 1
|
||||
|
||||
interface1_ip4 = None
|
||||
interface1_ip4_mask = None
|
||||
interface1_ip6 = None
|
||||
interface1_ip6_mask = None
|
||||
for address in if1.addrlist:
|
||||
ip, sep, mask = address.partition('/')
|
||||
mask = int(mask)
|
||||
if ipaddress.is_ipv4_address(ip):
|
||||
family = AF_INET
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface1_ip4_mask = mask
|
||||
else:
|
||||
family = AF_INET6
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface1_ip6_mask = mask
|
||||
|
||||
interface2_ip4 = None
|
||||
interface2_ip4_mask = None
|
||||
interface2_ip6 = None
|
||||
interface2_ip6_mask = None
|
||||
for address in if2.addrlist:
|
||||
ip, sep, mask = address.partition('/')
|
||||
mask = int(mask)
|
||||
if ipaddress.is_ipv4_address(ip):
|
||||
family = AF_INET
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface2_ip4_mask = mask
|
||||
else:
|
||||
family = AF_INET6
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface2_ip6_mask = mask
|
||||
|
||||
# TODO: not currently used
|
||||
# loss=netif.getparam('loss')
|
||||
link_data = LinkData(
|
||||
message_type=flags,
|
||||
node1_id=if1.node.objid,
|
||||
node2_id=if2.node.objid,
|
||||
link_type=self.linktype,
|
||||
unidirectional=unidirectional,
|
||||
delay=if1.getparam("delay"),
|
||||
bandwidth=if1.getparam("bw"),
|
||||
dup=if1.getparam("duplicate"),
|
||||
jitter=if1.getparam("jitter"),
|
||||
interface1_id=if1.node.getifindex(if1),
|
||||
interface1_mac=if1.hwaddr,
|
||||
interface1_ip4=interface1_ip4,
|
||||
interface1_ip4_mask=interface1_ip4_mask,
|
||||
interface1_ip6=interface1_ip6,
|
||||
interface1_ip6_mask=interface1_ip6_mask,
|
||||
interface2_id=if2.node.getifindex(if2),
|
||||
interface2_mac=if2.hwaddr,
|
||||
interface2_ip4=interface2_ip4,
|
||||
interface2_ip4_mask=interface2_ip4_mask,
|
||||
interface2_ip6=interface2_ip6,
|
||||
interface2_ip6_mask=interface2_ip6_mask,
|
||||
)
|
||||
|
||||
all_links.append(link_data)
|
||||
|
||||
# build a 2nd link message for the upstream link parameters
|
||||
# (swap if1 and if2)
|
||||
if unidirectional:
|
||||
link_data = LinkData(
|
||||
message_type=0,
|
||||
node1_id=if2.node.objid,
|
||||
node2_id=if1.node.objid,
|
||||
delay=if1.getparam("delay"),
|
||||
bandwidth=if1.getparam("bw"),
|
||||
dup=if1.getparam("duplicate"),
|
||||
jitter=if1.getparam("jitter"),
|
||||
unidirectional=1,
|
||||
interface1_id=if2.node.getifindex(if2),
|
||||
interface2_id=if1.node.getifindex(if1)
|
||||
)
|
||||
all_links.append(link_data)
|
||||
|
||||
return all_links
|
||||
|
||||
|
||||
class OvsSwitchNode(OvsNet):
|
||||
apitype = NodeTypes.SWITCH.value
|
||||
policy = "ACCEPT"
|
||||
type = "lanswitch"
|
||||
|
||||
|
||||
class OvsHubNode(OvsNet):
|
||||
apitype = NodeTypes.HUB.value
|
||||
policy = "ACCEPT"
|
||||
type = "hub"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True):
|
||||
"""
|
||||
the Hub node forwards packets to all bridge ports by turning off
|
||||
the MAC address learning
|
||||
"""
|
||||
OvsNet.__init__(self, session, objid, name, start)
|
||||
|
||||
if start:
|
||||
# TODO: verify that the below flow accomplishes what is desired for a "HUB"
|
||||
# TODO: replace "brctl setageing 0"
|
||||
subprocess.check_call([constants.OVS_FLOW_BIN, "add-flow", self.bridge_name, "action=flood"])
|
||||
|
||||
|
||||
class OvsWlanNode(OvsNet):
|
||||
apitype = NodeTypes.WIRELESS_LAN.value
|
||||
linktype = LinkTypes.WIRELESS.value
|
||||
policy = "DROP"
|
||||
type = "wlan"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True, policy=None):
|
||||
OvsNet.__init__(self, session, objid, name, start, policy)
|
||||
|
||||
# wireless model such as basic range
|
||||
self.model = None
|
||||
# mobility model such as scripted
|
||||
self.mobility = None
|
||||
|
||||
def attach(self, interface):
|
||||
OvsNet.attach(self, interface)
|
||||
|
||||
if self.model:
|
||||
interface.poshook = self.model.position_callback
|
||||
|
||||
if interface.node is None:
|
||||
return
|
||||
|
||||
x, y, z = interface.node.position.get()
|
||||
# invokes any netif.poshook
|
||||
interface.setposition(x, y, z)
|
||||
# self.model.setlinkparams()
|
||||
|
||||
def setmodel(self, model, config):
|
||||
"""
|
||||
Mobility and wireless model.
|
||||
"""
|
||||
logger.info("adding model %s", model.name)
|
||||
|
||||
if model.type == RegisterTlvs.WIRELESS.value:
|
||||
self.model = model(session=self.session, object_id=self.objid, values=config)
|
||||
if self.model.position_callback:
|
||||
for interface in self.netifs():
|
||||
interface.poshook = self.model.position_callback
|
||||
if interface.node is not None:
|
||||
x, y, z = interface.node.position.get()
|
||||
interface.poshook(interface, x, y, z)
|
||||
self.model.setlinkparams()
|
||||
elif model.type == RegisterTlvs.MOBILITY.value:
|
||||
self.mobility = model(session=self.session, object_id=self.objid, values=config)
|
||||
|
||||
def updatemodel(self, model_name, values):
|
||||
"""
|
||||
Allow for model updates during runtime (similar to setmodel().)
|
||||
"""
|
||||
logger.info("updating model %s", model_name)
|
||||
if self.model is None or self.model.name != model_name:
|
||||
logger.info(
|
||||
"failure to update model, model doesn't exist or invalid name: model(%s) - name(%s)",
|
||||
self.model, model_name
|
||||
)
|
||||
return
|
||||
|
||||
model = self.model
|
||||
if model.type == RegisterTlvs.WIRELESS.value:
|
||||
if not model.updateconfig(values):
|
||||
return
|
||||
if self.model.position_callback:
|
||||
for interface in self.netifs():
|
||||
interface.poshook = self.model.position_callback
|
||||
if interface.node is not None:
|
||||
x, y, z = interface.node.position.get()
|
||||
interface.poshook(interface, x, y, z)
|
||||
self.model.setlinkparams()
|
||||
|
||||
def all_link_data(self, flags):
|
||||
all_links = OvsNet.all_link_data(self, flags)
|
||||
|
||||
if self.model:
|
||||
all_links.extend(self.model.all_link_data(flags))
|
||||
|
||||
return all_links
|
||||
|
||||
|
||||
class OvsTunnelNode(GreTapBridge):
|
||||
apitype = NodeTypes.TUNNEL.value
|
||||
policy = "ACCEPT"
|
||||
type = "tunnel"
|
||||
|
||||
|
||||
class OvsGreTapBridge(OvsNet):
|
||||
"""
|
||||
A network consisting of a bridge with a gretap device for tunneling to
|
||||
another system.
|
||||
"""
|
||||
|
||||
def __init__(self, session, remoteip=None, objid=None, name=None, policy="ACCEPT",
|
||||
localip=None, ttl=255, key=None, start=True):
|
||||
OvsNet.__init__(self, session=session, objid=objid, name=name, policy=policy, start=False)
|
||||
self.grekey = key
|
||||
if self.grekey is None:
|
||||
self.grekey = self.session.session_id ^ self.objid
|
||||
|
||||
self.localnum = None
|
||||
self.remotenum = None
|
||||
self.remoteip = remoteip
|
||||
self.localip = localip
|
||||
self.ttl = ttl
|
||||
|
||||
if remoteip is None:
|
||||
self.gretap = None
|
||||
else:
|
||||
self.gretap = GreTap(node=self, name=None, session=session, remoteip=remoteip,
|
||||
objid=None, localip=localip, ttl=ttl, key=self.grekey)
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Creates a bridge and adds the gretap device to it.
|
||||
"""
|
||||
OvsNet.startup(self)
|
||||
|
||||
if self.gretap:
|
||||
self.attach(self.gretap)
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Detach the gretap device and remove the bridge.
|
||||
"""
|
||||
if self.gretap:
|
||||
self.detach(self.gretap)
|
||||
self.gretap.shutdown()
|
||||
self.gretap = None
|
||||
|
||||
OvsNet.shutdown(self)
|
||||
|
||||
def addrconfig(self, addresses):
|
||||
"""
|
||||
Set the remote tunnel endpoint. This is a one-time method for
|
||||
creating the GreTap device, which requires the remoteip at startup.
|
||||
The 1st address in the provided list is remoteip, 2nd optionally
|
||||
specifies localip.
|
||||
"""
|
||||
if self.gretap:
|
||||
raise ValueError("gretap already exists for %s" % self.name)
|
||||
|
||||
remoteip = addresses[0].split('/')[0]
|
||||
localip = None
|
||||
|
||||
if len(addresses) > 1:
|
||||
localip = addresses[1].split('/')[0]
|
||||
|
||||
self.gretap = GreTap(session=self.session, remoteip=remoteip, objid=None, name=None,
|
||||
localip=localip, ttl=self.ttl, key=self.grekey)
|
||||
self.attach(self.gretap)
|
||||
|
||||
def setkey(self, key):
|
||||
"""
|
||||
Set the GRE key used for the GreTap device. This needs to be set
|
||||
prior to instantiating the GreTap device (before addrconfig).
|
||||
"""
|
||||
self.grekey = key
|
|
@ -1,58 +1,53 @@
|
|||
#
|
||||
# CORE
|
||||
# Copyright (c)2011-2014 the Boeing Company.
|
||||
# See the LICENSE file included in this distribution.
|
||||
#
|
||||
# authors: Tom Goff <thomas.goff@boeing.com>
|
||||
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
||||
#
|
||||
'''
|
||||
vif.py: PyCoreNetIf classes that implement the interfaces available
|
||||
"""
|
||||
PyCoreNetIf classes that implement the interfaces available
|
||||
under Linux.
|
||||
'''
|
||||
"""
|
||||
|
||||
import os, signal, shutil, sys, subprocess, vnodeclient, threading, string
|
||||
import random, time
|
||||
from core.api import coreapi
|
||||
from core.misc.utils import *
|
||||
from core.constants import *
|
||||
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position
|
||||
from core.emane.nodes import EmaneNode
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from core import constants
|
||||
from core.coreobj import PyCoreNetIf
|
||||
from core.misc import log
|
||||
from core.misc import utils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
utils.check_executables([constants.IP_BIN])
|
||||
|
||||
checkexec([IP_BIN])
|
||||
|
||||
class VEth(PyCoreNetIf):
|
||||
def __init__(self, node, name, localname, mtu = 1500, net = None,
|
||||
start = True):
|
||||
def __init__(self, node, name, localname, mtu=1500, net=None, start=True):
|
||||
# note that net arg is ignored
|
||||
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu)
|
||||
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
|
||||
self.localname = localname
|
||||
self.up = False
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
def startup(self):
|
||||
check_call([IP_BIN, "link", "add", "name", self.localname,
|
||||
"type", "veth", "peer", "name", self.name])
|
||||
check_call([IP_BIN, "link", "set", self.localname, "up"])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "add", "name", self.localname,
|
||||
"type", "veth", "peer", "name", self.name])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
if not self.up:
|
||||
return
|
||||
if self.node:
|
||||
self.node.cmd([IP_BIN, "-6", "addr", "flush", "dev", self.name])
|
||||
self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
|
||||
if self.localname:
|
||||
mutedetach([IP_BIN, "link", "delete", self.localname])
|
||||
utils.mutedetach([constants.IP_BIN, "link", "delete", self.localname])
|
||||
self.up = False
|
||||
|
||||
|
||||
class TunTap(PyCoreNetIf):
|
||||
''' TUN/TAP virtual device in TAP mode
|
||||
'''
|
||||
def __init__(self, node, name, localname, mtu = 1500, net = None,
|
||||
start = True):
|
||||
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu)
|
||||
"""
|
||||
TUN/TAP virtual device in TAP mode
|
||||
"""
|
||||
|
||||
def __init__(self, node, name, localname, mtu=1500, net=None, start=True):
|
||||
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
|
||||
self.localname = localname
|
||||
self.up = False
|
||||
self.transport_type = "virtual"
|
||||
|
@ -62,24 +57,24 @@ class TunTap(PyCoreNetIf):
|
|||
def startup(self):
|
||||
# TODO: more sophisticated TAP creation here
|
||||
# Debian does not support -p (tap) option, RedHat does.
|
||||
# For now, this is disabled to allow the TAP to be created by another
|
||||
# For now, this is disabled to allow the TAP to be created by another
|
||||
# system (e.g. EMANE's emanetransportd)
|
||||
#check_call(["tunctl", "-t", self.name])
|
||||
# check_call(["tunctl", "-t", self.name])
|
||||
# self.install()
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
if not self.up:
|
||||
return
|
||||
self.node.cmd([IP_BIN, "-6", "addr", "flush", "dev", self.name])
|
||||
#if self.name:
|
||||
self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
|
||||
# if self.name:
|
||||
# mutedetach(["tunctl", "-d", self.localname])
|
||||
self.up = False
|
||||
|
||||
def waitfor(self, func, attempts = 10, maxretrydelay = 0.25):
|
||||
'''\
|
||||
def waitfor(self, func, attempts=10, maxretrydelay=0.25):
|
||||
"""
|
||||
Wait for func() to return zero with exponential backoff
|
||||
'''
|
||||
"""
|
||||
delay = 0.01
|
||||
for i in xrange(1, attempts + 1):
|
||||
r = func()
|
||||
|
@ -88,98 +83,93 @@ class TunTap(PyCoreNetIf):
|
|||
msg = 'attempt %s failed with nonzero exit status %s' % (i, r)
|
||||
if i < attempts + 1:
|
||||
msg += ', retrying...'
|
||||
self.node.info(msg)
|
||||
logger.info(msg)
|
||||
time.sleep(delay)
|
||||
delay = delay + delay
|
||||
if delay > maxretrydelay:
|
||||
delay = maxretrydelay
|
||||
else:
|
||||
msg += ', giving up'
|
||||
self.node.info(msg)
|
||||
raise RuntimeError, 'command failed after %s attempts' % attempts
|
||||
logger.info(msg)
|
||||
|
||||
raise RuntimeError('command failed after %s attempts' % attempts)
|
||||
|
||||
def waitfordevicelocal(self):
|
||||
'''\
|
||||
"""
|
||||
Check for presence of a local device - tap device may not
|
||||
appear right away waits
|
||||
'''
|
||||
"""
|
||||
|
||||
def localdevexists():
|
||||
cmd = (IP_BIN, 'link', 'show', self.localname)
|
||||
return mutecall(cmd)
|
||||
cmd = (constants.IP_BIN, 'link', 'show', self.localname)
|
||||
return utils.mutecall(cmd)
|
||||
|
||||
self.waitfor(localdevexists)
|
||||
|
||||
def waitfordevicenode(self):
|
||||
'''\
|
||||
"""
|
||||
Check for presence of a node device - tap device may not
|
||||
appear right away waits
|
||||
'''
|
||||
"""
|
||||
|
||||
def nodedevexists():
|
||||
cmd = (IP_BIN, 'link', 'show', self.name)
|
||||
cmd = (constants.IP_BIN, 'link', 'show', self.name)
|
||||
return self.node.cmd(cmd)
|
||||
count = 0
|
||||
while True:
|
||||
try:
|
||||
self.waitfor(nodedevexists)
|
||||
break
|
||||
except RuntimeError:
|
||||
# check if this is an EMANE interface; if so, continue
|
||||
# waiting if EMANE is still running
|
||||
if count < 5 and isinstance(self.net, EmaneNode) and \
|
||||
self.node.session.emane.emanerunning(self.node):
|
||||
count += 1
|
||||
else:
|
||||
raise
|
||||
|
||||
self.waitfor(nodedevexists)
|
||||
|
||||
def install(self):
|
||||
''' Install this TAP into its namespace. This is not done from the
|
||||
startup() method but called at a later time when a userspace
|
||||
program (running on the host) has had a chance to open the socket
|
||||
end of the TAP.
|
||||
'''
|
||||
"""
|
||||
Install this TAP into its namespace. This is not done from the
|
||||
startup() method but called at a later time when a userspace
|
||||
program (running on the host) has had a chance to open the socket
|
||||
end of the TAP.
|
||||
"""
|
||||
self.waitfordevicelocal()
|
||||
netns = str(self.node.pid)
|
||||
try:
|
||||
check_call([IP_BIN, "link", "set", self.localname, "netns", netns])
|
||||
except Exception, e:
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "netns", netns])
|
||||
except subprocess.CalledProcessError:
|
||||
msg = "error installing TAP interface %s, command:" % self.localname
|
||||
msg += "ip link set %s netns %s" % (self.localname, netns)
|
||||
self.node.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.localname, msg)
|
||||
self.node.warn(msg)
|
||||
logger.exception(msg)
|
||||
return
|
||||
self.node.cmd([IP_BIN, "link", "set", self.localname,
|
||||
"name", self.name])
|
||||
self.node.cmd([IP_BIN, "link", "set", self.name, "up"])
|
||||
|
||||
self.node.cmd([constants.IP_BIN, "link", "set", self.localname, "name", self.name])
|
||||
self.node.cmd([constants.IP_BIN, "link", "set", self.name, "up"])
|
||||
|
||||
def setaddrs(self):
|
||||
''' Set interface addresses based on self.addrlist.
|
||||
'''
|
||||
"""
|
||||
Set interface addresses based on self.addrlist.
|
||||
"""
|
||||
self.waitfordevicenode()
|
||||
for addr in self.addrlist:
|
||||
self.node.cmd([IP_BIN, "addr", "add", str(addr),
|
||||
"dev", self.name])
|
||||
self.node.cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name])
|
||||
|
||||
|
||||
class GreTap(PyCoreNetIf):
|
||||
''' GRE TAP device for tunneling between emulation servers.
|
||||
Uses the "gretap" tunnel device type from Linux which is a GRE device
|
||||
having a MAC address. The MAC address is required for bridging.
|
||||
'''
|
||||
def __init__(self, node = None, name = None, session = None, mtu = 1458,
|
||||
remoteip = None, objid = None, localip = None, ttl = 255,
|
||||
key = None, start = True):
|
||||
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu)
|
||||
"""
|
||||
GRE TAP device for tunneling between emulation servers.
|
||||
Uses the "gretap" tunnel device type from Linux which is a GRE device
|
||||
having a MAC address. The MAC address is required for bridging.
|
||||
"""
|
||||
|
||||
def __init__(self, node=None, name=None, session=None, mtu=1458,
|
||||
remoteip=None, objid=None, localip=None, ttl=255,
|
||||
key=None, start=True):
|
||||
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
|
||||
self.session = session
|
||||
if objid is None:
|
||||
# from PyCoreObj
|
||||
objid = (((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff)
|
||||
objid = ((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff
|
||||
self.objid = objid
|
||||
sessionid = self.session.shortsessionid()
|
||||
sessionid = self.session.short_session_id()
|
||||
# interface name on the local host machine
|
||||
self.localname = "gt.%s.%s" % (self.objid, sessionid)
|
||||
self.transport_type = "raw"
|
||||
if not start:
|
||||
self.up = False
|
||||
return
|
||||
|
||||
|
||||
if remoteip is None:
|
||||
raise ValueError, "missing remote IP required for GRE TAP device"
|
||||
cmd = ("ip", "link", "add", self.localname, "type", "gretap",
|
||||
|
@ -190,21 +180,21 @@ class GreTap(PyCoreNetIf):
|
|||
cmd += ("ttl", str(ttl))
|
||||
if key:
|
||||
cmd += ("key", str(key))
|
||||
check_call(cmd)
|
||||
subprocess.check_call(cmd)
|
||||
cmd = ("ip", "link", "set", self.localname, "up")
|
||||
check_call(cmd)
|
||||
subprocess.check_call(cmd)
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
if self.localname:
|
||||
cmd = ("ip", "link", "set", self.localname, "down")
|
||||
check_call(cmd)
|
||||
subprocess.check_call(cmd)
|
||||
cmd = ("ip", "link", "del", self.localname)
|
||||
check_call(cmd)
|
||||
subprocess.check_call(cmd)
|
||||
self.localname = None
|
||||
|
||||
def tonodemsg(self, flags):
|
||||
|
||||
def data(self, message_type):
|
||||
return None
|
||||
|
||||
def tolinkmsgs(self, flags):
|
||||
|
||||
def all_link_data(self, flags):
|
||||
return []
|
||||
|
|
|
@ -1,42 +1,48 @@
|
|||
#
|
||||
# CORE
|
||||
# Copyright (c)2010-2016 the Boeing Company.
|
||||
# See the LICENSE file included in this distribution.
|
||||
#
|
||||
# authors: Tom Goff <thomas.goff@boeing.com>
|
||||
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
||||
#
|
||||
'''
|
||||
vnet.py: PyCoreNet and LxBrNet classes that implement virtual networks using
|
||||
"""
|
||||
PyCoreNet and LxBrNet classes that implement virtual networks using
|
||||
Linux Ethernet bridging and ebtables rules.
|
||||
'''
|
||||
"""
|
||||
|
||||
import os, sys, threading, time, subprocess
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
from core.api import coreapi
|
||||
from core.misc.utils import *
|
||||
from core.constants import *
|
||||
from core.coreobj import PyCoreNet, PyCoreObj
|
||||
from core.netns.vif import VEth, GreTap
|
||||
from core import constants
|
||||
from core.coreobj import PyCoreNet
|
||||
from core.misc import log
|
||||
from core.misc import utils
|
||||
from core.netns.vif import GreTap
|
||||
from core.netns.vif import VEth
|
||||
|
||||
checkexec([BRCTL_BIN, IP_BIN, EBTABLES_BIN, TC_BIN])
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
utils.check_executables([
|
||||
constants.BRCTL_BIN,
|
||||
constants.IP_BIN,
|
||||
constants.EBTABLES_BIN,
|
||||
constants.TC_BIN
|
||||
])
|
||||
|
||||
ebtables_lock = threading.Lock()
|
||||
|
||||
|
||||
class EbtablesQueue(object):
|
||||
''' Helper class for queuing up ebtables commands into rate-limited
|
||||
"""
|
||||
Helper class for queuing up ebtables commands into rate-limited
|
||||
atomic commits. This improves performance and reliability when there are
|
||||
many WLAN link updates.
|
||||
'''
|
||||
"""
|
||||
# update rate is every 300ms
|
||||
rate = 0.3
|
||||
# ebtables
|
||||
atomic_file = "/tmp/pycore.ebtables.atomic"
|
||||
|
||||
|
||||
def __init__(self):
|
||||
''' Initialize the helper class, but don't start the update thread
|
||||
"""
|
||||
Initialize the helper class, but don't start the update thread
|
||||
until a WLAN is instantiated.
|
||||
'''
|
||||
"""
|
||||
self.doupdateloop = False
|
||||
self.updatethread = None
|
||||
# this lock protects cmds and updates lists
|
||||
|
@ -48,28 +54,31 @@ class EbtablesQueue(object):
|
|||
# timestamps of last WLAN update; this keeps track of WLANs that are
|
||||
# using this queue
|
||||
self.last_update_time = {}
|
||||
|
||||
|
||||
def startupdateloop(self, wlan):
|
||||
''' Kick off the update loop; only needs to be invoked once.
|
||||
'''
|
||||
"""
|
||||
Kick off the update loop; only needs to be invoked once.
|
||||
"""
|
||||
self.updatelock.acquire()
|
||||
self.last_update_time[wlan] = time.time()
|
||||
self.updatelock.release()
|
||||
if self.doupdateloop:
|
||||
return
|
||||
self.doupdateloop = True
|
||||
self.updatethread = threading.Thread(target = self.updateloop)
|
||||
self.updatethread = threading.Thread(target=self.updateloop)
|
||||
self.updatethread.daemon = True
|
||||
self.updatethread.start()
|
||||
|
||||
|
||||
def stopupdateloop(self, wlan):
|
||||
''' Kill the update loop thread if there are no more WLANs using it.
|
||||
'''
|
||||
"""
|
||||
Kill the update loop thread if there are no more WLANs using it.
|
||||
"""
|
||||
self.updatelock.acquire()
|
||||
try:
|
||||
del self.last_update_time[wlan]
|
||||
except KeyError:
|
||||
pass
|
||||
logger.exception("error deleting last update time for wlan: %s", wlan)
|
||||
|
||||
self.updatelock.release()
|
||||
if len(self.last_update_time) > 0:
|
||||
return
|
||||
|
@ -77,131 +86,132 @@ class EbtablesQueue(object):
|
|||
if self.updatethread:
|
||||
self.updatethread.join()
|
||||
self.updatethread = None
|
||||
|
||||
|
||||
def ebatomiccmd(self, cmd):
|
||||
''' Helper for building ebtables atomic file command list.
|
||||
'''
|
||||
r = [EBTABLES_BIN, "--atomic-file", self.atomic_file]
|
||||
"""
|
||||
Helper for building ebtables atomic file command list.
|
||||
"""
|
||||
r = [constants.EBTABLES_BIN, "--atomic-file", self.atomic_file]
|
||||
if cmd:
|
||||
r.extend(cmd)
|
||||
return r
|
||||
|
||||
|
||||
def lastupdate(self, wlan):
|
||||
''' Return the time elapsed since this WLAN was last updated.
|
||||
'''
|
||||
"""
|
||||
Return the time elapsed since this WLAN was last updated.
|
||||
"""
|
||||
try:
|
||||
elapsed = time.time() - self.last_update_time[wlan]
|
||||
except KeyError:
|
||||
self.last_update_time[wlan] = time.time()
|
||||
elapsed = 0.0
|
||||
return elapsed
|
||||
|
||||
|
||||
def updated(self, wlan):
|
||||
''' Keep track of when this WLAN was last updated.
|
||||
'''
|
||||
"""
|
||||
Keep track of when this WLAN was last updated.
|
||||
"""
|
||||
self.last_update_time[wlan] = time.time()
|
||||
self.updates.remove(wlan)
|
||||
|
||||
|
||||
def updateloop(self):
|
||||
''' Thread target that looks for WLANs needing update, and
|
||||
"""
|
||||
Thread target that looks for WLANs needing update, and
|
||||
rate limits the amount of ebtables activity. Only one userspace program
|
||||
should use ebtables at any given time, or results can be unpredictable.
|
||||
'''
|
||||
"""
|
||||
while self.doupdateloop:
|
||||
self.updatelock.acquire()
|
||||
for wlan in self.updates:
|
||||
'''
|
||||
Check if wlan is from a previously closed session. Because of the
|
||||
rate limiting scheme employed here, this may happen if a new session
|
||||
"""
|
||||
Check if wlan is from a previously closed session. Because of the
|
||||
rate limiting scheme employed here, this may happen if a new session
|
||||
is started soon after closing a previous session.
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
wlan.session
|
||||
except:
|
||||
# Just mark as updated to remove from self.updates.
|
||||
# Just mark as updated to remove from self.updates.
|
||||
self.updated(wlan)
|
||||
continue
|
||||
if self.lastupdate(wlan) > self.rate:
|
||||
self.buildcmds(wlan)
|
||||
#print "ebtables commit %d rules" % len(self.cmds)
|
||||
# print "ebtables commit %d rules" % len(self.cmds)
|
||||
self.ebcommit(wlan)
|
||||
self.updated(wlan)
|
||||
self.updatelock.release()
|
||||
time.sleep(self.rate)
|
||||
|
||||
|
||||
def ebcommit(self, wlan):
|
||||
''' Perform ebtables atomic commit using commands built in the
|
||||
"""
|
||||
Perform ebtables atomic commit using commands built in the
|
||||
self.cmds list.
|
||||
'''
|
||||
"""
|
||||
# save kernel ebtables snapshot to a file
|
||||
cmd = self.ebatomiccmd(["--atomic-save",])
|
||||
cmd = self.ebatomiccmd(["--atomic-save", ])
|
||||
try:
|
||||
check_call(cmd)
|
||||
except Exception, e:
|
||||
self.eberror(wlan, "atomic-save (%s)" % cmd, e)
|
||||
subprocess.check_call(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("atomic-save (%s)", cmd)
|
||||
# no atomic file, exit
|
||||
return
|
||||
# modify the table file using queued ebtables commands
|
||||
for c in self.cmds:
|
||||
cmd = self.ebatomiccmd(c)
|
||||
try:
|
||||
check_call(cmd)
|
||||
except Exception, e:
|
||||
self.eberror(wlan, "cmd=%s" % cmd, e)
|
||||
pass
|
||||
subprocess.check_call(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("cmd=%s", cmd)
|
||||
|
||||
self.cmds = []
|
||||
# commit the table file to the kernel
|
||||
cmd = self.ebatomiccmd(["--atomic-commit",])
|
||||
cmd = self.ebatomiccmd(["--atomic-commit", ])
|
||||
|
||||
try:
|
||||
check_call(cmd)
|
||||
subprocess.check_call(cmd)
|
||||
os.unlink(self.atomic_file)
|
||||
except Exception, e:
|
||||
self.eberror(wlan, "atomic-commit (%s)" % cmd, e)
|
||||
|
||||
except OSError:
|
||||
logger.exception("atomic-commit (%s)", cmd)
|
||||
|
||||
def ebchange(self, wlan):
|
||||
''' Flag a change to the given WLAN's _linked dict, so the ebtables
|
||||
"""
|
||||
Flag a change to the given WLAN's _linked dict, so the ebtables
|
||||
chain will be rebuilt at the next interval.
|
||||
'''
|
||||
"""
|
||||
self.updatelock.acquire()
|
||||
if wlan not in self.updates:
|
||||
self.updates.append(wlan)
|
||||
self.updatelock.release()
|
||||
|
||||
|
||||
def buildcmds(self, wlan):
|
||||
''' Inspect a _linked dict from a wlan, and rebuild the ebtables chain
|
||||
"""
|
||||
Inspect a _linked dict from a wlan, and rebuild the ebtables chain
|
||||
for that WLAN.
|
||||
'''
|
||||
"""
|
||||
wlan._linked_lock.acquire()
|
||||
# flush the chain
|
||||
self.cmds.extend([["-F", wlan.brname],])
|
||||
self.cmds.extend([["-F", wlan.brname], ])
|
||||
# rebuild the chain
|
||||
for (netif1, v) in wlan._linked.items():
|
||||
for (netif2, linked) in v.items():
|
||||
for netif1, v in wlan._linked.items():
|
||||
for netif2, linked in v.items():
|
||||
if wlan.policy == "DROP" and linked:
|
||||
self.cmds.extend([["-A", wlan.brname, "-i", netif1.localname,
|
||||
"-o", netif2.localname, "-j", "ACCEPT"],
|
||||
["-A", wlan.brname, "-o", netif1.localname,
|
||||
"-i", netif2.localname, "-j", "ACCEPT"]])
|
||||
"-o", netif2.localname, "-j", "ACCEPT"],
|
||||
["-A", wlan.brname, "-o", netif1.localname,
|
||||
"-i", netif2.localname, "-j", "ACCEPT"]])
|
||||
elif wlan.policy == "ACCEPT" and not linked:
|
||||
self.cmds.extend([["-A", wlan.brname, "-i", netif1.localname,
|
||||
"-o", netif2.localname, "-j", "DROP"],
|
||||
["-A", wlan.brname, "-o", netif1.localname,
|
||||
"-i", netif2.localname, "-j", "DROP"]])
|
||||
"-o", netif2.localname, "-j", "DROP"],
|
||||
["-A", wlan.brname, "-o", netif1.localname,
|
||||
"-i", netif2.localname, "-j", "DROP"]])
|
||||
wlan._linked_lock.release()
|
||||
|
||||
def eberror(self, wlan, source, error):
|
||||
''' Log an ebtables command error and send an exception.
|
||||
'''
|
||||
if not wlan:
|
||||
return
|
||||
wlan.exception(coreapi.CORE_EXCP_LEVEL_ERROR, wlan.brname,
|
||||
"ebtables command error: %s\n%s\n" % (source, error))
|
||||
|
||||
|
||||
|
||||
# a global object because all WLANs share the same queue
|
||||
# cannot have multiple threads invoking the ebtables commnd
|
||||
ebq = EbtablesQueue()
|
||||
|
||||
|
||||
def ebtablescmds(call, cmds):
|
||||
ebtables_lock.acquire()
|
||||
try:
|
||||
|
@ -210,19 +220,18 @@ def ebtablescmds(call, cmds):
|
|||
finally:
|
||||
ebtables_lock.release()
|
||||
|
||||
class LxBrNet(PyCoreNet):
|
||||
|
||||
class LxBrNet(PyCoreNet):
|
||||
policy = "DROP"
|
||||
|
||||
def __init__(self, session, objid = None, name = None, verbose = False,
|
||||
start = True, policy = None):
|
||||
PyCoreNet.__init__(self, session, objid, name, verbose, start)
|
||||
def __init__(self, session, objid=None, name=None, start=True, policy=None):
|
||||
PyCoreNet.__init__(self, session, objid, name, start)
|
||||
if name is None:
|
||||
name = str(self.objid)
|
||||
if policy is not None:
|
||||
self.policy = policy
|
||||
self.name = name
|
||||
sessionid = self.session.shortsessionid()
|
||||
sessionid = self.session.short_session_id()
|
||||
self.brname = "b.%s.%s" % (str(self.objid), sessionid)
|
||||
self.up = False
|
||||
if start:
|
||||
|
@ -231,28 +240,26 @@ class LxBrNet(PyCoreNet):
|
|||
|
||||
def startup(self):
|
||||
try:
|
||||
check_call([BRCTL_BIN, "addbr", self.brname])
|
||||
except Exception, e:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, self.brname,
|
||||
"Error adding bridge: %s" % e)
|
||||
subprocess.check_call([constants.BRCTL_BIN, "addbr", self.brname])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error adding bridge")
|
||||
|
||||
try:
|
||||
# turn off spanning tree protocol and forwarding delay
|
||||
check_call([BRCTL_BIN, "stp", self.brname, "off"])
|
||||
check_call([BRCTL_BIN, "setfd", self.brname, "0"])
|
||||
check_call([IP_BIN, "link", "set", self.brname, "up"])
|
||||
subprocess.check_call([constants.BRCTL_BIN, "stp", self.brname, "off"])
|
||||
subprocess.check_call([constants.BRCTL_BIN, "setfd", self.brname, "0"])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.brname, "up"])
|
||||
# create a new ebtables chain for this bridge
|
||||
ebtablescmds(check_call, [
|
||||
[EBTABLES_BIN, "-N", self.brname, "-P", self.policy],
|
||||
[EBTABLES_BIN, "-A", "FORWARD",
|
||||
"--logical-in", self.brname, "-j", self.brname]])
|
||||
ebtablescmds(subprocess.check_call, [
|
||||
[constants.EBTABLES_BIN, "-N", self.brname, "-P", self.policy],
|
||||
[constants.EBTABLES_BIN, "-A", "FORWARD", "--logical-in", self.brname, "-j", self.brname]
|
||||
])
|
||||
# turn off multicast snooping so mcast forwarding occurs w/o IGMP joins
|
||||
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % \
|
||||
self.brname
|
||||
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % self.brname
|
||||
if os.path.exists(snoop):
|
||||
open(snoop, "w").write('0')
|
||||
except Exception, e:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_WARNING, self.brname,
|
||||
"Error setting bridge parameters: %s" % e)
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error setting bridge parameters")
|
||||
|
||||
self.up = True
|
||||
|
||||
|
@ -260,12 +267,12 @@ class LxBrNet(PyCoreNet):
|
|||
if not self.up:
|
||||
return
|
||||
ebq.stopupdateloop(self)
|
||||
mutecall([IP_BIN, "link", "set", self.brname, "down"])
|
||||
mutecall([BRCTL_BIN, "delbr", self.brname])
|
||||
ebtablescmds(mutecall, [
|
||||
[EBTABLES_BIN, "-D", "FORWARD",
|
||||
utils.mutecall([constants.IP_BIN, "link", "set", self.brname, "down"])
|
||||
utils.mutecall([constants.BRCTL_BIN, "delbr", self.brname])
|
||||
ebtablescmds(utils.mutecall, [
|
||||
[constants.EBTABLES_BIN, "-D", "FORWARD",
|
||||
"--logical-in", self.brname, "-j", self.brname],
|
||||
[EBTABLES_BIN, "-X", self.brname]])
|
||||
[constants.EBTABLES_BIN, "-X", self.brname]])
|
||||
for netif in self.netifs():
|
||||
# removes veth pairs used for bridge-to-bridge connections
|
||||
netif.shutdown()
|
||||
|
@ -277,23 +284,19 @@ class LxBrNet(PyCoreNet):
|
|||
def attach(self, netif):
|
||||
if self.up:
|
||||
try:
|
||||
check_call([BRCTL_BIN, "addif", self.brname, netif.localname])
|
||||
check_call([IP_BIN, "link", "set", netif.localname, "up"])
|
||||
except Exception, e:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
|
||||
"Error joining interface %s to bridge %s: %s" % \
|
||||
(netif.localname, self.brname, e))
|
||||
subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, netif.localname])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", netif.localname, "up"])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error joining interface %s to bridge %s", netif.localname, self.brname)
|
||||
return
|
||||
PyCoreNet.attach(self, netif)
|
||||
|
||||
def detach(self, netif):
|
||||
if self.up:
|
||||
try:
|
||||
check_call([BRCTL_BIN, "delif", self.brname, netif.localname])
|
||||
except Exception, e:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
|
||||
"Error removing interface %s from bridge %s: %s" % \
|
||||
(netif.localname, self.brname, e))
|
||||
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, netif.localname])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error removing interface %s from bridge %s", netif.localname, self.brname)
|
||||
return
|
||||
PyCoreNet.detach(self, netif)
|
||||
|
||||
|
@ -316,9 +319,10 @@ class LxBrNet(PyCoreNet):
|
|||
return linked
|
||||
|
||||
def unlink(self, netif1, netif2):
|
||||
''' Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
|
||||
"""
|
||||
Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
|
||||
filtering rules.
|
||||
'''
|
||||
"""
|
||||
self._linked_lock.acquire()
|
||||
if not self.linked(netif1, netif2):
|
||||
self._linked_lock.release()
|
||||
|
@ -328,9 +332,10 @@ class LxBrNet(PyCoreNet):
|
|||
ebq.ebchange(self)
|
||||
|
||||
def link(self, netif1, netif2):
|
||||
''' Link two PyCoreNetIfs together, resulting in adding or removing
|
||||
"""
|
||||
Link two PyCoreNetIfs together, resulting in adding or removing
|
||||
ebtables filtering rules.
|
||||
'''
|
||||
"""
|
||||
self._linked_lock.acquire()
|
||||
if self.linked(netif1, netif2):
|
||||
self._linked_lock.release()
|
||||
|
@ -339,37 +344,35 @@ class LxBrNet(PyCoreNet):
|
|||
self._linked_lock.release()
|
||||
ebq.ebchange(self)
|
||||
|
||||
def linkconfig(self, netif, bw = None, delay = None,
|
||||
loss = None, duplicate = None, jitter = None, netif2 = None,
|
||||
devname = None):
|
||||
''' Configure link parameters by applying tc queuing disciplines on the
|
||||
interface.
|
||||
'''
|
||||
def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None,
|
||||
jitter=None, netif2=None, devname=None):
|
||||
"""
|
||||
Configure link parameters by applying tc queuing disciplines on the
|
||||
interface.
|
||||
"""
|
||||
if devname is None:
|
||||
devname = netif.localname
|
||||
tc = [TC_BIN, "qdisc", "replace", "dev", devname]
|
||||
tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname]
|
||||
parent = ["root"]
|
||||
changed = False
|
||||
if netif.setparam('bw', bw):
|
||||
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
|
||||
if bw is not None:
|
||||
burst = max(2 * netif.mtu, bw / 1000)
|
||||
limit = 0xffff # max IP payload
|
||||
limit = 0xffff # max IP payload
|
||||
tbf = ["tbf", "rate", str(bw),
|
||||
"burst", str(burst), "limit", str(limit)]
|
||||
if bw > 0:
|
||||
if self.up:
|
||||
if (self.verbose):
|
||||
self.info("linkconfig: %s" % \
|
||||
([tc + parent + ["handle", "1:"] + tbf],))
|
||||
check_call(tc + parent + ["handle", "1:"] + tbf)
|
||||
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "1:"] + tbf],))
|
||||
subprocess.check_call(tc + parent + ["handle", "1:"] + tbf)
|
||||
netif.setparam('has_tbf', True)
|
||||
changed = True
|
||||
elif netif.getparam('has_tbf') and bw <= 0:
|
||||
tcd = [] + tc
|
||||
tcd[2] = "delete"
|
||||
if self.up:
|
||||
check_call(tcd + parent)
|
||||
subprocess.check_call(tcd + parent)
|
||||
netif.setparam('has_tbf', False)
|
||||
# removing the parent removes the child
|
||||
netif.setparam('has_netem', False)
|
||||
|
@ -395,7 +398,7 @@ class LxBrNet(PyCoreNet):
|
|||
netem += ["delay", "0us", "%sus" % jitter, "25%"]
|
||||
else:
|
||||
netem += ["%sus" % jitter, "25%"]
|
||||
|
||||
|
||||
if loss is not None:
|
||||
netem += ["loss", "%s%%" % min(loss, 100)]
|
||||
if duplicate is not None:
|
||||
|
@ -406,24 +409,21 @@ class LxBrNet(PyCoreNet):
|
|||
return
|
||||
tc[2] = "delete"
|
||||
if self.up:
|
||||
if self.verbose:
|
||||
self.info("linkconfig: %s" % \
|
||||
([tc + parent + ["handle", "10:"]],))
|
||||
check_call(tc + parent + ["handle", "10:"])
|
||||
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"]],))
|
||||
subprocess.check_call(tc + parent + ["handle", "10:"])
|
||||
netif.setparam('has_netem', False)
|
||||
elif len(netem) > 1:
|
||||
if self.up:
|
||||
if self.verbose:
|
||||
self.info("linkconfig: %s" % \
|
||||
([tc + parent + ["handle", "10:"] + netem],))
|
||||
check_call(tc + parent + ["handle", "10:"] + netem)
|
||||
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem],))
|
||||
subprocess.check_call(tc + parent + ["handle", "10:"] + netem)
|
||||
netif.setparam('has_netem', True)
|
||||
|
||||
def linknet(self, net):
|
||||
''' Link this bridge with another by creating a veth pair and installing
|
||||
each device into each bridge.
|
||||
'''
|
||||
sessionid = self.session.shortsessionid()
|
||||
"""
|
||||
Link this bridge with another by creating a veth pair and installing
|
||||
each device into each bridge.
|
||||
"""
|
||||
sessionid = self.session.short_session_id()
|
||||
try:
|
||||
self_objid = '%x' % self.objid
|
||||
except TypeError:
|
||||
|
@ -434,19 +434,18 @@ class LxBrNet(PyCoreNet):
|
|||
net_objid = '%s' % net.objid
|
||||
localname = 'veth%s.%s.%s' % (self_objid, net_objid, sessionid)
|
||||
if len(localname) >= 16:
|
||||
raise ValueError, "interface local name '%s' too long" % \
|
||||
localname
|
||||
raise ValueError("interface local name '%s' too long" % localname)
|
||||
name = 'veth%s.%s.%s' % (net_objid, self_objid, sessionid)
|
||||
if len(name) >= 16:
|
||||
raise ValueError, "interface name '%s' too long" % name
|
||||
netif = VEth(node = None, name = name, localname = localname,
|
||||
mtu = 1500, net = self, start = self.up)
|
||||
raise ValueError("interface name '%s' too long" % name)
|
||||
netif = VEth(node=None, name=name, localname=localname,
|
||||
mtu=1500, net=self, start=self.up)
|
||||
self.attach(netif)
|
||||
if net.up:
|
||||
# this is similar to net.attach() but uses netif.name instead
|
||||
# this is similar to net.attach() but uses netif.name instead
|
||||
# of localname
|
||||
check_call([BRCTL_BIN, "addif", net.brname, netif.name])
|
||||
check_call([IP_BIN, "link", "set", netif.name, "up"])
|
||||
subprocess.check_call([constants.BRCTL_BIN, "addif", net.brname, netif.name])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", netif.name, "up"])
|
||||
i = net.newifindex()
|
||||
net._netif[i] = netif
|
||||
with net._linked_lock:
|
||||
|
@ -454,38 +453,39 @@ class LxBrNet(PyCoreNet):
|
|||
netif.net = self
|
||||
netif.othernet = net
|
||||
return netif
|
||||
|
||||
|
||||
def getlinknetif(self, net):
|
||||
''' Return the interface of that links this net with another net
|
||||
"""
|
||||
Return the interface of that links this net with another net
|
||||
(that were linked using linknet()).
|
||||
'''
|
||||
"""
|
||||
for netif in self.netifs():
|
||||
if hasattr(netif, 'othernet') and netif.othernet == net:
|
||||
return netif
|
||||
return None
|
||||
|
||||
def addrconfig(self, addrlist):
|
||||
''' Set addresses on the bridge.
|
||||
'''
|
||||
"""
|
||||
Set addresses on the bridge.
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
for addr in addrlist:
|
||||
try:
|
||||
check_call([IP_BIN, "addr", "add", str(addr), "dev", self.brname])
|
||||
except Exception, e:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
|
||||
"Error adding IP address: %s" % e)
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "add", str(addr), "dev", self.brname])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error adding IP address")
|
||||
|
||||
|
||||
class GreTapBridge(LxBrNet):
|
||||
''' A network consisting of a bridge with a gretap device for tunneling to
|
||||
another system.
|
||||
'''
|
||||
def __init__(self, session, remoteip = None, objid = None, name = None,
|
||||
policy = "ACCEPT", localip = None, ttl = 255, key = None,
|
||||
verbose = False, start = True):
|
||||
LxBrNet.__init__(self, session = session, objid = objid,
|
||||
name = name, verbose = verbose, policy = policy,
|
||||
start = False)
|
||||
"""
|
||||
A network consisting of a bridge with a gretap device for tunneling to
|
||||
another system.
|
||||
"""
|
||||
|
||||
def __init__(self, session, remoteip=None, objid=None, name=None,
|
||||
policy="ACCEPT", localip=None, ttl=255, key=None, start=True):
|
||||
LxBrNet.__init__(self, session=session, objid=objid, name=name, policy=policy, start=False)
|
||||
self.grekey = key
|
||||
if self.grekey is None:
|
||||
self.grekey = self.session.sessionid ^ self.objid
|
||||
|
@ -497,47 +497,51 @@ class GreTapBridge(LxBrNet):
|
|||
if remoteip is None:
|
||||
self.gretap = None
|
||||
else:
|
||||
self.gretap = GreTap(node = self, name = None, session = session,
|
||||
remoteip = remoteip, objid = None, localip = localip, ttl = ttl,
|
||||
key = self.grekey)
|
||||
self.gretap = GreTap(node=self, name=None, session=session,
|
||||
remoteip=remoteip, objid=None, localip=localip, ttl=ttl,
|
||||
key=self.grekey)
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
def startup(self):
|
||||
''' Creates a bridge and adds the gretap device to it.
|
||||
'''
|
||||
"""
|
||||
Creates a bridge and adds the gretap device to it.
|
||||
"""
|
||||
LxBrNet.startup(self)
|
||||
if self.gretap:
|
||||
self.attach(self.gretap)
|
||||
|
||||
def shutdown(self):
|
||||
''' Detach the gretap device and remove the bridge.
|
||||
'''
|
||||
"""
|
||||
Detach the gretap device and remove the bridge.
|
||||
"""
|
||||
if self.gretap:
|
||||
self.detach(self.gretap)
|
||||
self.gretap.shutdown()
|
||||
self.gretap = None
|
||||
LxBrNet.shutdown(self)
|
||||
|
||||
|
||||
def addrconfig(self, addrlist):
|
||||
''' Set the remote tunnel endpoint. This is a one-time method for
|
||||
creating the GreTap device, which requires the remoteip at startup.
|
||||
The 1st address in the provided list is remoteip, 2nd optionally
|
||||
specifies localip.
|
||||
'''
|
||||
"""
|
||||
Set the remote tunnel endpoint. This is a one-time method for
|
||||
creating the GreTap device, which requires the remoteip at startup.
|
||||
The 1st address in the provided list is remoteip, 2nd optionally
|
||||
specifies localip.
|
||||
"""
|
||||
if self.gretap:
|
||||
raise ValueError, "gretap already exists for %s" % self.name
|
||||
remoteip = addrlist[0].split('/')[0]
|
||||
localip = None
|
||||
if len(addrlist) > 1:
|
||||
localip = addrlist[1].split('/')[0]
|
||||
self.gretap = GreTap(session = self.session, remoteip = remoteip,
|
||||
objid = None, name = None,
|
||||
localip = localip, ttl = self.ttl, key = self.grekey)
|
||||
self.gretap = GreTap(session=self.session, remoteip=remoteip,
|
||||
objid=None, name=None,
|
||||
localip=localip, ttl=self.ttl, key=self.grekey)
|
||||
self.attach(self.gretap)
|
||||
|
||||
def setkey(self, key):
|
||||
''' Set the GRE key used for the GreTap device. This needs to be set
|
||||
prior to instantiating the GreTap device (before addrconfig).
|
||||
'''
|
||||
"""
|
||||
Set the GRE key used for the GreTap device. This needs to be set
|
||||
prior to instantiating the GreTap device (before addrconfig).
|
||||
"""
|
||||
self.grekey = key
|
||||
|
|
|
@ -1,35 +1,37 @@
|
|||
#
|
||||
# CORE
|
||||
# Copyright (c)2010-2012 the Boeing Company.
|
||||
# See the LICENSE file included in this distribution.
|
||||
#
|
||||
# authors: Tom Goff <thomas.goff@boeing.com>
|
||||
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
||||
#
|
||||
'''
|
||||
"""
|
||||
vnode.py: PyCoreNode and LxcNode classes that implement the network namespace
|
||||
virtual node.
|
||||
'''
|
||||
"""
|
||||
|
||||
import os, signal, sys, subprocess, vnodeclient, threading, string, shutil
|
||||
import random, time
|
||||
from core.api import coreapi
|
||||
from core.misc.utils import *
|
||||
from core.constants import *
|
||||
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position
|
||||
from core.netns.vif import VEth, TunTap
|
||||
from core.emane.nodes import EmaneNode
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import signal
|
||||
import string
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from core import constants
|
||||
from core.coreobj import PyCoreNetIf
|
||||
from core.coreobj import PyCoreNode
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import log
|
||||
from core.misc import nodeutils
|
||||
from core.misc import utils
|
||||
from core.netns import vnodeclient
|
||||
from core.netns.vif import TunTap
|
||||
from core.netns.vif import VEth
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
utils.check_executables([constants.IP_BIN])
|
||||
|
||||
checkexec([IP_BIN])
|
||||
|
||||
class SimpleLxcNode(PyCoreNode):
|
||||
def __init__(self, session, objid = None, name = None, nodedir = None,
|
||||
verbose = False, start = True):
|
||||
PyCoreNode.__init__(self, session, objid, name, verbose=verbose,
|
||||
start=start)
|
||||
def __init__(self, session, objid=None, name=None, nodedir=None, start=True):
|
||||
PyCoreNode.__init__(self, session, objid, name, start=start)
|
||||
self.nodedir = nodedir
|
||||
self.ctrlchnlname = \
|
||||
os.path.abspath(os.path.join(self.session.sessiondir, self.name))
|
||||
self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name))
|
||||
self.vnodeclient = None
|
||||
self.pid = None
|
||||
self.up = False
|
||||
|
@ -44,68 +46,74 @@ class SimpleLxcNode(PyCoreNode):
|
|||
return True
|
||||
|
||||
def startup(self):
|
||||
''' Start a new namespace node by invoking the vnoded process that
|
||||
allocates a new namespace. Bring up the loopback device and set
|
||||
the hostname.
|
||||
'''
|
||||
"""
|
||||
Start a new namespace node by invoking the vnoded process that
|
||||
allocates a new namespace. Bring up the loopback device and set
|
||||
the hostname.
|
||||
"""
|
||||
if self.up:
|
||||
raise Exception, "already up"
|
||||
vnoded = ["%s/vnoded" % CORE_SBIN_DIR, "-v", "-c", self.ctrlchnlname,
|
||||
raise Exception("already up")
|
||||
vnoded = ["%s/vnoded" % constants.CORE_SBIN_DIR, "-v", "-c", self.ctrlchnlname,
|
||||
"-l", self.ctrlchnlname + ".log",
|
||||
"-p", self.ctrlchnlname + ".pid"]
|
||||
if self.nodedir:
|
||||
vnoded += ["-C", self.nodedir]
|
||||
env = self.session.getenviron(state=False)
|
||||
env = self.session.get_environment(state=False)
|
||||
env['NODE_NUMBER'] = str(self.objid)
|
||||
env['NODE_NAME'] = str(self.name)
|
||||
|
||||
try:
|
||||
tmp = subprocess.Popen(vnoded, stdout = subprocess.PIPE, env = env)
|
||||
except OSError, e:
|
||||
msg = "error running vnoded command: %s (%s)" % (vnoded, e)
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL,
|
||||
"SimpleLxcNode.startup()", msg)
|
||||
raise Exception, msg
|
||||
tmp = subprocess.Popen(vnoded, stdout=subprocess.PIPE, env=env)
|
||||
except OSError:
|
||||
msg = "error running vnoded command: %s" % vnoded
|
||||
logger.exception("SimpleLxcNode.startup(): %s", msg)
|
||||
raise Exception(msg)
|
||||
|
||||
try:
|
||||
self.pid = int(tmp.stdout.read())
|
||||
tmp.stdout.close()
|
||||
except Exception:
|
||||
except ValueError:
|
||||
msg = "vnoded failed to create a namespace; "
|
||||
msg += "check kernel support and user priveleges"
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL,
|
||||
"SimpleLxcNode.startup()", msg)
|
||||
logger.exception("SimpleLxcNode.startup(): %s", msg)
|
||||
|
||||
if tmp.wait():
|
||||
raise Exception, ("command failed: %s" % vnoded)
|
||||
self.vnodeclient = vnodeclient.VnodeClient(self.name,
|
||||
self.ctrlchnlname)
|
||||
self.info("bringing up loopback interface")
|
||||
self.cmd([IP_BIN, "link", "set", "lo", "up"])
|
||||
self.info("setting hostname: %s" % self.name)
|
||||
raise Exception("command failed: %s" % vnoded)
|
||||
|
||||
self.vnodeclient = vnodeclient.VnodeClient(self.name, self.ctrlchnlname)
|
||||
logger.info("bringing up loopback interface")
|
||||
self.cmd([constants.IP_BIN, "link", "set", "lo", "up"])
|
||||
logger.info("setting hostname: %s" % self.name)
|
||||
self.cmd(["hostname", self.name])
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
if not self.up:
|
||||
return
|
||||
|
||||
while self._mounts:
|
||||
source, target = self._mounts.pop(-1)
|
||||
self.umount(target)
|
||||
|
||||
for netif in self.netifs():
|
||||
netif.shutdown()
|
||||
|
||||
try:
|
||||
os.kill(self.pid, signal.SIGTERM)
|
||||
os.waitpid(self.pid, 0)
|
||||
except OSError:
|
||||
pass
|
||||
logger.exception("error killing process")
|
||||
|
||||
try:
|
||||
os.unlink(self.ctrlchnlname)
|
||||
except OSError:
|
||||
pass
|
||||
logger.exception("error removing file")
|
||||
|
||||
self._netif.clear()
|
||||
self.vnodeclient.close()
|
||||
self.up = False
|
||||
|
||||
def cmd(self, args, wait = True):
|
||||
def cmd(self, args, wait=True):
|
||||
return self.vnodeclient.cmd(args, wait)
|
||||
|
||||
def cmdresult(self, args):
|
||||
|
@ -117,93 +125,107 @@ class SimpleLxcNode(PyCoreNode):
|
|||
def icmd(self, args):
|
||||
return self.vnodeclient.icmd(args)
|
||||
|
||||
def redircmd(self, infd, outfd, errfd, args, wait = True):
|
||||
def redircmd(self, infd, outfd, errfd, args, wait=True):
|
||||
return self.vnodeclient.redircmd(infd, outfd, errfd, args, wait)
|
||||
|
||||
def term(self, sh = "/bin/sh"):
|
||||
return self.vnodeclient.term(sh = sh)
|
||||
def term(self, sh="/bin/sh"):
|
||||
return self.vnodeclient.term(sh=sh)
|
||||
|
||||
def termcmdstring(self, sh = "/bin/sh"):
|
||||
return self.vnodeclient.termcmdstring(sh = sh)
|
||||
def termcmdstring(self, sh="/bin/sh"):
|
||||
return self.vnodeclient.termcmdstring(sh=sh)
|
||||
|
||||
def shcmd(self, cmdstr, sh = "/bin/sh"):
|
||||
return self.vnodeclient.shcmd(cmdstr, sh = sh)
|
||||
def shcmd(self, cmdstr, sh="/bin/sh"):
|
||||
return self.vnodeclient.shcmd(cmdstr, sh=sh)
|
||||
|
||||
def boot(self):
|
||||
pass
|
||||
|
||||
def mount(self, source, target):
|
||||
source = os.path.abspath(source)
|
||||
self.info("mounting %s at %s" % (source, target))
|
||||
logger.info("mounting %s at %s" % (source, target))
|
||||
try:
|
||||
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % \
|
||||
(target, MOUNT_BIN, source, target)
|
||||
(target, constants.MOUNT_BIN, source, target)
|
||||
self.shcmd(shcmd)
|
||||
self._mounts.append((source, target))
|
||||
except:
|
||||
self.warn("mounting failed for %s at %s" % (source, target))
|
||||
logger.exception("mounting failed for %s at %s", source, target)
|
||||
|
||||
def umount(self, target):
|
||||
self.info("unmounting '%s'" % target)
|
||||
logger.info("unmounting '%s'" % target)
|
||||
try:
|
||||
self.cmd([UMOUNT_BIN, "-n", "-l", target])
|
||||
self.cmd([constants.UMOUNT_BIN, "-n", "-l", target])
|
||||
except:
|
||||
self.warn("unmounting failed for %s" % target)
|
||||
logger.exception("unmounting failed for %s" % target)
|
||||
|
||||
def newifindex(self):
|
||||
with self.lock:
|
||||
return PyCoreNode.newifindex(self)
|
||||
|
||||
def newveth(self, ifindex = None, ifname = None, net = None):
|
||||
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()
|
||||
|
||||
sessionid = self.session.short_session_id()
|
||||
|
||||
try:
|
||||
suffix = '%x.%s.%s' % (self.objid, ifindex, sessionid)
|
||||
except TypeError:
|
||||
suffix = '%s.%s.%s' % (self.objid, ifindex, sessionid)
|
||||
|
||||
localname = 'veth' + suffix
|
||||
if len(localname) >= 16:
|
||||
raise ValueError, "interface local name '%s' too long" % \
|
||||
localname
|
||||
localname
|
||||
name = localname + 'p'
|
||||
if len(name) >= 16:
|
||||
raise ValueError, "interface name '%s' too long" % name
|
||||
ifclass = VEth
|
||||
veth = ifclass(node = self, name = name, localname = localname,
|
||||
mtu = 1500, net = net, start = self.up)
|
||||
veth = VEth(node=self, name=name, localname=localname, mtu=1500, net=net, start=self.up)
|
||||
|
||||
if self.up:
|
||||
check_call([IP_BIN, "link", "set", veth.name,
|
||||
"netns", str(self.pid)])
|
||||
self.cmd([IP_BIN, "link", "set", veth.name, "name", ifname])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)])
|
||||
self.cmd([constants.IP_BIN, "link", "set", veth.name, "name", ifname])
|
||||
|
||||
veth.name = ifname
|
||||
|
||||
# retrieve interface information
|
||||
result, output = self.cmdresult(["ip", "link", "show", veth.name])
|
||||
logger.info("interface command output: %s", output)
|
||||
output = output.split("\n")
|
||||
veth.flow_id = int(output[0].strip().split(":")[0]) + 1
|
||||
logger.info("interface flow index: %s - %s", veth.name, veth.flow_id)
|
||||
veth.hwaddr = output[1].strip().split()[1]
|
||||
logger.info("interface mac: %s - %s", veth.name, veth.hwaddr)
|
||||
|
||||
try:
|
||||
self.addnetif(veth, ifindex)
|
||||
except:
|
||||
veth.shutdown()
|
||||
del veth
|
||||
raise
|
||||
|
||||
return ifindex
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def newtuntap(self, ifindex = None, ifname = None, net = None):
|
||||
def newtuntap(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()
|
||||
sessionid = self.session.short_session_id()
|
||||
localname = "tap%s.%s.%s" % (self.objid, ifindex, sessionid)
|
||||
name = ifname
|
||||
ifclass = TunTap
|
||||
tuntap = ifclass(node = self, name = name, localname = localname,
|
||||
mtu = 1500, net = net, start = self.up)
|
||||
tuntap = ifclass(node=self, name=name, localname=localname,
|
||||
mtu=1500, net=net, start=self.up)
|
||||
try:
|
||||
self.addnetif(tuntap, ifindex)
|
||||
except:
|
||||
|
@ -217,50 +239,49 @@ class SimpleLxcNode(PyCoreNode):
|
|||
def sethwaddr(self, ifindex, addr):
|
||||
self._netif[ifindex].sethwaddr(addr)
|
||||
if self.up:
|
||||
(status, result) = self.cmdresult([IP_BIN, "link", "set", "dev",
|
||||
self.ifname(ifindex), "address", str(addr)])
|
||||
(status, result) = self.cmdresult([constants.IP_BIN, "link", "set", "dev",
|
||||
self.ifname(ifindex), "address", str(addr)])
|
||||
if status:
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
|
||||
"SimpleLxcNode.sethwaddr()",
|
||||
"error setting MAC address %s" % str(addr))
|
||||
logger.error("error setting MAC address %s", str(addr))
|
||||
|
||||
def addaddr(self, ifindex, addr):
|
||||
if self.up:
|
||||
self.cmd([IP_BIN, "addr", "add", str(addr),
|
||||
"dev", self.ifname(ifindex)])
|
||||
self.cmd([constants.IP_BIN, "addr", "add", str(addr),
|
||||
"dev", self.ifname(ifindex)])
|
||||
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)
|
||||
logger.exception("trying to delete unknown address: %s" % addr)
|
||||
|
||||
if self.up:
|
||||
self.cmd([IP_BIN, "addr", "del", str(addr),
|
||||
"dev", self.ifname(ifindex)])
|
||||
self.cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)])
|
||||
|
||||
valid_deladdrtype = ("inet", "inet6", "inet6link")
|
||||
def delalladdr(self, ifindex, addrtypes = valid_deladdrtype):
|
||||
addr = self.getaddr(self.ifname(ifindex), rescan = True)
|
||||
|
||||
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)
|
||||
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)
|
||||
self.getaddr(self.ifname(ifindex), rescan=True)
|
||||
|
||||
def ifup(self, ifindex):
|
||||
if self.up:
|
||||
self.cmd([IP_BIN, "link", "set", self.ifname(ifindex), "up"])
|
||||
self.cmd([constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"])
|
||||
|
||||
def newnetif(self, net = None, addrlist = [], hwaddr = None,
|
||||
ifindex = None, ifname = None):
|
||||
def newnetif(self, net=None, addrlist=[], hwaddr=None,
|
||||
ifindex=None, ifname=None):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if isinstance(net, EmaneNode):
|
||||
ifindex = self.newtuntap(ifindex = ifindex, ifname = ifname,
|
||||
net = net)
|
||||
if nodeutils.is_node(net, NodeTypes.EMANE):
|
||||
ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname,
|
||||
net=net)
|
||||
# TUN/TAP is not ready for addressing yet; the device may
|
||||
# take some time to appear, and installing it into a
|
||||
# namespace after it has been bound removes addressing;
|
||||
|
@ -268,18 +289,21 @@ class SimpleLxcNode(PyCoreNode):
|
|||
self.attachnet(ifindex, net)
|
||||
netif = self.netif(ifindex)
|
||||
netif.sethwaddr(hwaddr)
|
||||
for addr in maketuple(addrlist):
|
||||
for addr in utils.maketuple(addrlist):
|
||||
netif.addaddr(addr)
|
||||
return ifindex
|
||||
else:
|
||||
ifindex = self.newveth(ifindex = ifindex, ifname = ifname,
|
||||
net = net)
|
||||
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):
|
||||
|
||||
for addr in utils.maketuple(addrlist):
|
||||
self.addaddr(ifindex, addr)
|
||||
|
||||
self.ifup(ifindex)
|
||||
return ifindex
|
||||
finally:
|
||||
|
@ -291,44 +315,42 @@ class SimpleLxcNode(PyCoreNode):
|
|||
for x in xrange(tmplen)])
|
||||
tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase)
|
||||
for x in xrange(tmplen)])
|
||||
check_call([IP_BIN, "link", "add", "name", tmp1,
|
||||
"type", "veth", "peer", "name", tmp2])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "add", "name", tmp1,
|
||||
"type", "veth", "peer", "name", tmp2])
|
||||
|
||||
check_call([IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
|
||||
self.cmd([IP_BIN, "link", "set", tmp1, "name", ifname])
|
||||
subprocess.call([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
|
||||
self.cmd([constants.IP_BIN, "link", "set", tmp1, "name", ifname])
|
||||
self.addnetif(PyCoreNetIf(self, ifname), self.newifindex())
|
||||
|
||||
check_call([IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)])
|
||||
othernode.cmd([IP_BIN, "link", "set", tmp2, "name", otherifname])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)])
|
||||
othernode.cmd([constants.IP_BIN, "link", "set", tmp2, "name", otherifname])
|
||||
othernode.addnetif(PyCoreNetIf(othernode, otherifname),
|
||||
othernode.newifindex())
|
||||
|
||||
def addfile(self, srcname, filename):
|
||||
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % \
|
||||
(filename, srcname, filename)
|
||||
(filename, srcname, filename)
|
||||
self.shcmd(shcmd)
|
||||
|
||||
def getaddr(self, ifname, rescan = False):
|
||||
return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan)
|
||||
def getaddr(self, ifname, rescan=False):
|
||||
return self.vnodeclient.getaddr(ifname=ifname, rescan=rescan)
|
||||
|
||||
def netifstats(self, ifname = None):
|
||||
return self.vnodeclient.netifstats(ifname = ifname)
|
||||
def netifstats(self, ifname=None):
|
||||
return self.vnodeclient.netifstats(ifname=ifname)
|
||||
|
||||
|
||||
class LxcNode(SimpleLxcNode):
|
||||
def __init__(self, session, objid = None, name = None,
|
||||
nodedir = None, bootsh = "boot.sh", verbose = False,
|
||||
start = True):
|
||||
super(LxcNode, self).__init__(session = session, objid = objid,
|
||||
name = name, nodedir = nodedir,
|
||||
verbose = verbose, start = start)
|
||||
def __init__(self, session, objid=None, name=None,
|
||||
nodedir=None, bootsh="boot.sh", start=True):
|
||||
super(LxcNode, self).__init__(session=session, objid=objid,
|
||||
name=name, nodedir=nodedir, start=start)
|
||||
self.bootsh = bootsh
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
def boot(self):
|
||||
self.session.services.bootnodeservices(self)
|
||||
|
||||
|
||||
def validate(self):
|
||||
self.session.services.validatenodeservices(self)
|
||||
|
||||
|
@ -339,10 +361,8 @@ class LxcNode(SimpleLxcNode):
|
|||
super(LxcNode, self).startup()
|
||||
self.privatedir("/var/run")
|
||||
self.privatedir("/var/log")
|
||||
except OSError, e:
|
||||
self.warn("Error with LxcNode.startup(): %s" % e)
|
||||
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
|
||||
"LxcNode.startup()", "%s" % e)
|
||||
except OSError:
|
||||
logger.exception("error during LxcNode.startup()")
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
|
@ -351,7 +371,7 @@ class LxcNode(SimpleLxcNode):
|
|||
return
|
||||
self.lock.acquire()
|
||||
# services are instead stopped when session enters datacollect state
|
||||
#self.session.services.stopnodeservices(self)
|
||||
# self.session.services.stopnodeservices(self)
|
||||
try:
|
||||
super(LxcNode, self).shutdown()
|
||||
finally:
|
||||
|
@ -360,20 +380,20 @@ class LxcNode(SimpleLxcNode):
|
|||
|
||||
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('/', '.'))
|
||||
raise ValueError("path not fully qualified: %s" % 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
|
||||
logger.exception("error creating directory: %s", hostpath)
|
||||
|
||||
self.mount(hostpath, path)
|
||||
|
||||
def hostfilename(self, filename):
|
||||
''' Return the name of a node's file on the host filesystem.
|
||||
'''
|
||||
"""
|
||||
Return the name of a node's file on the host filesystem.
|
||||
"""
|
||||
dirname, basename = os.path.split(filename)
|
||||
if not basename:
|
||||
raise ValueError, "no basename for filename: " + filename
|
||||
|
@ -383,28 +403,27 @@ class LxcNode(SimpleLxcNode):
|
|||
dirname = os.path.join(self.nodedir, dirname)
|
||||
return os.path.join(dirname, basename)
|
||||
|
||||
def opennodefile(self, filename, mode = "w"):
|
||||
def opennodefile(self, filename, mode="w"):
|
||||
hostfilename = self.hostfilename(filename)
|
||||
dirname, basename = os.path.split(hostfilename)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname, mode = 0755)
|
||||
os.makedirs(dirname, mode=0755)
|
||||
return open(hostfilename, mode)
|
||||
|
||||
def nodefile(self, filename, contents, mode = 0644):
|
||||
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))
|
||||
|
||||
def nodefilecopy(self, filename, srcfilename, mode = None):
|
||||
''' Copy a file to a node, following symlinks and preserving metadata.
|
||||
logger.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode))
|
||||
|
||||
def nodefilecopy(self, filename, srcfilename, mode=None):
|
||||
"""
|
||||
Copy a file to a node, following symlinks and preserving metadata.
|
||||
Change file mode if specified.
|
||||
'''
|
||||
"""
|
||||
hostfilename = self.hostfilename(filename)
|
||||
shutil.copy2(srcfilename, hostfilename)
|
||||
if mode is not None:
|
||||
os.chmod(hostfilename, mode)
|
||||
self.info("copied nodefile: '%s'; mode: %s" % (hostfilename, mode))
|
||||
|
||||
|
||||
logger.info("copied nodefile: '%s'; mode: %s" % (hostfilename, mode))
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
#
|
||||
# CORE
|
||||
# Copyright (c)2010-2012 the Boeing Company.
|
||||
# See the LICENSE file included in this distribution.
|
||||
#
|
||||
# author: Tom Goff <thomas.goff@boeing.com>
|
||||
#
|
||||
'''
|
||||
"""
|
||||
vnodeclient.py: implementation of the VnodeClient class for issuing commands
|
||||
over a control channel to the vnoded process running in a network namespace.
|
||||
The control channel can be accessed via calls to the vcmd Python module or
|
||||
by invoking the vcmd shell command.
|
||||
'''
|
||||
"""
|
||||
|
||||
import os, stat, sys
|
||||
from core.constants import *
|
||||
import os
|
||||
import stat
|
||||
|
||||
from core import constants
|
||||
from core.misc import log
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
USE_VCMD_MODULE = True
|
||||
|
||||
|
@ -22,7 +20,8 @@ if USE_VCMD_MODULE:
|
|||
else:
|
||||
import subprocess
|
||||
|
||||
VCMD = os.path.join(CORE_SBIN_DIR, "vcmd")
|
||||
VCMD = os.path.join(constants.CORE_SBIN_DIR, "vcmd")
|
||||
|
||||
|
||||
class VnodeClient(object):
|
||||
def __init__(self, name, ctrlchnlname):
|
||||
|
@ -34,9 +33,6 @@ class VnodeClient(object):
|
|||
self.cmdchnl = None
|
||||
self._addr = {}
|
||||
|
||||
def warn(self, msg):
|
||||
print >> sys.stderr, "%s: %s" % (self.name, msg)
|
||||
|
||||
def connected(self):
|
||||
if USE_VCMD_MODULE:
|
||||
return self.cmdchnl.connected()
|
||||
|
@ -47,12 +43,13 @@ class VnodeClient(object):
|
|||
if USE_VCMD_MODULE:
|
||||
self.cmdchnl.close()
|
||||
|
||||
def cmd(self, args, wait = True):
|
||||
''' Execute a command on a node and return the status (return code).
|
||||
'''
|
||||
def cmd(self, args, wait=True):
|
||||
"""
|
||||
Execute a command on a node and return the status (return code).
|
||||
"""
|
||||
if USE_VCMD_MODULE:
|
||||
if not self.cmdchnl.connected():
|
||||
raise ValueError, "self.cmdchnl not connected"
|
||||
raise ValueError("self.cmdchnl not connected")
|
||||
tmp = self.cmdchnl.qcmd(args)
|
||||
if not wait:
|
||||
return tmp
|
||||
|
@ -62,19 +59,19 @@ class VnodeClient(object):
|
|||
mode = os.P_WAIT
|
||||
else:
|
||||
mode = os.P_NOWAIT
|
||||
tmp = os.spawnlp(mode, VCMD, VCMD, "-c",
|
||||
self.ctrlchnlname, "-q", "--", *args)
|
||||
tmp = os.spawnlp(mode, VCMD, VCMD, "-c", self.ctrlchnlname, "-q", "--", *args)
|
||||
if not wait:
|
||||
return tmp
|
||||
if tmp:
|
||||
self.warn("cmd exited with status %s: %s" % (tmp, str(args)))
|
||||
logger.warn("cmd exited with status %s: %s" % (tmp, str(args)))
|
||||
return tmp
|
||||
|
||||
def cmdresult(self, args):
|
||||
''' Execute a command on a node and return a tuple containing the
|
||||
exit status and result string. stderr output
|
||||
is folded into the stdout result string.
|
||||
'''
|
||||
"""
|
||||
Execute a command on a node and return a tuple containing the
|
||||
exit status and result string. stderr output
|
||||
is folded into the stdout result string.
|
||||
"""
|
||||
cmdid, cmdin, cmdout, cmderr = self.popen(args)
|
||||
result = cmdout.read()
|
||||
result += cmderr.read()
|
||||
|
@ -82,43 +79,40 @@ class VnodeClient(object):
|
|||
cmdout.close()
|
||||
cmderr.close()
|
||||
status = cmdid.wait()
|
||||
return (status, result)
|
||||
return status, result
|
||||
|
||||
def popen(self, args):
|
||||
if USE_VCMD_MODULE:
|
||||
if not self.cmdchnl.connected():
|
||||
raise ValueError, "self.cmdchnl not connected"
|
||||
raise ValueError("self.cmdchnl not connected")
|
||||
return self.cmdchnl.popen(args)
|
||||
else:
|
||||
cmd = [VCMD, "-c", self.ctrlchnlname, "--"]
|
||||
cmd.extend(args)
|
||||
tmp = subprocess.Popen(cmd, stdin = subprocess.PIPE,
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE)
|
||||
tmp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return tmp, tmp.stdin, tmp.stdout, tmp.stderr
|
||||
|
||||
def icmd(self, args):
|
||||
return os.spawnlp(os.P_WAIT, VCMD, VCMD, "-c", self.ctrlchnlname,
|
||||
"--", *args)
|
||||
return os.spawnlp(os.P_WAIT, VCMD, VCMD, "-c", self.ctrlchnlname, "--", *args)
|
||||
|
||||
def redircmd(self, infd, outfd, errfd, args, wait = True):
|
||||
'''
|
||||
def redircmd(self, infd, outfd, errfd, args, wait=True):
|
||||
"""
|
||||
Execute a command on a node with standard input, output, and
|
||||
error redirected according to the given file descriptors.
|
||||
'''
|
||||
"""
|
||||
if not USE_VCMD_MODULE:
|
||||
raise NotImplementedError
|
||||
if not self.cmdchnl.connected():
|
||||
raise ValueError, "self.cmdchnl not connected"
|
||||
raise ValueError("self.cmdchnl not connected")
|
||||
tmp = self.cmdchnl.redircmd(infd, outfd, errfd, args)
|
||||
if not wait:
|
||||
return tmp
|
||||
tmp = tmp.wait()
|
||||
if tmp:
|
||||
self.warn("cmd exited with status %s: %s" % (tmp, str(args)))
|
||||
logger.warn("cmd exited with status %s: %s" % (tmp, str(args)))
|
||||
return tmp
|
||||
|
||||
def term(self, sh = "/bin/sh"):
|
||||
def term(self, sh="/bin/sh"):
|
||||
cmd = ("xterm", "-ut", "-title", self.name, "-e",
|
||||
VCMD, "-c", self.ctrlchnlname, "--", sh)
|
||||
if "SUDO_USER" in os.environ:
|
||||
|
@ -127,19 +121,20 @@ class VnodeClient(object):
|
|||
os.environ["SUDO_USER"])
|
||||
return os.spawnvp(os.P_NOWAIT, cmd[0], cmd)
|
||||
|
||||
def termcmdstring(self, sh = "/bin/sh"):
|
||||
def termcmdstring(self, sh="/bin/sh"):
|
||||
return "%s -c %s -- %s" % (VCMD, self.ctrlchnlname, sh)
|
||||
|
||||
def shcmd(self, cmdstr, sh = "/bin/sh"):
|
||||
def shcmd(self, cmdstr, sh="/bin/sh"):
|
||||
return self.cmd([sh, "-c", cmdstr])
|
||||
|
||||
def getaddr(self, ifname, rescan = False):
|
||||
def getaddr(self, ifname, rescan=False):
|
||||
if ifname in self._addr and not rescan:
|
||||
return self._addr[ifname]
|
||||
tmp = {"ether": [], "inet": [], "inet6": [], "inet6link": []}
|
||||
cmd = [IP_BIN, "addr", "show", "dev", ifname]
|
||||
cmd = [constants.IP_BIN, "addr", "show", "dev", ifname]
|
||||
cmdid, cmdin, cmdout, cmderr = self.popen(cmd)
|
||||
cmdin.close()
|
||||
|
||||
for line in cmdout:
|
||||
line = line.strip().split()
|
||||
if line[0] == "link/ether":
|
||||
|
@ -152,21 +147,20 @@ class VnodeClient(object):
|
|||
elif line[3] == "link":
|
||||
tmp["inet6link"].append(line[1])
|
||||
else:
|
||||
self.warn("unknown scope: %s" % line[3])
|
||||
else:
|
||||
pass
|
||||
logger.warn("unknown scope: %s" % line[3])
|
||||
|
||||
err = cmderr.read()
|
||||
cmdout.close()
|
||||
cmderr.close()
|
||||
status = cmdid.wait()
|
||||
if status:
|
||||
self.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
|
||||
logger.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
|
||||
if err:
|
||||
self.warn("error output: %s" % err)
|
||||
logger.warn("error output: %s" % err)
|
||||
self._addr[ifname] = tmp
|
||||
return tmp
|
||||
|
||||
def netifstats(self, ifname = None):
|
||||
def netifstats(self, ifname=None):
|
||||
stats = {}
|
||||
cmd = ["cat", "/proc/net/dev"]
|
||||
cmdid, cmdin, cmdout, cmderr = self.popen(cmd)
|
||||
|
@ -195,34 +189,32 @@ class VnodeClient(object):
|
|||
cmderr.close()
|
||||
status = cmdid.wait()
|
||||
if status:
|
||||
self.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
|
||||
logger.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
|
||||
if err:
|
||||
self.warn("error output: %s" % err)
|
||||
logger.warn("error output: %s" % err)
|
||||
if ifname is not None:
|
||||
return stats[ifname]
|
||||
else:
|
||||
return stats
|
||||
|
||||
def createclients(sessiondir, clientcls = VnodeClient,
|
||||
cmdchnlfilterfunc = None):
|
||||
direntries = map(lambda x: os.path.join(sessiondir, x),
|
||||
os.listdir(sessiondir))
|
||||
|
||||
def createclients(sessiondir, clientcls=VnodeClient, cmdchnlfilterfunc=None):
|
||||
direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir))
|
||||
cmdchnls = filter(lambda x: stat.S_ISSOCK(os.stat(x).st_mode), direntries)
|
||||
if cmdchnlfilterfunc:
|
||||
cmdchnls = filter(cmdchnlfilterfunc, cmdchnls)
|
||||
cmdchnls.sort()
|
||||
return map(lambda x: clientcls(os.path.basename(x), x), cmdchnls)
|
||||
|
||||
def createremoteclients(sessiondir, clientcls = VnodeClient,
|
||||
filterfunc = None):
|
||||
''' Creates remote VnodeClients, for nodes emulated on other machines. The
|
||||
|
||||
def createremoteclients(sessiondir, clientcls=VnodeClient, filterfunc=None):
|
||||
"""
|
||||
Creates remote VnodeClients, for nodes emulated on other machines. The
|
||||
session.Broker writes a n1.conf/server file having the server's info.
|
||||
'''
|
||||
direntries = map(lambda x: os.path.join(sessiondir, x),
|
||||
os.listdir(sessiondir))
|
||||
"""
|
||||
direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir))
|
||||
nodedirs = filter(lambda x: stat.S_ISDIR(os.stat(x).st_mode), direntries)
|
||||
nodedirs = filter(lambda x: os.path.exists(os.path.join(x, "server")),
|
||||
nodedirs)
|
||||
nodedirs = filter(lambda x: os.path.exists(os.path.join(x, "server")), nodedirs)
|
||||
if filterfunc:
|
||||
nodedirs = filter(filterfunc, nodedirs)
|
||||
nodedirs.sort()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue