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.";