Source code for src.tools.simnet.simnet

#!/usr/bin/python

#
#    Copyright (c) 2015-2017 Nest Labs, Inc.
#    All rights reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#


import os
import sys
import subprocess
import re
import time
try:
    import ipaddress
except ImportError, ex:
    print ex.message
    print 'HINT: You can install the ipaddress package by running "sudo pip install ipaddress"'
    sys.exit(-1)
import shutil
import random
import socket


#===============================================================================
# Global Definitions
#===============================================================================

namePrefix = 'sn-'

defaultIEEEOUI = 0x18B430                           # Nest's OUI
baseMAC48Address = defaultIEEEOUI << 24
baseMAC64Address = defaultIEEEOUI << 40

defaultWeaveFabricId = 1

ulaPrefix = ipaddress.IPv6Network(u'fd00::/8')
llaPrefix = ipaddress.IPv6Network(u'fe80::/64')

defaultHostEntries = '''############################## misc ##############################
127.0.0.1                                localhost
::1                                      ip6-localhost ip6-loopback
fe00::0                                  ip6-localnet
ff00::0                                  ip6-mcastprefix
ff02::1                                  ip6-allnodes
ff02::2                                  ip6-allrouters
127.0.0.1                                %s
''' % (socket.gethostname()) # NOTE: An entry for the hostname is required for certain tools to operate properly (e.g. sudo).

scriptDirName = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))


#===============================================================================
# SimNet Class
#===============================================================================

