|
16 | 16 | import os |
17 | 17 | import sys |
18 | 18 | import json |
| 19 | +import re |
| 20 | +import time |
19 | 21 | from functools import partial |
20 | 22 | import pytest |
21 | 23 |
|
@@ -201,6 +203,136 @@ def test_protocols_convergence(): |
201 | 203 | assert success is True, "network 10.201.0.0/24 invalid for MPLS: not found on r2" |
202 | 204 |
|
203 | 205 |
|
| 206 | +def test_promiscuity_no_route_reset(): |
| 207 | + """ |
| 208 | + Test that promiscuity changes don't cause route resets on GRE tunnel interfaces |
| 209 | + and that the PROMISC flag is correctly displayed. |
| 210 | +
|
| 211 | + This simulates running tcpdump on an interface which sets promiscuous mode. |
| 212 | + Without the fix, this would cause routes to reset their timers. |
| 213 | + """ |
| 214 | + tgen = get_topogen() |
| 215 | + if tgen.routers_have_failure(): |
| 216 | + pytest.skip(tgen.errors) |
| 217 | + |
| 218 | + logger.info( |
| 219 | + "Test that promiscuity changes (like tcpdump) don't reset routes on GRE interfaces" |
| 220 | + ) |
| 221 | + router = tgen.gears["r1"] |
| 222 | + |
| 223 | + # INTENTIONAL: Wait 3 seconds at the beginning for routes to age |
| 224 | + logger.info("Waiting 3 seconds for routes to age") |
| 225 | + time.sleep(3) |
| 226 | + |
| 227 | + target_route = "10.201.0.0/24" |
| 228 | + logger.info("Getting route timer before promiscuity change") |
| 229 | + routes_before = router.vtysh_cmd("show ip route vrf vrf1 json") |
| 230 | + routes_json_before = json.loads(routes_before) |
| 231 | + |
| 232 | + uptime_before_promisc = None |
| 233 | + if target_route in routes_json_before: |
| 234 | + routes_list = routes_json_before[target_route] |
| 235 | + if routes_list and isinstance(routes_list, list): |
| 236 | + uptime_before_promisc = routes_list[0].get("uptime", None) |
| 237 | + logger.info( |
| 238 | + "Route {} timer before promiscuity: {}".format( |
| 239 | + target_route, uptime_before_promisc |
| 240 | + ) |
| 241 | + ) |
| 242 | + |
| 243 | + if not uptime_before_promisc: |
| 244 | + pytest.skip("Route {} not found in VRF vrf1".format(target_route)) |
| 245 | + |
| 246 | + # Toggle promiscuity on r1-eth1 |
| 247 | + logger.info("Setting promiscuity on r1-eth1") |
| 248 | + router.run("ip link set r1-eth1 promisc on") |
| 249 | + |
| 250 | + # Check that PROMISC flag is set on r1-eth1 |
| 251 | + def _check_promisc_on(): |
| 252 | + output = router.vtysh_cmd("show interface r1-eth1") |
| 253 | + if "PROMISC" in output: |
| 254 | + return None |
| 255 | + return "PROMISC flag not found in output" |
| 256 | + |
| 257 | + _, result = topotest.run_and_expect(_check_promisc_on, None, count=20, wait=3) |
| 258 | + assert result is None, "PROMISC flag not set after enabling: {}".format(result) |
| 259 | + logger.info("PROMISC flag correctly set on r1-eth1") |
| 260 | + |
| 261 | + # Turn off promiscuity on r1-eth1 |
| 262 | + logger.info("Clearing promiscuity on r1-eth1") |
| 263 | + router.run("ip link set r1-eth1 promisc off") |
| 264 | + |
| 265 | + # Check that PROMISC flag is cleared |
| 266 | + def _check_promisc_off(): |
| 267 | + output = router.vtysh_cmd("show interface r1-eth1") |
| 268 | + flags_match = re.search(r"flags:\s*<([^>]+)>", output) |
| 269 | + if flags_match: |
| 270 | + flags = flags_match.group(1) |
| 271 | + if "PROMISC" not in flags: |
| 272 | + return None |
| 273 | + return "PROMISC flag still present: {}".format(flags) |
| 274 | + return "Could not find flags in output" |
| 275 | + |
| 276 | + _, result = topotest.run_and_expect(_check_promisc_off, None, count=20, wait=3) |
| 277 | + assert result is None, "PROMISC flag not cleared: {}".format(result) |
| 278 | + logger.info("PROMISC flag correctly cleared on r1-eth1") |
| 279 | + |
| 280 | + # INTENTIONAL: Wait 3 seconds at the beginning for routes to age |
| 281 | + logger.info("Waiting 3 seconds for routes to age") |
| 282 | + time.sleep(3) |
| 283 | + |
| 284 | + # Get route timer after promiscuity changes and verify it didn't reset |
| 285 | + logger.info("Verifying route {} timer was not reset".format(target_route)) |
| 286 | + routes_after = router.vtysh_cmd("show ip route vrf vrf1 json") |
| 287 | + routes_json_after = json.loads(routes_after) |
| 288 | + |
| 289 | + uptime_after_promisc = None |
| 290 | + if target_route in routes_json_after: |
| 291 | + routes_list = routes_json_after[target_route] |
| 292 | + if routes_list and isinstance(routes_list, list): |
| 293 | + uptime_after_promisc = routes_list[0].get("uptime", None) |
| 294 | + |
| 295 | + if not uptime_after_promisc: |
| 296 | + pytest.fail( |
| 297 | + "Route {} disappeared after promiscuity changes".format(target_route) |
| 298 | + ) |
| 299 | + |
| 300 | + logger.info( |
| 301 | + "Route {} timer after promiscuity: {}".format( |
| 302 | + target_route, uptime_after_promisc |
| 303 | + ) |
| 304 | + ) |
| 305 | + |
| 306 | + def uptime_to_seconds(uptime_str): |
| 307 | + parts = uptime_str.split(":") |
| 308 | + if len(parts) == 3: |
| 309 | + return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2]) |
| 310 | + return 0 |
| 311 | + |
| 312 | + before_seconds = uptime_to_seconds(uptime_before_promisc) |
| 313 | + after_seconds = uptime_to_seconds(uptime_after_promisc) |
| 314 | + |
| 315 | + logger.info( |
| 316 | + "Timer before: {}s, Timer after: {}s".format(before_seconds, after_seconds) |
| 317 | + ) |
| 318 | + |
| 319 | + assert ( |
| 320 | + after_seconds >= before_seconds |
| 321 | + ), "Route {} timer was reset! Before: {}s ({}), After: {}s ({})".format( |
| 322 | + target_route, |
| 323 | + before_seconds, |
| 324 | + uptime_before_promisc, |
| 325 | + after_seconds, |
| 326 | + uptime_after_promisc, |
| 327 | + ) |
| 328 | + |
| 329 | + logger.info( |
| 330 | + "Route {} remained stable - timer NOT reset by promiscuity changes".format( |
| 331 | + target_route |
| 332 | + ) |
| 333 | + ) |
| 334 | + |
| 335 | + |
204 | 336 | def test_memory_leak(): |
205 | 337 | "Run the memory leak test and report results." |
206 | 338 | tgen = get_topogen() |
|
0 commit comments