summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/user/pbr.rst16
-rw-r--r--lib/pbr.h7
-rw-r--r--pbrd/pbr_map.c55
-rw-r--r--pbrd/pbr_map.h3
-rw-r--r--pbrd/pbr_vty.c107
-rw-r--r--pbrd/pbr_zebra.c1
-rw-r--r--tests/topotests/pbr-topo1/r1/pbr-map.json26
-rw-r--r--tests/topotests/pbr-topo1/r1/pbrd.conf10
-rw-r--r--zebra/rule_netlink.c31
-rw-r--r--zebra/zapi_msg.c4
-rw-r--r--zebra/zebra_dplane.c16
-rw-r--r--zebra/zebra_dplane.h2
-rw-r--r--zebra/zebra_pbr.h2
13 files changed, 267 insertions, 13 deletions
diff --git a/doc/user/pbr.rst b/doc/user/pbr.rst
index 149949e863..99ef258cb2 100644
--- a/doc/user/pbr.rst
+++ b/doc/user/pbr.rst
@@ -123,6 +123,22 @@ end destination.
on another platform it will be denied. This mark translates to the
underlying `ip rule .... fwmark XXXX` command.
+.. clicmd:: match dscp (DSCP|0-63)
+
+ Match packets according to the specified differentiated services code point
+ (DSCP) in the IP header; if this value matches then forward the packet
+ according to the nexthop(s) specified. The passed DSCP value may also be a
+ standard name for a differentiated service code point like cs0 or af11.
+
+ You may only specify one dscp per route map sequence; to match on multiple
+ dscp values you will need to create several sequences, one for each value.
+
+.. clicmd:: match ecn (0-3)
+
+ Match packets according to the specified explicit congestion notification
+ (ECN) field in the IP header; if this value matches then forward the packet
+ according to the nexthop(s) specified.
+
.. clicmd:: set nexthop-group NAME
Use the nexthop-group NAME as the place to forward packets when the match
diff --git a/lib/pbr.h b/lib/pbr.h
index cf6ac41d32..fd183d7115 100644
--- a/lib/pbr.h
+++ b/lib/pbr.h
@@ -49,6 +49,10 @@ struct pbr_filter {
#define PBR_FILTER_PROTO (1 << 5)
#define PBR_FILTER_SRC_PORT_RANGE (1 << 6)
#define PBR_FILTER_DST_PORT_RANGE (1 << 7)
+#define PBR_FILTER_DSFIELD (1 << 8)
+
+#define PBR_DSFIELD_DSCP (0xfc) /* Upper 6 bits of DS field: DSCP */
+#define PBR_DSFIELD_ECN (0x03) /* Lower 2 bits of DS field: BCN */
/* Source and Destination IP address with masks. */
struct prefix src_ip;
@@ -58,6 +62,9 @@ struct pbr_filter {
uint16_t src_port;
uint16_t dst_port;
+ /* Filter by Differentiated Services field */
+ uint8_t dsfield; /* DSCP (6 bits) & ECN (2 bits) */
+
/* Filter with fwmark */
uint32_t fwmark;
};
diff --git a/pbrd/pbr_map.c b/pbrd/pbr_map.c
index 0ef8432e5e..10a75a9f54 100644
--- a/pbrd/pbr_map.c
+++ b/pbrd/pbr_map.c
@@ -444,6 +444,59 @@ static void pbr_map_add_interfaces(struct pbr_map *pbrm)
}
}
+/* Decodes a standardized DSCP into its representative value */
+uint8_t pbr_map_decode_dscp_enum(const char *name)
+{
+ /* Standard Differentiated Services Field Codepoints */
+ if (!strcmp(name, "cs0"))
+ return 0;
+ if (!strcmp(name, "cs1"))
+ return 8;
+ if (!strcmp(name, "cs2"))
+ return 16;
+ if (!strcmp(name, "cs3"))
+ return 24;
+ if (!strcmp(name, "cs4"))
+ return 32;
+ if (!strcmp(name, "cs5"))
+ return 40;
+ if (!strcmp(name, "cs6"))
+ return 48;
+ if (!strcmp(name, "cs7"))
+ return 56;
+ if (!strcmp(name, "af11"))
+ return 10;
+ if (!strcmp(name, "af12"))
+ return 12;
+ if (!strcmp(name, "af13"))
+ return 14;
+ if (!strcmp(name, "af21"))
+ return 18;
+ if (!strcmp(name, "af22"))
+ return 20;
+ if (!strcmp(name, "af23"))
+ return 22;
+ if (!strcmp(name, "af31"))
+ return 26;
+ if (!strcmp(name, "af32"))
+ return 28;
+ if (!strcmp(name, "af33"))
+ return 30;
+ if (!strcmp(name, "af41"))
+ return 34;
+ if (!strcmp(name, "af42"))
+ return 36;
+ if (!strcmp(name, "af43"))
+ return 38;
+ if (!strcmp(name, "ef"))
+ return 46;
+ if (!strcmp(name, "voice-admit"))
+ return 44;
+
+ /* No match? Error out */
+ return -1;
+}
+
struct pbr_map_sequence *pbrms_get(const char *name, uint32_t seqno)
{
struct pbr_map *pbrm;
@@ -547,7 +600,7 @@ pbr_map_sequence_check_nexthops_valid(struct pbr_map_sequence *pbrms)
static void pbr_map_sequence_check_not_empty(struct pbr_map_sequence *pbrms)
{
- if (!pbrms->src && !pbrms->dst && !pbrms->mark)
+ if (!pbrms->src && !pbrms->dst && !pbrms->mark && !pbrms->dsfield)
pbrms->reason |= PBR_MAP_INVALID_EMPTY;
}
diff --git a/pbrd/pbr_map.h b/pbrd/pbr_map.h
index 704f1a0361..64c090d2e8 100644
--- a/pbrd/pbr_map.h
+++ b/pbrd/pbr_map.h
@@ -89,6 +89,7 @@ struct pbr_map_sequence {
*/
struct prefix *src;
struct prefix *dst;
+ uint8_t dsfield;
uint32_t mark;
/*
@@ -168,6 +169,8 @@ extern void pbr_map_add_interface(struct pbr_map *pbrm, struct interface *ifp);
extern void pbr_map_interface_delete(struct pbr_map *pbrm,
struct interface *ifp);
+extern uint8_t pbr_map_decode_dscp_enum(const char *name);
+
/* Update maps installed on interface */
extern void pbr_map_policy_interface_update(const struct interface *ifp,
bool state_up);
diff --git a/pbrd/pbr_vty.c b/pbrd/pbr_vty.c
index cd9096cbc8..a73d885ea6 100644
--- a/pbrd/pbr_vty.c
+++ b/pbrd/pbr_vty.c
@@ -183,6 +183,91 @@ DEFPY(pbr_map_match_dst, pbr_map_match_dst_cmd,
return CMD_SUCCESS;
}
+DEFPY(pbr_map_match_dscp, pbr_map_match_dscp_cmd,
+ "[no] match dscp DSCP$dscp",
+ NO_STR
+ "Match the rest of the command\n"
+ "Match based on IP DSCP field\n"
+ "DSCP value (below 64) or standard codepoint name\n")
+{
+ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence);
+ char dscpname[100];
+ uint8_t rawDscp;
+
+ /* Discriminate dscp enums (cs0, cs1 etc.) and numbers */
+ bool isANumber = true;
+ for (int i = 0; i < (int)strlen(dscp); i++) {
+ /* Letters are not numbers */
+ if (!isdigit(dscp[i]))
+ isANumber = false;
+
+ /* Lowercase the dscp enum (if needed) */
+ if (isupper(dscp[i]))
+ dscpname[i] = tolower(dscp[i]);
+ else
+ dscpname[i] = dscp[i];
+ }
+ dscpname[strlen(dscp)] = '\0';
+
+ if (isANumber) {
+ /* dscp passed is a regular number */
+ long dscpAsNum = strtol(dscp, NULL, 0);
+
+ if (dscpAsNum > PBR_DSFIELD_DSCP >> 2) {
+ /* Refuse to install on overflow */
+ vty_out(vty, "dscp (%s) must be less than 64\n", dscp);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ rawDscp = dscpAsNum;
+ } else {
+ /* check dscp if it is an enum like cs0 */
+ rawDscp = pbr_map_decode_dscp_enum(dscpname);
+ if (rawDscp > PBR_DSFIELD_DSCP) {
+ vty_out(vty, "Invalid dscp value: %s\n", dscpname);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ if (!no) {
+ if (((pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2) == rawDscp)
+ return CMD_SUCCESS;
+
+ /* Set the DSCP bits of the DSField */
+ pbrms->dsfield =
+ (pbrms->dsfield & ~PBR_DSFIELD_DSCP) | (rawDscp << 2);
+ } else {
+ pbrms->dsfield &= ~PBR_DSFIELD_DSCP;
+ }
+
+ pbr_map_check(pbrms, true);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(pbr_map_match_ecn, pbr_map_match_ecn_cmd,
+ "[no] match ecn (0-3)$ecn",
+ NO_STR
+ "Match the rest of the command\n"
+ "Match based on IP ECN field\n"
+ "Explicit Congestion Notification\n")
+{
+ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence);
+
+ if (!no) {
+ if ((pbrms->dsfield & PBR_DSFIELD_ECN) == ecn)
+ return CMD_SUCCESS;
+
+ /* Set the ECN bits of the DSField */
+ pbrms->dsfield = (pbrms->dsfield & ~PBR_DSFIELD_ECN) | ecn;
+ } else {
+ pbrms->dsfield &= ~PBR_DSFIELD_ECN;
+ }
+
+ pbr_map_check(pbrms, true);
+
+ return CMD_SUCCESS;
+}
+
DEFPY(pbr_map_match_mark, pbr_map_match_mark_cmd,
"[no] match mark (1-4294967295)$mark",
NO_STR
@@ -559,6 +644,12 @@ static void vty_show_pbrms(struct vty *vty,
if (pbrms->dst)
vty_out(vty, " DST Match: %s\n",
prefix2str(pbrms->dst, buf, sizeof(buf)));
+ if (pbrms->dsfield & PBR_DSFIELD_DSCP)
+ vty_out(vty, " DSCP Match: %u\n",
+ (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2);
+ if (pbrms->dsfield & PBR_DSFIELD_ECN)
+ vty_out(vty, " ECN Match: %u\n",
+ pbrms->dsfield & PBR_DSFIELD_ECN);
if (pbrms->mark)
vty_out(vty, " MARK Match: %u\n", pbrms->mark);
@@ -653,6 +744,12 @@ static void vty_json_pbrms(json_object *j, struct vty *vty,
prefix2str(pbrms->dst, buf, sizeof(buf)));
if (pbrms->mark)
json_object_int_add(jpbrm, "matchMark", pbrms->mark);
+ if (pbrms->dsfield & PBR_DSFIELD_DSCP)
+ json_object_int_add(jpbrm, "matchDscp",
+ (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2);
+ if (pbrms->dsfield & PBR_DSFIELD_ECN)
+ json_object_int_add(jpbrm, "matchEcn",
+ pbrms->dsfield & PBR_DSFIELD_ECN);
json_object_array_add(j, jpbrm);
}
@@ -946,6 +1043,14 @@ static int pbr_vty_map_config_write_sequence(struct vty *vty,
vty_out(vty, " match dst-ip %s\n",
prefix2str(pbrms->dst, buff, sizeof(buff)));
+ if (pbrms->dsfield & PBR_DSFIELD_DSCP)
+ vty_out(vty, " match dscp %u\n",
+ (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2);
+
+ if (pbrms->dsfield & PBR_DSFIELD_ECN)
+ vty_out(vty, " match ecn %u\n",
+ pbrms->dsfield & PBR_DSFIELD_ECN);
+
if (pbrms->mark)
vty_out(vty, " match mark %u\n", pbrms->mark);
@@ -1026,6 +1131,8 @@ void pbr_vty_init(void)
install_element(INTERFACE_NODE, &pbr_policy_cmd);
install_element(PBRMAP_NODE, &pbr_map_match_src_cmd);
install_element(PBRMAP_NODE, &pbr_map_match_dst_cmd);
+ install_element(PBRMAP_NODE, &pbr_map_match_dscp_cmd);
+ install_element(PBRMAP_NODE, &pbr_map_match_ecn_cmd);
install_element(PBRMAP_NODE, &pbr_map_match_mark_cmd);
install_element(PBRMAP_NODE, &pbr_map_nexthop_group_cmd);
install_element(PBRMAP_NODE, &no_pbr_map_nexthop_group_cmd);
diff --git a/pbrd/pbr_zebra.c b/pbrd/pbr_zebra.c
index de2a99e269..d0099a46e3 100644
--- a/pbrd/pbr_zebra.c
+++ b/pbrd/pbr_zebra.c
@@ -536,6 +536,7 @@ static void pbr_encode_pbr_map_sequence(struct stream *s,
stream_putw(s, 0); /* src port */
pbr_encode_pbr_map_sequence_prefix(s, pbrms->dst, family);
stream_putw(s, 0); /* dst port */
+ stream_putc(s, pbrms->dsfield);
stream_putl(s, pbrms->mark);
if (pbrms->vrf_unchanged || pbrms->vrf_lookup)
diff --git a/tests/topotests/pbr-topo1/r1/pbr-map.json b/tests/topotests/pbr-topo1/r1/pbr-map.json
index f0738dc540..bfa0ecb849 100644
--- a/tests/topotests/pbr-topo1/r1/pbr-map.json
+++ b/tests/topotests/pbr-topo1/r1/pbr-map.json
@@ -62,6 +62,32 @@
},
"matchDst":"dead:beef::\/64",
"matchMark":314159
+ },
+ {
+ "sequenceNumber":15,
+ "vrfUnchanged":false,
+ "installed":true,
+ "installedReason":"Valid",
+ "nexthopGroup":{
+ "name":"ASAKUSA15",
+ "installed":true,
+ "installedInternally":1
+ },
+ "matchDst":"dead:beef::/64",
+ "matchDscp":10
+ },
+ {
+ "sequenceNumber":20,
+ "vrfUnchanged":false,
+ "installed":true,
+ "installedReason":"Valid",
+ "nexthopGroup":{
+ "name":"ASAKUSA20",
+ "installed":true,
+ "installedInternally":1
+ },
+ "matchDst":"dead:beef::/64",
+ "matchEcn":1
}
]
},
diff --git a/tests/topotests/pbr-topo1/r1/pbrd.conf b/tests/topotests/pbr-topo1/r1/pbrd.conf
index 298cba2860..45cb7656ab 100644
--- a/tests/topotests/pbr-topo1/r1/pbrd.conf
+++ b/tests/topotests/pbr-topo1/r1/pbrd.conf
@@ -73,6 +73,16 @@ pbr-map ASAKUSA seq 10
match mark 314159
set nexthop c0ff:ee::1
!
+pbr-map ASAKUSA seq 15
+ match dst-ip dead:beef::/64
+ match dscp af11
+ set nexthop c0ff:ee::1
+!
+pbr-map ASAKUSA seq 20
+ match dst-ip dead:beef::/64
+ match ecn 1
+ set nexthop c0ff:ee::1
+!
# Interface policies
int r1-eth1
pbr-policy EVA
diff --git a/zebra/rule_netlink.c b/zebra/rule_netlink.c
index d5686ad883..648e9eabe1 100644
--- a/zebra/rule_netlink.c
+++ b/zebra/rule_netlink.c
@@ -58,10 +58,12 @@
* Returns -1 on failure, 0 when the msg doesn't fit entirely in the buffer
* or the number of bytes written to buf.
*/
-static ssize_t netlink_rule_msg_encode(
- int cmd, const struct zebra_dplane_ctx *ctx, uint32_t filter_bm,
- uint32_t priority, uint32_t table, const struct prefix *src_ip,
- const struct prefix *dst_ip, uint32_t fwmark, void *buf, size_t buflen)
+static ssize_t
+netlink_rule_msg_encode(int cmd, const struct zebra_dplane_ctx *ctx,
+ uint32_t filter_bm, uint32_t priority, uint32_t table,
+ const struct prefix *src_ip,
+ const struct prefix *dst_ip, uint32_t fwmark,
+ uint8_t dsfield, void *buf, size_t buflen)
{
uint8_t protocol = RTPROT_ZEBRA;
int family;
@@ -122,6 +124,10 @@ static ssize_t netlink_rule_msg_encode(
return 0;
}
+ /* dsfield, if specified */
+ if (filter_bm & PBR_FILTER_DSFIELD)
+ req->frh.tos = dsfield;
+
/* Route table to use to forward, if filter criteria matches. */
if (table < 256)
req->frh.table = table;
@@ -145,16 +151,15 @@ static ssize_t netlink_rule_msg_encode(
/* Install or uninstall specified rule for a specific interface.
* Form netlink message and ship it.
*/
-static int
-netlink_rule_update_internal(int cmd, const struct zebra_dplane_ctx *ctx,
- uint32_t filter_bm, uint32_t priority,
- uint32_t table, const struct prefix *src_ip,
- const struct prefix *dst_ip, uint32_t fwmark)
+static int netlink_rule_update_internal(
+ int cmd, const struct zebra_dplane_ctx *ctx, uint32_t filter_bm,
+ uint32_t priority, uint32_t table, const struct prefix *src_ip,
+ const struct prefix *dst_ip, uint32_t fwmark, uint8_t dsfield)
{
char buf[NL_PKT_BUF_SIZE];
netlink_rule_msg_encode(cmd, ctx, filter_bm, priority, table, src_ip,
- dst_ip, fwmark, buf, sizeof(buf));
+ dst_ip, fwmark, dsfield, buf, sizeof(buf));
return netlink_talk_info(netlink_talk_filter, (void *)&buf,
dplane_ctx_get_ns(ctx), 0);
}
@@ -188,7 +193,8 @@ enum zebra_dplane_result kernel_pbr_rule_update(struct zebra_dplane_ctx *ctx)
dplane_ctx_rule_get_priority(ctx),
dplane_ctx_rule_get_table(ctx), dplane_ctx_rule_get_src_ip(ctx),
dplane_ctx_rule_get_dst_ip(ctx),
- dplane_ctx_rule_get_fwmark(ctx));
+ dplane_ctx_rule_get_fwmark(ctx),
+ dplane_ctx_rule_get_dsfield(ctx));
/**
* Delete the old one.
@@ -203,7 +209,8 @@ enum zebra_dplane_result kernel_pbr_rule_update(struct zebra_dplane_ctx *ctx)
dplane_ctx_rule_get_old_table(ctx),
dplane_ctx_rule_get_old_src_ip(ctx),
dplane_ctx_rule_get_old_dst_ip(ctx),
- dplane_ctx_rule_get_old_fwmark(ctx));
+ dplane_ctx_rule_get_old_fwmark(ctx),
+ dplane_ctx_rule_get_old_dsfield(ctx));
return (ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS
diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c
index d088a3a3d0..062941a1e4 100644
--- a/zebra/zapi_msg.c
+++ b/zebra/zapi_msg.c
@@ -2526,6 +2526,7 @@ static inline void zread_rule(ZAPI_HANDLER_ARGS)
STREAM_GET(&zpr.rule.filter.dst_ip.u.prefix, s,
prefix_blen(&zpr.rule.filter.dst_ip));
STREAM_GETW(s, zpr.rule.filter.dst_port);
+ STREAM_GETC(s, zpr.rule.filter.dsfield);
STREAM_GETL(s, zpr.rule.filter.fwmark);
STREAM_GETL(s, zpr.rule.action.table);
STREAM_GETL(s, zpr.rule.ifindex);
@@ -2556,6 +2557,9 @@ static inline void zread_rule(ZAPI_HANDLER_ARGS)
if (zpr.rule.filter.dst_port)
zpr.rule.filter.filter_bm |= PBR_FILTER_DST_PORT;
+ if (zpr.rule.filter.dsfield)
+ zpr.rule.filter.filter_bm |= PBR_FILTER_DSFIELD;
+
if (zpr.rule.filter.fwmark)
zpr.rule.filter.filter_bm |= PBR_FILTER_FWMARK;
diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c
index 4e19e58d7a..2f20b3cd75 100644
--- a/zebra/zebra_dplane.c
+++ b/zebra/zebra_dplane.c
@@ -204,6 +204,7 @@ struct dplane_ctx_rule {
/* Filter criteria */
uint32_t filter_bm;
uint32_t fwmark;
+ uint8_t dsfield;
struct prefix src_ip;
struct prefix dst_ip;
};
@@ -1676,6 +1677,20 @@ uint32_t dplane_ctx_rule_get_old_fwmark(const struct zebra_dplane_ctx *ctx)
return ctx->u.rule.old.fwmark;
}
+uint8_t dplane_ctx_rule_get_dsfield(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+
+ return ctx->u.rule.new.dsfield;
+}
+
+uint8_t dplane_ctx_rule_get_old_dsfield(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+
+ return ctx->u.rule.old.dsfield;
+}
+
const struct prefix *
dplane_ctx_rule_get_src_ip(const struct zebra_dplane_ctx *ctx)
{
@@ -2129,6 +2144,7 @@ static void dplane_ctx_rule_init_single(struct dplane_ctx_rule *dplane_rule,
dplane_rule->filter_bm = rule->rule.filter.filter_bm;
dplane_rule->fwmark = rule->rule.filter.fwmark;
+ dplane_rule->dsfield = rule->rule.filter.dsfield;
prefix_copy(&(dplane_rule->dst_ip), &rule->rule.filter.dst_ip);
prefix_copy(&(dplane_rule->src_ip), &rule->rule.filter.src_ip);
}
diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h
index 8e873886df..0fa21f620d 100644
--- a/zebra/zebra_dplane.h
+++ b/zebra/zebra_dplane.h
@@ -412,6 +412,8 @@ uint32_t dplane_ctx_rule_get_filter_bm(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_rule_get_old_filter_bm(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_rule_get_fwmark(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_rule_get_old_fwmark(const struct zebra_dplane_ctx *ctx);
+uint8_t dplane_ctx_rule_get_dsfield(const struct zebra_dplane_ctx *ctx);
+uint8_t dplane_ctx_rule_get_old_dsfield(const struct zebra_dplane_ctx *ctx);
const struct prefix *
dplane_ctx_rule_get_src_ip(const struct zebra_dplane_ctx *ctx);
const struct prefix *
diff --git a/zebra/zebra_pbr.h b/zebra/zebra_pbr.h
index 4bc0f40037..dd2d7a190d 100644
--- a/zebra/zebra_pbr.h
+++ b/zebra/zebra_pbr.h
@@ -54,6 +54,8 @@ struct zebra_pbr_rule {
(r->rule.filter.filter_bm & PBR_FILTER_SRC_PORT)
#define IS_RULE_FILTERING_ON_DST_PORT(r) \
(r->rule.filter.filter_bm & PBR_FILTER_DST_PORT)
+#define IS_RULE_FILTERING_ON_DSFIELD(r) \
+ (r->rule.filter.filter_bm & PBR_FILTER_DSFIELD)
#define IS_RULE_FILTERING_ON_FWMARK(r) \
(r->rule.filter.filter_bm & PBR_FILTER_FWMARK)