diff options
| -rw-r--r-- | bgpd/bgpd.c | 5 | ||||
| -rw-r--r-- | doc/user/bgp.rst | 4 | ||||
| -rw-r--r-- | lib/mgmt_msg_native.h | 27 | ||||
| -rw-r--r-- | nhrpd/nhrp_main.c | 4 | ||||
| -rw-r--r-- | nhrpd/nhrp_shortcut.c | 12 | ||||
| -rw-r--r-- | ospf6d/ospf6_auth_trailer.c | 9 | ||||
| -rw-r--r-- | tests/topotests/munet/__main__.py | 24 | ||||
| -rw-r--r-- | tests/topotests/munet/base.py | 60 | ||||
| -rw-r--r-- | tests/topotests/munet/cleanup.py | 34 | ||||
| -rw-r--r-- | tests/topotests/munet/cli.py | 12 | ||||
| -rw-r--r-- | tests/topotests/munet/logconf-mutest.yaml | 5 | ||||
| -rw-r--r-- | tests/topotests/munet/mucmd.py | 4 | ||||
| -rw-r--r-- | tests/topotests/munet/mulog.py | 26 | ||||
| -rw-r--r-- | tests/topotests/munet/munet-schema.json | 9 | ||||
| -rw-r--r-- | tests/topotests/munet/mutest/__main__.py | 15 | ||||
| -rw-r--r-- | tests/topotests/munet/mutest/userapi.py | 8 | ||||
| -rw-r--r-- | tests/topotests/munet/native.py | 216 | ||||
| -rw-r--r-- | tests/topotests/munet/testing/fixtures.py | 24 | ||||
| -rw-r--r-- | tests/topotests/munet/watchlog.py | 1 | ||||
| -rw-r--r-- | tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py | 131 | ||||
| -rw-r--r-- | zebra/dplane_fpm_nl.c | 16 | ||||
| -rw-r--r-- | zebra/zebra_netns_notify.c | 15 |
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; } |
