Merge branch 'rel/5.1'

This commit is contained in:
bharnden 2018-05-22 20:44:26 -07:00
commit c3d0b01b7f
293 changed files with 6907 additions and 34130 deletions

View file

@ -0,0 +1,64 @@
#!/bin/sh
if [ "z$1" = "z-h" -o "z$1" = "z--help" ]; then
echo "usage: $0 [-d [-l]]"
echo -n " Clean up all CORE namespaces processes, bridges, interfaces, "
echo "and session\n directories. Options:"
echo " -h show this help message and exit"
echo " -d also kill the Python daemon"
echo " -l remove the core-daemon.log file"
exit 0
fi
if [ `id -u` != 0 ]; then
echo "Permission denied. Re-run this script as root."
exit 1
fi
PATH="/sbin:/bin:/usr/sbin:/usr/bin"
export PATH
if [ "z$1" = "z-d" ]; then
pypids=`pidof python python2`
for p in $pypids; do
grep -q core-daemon /proc/$p/cmdline
if [ $? = 0 ]; then
echo "cleaning up core-daemon process: $p"
kill -9 $p
fi
done
fi
if [ "z$2" = "z-l" ]; then
rm -f /var/log/core-daemon.log
fi
vnodedpids=`pidof vnoded`
if [ "z$vnodedpids" != "z" ]; then
echo "cleaning up old vnoded processes: $vnodedpids"
killall -v -KILL vnoded
# pause for 1 second for interfaces to disappear
sleep 1
fi
killall -q emane
killall -q emanetransportd
killall -q emaneeventservice
if [ -d /sys/class/net ]; then
ifcommand="ls -1 /sys/class/net"
else
ifcommand="ip -o link show | sed -r -e 's/[0-9]+: ([^[:space:]]+): .*/\1/'"
fi
eval "$ifcommand" | awk '
/^veth[0-9]+\./ {print "removing interface " $1; system("ip link del " $1);}
/tmp\./ {print "removing interface " $1; system("ip link del " $1);}
/gt\./ {print "removing interface " $1; system("ip link del " $1);}
/b\./ {print "removing bridge " $1; system("ip link set " $1 " down; brctl delbr " $1);}
'
ebtables -L FORWARD | awk '
/^-.*b\./ {print "removing ebtables " $0; system("ebtables -D FORWARD " $0); print "removing ebtables chain " $4; system("ebtables -X " $4);}
'
rm -rf /tmp/pycore*

141
daemon/scripts/core-daemon Normal file
View file

