Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F113380046
repro_standalone.py
All Users
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
cr0ntab
May 1 2026, 3:58 PM
2026-05-01 15:58:26 (UTC+0)
Size
4 KB
Referenced Files
None
Subscribers
None
repro_standalone.py
View Options
#!/usr/bin/env python3
"""
Standalone reproducer for the vyos.vyos rm_templates regex slowdown.
No ansible or vyos.vyos dependency: just stdlib `re` and `time`.
The pattern below is copied verbatim from
vyos.vyos/plugins/module_utils/network/vyos/rm_templates/route_maps_14.py
parser name="route_map" (lines ~33-39). The script times it against
realistic non-matching input, alongside a `\\s*$`-fixed variant for
comparison.
Run: python3 repro_standalone.py
"""
import
re
import
time
# Pattern copied verbatim from rm_templates/route_maps_14.py lines 33-39.
BAD
=
re
.
compile
(
r"""
^set\spolicy\sroute-map\s(?P<route_map>\S+)
*$"""
,
re
.
VERBOSE
,
)
# Under re.VERBOSE the literal newline + spaces between `\S+` and `*$`
# are stripped at compile time, so the pattern that actually executes
# is `^set\spolicy\sroute-map\s(?P<route_map>\S+)*$`. The trailing `*`
# ends up quantifying the named group, which forces the regex engine
# into O(2^n) backtracking on inputs that share the prefix but don't
# match overall.
# Same positive matches, no group-quantifier.
FIXED
=
re
.
compile
(
r"""^set\spolicy\sroute-map\s(?P<route_map>\S+)\s*$"""
,
re
.
VERBOSE
,
)
# ---------------------------------------------------------------------------
# Inputs
# ---------------------------------------------------------------------------
# Positive case: these lines DO match the parser.
POSITIVE
=
[
"set policy route-map MY-MAP"
,
"set policy route-map ANOTHER"
,
]
# Negative case: lines that share the `set policy route-map <name>`
# prefix but don't match the route_map parser overall. These are
# what `show configuration commands | grep route-map` returns on any
# non-trivial device.
#
# The runtime cost is exponential in the length of the FIRST contiguous
# \S+ run after the prefix. 6-char names like "MY-MAP" (2^5 backtracks
# per line) finish in microseconds; 20-29 char names like the ones below
# (2^19 .. 2^28 per line) take seconds to minutes per line.
NEGATIVE
=
[
"set policy route-map ADVERTISE-ANYCAST-v6 rule 10 action 'permit'"
,
"set policy route-map ADVERTISE-ANYCAST-v6 rule 10 match ipv6 address prefix-list 'ANYCAST-AGGREGATE-v6'"
,
"set policy route-map DEFAULT-ORIGINATE-SENTINEL-v6 rule 10 action 'permit'"
,
"set policy route-map DEFAULT-ORIGINATE-SENTINEL-v6 rule 10 match ipv6 address prefix-list 'AS64496-SENTINEL-v6'"
,
"set policy route-map DEFAULT-ORIGINATE-SENTINEL-v6 rule 10 set local-preference '120'"
,
"set policy route-map IXP-PEER-IN rule 10 action 'permit'"
,
"set policy route-map IXP-PEER-IN rule 10 match ip address prefix-list 'IXP-INBOUND-v4'"
,
"set policy route-map IXP-PEER-IN rule 10 set community 'additive 65000:100'"
,
"set policy route-map TRANSIT-OUT rule 100 action 'permit'"
,
"set policy route-map TRANSIT-OUT rule 100 match ip address prefix-list 'CUSTOMER-AGGREGATES-v4'"
,
"set policy route-map TRANSIT-OUT rule 100 set as-path prepend '65000 65000'"
,
"set policy route-map UPSTREAM-IN-v4 rule 10 action 'permit'"
,
]
def
time_match
(
pattern
,
lines
,
label
):
t0
=
time
.
perf_counter
()
matches
=
0
for
ln
in
lines
:
if
pattern
.
match
(
ln
):
matches
+=
1
dt
=
time
.
perf_counter
()
-
t0
print
(
f
" {label:<35s} matched={matches:<3d} elapsed={dt:8.4f}s ({len(lines)} lines)"
)
return
dt
def
main
():
print
(
"vyos.vyos rm_templates regex reproducer"
)
print
()
print
(
"Compiled pattern strings (after re.VERBOSE whitespace strip):"
)
print
(
f
" BAD : {BAD.pattern.replace(chr(10), '').strip()!r}"
)
print
(
f
" FIXED: {FIXED.pattern.replace(chr(10), '').strip()!r}"
)
print
(
"
\n
Positive case (lines that match the route_map parser):"
)
time_match
(
BAD
,
POSITIVE
,
"bad pattern"
)
time_match
(
FIXED
,
POSITIVE
,
"fixed pattern"
)
print
(
"
\n
Negative case (rule-lines that share the prefix but don't match):"
)
time_match
(
BAD
,
NEGATIVE
,
"bad pattern"
)
time_match
(
FIXED
,
NEGATIVE
,
"fixed pattern"
)
print
(
"
\n
Negative case, 25 lines (closer to real device output):"
)
big
=
NEGATIVE
*
2
+
NEGATIVE
[:
1
]
time_match
(
BAD
,
big
,
"bad pattern"
)
time_match
(
FIXED
,
big
,
"fixed pattern"
)
if
__name__
==
"__main__"
:
main
()
File Metadata
Details
Attached
Mime Type
text/x-script.python
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3637785
Default Alt Text
repro_standalone.py (4 KB)
Attached To
Mode
T8609: vyos_route_maps and other resource modules parse very slowly: stray regex quantifier in rm_templates
Attached
Detach File
Event Timeline
Log In to Comment