Commit 36fc3fbf8c for frr
commit 36fc3fbf8cc243ebc87decaa206053a148e3d714
Author: Donald Sharp <donaldsharp72@gmail.com>
Date: Sun Nov 8 15:56:57 2026 -0500
Revert "bgpd: fix colored extended communities crash and regression"
diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c
index f74eb41ee4..54358f2f23 100644
--- a/bgpd/bgp_ecommunity.c
+++ b/bgpd/bgp_ecommunity.c
@@ -924,9 +924,10 @@ static const char *ecommunity_gettoken(const char *str, void *eval_ptr,
ecomm_type = ECOMMUNITY_ENCODE_AS4;
else if (sub_type == ECOMMUNITY_COLOR) {
ecomm_type = ECOMMUNITY_ENCODE_OPAQUE;
- /* A color without CO bits is encoded with CO bits 00 */
- if (val_color)
+ if (val_color) {
val = val_color;
+ as = 1;
+ }
} else if (as > 0)
ecomm_type = ECOMMUNITY_ENCODE_AS;
}
diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c
index 4294a649a8..35a10e3dc3 100644
--- a/bgpd/bgp_routemap.c
+++ b/bgpd/bgp_routemap.c
@@ -7760,29 +7760,6 @@ DEFPY_YANG (no_set_ecommunity_nt,
return nb_cli_apply_changes(vty, NULL);
}
-/* Check a color against the color-list YANG type: [CO:]COLOR */
-static bool set_ecommunity_color_valid(const char *color)
-{
- unsigned long long value;
- char *endptr;
-
- if (strlen(color) > 3 && color[2] == ':') {
- if ((color[0] != '0' && color[0] != '1') || (color[1] != '0' && color[1] != '1'))
- return false;
- color += 3;
- }
-
- if (color[0] < '1' || color[0] > '9')
- return false;
-
- errno = 0;
- value = strtoull(color, &endptr, 10);
- if (*endptr != '\0' || errno || value > UINT32_MAX)
- return false;
-
- return true;
-}
-
DEFPY_YANG(set_ecommunity_color, set_ecommunity_color_cmd,
"set extcommunity color RTLIST...",
SET_STR
@@ -7797,13 +7774,6 @@ DEFPY_YANG(set_ecommunity_color, set_ecommunity_color_cmd,
"./set-action[action='frr-bgp-route-map:set-extcommunity-color']";
char xpath_value[XPATH_MAXLEN];
- for (int i = idx_color; i < argc; i++) {
- if (!set_ecommunity_color_valid(argv[i]->arg)) {
- vty_out(vty, "%% Malformed color extended community %s\n", argv[i]->arg);
- return CMD_WARNING_CONFIG_FAILED;
- }
- }
-
nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
snprintf(xpath_value, sizeof(xpath_value),
diff --git a/tests/topotests/bgp_color_extcommunity_syntax/__init__.py b/tests/topotests/bgp_color_extcommunity_syntax/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/topotests/bgp_color_extcommunity_syntax/r1/frr.conf b/tests/topotests/bgp_color_extcommunity_syntax/r1/frr.conf
deleted file mode 100644
index ff04634e01..0000000000
--- a/tests/topotests/bgp_color_extcommunity_syntax/r1/frr.conf
+++ /dev/null
@@ -1,25 +0,0 @@
-!
-interface lo
- ip address 10.10.10.1/32
-exit
-!
-interface r1-eth0
- ip address 192.168.1.1/24
-exit
-!
-router bgp 65001
- bgp router-id 10.10.10.1
- no bgp ebgp-requires-policy
- no bgp network import-check
- neighbor 192.168.1.2 remote-as external
- neighbor 192.168.1.2 timers 1 3
- neighbor 192.168.1.2 timers connect 1
- !
- address-family ipv4 unicast
- network 10.10.10.1/32 route-map COLOR
- exit-address-family
-exit
-!
-route-map COLOR permit 10
-exit
-!
diff --git a/tests/topotests/bgp_color_extcommunity_syntax/r2/frr.conf b/tests/topotests/bgp_color_extcommunity_syntax/r2/frr.conf
deleted file mode 100644
index 137ff70ad0..0000000000
--- a/tests/topotests/bgp_color_extcommunity_syntax/r2/frr.conf
+++ /dev/null
@@ -1,13 +0,0 @@
-!
-interface r2-eth0
- ip address 192.168.1.2/24
-exit
-!
-router bgp 65002
- bgp router-id 192.168.1.2
- no bgp ebgp-requires-policy
- neighbor 192.168.1.1 remote-as external
- neighbor 192.168.1.1 timers 1 3
- neighbor 192.168.1.1 timers connect 1
-exit
-!
diff --git a/tests/topotests/bgp_color_extcommunity_syntax/r3/color.json b/tests/topotests/bgp_color_extcommunity_syntax/r3/color.json
deleted file mode 100644
index f4cd6c4e40..0000000000
--- a/tests/topotests/bgp_color_extcommunity_syntax/r3/color.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "frr-route-map:lib": {
- "route-map": [
- {
- "name": "JSON",
- "entry": [
- {
- "sequence": 10,
- "action": "permit",
- "set-action": [
- {
- "action": "frr-bgp-route-map:set-extcommunity-color",
- "rmap-set-action": {
- "frr-bgp-route-map:extcommunity-color": "01:100 10:200"
- }
- }
- ]
- }
- ]
- }
- ]
- }
-}
diff --git a/tests/topotests/bgp_color_extcommunity_syntax/r3/color_oversized.json b/tests/topotests/bgp_color_extcommunity_syntax/r3/color_oversized.json
deleted file mode 100644
index 695c52fbe2..0000000000
--- a/tests/topotests/bgp_color_extcommunity_syntax/r3/color_oversized.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "frr-route-map:lib": {
- "route-map": [
- {
- "name": "JSON",
- "entry": [
- {
- "sequence": 10,
- "action": "permit",
- "set-action": [
- {
- "action": "frr-bgp-route-map:set-extcommunity-color",
- "rmap-set-action": {
- "frr-bgp-route-map:extcommunity-color": "4294967296"
- }
- }
- ]
- }
- ]
- }
- ]
- }
-}
diff --git a/tests/topotests/bgp_color_extcommunity_syntax/r3/frr.conf b/tests/topotests/bgp_color_extcommunity_syntax/r3/frr.conf
deleted file mode 100644
index cdf4cb4feb..0000000000
--- a/tests/topotests/bgp_color_extcommunity_syntax/r3/frr.conf
+++ /dev/null
@@ -1 +0,0 @@
-!
diff --git a/tests/topotests/bgp_color_extcommunity_syntax/test_bgp_color_extcommunity_syntax.py b/tests/topotests/bgp_color_extcommunity_syntax/test_bgp_color_extcommunity_syntax.py
deleted file mode 100644
index 93dd3e9fc7..0000000000
--- a/tests/topotests/bgp_color_extcommunity_syntax/test_bgp_color_extcommunity_syntax.py
+++ /dev/null
@@ -1,451 +0,0 @@
-#!/usr/bin/env python
-# SPDX-License-Identifier: ISC
-#
-# Copyright (c) 2026 by 6WIND S.A.
-# Louis Scalbert <louis.scalbert@6wind.com>
-#
-
-"""
-Check the syntax of the "set extcommunity color" route-map command.
-
-r1 announces 10.10.10.1/32 to r2 with the COLOR route-map.
-
-Valid colors ([CO:]COLOR, CO being 00, 01, 10 or 11 and COLOR being 1 to
-4294967295) are applied and received by r2. A color without CO bits is
-encoded with CO bits 00.
-
-Malformed colors are rejected by the CLI. Previously, they left a
-set-action without value in the configuration and bgpd aborted on
-"show running-config".
-
-r3 bgpd runs in transactional CLI mode to load colors from JSON files,
-which are only checked by YANG. An out of range color must be rejected.
-"""
-
-import os
-import sys
-from functools import partial
-
-import pytest
-
-CWD = os.path.dirname(os.path.realpath(__file__))
-sys.path.append(os.path.join(CWD, "../"))
-
-# pylint: disable=C0413
-from lib import topotest
-from lib.topogen import Topogen, get_topogen
-from lib.topolog import logger
-
-pytestmark = [pytest.mark.bgpd]
-
-
-def build_topo(tgen):
- tgen.add_router("r1")
- tgen.add_router("r2")
- tgen.add_router("r3")
-
- switch = tgen.add_switch("s1")
- switch.add_link(tgen.gears["r1"])
- switch.add_link(tgen.gears["r2"])
-
-
-def setup_module(mod):
- tgen = Topogen(build_topo, mod.__name__)
- tgen.start_topology()
-
- for rname in ("r1", "r2"):
- tgen.gears[rname].load_frr_config(os.path.join(CWD, f"{rname}/frr.conf"))
-
- # "configuration load" is only available in transactional CLI mode
- tgen.gears["r3"].load_frr_config(
- os.path.join(CWD, "r3/frr.conf"), daemons=["zebra", ("bgpd", "--tcli")]
- )
-
- tgen.start_router()
-
-
-def teardown_module(_mod):
- tgen = get_topogen()
- tgen.stop_topology()
-
-
-def check_r2_route(ext_community):
- tgen = get_topogen()
- r2 = tgen.gears["r2"]
-
- if ext_community is None:
- expected_ext_community = None
- else:
- expected_ext_community = {"string": ext_community}
- expected = {
- "prefix": "10.10.10.1/32",
- "paths": [
- {
- "valid": True,
- "extendedCommunity": expected_ext_community,
- }
- ],
- }
- test_func = partial(
- topotest.router_json_cmp,
- r2,
- "show bgp ipv4 unicast 10.10.10.1/32 json",
- expected,
- )
- _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
- assert result is None, '"r2" 10.10.10.1/32 has not the expected extended community'
-
-
-def check_r1_route_map(rmap_name, set_clauses):
- tgen = get_topogen()
- r1 = tgen.gears["r1"]
-
- expected = {
- "bgpd": {
- rmap_name: {
- "rules": [
- {
- "sequenceNumber": 10,
- "setClauses": ["__ordered__"] + set_clauses,
- }
- ]
- }
- }
- }
- test_func = partial(
- topotest.router_json_cmp,
- r1,
- f"show route-map {rmap_name} json",
- expected,
- )
- _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
- assert (
- result is None
- ), f'"r1" route-map {rmap_name} has not the expected set clauses'
-
-
-def r3_load_json(filename):
- "Load a JSON configuration file into r3 bgpd and commit it"
- tgen = get_topogen()
- r3 = tgen.gears["r3"]
-
- vty_cmd = os.path.join(CWD, "vty_cmd.py")
- json_file = os.path.join(CWD, f"r3/{filename}")
- return r3.cmd(
- f"python3 {vty_cmd} /var/run/frr/bgpd.vty 'enable' 'configure'"
- f" 'configuration load file json {json_file}' 'commit' 'end'"
- )
-
-
-def check_r3_route_map(set_clauses):
- tgen = get_topogen()
- r3 = tgen.gears["r3"]
-
- expected = {
- "bgpd": {
- "JSON": {
- "rules": [
- {
- "sequenceNumber": 10,
- "setClauses": ["__ordered__"] + set_clauses,
- }
- ]
- }
- }
- }
- test_func = partial(
- topotest.router_json_cmp,
- r3,
- "show route-map JSON json",
- expected,
- )
- _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
- assert result is None, '"r3" route-map JSON has not the expected set clauses'
-
-
-def check_r1_malformed(output, color):
- tgen = get_topogen()
- r1 = tgen.gears["r1"]
-
- assert (
- f"% Malformed color extended community {color}" in output
- ), f'"r1" color {color} is not rejected'
-
- # bgpd used to abort here because of a set-action without value
- r1.vtysh_cmd("show running-config")
-
- check_r1_route_map("MALFORMED", [])
-
-
-def test_bgp_converge():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- logger.info("r2, check 10.10.10.1/32 is received without extended community")
- check_r2_route(None)
-
-
-def test_bgp_color_without_co_bits():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color without CO bits")
- r1.vtysh_cmd(
- """
-configure terminal
-route-map COLOR permit 10
- set extcommunity color 100
-"""
- )
-
- check_r1_route_map("COLOR", ["extcommunity color 100"])
-
- logger.info("r2, check the color is received with CO bits 00")
- check_r2_route("Color:00:100")
-
-
-def test_bgp_color_with_co_bits():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color with all the CO bits values")
- r1.vtysh_cmd(
- """
-configure terminal
-route-map COLOR permit 10
- set extcommunity color 00:200 01:300 10:400 11:4294967295
-"""
- )
-
- check_r1_route_map(
- "COLOR", ["extcommunity color 00:200 01:300 10:400 11:4294967295"]
- )
-
- logger.info("r2, check the colors are received with their CO bits")
- check_r2_route("Color:00:200 Color:01:300 Color:10:400 Color:11:4294967295")
-
-
-def test_bgp_color_mixed_co_bits():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color with and without CO bits")
- r1.vtysh_cmd(
- """
-configure terminal
-route-map COLOR permit 10
- set extcommunity color 500 10:600
-"""
- )
-
- check_r1_route_map("COLOR", ["extcommunity color 500 10:600"])
-
- logger.info("r2, check the colors are received")
- check_r2_route("Color:00:500 Color:10:600")
-
-
-def test_bgp_color_malformed_not_a_number():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color abc")
- output = r1.vtysh_cmd(
- """
-configure terminal
-route-map MALFORMED permit 10
- set extcommunity color abc
-"""
- )
-
- check_r1_malformed(output, "abc")
-
- logger.info("r2, check the route is unchanged")
- check_r2_route("Color:00:500 Color:10:600")
-
-
-def test_bgp_color_malformed_color_zero():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color 0")
- output = r1.vtysh_cmd(
- """
-configure terminal
-route-map MALFORMED permit 10
- set extcommunity color 0
-"""
- )
-
- check_r1_malformed(output, "0")
-
- logger.info("r2, check the route is unchanged")
- check_r2_route("Color:00:500 Color:10:600")
-
-
-def test_bgp_color_malformed_color_too_big():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color 4294967296")
- output = r1.vtysh_cmd(
- """
-configure terminal
-route-map MALFORMED permit 10
- set extcommunity color 4294967296
-"""
- )
-
- check_r1_malformed(output, "4294967296")
-
- logger.info("r2, check the route is unchanged")
- check_r2_route("Color:00:500 Color:10:600")
-
-
-def test_bgp_color_malformed_co_bits():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color 02:100")
- output = r1.vtysh_cmd(
- """
-configure terminal
-route-map MALFORMED permit 10
- set extcommunity color 02:100
-"""
- )
-
- check_r1_malformed(output, "02:100")
-
- logger.info("r2, check the route is unchanged")
- check_r2_route("Color:00:500 Color:10:600")
-
-
-def test_bgp_color_malformed_route_target():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color 65000:100")
- output = r1.vtysh_cmd(
- """
-configure terminal
-route-map MALFORMED permit 10
- set extcommunity color 65000:100
-"""
- )
-
- check_r1_malformed(output, "65000:100")
-
- logger.info("r2, check the route is unchanged")
- check_r2_route("Color:00:500 Color:10:600")
-
-
-def test_bgp_color_malformed_in_list():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, set extcommunity color 01:100 abc")
- output = r1.vtysh_cmd(
- """
-configure terminal
-route-map MALFORMED permit 10
- set extcommunity color 01:100 abc
-"""
- )
-
- check_r1_malformed(output, "abc")
-
- logger.info("r2, check the route is unchanged")
- check_r2_route("Color:00:500 Color:10:600")
-
-
-def test_bgp_color_malformed_existing_color():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- logger.info("r1, replace the COLOR route-map color with a malformed one")
- output = r1.vtysh_cmd(
- """
-configure terminal
-route-map COLOR permit 10
- set extcommunity color abc
-"""
- )
-
- assert (
- "% Malformed color extended community abc" in output
- ), '"r1" color abc is not rejected'
-
- logger.info("r1, check the previous color is kept")
- check_r1_route_map("COLOR", ["extcommunity color 500 10:600"])
-
- logger.info("r2, check the route is unchanged")
- check_r2_route("Color:00:500 Color:10:600")
-
-
-def test_bgp_color_json_load():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- logger.info("r3, load colors 01:100 10:200 from a JSON file")
- output = r3_load_json("color.json")
-
- assert (
- "Configuration committed successfully" in output
- ), f'"r3" failed to load color.json: {output}'
-
- check_r3_route_map(["extcommunity color 01:100 10:200"])
-
-
-def test_bgp_color_json_load_oversized():
- tgen = get_topogen()
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- logger.info("r3, load color 4294967296 from a JSON file")
- output = r3_load_json("color_oversized.json")
-
- # Without a white space between the colors in the YANG pattern,
- # 4294967296 matched as 429496729 and 6
- assert (
- 'Unsatisfied pattern - "4294967296"' in output
- ), f'"r3" color 4294967296 is not rejected: {output}'
-
- logger.info("r3, check the previous colors are kept")
- check_r3_route_map(["extcommunity color 01:100 10:200"])
-
-
-if __name__ == "__main__":
- args = ["-s"] + sys.argv[1:]
- sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_color_extcommunity_syntax/vty_cmd.py b/tests/topotests/bgp_color_extcommunity_syntax/vty_cmd.py
deleted file mode 100644
index e567f28aa1..0000000000
--- a/tests/topotests/bgp_color_extcommunity_syntax/vty_cmd.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: ISC
-#
-# Copyright (c) 2026 by 6WIND S.A.
-# Louis Scalbert <louis.scalbert@6wind.com>
-#
-
-"""
-Run commands directly on a FRR daemon vty socket, without vtysh.
-
-This allows to run the daemon commands that vtysh does not know, such as
-"configuration load" in transactional CLI mode.
-
-usage: vty_cmd.py SOCKET COMMAND...
-"""
-
-import socket
-import sys
-
-
-def vty_cmd(sock, cmd):
- sock.sendall(cmd.encode() + b"\0")
- output = b""
- # The daemon ends its output with three null bytes and a status byte
- while len(output) < 4 or output[-4:-1] != b"\0\0\0":
- data = sock.recv(4096)
- if not data:
- raise RuntimeError(f"{sys.argv[1]}: connection closed")
- output += data
- return output[:-4].decode(errors="replace")
-
-
-def main():
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- sock.connect(sys.argv[1])
- for cmd in sys.argv[2:]:
- print(vty_cmd(sock, cmd), end="")
- sock.close()
-
-
-if __name__ == "__main__":
- main()
diff --git a/yang/frr-bgp-route-map.yang b/yang/frr-bgp-route-map.yang
index b82be01159..bc1478c5ec 100644
--- a/yang/frr-bgp-route-map.yang
+++ b/yang/frr-bgp-route-map.yang
@@ -586,23 +586,16 @@ identity set-extcommunity-color {
typedef color-list {
type string {
- pattern '((00|01|10|11):)?(429496729[0-5]|42949672[0-8][0-9]|'
+ pattern '((00|01|10|11):(429496729[0-5]|42949672[0-8][0-9]|'
+ '4294967[0-1][0-9]{2}|429496[0-6][0-9]{3}|'
+ '42949[0-5][0-9]{4}|4294[0-8][0-9]{5}|'
+ '429[0-3][0-9]{6}|42[0-8][0-9]{7}|'
+ '4[0-1][0-9]{8}|[1-3][0-9]{9}|'
- + '[1-9][0-9]{0,8})'
- + '(\s+((00|01|10|11):)?(429496729[0-5]|42949672[0-8][0-9]|'
- + '4294967[0-1][0-9]{2}|429496[0-6][0-9]{3}|'
- + '42949[0-5][0-9]{4}|4294[0-8][0-9]{5}|'
- + '429[0-3][0-9]{6}|42[0-8][0-9]{7}|'
- + '4[0-1][0-9]{8}|[1-3][0-9]{9}|'
- + '[1-9][0-9]{0,8}))*\s*';
+ + '[1-9][0-9]{0,8})(\s*))+';
}
description
"The color-list type represent a set of colors of value (examples 00:200 01:200 10:200)
- values are separated by white spaces. The CO bits prefix is optional
- for backward compatibility (example 200)";
+ values are separated by white spaces";
reference
"RFC 9012 - The BGP Tunnel Encapsulation Attribute.
RFC 9256 - Segment Routing Policy Architecture.";