From 01f23affdb68d0117d0f2be652eebd835bdbc13e Mon Sep 17 00:00:00 2001 From: Wesley Coakley Date: Wed, 17 Jun 2020 20:20:05 -0400 Subject: [PATCH] pbrd, zebra, lib: DSCP / ECN-based PBR Matching Extend PBR maps to discriminate by Differentiated Services Code Point and / or Explicit Congestion Notification fields. These fields are used in the IP header for classifying network traffic. 0 1 2 3 4 5 6 7 +-----+-----+-----+-----+-----+-----+-----+-----+ | DS FIELD, DSCP | ECN FIELD | +-----+-----+-----+-----+-----+-----+-----+-----+ DSCP: differentiated services codepoint ECN: Explicit Congestion Notification Signed-off-by: Wesley Coakley Signed-off-by: Saurav Kumar Paul --- doc/user/pbr.rst | 12 ++++++++ lib/pbr.h | 7 +++++ pbrd/pbr_map.c | 2 +- pbrd/pbr_map.h | 1 + pbrd/pbr_vty.c | 67 ++++++++++++++++++++++++++++++++++++++++++++ pbrd/pbr_zebra.c | 1 + zebra/rule_netlink.c | 31 ++++++++++++-------- zebra/zapi_msg.c | 4 +++ zebra/zebra_dplane.c | 16 +++++++++++ zebra/zebra_dplane.h | 2 ++ zebra/zebra_pbr.h | 2 ++ 11 files changed, 132 insertions(+), 13 deletions(-) diff --git a/doc/user/pbr.rst b/doc/user/pbr.rst index 149949e863..a483f17c79 100644 --- a/doc/user/pbr.rst +++ b/doc/user/pbr.rst @@ -123,6 +123,18 @@ end destination. on another platform it will be denied. This mark translates to the underlying `ip rule .... fwmark XXXX` command. +.. clicmd:: match 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. + +.. 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 edc3f1d8da..3a3bea349f 100644 --- a/pbrd/pbr_map.c +++ b/pbrd/pbr_map.c @@ -547,7 +547,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 41f1703954..fb8c6bf032 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; /* diff --git a/pbrd/pbr_vty.c b/pbrd/pbr_vty.c index cd9096cbc8..9472503238 100644 --- a/pbrd/pbr_vty.c +++ b/pbrd/pbr_vty.c @@ -183,6 +183,55 @@ 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 (0-63)$dscp", + NO_STR + "Match the rest of the command\n" + "Match based on IP DSCP field\n" + "Differentiated Service Code Point\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!no) { + if (((pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2) == dscp) + return CMD_SUCCESS; + + /* Set the DSCP bits of the DSField */ + pbrms->dsfield = + (pbrms->dsfield & ~PBR_DSFIELD_DSCP) | (dscp << 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 +608,14 @@ 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->dsfield) + vty_out(vty, " DSField Match: %u\n", (pbrms->dsfield)); if (pbrms->mark) vty_out(vty, " MARK Match: %u\n", pbrms->mark); @@ -946,6 +1003,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 +1091,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/zebra/rule_netlink.c b/zebra/rule_netlink.c index b7be398506..f0cfe64b0f 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 dc7c595d26..daf08e96a0 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 e34b6f23ff..7657ae8dc5 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) -- 2.39.5