Merge pull request #6730 from wesleycoakley/pbrd-dscp-ecn

DSCP / ECN-based PBR Matching
This commit is contained in:
Russ White 2020-07-23 12:08:38 -04:00 committed by GitHub
commit a90f46738a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 267 additions and 13 deletions

View File

@ -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

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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
}
]
},

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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 *

View File

@ -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)