Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/gr_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <stdlib.h>

// Must be bumped when making non-backward compatible changes in API headers
#define GR_API_VERSION 3
#define GR_API_VERSION 4

// API request header.
struct gr_api_request {
Expand Down
8 changes: 8 additions & 0 deletions api/gr_net_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ struct rte_ipv6_addr {
uint8_t a[RTE_IPV6_ADDR_SIZE];
};

static inline bool rte_ipv6_addr_is_unspec(const struct rte_ipv6_addr *ip) {
for (unsigned i = 0; i < RTE_IPV6_ADDR_SIZE; i++) {
if (ip->a[i] != 0)
return false;
}
return true;
}

static inline void rte_ipv6_addr_mask(struct rte_ipv6_addr *ip, uint8_t depth) {
if (depth < RTE_IPV6_MAX_DEPTH) {
unsigned int d = depth / CHAR_BIT;
Expand Down
7 changes: 6 additions & 1 deletion frr/rt_grout.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ static int grout_gr_nexthop_to_frr_nexthop(
encap_behavior
#if CURRENT_FRR_VERSION >= MAKE_FRRVERSION(10, 8, 0)
,
NULL
rte_ipv6_addr_is_unspec(&sr6->encap_src) ? NULL : (void *)&sr6->encap_src
#endif
);
nh->type = nh->ifindex ? NEXTHOP_TYPE_IPV6_IFINDEX : NEXTHOP_TYPE_IPV6;
Expand Down Expand Up @@ -850,6 +850,11 @@ grout_add_nexthop(uint32_t nh_id, gr_nh_origin_t origin, const struct nexthop *n
memcpy(&sr6->seglist[i],
&nh->nh_srv6->seg6_segs->seg[i],
sizeof(sr6->seglist[i]));
#if CURRENT_FRR_VERSION >= MAKE_FRRVERSION(10, 8, 0)
memcpy(&sr6->encap_src,
&nh->nh_srv6->seg6_segs->encap_source,
sizeof(sr6->encap_src));
#endif

break;
case GR_NH_T_BLACKHOLE:
Expand Down
1 change: 1 addition & 0 deletions modules/srv6/api/gr_srv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ typedef enum : uint8_t {
// Used with GR_NH_T_SR6_OUTPUT nexthops via GR_NH_ADD from gr_infra.h.
struct gr_nexthop_info_srv6 {
gr_srv6_encap_behavior_t encap_behavior;
struct rte_ipv6_addr encap_src;
uint8_t n_seglist;
struct rte_ipv6_addr seglist[];
};
Expand Down
13 changes: 11 additions & 2 deletions modules/srv6/cli/route.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ static cmd_status_t srv6_nh_add(struct gr_api_client *c, const struct ec_pnode *
else
sr6->encap_behavior = SR_H_ENCAPS;

if (arg_ip6(p, "SRC", &sr6->encap_src) < 0 && errno != ENOENT)
goto out;

if (gr_api_client_send_recv(c, GR_NH_ADD, len, req, NULL) < 0)
goto out;

Expand Down Expand Up @@ -108,6 +111,7 @@ static cmd_status_t srv6_tunsrc_show(struct gr_api_client *c, const struct ec_pn

static void add_columns_srv6(struct gr_table *table) {
gr_table_column(table, "ENCAP", GR_DISP_LEFT);
gr_table_column(table, "ENCAP_SRC", GR_DISP_LEFT);
gr_table_column(table, "SEGLIST", GR_DISP_STR_ARRAY);
}

Expand All @@ -122,6 +126,8 @@ static void fill_table_srv6(struct gr_table *table, unsigned start_col, const vo
"%s",
sr6->encap_behavior == SR_H_ENCAPS_RED ? "h.encaps.red" : "h.encaps"
);
if (!rte_ipv6_addr_is_unspec(&sr6->encap_src))
gr_table_cell(table, start_col + 1, IP6_F, &sr6->encap_src);
for (unsigned i = 0; i < sr6->n_seglist; i++) {
SAFE_BUF(snprintf, sizeof(buf), "%s" IP6_F, i > 0 ? " " : "", &sr6->seglist[i]);
if (sizeof(buf) - n < 50) {
Expand All @@ -131,7 +137,7 @@ static void fill_table_srv6(struct gr_table *table, unsigned start_col, const vo
}
err:
if (n > 0)
gr_table_cell(table, start_col + 1, "%s", buf);
gr_table_cell(table, start_col + 2, "%s", buf);
}

static void fill_object_srv6(struct gr_object *o, const void *info) {
Expand All @@ -144,6 +150,8 @@ static void fill_object_srv6(struct gr_object *o, const void *info) {
"%s",
sr6->encap_behavior == SR_H_ENCAPS_RED ? "h.encaps.red" : "h.encaps"
);
if (!rte_ipv6_addr_is_unspec(&sr6->encap_src))
gr_object_field(o, "encap_src", 0, IP6_F, &sr6->encap_src);
gr_object_array_open(o, "seglist");
for (unsigned i = 0; i < sr6->n_seglist; i++)
gr_object_array_item(o, 0, IP6_F, &sr6->seglist[i]);
Expand All @@ -165,12 +173,13 @@ static int ctx_init(struct ec_node *root) {

ret = CLI_COMMAND(
NEXTHOP_ADD_CTX(root),
"srv6 seglist SEGLIST+ [(encap h.encaps|h.encaps.red),(vrf VRF),(id ID)]",
"srv6 seglist SEGLIST+ [(encap h.encaps|h.encaps.red),(src SRC),(vrf VRF),(id ID)]",
srv6_nh_add,
"Add SRv6 encap nexthop.",
with_help("Encaps.", ec_node_str("h.encaps", "h.encaps")),
with_help("Encaps Reduced.", ec_node_str("h.encaps.red", "h.encaps.red")),
with_help("Next SID to visit.", ec_node_re("SEGLIST", IPV6_RE)),
with_help("Encap source address.", ec_node_re("SRC", IPV6_RE)),
with_help("Nexthop ID.", ec_node_uint("ID", 1, UINT32_MAX - 1, 10)),
with_help("L3 routing domain name.", ec_node_dyn("VRF", complete_vrf_names, NULL))
);
Expand Down
6 changes: 6 additions & 0 deletions modules/srv6/control/route.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ static bool srv6_output_nh_equal(const struct nexthop *a, const struct nexthop *
if (ad->encap != bd->encap)
return false;

if (!rte_ipv6_addr_eq(&ad->encap_src, &bd->encap_src))
return false;

if (ad->n_seglist != bd->n_seglist)
return false;

Expand All @@ -40,6 +43,8 @@ static int srv6_output_nh_import_info(struct nexthop *nh, const void *info) {
memcpy(seglist, pub->seglist, sizeof(*seglist) * pub->n_seglist);

priv->encap = pub->encap_behavior;
priv->encap_src = pub->encap_src;
priv->flags = rte_ipv6_addr_is_unspec(&pub->encap_src) ? 0 : SR_ENCAP_F_SRC;
priv->n_seglist = pub->n_seglist;
tmp = priv->seglist;
priv->seglist = seglist;
Expand All @@ -63,6 +68,7 @@ static struct gr_nexthop *srv6_output_nh_to_api(const struct nexthop *nh, size_t
sr6_pub = (struct gr_nexthop_info_srv6 *)pub->info;

sr6_pub->encap_behavior = sr6_priv->encap;
sr6_pub->encap_src = sr6_priv->encap_src;
sr6_pub->n_seglist = sr6_priv->n_seglist;
memcpy(sr6_pub->seglist,
sr6_priv->seglist,
Expand Down
6 changes: 6 additions & 0 deletions modules/srv6/control/srv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
//
GR_NH_TYPE_INFO(GR_NH_T_SR6_LOCAL, nexthop_info_srv6_local, { BASE(gr_nexthop_info_srv6_local); });

typedef enum : uint8_t {
SR_ENCAP_F_SRC = GR_BIT8(0),
} sr_encap_flags_t;

//
// srv6 encap data is allocated dynamically.
// A pointer to it is stored in nexthop priv.
//
GR_NH_TYPE_INFO(GR_NH_T_SR6_OUTPUT, nexthop_info_srv6_output, {
gr_srv6_encap_behavior_t encap;
sr_encap_flags_t flags;
uint16_t n_seglist;
struct rte_ipv6_addr *seglist;
struct rte_ipv6_addr encap_src;
});

extern struct nexthop *tunsrc_nh;
Expand Down
18 changes: 10 additions & 8 deletions modules/srv6/datapath/srv6_output.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,17 @@ srv6_output_process(struct rte_graph *graph, struct rte_node *node, void **objs,
}
l3_mbuf_data(m)->nh = nh;

nh = sr_tunsrc_get(nh->iface_id, &d->seglist[0]);
if (nh == NULL) {
// cannot output packet on interface that does not have ip6 addr
edge = NO_ROUTE;
goto next;
if (d->flags & SR_ENCAP_F_SRC) {
ip6_set_fields(outer_ip6, plen, proto, &d->encap_src, &d->seglist[0]);
} else {
nh = sr_tunsrc_get(nh->iface_id, &d->seglist[0]);
if (nh == NULL) {
edge = NO_ROUTE;
goto next;
}
l3 = nexthop_info_l3(nh);
ip6_set_fields(outer_ip6, plen, proto, &l3->ipv6, &d->seglist[0]);
}
l3 = nexthop_info_l3(nh);

ip6_set_fields(outer_ip6, plen, proto, &l3->ipv6, &d->seglist[0]);
edge = IP6_OUTPUT;

next:
Expand Down
9 changes: 7 additions & 2 deletions smoke/_init_frr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ set_srv6_localsid() {
exit"
}

# set_srv6_route [--persist] <prefix> <next-hop|iface>
# set_srv6_route [--persist] [--encap-src <addr>] <prefix> <next-hop|iface>
# [<vrf> [<nexthop-vrf>]] <sid1> [sid2 sid3 ...]
#
# EXAMPLES
Expand All @@ -185,6 +185,8 @@ exit"
set_srv6_route() {
local persist=0
[ "$1" = "--persist" ] && { persist=1; shift; }
local encap_src=""
[ "$1" = "--encap-src" ] && { encap_src=$2; shift 2; }
local prefix="$1"
local nhop="$2"
shift 2
Expand Down Expand Up @@ -232,9 +234,12 @@ set_srv6_route() {
local nh_vrf_clause=""
[ -n "$nexthop_vrf_name" ] && nh_vrf_clause=" nexthop-vrf ${nexthop_vrf_name}"

local encap_src_clause=""
[ -n "$encap_src" ] && encap_src_clause=" encap-source ${encap_src}"

_apply_frr_config "$persist" \
"$route add: vrf=$gr_vrf_name $prefix origin=zebra_static via type=SRv6 .*${sids[0]}" \
"${frr_ip} route ${prefix} ${nhop} segments ${seg_frr} vrf ${vrf_name}${nh_vrf_clause}"
"${frr_ip} route ${prefix} ${nhop} segments ${seg_frr}${encap_src_clause} vrf ${vrf_name}${nh_vrf_clause}"
}

# kill_frr_daemons <daemon> [<daemon>...]
Expand Down
76 changes: 76 additions & 0 deletions smoke/srv6_encapsrc_frr_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/bash
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2026 Robin Jarry

set -e
zebra=$(PATH="$1/frr_install/sbin:$1/frr_install/bin:$PATH" command -v zebra)
frr_version=$($zebra --version | sed -En 's/zebra version //p')
min_version=$(printf '%s\n%s\n' "$frr_version" "10.8.0" | sort -V | head -n1)
if ! [ "$min_version" = "10.8.0" ]; then
echo "$0: FRR $frr_version does not support per-nexthop encap source"
exit 125
fi

. $(dirname $0)/_init_frr.sh

create_interface p0
create_interface p1

for n in 0 1; do
p=x-p$n
ns=n$n
netns_add $ns
move_to_netns $p $ns
done
ip -n n0 addr add 192.168.61.2/24 dev x-p0
ip -n n1 addr add fd00:102::2/32 dev x-p1

set_ip_address p0 192.168.61.1/24
set_ip_address p1 fd00:102::1/32

#
# network layout:
# (client) p0(netns) <--> p0 <grout> p1 <---> p1(netns)
# ipv4 ---------------| srv6 |-- ipv4
#
# test case:
# - configure an SRv6 route with an explicit encap source via FRR
# - verify grout uses the per-nexthop source instead of the global one
#

# only linux's p1 will see srv6
ip netns exec n1 sysctl -w net.ipv6.conf.x-p1.seg6_enabled=1
ip netns exec n1 sysctl -w net.ipv6.conf.x-p1.forwarding=1

# client default route
ip -n n0 route add default via 192.168.61.1 dev x-p0

# linux decap and reply network
ip -n n1 -6 route add fd00:202:200:: \
encap seg6local action End.DX4 nh4 192.168.60.1 count dev x-p1
ip -n n1 addr add 192.168.60.1/24 dev x-p1
ip -n n1 route add 192.168.61.0/24 \
encap seg6 mode encap segs fd00:202:100:: dev x-p1
ip -n n1 -6 route add fd00:202::/32 via fd00:102::1 dev x-p1

# grout decap localsid
set_srv6_localsid locator_grout fd00:202 fd00:202:100::

# underlay route
set_ip_route fd00:202::/32 fd00:102::2

# SRv6 route with explicit per-nexthop encap source via FRR
encap_src=fd00:102::42
set_srv6_route --encap-src $encap_src 192.168.0.0/16 p1 fd00:202:200::

# capture and verify the outer source address
ip netns exec n1 timeout 5 tcpdump -c1 -nn -l \
"ip6 src $encap_src" -i x-p1 > $tmp/tcpdump.out 2>&1 &
tcpdump_pid=$!
sleep 1

ip netns exec n0 ping -i0.01 -c3 -n 192.168.60.1
wait $tcpdump_pid || true

grep -q "$encap_src" $tmp/tcpdump.out \
|| fail "encapsulated packet did not use per-nexthop encap_src $encap_src"
24 changes: 24 additions & 0 deletions smoke/srv6_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,27 @@ ip -n n1 route replace 192.168.61.0/24 encap seg6 mode encap segs fd00:202:0300:

# test: ping goes through the NEXT-CSID transit node
ip netns exec n0 ping -i0.01 -c3 -n 192.168.60.1

#
# Per-nexthop encap source test
#
# Create a new SRv6 output nexthop with an explicit source address and
# verify the encapsulated packets use it as the outer IPv6 source.
#

encap_src=fd00:102::42
grcli address add $encap_src/128 iface p1
grcli nexthop add srv6 seglist fd00:202:200:: src $encap_src id 43
grcli route add 192.168.0.0/16 via id 43

# capture a few packets on x-p1 in n1
ip netns exec n1 timeout 5 tcpdump -c1 -pnn -l \
"ip6 src $encap_src" -i x-p1 > $tmp/tcpdump.out 2>&1 &
tcpdump_pid=$!
sleep 1

ip netns exec n0 ping -i0.01 -c3 -n 192.168.60.1
wait $tcpdump_pid || true

grep -q "$encap_src" $tmp/tcpdump.out \
|| fail "encapsulated packet did not use per-nexthop encap_src $encap_src"