663 lines
19 KiB
Python
663 lines
19 KiB
Python
import os
|
|
import tempfile
|
|
from functools import wraps
|
|
from threading import Lock
|
|
|
|
from flask import Flask
|
|
from flask import jsonify
|
|
from flask import render_template
|
|
from flask import request
|
|
from flask import send_file
|
|
from flask_socketio import SocketIO
|
|
from flask_socketio import emit
|
|
|
|
from core import logger
|
|
from core.data import ConfigData
|
|
from core.emulator.coreemu import CoreEmu
|
|
from core.emulator.emudata import InterfaceData
|
|
from core.emulator.emudata import LinkOptions
|
|
from core.emulator.emudata import NodeOptions
|
|
from core.enumerations import EventTypes, ConfigFlags
|
|
from core.enumerations import LinkTypes
|
|
from core.enumerations import NodeTypes
|
|
from core.misc import nodeutils
|
|
from core.misc.ipaddress import Ipv4Prefix, Ipv6Prefix
|
|
|
|
CORE_LOCK = Lock()
|
|
|
|
app = Flask(__name__)
|
|
app.config["SECRET_KEY"] = "core"
|
|
socketio = SocketIO(app)
|
|
|
|
coreemu = CoreEmu()
|
|
|
|
|
|
def synchronized(function):
|
|
global CORE_LOCK
|
|
|
|
@wraps(function)
|
|
def wrapper(*args, **kwargs):
|
|
with CORE_LOCK:
|
|
return function(*args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
|
|
def link_data_str(link, key):
|
|
value = link.get(key)
|
|
if value:
|
|
link[key] = str(value)
|
|
|
|
|
|
@socketio.on("connect")
|
|
def websocket_connect():
|
|
emit("info", {"message": "You are connected!"})
|
|
socketio.emit("node", {
|
|
"id": 1,
|
|
"x": 100,
|
|
"y": 101
|
|
})
|
|
socketio.emit("node", {
|
|
"id": 1,
|
|
"x": 100,
|
|
"y": 150
|
|
})
|
|
|
|
|
|
@socketio.on("disconnect")
|
|
def websocket_disconnect():
|
|
logger.info("websocket client disconnected")
|
|
|
|
|
|
@app.route("/")
|
|
def home():
|
|
return render_template('index.html')
|
|
|
|
|
|
@app.route("/ips", methods=["POST"])
|
|
def get_ips():
|
|
data = request.get_json() or {}
|
|
node_id = data["id"]
|
|
node_id = int(node_id)
|
|
|
|
ip4_prefix = data.get("ip4")
|
|
ip6_prefix = data.get("ip6")
|
|
|
|
ip4_prefixes = Ipv4Prefix(ip4_prefix)
|
|
ip6_prefixes = Ipv6Prefix(ip6_prefix)
|
|
|
|
return jsonify(
|
|
ip4=str(ip4_prefixes.addr(node_id)),
|
|
ip4mask=ip4_prefixes.prefixlen,
|
|
ip6=str(ip6_prefixes.addr(node_id)),
|
|
ip6mask=ip6_prefixes.prefixlen
|
|
)
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/xml")
|
|
def save_xml(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
_, temp_path = tempfile.mkstemp()
|
|
session.save_xml(temp_path, "1.0")
|
|
return send_file(
|
|
temp_path,
|
|
as_attachment=True,
|
|
attachment_filename="session-%s.xml" % session_id
|
|
)
|
|
|
|
|
|
@app.route("/sessions/xml", methods=["POST"])
|
|
def open_xml():
|
|
session = coreemu.create_session()
|
|
logger.info("open xml: %s", request.files)
|
|
_, temp_path = tempfile.mkstemp()
|
|
session_file = request.files['session']
|
|
session_file.save(temp_path)
|
|
|
|
try:
|
|
session.open_xml(temp_path, start=True)
|
|
return jsonify(id=session.session_id)
|
|
except:
|
|
logger.exception("error opening session file")
|
|
coreemu.delete_session(session.session_id)
|
|
return jsonify(error="error opening session file"), 404
|
|
|
|
|
|
@app.route("/sessions")
|
|
def get_sessions():
|
|
sessions = []
|
|
for session in coreemu.sessions.itervalues():
|
|
sessions.append({
|
|
"id": session.session_id,
|
|
"state": session.state,
|
|
"nodes": session.get_node_count()
|
|
})
|
|
return jsonify(sessions=sessions)
|
|
|
|
|
|
@app.route("/sessions", methods=["POST"])
|
|
@synchronized
|
|
def create_session():
|
|
session = coreemu.create_session()
|
|
session.set_state(EventTypes.CONFIGURATION_STATE)
|
|
|
|
# set session location
|
|
session.location.setrefgeo(47.57917, -122.13232, 2.0)
|
|
session.location.refscale = 150.0
|
|
|
|
response_data = jsonify(
|
|
id=session.session_id,
|
|
state=session.state,
|
|
url="/sessions/%s" % session.session_id
|
|
)
|
|
return response_data, 201
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>", methods=["DELETE"])
|
|
@synchronized
|
|
def delete_session(session_id):
|
|
result = coreemu.delete_session(session_id)
|
|
if result:
|
|
return jsonify()
|
|
else:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>")
|
|
def get_session(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
nodes = []
|
|
for node in session.objects.itervalues():
|
|
emane_model = None
|
|
if nodeutils.is_node(node, NodeTypes.EMANE):
|
|
emane_model = node.model.name
|
|
|
|
services = [x._name for x in getattr(node, "services", [])]
|
|
nodes.append({
|
|
"id": node.objid,
|
|
"name": node.name,
|
|
"type": nodeutils.get_node_type(node.__class__).value,
|
|
"model": getattr(node, "type", None),
|
|
"position": {
|
|
"x": node.position.x,
|
|
"y": node.position.y,
|
|
"z": node.position.z
|
|
},
|
|
"services": services,
|
|
"emane": emane_model,
|
|
"url": "/sessions/%s/nodes/%s" % (session_id, node.objid)
|
|
})
|
|
|
|
return jsonify(
|
|
state=session.state,
|
|
nodes=nodes
|
|
)
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/config", methods=["PUT"])
|
|
@synchronized
|
|
def set_config(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
data = request.get_json() or {}
|
|
name = data["name"]
|
|
node_id = data.get("node")
|
|
values = data["values"]
|
|
data_types = [value["type"] for value in values]
|
|
data_values = "|".join(["%s=%s" % (value["name"], value["value"]) for value in values])
|
|
|
|
config_data = ConfigData(
|
|
node=node_id,
|
|
object=name,
|
|
type=ConfigFlags.NONE.value,
|
|
data_types=tuple(data_types),
|
|
data_values=data_values
|
|
)
|
|
logger.info("setting config: %s", config_data)
|
|
session.config_object(config_data)
|
|
return jsonify()
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/config")
|
|
def get_config(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
node_id = request.args.get("node")
|
|
if node_id.isdigit():
|
|
node_id = int(node_id)
|
|
|
|
name = request.args["name"]
|
|
|
|
config_data = ConfigData(
|
|
node=node_id,
|
|
object=name,
|
|
type=ConfigFlags.REQUEST.value
|
|
)
|
|
replies = session.config_object(config_data)
|
|
if len(replies) != 1:
|
|
return jsonify(error="failure getting config options"), 404
|
|
config_groups = replies[0]
|
|
|
|
captions = config_groups.captions.split("|")
|
|
data_values = config_groups.data_values.split("|")
|
|
possible_values = config_groups.possible_values.split("|")
|
|
groups = config_groups.groups.split("|")
|
|
|
|
config_options = []
|
|
for i, data_type in enumerate(config_groups.data_types):
|
|
data_value = data_values[i].split("=")
|
|
value = None
|
|
name = data_value[0]
|
|
if len(data_value) == 2:
|
|
value = data_value[1]
|
|
|
|
possible_value = possible_values[i]
|
|
select = None
|
|
if possible_value:
|
|
select = possible_value.split(",")
|
|
|
|
label = captions[i]
|
|
|
|
config_option = {
|
|
"label": label,
|
|
"name": name,
|
|
"value": value,
|
|
"type": data_type,
|
|
"select": select
|
|
}
|
|
config_options.append(config_option)
|
|
|
|
config_groups = []
|
|
for group in groups:
|
|
name, indexes = group.split(":")
|
|
indexes = [int(x) for x in indexes.split("-")]
|
|
start = indexes[0] - 1
|
|
stop = indexes[1]
|
|
config_groups.append({
|
|
"name": name,
|
|
"options": config_options[start: stop]
|
|
})
|
|
|
|
return jsonify(groups=config_groups)
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/emane/models")
|
|
def get_emane_models(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
models = []
|
|
for model in session.emane._modelclsmap.keys():
|
|
if len(model.split("_")) != 2:
|
|
continue
|
|
models.append(model)
|
|
|
|
return jsonify(models=models)
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/nodes", methods=["POST"])
|
|
@synchronized
|
|
def create_node(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
data = request.get_json() or {}
|
|
node_id = data.get("id")
|
|
node_type = data.get("type", NodeTypes.DEFAULT.value)
|
|
node_type = NodeTypes(node_type)
|
|
logger.info("creating node: %s - %s", node_type.name, data)
|
|
|
|
node_options = NodeOptions(
|
|
name=data.get("name"),
|
|
model=data.get("model")
|
|
)
|
|
node_options.icon = data.get("icon")
|
|
node_options.opaque = data.get("opaque")
|
|
node_options.services = data.get("services", [])
|
|
node_options.set_position(data.get("x"), data.get("y"))
|
|
node_options.set_location(data.get("lat"), data.get("lon"), data.get("alt"))
|
|
node = session.add_node(_type=node_type, _id=node_id, node_options=node_options)
|
|
|
|
# configure emane if provided
|
|
emane_model = data.get("emane")
|
|
if emane_model:
|
|
config_data = ConfigData(
|
|
node=node_id,
|
|
object=emane_model,
|
|
type=2
|
|
)
|
|
session.config_object(config_data)
|
|
|
|
return jsonify(
|
|
id=node.objid,
|
|
url="/sessions/%s/nodes/%s" % (session_id, node.objid)
|
|
), 201
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/nodes/<node_id>", methods=["PUT"])
|
|
@synchronized
|
|
def edit_node(session_id, node_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
if node_id.isdigit():
|
|
node_id = int(node_id)
|
|
node = session.objects.get(node_id)
|
|
if not node:
|
|
return jsonify(error="node does not exist"), 404
|
|
|
|
data = request.get_json() or {}
|
|
|
|
node_options = NodeOptions()
|
|
x = data.get("x")
|
|
y = data.get("y")
|
|
node_options.set_position(x, y)
|
|
lat = data.get("lat")
|
|
lon = data.get("lon")
|
|
alt = data.get("alt")
|
|
node_options.set_location(lat, lon, alt)
|
|
|
|
result = session.update_node(node_id, node_options)
|
|
if result:
|
|
return jsonify()
|
|
else:
|
|
return jsonify(error="error during node edit"), 404
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/nodes/<node_id>")
|
|
def get_node(session_id, node_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
if node_id.isdigit():
|
|
node_id = int(node_id)
|
|
node = session.objects.get(node_id)
|
|
if not node:
|
|
return jsonify(error="node does not exist"), 404
|
|
|
|
interfaces = []
|
|
for interface_id, interface in node._netif.iteritems():
|
|
net_id = None
|
|
if interface.net:
|
|
net_id = interface.net.objid
|
|
|
|
interfaces.append({
|
|
"id": interface_id,
|
|
"netid": net_id,
|
|
"name": interface.name,
|
|
"mac": str(interface.hwaddr),
|
|
"mtu": interface.mtu,
|
|
"flowid": interface.flow_id
|
|
})
|
|
|
|
services = [x._name for x in getattr(node, "services", [])]
|
|
|
|
emane_model = None
|
|
if nodeutils.is_node(node, NodeTypes.EMANE):
|
|
emane_model = node.model.name
|
|
|
|
return jsonify(
|
|
name=node.name,
|
|
type=nodeutils.get_node_type(node.__class__).value,
|
|
services=services,
|
|
emane=emane_model,
|
|
model=node.type,
|
|
interfaces=interfaces,
|
|
linksurl="/sessions/%s/nodes/%s/links" % (session_id, node.objid)
|
|
)
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/nodes/<node_id>/terminal")
|
|
def node_terminal(session_id, node_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
if node_id.isdigit():
|
|
node_id = int(node_id)
|
|
node = session.objects.get(node_id)
|
|
if not node:
|
|
return jsonify(error="node does not exist"), 404
|
|
|
|
node.client.term("bash")
|
|
|
|
return jsonify()
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/nodes/<node_id>", methods=["DELETE"])
|
|
@synchronized
|
|
def delete_node(session_id, node_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
if node_id.isdigit():
|
|
node_id = int(node_id)
|
|
node = session.objects.get(node_id)
|
|
if not node:
|
|
return jsonify(error="node does not exist"), 404
|
|
|
|
result = session.delete_node(node_id)
|
|
if result:
|
|
return jsonify()
|
|
else:
|
|
return jsonify(error="failure to delete node"), 404
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/nodes/<node_id>/services")
|
|
def node_services(session_id, node_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
config_data = ConfigData(
|
|
node=node_id,
|
|
object="services",
|
|
type=1,
|
|
)
|
|
logger.debug("configuration message for %s node %s", config_data.object, config_data.node)
|
|
|
|
# dispatch to any registered callback for this object type
|
|
replies = session.config_object(config_data)
|
|
if len(replies) != 1:
|
|
return jsonify(error="failure getting node services"), 404
|
|
|
|
service_data = replies[0]
|
|
names = service_data.captions.split("|")
|
|
services = {}
|
|
for group in service_data.groups.split("|"):
|
|
group = group.split(":")
|
|
group_name = group[0]
|
|
group_start, group_stop = [int(x) for x in group[1].split("-")]
|
|
group_start -= 1
|
|
services[group_name] = names[group_start:group_stop]
|
|
|
|
return jsonify(services)
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/state", methods=["PUT"])
|
|
@synchronized
|
|
def set_session_state(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
data = request.get_json()
|
|
try:
|
|
state = EventTypes(data["state"])
|
|
session.set_state(state)
|
|
|
|
if state == EventTypes.INSTANTIATION_STATE:
|
|
# create session directory if it does not exist
|
|
if not os.path.exists(session.session_dir):
|
|
os.mkdir(session.session_dir)
|
|
session.instantiate()
|
|
elif state == EventTypes.SHUTDOWN_STATE:
|
|
session.shutdown()
|
|
elif state == EventTypes.DATACOLLECT_STATE:
|
|
session.data_collect()
|
|
elif state == EventTypes.DEFINITION_STATE:
|
|
session.clear()
|
|
|
|
return jsonify()
|
|
except KeyError:
|
|
return jsonify(error="invalid state"), 404
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/links", methods=["POST"])
|
|
@synchronized
|
|
def add_link(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
data = request.get_json()
|
|
|
|
node_one = data.get("node_one")
|
|
node_two = data.get("node_two")
|
|
|
|
interface_one = None
|
|
interface_one_data = data.get("interface_one")
|
|
if interface_one_data:
|
|
interface_one = InterfaceData(
|
|
_id=interface_one_data.get("id"),
|
|
name=interface_one_data.get("name"),
|
|
mac=interface_one_data.get("mac"),
|
|
ip4=interface_one_data.get("ip4"),
|
|
ip4_mask=interface_one_data.get("ip4mask"),
|
|
ip6=interface_one_data.get("ip6"),
|
|
ip6_mask=interface_one_data.get("ip6mask"),
|
|
)
|
|
|
|
interface_two = None
|
|
interface_two_data = data.get("interface_two")
|
|
if interface_two_data:
|
|
interface_two = InterfaceData(
|
|
_id=interface_two_data.get("id"),
|
|
name=interface_two_data.get("name"),
|
|
mac=interface_two_data.get("mac"),
|
|
ip4=interface_two_data.get("ip4"),
|
|
ip4_mask=interface_two_data.get("ip4mask"),
|
|
ip6=interface_two_data.get("ip6"),
|
|
ip6_mask=interface_two_data.get("ip6mask"),
|
|
)
|
|
|
|
link_type = None
|
|
link_type_value = data.get("type")
|
|
if link_type_value is not None:
|
|
link_type = LinkTypes(link_type_value)
|
|
|
|
options_data = data.get("options")
|
|
link_options = LinkOptions(_type=link_type)
|
|
if options_data:
|
|
link_options.delay = options_data.get("delay")
|
|
link_options.bandwidth = options_data.get("bandwidth")
|
|
link_options.session = options_data.get("session")
|
|
link_options.per = options_data.get("per")
|
|
link_options.dup = options_data.get("dup")
|
|
link_options.jitter = options_data.get("jitter")
|
|
link_options.mer = options_data.get("mer")
|
|
link_options.burst = options_data.get("burst")
|
|
link_options.mburst = options_data.get("mburst")
|
|
link_options.unidirectional = options_data.get("unidirectional")
|
|
link_options.key = options_data.get("key")
|
|
link_options.opaque = options_data.get("opaque")
|
|
|
|
session.add_link(node_one, node_two, interface_one, interface_two, link_options=link_options)
|
|
return jsonify(), 201
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/links", methods=["PUT"])
|
|
@synchronized
|
|
def edit_link(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
data = request.get_json()
|
|
|
|
node_one = data.get("node_one")
|
|
node_two = data.get("node_two")
|
|
interface_one = data.get("interface_one")
|
|
interface_two = data.get("interface_two")
|
|
|
|
options_data = data.get("options")
|
|
link_options = LinkOptions()
|
|
if options_data:
|
|
link_options.delay = options_data.get("delay")
|
|
link_options.bandwidth = options_data.get("bandwidth")
|
|
link_options.session = options_data.get("session")
|
|
link_options.per = options_data.get("per")
|
|
link_options.dup = options_data.get("dup")
|
|
link_options.jitter = options_data.get("jitter")
|
|
link_options.mer = options_data.get("mer")
|
|
link_options.burst = options_data.get("burst")
|
|
link_options.mburst = options_data.get("mburst")
|
|
link_options.unidirectional = options_data.get("unidirectional")
|
|
link_options.key = options_data.get("key")
|
|
link_options.opaque = options_data.get("opaque")
|
|
|
|
session.update_link(node_one, node_two, link_options, interface_one, interface_two)
|
|
return jsonify(), 201
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/links", methods=["DELETE"])
|
|
@synchronized
|
|
def delete_link(session_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
data = request.get_json()
|
|
node_one = data.get("node_one")
|
|
node_two = data.get("node_two")
|
|
interface_one = data.get("interface_one")
|
|
interface_two = data.get("interface_two")
|
|
session.delete_link(node_one, node_two, interface_one, interface_two)
|
|
return jsonify()
|
|
|
|
|
|
@app.route("/sessions/<int:session_id>/nodes/<node_id>/links")
|
|
def get_node_links(session_id, node_id):
|
|
session = coreemu.sessions.get(session_id)
|
|
if not session:
|
|
return jsonify(error="session does not exist"), 404
|
|
|
|
if node_id.isdigit():
|
|
node_id = int(node_id)
|
|
node = session.objects.get(node_id)
|
|
if not node:
|
|
return jsonify(error="node does not exist"), 404
|
|
|
|
links_data = node.all_link_data(0)
|
|
links = []
|
|
for link_data in links_data:
|
|
link = link_data._asdict()
|
|
del link["message_type"]
|
|
link_data_str(link, "interface1_ip4")
|
|
link_data_str(link, "interface1_ip6")
|
|
link_data_str(link, "interface1_mac")
|
|
link_data_str(link, "interface2_ip4")
|
|
link_data_str(link, "interface2_ip6")
|
|
link_data_str(link, "interface2_mac")
|
|
links.append(link)
|
|
|
|
return jsonify(links=links)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
socketio.run(app, host="0.0.0.0", debug=True)
|