[docs]class SimNet: def __init__(self): self.networks = [] self.nodes = [] self.additionalHostsEntries = '' self.layoutSearchDirs = [ '.', os.path.join(os.environ['HOME'], '.simnet/layouts'), os.path.join(scriptDirName, 'lib/simnet/layouts'), os.path.join(scriptDirName, '../lib/simnet/layouts'), ]
[docs] def loadLayoutFile(self, layoutFileName): # If the layout file name does *not* include a path component, search for the # file in the configured search directories... if os.path.dirname(layoutFileName) == '': for dirName in self.layoutSearchDirs: f = os.path.join(dirName, layoutFileName) if os.path.exists(f): layoutFileName = f break f = f + '.py' if os.path.exists(f): layoutFileName = f break if not os.path.exists(layoutFileName): raise UsageException("Network layout file not found: %s" % layoutFileName) print "Loading: %s" % (layoutFileName) global networks, nodes networks = self.networks nodes = self.nodes execfile(layoutFileName) for node in self.nodes: node.configureAddresses() for node in self.nodes: node.configureRoutes()
[docs] def buildNetworksAndNodes(self): verifyRoot() global logActions, logIndent logActions = True # If one or more of the nodes is implemented by the host, initialize the host's iptables. if True in [ n.isHostNode for n in self.nodes ]: logAction('Initializing host iptables') logIndent += 1 initSimnetIPTables() logIndent -= 1 for network in self.networks: network.buildNetwork() hostsTable = self.generateHostsTable() for node in self.nodes: node.buildNode(hostsTable) logActions = False
[docs] def clearNetworksAndNodes(self): verifyRoot() global logActions, logIndent logActions = True for node in self.nodes: node.clearNode() for network in self.networks: network.clearNetwork() clearSimnetIPTables() logActions = False
[docs] def summarizeNetwork(self, networks=True, nodes=True, hosts=True, env=True): s = '' if networks: s += 'Networks:\n' for network in self.networks: s += network.summary(prefix=' ') if nodes or env: s += 'Nodes:\n' for node in self.nodes: if nodes: s += node.summary(prefix=' ') else: s += ' Node "%s" (%s):\n' % (node.name, node.__class__.__name__) if env: s += ' Environment:\n' envVars = {} node.setEnvironVars(envVars) for name in sorted(envVars.keys()): s += ' %s: %s\n' % (name, envVars[name]) if hosts: s += 'Hosts Table:\n' s += re.sub('^', ' ', self.generateHostsTable(), flags=re.M) return s
[docs] def startShell(self, node, noOverridePrompt=False): verifyRoot() if not isinstance(node, Node): nodeName = node node = self.findNode(nodeName) if not node: raise UsageException('Unknown node: %s' % nodeName) if not node.isNodeBuilt(): raise UsageException('Node currently not active: %s' % nodeName) shell = os.path.realpath(os.path.abspath(os.environ['SHELL'])) if os.path.basename(shell) == 'bash' and not noOverridePrompt: promptOverride = [ '-c', '''exec $SHELL --rcfile <(cat ~/.bashrc; echo '[ "$SN_NO_OVERRIDE_PROMPT" ] || PS1="\e[1m[\$SN_NODE_NAME]\e[0m $PS1"')''' ] else: promptOverride = [ ] cmd = [ 'su', ] + promptOverride + [ os.environ['SUDO_USER'] ] if not node.isHostNode: cmd = [ 'ip', 'netns', 'exec', node.nsName ] + cmd cmdEnv = os.environ.copy() node.setEnvironVars(cmdEnv) subprocess.call(cmd, env=cmdEnv)
[docs] def clearAll(self): verifyRoot() global logActions, logIndent logActions = True # Clear all node namespaces for ns in getNamespaces(): if ns.startswith(namePrefix): deleteNamespace(ns) # Clear all node namespace directories for name in os.listdir('/etc/netns'): if name.startswith(namePrefix): name = os.path.join('/etc/netns', name) if os.path.isdir(name): print 'Deleting %s' % name shutil.rmtree(name) # Clear all network bridges for b in getBridges(): if b.startswith(namePrefix): deleteBridge(b) # Delete any remaining interfaces for name in [ i['dev'] for i in getInterfaces() ]: if name.startswith(namePrefix): deleteInterface(name) # Clear any simnet iptables configuration on the host. clearSimnetIPTables() # TODO: clear routes # Give a little time for async activities to settle. time.sleep(0.1) logActions = False
[docs] def findNode(self, nodeName): nodeName = nodeName.lower() for node in self.nodes: if node.name.lower() == nodeName: return node return None
[docs] def getHostsEntries(self): hostsEntriesByNode = {} for node in nodes: hostsEntriesByNode[node.name] = node.getHostsEntries() return hostsEntriesByNode
[docs] def generateHostsTable(self, prefix=''): hostsEntriesByNode = self.getHostsEntries() t = '' # Add a set of entries for each host. for nodeName in sorted(hostsEntriesByNode.keys()): t += '%s############################## %s ##############################\n' % (prefix, nodeName) hostsEntries = hostsEntriesByNode[nodeName] for name in sorted(hostsEntries.keys()): addrs = hostsEntries[name] if len(addrs) == 0: continue for a in addrs: if isinstance(a, ipaddress.IPv4Address): t += '%s%-40s %s\n' % (prefix, a, name) for a in addrs: if isinstance(a, ipaddress.IPv6Address): t += '%s%-40s %s\n' % (prefix, a, name) t += '\n' # Add a set of default entries t += re.sub('^', prefix, defaultHostEntries + self.additionalHostsEntries, re.M) return t
#=============================================================================== # Network Classes #===============================================================================
[docs]class Network(): def __init__(self, name): self.name = name self.networkIndex = len(networks) + 1 # NOTE: networkIndexes must be > 0 self.nsName = namePrefix + name self.brName = namePrefix + name self.attachedInterfaces = [] self.ip4Supported = True self.ip6Supported = True self.usesMAC64 = False # Add the new network to the list of networks associated with the active simnet object. networks.append(self)
[docs] def buildNetwork(self): global logIndent logAction('Building network: %s' % self.name) logIndent += 1 try: self._buildNetwork() finally: logIndent -= 1
def _buildNetwork(self): # Create a namespace for the network. addNamespace(self.nsName) # Create a virtual ethernet bridge to simulate the network addBridge(self.brName, nsName=self.nsName) # Enable the host interface for the bridge enableInterface(self.brName, nsName=self.nsName)
[docs] def clearNetwork(self): # Delete the namespace for the network. deleteNamespace(self.nsName)
# Delete the network bridge. # deleteBridge(self.brName)
[docs] def summary(self, prefix=''): s = '%sNetwork "%s" (%s implemented by namespace %s):\n' % (prefix, self.name, self.__class__.__name__, self.nsName) s += '%s Network Index: %d\n' % (prefix, self.networkIndex) s += '%s Virtual Bridge: %s\n' % (prefix, self.brName) return s
[docs] def requestIP4AutoConfig(self, interface): return None
[docs] def requestIP6AutoConfig(self, interface): return []
[docs] def requestIP4AdvertisedRoutes(self, interface): return []
[docs] def requestIP6AdvertisedRoutes(self, interface): return []
[docs] @staticmethod def findNetwork(name, errMsg=''): global networks for n in networks: if n.name == name: return n if errMsg: raise ConfigException(errMsg + 'No such network: %s' % (name)) return None
[docs]class Internet(Network): def __init__(self, ip4Subnet='4.0.0.0/8', ip6Prefix='2000::/3'): Network.__init__(self, 'inet') self.ip4Subnet = toIP4Subnet(ip4Subnet, 'Unable to initialize inet network: ') self.ip6Prefix = toIP6Prefix(ip6Prefix, 'Unable to initialize inet network: ') self.ip4Supported = self.ip4Subnet != None self.ip6Supported = self.ip6Prefix != None self.ip6PrefixRoutes = [ ]
[docs] def requestIP4AutoConfig(self, interface): if interface.network != self: raise ConfigException('Attempt to auto-configure IPv4 interface %s which is not on network %s' % (interface.ifName, self.name)) # Return nothing if an IPv4 subnet has not been specified. if not self.ip4Subnet: return None # Within the IPv4 subnet, return a /32 interface address for the node using the node id in the bottom bits. # This will be the node's externally-facing IPv4 address on the Internet. autoConfig = IP4AutoConfigInfo( address = makeIP4IntefaceAddress(self.ip4Subnet, interface.node.nodeIndex, prefixLen=32), defaultGateway = None, dnsServers = [ ] ) return autoConfig
[docs] def requestIP6AutoConfig(self, interface): if interface.network != self: raise ConfigException('Attempt to auto-configure IPv6 interface %s which is not on network %s' % (interface.ifName, self.name)) # Return nothing if an IPv6 prefix has not been specified. if not self.ip6Prefix: return [] # Form a unique /48 prefix for the node based on its node id. This prefix will be delegated to the node # for further use in numbering internal networks. nodePrefix = makeIP6Prefix(self.ip6Prefix, networkNum=interface.node.nodeIndex, prefixLen=48) # Within the node prefix, form an /128 interface address for the node with a subnet of 0 and an IID based on # the MAC address of the interface. This is the node's externally-facing IPv6 address on the Internet. addr = makeIP6InterfaceAddress(nodePrefix, macAddr=interface.macAddress, prefixLen=128) # Record a global route that directs traffic for the delegated prefix to the node. self.ip6PrefixRoutes.append( IPRoute( dest = nodePrefix, interface = None, via = addr.ip ) ) # Return a single auto-config response containing the node address and delegated prefix. return [ IP6AutoConfigInfo( addresses = [ addr ], delegatedPrefixes = [ nodePrefix ], defaultGateway = None, dnsServers = [ ] ) ]
[docs] def requestIP4AdvertisedRoutes(self, interface): routes = [] if self.ip4Subnet != None: # Add routes.append( IPRoute( dest = self.ip4Subnet, interface = interface, via = None ) ) return routes
[docs] def requestIP6AdvertisedRoutes(self, interface): routes = [] if self.ip6Prefix != None: # Add a global route for the entire Internet that declares it 'on-link' on the requesting # node's interface. routes.append( IPRoute( dest = self.ip6Prefix, interface = interface, via = None ) ) # Add prefix routes for each of the nodes on the Internet, directing traffic destined to internal # networks to the respective gateway. However exclude any routes that point to the node that is # making the route request. nodeAddresses = [ a.ip for a in interface.ip6Addresses ] routes += [ p for p in self.ip6PrefixRoutes if p.via not in nodeAddresses ] return routes
[docs] def summary(self, prefix=''): s = Network.summary(self, prefix) if self.ip4Subnet: s += '%s IPv4 Subnet: %s\n' % (prefix, self.ip4Subnet) if self.ip6Prefix: s += '%s IPv6 Prefix: %s\n' % (prefix, self.ip6Prefix) return s
[docs]class WiFiNetwork(Network): def __init__(self, name): Network.__init__(self, name)
[docs] def requestIP4AutoConfig(self, interface): if interface.network != self: raise ConfigException('Attempt to auto-configure IPv4 interface %s which is not on network %s' % (interface.ifName, self.name)) # For each node attached to the network... for i in self.attachedInterfaces: # Skip the node if it is the same one that is requesting auto-config. if i.node == interface.node: continue # Request auto-config information from the current node. If successful, # return the info to the caller. autoConfig = i.node.requestIP4AutoConfig(interface) if autoConfig: return autoConfig return None
[docs] def requestIP6AutoConfig(self, interface): if interface.network != self: raise ConfigException('Attempt to auto-configure IPv6 interface %s which is not on network %s' % (interface.ifName, self.name)) autoConfigs = [] # For each node attached to the network... for i in self.attachedInterfaces: # Skip the node if it is the same one that is requesting auto-config. if i.node == interface.node: continue # Request auto-config information from the current node. If successful, add the result # to the information collected from other nodes on the network. autoConfig = i.node.requestIP6AutoConfig(interface) if autoConfig: autoConfigs.append(autoConfig) return autoConfigs
[docs] def requestIP4AdvertisedRoutes(self, interface): routes = [] # TODO: implement this return routes
[docs] def requestIP6AdvertisedRoutes(self, interface): routes = [] # TODO: implement this return routes
[docs]class ThreadNetwork(Network): def __init__(self, name, meshLocalPrefix): Network.__init__(self, name) self.meshLocalPrefix = toIP6Prefix(meshLocalPrefix, 'Unable to initialize thread network %s: ' % name) if not self.meshLocalPrefix or self.meshLocalPrefix.prefixlen != 64: raise ConfigException('Invalid mesh local prefix specified for network %s' % name) self.ip4Supported = False self.usesMAC64 = True
[docs] def requestIP6AutoConfig(self, interface): if interface.network != self: raise ConfigException('Attempt to auto-configure IPv6 interface %s which is not on network %s' % (interface.ifName, self.name)) # Form an /64 interface address for the node with an IID derived from the node id. This is the # node's mesh-local address. addr = makeIP6InterfaceAddress(self.meshLocalPrefix, macAddr=interface.macAddress, prefixLen=64, macIs64Bit=True) # Return a single auto-config response containing the node address. return [ IP6AutoConfigInfo( addresses = [ addr ], delegatedPrefixes = [ ], defaultGateway= None, dnsServers = [ ] ) ]
[docs] def summary(self, prefix=''): s = Network.summary(self, prefix) s += '%s Mesh Local Prefix: %s\n' % (prefix, self.meshLocalPrefix) return s
[docs]class LegacyThreadNetwork(Network): def __init__(self, name): Network.__init__(self, name) self.ip4Supported = False self.usesMAC64 = True
[docs]class CellularNetwork(WiFiNetwork): pass
[docs]class HostNetwork(Network): def __init__(self, name): Network.__init__(self, name)
[docs] def buildNetwork(self): # Do nothing. pass
[docs] def clearNetwork(self): # Do nothing. pass
[docs] def summary(self, prefix=''): # Don't show host networks. They only exist to give host interface objects # something to point at. return ''
#=============================================================================== # Node Classes #===============================================================================
[docs]class Node(): def __init__(self, name, isHostNode=False): self.name = name self.isHostNode = isHostNode if isHostNode: self.nsName = None else: self.nsName = namePrefix + name self.nodeIndex = len(nodes) + 1 # NOTE: nodeIndexes must be > 0 self.nextInterfaceIndex = 1 self.configState = "incomplete" self.interfaces = [] self.ip4Enabled = True self.ip4DefaultGateway = None self.ip4DefaultInterface = None self.ip4Routes = [] self.ip4DNSServers = None self.ip6Enabled = True self.ip6DefaultGateway = None self.ip6DefaultInterface = None self.ip6Routes = [] self.ip6DNSServers= None self.useTapInterfaces = False self.hostAliases = [] # Add the new node to the list of nodes associated with the active simnet object. nodes.append(self)
[docs] def addInterface(self, network, name): # If a network name was specified (verses an actual Network object), find the associated Network object. if isinstance(network, str): network = Network.findNetwork(network, 'Unable to add interface to node %s: ' % self.name) existingInterface = None # If the node is implemented by the host and an network has NOT been specified... if self.isHostNode and network == None: # If the specified interface name is 'host-default', substitute in the name of the host's # default interface, as determined by examining the host's default IPv4 route. if name == 'host-default': name = next((r['dev'] for r in getHostRoutes() if r['dest'] == 'default'), None) if not name: raise ConfigException('Unable to determine host default interface') # Search the list of existing host interfaces for one that matches the specified # interface name. If a match is found then this interface will be implemented by the # existing host interface, rather than being a virtual interface. existingInterface = next((i for i in getHostInterfaces() if i['dev'] == name), None) # Construct the appropriate type of interface. if existingInterface: i = ExistingHostInterface(self, existingInterface) elif self.useTapInterfaces: i = TapInterface(self, network, name) else: i = VirtualInterface(self, network, name) return i
[docs] def configureAddresses(self): if self.configState == "complete": return if self.configState == "inprogress": raise ConfigException('Loop detected in automatic address assignment') self.configState = "inprogress" self._configureAddresses() self.configState = "complete"
def _configureAddresses(self): # Perform configuration for each interface... for i in self.interfaces: # If IPv4 is enabled for the interface and the attached network supports IPv4... if i.ip4Enabled and i.network.ip4Supported: # If IPv4 automatic configuration is enabled... if i.autoConfigIP4: # If an IPv4 address has not been assigned query the associated network for # IPv4 auto-config information and configure the interface to used the returned # address. Fail if unsuccessful. if i.ip4Address == None: i.ip4Address = i.requestIP4AutoConfig().address if i.ip4Address == None: raise ConfigException('Unable to automatically assign IPv4 address to node %s, interface %s' % (self.name, i.ifName)) # Auto configure the IPv4 default gateway if necessary. if self.ip4DefaultGateway == None: self.ip4DefaultGateway = i.requestIP4AutoConfig().defaultGateway self.ip4DefaultInterface = i # Auto configure the IPv4 DNS servers if necessary. if self.ip4DNSServers == None: self.ip4DNSServers = i.requestIP4AutoConfig().dnsServers # If the assigned IPv4 interface address is a network address (i.e. the node-specific # bits of the address are all zeros), then assign a host interface address within # specified network based on the node's index. if i.ip4Address != None and isNetworkAddress(i.ip4Address): i.ip4Address = makeIP4IntefaceAddress(i.ip4Address.network, self.nodeIndex) # If IPv6 is enabled for the interface and the attached network supports IPv6... if i.ip6Enabled and i.network.ip6Supported: # Form the interface's link-local address if it hasn't already been specified. if i.ip6LinkLocalAddress == None: i.ip6LinkLocalAddress = makeIP6InterfaceAddress(llaPrefix, macAddr=i.macAddress, macIs64Bit=i.usesMAC64, prefixLen=64) if i.ip6LinkLocalAddress not in i.ip6Addresses: i.ip6Addresses = [ i.ip6LinkLocalAddress ] + i.ip6Addresses # If IPv6 automatic configuration is enabled... if i.autoConfigIP6: # Query the associated network for IPv6 auto-config information. Delegate to the node subclass # to filter the responses. For each remaining response... for autoConfig in self.filterIP6AutoConfig(i, i.requestIP6AutoConfig(i)): # Add the auto-config addresses to the list of addresses for the interface. i.ip6Addresses += autoConfig.addresses # Auto configure the IPv6 default gateway if necessary. if self.ip6DefaultGateway == None: self.ip6DefaultGateway = autoConfig.defaultGateway self.ip6DefaultInterface = i # Auto configure the IPv4 DNS servers if necessary. if self.ip6DNSServers == None: self.ip6DNSServers = autoConfig.dnsServers # Save a list of the prefixes that have been delegated to this interface. if autoConfig.delegatedPrefixes: i.delegatedIP6Prefixes += autoConfig.delegatedPrefixes # Save any further routes associated with the auto-config response if autoConfig.furtherRoutes: self.ip6Routes += autoConfig.furtherRoutes # If any of the assigned IPv6 addresses are network addresses (i.e. the IID is # all zeros), then convert them to host addresses using the interface MAC address # as the IID. for n in range(len(i.ip6Addresses)): a = i.ip6Addresses[n] if isNetworkAddress(a): a = makeIP6InterfaceAddress(a.network, macAddr=i.macAddress, macIs64Bit=i.usesMAC64) i.ip6Addresses[n] = a
[docs] def configureRoutes(self): # Add default routes... if self.ip4DefaultGateway: self.ip4Routes.append( IPRoute( dest = None, interface = self.ip4DefaultInterface, via = self.ip4DefaultGateway ) ) if self.ip6DefaultGateway: self.ip6Routes.append( IPRoute( dest = None, interface = self.ip6DefaultInterface, via = self.ip6DefaultGateway ) ) # Perform route configuration for each interface... for i in self.interfaces: # If IPv4 is enabled for the interface and the attached network supports IPv4 # request advertised IPv4 routes and add them to the node's route table. if i.ip4Enabled and i.network.ip4Supported: self.ip4Routes += i.network.requestIP4AdvertisedRoutes(i) # If IPv6 is enabled for the interface and the attached network supports IPv6 # request advertised IPv6 routes and add them to the node's route table. if i.ip4Enabled and i.network.ip4Supported: self.ip6Routes += i.network.requestIP6AdvertisedRoutes(i)
[docs] def requestIP4AutoConfig(self, interface): return None
[docs] def requestIP6AutoConfig(self, interface): return None
[docs] def requestIP4AdvertisedRoutes(self, interface): return []
[docs] def requestIP6AdvertisedRoutes(self, interface): return []
[docs] def filterIP6AutoConfig(self, interface, autoConfigs): return autoConfigs
[docs] def buildNode(self, hostsTable): global logIndent logAction('Building node: %s' % self.name) logIndent += 1 try: self._buildNode() self.installHostsTable(hostsTable) self.installResolverConfig() finally: logIndent -= 1
def _buildNode(self): # If the node is not implemented by the host... if not self.isHostNode: # Create a network namespace for the node. addNamespace(self.nsName) # Create a /etc/netns directory for the node. etcDirName = os.path.join('/etc/netns', self.nsName) if not os.path.exists(etcDirName): logAction('Making directory: %s' % etcDirName) os.makedirs(etcDirName) # If the node is not implemented by the host, enable its loopback interface. (If the node # is implemented by the host, presumably the loopback interface is already enabled). if not self.isHostNode: enableInterface('lo', nsName=self.nsName) # Build each of the node's interfaces... for i in self.interfaces: i.buildInterface() # Add routes for the node. if not self.useTapInterfaces: for r in self.ip4Routes + self.ip6Routes: addRoute( dest = r.destination, dev = r.interface.ifName if r.interface != None else None, via = r.via, options = r.options, nsName = self.nsName )
[docs] def clearNode(self): # Clear the node's interfaces. if self.isHostNode: for i in self.interfaces: i.clearInterface() # Delete the node's namespace. if not self.isHostNode: deleteNamespace(self.nsName) # Delete the node's namespace /etc directory if not self.isHostNode: etcDirName = os.path.join('/etc/netns', self.nsName) if os.path.isdir(etcDirName): logAction('Deleting directory: %s' % etcDirName) shutil.rmtree(etcDirName)
[docs] def isNodeBuilt(self): if self.isHostNode: return True else: nsList = getNamespaces() return self.nsName in nsList
[docs] def getHostsEntries(self): hostEntries = {} nodeIP4Addrs = [] nodeIP6Addrs = [] for i in self.interfaces: if i.ip4Address: nodeIP4Addrs.append(i.ip4Address.ip) hostEntries[self.name + '-' + i.ifName + '-ip4'] = [ i.ip4Address.ip ] if len(i.ip6Addresses) > 0: addrs = [ a.ip for a in i.ip6Addresses if not a.is_link_local ] nodeIP6Addrs += addrs hostEntries[self.name + '-' + i.ifName + '-ip6'] = preferGlobalAddresses(addrs) # Filter address lists to prefer global addresses over non-global ones. nodeIP4Addrs = preferGlobalAddresses(nodeIP4Addrs) nodeIP6Addrs = preferGlobalAddresses(nodeIP6Addrs) hostEntries[self.name] = nodeIP4Addrs + nodeIP6Addrs for alias in self.hostAliases: hostEntries[alias] = nodeIP4Addrs + nodeIP6Addrs return hostEntries
[docs] def installHostsTable(self, hostsTable): if not self.isHostNode: etcDirName = os.path.join('/etc/netns', self.nsName) hostsFileName = os.path.join(etcDirName, 'hosts') logAction('Installing hosts file: %s' % hostsFileName) with open(hostsFileName, 'w') as f: f.write(hostsTable)
[docs] def installResolverConfig(self): if not self.isHostNode: etcDirName = os.path.join('/etc/netns', self.nsName) resolvConfFileName = os.path.join(etcDirName, 'resolv.conf') logAction('Installing resolv.conf file: %s' % resolvConfFileName) with open(resolvConfFileName, 'w') as f: if self.ip4DNSServers: for a in self.ip4DNSServers: f.write('nameserver %s\n' % a) if self.ip6DNSServers: for a in self.ip6DNSServers: f.write('nameserver %s\n' % a)
[docs] def setEnvironVars(self, environ): environ['SN_NODE_NAME'] = self.name if not self.isHostNode: environ['SN_NAMESPACE'] = self.nsName environ['SN_NODE_INDEX'] = str(self.nodeIndex)
[docs] def getLwIPConfig(self): lwipConfig = '' for i in self.interfaces: if isinstance(i, TapInterface): lwipConfig += i.getLwipConfig() if self.ip4DefaultGateway: lwipConfig += '--ip4-default-gw %s ' % self.ip4DefaultGateway if self.ip4DNSServers: lwipConfig += '--dns-server %s ' % (','.join(self.ip4DNSServers)) # TODO: handle routes return lwipConfig
[docs] def summary(self, prefix=''): s = '%sNode "%s" (%s ' % (prefix, self.name, self.__class__.__name__,) if self.isHostNode: s += 'implemented by host):\n' else: s += 'implemented by namespace %s):\n' % self.nsName s += '%s Node Index: %d\n' % (prefix, self.nodeIndex) s += '%s Interfaces (%d):\n' % (prefix, len(self.interfaces)) for i in self.interfaces: s += i.summary(prefix + ' ') s += '%s IPv4 Routes:%s\n' % (prefix, ' None' if len(self.ip4Routes) == 0 else '') for r in self.ip4Routes: s += r.summary(prefix + ' ') s += '%s IPv6 Routes:%s\n' % (prefix, ' None' if len(self.ip6Routes) == 0 else '') for r in self.ip6Routes: s += r.summary(prefix + ' ') s += '%s IPv4 DNS Servers: %s\n' % (prefix, 'None' if self.ip4DNSServers == None or len(self.ip4DNSServers) == 0 else ', '.join(self.ip4DNSServers)) s += '%s IPv6 DNS Servers: %s\n' % (prefix, 'None' if self.ip6DNSServers == None or len(self.ip6DNSServers) == 0 else ', '.join(self.ip6DNSServers)) return s
[docs]class Gateway(Node): def __init__(self, name, outsideNetwork=None, outsideInterface='outside', outsideIP4Address=None, outsideIP6Addresses=[], insideNetwork=None, insideIP4Subnet=None, insideInterface='inside', insideIP6Subnets=[], isIP4DefaultGateway=True, isIP6DefaultGateway=True, ip4DNSServers=['8.8.8.8'], ip6DNSServers=None, hostAliases=[], useHost=False): Node.__init__(self, name, isHostNode=useHost) if not useHost: if not outsideNetwork: raise ConfigException('Must specify outside network for gateway %s' % (name)) if not insideNetwork: raise ConfigException('Must specify inside network for gateway %s' % (name)) self.outsideInterface = self.addInterface(outsideNetwork, name=outsideInterface) if not isinstance(self.outsideInterface, ExistingHostInterface): if not isinstance(self.outsideInterface.network, Internet) and not isinstance(self.outsideInterface.network, WiFiNetwork): raise ConfigException('Incompatible network %s attached to outside interface of gateway %s' % (outsideNetwork, name)) self.outsideInterface.ip4Address = toIP4InterfaceAddress(outsideIP4Address, 'Unable to assign IPv4 address to node %s, outside interface %s: ' % (name, self.outsideInterface.ifName)) self.outsideInterface.ip6Addresses = [ toIP6InterfaceAddress(a, 'Unable to assign IPv6 address to node %s, outside interface %s: ' % (name, self.outsideInterface.ifName)) for a in outsideIP6Addresses ] self.outsideInterface.autoConfigIP4 = True self.outsideInterface.autoConfigIP6 = (self.outsideInterface.ip6Addresses != None and len(self.outsideInterface.ip6Addresses) == 0) else: if outsideIP4Address != None or len(outsideIP6Addresses) != 0: raise ConfigException('Cannot specify addresses for host interface %s on node %s' % (self.outsideInterface.ifName, name)) self.insideInterface = self.addInterface(insideNetwork, name=insideInterface) if not isinstance(self.insideInterface, ExistingHostInterface): if not isinstance(self.insideInterface.network, WiFiNetwork): raise ConfigException('Incompatible network %s attached to inside interface of gateway %s' % (insideNetwork, name)) self.insideInterface.ip4Address = toIP4InterfaceAddress(insideIP4Subnet, 'Unable to assign IPv4 address to node %s, inside interface %s: ' % (name, self.insideInterface.ifName)) self.insideInterface.ip6Addresses = [ toIP6InterfaceAddress(a, 'Unable to assign IPv6 address to node %s, inside interface %s: ' % (name, self.insideInterface.ifName)) for a in insideIP6Subnets ] self.insideInterface.autoConfigIP4 = False self.insideInterface.autoConfigIP6 = False else: if insideIP4Subnet != None or len(insideIP6Subnets) != 0: raise ConfigException('Cannot specify addresses for host interface %s on node %s' % (self.insideInterface.ifName, name)) self.isIP4DefaultGateway = isIP4DefaultGateway self.isIP6DefaultGateway = isIP6DefaultGateway if self.insideInterface.ip4Address != None and self.insideInterface.ip4Address.network.prefixlen < 32: self.advertisedIP4Subnet = self.insideInterface.ip4Address.network else: self.advertisedIP4Subnet = None if self.insideInterface.ip6Addresses != None: for a in self.insideInterface.ip6Addresses: if a.network.prefixlen < 128: self.insideInterface.advertisedIP6Prefixes.append(a.network) self.ip4DNSServers = ip4DNSServers self.ip6DNSServers = ip6DNSServers self.hostAliases = hostAliases def _configureAddresses(self): Node._configureAddresses(self) if self.insideInterface.ip6Enabled and self.insideInterface.network.ip6Supported: # Assign an address to the inside interface for all prefixes that have been delegated to the outside interface. self.insideInterface.ip6Addresses += [ makeIP6InterfaceAddress(p, subnetNum=0, macAddr=self.insideInterface.macAddress, prefixLen=64) for p in self.outsideInterface.delegatedIP6Prefixes ] # On the inside interface, advertise a /64 prefix for all prefixes that have been delegated to the outside interface. self.insideInterface.advertisedIP6Prefixes += [ makeIP6Prefix(p, prefixLen=64) for p in self.outsideInterface.delegatedIP6Prefixes ]
[docs] def requestIP4AutoConfig(self, interface): if interface.network == self.insideInterface.network and self.advertisedIP4Subnet: return IP4AutoConfigInfo( address = makeIP4IntefaceAddress(self.advertisedIP4Subnet, interface.node.nodeIndex, self.advertisedIP4Subnet.prefixlen), defaultGateway = self.insideInterface.ip4Address.ip if self.isIP4DefaultGateway else None, dnsServers = self.ip4DNSServers ) else: return None
[docs] def requestIP6AutoConfig(self, interface): # If the node hasn't been configured, do it now. This ensures that prefix delegation to the # gateway is complete before we attempt to assign addresses to nodes on the inside network. if self.configState == "incomplete": self.configureAddresses() if interface.network == self.insideInterface.network: return IP6AutoConfigInfo( addresses = [ makeIP6InterfaceAddress(p, macAddr=interface.macAddress, prefixLen=64) for p in self.insideInterface.advertisedIP6Prefixes ], defaultGateway = self.insideInterface.ip6LinkLocalAddress.ip if self.isIP6DefaultGateway else None, furtherRoutes = [ IPRoute( dest = a, interface = interface, via = self.insideInterface.ip6LinkLocalAddress.ip ) for a in self.outsideInterface.ip6Addresses if not a.is_link_local ], dnsServers = self.ip6DNSServers # TODO: return delegated prefixes ) else: return None
[docs] def requestIP6AdvertisedRoutes(self, interface): if interface.network == self.outsideInterface.network: return self.outsideInterface.delegatedIP6Prefixes else: return []
def _buildNode(self): Node._buildNode(self) # If the node is implemented using a namespace (vs the host) initialize the simnet # iptables configuration within the namespace. if not self.isHostNode: initSimnetIPTables(self.nsName) # If the outside interface supports IPv4... if self.outsideInterface.ip4Enabled and self.outsideInterface.ip4Address: # Enable IPv4 NAT between the inside and outside interfaces. enableIP4NAT(self.insideInterface.ifName, self.outsideInterface.ifName, self.nsName) # Enable IPv4 routing for the node setSysctl('net.ipv4.conf.all.forwarding', 1, nsName=self.nsName) # If the outside interface supports IPv6... if self.outsideInterface.ip6Enabled and len(self.outsideInterface.ip6Addresses) > 0: # Enable Ipv6 forwarding between the inside and outside interfaces. enableIP6Forwarding(self.insideInterface.ifName, self.outsideInterface.ifName, self.nsName) # Enable IPv6 routing for the node setSysctl('net.ipv6.conf.all.forwarding', 1, nsName=self.nsName)
[docs] def clearNode(self): Node.clearNode(self) # Clear various configuration if this is a host node... # (No need to do this for namespace nodes, since all configuration is cleared # when the namespace is deleted). if self.isHostNode: # Disable IPv4 routing for the node. setSysctl('net.ipv4.conf.all.forwarding', 0, nsName=self.nsName) # Disable IPv6 routing for the node setSysctl('net.ipv6.conf.all.forwarding', 0, nsName=self.nsName)
[docs] def getHostsEntries(self): hostsEntries = Node.getHostsEntries(self) nameEntries = [] if self.name + '-outside-ip4' in hostsEntries: nameEntries += hostsEntries[self.name + '-outside-ip4'] if self.name + '-outside-ip6' in hostsEntries: nameEntries += hostsEntries[self.name + '-outside-ip6'] if len(nameEntries) > 0: hostsEntries[self.name] = nameEntries return hostsEntries
[docs]class WeaveDevice(Node): def __init__(self, name, wifiNetwork=None, wifiInterface=None, threadNetwork=None, threadInterface=None, legacyNetwork=None, legacyInterface=None, cellularNetwork=None, cellularInterface=None, weaveNodeId=None, weaveFabricId=None, isWeaveBorderGateway=False, advertiseWiFiPrefix=True, hostAliases=[], useLwIP=False, useHost=False): if useHost and useLwIP: raise ConfigException('Cannot specify both useHost and useLwIP for Weave node %s' % (name)) Node.__init__(self, name, isHostNode=useHost) self.weaveNodeId = weaveNodeId if weaveNodeId != None else self.nodeIndex self.weaveFabricId = weaveFabricId if weaveFabricId != None else defaultWeaveFabricId self.fabricAddrGlobalId = self.weaveFabricId % ((1 << 40) - 1) # bottom 40 bits of fabric id self.advertiseWiFiPrefix = advertiseWiFiPrefix self.useLwIP = useLwIP self.useTapInterfaces = useLwIP if wifiNetwork or wifiInterface: if not wifiInterface: wifiInterface = 'wifi' if not useLwIP else 'et0' self.wifiInterface = self.addInterface(wifiNetwork, name=wifiInterface) if not isinstance(self.wifiInterface.network, (WiFiNetwork, HostNetwork)): raise ConfigException('Incompatible network %s attached to wifi interface of node %s' % (self.wifiInterface.network.name, name)) else: self.wifiInterface = None if threadNetwork or threadInterface: if not threadInterface: threadInterface = 'thread' if not useLwIP else 'th0' self.threadInterface = self.addInterface(threadNetwork, name=threadInterface) if not isinstance(self.threadInterface.network, (ThreadNetwork, HostNetwork)): raise ConfigException('Incompatible network %s attached to thread interface of node %s' % (self.threadInterface.network.name, name)) else: self.threadInterface = None if legacyNetwork or legacyInterface: if not legacyInterface: legacyInterface = 'legacy' if not useLwIP else 'al0' self.legacyInterface = self.addInterface(legacyNetwork, name=legacyInterface) if not isinstance(self.legacyInterface.network, (LegacyThreadNetwork, HostNetwork)): raise ConfigException('Incompatible network %s attached to legacy thread interface of node %s' % (self.legacyInterface.network.name, name)) else: self.legacyInterface = None if cellularNetwork or cellularInterface: if not cellularInterface: cellularInterface = 'cell' if not useLwIP else 'cl0' self.cellularInterface = self.addInterface(cellularNetwork, name=cellularInterface) if not isinstance(self.cellularInterface.network, (WiFiNetwork, Internet, HostNetwork)): raise ConfigException('Incompatible network %s attached to cell interface of node %s' % (self.cellularInterface.network.name, name)) else: self.cellularInterface = None self.hostAliases = hostAliases def _configureAddresses(self): Node._configureAddresses(self) if self.wifiInterface: self.wifiWeaveAddr = self.weaveInterfaceAddress(subnetNum=1) self.wifiInterface.ip6Addresses.append(self.wifiWeaveAddr) if self.threadInterface: self.threadMeshAddr = self.threadInterface.ip6Addresses[1] self.threadWeaveAddr = self.weaveInterfaceAddress(subnetNum=6) self.threadInterface.ip6Addresses.append(self.threadWeaveAddr) if self.legacyInterface: self.legacyWeaveAddr = self.weaveInterfaceAddress(subnetNum=2) self.legacyInterface.ip6Addresses.append(self.legacyWeaveAddr)
[docs] def requestIP6AutoConfig(self, interface): if self.wifiInterface and interface.network == self.wifiInterface.network and self.advertiseWiFiPrefix: wifiPrefix = self.weaveSubnetPrefix(subnetNum=1) return IP6AutoConfigInfo( addresses = [ makeIP6InterfaceAddress(wifiPrefix, macAddr=interface.macAddress) ] ) else: return None
[docs] def filterIP6AutoConfig(self, interface, autoConfigs): # On the WiFi interface, only accept auto-config addresses that are global. Among other this, # this prevents Weave nodes in one fabric from picking up addresses from nodes in other fabrics. if interface == self.wifiInterface: for autoConfig in autoConfigs: autoConfig.addresses = [ a for a in autoConfig.addresses if a.is_global ] return autoConfigs
[docs] def weaveSubnetPrefix(self, subnetNum): return makeIP6Prefix(ulaPrefix, networkNum=self.fabricAddrGlobalId, subnetNum=subnetNum, prefixLen=64)
[docs] def weaveInterfaceAddress(self, subnetNum): subnetPrefix = self.weaveSubnetPrefix(subnetNum) # As a convenience to testing, interface addresses for Weave nodes with a node id less than 65536 # are considered 'local', and therefore have their universal/local bit is set to zero. This simplifies # the string representation of the corresponding IPv6 addresses. For example a WiFi ULA for a node # with a node id of 10 would be FD00:0:1:1::A, instead of FD00:0:1:1:0200::A. This behavior matches # the behavior of the C++ Weave code. if self.weaveNodeId < 65536: iid = self.weaveNodeId # 'test' node id, generate 'local' iid else: iid = self.weaveNodeId | (1 << 57) # 'real' node id, generate 'universal' iid a = makeIP6InterfaceAddress(subnetPrefix, iid=iid, prefixLen=64) return a
[docs] def getHostsEntries(self): hostsEntries = { } # Add interface-name specific entries (<name>-<interface>-ip4, -ip6, -weave, -mesh). if self.wifiInterface: if self.wifiInterface.ip4Address: hostsEntries[self.name + '-wifi-ip4'] = [ self.wifiInterface.ip4Address.ip ] hostsEntries[self.name + '-wifi-ip6'] = [ a.ip for a in self.wifiInterface.ip6Addresses if a.is_global ] hostsEntries[self.name + '-wifi-weave'] = [ self.wifiWeaveAddr.ip ] if self.threadInterface: hostsEntries[self.name + '-thread-weave'] = [ self.threadWeaveAddr.ip ] hostsEntries[self.name + '-thread-mesh'] = [ self.threadMeshAddr.ip ] if self.legacyInterface: hostsEntries[self.name + '-legacy-weave'] = [ self.legacyWeaveAddr.ip ] if self.cellularInterface: if self.cellularInterface.ip4Address: hostsEntries[self.name + '-cell-ip4'] = [ self.cellularInterface.ip4Address.ip ] hostsEntries[self.name + '-cell-ip6'] = [ a.ip for a in self.cellularInterface.ip6Addresses if a.is_global ] # Add an entry for the primary name of the node (<name>). primaryAddrs = [] if self.wifiInterface: if self.wifiInterface.ip4Address: primaryAddrs.append(self.wifiInterface.ip4Address.ip) primaryAddrs += [ a.ip for a in self.wifiInterface.ip6Addresses if a.is_global ] elif self.threadInterface: primaryAddrs.append(self.threadWeaveAddr.ip) elif self.cellularInterface: if self.cellularInterface.ip4Address: primaryAddrs.append(self.cellularInterface.ip4Address.ip) primaryAddrs += [ a.ip for a in self.cellularInterface.ip6Address if a.is_global ] elif self.legacyInterface: primaryAddrs.append(self.legacyWeaveAddr.ip) hostsEntries[self.name] = primaryAddrs for alias in self.hostAliases: hostsEntries[alias] = primaryAddrs # Add an entry for the primary weave name of the node (<name>-weave). primaryWeaveAddr = None if self.wifiInterface: primaryWeaveAddr = self.wifiWeaveAddr elif self.threadInterface: primaryWeaveAddr = self.threadWeaveAddr elif self.legacyInterface: primaryWeaveAddr = self.legacyWeaveAddr if primaryWeaveAddr: hostsEntries[self.name + '-weave'] = [ primaryWeaveAddr.ip ] return hostsEntries
[docs] def setEnvironVars(self, environ): Node.setEnvironVars(self, environ) weaveConfig = '--node-id %016X ' % self.weaveNodeId weaveConfig += '--fabric-id %016X ' % self.weaveFabricId if self.wifiInterface: weaveConfig += '--default-subnet 1 ' elif self.threadInterface: weaveConfig += '--default-subnet 6 ' elif self.legacyInterface: weaveConfig += '--default-subnet 2 ' if self.useLwIP: weaveConfig += self.getLwIPConfig() environ['WEAVE_CONFIG'] = weaveConfig
[docs] def summary(self, prefix=''): s = Node.summary(self, prefix) s += '%s Weave Node Id: %016X\n' % (prefix, self.weaveNodeId) s += '%s Weave Fabric Id: %016X\n' % (prefix, self.weaveFabricId) return s
[docs]class SimpleNode(Node): def __init__(self, name, network, ip4Address=None, ip6Addresses=[], autoConfigIP4=True, autoConfigIP6=True, useHost=False, hostAliases=[]): Node.__init__(self, name, isHostNode=useHost) i = self.addInterface(network, name='eth0') if not isinstance(i.network, (WiFiNetwork, Internet)): raise ConfigException('Incompatible network %s attached to interface of node %s' % (i.network.name, name)) i.ip4Address = toIP4InterfaceAddress(ip4Address, 'Unable to assign IPv4 address to node %s, interface %s: ' % (name, i.ifName)) i.ip6Addresses = [ toIP6InterfaceAddress(a, 'Unable to assign IPv6 address to node %s, interface %s: ' % (name, i.ifName)) for a in ip6Addresses ] i.autoConfigIP4 = autoConfigIP4 i.autoConfigIP6 = autoConfigIP6 self.hostAliases = hostAliases
#=============================================================================== # NodeInterface Classes #===============================================================================
[docs]class NodeInterface(): def __init__(self, node, network, name): self.node = node self.network = network self.ifIndex = len(node.interfaces) if len(name) > 15: raise ConfigException('Interface name for node %s is too long: %s' % (node.name, name)) self.ifName = name self.ip4Enabled = True self.ip6Enabled = True self.autoConfigIP4 = True self.autoConfigIP6 = True self.ip4Address = None self.ip6Addresses = [] self.ip6LinkLocalAddress = None self.delegatedIP6Prefixes = [] self.advertisedIP4Subnet = None self.advertisedIP6Prefixes = [] self.ip4AutoConfig = None self.ip6AutoConfig = None self.isTapInterface = False # Form a MAC address for the interface from the base MAC address, the node index and the interface index. # Use a 48-bit or 64-bit MAC based on the nature of the connected network. self.macAddress = (baseMAC64Address if network.usesMAC64 else baseMAC48Address) + node.nodeIndex * 16 + self.ifIndex self.usesMAC64 = network.usesMAC64 # Attach the interface to the node and to the network. node.interfaces.append(self) network.attachedInterfaces.append(self)
[docs] def requestIP4AutoConfig(self): if self.ip4AutoConfig == None: self.ip4AutoConfig = self.network.requestIP4AutoConfig(self) if self.ip4AutoConfig == None: self.ip4AutoConfig = IP4AutoConfigInfo() return self.ip4AutoConfig
[docs] def requestIP6AutoConfig(self, interface): if self.ip6AutoConfig == None: self.ip6AutoConfig = self.network.requestIP6AutoConfig(self) return self.ip6AutoConfig
[docs] def summary(self, prefix=''): s = '%s%s (%s):\n' % (prefix, self.ifName, self.typeSummary()) s += '%s MAC Address: %012X\n' % (prefix, self.macAddress) if self.ip4Enabled: s += '%s IPv4 Address: %s\n' % (prefix, self.ip4Address if self.ip4Address else 'None') if self.ip6Enabled: s += '%s IPv6 Addresses:%s\n' % (prefix, ('' if (self.ip6Addresses != None and len(self.ip6Addresses) > 0) else ' None')) for a in self.ip6Addresses: s += '%s %s\n' % (prefix, a) return s
[docs]class VirtualInterface(NodeInterface): def __init__(self, node, network, name): # If the node is implemented by the host, include the node index in the interface name to # ensure it is unique. if node.isHostNode: name = name + '-' + str(node.nodeIndex) # Initialize the base class. NodeInterface.__init__(self, node, network, name) # Form the peer interface name from the network, node and interface indexes. (Note that interface # names are limited to 16 characters, making it difficult to use a more intuitive naming scheme here). self.peerIfName = namePrefix + str(network.networkIndex) + '-' + str(node.nodeIndex) + '-' + str(self.ifIndex)
[docs] def buildInterface(self): # Create a pair of virtual ethernet interfaces for connecting the node to the target network. # Use a temporary name for the first interface. tmpInterfaceName = namePrefix + 'tmp-' + str(os.getpid()) + str(random.randint(0, 100)) createVETHPair(tmpInterfaceName, self.peerIfName) # Set the MAC address of the node interface. If we are simulating an interface that uses a 64-bit # MAC, form a MAC-48 approximation of the 64-bit value. if self.usesMAC64: macAddress = ((self.macAddress & 0xFFFFFF0000000000) >> 16) | (self.macAddress & 0xFFFFFF) else: macAddress = self.macAddress setInterfaceMACAddress(tmpInterfaceName, macAddress) # Attach the second interface (the peer interface) to the network bridge. moveInterfaceToNamespace(self.peerIfName, nsName=self.network.nsName) attachInterfaceToBridge(self.peerIfName, self.network.brName, nsName=self.network.nsName) # Assign the first interface to the node namespace and rename it to the appropriate name. if not self.node.isHostNode: moveInterfaceToNamespace(tmpInterfaceName, nsName=self.node.nsName) renameInterface(tmpInterfaceName, self.ifName, nsName=self.node.nsName) # Enable the virtual interfaces. enableInterface(self.ifName, nsName=self.node.nsName) enableInterface(self.peerIfName, nsName=self.network.nsName) # Flush any IPv6 link-local addresses automatically created by the network. The appropriate # LL addresses will be added later (see below). runCmd([ 'ip', '-6', 'addr', 'flush', 'dev', self.ifName, 'scope', 'link' ], nsName=self.node.nsName, errMsg='Unable to flush IPv6 LL addresses on interface %s in namespace %s' % (self.ifName, self.node.nsName)) # Assign the IPv4 address to the node interface. if self.ip4Address != None: assignAddressToInterface(self.ifName, self.ip4Address, nsName=self.node.nsName) # Assign the IPv6 addresses to the node interface. if self.ip6Addresses != None: for a in self.ip6Addresses: try: assignAddressToInterface(self.ifName, a, nsName=self.node.nsName) except CommandException, ex: if 'RTNETLINK answers: File exists' not in ex.message: raise
[docs] def clearInterface(self): # Delete the virtual interfaces. deleteInterface(self.ifName, nsName=self.node.nsName) deleteInterface(self.peerIfName, nsName=self.network.nsName)
[docs] def typeSummary(self): return 'virtual interface to network "%s" via peer interface %s' % (self.network.name, self.peerIfName)
[docs]class TapInterface(NodeInterface): def __init__(self, node, network, name=None): # If the node is implemented by the host, include the node index in the interface name to # ensure it is unique. if node.isHostNode: name = name + '-' + str(node.nodeIndex) # Initialize the base class. NodeInterface.__init__(self, node, network, name) # Form the name of the tap device from the node index and the name of the network to which it is attached. self.tapDevName = namePrefix + network.name + '-' + str(node.nodeIndex)
[docs] def buildInterface(self): # Create the tap device # TODO: set owner and group createTapInterface(self.tapDevName) # Attach the tap interface to the network bridge. moveInterfaceToNamespace(self.tapDevName, nsName=self.network.nsName) attachInterfaceToBridge(self.tapDevName, self.network.brName, nsName=self.network.nsName) # Enable the tap interface. enableInterface(self.tapDevName, nsName=self.network.nsName)
[docs] def clearInterface(self): # Delete the tap device. deleteInterface(self.tapDevName, nsName=self.network.nsName)
[docs] def typeSummary(self): return 'tap device connected to network "%s" via interface %s' % (self.network.name, self.tapDevName)
[docs] def getLwIPConfig(self): lwipConfig = '--%s-tap-device %s ' % (i.ifName, i.tapDevName) lwipConfig += '--%s-mac-addr %012X ' % (i.ifName, i.macAddress) # TODO: handle case where MAC is 64-bits if i.ip4Enabled and i.ip4Address: lwipConfig += '--%s-ip4-addr %s ' % (i.ifName, i.ip4Address) if i.ip6Enabled and len(i.ip6Addresses) > 0: lwipConfig += '--%s-ip6-addrs %s ' % (i.ifName, ','.join([ str(a) for a in i.ip6Addresses ])) return lwipConfig;
[docs]class ExistingHostInterface(NodeInterface): def __init__(self, node, hostInterfaceConfig): # Use the existing interface's name. name = hostInterfaceConfig['dev'] # Construct a HostNetwork object to represent the network to which the host interface is attached. # Note that this is just a place-holder object so the interface object has something to point to. network = HostNetwork(name + '-net') # Initialize the base class. NodeInterface.__init__(self, node, network, name) # Use the MAC address associated with the existing interface. self.macAddress = parseMACAddress(hostInterfaceConfig['address']) # Disable auto configuration of addresses. self.autoConfigIP4 = False self.autoConfigIP6 = False # Use the host interface's IPv4 address, if any. self.ip4Address = next(iter(getInterfaceAddresses(self.ifName, ipv6=False)), None) # Search for a IPv6 link-local on the host interface. If found, use it instead of creating a new one. # If not found, disable IPv6 on the interface. ip6LinkLocalAddress = next((a for a in getInterfaceAddresses(self.ifName, ipv6=True) if a.is_link_local), None) if ip6LinkLocalAddress: self.ip6LinkLocalAddress = ip6LinkLocalAddress else: self.ip6Enabled = False
[docs] def buildInterface(self): # Assign the IPv6 addresses to the host interface, excluding the link-local address, which is # presumed to already exist. if self.ip6Enabled and self.ip6Addresses != None: for a in self.ip6Addresses: if a != self.ip6LinkLocalAddress: assignAddressToInterface(self.ifName, a, nsName=self.node.nsName)
[docs] def clearInterface(self): # Remove all assigned IPv6 addresses from the host interface, excluding the link-local # address, which existed on the interface to begin with. if self.ip6Enabled and self.ip6Addresses != None: for a in self.ip6Addresses: if a != self.ip6LinkLocalAddress: removeAddressFromInterface(self.ifName, a, nsName=self.node.nsName)
[docs] def typeSummary(self): return 'existing host interface'
#=============================================================================== # Utility Classes #===============================================================================
[docs]class IP4AutoConfigInfo: def __init__(self, address=None, defaultGateway=None, dnsServers=[]): self.address = address self.defaultGateway = defaultGateway self.dnsServers = dnsServers
[docs]class IP6AutoConfigInfo: def __init__(self, addresses=[], delegatedPrefixes=None, defaultGateway=None, dnsServers=[], furtherRoutes=[]): self.addresses = addresses self.delegatedPrefixes = delegatedPrefixes self.defaultGateway = defaultGateway self.dnsServers = dnsServers self.furtherRoutes = furtherRoutes
[docs]class IPRoute: def __init__(self, dest, interface=None, via=None, options=None): self.destination = dest self.interface = interface self.via = via self.options = options
[docs] def summary(self, prefix=''): s = '%s%s' % (prefix, self.destination if self.destination != None else 'default') if self.interface: s += ' interface %s' % (self.interface.ifName) if self.via: s += ' via %s' % (self.via) if self.options: for option in self.options.keys(): s += ' %s %s' % (option, self.options[option]) s += '\n' return s
[docs]class CommandException(Exception): pass
[docs]class ConfigException(Exception): pass
[docs]class UsageException(Exception): pass
#=============================================================================== # Utility Functions #===============================================================================
[docs]def runCmd(cmd, errMsg='', ignoreErr=False, nsName=None): if nsName: cmd = [ 'ip', 'netns', 'exec', nsName ] + cmd if errMsg != '': errMsg += ' in namespace %s' % (nsName) if errMsg != '': errMsg += '\n' logAction('Running: ' + ' '.join(cmd)) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = p.communicate() if p.returncode != 0 and not ignoreErr: err = err.strip() err = re.sub('^', '>> ', err) raise CommandException(errMsg + 'Command failed: %s\n%s' % (' '.join(cmd), err)) return out
[docs]def getBridges(nsName=None): out = runCmd([ 'brctl', 'show' ], nsName=nsName, errMsg='Failed to enumerate network bridges') for line in re.split('\n', out): if line.startswith('bridge name'): continue m = re.match('\S+', line) if m: yield m.group(0)
[docs]def addBridge(name, nsName=None): runCmd([ 'brctl', 'addbr', name ], nsName=nsName, errMsg='Failed to create bridge %s' % (name))
[docs]def deleteBridge(name, nsName=None): try: runCmd([ 'ip', 'link', 'set', name, 'down' ], nsName=nsName, errMsg='Failed to disable host interface for network bridge %s' % name) runCmd([ 'brctl', 'delbr', name ], nsName=nsName, errMsg='Failed to delete network bridge %s' % name) except Exception, ex: if not 'Cannot find device' in ex.message: raise
[docs]def getNamespaces(): out = runCmd([ 'ip', 'netns', 'list' ], errMsg='Failed to enumerate network namespaces') for line in re.split('\n', out): line = line.strip() if len(line) == 0: continue yield line
[docs]def addNamespace(name): runCmd([ 'ip', 'netns', 'add', name ], errMsg='Failed to create network namespace %s' % (name))
[docs]def deleteNamespace(name): try: runCmd([ 'ip', 'netns', 'del', name ], errMsg='Failed to delete network namespace %s' % name) except Exception, ex: if not 'No such file or directory' in ex.message: raise
[docs]def enableInterface(name, nsName=None): runCmd([ 'ip', 'link', 'set', name, 'up' ], errMsg='Failed to enable interface %s' % (name), nsName=nsName)
[docs]def disableInterface(name, nsName=None): runCmd([ 'ip', 'link', 'set', name, 'down' ], errMsg='Failed to disable interface %s' % (name), nsName=nsName)
[docs]def createVETHPair(name, peerName): runCmd([ 'ip', 'link', 'add', 'name', name, 'type', 'veth', 'peer', 'name', peerName ], errMsg='Unable to create virtual ethernet interface pair %s' % (name)) time.sleep(0.01)
[docs]def createTapInterface(name, user='root', group='root'): runCmd([ 'tunctl', '-u', user, '-g', group, '-t', name], errMsg='Unable to create tap interface pair %s' % (name))
[docs]def moveInterfaceToNamespace(ifName, nsName=None): runCmd([ 'ip', 'link', 'set', ifName, 'netns', nsName ], errMsg='Unable to assign virtual ethernet interface %s to namespace %s' % (ifName, nsName))
[docs]def renameInterface(name, newName, nsName=None): runCmd([ 'ip', 'link', 'set', name, 'name', newName ], errMsg='Unable to rename interface %s to %s' % (name, newName), nsName=nsName)
[docs]def deleteInterface(name, nsName=None): try: runCmd([ 'ip', 'link', 'del', name, ], errMsg='Unable to delete interface %s' % (name), nsName=nsName) except Exception, ex: if not 'Cannot find device' in ex.message and not 'Cannot open network namespace: No such file or directory' in ex.message: raise
[docs]def assignAddressToInterface(ifName, addr, nsName=None): runCmd([ 'ip', 'addr', 'add', str(addr), 'dev', ifName ], errMsg='Unable to assign address %s to interface %s in namespace %s' % (addr, ifName, nsName), nsName=nsName)
[docs]def removeAddressFromInterface(ifName, addr, nsName=None): runCmd([ 'ip', 'addr', 'del', str(addr), 'dev', ifName ], errMsg='Unable to remove address %s from interface %s in namespace %s' % (addr, ifName, nsName), nsName=nsName)
[docs]def setInterfaceMACAddress(ifName, macAddr, nsName=None): macAddr = macAddressToString(macAddr) runCmd([ 'ip', 'link', 'set', 'dev', ifName, 'address', macAddr ], nsName=nsName, errMsg='Unable to set MAC address for interface %s in namespace %s' % (ifName, nsName))
[docs]def attachInterfaceToBridge(ifName, brName, nsName=None): runCmd([ 'brctl', 'addif', brName, ifName ], nsName=nsName, errMsg='Unable to assign virtual ethernet interface %s to bridge %s' % (ifName, brName))
[docs]def getInterfaces(nsName=None): out = runCmd([ 'ip', '-o', 'link', 'show' ], errMsg='Failed to enumerate interfaces', nsName=nsName) out = out.replace('\\', '') # move backslashes introduced by -o interfaces = [] for m in re.finditer('^\d+:\s+(\S+):\s+<([^>]*)>(.*)$', out, re.M): i = { 'dev' : m.group(1), } for flag in m.group(2).split(','): i[flag] = True attrs = m.group(3) for m2 in re.finditer('\s*(\S+)\s+(\S+)', attrs): name = m2.group(1) if name.startswith('link/'): name = 'address' if name != 'alias': value = m2.group(2) else: value = attrs[m2.start(2):] i[name] = value interfaces.append(i) return interfaces
hostInterfaces = None
[docs]def getHostInterfaces(): global hostInterfaces if not hostInterfaces: hostInterfaces = getInterfaces() return hostInterfaces
[docs]def getInterfaceAddresses(dev, ipv6=False, nsName=None): cmd = [ 'ip', '-o' ] if ipv6: cmd.append('-6') else: cmd.append('-4') cmd += [ 'addr', 'list', 'dev', str(dev) ] out = runCmd(cmd, errMsg='Failed to enumerate address for interface %s' % dev, nsName=nsName) addrs = [ m.group(1) for m in re.finditer('^\d+:\s+\S+\s+inet6?\s+(\S+)', out, re.M) ] if ipv6: addrs = [ ipaddress.IPv6Interface(unicode(a)) for a in addrs ] else: addrs = [ ipaddress.IPv4Interface(unicode(a)) for a in addrs ] return addrs
[docs]def addRoute(dest, dev=None, via=None, options=None, nsName=None): cmd = [ 'ip' ] if isinstance(dest, ipaddress.IPv6Address) or isinstance(dest, ipaddress.IPv6Network): cmd.append('-6') if dest == None: dest = 'default' cmd += [ 'route', 'add', 'to', str(dest) ] if dev != None: cmd += [ 'dev', str(dev) ] if via != None: cmd += [ 'via', str(via) ] if options != None: for option in options.keys(): cmd += [ option, str(options[option]) ] runCmd(cmd, errMsg='Unable to add route in namespace %s' % (nsName), nsName=nsName)
[docs]def getRoutes(ipv6=False, nsName=None): cmd = [ 'ip', '-o' ] if ipv6: cmd.append('-6') cmd += [ 'route', 'list', ] out = runCmd(cmd, errMsg='Unable to list routes in namespace %s' % (nsName), nsName=nsName) routes = [] for routeLine in out.split('\n'): m = re.match('\s*(\S+)\s+(.*)$', routeLine) if m: dest = m.group(1) args = m.group(2) route = { 'dest': dest } for m in re.finditer('\s*(\S+)\s+(\S+)', args): route[m.group(1)] = m.group(2) routes.append(route) return routes
hostIP6Routes = None hostIP4Routes = None
[docs]def getHostRoutes(ipv6=False): if ipv6: global hostIP6Routes if not hostIP6Routes: hostIP6Routes = getRoutes(ipv6=True) return hostIP6Routes else: global hostIP4Routes if not hostIP4Routes: hostIP4Routes = getRoutes(ipv6=False) return hostIP4Routes
hostIPTablesInitialized = False
[docs]def initSimnetIPTables(nsName=None): # Only initialize the simnet configuration for the host once. if nsName == None: global hostIPTablesInitialized if hostIPTablesInitialized: return hostIPTablesInitialized = True # Create the simnet IPv4 FORWARD chain and add it to the default FORWARD chain. runCmd([ 'iptables', '-N', namePrefix + 'FORWARD' ], nsName=nsName, errMsg='Unable to create simnet iptables FORWARD chain in namespace %s' % nsName) runCmd([ 'iptables', '-A', 'FORWARD', '-j', namePrefix + 'FORWARD' ], nsName=nsName, errMsg='Unable to add simnet iptables FORWARD chain to default FORWARD chain in namespace %s' % nsName) # Set the policy on the default IPv4 FORWARD chain to DROP. runCmd([ 'iptables', '-P', 'FORWARD', 'DROP' ], nsName=nsName, errMsg='Unable to set polify on default iptables FORWARD chain in namespace %s' % nsName) # Create the simnet IPv4 NAT POSTROUTING chain and it to the default NAT POSTROUTING chain. runCmd([ 'iptables', '-t', 'nat', '-N', namePrefix + 'POSTROUTING' ], nsName=nsName, errMsg='Unable to simnet iptables chain in namespace %s' % nsName) runCmd([ 'iptables', '-t', 'nat', '-A', 'POSTROUTING', '-j', namePrefix + 'POSTROUTING' ], nsName=nsName, errMsg='Unable to add simnet iptables POSTROUTING chain to default POSTROUTING chain in namespace %s' % nsName) # Create the simnet IPv6 FORWARD chain and add it to the default FORWARD chain. runCmd([ 'ip6tables', '-N', namePrefix + 'FORWARD' ], nsName=nsName, errMsg='Unable to create simnet ip6tables FORWARD chain in namespace %s' % nsName) runCmd([ 'ip6tables', '-A', 'FORWARD', '-j', namePrefix + 'FORWARD' ], nsName=nsName, errMsg='Unable to add simnet ip6tables FORWARD chain to default FORWARD chain in namespace %s' % nsName) # Set the policy on the default IPv6 FORWARD chains to DROP. runCmd([ 'ip6tables', '-P', 'FORWARD', 'DROP' ], nsName=nsName, errMsg='Unable to set polify on default ip6tables FORWARD chain in namespace %s' % nsName)
hostIPTablesCleared = False
[docs]def clearSimnetIPTables(nsName=None): # Only clear the simnet configuration for the host once. if nsName == None: global hostIPTablesCleared if hostIPTablesCleared: return hostIPTablesCleared = True # Remove the simnet IPv4 FORWARD chain from the default FORWARD chain, flush it and delete it. runCmd([ 'iptables', '-D', 'FORWARD', '-j', namePrefix + 'FORWARD' ], nsName=nsName, ignoreErr=True) runCmd([ 'iptables', '-F', namePrefix + 'FORWARD' ], nsName=nsName, ignoreErr=True) runCmd([ 'iptables', '-X', namePrefix + 'FORWARD' ], nsName=nsName, ignoreErr=True) # Remove the simnet IPv6 FORWARD chain from the default FORWARD chain, flush it and delete it. runCmd([ 'ip6tables', '-D', 'FORWARD', '-j', namePrefix + 'FORWARD' ], nsName=nsName, ignoreErr=True) runCmd([ 'ip6tables', '-F', namePrefix + 'FORWARD' ], nsName=nsName, ignoreErr=True) runCmd([ 'ip6tables', '-X', namePrefix + 'FORWARD' ], nsName=nsName, ignoreErr=True) # Remove the simnet IPv4 NAT POSTROUTING chain from the default NAT POSTROUTING chain runCmd([ 'iptables', '-t', 'nat', '-D', 'POSTROUTING', '-j', namePrefix + 'POSTROUTING' ], nsName=nsName, ignoreErr=True) runCmd([ 'iptables', '-t', 'nat', '-F', namePrefix + 'POSTROUTING' ], nsName=nsName, ignoreErr=True) runCmd([ 'iptables', '-t', 'nat', '-X', namePrefix + 'POSTROUTING' ], nsName=nsName, ignoreErr=True)
[docs]def enableIP4NAT(insideIfName, outsideIfName, nsName=None): # Enable MASQUERADE for IPv4 packets leaving the outside interface. runCmd([ 'iptables', '-t', 'nat', '-A', namePrefix + 'POSTROUTING', '-o', outsideIfName, '-j', 'MASQUERADE' ], nsName=nsName, errMsg='Unable to enable IPv4 NAT in namespace %s' % nsName) # Allow IPv4 packets to pass from the inside interface to the outside interface. runCmd([ 'iptables', '-A', namePrefix + 'FORWARD', '-i', insideIfName, '-o', outsideIfName, '-j', 'ACCEPT' ], nsName=nsName, errMsg='Unable to enable IPv4 NAT in namespace %s' % nsName) # Allow returning IPv4 packets to pass from the outside interface to the inside interface if they are part # of an established conntrack session. runCmd([ 'iptables', '-A', namePrefix + 'FORWARD', '-i', outsideIfName, '-o', insideIfName, '-m', 'state', '--state', 'RELATED,ESTABLISHED', '-j', 'ACCEPT' ], nsName=nsName, errMsg='Unable to enable IPv4 NAT in namespace %s' % nsName)
[docs]def enableIP6Forwarding(insideIfName, outsideIfName, nsName=None): # Allow IPv6 packets to pass from the inside interface to the outside interface. runCmd([ 'ip6tables', '-A', namePrefix + 'FORWARD', '-i', insideIfName, '-o', outsideIfName, '-j', 'ACCEPT' ], nsName=nsName, errMsg='Unable to enable IPv6 forwarding in namespace %s' % nsName) # Allow return IPv6 packets to pass from the outside interface to the inside interface if they are part # of an established conntrack session. runCmd([ 'ip6tables', '-A', namePrefix + 'FORWARD', '-i', outsideIfName, '-o', insideIfName, '-m', 'state', '--state', 'RELATED,ESTABLISHED', '-j', 'ACCEPT' ], nsName=nsName, errMsg='Unable to enable IPv6 forwarding in namespace %s' % nsName)
[docs]def setSysctl(name, value, nsName=None): runCmd([ 'sysctl', '-q', name + '=' + str(value) ], "Unabled to set sysctl value %s in namespace %s" % (name, nsName), nsName=nsName)
[docs]def toIP4InterfaceAddress(v, errStr): if v == None or isinstance(v, ipaddress.IPv4Interface): return v vStr = str(v) if vStr.find('/') == -1: vStr = vStr + '/32' try: v = ipaddress.IPv4Interface(unicode(vStr)) except ValueError: raise ConfigException('%sSpecified IPv4 interface address is invalid: %s' % (errStr, v)) return v
[docs]def toIP6InterfaceAddress(v, errStr): if v == None or isinstance(v, ipaddress.IPv6Interface): return v vStr = str(v) if vStr.find('/') == -1: vStr = vStr + '/32' try: v = ipaddress.IPv6Interface(unicode(vStr)) except ValueError: raise ConfigException('%sSpecified IPv6 interface address is invalid: %s' % (errStr, v)) return v
[docs]def toIP4Subnet(v, errStr): if v == None or isinstance(v, ipaddress.IPv4Network): return v try: v = ipaddress.IPv4Network(unicode(v)) except ValueError: raise ConfigException('%sSpecified IPv4 subnet is invalid: %s' % (errStr, v)) return v
[docs]def toIP6Prefix(v, errStr): if v == None or isinstance(v, ipaddress.IPv6Network): return v try: v = ipaddress.IPv6Network(unicode(v)) except ValueError: raise ConfigException('%sSpecified IPv6 prefix is invalid: %s' % (errStr, v)) return v
[docs]def makeIP4IntefaceAddress(subnet, nodeIndex, prefixLen=-1): return ipaddress.IPv4Interface((subnet[nodeIndex], prefixLen if prefixLen >= 0 else subnet.prefixlen))
[docs]def makeIP6InterfaceAddress(prefix, subnetNum=0, iid=None, macAddr=None, prefixLen=-1, macIs64Bit=False): # If IID not specified, derive it from the MAC address. if not iid: # If necessary, convert MAC-48 to MAC-64 according per EUI64 spec if not macIs64Bit: macAddr = ((macAddr & 0xFFFFFF000000) << 16) | 0xFFFE000000 | (macAddr & 0xFFFFFF) # Convert MAC address to IPv6 IID by inverting the u bit per rfc-4291. iid = macAddr ^ (1 << 57) # Retain the prefix's length if not specified. if prefixLen < 0: prefixLen = prefix.prefixlen # Compute the host address. addr = prefix[ (subnetNum << 64) + iid ] # Return the host address with the network prefix. return ipaddress.IPv6Interface((addr, prefixLen))
[docs]def makeIP6Prefix(basePrefix, networkNum=0, subnetNum=0, prefixLen=48): return ipaddress.IPv6Network((basePrefix[(networkNum << 80) + (subnetNum << 64)], prefixLen))
[docs]def isNetworkAddress(addr): return addr.network.prefixlen < addr.network.max_prefixlen and addr.ip == addr.network.network_address
[docs]def containsGlobalAddress(addrList): for a in addrList: if not a.is_private: return True return False
[docs]def preferGlobalAddresses(addrList): if containsGlobalAddress(addrList): return [ a for a in addrList if not a.is_private ] else: return addrList
[docs]def verifyRoot(): if os.geteuid() != 0: raise UsageException('Operation not permitted: This command requires root privileges.')
[docs]def parseMACAddress(addrStr): addr = 0 addrHexStr = addrStr.replace(':', '') if len(addrHexStr) != 12 and len(addrHexStr) != 16: raise ValueError('Invalid MAC address: ' + addrStr) try: for v in [ addrHexStr[i:i+2] for i in range(0, len(addrHexStr), 2) ]: addr = (addr << 8) + int(v, 16) except ValueError: raise ValueError('Invalid MAC address: ' + addrStr) return addr
[docs]def macAddressToString(addr): addrHexString = '%012X' % addr addrHexString = ':'.join(( addrHexString[i:i+2] for i in range(0, len(addrHexString), 2))) return addrHexString
logActions = False logIndent = 0
[docs]def logAction(msg): if logActions: print '%s%s' % (' ' * logIndent, msg)
#=============================================================================== # Main Code #=============================================================================== # Main code # if __name__ == "__main__": toolName = os.path.basename(sys.argv[0]) usage = ''' Usage: [ sudo ] {0} <network-layout-file> <command> [ <arguments> ] [ sudo ] {0} clear-all [ sudo ] {0} help '''.format(toolName) help = ''' simnet -- an IP network simulator based on Linux namespaces Usage: [ sudo ] {0} <network-layout-file> <command> [ <arguments> ] [ sudo ] {0} clear-all [ sudo ] {0} help Available commands: build Build a simulated IP network based on the description contained in the specified network layout file. shell <node-name> Invoke a shell in the context of a simulated network node. show [ all | networks | nodes | hosts | env ] Show detailed information about simulated networks and nodes. clear Tear down all simulated networks and nodes associated with the specified network layout file. clear-all Tear down all active simulated networks and nodes. help Display this help message. '''.format(toolName) try: simnet = SimNet() args = sys.argv[1:] if len(args) == 0: print usage sys.exit(-1) cmdOrFileName = args[0].lower() if cmdOrFileName == 'clear-all': cmd = 'clear-all' layoutFileName = None elif cmdOrFileName == 'help' or cmdOrFileName == '-h' or cmdOrFileName == '--help': cmd = 'help' layoutFileName = None else: if len(args) < 2: print usage sys.exit(-1) layoutFileName = args.pop(0) cmd = args[0].lower() if layoutFileName: simnet.loadLayoutFile(layoutFileName) if cmd == 'help': print help elif cmd == 'clear-all': simnet.clearAll() elif cmd == 'build': simnet.buildNetworksAndNodes() elif cmd == 'clear': simnet.clearNetworksAndNodes() elif cmd == 'show': if len(args) == 1: (showNets, showNodes, showHosts, showEnv) = (True, True, False, False) else: subCmd = args[1].lower() if subCmd == 'networks': (showNets, showNodes, showHosts, showEnv) = (True, False, False) elif subCmd == 'nodes': (showNets, showNodes, showHosts, showEnv) = (False, True, False, False) elif subCmd == 'hosts': (showNets, showNodes, showHosts, showEnv) = (False, False, True, False) elif subCmd == 'env': (showNets, showNodes, showHosts, showEnv) = (False, False, False, True) elif subCmd == 'all': (showNets, showNodes, showHosts, showEnv) = (True, True, True, True) else: raise UsageException('Unknown command: %s' % ' '.join(args)) print simnet.summarizeNetwork(showNets, showNodes, showHosts, showEnv) elif cmd == 'shell': if len(args) < 2: raise UsageException('Please specify node name') nodeName = args[1] simnet.startShell(nodeName) else: raise UsageException('Unknown command: %s' % args[0]) sys.exit(0) except CommandException, ex: print ex.message sys.exit(-1) except ConfigException, ex: print ex.message sys.exit(-1) except UsageException, ex: print ex.message sys.exit(-1)