blob: 727e3809e5ed063e12b04a44798475a1264af40e [file] [log] [blame]
#!/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
#===============================================================================
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'),
]
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()
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
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
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
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)
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
def findNode(self, nodeName):
nodeName = nodeName.lower()
for node in self.nodes:
if node.name.lower() == nodeName:
return node
return None
def getHostsEntries(self):
hostsEntriesByNode = {}
for node in nodes:
hostsEntriesByNode[node.name] = node.getHostsEntries()
return hostsEntriesByNode
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
#===============================================================================
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)
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)
def clearNetwork(self):
# Delete the namespace for the network.
deleteNamespace(self.nsName)
# Delete the network bridge.
# deleteBridge(self.brName)
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
def requestIP4AutoConfig(self, interface):
return None
def requestIP6AutoConfig(self, interface):
return []
def requestIP4AdvertisedRoutes(self, interface):
return []
def requestIP6AdvertisedRoutes(self, interface):
return []
@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
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 = [ ]
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
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 = [ ]
)
]
def requestIP4AdvertisedRoutes(self, interface):
routes = []
if self.ip4Subnet != None:
# Add
routes.append(
IPRoute(
dest = self.ip4Subnet,
interface = interface,
via = None
)
)
return routes
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
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
class WiFiNetwork(Network):
def __init__(self, name):
Network.__init__(self, name)
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
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
def requestIP4AdvertisedRoutes(self, interface):
routes = []
# TODO: implement this
return routes
def requestIP6AdvertisedRoutes(self, interface):
routes = []
# TODO: implement this
return routes
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
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 = [ ]
)
]
def summary(self, prefix=''):
s = Network.summary(self, prefix)
s += '%s Mesh Local Prefix: %s\n' % (prefix, self.meshLocalPrefix)
return s
class LegacyThreadNetwork(Network):
def __init__(self, name):
Network.__init__(self, name)
self.ip4Supported = False
self.usesMAC64 = True
class CellularNetwork(WiFiNetwork):
pass
class HostNetwork(Network):
def __init__(self, name):
Network.__init__(self, name)
def buildNetwork(self):
# Do nothing.
pass
def clearNetwork(self):
# Do nothing.
pass
def summary(self, prefix=''):
# Don't show host networks. They only exist to give host interface objects
# something to point at.
return ''
#===============================================================================
# Node Classes
#===============================================================================
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)
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
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
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)
def requestIP4AutoConfig(self, interface):
return None
def requestIP6AutoConfig(self, interface):
return None
def requestIP4AdvertisedRoutes(self, interface):
return []
def requestIP6AdvertisedRoutes(self, interface):
return []
def filterIP6AutoConfig(self, interface, autoConfigs):
return autoConfigs
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
)
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)
def isNodeBuilt(self):
if self.isHostNode:
return True
else:
nsList = getNamespaces()
return self.nsName in nsList
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
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)
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)
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)
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
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
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
]
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
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
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)
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)
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
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)
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
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
def weaveSubnetPrefix(self, subnetNum):
return makeIP6Prefix(ulaPrefix, networkNum=self.fabricAddrGlobalId, subnetNum=subnetNum, prefixLen=64)
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
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
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
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
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
#===============================================================================
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)
def requestIP4AutoConfig(self):
if self.ip4AutoConfig == None:
self.ip4AutoConfig = self.network.requestIP4AutoConfig(self)
if self.ip4AutoConfig == None:
self.ip4AutoConfig = IP4AutoConfigInfo()
return self.ip4AutoConfig
def requestIP6AutoConfig(self, interface):
if self.ip6AutoConfig == None:
self.ip6AutoConfig = self.network.requestIP6AutoConfig(self)
return self.ip6AutoConfig
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
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)
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
def clearInterface(self):
# Delete the virtual interfaces.
deleteInterface(self.ifName, nsName=self.node.nsName)
deleteInterface(self.peerIfName, nsName=self.network.nsName)
def typeSummary(self):
return 'virtual interface to network "%s" via peer interface %s' % (self.network.name, self.peerIfName)
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)
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)
def clearInterface(self):
# Delete the tap device.
deleteInterface(self.tapDevName, nsName=self.network.nsName)
def typeSummary(self):
return 'tap device connected to network "%s" via interface %s' % (self.network.name, self.tapDevName)
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;
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
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)
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)
def typeSummary(self):
return 'existing host interface'
#===============================================================================
# Utility Classes
#===============================================================================
class IP4AutoConfigInfo:
def __init__(self, address=None, defaultGateway=None, dnsServers=[]):
self.address = address
self.defaultGateway = defaultGateway
self.dnsServers = dnsServers
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
class IPRoute:
def __init__(self, dest, interface=None, via=None, options=None):
self.destination = dest
self.interface = interface
self.via = via
self.options = options
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
class CommandException(Exception):
pass
class ConfigException(Exception):
pass
class UsageException(Exception):
pass
#===============================================================================
# Utility Functions
#===============================================================================
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
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)
def addBridge(name, nsName=None):
runCmd([ 'brctl', 'addbr', name ], nsName=nsName,
errMsg='Failed to create bridge %s' % (name))
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
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
def addNamespace(name):
runCmd([ 'ip', 'netns', 'add', name ], errMsg='Failed to create network namespace %s' % (name))
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
def enableInterface(name, nsName=None):
runCmd([ 'ip', 'link', 'set', name, 'up' ], errMsg='Failed to enable interface %s' % (name), nsName=nsName)
def disableInterface(name, nsName=None):
runCmd([ 'ip', 'link', 'set', name, 'down' ], errMsg='Failed to disable interface %s' % (name), nsName=nsName)
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)
def createTapInterface(name, user='root', group='root'):
runCmd([ 'tunctl', '-u', user, '-g', group, '-t', name], errMsg='Unable to create tap interface pair %s' % (name))
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))
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)
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
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)
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)
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))
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))
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
def getHostInterfaces():
global hostInterfaces
if not hostInterfaces:
hostInterfaces = getInterfaces()
return hostInterfaces
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
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)
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
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
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 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
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)
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)
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)
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)
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
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
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
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
def makeIP4IntefaceAddress(subnet, nodeIndex, prefixLen=-1):
return ipaddress.IPv4Interface((subnet[nodeIndex], prefixLen if prefixLen >= 0 else subnet.prefixlen))
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))
def makeIP6Prefix(basePrefix, networkNum=0, subnetNum=0, prefixLen=48):
return ipaddress.IPv6Network((basePrefix[(networkNum << 80) + (subnetNum << 64)], prefixLen))
def isNetworkAddress(addr):
return addr.network.prefixlen < addr.network.max_prefixlen and addr.ip == addr.network.network_address
def containsGlobalAddress(addrList):
for a in addrList:
if not a.is_private:
return True
return False
def preferGlobalAddresses(addrList):
if containsGlobalAddress(addrList):
return [ a for a in addrList if not a.is_private ]
else:
return addrList
def verifyRoot():
if os.geteuid() != 0:
raise UsageException('Operation not permitted: This command requires root privileges.')
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
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
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)