@ -0,0 +1,141 @@
#!/usr/bin/env python
"""
core-daemon: the CORE daemon is a server process that receives CORE API
messages and instantiates emulated nodes and networks within the kernel. Various
message handlers are defined and some support for sending messages.
"""
import ConfigParser
import optparse
import sys
import time
from core import constants
from core import enumerations
from core import logger
from core.corehandlers import CoreHandler
from core.coreserver import CoreServer
from core.misc.utils import close_onexec
from core.service import ServiceManager
def banner():
"""
Output the program banner printed to the terminal or log file.
:return: nothing
"""
logger.info("CORE daemon v.%s started %s", constants.COREDPY_VERSION, time.ctime())
def cored(cfg, use_ovs):
"""
Start the CoreServer object and enter the server loop.
:param dict cfg: core configuration
:param bool use_ovs: flag to determine if ovs nodes should be used
:return: nothing
"""
host = cfg["listenaddr"]
port = int(cfg["port"])
if host == "" or host is None:
host = "localhost"
try:
server = CoreServer((host, port), CoreHandler, cfg)
if use_ovs:
from core.netns.openvswitch import OVS_NODES
server.coreemu.update_nodes(OVS_NODES)
except:
logger.exception("error starting main server on: %s:%s", host, port)
sys.exit(1)
close_onexec(server.fileno())
logger.info("server started, listening on: %s:%s", host, port)
server.serve_forever()
def get_merged_config(filename):
"""
Return a configuration after merging config file and command-line arguments.
:param str filename: file name to merge configuration settings with
:return: merged configuration
:rtype: dict
"""
# these are the defaults used in the config file
defaults = {
"port": "%d" % enumerations.CORE_API_PORT,
"listenaddr": "localhost",
"xmlfilever": "1.0",
"numthreads": "1",
}
usagestr = "usage: %prog [-h] [options] [args]\n\n" + \
"CORE daemon v.%s instantiates Linux network namespace " \
"nodes." % constants.COREDPY_VERSION
parser = optparse.OptionParser(usage=usagestr)
parser.add_option("-f", "--configfile", dest="configfile", type="string",
help="read config from specified file; default = %s" % filename)
parser.add_option("-p", "--port", dest="port", type=int,
help="port number to listen on; default = %s" % defaults["port"])
parser.add_option("-t", "--numthreads", dest="numthreads", type=int,
help="number of server threads; default = %s" % defaults["numthreads"])
# parse command line options
options, args = parser.parse_args()
# read the config file
if options.configfile is not None:
filename = options.configfile
del options.configfile
cfg = ConfigParser.SafeConfigParser(defaults)
cfg.read(filename)
section = "core-daemon"
if not cfg.has_section(section):
cfg.add_section(section)
# merge command line with config file
for opt in options.__dict__:
val = options.__dict__[opt]
if val is not None:
cfg.set(section, opt, val.__str__())
return dict(cfg.items(section)), args
def main():
"""
Main program startup.
:return: nothing
"""
# get a configuration merged from config file and command-line arguments
cfg, args = get_merged_config("%s/core.conf" % constants.CORE_CONF_DIR)
for a in args:
logger.error("ignoring command line argument: %s", a)
# attempt load custom services
service_paths = cfg.get("custom_services_dir")
logger.debug("custom service paths: %s", service_paths)
if service_paths:
for service_path in service_paths.split(','):
service_path = service_path.strip()
ServiceManager.add_services(service_path)
banner()
# check if ovs flag was provided
use_ovs = len(sys.argv) == 2 and sys.argv[1] == "ovs"
try:
cored(cfg, use_ovs)
except KeyboardInterrupt:
logger.info("keyboard interrupt, stopping core daemon")
sys.exit(0)
if __name__ == "__main__":
main()

248
daemon/scripts/core-manage Normal file
View file

