summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgpd.c5
-rw-r--r--doc/user/bgp.rst4
-rw-r--r--lib/mgmt_msg_native.h27
-rw-r--r--nhrpd/nhrp_main.c4
-rw-r--r--nhrpd/nhrp_shortcut.c12
-rw-r--r--ospf6d/ospf6_auth_trailer.c9
-rw-r--r--tests/topotests/munet/__main__.py24
-rw-r--r--tests/topotests/munet/base.py60
-rw-r--r--tests/topotests/munet/cleanup.py34
-rw-r--r--tests/topotests/munet/cli.py12
-rw-r--r--tests/topotests/munet/logconf-mutest.yaml5
-rw-r--r--tests/topotests/munet/mucmd.py4
-rw-r--r--tests/topotests/munet/mulog.py26
-rw-r--r--tests/topotests/munet/munet-schema.json9
-rw-r--r--tests/topotests/munet/mutest/__main__.py15
-rw-r--r--tests/topotests/munet/mutest/userapi.py8
-rw-r--r--tests/topotests/munet/native.py216
-rw-r--r--tests/topotests/munet/testing/fixtures.py24
-rw-r--r--tests/topotests/munet/watchlog.py1
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py131
-rw-r--r--zebra/dplane_fpm_nl.c16
-rw-r--r--zebra/zebra_netns_notify.c15
22 files changed, 479 insertions, 182 deletions
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index d8eba0ab22..5c79535089 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -3620,10 +3620,13 @@ struct bgp *bgp_lookup_by_name(const char *name)
struct bgp *bgp;
struct listnode *node, *nnode;
- for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp))
+ for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) {
+ if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO))
+ continue;
if ((bgp->name == NULL && name == NULL)
|| (bgp->name && name && strcmp(bgp->name, name) == 0))
return bgp;
+ }
return NULL;
}
diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst
index 58fdbf969b..150a915e3a 100644
--- a/doc/user/bgp.rst
+++ b/doc/user/bgp.rst
@@ -4102,6 +4102,10 @@ The following are available in the top level *enable* mode:
Clear all peers.
+.. clicmd:: clear bgp ipv4|ipv6 ASNUM
+
+ Clear peers with the AS number in plain or dotted format.
+
.. clicmd:: clear bgp ipv4|ipv6 \*
Clear all peers with this address-family activated.
diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h
index cf528a6356..21f702cc61 100644
--- a/lib/mgmt_msg_native.h
+++ b/lib/mgmt_msg_native.h
@@ -4,6 +4,15 @@
*
* Copyright (c) 2023, LabN Consulting, L.L.C.
*
+ * Public APIs:
+ *
+ * The message type codes and corresponding message data definitions for
+ * front-end client messages represent a public API, as such any changes should
+ * only be made according to backward compatible principles (basically never,
+ * just use a new message type). Back-end clients being always compiled with FRR
+ * can be updated (although one should take care in modifying BE messages as it
+ * could impact private back-end client implementations which will then need to
+ * be updated by their owners).
*/
#ifndef _FRR_MGMT_MSG_NATIVE_H_
@@ -158,15 +167,15 @@ DECLARE_MTYPE(MSG_NATIVE_RPC_REPLY);
/*
* Native message codes
*/
-#define MGMT_MSG_CODE_ERROR 0
-#define MGMT_MSG_CODE_GET_TREE 1
-#define MGMT_MSG_CODE_TREE_DATA 2
-#define MGMT_MSG_CODE_GET_DATA 3
-#define MGMT_MSG_CODE_NOTIFY 4
-#define MGMT_MSG_CODE_EDIT 5
-#define MGMT_MSG_CODE_EDIT_REPLY 6
-#define MGMT_MSG_CODE_RPC 7
-#define MGMT_MSG_CODE_RPC_REPLY 8
+#define MGMT_MSG_CODE_ERROR 0 /* Public API */
+#define MGMT_MSG_CODE_GET_TREE 1 /* BE only, non-public API */
+#define MGMT_MSG_CODE_TREE_DATA 2 /* Public API */
+#define MGMT_MSG_CODE_GET_DATA 3 /* Public API */
+#define MGMT_MSG_CODE_NOTIFY 4 /* Public API */
+#define MGMT_MSG_CODE_EDIT 5 /* Public API */
+#define MGMT_MSG_CODE_EDIT_REPLY 6 /* Public API */
+#define MGMT_MSG_CODE_RPC 7 /* Public API */
+#define MGMT_MSG_CODE_RPC_REPLY 8 /* Public API */
/*
* Datastores
diff --git a/nhrpd/nhrp_main.c b/nhrpd/nhrp_main.c
index 5d492249d3..adb8be36d3 100644
--- a/nhrpd/nhrp_main.c
+++ b/nhrpd/nhrp_main.c
@@ -83,13 +83,13 @@ static void nhrp_request_stop(void)
debugf(NHRP_DEBUG_COMMON, "Exiting...");
frr_early_fini();
- vrf_terminate();
+ nhrp_shortcut_terminate();
nhrp_nhs_terminate();
nhrp_zebra_terminate();
vici_terminate();
evmgr_terminate();
+ vrf_terminate();
nhrp_vc_terminate();
- nhrp_shortcut_terminate();
debugf(NHRP_DEBUG_COMMON, "Done.");
diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c
index 04dad2aea6..e83ce7f58f 100644
--- a/nhrpd/nhrp_shortcut.c
+++ b/nhrpd/nhrp_shortcut.c
@@ -19,7 +19,8 @@ DEFINE_MTYPE_STATIC(NHRPD, NHRP_SHORTCUT, "NHRP shortcut");
static struct route_table *shortcut_rib[AFI_MAX];
static void nhrp_shortcut_do_purge(struct event *t);
-static void nhrp_shortcut_delete(struct nhrp_shortcut *s);
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s,
+ void *arg __attribute__((__unused__)));
static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s);
static void nhrp_shortcut_check_use(struct nhrp_shortcut *s)
@@ -72,7 +73,7 @@ static void nhrp_shortcut_cache_notify(struct notifier_block *n,
s->route_installed = 0;
}
if (cmd == NOTIFY_CACHE_DELETE)
- nhrp_shortcut_delete(s);
+ nhrp_shortcut_delete(s, NULL);
break;
}
}
@@ -132,7 +133,8 @@ static void nhrp_shortcut_update_binding(struct nhrp_shortcut *s,
}
}
-static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s,
+ void *arg __attribute__((__unused__)))
{
struct route_node *rn;
afi_t afi = family2afi(PREFIX_FAMILY(s->p));
@@ -158,7 +160,7 @@ static void nhrp_shortcut_do_purge(struct event *t)
{
struct nhrp_shortcut *s = EVENT_ARG(t);
s->t_timer = NULL;
- nhrp_shortcut_delete(s);
+ nhrp_shortcut_delete(s, NULL);
}
static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
@@ -469,6 +471,8 @@ void nhrp_shortcut_init(void)
void nhrp_shortcut_terminate(void)
{
+ nhrp_shortcut_foreach(AFI_IP, nhrp_shortcut_delete, NULL);
+ nhrp_shortcut_foreach(AFI_IP6, nhrp_shortcut_delete, NULL);
route_table_finish(shortcut_rib[AFI_IP]);
route_table_finish(shortcut_rib[AFI_IP6]);
}
diff --git a/ospf6d/ospf6_auth_trailer.c b/ospf6d/ospf6_auth_trailer.c
index 8d9eff409e..860d273796 100644
--- a/ospf6d/ospf6_auth_trailer.c
+++ b/ospf6d/ospf6_auth_trailer.c
@@ -517,6 +517,15 @@ int ospf6_auth_check_digest(struct ospf6_header *oh, struct ospf6_interface *oi,
}
} else if (CHECK_FLAG(oi->at_data.flags,
OSPF6_AUTH_TRAILER_MANUAL_KEY)) {
+ if (oi->at_data.key_id != ntohs(ospf6_auth->id)) {
+ if (IS_OSPF6_DEBUG_AUTH_RX)
+ zlog_err("RECV[%s]: Auth SA ID mismatch for %s, received %u vs configured %u",
+ oi->interface->name,
+ ospf6_message_type(oh->type),
+ ntohs(ospf6_auth->id),
+ oi->at_data.key_id);
+ return OSPF6_AUTH_VALIDATE_FAILURE;
+ }
auth_str = oi->at_data.auth_key;
hash_algo = oi->at_data.hash_algo;
}
diff --git a/tests/topotests/munet/__main__.py b/tests/topotests/munet/__main__.py
index e50fea6915..145eb265ab 100644
--- a/tests/topotests/munet/__main__.py
+++ b/tests/topotests/munet/__main__.py
@@ -19,6 +19,7 @@ from . import parser
from .args import add_launch_args
from .base import get_event_loop
from .cleanup import cleanup_previous
+from .cleanup import is_running_in_rundir
from .compat import PytestConfig
@@ -139,10 +140,11 @@ def main(*args):
eap = ap.add_argument_group(title="Uncommon", description="uncommonly used options")
eap.add_argument("--log-config", help="logging config file (yaml, toml, json, ...)")
eap.add_argument(
- "--no-kill",
+ "--kill",
action="store_true",
- help="Do not kill previous running processes",
+ help="Kill previous running processes using same rundir and exit",
)
+ eap.add_argument("--no-kill", action="store_true", help=argparse.SUPPRESS)
eap.add_argument(
"--no-cli", action="store_true", help="Do not run the interactive CLI"
)
@@ -157,7 +159,18 @@ def main(*args):
sys.exit(0)
rundir = args.rundir if args.rundir else "/tmp/munet"
+ rundir = os.path.abspath(rundir)
args.rundir = rundir
+
+ if args.kill:
+ logging.info("Killing any previous run using rundir: {rundir}")
+ cleanup_previous(args.rundir)
+ elif is_running_in_rundir(args.rundir):
+ logging.fatal(
+ "Munet processes using rundir: %s, use `--kill` to cleanup first", rundir
+ )
+ return 1
+
if args.cleanup:
if os.path.exists(rundir):
if not os.path.exists(f"{rundir}/config.json"):
@@ -169,6 +182,10 @@ def main(*args):
sys.exit(1)
else:
subprocess.run(["/usr/bin/rm", "-rf", rundir], check=True)
+
+ if args.kill:
+ return 0
+
subprocess.run(f"mkdir -p {rundir} && chmod 755 {rundir}", check=True, shell=True)
os.environ["MUNET_RUNDIR"] = rundir
@@ -183,9 +200,6 @@ def main(*args):
logger.critical("No nodes defined in config file")
return 1
- if not args.no_kill:
- cleanup_previous()
-
loop = None
status = 4
try:
diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py
index a4811f179c..e77eb15dc8 100644
--- a/tests/topotests/munet/base.py
+++ b/tests/topotests/munet/base.py
@@ -469,6 +469,8 @@ class Commander: # pylint: disable=R0904
env = {**(kwargs["env"] if "env" in kwargs else os.environ)}
if "MUNET_NODENAME" not in env:
env["MUNET_NODENAME"] = self.name
+ if "MUNET_PID" not in env and "MUNET_PID" in os.environ:
+ env["MUNET_PID"] = os.environ["MUNET_PID"]
kwargs["env"] = env
defaults.update(kwargs)
@@ -780,8 +782,14 @@ class Commander: # pylint: disable=R0904
ps1 = re.escape(ps1)
ps2 = re.escape(ps2)
-
- extra = "PAGER=cat; export PAGER; TERM=dumb; unset HISTFILE; set +o emacs +o vi"
+ extra = [
+ "TERM=dumb",
+ "set +o emacs",
+ "set +o vi",
+ "unset HISTFILE",
+ "PAGER=cat",
+ "export PAGER",
+ ]
pchg = "PS1='{0}' PS2='{1}' PROMPT_COMMAND=''\n".format(ps1p, ps2p)
p.send(pchg)
return ShellWrapper(p, ps1, ps2, extra_init_cmd=extra, will_echo=will_echo)
@@ -934,15 +942,25 @@ class Commander: # pylint: disable=R0904
def _cmd_status(self, cmds, raises=False, warn=True, stdin=None, **kwargs):
"""Execute a command."""
+ timeout = None
+ if "timeout" in kwargs:
+ timeout = kwargs["timeout"]
+ del kwargs["timeout"]
+
pinput, stdin = Commander._cmd_status_input(stdin)
p, actual_cmd = self._popen("cmd_status", cmds, stdin=stdin, **kwargs)
- o, e = p.communicate(pinput)
+ o, e = p.communicate(pinput, timeout=timeout)
return self._cmd_status_finish(p, cmds, actual_cmd, o, e, raises, warn)
async def _async_cmd_status(
self, cmds, raises=False, warn=True, stdin=None, text=None, **kwargs
):
"""Execute a command."""
+ timeout = None
+ if "timeout" in kwargs:
+ timeout = kwargs["timeout"]
+ del kwargs["timeout"]
+
pinput, stdin = Commander._cmd_status_input(stdin)
p, actual_cmd = await self._async_popen(
"async_cmd_status", cmds, stdin=stdin, **kwargs
@@ -955,7 +973,12 @@ class Commander: # pylint: disable=R0904
if encoding is not None and isinstance(pinput, str):
pinput = pinput.encode(encoding)
- o, e = await p.communicate(pinput)
+ try:
+ o, e = await asyncio.wait_for(p.communicate(), timeout=timeout)
+ except (TimeoutError, asyncio.TimeoutError) as error:
+ raise subprocess.TimeoutExpired(
+ cmd=actual_cmd, timeout=timeout, output=None, stderr=None
+ ) from error
if encoding is not None:
o = o.decode(encoding) if o is not None else o
e = e.decode(encoding) if e is not None else e
@@ -1220,7 +1243,13 @@ class Commander: # pylint: disable=R0904
if self.is_vm and self.use_ssh and not ns_only: # pylint: disable=E1101
if isinstance(cmd, str):
cmd = shlex.split(cmd)
- cmd = ["/usr/bin/env", f"MUNET_NODENAME={self.name}"] + cmd
+ cmd = [
+ "/usr/bin/env",
+ f"MUNET_NODENAME={self.name}",
+ ]
+ if "MUNET_PID" in os.environ:
+ cmd.append(f"MUNET_PID={os.environ.get('MUNET_PID')}")
+ cmd += cmd
# get the ssh cmd
cmd = self._get_pre_cmd(False, True, ns_only=ns_only) + [shlex.join(cmd)]
@@ -1240,6 +1269,8 @@ class Commander: # pylint: disable=R0904
envvars = f"MUNET_NODENAME={self.name} NODENAME={self.name}"
if hasattr(self, "rundir"):
envvars += f" RUNDIR={self.rundir}"
+ if "MUNET_PID" in os.environ:
+ envvars += f" MUNET_PID={os.environ.get('MUNET_PID')}"
if hasattr(self.unet, "config_dirname") and self.unet.config_dirname:
envvars += f" CONFIGDIR={self.unet.config_dirname}"
elif "CONFIGDIR" in os.environ:
@@ -2520,7 +2551,7 @@ class Bridge(SharedNamespace, InterfaceMixin):
self.logger.debug("Bridge: Creating")
- assert len(self.name) <= 16 # Make sure fits in IFNAMSIZE
+ # assert len(self.name) <= 16 # Make sure fits in IFNAMSIZE
self.cmd_raises(f"ip link delete {name} || true")
self.cmd_raises(f"ip link add {name} type bridge")
if self.mtu:
@@ -2644,10 +2675,6 @@ class BaseMunet(LinuxNamespace):
self.cfgopt = munet_config.ConfigOptionsProxy(pytestconfig)
- super().__init__(
- name, mount=True, net=isolated, uts=isolated, pid=pid, unet=None, **kwargs
- )
-
# This allows us to cleanup any leftover running munet's
if "MUNET_PID" in os.environ:
if os.environ["MUNET_PID"] != str(our_pid):
@@ -2658,6 +2685,10 @@ class BaseMunet(LinuxNamespace):
)
os.environ["MUNET_PID"] = str(our_pid)
+ super().__init__(
+ name, mount=True, net=isolated, uts=isolated, pid=pid, unet=None, **kwargs
+ )
+
# this is for testing purposes do not use
if not BaseMunet.g_unet:
BaseMunet.g_unet = self
@@ -2765,7 +2796,7 @@ class BaseMunet(LinuxNamespace):
self.logger.error('"%s" len %s > 16', nsif1, len(nsif1))
elif len(nsif2) > 16:
self.logger.error('"%s" len %s > 16', nsif2, len(nsif2))
- assert len(nsif1) <= 16 and len(nsif2) <= 16 # Make sure fits in IFNAMSIZE
+ assert len(nsif1) < 16 and len(nsif2) < 16 # Make sure fits in IFNAMSIZE
self.logger.debug("%s: Creating veth pair for link %s", self, lname)
@@ -2993,8 +3024,11 @@ if True: # pylint: disable=using-constant-test
self._expectf = self.child.expect
if extra_init_cmd:
- self.expect_prompt()
- self.child.sendline(extra_init_cmd)
+ if isinstance(extra_init_cmd, str):
+ extra_init_cmd = [extra_init_cmd]
+ for ecmd in extra_init_cmd:
+ self.expect_prompt()
+ self.child.sendline(ecmd)
self.expect_prompt()
def expect_prompt(self, timeout=-1):
diff --git a/tests/topotests/munet/cleanup.py b/tests/topotests/munet/cleanup.py
index c641cda685..12ea6e2840 100644
--- a/tests/topotests/munet/cleanup.py
+++ b/tests/topotests/munet/cleanup.py
@@ -59,25 +59,33 @@ def _get_our_pids():
return {}
-def _get_other_pids():
- piddict = get_pids_with_env("MUNET_PID")
- unet_pids = {d["MUNET_PID"] for d in piddict.values()}
+def _get_other_pids(rundir):
+ if rundir:
+ # get only munet pids using the given rundir
+ piddict = get_pids_with_env("MUNET_RUNDIR", str(rundir))
+ else:
+ # Get all munet pids
+ piddict = get_pids_with_env("MUNET_PID")
+ unet_pids = {d["MUNET_PID"] for d in piddict.values() if "MUNET_PID" in d}
pids_by_upid = {p: set() for p in unet_pids}
for pid, envdict in piddict.items():
+ if "MUNET_PID" not in envdict:
+ continue
unet_pid = envdict["MUNET_PID"]
pids_by_upid[unet_pid].add(pid)
# Filter out any child pid sets whos munet pid is still running
return {x: y for x, y in pids_by_upid.items() if x not in y}
-def _get_pids_by_upid(ours):
+def _get_pids_by_upid(ours, rundir):
if ours:
+ assert rundir is None
return _get_our_pids()
- return _get_other_pids()
+ return _get_other_pids(rundir)
-def _cleanup_pids(ours):
- pids_by_upid = _get_pids_by_upid(ours).items()
+def _cleanup_pids(ours, rundir):
+ pids_by_upid = _get_pids_by_upid(ours, rundir).items()
if not pids_by_upid:
return
@@ -94,7 +102,7 @@ def _cleanup_pids(ours):
# return
# time.sleep(1)
- pids_by_upid = _get_pids_by_upid(ours).items()
+ pids_by_upid = _get_pids_by_upid(ours, rundir).items()
_kill_piddict(pids_by_upid, signal.SIGKILL)
@@ -103,12 +111,16 @@ def cleanup_current():
Currently this only scans for old processes.
"""
- _cleanup_pids(True)
+ _cleanup_pids(True, None)
-def cleanup_previous():
+def cleanup_previous(rundir=None):
"""Attempt to cleanup preview runs.
Currently this only scans for old processes.
"""
- _cleanup_pids(False)
+ _cleanup_pids(False, rundir)
+
+
+def is_running_in_rundir(rundir):
+ return bool(get_pids_with_env("MUNET_RUNDIR", str(rundir)))
diff --git a/tests/topotests/munet/cli.py b/tests/topotests/munet/cli.py
index 133644e85e..01a7091512 100644
--- a/tests/topotests/munet/cli.py
+++ b/tests/topotests/munet/cli.py
@@ -106,9 +106,13 @@ def is_host_regex(restr):
def get_host_regex(restr):
- if len(restr) < 3 or restr[0] != "/" or restr[-1] != "/":
+ try:
+ if len(restr) < 3 or restr[0] != "/" or restr[-1] != "/":
+ return None
+ return re.compile(restr[1:-1])
+ except re.error:
+ logging.error("Invalid regex")
return None
- return re.compile(restr[1:-1])
def host_in(restr, names):
@@ -126,8 +130,8 @@ def expand_host(restr, names):
hosts = []
regexp = get_host_regex(restr)
if not regexp:
- assert restr in names
- hosts.append(restr)
+ if restr in names:
+ hosts.append(restr)
else:
for name in names:
if regexp.fullmatch(name):
diff --git a/tests/topotests/munet/logconf-mutest.yaml b/tests/topotests/munet/logconf-mutest.yaml
index b450fb9382..c0b636cd61 100644
--- a/tests/topotests/munet/logconf-mutest.yaml
+++ b/tests/topotests/munet/logconf-mutest.yaml
@@ -1,5 +1,8 @@
version: 1
formatters:
+ result_color:
+ class: munet.mulog.ResultColorFormatter
+ format: '%(levelname)5s: %(message)s'
brief:
format: '%(levelname)5s: %(message)s'
operfmt:
@@ -22,7 +25,7 @@ handlers:
info_console:
level: INFO
class: logging.StreamHandler
- formatter: brief
+ formatter: result_color
stream: ext://sys.stderr
oper_console:
level: DEBUG
diff --git a/tests/topotests/munet/mucmd.py b/tests/topotests/munet/mucmd.py
index d6101e1a55..cd356f38ad 100644
--- a/tests/topotests/munet/mucmd.py
+++ b/tests/topotests/munet/mucmd.py
@@ -89,14 +89,14 @@ def main(*args):
ecmd = "/usr/bin/nsenter"
eargs = [ecmd]
- # start mucmd same way base process is started
+ #start mucmd same way base process is started
eargs.append(f"--mount=/proc/{pid}/ns/mnt")
eargs.append(f"--net=/proc/{pid}/ns/net")
eargs.append(f"--pid=/proc/{pid}/ns/pid_for_children")
eargs.append(f"--uts=/proc/{pid}/ns/uts")
eargs.append(f"--wd={rundir}")
eargs += args.shellcmd
- # print("Using ", eargs)
+ #print("Using ", eargs)
return os.execvpe(ecmd, eargs, {**env, **envcfg})
diff --git a/tests/topotests/munet/mulog.py b/tests/topotests/munet/mulog.py
index f840eae2d8..968acd9d19 100644
--- a/tests/topotests/munet/mulog.py
+++ b/tests/topotests/munet/mulog.py
@@ -12,6 +12,9 @@ import logging
from pathlib import Path
+do_color = True
+
+
class MultiFileHandler(logging.FileHandler):
"""A logging handler that logs to new files based on the logger name.
@@ -118,5 +121,28 @@ class ColorFormatter(logging.Formatter):
super().__init__(fmt, datefmt, style, **kwargs)
def format(self, record):
+ if not do_color:
+ return super().format(record)
formatter = self.formatters.get(record.levelno)
return formatter.format(record)
+
+
+class ResultColorFormatter(logging.Formatter):
+ """A formatter that colorizes PASS/FAIL strings based on level."""
+
+ green = "\x1b[32m"
+ red = "\x1b[31m"
+ reset = "\x1b[0m"
+
+ def format(self, record):
+ s = super().format(record)
+ if not do_color:
+ return s
+ idx = s.find("FAIL")
+ if idx >= 0 and record.levelno > logging.INFO:
+ s = s[:idx] + self.red + "FAIL" + self.reset + s[idx + 4 :]
+ elif record.levelno == logging.INFO:
+ idx = s.find("PASS")
+ if idx >= 0:
+ s = s[:idx] + self.green + "PASS" + self.reset + s[idx + 4 :]
+ return s
diff --git a/tests/topotests/munet/munet-schema.json b/tests/topotests/munet/munet-schema.json
index 7d577e63b3..6ebc368dcb 100644
--- a/tests/topotests/munet/munet-schema.json
+++ b/tests/topotests/munet/munet-schema.json
@@ -93,6 +93,9 @@
"image": {
"type": "string"
},
+ "hostnet": {
+ "type": "boolean"
+ },
"server": {
"type": "string"
},
@@ -383,6 +386,9 @@
},
"ipv6": {
"type": "string"
+ },
+ "external": {
+ "type": "boolean"
}
}
}
@@ -422,6 +428,9 @@
"image": {
"type": "string"
},
+ "hostnet": {
+ "type": "boolean"
+ },
"server": {
"type": "string"
},
diff --git a/tests/topotests/munet/mutest/__main__.py b/tests/topotests/munet/mutest/__main__.py
index d94e702c52..a78c69e26e 100644
--- a/tests/topotests/munet/mutest/__main__.py
+++ b/tests/topotests/munet/mutest/__main__.py
@@ -20,6 +20,7 @@ from copy import deepcopy
from pathlib import Path
from typing import Union
+from munet import mulog
from munet import parser
from munet.args import add_testing_args
from munet.base import Bridge
@@ -380,8 +381,10 @@ async def run_tests(args):
for result in results:
test_name, passed, failed, e = result
tnum += 1
- s = "FAIL" if failed or e else "PASS"
- reslog.info(" %s %s:%s", s, tnum, test_name)
+ if failed or e:
+ reslog.warning(" FAIL %s:%s", tnum, test_name)
+ else:
+ reslog.info(" PASS %s:%s", tnum, test_name)
reslog.info("-" * 70)
reslog.info(
@@ -447,8 +450,9 @@ def main():
sys.exit(0)
rundir = args.rundir if args.rundir else "/tmp/mutest"
- args.rundir = Path(rundir)
- os.environ["MUNET_RUNDIR"] = rundir
+ rundir = Path(rundir).absolute()
+ args.rundir = rundir
+ os.environ["MUNET_RUNDIR"] = str(rundir)
subprocess.run(f"mkdir -p {rundir} && chmod 755 {rundir}", check=True, shell=True)
config = parser.setup_logging(args, config_base="logconf-mutest")
@@ -459,6 +463,9 @@ def main():
fconfig.get("format"), fconfig.get("datefmt")
)
+ if not hasattr(sys.stderr, "isatty") or not sys.stderr.isatty():
+ mulog.do_color = False
+
loop = None
status = 4
try:
diff --git a/tests/topotests/munet/mutest/userapi.py b/tests/topotests/munet/mutest/userapi.py
index f42fbc1893..abc63af365 100644
--- a/tests/topotests/munet/mutest/userapi.py
+++ b/tests/topotests/munet/mutest/userapi.py
@@ -544,7 +544,9 @@ class TestCase:
"""
js = self._command_json(target, cmd)
if js is None:
- return expect_fail, {}
+ # Always fail on bad json, even if user expected failure
+ # return expect_fail, {}
+ return False, {}
try:
# Convert to string to validate the input is valid JSON
@@ -556,7 +558,9 @@ class TestCase:
self.olog.warning(
"JSON load failed. Check match value is in JSON format: %s", error
)
- return expect_fail, {}
+ # Always fail on bad json, even if user expected failure
+ # return expect_fail, {}
+ return False, {}
if exact_match:
deep_diff = json_cmp(expect, js)
diff --git a/tests/topotests/munet/native.py b/tests/topotests/munet/native.py
index de0f0ffc6c..5747d5e1d7 100644
--- a/tests/topotests/munet/native.py
+++ b/tests/topotests/munet/native.py
@@ -28,8 +28,10 @@ from . import cli
from .base import BaseMunet
from .base import Bridge
from .base import Commander
+from .base import InterfaceMixin
from .base import LinuxNamespace
from .base import MunetError
+from .base import SharedNamespace
from .base import Timeout
from .base import _async_get_exec_path
from .base import _get_exec_path
@@ -132,6 +134,22 @@ def convert_ranges_to_bitmask(ranges):
return bitmask
+class ExternalNetwork(SharedNamespace, InterfaceMixin):
+ """A network external to munet."""
+
+ def __init__(self, name=None, unet=None, logger=None, mtu=None, config=None):
+ """Create an external network."""
+ del logger # avoid linter
+ del mtu # avoid linter
+ # Do we want to use os.getpid() rather than unet.pid?
+ super().__init__(name, pid=unet.pid, nsflags=unet.nsflags, unet=unet)
+ self.config = config if config else {}
+
+ async def _async_delete(self):
+ self.logger.debug("%s: deleting", self)
+ await super()._async_delete()
+
+
class L2Bridge(Bridge):
"""A linux bridge with no IP network address."""
@@ -555,17 +573,38 @@ class NodeMixin:
await super()._async_delete()
+class HostnetNode(NodeMixin, LinuxNamespace):
+ """A node for running commands in the host network namespace."""
+
+ def __init__(self, name, pid=True, **kwargs):
+ if "net" in kwargs:
+ del kwargs["net"]
+ super().__init__(name, pid=pid, net=False, **kwargs)
+
+ self.logger.debug("%s: creating", self)
+
+ self.mgmt_ip = None
+ self.mgmt_ip6 = None
+ self.set_ns_cwd(self.rundir)
+
+ super().pytest_hook_open_shell()
+ self.logger.info("%s: created", self)
+
+ def get_ifname(self, netname): # pylint: disable=useless-return
+ del netname
+ return None
+
+ async def _async_delete(self):
+ self.logger.debug("%s: deleting", self)
+ await super()._async_delete()
+
+
class SSHRemote(NodeMixin, Commander):
"""SSHRemote a node representing an ssh connection to something."""
def __init__(
self,
name,
- server,
- port=22,
- user=None,
- password=None,
- idfile=None,
**kwargs,
):
super().__init__(name, **kwargs)
@@ -580,32 +619,33 @@ class SSHRemote(NodeMixin, Commander):
self.mgmt_ip = None
self.mgmt_ip6 = None
- self.port = port
-
- if user:
- self.user = user
- elif "SUDO_USER" in os.environ:
- self.user = os.environ["SUDO_USER"]
- else:
+ self.server = self.config["server"]
+ self.port = int(self.config.get("server-port", 22))
+ self.sudo_user = os.environ.get("SUDO_USER")
+ self.user = self.config.get("ssh-user")
+ if not self.user:
+ self.user = self.sudo_user
+ if not self.user:
self.user = getpass.getuser()
- self.password = password
- self.idfile = idfile
-
- self.server = f"{self.user}@{server}"
+ self.password = self.config.get("ssh-password")
+ self.idfile = self.config.get("ssh-identity-file")
+ self.use_host_network = None
# Setup our base `pre-cmd` values
#
# We maybe should add environment variable transfer here in particular
# MUNET_NODENAME. The problem is the user has to explicitly approve
# of SendEnv variables.
- self.__base_cmd = [
- get_exec_path_host("sudo"),
- "-E",
- f"-u{self.user}",
- get_exec_path_host("ssh"),
- ]
- if port != 22:
- self.__base_cmd.append(f"-p{port}")
+ self.__base_cmd = []
+ if self.idfile and self.sudo_user:
+ self.__base_cmd += [
+ get_exec_path_host("sudo"),
+ "-E",
+ f"-u{self.sudo_user}",
+ ]
+ self.__base_cmd.append(get_exec_path_host("ssh"))
+ if self.port != 22:
+ self.__base_cmd.append(f"-p{self.port}")
self.__base_cmd.append("-q")
self.__base_cmd.append("-oStrictHostKeyChecking=no")
self.__base_cmd.append("-oUserKnownHostsFile=/dev/null")
@@ -615,15 +655,34 @@ class SSHRemote(NodeMixin, Commander):
# self.__base_cmd.append("-oSendVar='TEST'")
self.__base_cmd_pty = list(self.__base_cmd)
self.__base_cmd_pty.append("-t")
- self.__base_cmd.append(self.server)
- self.__base_cmd_pty.append(self.server)
+ server_str = f"{self.user}@{self.server}"
+ self.__base_cmd.append(server_str)
+ self.__base_cmd_pty.append(server_str)
# self.set_pre_cmd(pre_cmd, pre_cmd_tty)
self.logger.info("%s: created", self)
def _get_pre_cmd(self, use_str, use_pty, ns_only=False, **kwargs):
- pre_cmd = []
- if self.unet:
+ # None on first use, set after
+ if self.use_host_network is None:
+ # We have networks now so try and ping the server in the namespace
+ if not self.unet:
+ self.use_host_network = True
+ else:
+ rc, _, _ = self.unet.cmd_status(f"ping -w1 -c1 {self.server}")
+ if rc:
+ self.use_host_network = True
+ else:
+ self.use_host_network = False
+
+ if self.use_host_network:
+ self.logger.debug("Using host namespace for ssh connection")
+ else:
+ self.logger.debug("Using munet namespace for ssh connection")
+
+ if self.use_host_network:
+ pre_cmd = []
+ else:
pre_cmd = self.unet._get_pre_cmd(False, use_pty, ns_only=False, **kwargs)
if ns_only:
return pre_cmd
@@ -979,17 +1038,16 @@ ff02::2\tip6-allrouters
)
self.unet.rootcmd.cmd_status(f"ip link set {dname} name {hname}")
- rc, o, _ = self.unet.rootcmd.cmd_status("ip -o link show")
- m = re.search(rf"\d+:\s+{re.escape(hname)}:.*", o)
- if m:
- self.unet.rootcmd.cmd_nostatus(f"ip link set {hname} down ")
- self.unet.rootcmd.cmd_raises(f"ip link set {hname} netns {self.pid}")
+ # Make sure the interface is there.
+ self.unet.rootcmd.cmd_raises(f"ip -o link show {hname}")
+ self.unet.rootcmd.cmd_nostatus(f"ip link set {hname} down ")
+ self.unet.rootcmd.cmd_raises(f"ip link set {hname} netns {self.pid}")
+
# Wait for interface to show up in namespace
for retry in range(0, 10):
rc, o, _ = self.cmd_status(f"ip -o link show {hname}")
if not rc:
- if re.search(rf"\d+: {re.escape(hname)}:.*", o):
- break
+ break
if retry > 0:
await asyncio.sleep(1)
self.cmd_raises(f"ip link set {hname} name {lname}")
@@ -1001,12 +1059,11 @@ ff02::2\tip6-allrouters
lname = self.host_intfs[hname]
self.cmd_raises(f"ip link set {lname} down")
self.cmd_raises(f"ip link set {lname} name {hname}")
- self.cmd_status(f"ip link set netns 1 dev {hname}")
- # The above is failing sometimes and not sure why
- # logging.error(
- # "XXX after setns %s",
- # self.unet.rootcmd.cmd_nostatus(f"ip link show {hname}"),
- # )
+ # We need to NOT run this command in the new pid namespace so that pid 1 is the
+ # root init process and so the interface gets returned to the root namespace
+ self.unet.rootcmd.cmd_raises(
+ f"nsenter -t {self.pid} -n ip link set netns 1 dev {hname}"
+ )
del self.host_intfs[hname]
async def add_phy_intf(self, devaddr, lname):
@@ -1917,7 +1974,11 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
# InterfaceMixin override
# We need a name unique in the shared namespace.
def get_ns_ifname(self, ifname):
- return self.name + ifname
+ ifname = self.name + ifname
+ ifname = re.sub("gigabitethernet", "GE", ifname, flags=re.I)
+ if len(ifname) >= 16:
+ ifname = ifname[0:7] + ifname[-8:]
+ return ifname
async def add_host_intf(self, hname, lname, mtu=None):
# L3QemuVM needs it's own add_host_intf for macvtap, We need to create the tap
@@ -2093,16 +2154,22 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
)
con.cmd_raises(rf"rm -rf {tmpdir}")
- self.logger.info("Saved coverage data in VM at %s", dest)
+ self.logger.debug("Saved coverage data in VM at %s", dest)
ldest = os.path.join(self.rundir, "gcov-data.tgz")
if self.use_ssh:
self.cmd_raises(["/bin/cat", dest], stdout=open(ldest, "wb"))
- self.logger.info("Saved coverage data on host at %s", ldest)
+ self.logger.debug("Saved coverage data on host at %s", ldest)
else:
output = con.cmd_raises(rf"base64 {dest}")
with open(ldest, "wb") as f:
f.write(base64.b64decode(output))
- self.logger.info("Saved coverage data on host at %s", ldest)
+ self.logger.debug("Saved coverage data on host at %s", ldest)
+ self.logger.info("Extracting coverage for %s into %s", self.name, ldest)
+
+ # We need to place the gcda files where munet expects to find them
+ gcdadir = Path(os.environ["GCOV_PREFIX"]) / self.name
+ self.unet.cmd_raises_nsonly(f"mkdir -p {gcdadir}")
+ self.unet.cmd_raises_nsonly(f"tar -C {gcdadir} -xzf {ldest}")
async def _opencons(
self,
@@ -2878,7 +2945,9 @@ ff02::2\tip6-allrouters
else:
node2.set_lan_addr(node1, c2)
- if "physical" not in c1 and not node1.is_vm:
+ if isinstance(node1, ExternalNetwork):
+ pass
+ elif "physical" not in c1 and not node1.is_vm:
node1.set_intf_constraints(if1, **c1)
if "physical" not in c2 and not node2.is_vm:
node2.set_intf_constraints(if2, **c2)
@@ -2891,14 +2960,8 @@ ff02::2\tip6-allrouters
cls = L3QemuVM
elif config and config.get("server"):
cls = SSHRemote
- kwargs["server"] = config["server"]
- kwargs["port"] = int(config.get("server-port", 22))
- if "ssh-identity-file" in config:
- kwargs["idfile"] = config.get("ssh-identity-file")
- if "ssh-user" in config:
- kwargs["user"] = config.get("ssh-user")
- if "ssh-password" in config:
- kwargs["password"] = config.get("ssh-password")
+ elif config and config.get("hostnet"):
+ cls = HostnetNode
else:
cls = L3NamespaceNode
return super().add_host(name, cls=cls, config=config, **kwargs)
@@ -2908,7 +2971,12 @@ ff02::2\tip6-allrouters
if config is None:
config = {}
- cls = L3Bridge if config.get("ip") else L2Bridge
+ if config.get("external"):
+ cls = ExternalNetwork
+ elif config.get("ip"):
+ cls = L3Bridge
+ else:
+ cls = L2Bridge
mtu = kwargs.get("mtu", config.get("mtu"))
return super().add_switch(name, cls=cls, config=config, mtu=mtu, **kwargs)
@@ -2947,7 +3015,7 @@ ff02::2\tip6-allrouters
bdir = Path(os.environ["GCOV_BUILD_DIR"])
gcdadir = Path(os.environ["GCOV_PREFIX"])
- # Create GCNO symlinks
+ # Create .gcno symlinks if they don't already exist, for kernel they will
self.logger.info("Creating .gcno symlinks from '%s' to '%s'", gcdadir, bdir)
commander.cmd_raises(
f'cd "{gcdadir}"; bdir="{bdir}"'
@@ -2955,9 +3023,11 @@ ff02::2\tip6-allrouters
for f in $(find . -name '*.gcda'); do
f=${f#./};
f=${f%.gcda}.gcno;
- ln -fs $bdir/$f $f;
- touch -h -r $bdir/$f $f;
- echo $f;
+ if [ ! -h "$f" ]; then
+ ln -fs $bdir/$f $f;
+ touch -h -r $bdir/$f $f;
+ echo $f;
+ fi;
done"""
)
@@ -2977,10 +3047,30 @@ done"""
# f"\nCOVERAGE-SUMMARY-START\n{output}\nCOVERAGE-SUMMARY-END\n"
# )
- async def run(self):
+ async def load_images(self, images):
tasks = []
+ for image in images:
+ logging.debug("Checking for image %s", image)
+ rc, _, _ = self.rootcmd.cmd_status(
+ f"podman image inspect {image}", warn=False
+ )
+ if not rc:
+ continue
+ logging.info("Pulling missing image %s", image)
+ aw = self.rootcmd.async_cmd_raises(f"podman pull {image}")
+ tasks.append(asyncio.create_task(aw))
+ if not tasks:
+ return
+ _, pending = await asyncio.wait(tasks, timeout=600)
+ assert not pending, "Failed to pull container images"
+ async def run(self):
+ tasks = []
hosts = self.hosts.values()
+
+ images = {x.container_image for x in hosts if hasattr(x, "container_image")}
+ await self.load_images(images)
+
launch_nodes = [x for x in hosts if hasattr(x, "launch")]
launch_nodes = [x for x in launch_nodes if x.config.get("qemu")]
run_nodes = [x for x in hosts if x.has_run_cmd()]
@@ -3049,10 +3139,10 @@ done"""
await asyncio.sleep(0.25)
logging.debug("%s is ready!", x)
+ tasks = [asyncio.create_task(wait_until_ready(x)) for x in ready_nodes]
+
logging.debug("Waiting for ready on nodes: %s", ready_nodes)
- _, pending = await asyncio.wait(
- [wait_until_ready(x) for x in ready_nodes], timeout=30
- )
+ _, pending = await asyncio.wait(tasks, timeout=30)
if pending:
logging.warning("Timeout waiting for ready: %s", pending)
for nr in pending:
diff --git a/tests/topotests/munet/testing/fixtures.py b/tests/topotests/munet/testing/fixtures.py
index 4150d28b59..3c6ddf9aed 100644
--- a/tests/topotests/munet/testing/fixtures.py
+++ b/tests/topotests/munet/testing/fixtures.py
@@ -25,7 +25,6 @@ from ..base import BaseMunet
from ..base import Bridge
from ..base import get_event_loop
from ..cleanup import cleanup_current
-from ..cleanup import cleanup_previous
from ..native import L3NodeMixin
from ..parser import async_build_topology
from ..parser import get_config
@@ -130,9 +129,12 @@ def session_autouse():
else:
is_worker = True
- if not is_worker:
- # This is unfriendly to multi-instance
- cleanup_previous()
+ # We dont want to kill all munet and we don't have the rundir here yet
+ # This was more useful back when we used to leave processes around a lot
+ # more.
+ # if not is_worker:
+ # # This is unfriendly to multi-instance
+ # cleanup_previous()
# We never pop as we want to keep logging
_push_log_handler("session", "/tmp/unet-test/pytest-session.log")
@@ -150,8 +152,9 @@ def session_autouse():
@pytest.fixture(autouse=True, scope="module")
def module_autouse(request):
+ root_path = os.environ.get("MUNET_RUNDIR", "/tmp/unet-test")
logpath = get_test_logdir(request.node.nodeid, True)
- logpath = os.path.join("/tmp/unet-test", logpath, "pytest-exec.log")
+ logpath = os.path.join(root_path, logpath, "pytest-exec.log")
with log_handler("module", logpath):
sdir = os.path.dirname(os.path.realpath(request.fspath))
with chdir(sdir, "module autouse fixture"):
@@ -174,7 +177,8 @@ def event_loop():
@pytest.fixture(scope="module")
def rundir_module():
- d = os.path.join("/tmp/unet-test", get_test_logdir(module=True))
+ root_path = os.environ.get("MUNET_RUNDIR", "/tmp/unet-test")
+ d = os.path.join(root_path, get_test_logdir(module=True))
logging.debug("conftest: test module rundir %s", d)
return d
@@ -375,7 +379,8 @@ async def astepf(pytestconfig):
@pytest.fixture(scope="function")
def rundir():
- d = os.path.join("/tmp/unet-test", get_test_logdir(module=False))
+ root_path = os.environ.get("MUNET_RUNDIR", "/tmp/unet-test")
+ d = os.path.join(root_path, get_test_logdir(module=False))
logging.debug("conftest: test function rundir %s", d)
return d
@@ -383,9 +388,8 @@ def rundir():
# Configure logging
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_setup(item):
- d = os.path.join(
- "/tmp/unet-test", get_test_logdir(nodeid=item.nodeid, module=False)
- )
+ root_path = os.environ.get("MUNET_RUNDIR", "/tmp/unet-test")
+ d = os.path.join(root_path, get_test_logdir(nodeid=item.nodeid, module=False))
config = item.config
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
filename = Path(d, "pytest-exec.log")
diff --git a/tests/topotests/munet/watchlog.py b/tests/topotests/munet/watchlog.py
index f764f9dac3..27bc3251a6 100644
--- a/tests/topotests/munet/watchlog.py
+++ b/tests/topotests/munet/watchlog.py
@@ -15,7 +15,6 @@ from pathlib import Path
class MatchFoundError(Exception):
"""An error raised when a match is not found."""
-
def __init__(self, watchlog, match):
self.watchlog = watchlog
self.match = match
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py
index 58608e249b..00c98ac97c 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py
@@ -175,7 +175,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -208,7 +208,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
@@ -266,7 +266,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 using"
+ "Verify that the neighbor is FULL between R1 and R2 using"
" show ip ospf6 neighbor cmd."
)
@@ -283,7 +283,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
dut = "r2"
step(
- "Verify that the neighbour is not FULL between R1 and R2 using "
+ "Verify that the neighbor is not FULL between R1 and R2 using "
"show ip ospf6 neighbor cmd."
)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, expected=False)
@@ -295,7 +295,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
shutdown_bringup_interface(tgen, dut, intf, True)
step(
- "Verify that the neighbour is FULL between R1 and R2 using "
+ "Verify that the neighbor is FULL between R1 and R2 using "
"show ip ospf6 neighbor cmd."
)
@@ -341,7 +341,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -374,7 +374,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
@@ -432,7 +432,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 using"
+ "Verify that the neighbor is FULL between R1 and R2 using"
" show ip ospf6 neighbor cmd."
)
@@ -449,7 +449,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
dut = "r2"
step(
- "Verify that the neighbour is not FULL between R1 and R2 using "
+ "Verify that the neighbor is not FULL between R1 and R2 using "
"show ip ospf6 neighbor cmd."
)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, expected=False)
@@ -461,7 +461,66 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
shutdown_bringup_interface(tgen, dut, intf, True)
step(
- "Verify that the neighbour is FULL between R1 and R2 using "
+ "Verify that the neighbor is FULL between R1 and R2 using "
+ "show ip ospf6 neighbor cmd."
+ )
+
+ dut = "r2"
+ ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, ospf6_covergence
+ )
+
+ step("Change the key ID on R2 to not match R1")
+ r2_ospf6_auth = {
+ "r2": {
+ "links": {
+ "r1": {
+ "ospf6": {
+ "hash-algo": "hmac-sha-256",
+ "key": "ospf6",
+ "key-id": "30",
+ }
+ }
+ }
+ }
+ }
+ result = config_ospf6_interface(tgen, topo, r2_ospf6_auth)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step(
+ "Verify on R1 that R2 nbr is deleted due to key-id mismatch "
+ "after dead interval expiry"
+ )
+ # wait till the dead timer expiry
+ sleep(6)
+ dut = "r2"
+ ospf6_covergence = verify_ospf6_neighbor(
+ tgen, topo, dut=dut, expected=False, retry_timeout=5
+ )
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, ospf6_covergence
+ )
+
+ step("Correct the key ID on R2 so that it matches R1")
+ r2_ospf6_auth = {
+ "r2": {
+ "links": {
+ "r1": {
+ "ospf6": {
+ "hash-algo": "hmac-sha-256",
+ "key": "ospf6",
+ "key-id": "10",
+ }
+ }
+ }
+ }
+ }
+ result = config_ospf6_interface(tgen, topo, r2_ospf6_auth)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step(
+ "Verify that the neighbor is FULL between R1 and R2 using "
"show ip ospf6 neighbor cmd."
)
@@ -524,7 +583,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -555,7 +614,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
@@ -600,7 +659,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 using"
+ "Verify that the neighbor is FULL between R1 and R2 using"
" show ip ospf6 neighbor cmd."
)
@@ -617,7 +676,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
dut = "r2"
step(
- "Verify that the neighbour is not FULL between R1 and R2 using "
+ "Verify that the neighbor is not FULL between R1 and R2 using "
"show ip ospf6 neighbor cmd."
)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, expected=False)
@@ -629,7 +688,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
shutdown_bringup_interface(tgen, dut, intf, True)
step(
- "Verify that the neighbour is FULL between R1 and R2 using "
+ "Verify that the neighbor is FULL between R1 and R2 using "
"show ip ospf6 neighbor cmd."
)
@@ -692,7 +751,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -723,7 +782,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
@@ -768,7 +827,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 using"
+ "Verify that the neighbor is FULL between R1 and R2 using"
" show ip ospf6 neighbor cmd."
)
@@ -785,7 +844,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
dut = "r2"
step(
- "Verify that the neighbour is not FULL between R1 and R2 using "
+ "Verify that the neighbor is not FULL between R1 and R2 using "
"show ip ospf6 neighbor cmd."
)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, expected=False)
@@ -797,7 +856,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
shutdown_bringup_interface(tgen, dut, intf, True)
step(
- "Verify that the neighbour is FULL between R1 and R2 using "
+ "Verify that the neighbor is FULL between R1 and R2 using "
"show ip ospf6 neighbor cmd."
)
@@ -843,7 +902,7 @@ def test_ospf6_auth_trailer_tc5_md5_keymissmatch(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -876,11 +935,11 @@ def test_ospf6_auth_trailer_tc5_md5_keymissmatch(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is not FULL between R1 and R2 "
+ "Verify that the neighbor is not FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
- step("Verify that the neighbour is FULL between R1 and R2.")
+ step("Verify that the neighbor is FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r2"
@@ -913,7 +972,7 @@ def test_ospf6_auth_trailer_tc5_md5_keymissmatch(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
@@ -959,7 +1018,7 @@ def test_ospf6_auth_trailer_tc6_sha256_mismatch(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -991,7 +1050,7 @@ def test_ospf6_auth_trailer_tc6_sha256_mismatch(request):
result = config_ospf6_interface(tgen, topo, r2_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r2"
@@ -1024,7 +1083,7 @@ def test_ospf6_auth_trailer_tc6_sha256_mismatch(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
@@ -1095,7 +1154,7 @@ def test_ospf6_auth_trailer_tc7_keychain_md5_missmatch(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -1125,7 +1184,7 @@ def test_ospf6_auth_trailer_tc7_keychain_md5_missmatch(request):
result = config_ospf6_interface(tgen, topo, r2_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r2"
@@ -1156,7 +1215,7 @@ def test_ospf6_auth_trailer_tc7_keychain_md5_missmatch(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
@@ -1227,7 +1286,7 @@ def test_ospf6_auth_trailer_tc8_keychain_sha256_missmatch(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -1257,7 +1316,7 @@ def test_ospf6_auth_trailer_tc8_keychain_sha256_missmatch(request):
result = config_ospf6_interface(tgen, topo, r2_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r2"
@@ -1288,7 +1347,7 @@ def test_ospf6_auth_trailer_tc8_keychain_sha256_missmatch(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
@@ -1335,7 +1394,7 @@ def test_ospf6_auth_trailer_tc9_keychain_not_configured(request):
result = config_ospf6_interface(tgen, topo, r1_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r1"
@@ -1365,7 +1424,7 @@ def test_ospf6_auth_trailer_tc9_keychain_not_configured(request):
result = config_ospf6_interface(tgen, topo, r2_ospf6_auth)
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
- step("Verify that the neighbour is not FULL between R1 and R2.")
+ step("Verify that the neighbor is not FULL between R1 and R2.")
# wait for dead time expiry.
sleep(6)
dut = "r2"
@@ -1396,7 +1455,7 @@ def test_ospf6_auth_trailer_tc10_no_auth_trailer(request):
router2 = tgen.gears["r2"]
step(
- "Verify that the neighbour is FULL between R1 and R2 "
+ "Verify that the neighbor is FULL between R1 and R2 "
"using show ipv6 ospf6 neighbor cmd."
)
diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c
index 245b799a91..9ad92d6269 100644
--- a/zebra/dplane_fpm_nl.c
+++ b/zebra/dplane_fpm_nl.c
@@ -654,14 +654,6 @@ static void fpm_read(struct event *t)
hdr_available_bytes = fpm.msg_len - FPM_MSG_HDR_LEN;
available_bytes -= hdr_available_bytes;
- /* Sanity check: must be at least header size. */
- if (hdr->nlmsg_len < sizeof(*hdr)) {
- zlog_warn(
- "%s: [seq=%u] invalid message length %u (< %zu)",
- __func__, hdr->nlmsg_seq, hdr->nlmsg_len,
- sizeof(*hdr));
- continue;
- }
if (hdr->nlmsg_len > fpm.msg_len) {
zlog_warn(
"%s: Received a inner header length of %u that is greater than the fpm total length of %u",
@@ -691,6 +683,14 @@ static void fpm_read(struct event *t)
switch (hdr->nlmsg_type) {
case RTM_NEWROUTE:
+ /* Sanity check: need at least route msg header size. */
+ if (hdr->nlmsg_len < sizeof(struct rtmsg)) {
+ zlog_warn("%s: [seq=%u] invalid message length %u (< %zu)",
+ __func__, hdr->nlmsg_seq,
+ hdr->nlmsg_len, sizeof(struct rtmsg));
+ break;
+ }
+
ctx = dplane_ctx_alloc();
dplane_ctx_route_init(ctx, DPLANE_OP_ROUTE_NOTIFY, NULL,
NULL);
diff --git a/zebra/zebra_netns_notify.c b/zebra/zebra_netns_notify.c
index 617a2225f8..fb326b0ddf 100644
--- a/zebra/zebra_netns_notify.c
+++ b/zebra/zebra_netns_notify.c
@@ -15,6 +15,7 @@
#include <sched.h>
#endif
#include <dirent.h>
+#include <libgen.h>
#include <sys/inotify.h>
#include <sys/stat.h>
@@ -234,6 +235,7 @@ static void zebra_ns_ready_read(struct event *t)
{
struct zebra_netns_info *zns_info = EVENT_ARG(t);
const char *netnspath;
+ const char *netnspath_basename;
int err, stop_retry = 0;
if (!zns_info)
@@ -261,23 +263,24 @@ static void zebra_ns_ready_read(struct event *t)
zebra_ns_continue_read(zns_info, stop_retry);
return;
}
+ netnspath_basename = basename(strdupa(netnspath));
/* check default name is not already set */
- if (strmatch(VRF_DEFAULT_NAME, basename(netnspath))) {
- zlog_warn("NS notify : NS %s is already default VRF.Cancel VRF Creation", basename(netnspath));
+ if (strmatch(VRF_DEFAULT_NAME, netnspath_basename)) {
+ zlog_warn("NS notify : NS %s is already default VRF.Cancel VRF Creation", netnspath_basename);
zebra_ns_continue_read(zns_info, 1);
return;
}
- if (zebra_ns_notify_is_default_netns(basename(netnspath))) {
+ if (zebra_ns_notify_is_default_netns(netnspath_basename)) {
zlog_warn(
"NS notify : NS %s is default VRF. Ignore VRF creation",
- basename(netnspath));
+ netnspath_basename);
zebra_ns_continue_read(zns_info, 1);
return;
}
/* success : close fd and create zns context */
- zebra_ns_notify_create_context_from_entry_name(basename(netnspath));
+ zebra_ns_notify_create_context_from_entry_name(netnspath_basename);
zebra_ns_continue_read(zns_info, 1);
}
@@ -396,7 +399,7 @@ void zebra_ns_notify_parse(void)
continue;
}
/* check default name is not already set */
- if (strmatch(VRF_DEFAULT_NAME, basename(dent->d_name))) {
+ if (strmatch(VRF_DEFAULT_NAME, basename(strdupa(dent->d_name)))) {
zlog_warn("NS notify : NS %s is already default VRF.Cancel VRF Creation", dent->d_name);
continue;
}