From 664b049bf71aa77e8204a61d4d1bc5668a83f0e4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 2 Dec 2020 20:40:03 -0800 Subject: [PATCH 01/21] pygui: updated edge token to default to 0 for interface ids due to grpc messages not properly supporting None --- daemon/core/gui/graph/edges.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 9829cbd49..4825b5256 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -28,8 +28,8 @@ def create_wireless_token(src: int, dst: int, network: int) -> str: def create_edge_token(link: Link) -> str: - iface1_id = link.iface1.id if link.iface1 else None - iface2_id = link.iface2.id if link.iface2 else None + iface1_id = link.iface1.id if link.iface1 else 0 + iface2_id = link.iface2.id if link.iface2 else 0 return f"{link.node1_id}-{iface1_id}-{link.node2_id}-{iface2_id}" From 41222f77c200afe4f255a3b09d2afb61217a080d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 3 Dec 2020 22:28:44 -0800 Subject: [PATCH 02/21] daemon: fix delete link for network to network nodes --- daemon/core/emulator/session.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 6d583860b..e3363f7c0 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -369,6 +369,19 @@ def delete_link( node1.delete_iface(iface1_id) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): node2.delete_iface(iface2_id) + elif isinstance(node1, CoreNetworkBase) and isinstance( + node2, CoreNetworkBase + ): + for iface in node1.get_ifaces(control=False): + if iface.othernet == node2: + node1.detach(iface) + iface.shutdown() + break + for iface in node2.get_ifaces(control=False): + if iface.othernet == node1: + node2.detach(iface) + iface.shutdown() + break self.sdt.delete_link(node1_id, node2_id) def update_link( From b762fe664b2f4ea071d5f08aa0bd34b27abdf7f9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 4 Dec 2020 00:03:30 -0800 Subject: [PATCH 03/21] pygui: avoid saving edge config metadata when values are default --- daemon/core/gui/coreclient.py | 2 ++ daemon/core/gui/graph/edges.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 114aa5b29..01225c6b6 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -582,6 +582,8 @@ def set_metadata(self) -> None: # create edges config edges_config = [] for edge in self.links.values(): + if not edge.is_customized(): + continue edge_config = dict(token=edge.token, width=edge.width, color=edge.color) edges_config.append(edge_config) edges_config = json.dumps(edges_config) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 4825b5256..216fc7f26 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -297,6 +297,9 @@ def __init__( self.context: tk.Menu = tk.Menu(self.canvas) self.create_context() + def is_customized(self) -> bool: + return self.width != EDGE_WIDTH or self.color != EDGE_COLOR + def create_context(self) -> None: themes.style_menu(self.context) self.context.add_command(label="Configure", command=self.click_configure) From a23ef7d6039cbbed94bd639f24f84eb8772cfb96 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 5 Dec 2020 09:01:53 -0800 Subject: [PATCH 04/21] daemon: properly go through the data collect state for grpc session shutdown, also check and avoid repeating data collect or shutdown when already past those states --- daemon/core/api/grpc/server.py | 1 + daemon/core/emulator/coreemu.py | 1 + daemon/core/emulator/enumerations.py | 3 +++ daemon/core/emulator/session.py | 21 ++++++++++++++------- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 3159776a6..fcd3210d5 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -315,6 +315,7 @@ def StopSession( """ logging.debug("stop session: %s", request) session = self.get_session(request.session_id, context) + session.data_collect() session.shutdown() return core_pb2.StopSessionResponse(result=True) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index c07d8c953..885fb4318 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -144,6 +144,7 @@ def delete_session(self, _id: int) -> bool: result = False if session: logging.info("shutting session down: %s", _id) + session.data_collect() session.shutdown() result = True else: diff --git a/daemon/core/emulator/enumerations.py b/daemon/core/emulator/enumerations.py index 5cad0aa57..83e7bffd8 100644 --- a/daemon/core/emulator/enumerations.py +++ b/daemon/core/emulator/enumerations.py @@ -106,6 +106,9 @@ class EventTypes(Enum): def should_start(self) -> bool: return self.value > self.DEFINITION_STATE.value + def already_collected(self) -> bool: + return self.value >= self.DATACOLLECT_STATE.value + class ExceptionLevels(Enum): """ diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index e3363f7c0..d98564ede 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -770,10 +770,11 @@ def shutdown(self) -> None: """ Shutdown all session nodes and remove the session directory. """ + if self.state == EventTypes.SHUTDOWN_STATE: + logging.info("session(%s) state(%s) already shutdown", self.id, self.state) + return logging.info("session(%s) state(%s) shutting down", self.id, self.state) - if self.state != EventTypes.SHUTDOWN_STATE: - self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True) - self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True) + self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True) # clear out current core session self.clear() # shutdown sdt @@ -1258,6 +1259,14 @@ def data_collect(self) -> None: :return: nothing """ + if self.state.already_collected(): + logging.info( + "session(%s) state(%s) already data collected", self.id, self.state + ) + return + logging.info("session(%s) state(%s) data collection", self.id, self.state) + self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True) + # stop event loop self.event_loop.stop() @@ -1279,10 +1288,8 @@ def data_collect(self) -> None: self.update_control_iface_hosts(remove=True) # remove all four possible control networks - self.add_remove_control_net(0, remove=True) - self.add_remove_control_net(1, remove=True) - self.add_remove_control_net(2, remove=True) - self.add_remove_control_net(3, remove=True) + for i in range(4): + self.add_remove_control_net(i, remove=True) def short_session_id(self) -> str: """ From 6793382f44e81ce2bcf99e6fe6ab8f3f77f2c6f2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 7 Dec 2020 21:08:05 -0800 Subject: [PATCH 05/21] pygui: fixed edit node mac setting to auto to properly clear out current mac setting --- daemon/core/gui/dialogs/nodeconfig.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 9ceafa351..19dd2bcca 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -342,7 +342,9 @@ def click_apply(self) -> None: mac = data.mac.get() auto_mac = data.is_auto.get() - if not auto_mac and not netaddr.valid_mac(mac): + if auto_mac: + iface.mac = None + elif not auto_mac and not netaddr.valid_mac(mac): title = f"MAC Error for {iface.name}" messagebox.showerror(title, "Invalid MAC Address") error = True From 5b93c2d7acd5d94ad37aa564414c5ec9e7bb2974 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 7 Dec 2020 22:31:53 -0800 Subject: [PATCH 06/21] daemon: added support for link options buffer read/write to xml --- daemon/core/nodes/interface.py | 4 ++++ daemon/core/xml/corexml.py | 2 ++ daemon/tests/test_xml.py | 8 ++++++++ 3 files changed, 14 insertions(+) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 28f7f925c..99f4fb8d3 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -241,12 +241,16 @@ def get_link_options(self, unidirectional: int) -> LinkOptions: jitter = self.getparam("jitter") if jitter is not None: jitter = int(jitter) + buffer = self.getparam("buffer") + if buffer is not None: + buffer = int(buffer) return LinkOptions( delay=delay, bandwidth=bandwidth, dup=dup, jitter=jitter, loss=self.getparam("loss"), + buffer=buffer, unidirectional=unidirectional, ) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 79d3209d6..667ebae88 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -570,6 +570,7 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: add_attribute(options, "unidirectional", options_data.unidirectional) add_attribute(options, "network_id", link_data.network_id) add_attribute(options, "key", options_data.key) + add_attribute(options, "buffer", options_data.buffer) if options.items(): link_element.append(options) @@ -976,6 +977,7 @@ def read_links(self) -> None: if options.loss is None: options.loss = get_float(options_element, "per") options.unidirectional = get_int(options_element, "unidirectional") + options.buffer = get_int(options_element, "buffer") if options.unidirectional == 1 and node_set in node_sets: logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id) diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index fb8bc4d9d..8a6e465d1 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -309,6 +309,7 @@ def test_link_options( options.jitter = 10 options.delay = 30 options.dup = 5 + options.buffer = 100 session.add_link(node1.id, switch.id, iface1_data, options=options) # instantiate session @@ -352,6 +353,7 @@ def test_link_options( assert options.jitter == link.options.jitter assert options.delay == link.options.delay assert options.dup == link.options.dup + assert options.buffer == link.options.buffer def test_link_options_ptp( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -376,6 +378,7 @@ def test_link_options_ptp( options.jitter = 10 options.delay = 30 options.dup = 5 + options.buffer = 100 session.add_link(node1.id, node2.id, iface1_data, iface2_data, options) # instantiate session @@ -419,6 +422,7 @@ def test_link_options_ptp( assert options.jitter == link.options.jitter assert options.delay == link.options.delay assert options.dup == link.options.dup + assert options.buffer == link.options.buffer def test_link_options_bidirectional( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -444,6 +448,7 @@ def test_link_options_bidirectional( options1.loss = 10.5 options1.dup = 5 options1.jitter = 5 + options1.buffer = 50 session.add_link(node1.id, node2.id, iface1_data, iface2_data, options1) options2 = LinkOptions() options2.unidirectional = 1 @@ -452,6 +457,7 @@ def test_link_options_bidirectional( options2.loss = 10 options2.dup = 10 options2.jitter = 10 + options2.buffer = 100 session.update_link( node2.id, node1.id, iface2_data.id, iface1_data.id, options2 ) @@ -499,8 +505,10 @@ def test_link_options_bidirectional( assert options1.loss == link1.options.loss assert options1.dup == link1.options.dup assert options1.jitter == link1.options.jitter + assert options1.buffer == link1.options.buffer assert options2.bandwidth == link2.options.bandwidth assert options2.delay == link2.options.delay assert options2.loss == link2.options.loss assert options2.dup == link2.options.dup assert options2.jitter == link2.options.jitter + assert options2.buffer == link2.options.buffer From 836e929fbc136cba4c4f91e709212d6237f5b406 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 8 Dec 2020 10:02:34 -0800 Subject: [PATCH 07/21] pygui: add support to rename interfaces in the node config dialog, some small cleanup to interface validation --- daemon/core/gui/dialogs/nodeconfig.py | 130 +++++++++++++++++--------- 1 file changed, 88 insertions(+), 42 deletions(-) diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 19dd2bcca..aad2d2805 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -7,7 +7,7 @@ import netaddr from PIL.ImageTk import PhotoImage -from core.api.grpc.wrappers import Node +from core.api.grpc.wrappers import Interface, Node from core.gui import nodeutils, validation from core.gui.appconfig import ICONS_PATH from core.gui.dialogs.dialog import Dialog @@ -21,8 +21,10 @@ from core.gui.app import Application from core.gui.graph.node import CanvasNode +IFACE_NAME_LEN: int = 15 -def check_ip6(parent, name: str, value: str) -> bool: + +def check_ip6(parent: tk.BaseWidget, name: str, value: str) -> bool: if not value: return True title = f"IP6 Error for {name}" @@ -47,7 +49,7 @@ def check_ip6(parent, name: str, value: str) -> bool: return True -def check_ip4(parent, name: str, value: str) -> bool: +def check_ip4(parent: tk.BaseWidget, name: str, value: str) -> bool: if not value: return True title = f"IP4 Error for {name}" @@ -84,16 +86,88 @@ def mac_auto(is_auto: tk.BooleanVar, entry: ttk.Entry, mac: tk.StringVar) -> Non class InterfaceData: def __init__( self, + name: tk.StringVar, is_auto: tk.BooleanVar, mac: tk.StringVar, ip4: tk.StringVar, ip6: tk.StringVar, ) -> None: + self.name: tk.StringVar = name self.is_auto: tk.BooleanVar = is_auto self.mac: tk.StringVar = mac self.ip4: tk.StringVar = ip4 self.ip6: tk.StringVar = ip6 + def validate(self, parent: tk.BaseWidget, iface: Interface) -> bool: + valid_name = self._validate_name(parent, iface) + valid_ip4 = self._validate_ip4(parent, iface) + valid_ip6 = self._validate_ip6(parent, iface) + valid_mac = self._validate_mac(parent, iface) + return all([valid_name, valid_ip4, valid_ip6, valid_mac]) + + def _validate_name(self, parent: tk.BaseWidget, iface: Interface) -> bool: + name = self.name.get() + title = f"Interface Name Error for {iface.name}" + if not name: + messagebox.showerror(title, "Name cannot be empty", parent=parent) + return False + if len(name) > IFACE_NAME_LEN: + messagebox.showerror( + title, + f"Name cannot be greater than {IFACE_NAME_LEN} chars", + parent=parent, + ) + return False + for x in name: + if x.isspace() or x == "/": + messagebox.showerror( + title, "Name cannot contain space or /", parent=parent + ) + return False + iface.name = name + return True + + def _validate_ip4(self, parent: tk.BaseWidget, iface: Interface) -> bool: + ip4_net = self.ip4.get() + if not check_ip4(parent, iface.name, ip4_net): + return False + if ip4_net: + ip4, ip4_mask = ip4_net.split("/") + ip4_mask = int(ip4_mask) + else: + ip4, ip4_mask = "", 0 + iface.ip4 = ip4 + iface.ip4_mask = ip4_mask + return True + + def _validate_ip6(self, parent: tk.BaseWidget, iface: Interface) -> bool: + ip6_net = self.ip6.get() + if not check_ip6(parent, iface.name, ip6_net): + return False + if ip6_net: + ip6, ip6_mask = ip6_net.split("/") + ip6_mask = int(ip6_mask) + else: + ip6, ip6_mask = "", 0 + iface.ip6 = ip6 + iface.ip6_mask = ip6_mask + return True + + def _validate_mac(self, parent: tk.BaseWidget, iface: Interface) -> bool: + mac = self.mac.get() + auto_mac = self.is_auto.get() + if auto_mac: + iface.mac = None + else: + if not netaddr.valid_mac(mac): + title = f"MAC Error for {iface.name}" + messagebox.showerror(title, "Invalid MAC Address", parent=parent) + return False + else: + mac = netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded) + iface.mac = str(mac) + return True + class NodeConfigDialog(Dialog): def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: @@ -229,6 +303,14 @@ def draw_ifaces(self) -> None: button.grid(row=row, sticky=tk.EW, columnspan=3, pady=PADY) row += 1 + label = ttk.Label(tab, text="Name") + label.grid(row=row, column=0, padx=PADX, pady=PADY) + name = tk.StringVar(value=iface.name) + entry = ttk.Entry(tab, textvariable=name, state=state) + entry.var = name + entry.grid(row=row, column=1, columnspan=2, sticky=tk.EW) + row += 1 + label = ttk.Label(tab, text="MAC") label.grid(row=row, column=0, padx=PADX, pady=PADY) auto_set = not iface.mac @@ -267,7 +349,7 @@ def draw_ifaces(self) -> None: entry = ttk.Entry(tab, textvariable=ip6, state=state) entry.grid(row=row, column=1, columnspan=2, sticky=tk.EW) - self.ifaces[iface.id] = InterfaceData(is_auto, mac, ip4, ip6) + self.ifaces[iface.id] = InterfaceData(name, is_auto, mac, ip4, ip6) def draw_buttons(self) -> None: frame = ttk.Frame(self.top) @@ -313,45 +395,9 @@ def click_apply(self) -> None: # update node interface data for iface in self.canvas_node.ifaces.values(): data = self.ifaces[iface.id] - - # validate ip4 - ip4_net = data.ip4.get() - if not check_ip4(self, iface.name, ip4_net): - error = True + error = not data.validate(self, iface) + if error: break - if ip4_net: - ip4, ip4_mask = ip4_net.split("/") - ip4_mask = int(ip4_mask) - else: - ip4, ip4_mask = "", 0 - iface.ip4 = ip4 - iface.ip4_mask = ip4_mask - - # validate ip6 - ip6_net = data.ip6.get() - if not check_ip6(self, iface.name, ip6_net): - error = True - break - if ip6_net: - ip6, ip6_mask = ip6_net.split("/") - ip6_mask = int(ip6_mask) - else: - ip6, ip6_mask = "", 0 - iface.ip6 = ip6 - iface.ip6_mask = ip6_mask - - mac = data.mac.get() - auto_mac = data.is_auto.get() - if auto_mac: - iface.mac = None - elif not auto_mac and not netaddr.valid_mac(mac): - title = f"MAC Error for {iface.name}" - messagebox.showerror(title, "Invalid MAC Address") - error = True - break - elif not auto_mac: - mac = netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded) - iface.mac = str(mac) # redraw if not error: From d824fbd1c6cdf548e884750a4032d14433a5c943 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 9 Dec 2020 10:05:13 -0800 Subject: [PATCH 08/21] grpc: fixed creating session directory if need be when starting session, before setting config state, avoids path not existing error --- daemon/core/api/grpc/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index fcd3210d5..73fa2fa67 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -221,9 +221,9 @@ def StartSession( # clear previous state and setup for creation session.clear() - session.set_state(EventTypes.CONFIGURATION_STATE) if not os.path.exists(session.session_dir): os.mkdir(session.session_dir) + session.set_state(EventTypes.CONFIGURATION_STATE) # location if request.HasField("location"): From 7308dd50ff9b5df2e266b18d50867794a260dde5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 9 Dec 2020 15:43:19 -0800 Subject: [PATCH 09/21] daemon: fixed issue related to updating networks of moved nodes with multiple mobility scripts --- daemon/core/emane/emanemodel.py | 4 +--- daemon/core/location/mobility.py | 38 ++++++++++++-------------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 0ee9aa404..755f07aa9 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -12,7 +12,6 @@ from core.emulator.enumerations import ConfigDataTypes from core.errors import CoreError from core.location.mobility import WirelessModel -from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface from core.xml import emanexml @@ -119,13 +118,12 @@ def post_startup(self) -> None: """ logging.debug("emane model(%s) has no post setup tasks", self.name) - def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: + def update(self, moved_ifaces: List[CoreInterface]) -> None: """ Invoked from MobilityModel when nodes are moved; this causes emane location events to be generated for the nodes in the moved list, making EmaneModels compatible with Ns2ScriptedMobility. - :param moved: moved nodes :param moved_ifaces: interfaces that were moved :return: nothing """ diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index a548433cb..9262620ae 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -173,24 +173,20 @@ def sendevent(self, model: "WayPointMobility") -> None: self.session.broadcast_event(event_data) def update_nets( - self, moved: List[CoreNode], moved_ifaces: List[CoreInterface] + self, net: Union[WlanNode, EmaneNet], moved_ifaces: List[CoreInterface] ) -> None: """ - A mobility script has caused nodes in the 'moved' list to move. - Update every mobility network. This saves range calculations if the model - were to recalculate for each individual node movement. + A mobility script has caused a set of interfaces to move for a given + mobility network. Update the network of the moved interfaces. - :param moved: moved nodes + :param net: network interfaces were moved witin :param moved_ifaces: moved network interfaces :return: nothing """ - for node_id in self.nodes(): - try: - node = get_mobility_node(self.session, node_id) - if node.model: - node.model.update(moved, moved_ifaces) - except CoreError: - logging.exception("error updating mobility node") + if not net.model: + logging.error("moved interfaces network has no model: %s", net.name) + return + net.model.update(moved_ifaces) class WirelessModel(ConfigurableOptions): @@ -223,11 +219,10 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ return [] - def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: + def update(self, moved_ifaces: List[CoreInterface]) -> None: """ Update this wireless model. - :param moved: moved nodes :param moved_ifaces: moved network interfaces :return: nothing """ @@ -369,14 +364,13 @@ def set_position(self, iface: CoreInterface) -> None: position_callback = set_position - def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: + def update(self, moved_ifaces: List[CoreInterface]) -> None: """ Node positions have changed without recalc. Update positions from node.position, then re-calculate links for those that have moved. Assumes bidirectional links, with one calculation per node pair, where one of the nodes has moved. - :param moved: moved nodes :param moved_ifaces: moved network interfaces :return: nothing """ @@ -420,7 +414,6 @@ def calclink(self, iface: CoreInterface, iface2: CoreInterface) -> None: with self.wlan._linked_lock: linked = self.wlan.linked(a, b) - if d > self.range: if linked: logging.debug("was linked, unlinking") @@ -638,17 +631,15 @@ def runround(self) -> None: return return self.run() - # only move interfaces attached to self.wlan, or all nodenum in script? - moved = [] + # TODO: only move interfaces attached to self.wlan, or all nodenum in script? moved_ifaces = [] for iface in self.net.get_ifaces(): node = iface.node if self.movenode(node, dt): - moved.append(node) moved_ifaces.append(iface) # calculate all ranges after moving nodes; this saves calculations - self.session.mobility.update_nets(moved, moved_ifaces) + self.session.mobility.update_nets(self.net, moved_ifaces) # TODO: check session state self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround) @@ -719,7 +710,7 @@ def movenodesinitial(self) -> None: :return: nothing """ - moved = [] + # TODO: only move interfaces attached to self.wlan, or all nodenum in script? moved_ifaces = [] for iface in self.net.get_ifaces(): node = iface.node @@ -727,9 +718,8 @@ def movenodesinitial(self) -> None: continue x, y, z = self.initial[node.id].coords self.setnodeposition(node, x, y, z) - moved.append(node) moved_ifaces.append(iface) - self.session.mobility.update_nets(moved, moved_ifaces) + self.session.mobility.update_nets(self.net, moved_ifaces) def addwaypoint( self, From 02d8a32a50c718a759364ef884b849e45e5320f1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 9 Dec 2020 16:05:36 -0800 Subject: [PATCH 10/21] daemon: removing function to move nodes when mobility already has the network, updated logging for mobility script control to include file name being used --- daemon/core/location/mobility.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 9262620ae..c3b97e7c2 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -172,22 +172,6 @@ def sendevent(self, model: "WayPointMobility") -> None: ) self.session.broadcast_event(event_data) - def update_nets( - self, net: Union[WlanNode, EmaneNet], moved_ifaces: List[CoreInterface] - ) -> None: - """ - A mobility script has caused a set of interfaces to move for a given - mobility network. Update the network of the moved interfaces. - - :param net: network interfaces were moved witin - :param moved_ifaces: moved network interfaces - :return: nothing - """ - if not net.model: - logging.error("moved interfaces network has no model: %s", net.name) - return - net.model.update(moved_ifaces) - class WirelessModel(ConfigurableOptions): """ @@ -631,7 +615,6 @@ def runround(self) -> None: return return self.run() - # TODO: only move interfaces attached to self.wlan, or all nodenum in script? moved_ifaces = [] for iface in self.net.get_ifaces(): node = iface.node @@ -639,7 +622,7 @@ def runround(self) -> None: moved_ifaces.append(iface) # calculate all ranges after moving nodes; this saves calculations - self.session.mobility.update_nets(self.net, moved_ifaces) + self.net.model.update(moved_ifaces) # TODO: check session state self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround) @@ -650,7 +633,6 @@ def run(self) -> None: :return: nothing """ - logging.info("running mobility scenario") self.timezero = time.monotonic() self.lasttime = self.timezero - (0.001 * self.refresh_ms) self.movenodesinitial() @@ -710,7 +692,6 @@ def movenodesinitial(self) -> None: :return: nothing """ - # TODO: only move interfaces attached to self.wlan, or all nodenum in script? moved_ifaces = [] for iface in self.net.get_ifaces(): node = iface.node @@ -719,7 +700,7 @@ def movenodesinitial(self) -> None: x, y, z = self.initial[node.id].coords self.setnodeposition(node, x, y, z) moved_ifaces.append(iface) - self.session.mobility.update_nets(self.net, moved_ifaces) + self.net.model.update(moved_ifaces) def addwaypoint( self, @@ -1104,7 +1085,7 @@ def start(self) -> None: :return: nothing """ - logging.info("starting script") + logging.info("starting script: %s", self.file) laststate = self.state super().start() if laststate == self.STATE_PAUSED: @@ -1125,6 +1106,7 @@ def pause(self) -> None: :return: nothing """ + logging.info("pausing script: %s", self.file) super().pause() self.statescript("pause") @@ -1136,6 +1118,7 @@ def stop(self, move_initial: bool = True) -> None: position :return: nothing """ + logging.info("stopping script: %s", self.file) super().stop(move_initial=move_initial) self.statescript("stop") From d1c2b1bdb955761fa991d512750838fda600c1f6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 9 Dec 2020 21:22:19 -0800 Subject: [PATCH 11/21] pygui: fixed configuring node back to default server after switching to a distributed server --- daemon/core/gui/dialogs/nodeconfig.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index aad2d2805..de5916315 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -22,6 +22,7 @@ from core.gui.graph.node import CanvasNode IFACE_NAME_LEN: int = 15 +DEFAULT_SERVER: str = "localhost" def check_ip6(parent: tk.BaseWidget, name: str, value: str) -> bool: @@ -183,7 +184,7 @@ def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: self.name: tk.StringVar = tk.StringVar(value=self.node.name) self.type: tk.StringVar = tk.StringVar(value=self.node.model) self.container_image: tk.StringVar = tk.StringVar(value=self.node.image) - server = "localhost" + server = DEFAULT_SERVER if self.node.server: server = self.node.server self.server: tk.StringVar = tk.StringVar(value=server) @@ -250,7 +251,7 @@ def draw(self) -> None: frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text="Server") label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY) - servers = ["localhost"] + servers = [DEFAULT_SERVER] servers.extend(list(sorted(self.app.core.servers.keys()))) combobox = ttk.Combobox( frame, textvariable=self.server, values=servers, state=combo_state @@ -382,8 +383,11 @@ def click_apply(self) -> None: if NodeUtils.is_image_node(self.node.type): self.node.image = self.container_image.get() server = self.server.get() - if NodeUtils.is_container_node(self.node.type) and server != "localhost": - self.node.server = server + if NodeUtils.is_container_node(self.node.type): + if server == DEFAULT_SERVER: + self.node.server = None + else: + self.node.server = server # set custom icon if self.image_file: From e7320a61a6707904f30fb33914b07218d4dadb3b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 10 Dec 2020 15:16:05 -0800 Subject: [PATCH 12/21] daemon: revert wlan mac learning change, due to undesired default behavior, there may be some cases where this behavior is desired, so the option to enable a promiscuous mode has been added and will be present in core-pygui --- daemon/core/location/mobility.py | 16 ++++++++++++++++ daemon/core/nodes/netclient.py | 7 ++++--- daemon/core/nodes/network.py | 6 +++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index c3b97e7c2..95516ce86 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -31,6 +31,9 @@ if TYPE_CHECKING: from core.emulator.session import Session +LEARNING_DISABLED: int = 0 +LEARNING_ENABLED: int = 30000 + def get_mobility_node(session: "Session", node_id: int) -> Union[WlanNode, EmaneNet]: try: @@ -259,6 +262,12 @@ class BasicRangeModel(WirelessModel): Configuration( _id="error", _type=ConfigDataTypes.STRING, default="0", label="loss (%)" ), + Configuration( + _id="promiscuous", + _type=ConfigDataTypes.BOOL, + default="0", + label="promiscuous mode", + ), ] @classmethod @@ -282,6 +291,7 @@ def __init__(self, session: "Session", _id: int) -> None: self.delay: Optional[int] = None self.loss: Optional[float] = None self.jitter: Optional[int] = None + self.promiscuous: bool = False def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int: """ @@ -444,6 +454,12 @@ def update_config(self, config: Dict[str, str]) -> None: self.delay = self._get_config(self.delay, config, "delay") self.loss = self._get_config(self.loss, config, "error") self.jitter = self._get_config(self.jitter, config, "jitter") + promiscuous = config["promiscuous"] == "1" + if self.promiscuous and not promiscuous: + self.wlan.net_client.set_mac_learning(self.wlan.brname, LEARNING_ENABLED) + elif not self.promiscuous and promiscuous: + self.wlan.net_client.set_mac_learning(self.wlan.brname, LEARNING_DISABLED) + self.promiscuous = promiscuous self.setlinkparams() def create_link_data( diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 68fbef985..729550b6f 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -286,14 +286,15 @@ def existing_bridges(self, _id: int) -> bool: return True return False - def disable_mac_learning(self, name: str) -> None: + def set_mac_learning(self, name: str, value: int) -> None: """ - Disable mac learning for a Linux bridge. + Set mac learning for a Linux bridge. :param name: bridge name + :param value: ageing time value :return: nothing """ - self.run(f"{IP} link set {name} type bridge ageing_time 0") + self.run(f"{IP} link set {name} type bridge ageing_time {value}") class OvsNetClient(LinuxNetClient): diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index aef4b04b7..cb3aca792 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -32,7 +32,8 @@ WirelessModelType = Type[WirelessModel] -ebtables_lock = threading.Lock() +LEARNING_DISABLED: int = 0 +ebtables_lock: threading.Lock = threading.Lock() class EbtablesQueue: @@ -946,7 +947,7 @@ def startup(self) -> None: :return: nothing """ super().startup() - self.net_client.disable_mac_learning(self.brname) + self.net_client.set_mac_learning(self.brname, LEARNING_DISABLED) class WlanNode(CoreNetwork): @@ -989,7 +990,6 @@ def startup(self) -> None: :return: nothing """ super().startup() - self.net_client.disable_mac_learning(self.brname) ebq.ebchange(self) def attach(self, iface: CoreInterface) -> None: From d6b95bab24e272d6f65eb9fe46000d26634f9cb1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 11 Dec 2020 18:46:29 -0800 Subject: [PATCH 13/21] install: adjustment to account for /etc/os-release that does not have an ID_LIKE field --- tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 075119a27..bdebffc11 100644 --- a/tasks.py +++ b/tasks.py @@ -100,6 +100,7 @@ def get(cls, name: str, like: List[str], version: Optional[str]) -> "OsInfo": if not os_like: like = " ".join(like) print(f"unsupported os install type({like})") + print("trying using the -i option to specify an install type") sys.exit(1) if version: try: @@ -141,7 +142,7 @@ def get_os(install_type: Optional[str]) -> OsInfo: key, value = line.split("=") d[key] = value.strip("\"") name_value = d["ID"] - like_value = d["ID_LIKE"] + like_value = d.get("ID_LIKE", "") version_value = d["VERSION_ID"] return OsInfo.get(name_value, like_value.split(), version_value) From ad839bbc07f39d7e3f19357e2785e8e5d7b31a12 Mon Sep 17 00:00:00 2001 From: Riley Baxter Date: Mon, 14 Dec 2020 13:08:52 -0500 Subject: [PATCH 14/21] Fix session id attribute name in UDP TLV API Handler --- daemon/core/api/tlv/corehandlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index b99b86308..65abed8c4 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -2029,7 +2029,7 @@ def handle(self): for session_id in sessions: session = self.server.mainserver.coreemu.sessions.get(session_id) if session: - logging.debug("session handling message: %s", session.session_id) + logging.debug("session handling message: %s", session.id) self.session = session self.handle_message(message) self.broadcast(message) From 4b6afe4db7dacc5928e8ac8e26b49c321eee00c4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 15 Dec 2020 09:34:42 -0800 Subject: [PATCH 15/21] daemon: fix for deleting an interface from rj45 node, better error messaging when trying to add an interface to a node that already exists --- daemon/core/nodes/base.py | 7 ++++++- daemon/core/nodes/physical.py | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 9069b14c3..9a301432a 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -837,7 +837,12 @@ def new_iface( if net.has_custom_iface: return net.custom_iface(self, iface_data) else: - iface_id = self.newveth(iface_data.id, iface_data.name) + iface_id = iface_data.id + if iface_id is not None and iface_id in self.ifaces: + raise CoreError( + f"node({self.name}) already has interface({iface_id})" + ) + iface_id = self.newveth(iface_id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: self.set_mac(iface_id, iface_data.mac) diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index e69985ef3..4e8c94644 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -266,7 +266,9 @@ def __init__( will run on, default is None for localhost """ super().__init__(session, _id, name, server) - self.iface = CoreInterface(session, self, name, name, mtu, server) + self.iface: CoreInterface = CoreInterface( + session, self, name, name, mtu, server + ) self.iface.transport_type = TransportType.RAW self.lock: threading.RLock = threading.RLock() self.iface_id: Optional[int] = None @@ -335,11 +337,12 @@ def new_iface( if iface_id is None: iface_id = 0 if self.iface.net is not None: - raise CoreError("RJ45 nodes support at most 1 network interface") + raise CoreError( + f"RJ45({self.name}) nodes support at most 1 network interface" + ) self.ifaces[iface_id] = self.iface self.iface_id = iface_id - if net is not None: - self.iface.attachnet(net) + self.iface.attachnet(net) for ip in iface_data.get_ips(): self.add_ip(ip) return self.iface @@ -353,6 +356,12 @@ def delete_iface(self, iface_id: int) -> None: """ self.get_iface(iface_id) self.ifaces.pop(iface_id) + if self.iface.net is None: + raise CoreError( + f"RJ45({self.name}) is not currently connected to a network" + ) + self.iface.detachnet() + self.iface.net = None self.shutdown() def get_iface(self, iface_id: int) -> CoreInterface: From 4ec9ea7b1622de31dc4171ef41bfc1e192ef89b8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 16 Dec 2020 10:19:17 -0800 Subject: [PATCH 16/21] daemon: small cleanup to boot nodes logic, moved control interface creation to occur before service startup avoiding thread race conditions validating if an interface is for a control network --- daemon/core/emulator/session.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index d98564ede..29f85917f 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -13,7 +13,7 @@ import threading import time from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union from core import constants, utils from core.configservice.manager import ConfigServiceManager @@ -571,11 +571,11 @@ def add_node( if isinstance(node, WlanNode): self.mobility.set_model_config(_id, BasicRangeModel.name) - # boot nodes after runtime, CoreNodes, Physical, and RJ45 are all nodes - is_boot_node = isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node) + # boot nodes after runtime CoreNodes and PhysicalNodes + is_boot_node = isinstance(node, (CoreNode, PhysicalNode)) if self.state == EventTypes.RUNTIME_STATE and is_boot_node: self.write_nodes() - self.add_remove_control_iface(node=node, remove=False) + self.add_remove_control_iface(node, remove=False) self.services.boot_services(node) self.sdt.add_node(node) @@ -1310,7 +1310,6 @@ def boot_node(self, node: CoreNode) -> None: :return: nothing """ logging.info("booting node(%s): %s", node.name, [x.name for x in node.services]) - self.add_remove_control_iface(node=node, remove=False) self.services.boot_services(node) node.start_config_services() @@ -1325,11 +1324,10 @@ def boot_nodes(self) -> List[Exception]: with self.nodes_lock: funcs = [] start = time.monotonic() - for _id in self.nodes: - node = self.nodes[_id] - if isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node): - args = (node,) - funcs.append((self.boot_node, args, {})) + for node in self.nodes.values(): + if isinstance(node, (CoreNode, PhysicalNode)): + self.add_remove_control_iface(node, remove=False) + funcs.append((self.boot_node, (node,), {})) results, exceptions = utils.threadpool(funcs) total = time.monotonic() - start logging.debug("boot run time: %s", total) @@ -1477,7 +1475,7 @@ def add_remove_control_net( def add_remove_control_iface( self, - node: CoreNode, + node: Union[CoreNode, PhysicalNode], net_index: int = 0, remove: bool = False, conf_required: bool = True, From 2e77907d7235422dbab4571142415dd6b17661ef Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 16 Dec 2020 21:32:47 -0800 Subject: [PATCH 17/21] daemon: removed unused variable in sdt code and update to avoid deadlock when deleting nodes due to sdt trying to leverage the same lock on reconnect --- daemon/core/emulator/session.py | 5 ++++- daemon/core/plugins/sdt.py | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 29f85917f..9264ce84b 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1129,13 +1129,16 @@ def delete_nodes(self) -> None: """ Clear the nodes dictionary, and call shutdown for each node. """ + nodes_ids = [] with self.nodes_lock: funcs = [] while self.nodes: _, node = self.nodes.popitem() - self.sdt.delete_node(node.id) + nodes_ids.append(node.id) funcs.append((node.shutdown, [], {})) utils.threadpool(funcs) + for node_id in nodes_ids: + self.sdt.delete_node(node_id) def write_nodes(self) -> None: """ diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 27e54ff33..4d56f1a96 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -4,7 +4,6 @@ import logging import socket -import threading from typing import IO, TYPE_CHECKING, Dict, Optional, Set, Tuple from urllib.parse import urlparse @@ -65,7 +64,6 @@ def __init__(self, session: "Session") -> None: :param session: session this manager is tied to """ self.session: "Session" = session - self.lock: threading.Lock = threading.Lock() self.sock: Optional[IO] = None self.connected: bool = False self.url: str = self.DEFAULT_SDT_URL From f91952005860e093968c952c3acd0131803e5c92 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 5 Jan 2021 15:28:50 -0800 Subject: [PATCH 18/21] Setting the args in distributed_switch.py to required --- daemon/examples/grpc/distributed_switch.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index 0d781c19f..e8ddfb4c7 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -74,10 +74,14 @@ def main(args): parser.add_argument( "-a", "--address", + required=True, help="local address that distributed servers will use for gre tunneling", ) parser.add_argument( - "-s", "--server", help="distributed server to use for creating nodes" + "-s", + "--server", + required=True, + help="distributed server to use for creating nodes", ) args = parser.parse_args() main(args) From a660b01e93aca51df1d62e5d2666357b95e4f04d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 7 Jan 2021 14:24:57 -0800 Subject: [PATCH 19/21] pygui: validation of the node name field will allow - instead of _, - is valid while _ is not --- daemon/core/gui/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/gui/validation.py b/daemon/core/gui/validation.py index 22f12bb8b..2360ab0b0 100644 --- a/daemon/core/gui/validation.py +++ b/daemon/core/gui/validation.py @@ -107,7 +107,7 @@ def is_valid(self, s: str) -> bool: if len(s) == 0: return True for x in s: - if not x.isalnum() and x != "_": + if not x.isalnum() and x != "-": return False return True From 4904f7170f40516226ba3040455736485d725fd6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 11 Jan 2021 00:34:38 -0800 Subject: [PATCH 20/21] updated version for next release --- configure.ac | 2 +- daemon/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 7b91b304c..d8e6a34c3 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 7.3.0) +AC_INIT(core, 7.4.0) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 6916c1970..44d4b2279 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "core" -version = "7.3.0" +version = "7.4.0" description = "CORE Common Open Research Emulator" authors = ["Boeing Research and Technology"] license = "BSD-2-Clause" From d533083b5f1c4be9279a8e5eecfe37b97411fe0a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 11 Jan 2021 00:39:38 -0800 Subject: [PATCH 21/21] updated changelog for next release --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b5c711c..f02b0eece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## 2021-01-11 CORE 7.4.0 + +* Installation + * fixed issue for automated install assuming ID_LIKE is always present in /etc/os-release +* gRPC API + * fixed issue stopping session and not properly going to data collect state + * fixed issue to have start session properly create a directory before configuration state +* core-pygui + * fixed issue handling deletion of wired link to a switch + * avoid saving edge metadata to xml when values are default + * fixed issue editing node mac addresses + * added support for configuring interface names + * fixed issue with potential node names to allow hyphens and remove under bars + * \#531 - fixed issue changing distributed nodes back to local +* core-daemon + * fixed issue to properly handle deleting links from a network to network node + * updated xml to support writing and reading link buffer configurations + * reverted change and removed mac learning from wlan, due to promiscuous like behavior + * fixed issue creating control interfaces when starting services + * fixed deadlock issue when clearing a session using sdt + * \#116 - fixed issue for wlans handling multiple mobility scripts at once + * \#539 - fixed issue in udp tlv api + ## 2020-12-02 CORE 7.3.0 * core-daemon