@ -0,0 +1,248 @@
#!/usr/bin/env python
"""
core-manage: Helper tool to add, remove, or check for services, models, and
node types in a CORE installation.
"""
import ast
import optparse
import os
import re
import sys
from core import services
from core.constants import CORE_CONF_DIR
class FileUpdater(object):
"""
Helper class for changing configuration files.
"""
actions = ("add", "remove", "check")
targets = ("service", "model", "nodetype")
def __init__(self, action, target, data, options):
"""
"""
self.action = action
self.target = target
self.data = data
self.options = options
self.verbose = options.verbose
self.search, self.filename = self.get_filename(target)
def process(self):
""" Invoke update_file() using a helper method depending on target.
"""
if self.verbose:
txt = "Updating"
if self.action == "check":
txt = "Checking"
sys.stdout.write("%s file: %s\n" % (txt, self.filename))
if self.target == "service":
r = self.update_file(fn=self.update_services)
elif self.target == "model":
r = self.update_file(fn=self.update_emane_models)
elif self.target == "nodetype":
r = self.update_nodes_conf()
if self.verbose:
txt = ""
if not r:
txt = "NOT "
if self.action == "check":
sys.stdout.write("String %sfound.\n" % txt)
else:
sys.stdout.write("File %supdated.\n" % txt)
return r
def update_services(self, line):
""" Modify the __init__.py file having this format:
__all__ = ["quagga", "nrl", "xorp", "bird", ]
Returns True or False when "check" is the action, a modified line
otherwise.
"""
line = line.strip("\n")
key, valstr = line.split("= ")
vals = ast.literal_eval(valstr)
r = self.update_keyvals(key, vals)
if self.action == "check":
return r
valstr = "%s" % r
return "= ".join([key, valstr]) + "\n"
def update_emane_models(self, line):
""" Modify the core.conf file having this format:
emane_models = RfPipe, Ieee80211abg, CommEffect, Bypass
Returns True or False when "check" is the action, a modified line
otherwise.
"""
line = line.strip("\n")
key, valstr = line.split("= ")
vals = valstr.split(", ")
r = self.update_keyvals(key, vals)
if self.action == "check":
return r
valstr = ", ".join(r)
return "= ".join([key, valstr]) + "\n"
def update_keyvals(self, key, vals):
""" Perform self.action on (key, vals).
Returns True or False when "check" is the action, a modified line
otherwise.
"""
if self.action == "check":
if self.data in vals:
return True
else:
return False
elif self.action == "add":
if self.data not in vals:
vals.append(self.data)
elif self.action == "remove":
try:
vals.remove(self.data)
except ValueError:
pass
return vals
def get_filename(self, target):
""" Return search string and filename based on target.
"""
if target == "service":
filename = os.path.abspath(services.__file__)
search = "__all__ ="
elif target == "model":
filename = os.path.join(CORE_CONF_DIR, "core.conf")
search = "emane_models ="
elif target == "nodetype":
if self.options.userpath is None:
raise ValueError, "missing user path"
filename = os.path.join(self.options.userpath, "nodes.conf")
search = self.data
else:
raise ValueError, "unknown target"
if not os.path.exists(filename):
raise ValueError, "file %s does not exist" % filename
return search, filename
def update_file(self, fn=None):
""" Open a file and search for self.search, invoking the supplied
function on the matching line. Write file changes if necessary.
Returns True if the file has changed (or action is "check" and the
search string is found), False otherwise.
"""
changed = False
output = "" # this accumulates output, assumes input is small
with open(self.filename, "r") as f:
for line in f:
if line[:len(self.search)] == self.search:
r = fn(line) # line may be modified by fn() here
if self.action == "check":
return r
else:
if line != r:
changed = True
line = r
output += line
if changed:
with open(self.filename, "w") as f:
f.write(output)
return changed
def update_nodes_conf(self):
""" Add/remove/check entries from nodes.conf. This file
contains a Tcl-formatted array of node types. The array index must be
properly set for new entries. Uses self.{action, filename, search,
data} variables as input and returns the same value as update_file().
"""
changed = False
output = "" # this accumulates output, assumes input is small
with open(self.filename, "r") as f:
for line in f:
# make sure data is not added twice
if line.find(self.search) >= 0:
if self.action == "check":
return True
elif self.action == "add":
return False
elif self.action == "remove":
changed = True
continue
else:
output += line
if self.action == "add":
index = int(re.match("^\d+", line).group(0))
output += str(index + 1) + " " + self.data + "\n"
changed = True
if changed:
with open(self.filename, "w") as f:
f.write(output)
return changed
def main():
usagestr = "usage: %prog [-h] [options] <action> <target> <string>\n"
usagestr += "\nHelper tool to add, remove, or check for "
usagestr += "services, models, and node types\nin a CORE installation.\n"
usagestr += "\nExamples:\n %prog add service newrouting"
usagestr += "\n %prog -v check model RfPipe"
usagestr += "\n %prog --userpath=\"$HOME/.core\" add nodetype \"{ftp ftp.gif ftp.gif {DefaultRoute FTP} netns {FTP server} }\" \n"
usagestr += "\nArguments:\n <action> should be one of: %s" % \
", ".join(FileUpdater.actions)
usagestr += "\n <target> should be one of: %s" % \
", ".join(FileUpdater.targets)
usagestr += "\n <string> is the text to %s" % \
", ".join(FileUpdater.actions)
parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(userpath=None, verbose=False, )
parser.add_option("--userpath", dest="userpath", type="string",
help="use the specified user path (e.g. \"$HOME/.core" \
"\") to access nodes.conf")
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="be verbose when performing action")
def usage(msg=None, err=0):
sys.stdout.write("\n")
if msg:
sys.stdout.write(msg + "\n\n")
parser.print_help()
sys.exit(err)
(options, args) = parser.parse_args()
if len(args) != 3:
usage("Missing required arguments!", 1)
action = args[0]
if action not in FileUpdater.actions:
usage("invalid action %s" % action, 1)
target = args[1]
if target not in FileUpdater.targets:
usage("invalid target %s" % target, 1)
if target == "nodetype" and not options.userpath:
usage("user path option required for this target (%s)" % target)
data = args[2]
try:
up = FileUpdater(action, target, data, options)
r = up.process()
except Exception, e:
sys.stderr.write("Exception: %s\n" % e)
sys.exit(1)
if not r:
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()

274
daemon/scripts/coresendmsg Normal file
View file

@ -0,0 +1,274 @@
#!/usr/bin/env python
"""
coresendmsg: utility for generating CORE messages
"""
import optparse
import os
import socket
import sys
from core.api import coreapi
from core.enumerations import CORE_API_PORT
from core.enumerations import MessageFlags
from core.enumerations import MessageTypes
from core.enumerations import SessionTlvs
def print_available_tlvs(t, tlv_class):
"""
Print a TLV list.
"""
print "TLVs available for %s message:" % t
for tlv in sorted([tlv for tlv in tlv_class.tlv_type_map], key=lambda x: x.name):
print "%s:%s" % (tlv.value, tlv.name)
def print_examples(name):
"""
Print example usage of this script.
"""
examples = [
("link n1number=2 n2number=3 delay=15000",
"set a 15ms delay on the link between n2 and n3"),
("link n1number=2 n2number=3 guiattr=\"color=blue\"",
"change the color of the link between n2 and n3"),
("node number=3 xpos=125 ypos=525",
"move node number 3 to x,y=(125,525)"),
("node number=4 icon=/usr/local/share/core/icons/normal/router_red.gif",
"change node number 4\"s icon to red"),
("node flags=add number=5 type=0 name=\"n5\" xpos=500 ypos=500",
"add a new router node n5"),
("link flags=add n1number=4 n2number=5 if1ip4=\"10.0.3.2\" " \
"if1ip4mask=24 if2ip4=\"10.0.3.1\" if2ip4mask=24",
"link node n5 with n4 using the given interface addresses"),
("exec flags=str,txt node=1 num=1000 cmd=\"uname -a\" -l",
"run a command on node 1 and wait for the result"),
("exec node=2 num=1001 cmd=\"killall ospfd\"",
"run a command on node 2 and ignore the result"),
("file flags=add node=1 name=\"/var/log/test.log\" data=\"Hello World.\"",
"write a test.log file on node 1 with the given contents"),
("file flags=add node=2 name=\"test.log\" " \
"srcname=\"./test.log\"",
"move a test.log file from host to node 2"),
]
print "Example %s invocations:" % name
for cmd, descr in examples:
print " %s %s\n\t\t%s" % (name, cmd, descr)
def receive_message(sock):
"""
Retrieve a message from a socket and return the CoreMessage object or
None upon disconnect. Socket data beyond the first message is dropped.
"""
try:
# large receive buffer used for UDP sockets, instead of just receiving
# the 4-byte header
data = sock.recv(4096)
msghdr = data[:coreapi.CoreMessage.header_len]
except KeyboardInterrupt:
print "CTRL+C pressed"
sys.exit(1)
if len(msghdr) == 0:
return None
msgdata = None
msgtype, msgflags, msglen = coreapi.CoreMessage.unpack_header(msghdr)
if msglen:
msgdata = data[coreapi.CoreMessage.header_len:]
try:
msgcls = coreapi.CLASS_MAP[msgtype]
except KeyError:
msg = coreapi.CoreMessage(msgflags, msghdr, msgdata)
msg.message_type = msgtype
print "unimplemented CORE message type: %s" % msg.type_str()
return msg
if len(data) > msglen + coreapi.CoreMessage.header_len:
print "received a message of type %d, dropping %d bytes of extra data" \
% (msgtype, len(data) - (msglen + coreapi.CoreMessage.header_len))
return msgcls(msgflags, msghdr, msgdata)
def connect_to_session(sock, requested):
"""
Use Session Messages to retrieve the current list of sessions and
connect to the first one.
"""
# request the session list
tlvdata = coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, "")
flags = MessageFlags.STRING.value
smsg = coreapi.CoreSessionMessage.pack(flags, tlvdata)
sock.sendall(smsg)
print "waiting for session list..."
smsgreply = receive_message(sock)
if smsgreply is None:
print "disconnected"
return False
sessstr = smsgreply.get_tlv(SessionTlvs.NUMBER.value)
if sessstr is None:
print "missing session numbers"
return False
# join the first session (that is not our own connection)
tmp, localport = sock.getsockname()
sessions = sessstr.split("|")
sessions.remove(str(localport))
if len(sessions) == 0:
print "no sessions to join"
return False
if not requested:
session = sessions[0]
elif requested in sessions:
session = requested
else:
print "requested session not found!"
return False
print "joining session: %s" % session
tlvdata = coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, session)
flags = MessageFlags.ADD.value
smsg = coreapi.CoreSessionMessage.pack(flags, tlvdata)
sock.sendall(smsg)
return True
def receive_response(sock, opt):
"""
Receive and print a CORE message from the given socket.
"""
print "waiting for response..."
msg = receive_message(sock)
if msg is None:
print "disconnected from %s:%s" % (opt.address, opt.port)
sys.exit(0)
print "received message:", msg
def main():
"""
Parse command-line arguments to build and send a CORE message.
"""
types = [message_type.name for message_type in MessageTypes]
flags = [flag.name for flag in MessageFlags]
usagestr = "usage: %prog [-h|-H] [options] [message-type] [flags=flags] "
usagestr += "[message-TLVs]\n\n"
usagestr += "Supported message types:\n %s\n" % types
usagestr += "Supported message flags (flags=f1,f2,...):\n %s" % flags
parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(
port=CORE_API_PORT,
address="localhost",
session=None,
listen=False,
examples=False,
tlvs=False,
tcp=False
)
parser.add_option("-H", dest="examples", action="store_true",
help="show example usage help message and exit")
parser.add_option("-p", "--port", dest="port", type=int,
help="TCP port to connect to, default: %d" % \
parser.defaults["port"])
parser.add_option("-a", "--address", dest="address", type=str,
help="Address to connect to, default: %s" % \
parser.defaults["address"])
parser.add_option("-s", "--session", dest="session", type=str,
help="Session to join, default: %s" % \
parser.defaults["session"])
parser.add_option("-l", "--listen", dest="listen", action="store_true",
help="Listen for a response message and print it.")
parser.add_option("-t", "--list-tlvs", dest="tlvs", action="store_true",
help="List TLVs for the specified message type.")
def usage(msg=None, err=0):
sys.stdout.write("\n")
if msg:
sys.stdout.write(msg + "\n\n")
parser.print_help()
sys.exit(err)
# parse command line opt
opt, args = parser.parse_args()
if opt.examples:
print_examples(os.path.basename(sys.argv[0]))
sys.exit(0)
if len(args) == 0:
usage("Please specify a message type to send.")
# given a message type t, determine the message and TLV classes
t = args.pop(0)
if t not in types:
usage("Unknown message type requested: %s" % t)
message_type = MessageTypes[t]
msg_cls = coreapi.CLASS_MAP[message_type.value]
tlv_cls = msg_cls.tlv_class
# list TLV types for this message type
if opt.tlvs:
print_available_tlvs(t, tlv_cls)
sys.exit(0)
# build a message consisting of TLVs from "type=value" arguments
flagstr = ""
tlvdata = ""
for a in args:
typevalue = a.split("=")
if len(typevalue) < 2:
usage("Use \"type=value\" syntax instead of \"%s\"." % a)
tlv_typestr = typevalue[0]
tlv_valstr = "=".join(typevalue[1:])
if tlv_typestr == "flags":
flagstr = tlv_valstr
continue
tlv_name = tlv_typestr
try:
tlv_type = tlv_cls.tlv_type_map[tlv_name]
tlvdata += tlv_cls.pack_string(tlv_type.value, tlv_valstr)
except KeyError:
usage("Unknown TLV: \"%s\"" % tlv_name)
flags = 0
for f in flagstr.split(","):
if f == "":
continue
try:
flag_enum = MessageFlags[f]
n = flag_enum.value
flags |= n
except KeyError:
usage("Invalid flag \"%s\"." % f)
msg = msg_cls.pack(flags, tlvdata)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(True)
try:
sock.connect((opt.address, opt.port))
except Exception as e:
print "Error connecting to %s:%s:\n\t%s" % (opt.address, opt.port, e)
sys.exit(1)
if not connect_to_session(sock, opt.session):
print "warning: continuing without joining a session!"
sock.sendall(msg)
if opt.listen:
receive_response(sock, opt)
if opt.tcp:
sock.shutdown(socket.SHUT_RDWR)
sock.close()
sys.exit(0)
if __name__ == "__main__":
main()