// SPDX-License-Identifier: GPL-2.0-or-later /* BGP FlowSpec Utilities * Portions: * Copyright (C) 2017 ChinaTelecom SDN Group * Copyright (C) 2018 6WIND */ #include "zebra.h" #include "lib/printfrr.h" #include "prefix.h" #include "lib_errors.h" #include "bgp_route.h" #include "bgp_table.h" #include "bgp_flowspec_util.h" #include "bgp_flowspec_private.h" #include "bgp_pbr.h" #include "bgp_errors.h" static void hex2bin(uint8_t *hex, int *bin) { int remainder = *hex; int i = 0; while (remainder >= 1 && i < 8) { bin[7-i] = remainder % 2; remainder = remainder / 2; i++; } for (; i < 8; i++) bin[7-i] = 0; } static int hexstr2num(uint8_t *hexstr, int len) { int i = 0; int num = 0; for (i = 0; i < len; i++) num = hexstr[i] + 16*16*num; return num; } /* call bgp_flowspec_op_decode * returns offset */ static int bgp_flowspec_call_non_opaque_decode(uint8_t *nlri_content, int len, struct bgp_pbr_match_val *mval, uint8_t *match_num, int *error) { int ret; ret = bgp_flowspec_op_decode( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content, len, mval, error); if (*error < 0) flog_err(EC_BGP_FLOWSPEC_PACKET, "%s: flowspec_op_decode error %d", __func__, *error); else *match_num = *error; return ret; } bool bgp_flowspec_contains_prefix(const struct prefix *pfs, struct prefix *input, int prefix_check) { uint32_t offset = 0; int type; int ret = 0, error = 0; uint8_t *nlri_content = (uint8_t *)pfs->u.prefix_flowspec.ptr; size_t len = pfs->u.prefix_flowspec.prefixlen; afi_t afi = family2afi(pfs->u.prefix_flowspec.family); struct prefix compare; error = 0; while (offset < len-1 && error >= 0) { type = nlri_content[offset]; offset++; switch (type) { case FLOWSPEC_DEST_PREFIX: case FLOWSPEC_SRC_PREFIX: memset(&compare, 0, sizeof(compare)); ret = bgp_flowspec_ip_address( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content+offset, len - offset, &compare, &error, afi, NULL); if (ret <= 0) break; if (prefix_check && compare.prefixlen != input->prefixlen) break; if (compare.family != input->family) break; if ((input->family == AF_INET) && IPV4_ADDR_SAME(&input->u.prefix4, &compare.u.prefix4)) return true; if ((input->family == AF_INET6) && IPV6_ADDR_SAME(&input->u.prefix6.s6_addr, &compare.u.prefix6.s6_addr)) return true; break; case FLOWSPEC_FLOW_LABEL: if (afi == AFI_IP) { error = -1; continue; } ret = bgp_flowspec_op_decode(BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content+offset, len - offset, NULL, &error); break; case FLOWSPEC_IP_PROTOCOL: case FLOWSPEC_PORT: case FLOWSPEC_DEST_PORT: case FLOWSPEC_SRC_PORT: case FLOWSPEC_ICMP_TYPE: case FLOWSPEC_ICMP_CODE: ret = bgp_flowspec_op_decode(BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content+offset, len - offset, NULL, &error); break; case FLOWSPEC_FRAGMENT: case FLOWSPEC_TCP_FLAGS: ret = bgp_flowspec_bitmask_decode( BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content+offset, len - offset, NULL, &error); break; case FLOWSPEC_PKT_LEN: case FLOWSPEC_DSCP: ret = bgp_flowspec_op_decode( BGP_FLOWSPEC_VALIDATE_ONLY, nlri_content + offset, len - offset, NULL, &error); break; default: error = -1; break; } offset += ret; } return false; } /* * handle the flowspec address src/dst or generic address NLRI * return number of bytes analysed ( >= 0). */ int bgp_flowspec_ip_address(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error, afi_t afi, uint8_t *ipv6_offset) { char *display = (char *)result; /* for return_string */ struct prefix *prefix = (struct prefix *)result; uint32_t offset = 0; struct prefix prefix_local; int psize; uint8_t prefix_offset = 0; *error = 0; memset(&prefix_local, 0, sizeof(prefix_local)); /* read the prefix length */ prefix_local.prefixlen = nlri_ptr[offset]; psize = PSIZE(prefix_local.prefixlen); offset++; prefix_local.family = afi2family(afi); if (prefix_local.family == AF_INET6) { prefix_offset = nlri_ptr[offset]; if (ipv6_offset) *ipv6_offset = prefix_offset; offset++; } /* Prefix length check. */ if (prefix_local.prefixlen > prefix_blen(&prefix_local) * 8) *error = -1; /* When packet overflow occur return immediately. */ if (psize + offset > max_len) *error = -1; /* Defensive coding, double-check * the psize fits in a struct prefix */ if (psize > (ssize_t)sizeof(prefix_local.u)) *error = -1; memcpy(&prefix_local.u.prefix, &nlri_ptr[offset], psize); offset += psize; switch (type) { case BGP_FLOWSPEC_RETURN_STRING: if (prefix_local.family == AF_INET6) { int ret; ret = snprintfrr( display, BGP_FLOWSPEC_STRING_DISPLAY_MAX, "%pFX/off %u", &prefix_local, prefix_offset); if (ret < 0) { *error = -1; break; } } else prefix2str(&prefix_local, display, BGP_FLOWSPEC_STRING_DISPLAY_MAX); break; case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: if (prefix) prefix_copy(prefix, &prefix_local); break; case BGP_FLOWSPEC_VALIDATE_ONLY: case BGP_FLOWSPEC_RETURN_JSON: break; } return offset; } /* * handle the flowspec operator NLRI * return number of bytes analysed * if there is an error, the passed error param is used to give error: * -1 if decoding error, * if result is a string, its assumed length * is BGP_FLOWSPEC_STRING_DISPLAY_MAX */ int bgp_flowspec_op_decode(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error) { int op[8]; int len, value, value_size; int loop = 0; char *ptr = (char *)result; /* for return_string */ uint32_t offset = 0; int len_string = BGP_FLOWSPEC_STRING_DISPLAY_MAX; int len_written; struct bgp_pbr_match_val *mval = (struct bgp_pbr_match_val *)result; *error = 0; do { if (loop > BGP_PBR_MATCH_VAL_MAX) *error = -2; hex2bin(&nlri_ptr[offset], op); offset++; len = 2*op[2]+op[3]; value_size = 1 << len; value = hexstr2num(&nlri_ptr[offset], value_size); /* can not be < and > at the same time */ if (op[5] == 1 && op[6] == 1) *error = -1; /* if first element, AND bit can not be set */ if (op[1] == 1 && loop == 0) *error = -1; switch (type) { case BGP_FLOWSPEC_RETURN_STRING: if (loop) { len_written = snprintf(ptr, len_string, ", "); len_string -= len_written; ptr += len_written; } if (op[5] == 1) { len_written = snprintf(ptr, len_string, "<"); len_string -= len_written; ptr += len_written; } if (op[6] == 1) { len_written = snprintf(ptr, len_string, ">"); len_string -= len_written; ptr += len_written; } if (op[7] == 1) { len_written = snprintf(ptr, len_string, "="); len_string -= len_written; ptr += len_written; } len_written = snprintf(ptr, len_string, " %d ", value); len_string -= len_written; ptr += len_written; break; case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: /* limitation: stop converting */ if (*error == -2) break; mval->value = value; if (op[5] == 1) mval->compare_operator |= OPERATOR_COMPARE_LESS_THAN; if (op[6] == 1) mval->compare_operator |= OPERATOR_COMPARE_GREATER_THAN; if (op[7] == 1) mval->compare_operator |= OPERATOR_COMPARE_EQUAL_TO; if (op[1] == 1) mval->unary_operator = OPERATOR_UNARY_AND; else mval->unary_operator = OPERATOR_UNARY_OR; mval++; break; case BGP_FLOWSPEC_VALIDATE_ONLY: case BGP_FLOWSPEC_RETURN_JSON: /* no action */ break; } offset += value_size; loop++; } while (op[0] == 0 && offset < max_len - 1); if (offset > max_len) *error = -1; /* use error parameter to count the number of entries */ if (*error == 0) *error = loop; return offset; } /* * handle the flowspec tcpflags or fragment field * return number of bytes analysed * if there is an error, the passed error param is used to give error: * -1 if decoding error, * if result is a string, its assumed length * is BGP_FLOWSPEC_STRING_DISPLAY_MAX */ int bgp_flowspec_bitmask_decode(enum bgp_flowspec_util_nlri_t type, uint8_t *nlri_ptr, uint32_t max_len, void *result, int *error) { int op[8]; int len, value_size, loop = 0, value; char *ptr = (char *)result; /* for return_string */ struct bgp_pbr_match_val *mval = (struct bgp_pbr_match_val *)result; uint32_t offset = 0; int len_string = BGP_FLOWSPEC_STRING_DISPLAY_MAX; int len_written; *error = 0; do { if (loop > BGP_PBR_MATCH_VAL_MAX) *error = -2; hex2bin(&nlri_ptr[offset], op); /* if first element, AND bit can not be set */ if (op[1] == 1 && loop == 0) *error = -1; offset++; len = 2 * op[2] + op[3]; value_size = 1 << len; value = hexstr2num(&nlri_ptr[offset], value_size); switch (type) { case BGP_FLOWSPEC_RETURN_STRING: if (op[1] == 1 && loop != 0) { len_written = snprintf(ptr, len_string, ",&"); len_string -= len_written; ptr += len_written; } else if (op[1] == 0 && loop != 0) { len_written = snprintf(ptr, len_string, ",|"); len_string -= len_written; ptr += len_written; } if (op[7] == 1) { len_written = snprintf(ptr, len_string, "= "); len_string -= len_written; ptr += len_written; } else { len_written = snprintf(ptr, len_string, "∋ "); len_string -= len_written; ptr += len_written; } if (op[6] == 1) { len_written = snprintf(ptr, len_string, "! "); len_string -= len_written; ptr += len_written; } len_written = snprintf(ptr, len_string, "%d", value); len_string -= len_written; ptr += len_written; break; case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: /* limitation: stop converting */ if (*error == -2) break; mval->value = value; if (op[6] == 1) { /* different from */ mval->compare_operator |= OPERATOR_COMPARE_LESS_THAN; mval->compare_operator |= OPERATOR_COMPARE_GREATER_THAN; } else mval->compare_operator |= OPERATOR_COMPARE_EQUAL_TO; if (op[7] == 1) mval->compare_operator |= OPERATOR_COMPARE_EXACT_MATCH; if (op[1] == 1) mval->unary_operator = OPERATOR_UNARY_AND; else mval->unary_operator = OPERATOR_UNARY_OR; mval++; break; case BGP_FLOWSPEC_VALIDATE_ONLY: case BGP_FLOWSPEC_RETURN_JSON: /* no action */ break; } offset += value_size; loop++; } while (op[0] == 0 && offset < max_len - 1); if (offset > max_len) *error = -1; /* use error parameter to count the number of entries */ if (*error == 0) *error = loop; return offset; } int bgp_flowspec_match_rules_fill(uint8_t *nlri_content, int len, struct bgp_pbr_entry_main *bpem, afi_t afi) { int offset = 0, error = 0; struct prefix *prefix; struct bgp_pbr_match_val *mval; uint8_t *match_num; uint8_t bitmask = 0; int ret = 0, type; uint8_t *prefix_offset; while (offset < len - 1 && error >= 0) { type = nlri_content[offset]; offset++; switch (type) { case FLOWSPEC_DEST_PREFIX: case FLOWSPEC_SRC_PREFIX: bitmask = 0; if (type == FLOWSPEC_DEST_PREFIX) { bitmask |= PREFIX_DST_PRESENT; prefix = &bpem->dst_prefix; prefix_offset = &bpem->dst_prefix_offset; } else { bitmask |= PREFIX_SRC_PRESENT; prefix = &bpem->src_prefix; prefix_offset = &bpem->src_prefix_offset; } ret = bgp_flowspec_ip_address( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content + offset, len - offset, prefix, &error, afi, prefix_offset); if (error < 0) flog_err(EC_BGP_FLOWSPEC_PACKET, "%s: flowspec_ip_address error %d", __func__, error); else { /* if src or dst address is 0.0.0.0, * ignore that rule */ if (prefix->family == AF_INET && prefix->u.prefix4.s_addr == INADDR_ANY) bpem->match_bitmask_iprule |= bitmask; else if (prefix->family == AF_INET6 && !memcmp(&prefix->u.prefix6, &in6addr_any, sizeof(struct in6_addr))) bpem->match_bitmask_iprule |= bitmask; else bpem->match_bitmask |= bitmask; } offset += ret; break; case FLOWSPEC_FLOW_LABEL: if (afi == AFI_IP) { error = -1; continue; } match_num = &(bpem->match_flowlabel_num); mval = (struct bgp_pbr_match_val *) &(bpem->flow_label); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_IP_PROTOCOL: match_num = &(bpem->match_protocol_num); mval = (struct bgp_pbr_match_val *) &(bpem->protocol); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_PORT: match_num = &(bpem->match_port_num); mval = (struct bgp_pbr_match_val *) &(bpem->port); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_DEST_PORT: match_num = &(bpem->match_dst_port_num); mval = (struct bgp_pbr_match_val *) &(bpem->dst_port); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_SRC_PORT: match_num = &(bpem->match_src_port_num); mval = (struct bgp_pbr_match_val *) &(bpem->src_port); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_ICMP_TYPE: match_num = &(bpem->match_icmp_type_num); mval = (struct bgp_pbr_match_val *) &(bpem->icmp_type); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_ICMP_CODE: match_num = &(bpem->match_icmp_code_num); mval = (struct bgp_pbr_match_val *) &(bpem->icmp_code); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_PKT_LEN: match_num = &(bpem->match_packet_length_num); mval = (struct bgp_pbr_match_val *) &(bpem->packet_length); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_DSCP: match_num = &(bpem->match_dscp_num); mval = (struct bgp_pbr_match_val *) &(bpem->dscp); offset += bgp_flowspec_call_non_opaque_decode( nlri_content + offset, len - offset, mval, match_num, &error); break; case FLOWSPEC_TCP_FLAGS: ret = bgp_flowspec_bitmask_decode( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content + offset, len - offset, &bpem->tcpflags, &error); if (error < 0) flog_err( EC_BGP_FLOWSPEC_PACKET, "%s: flowspec_tcpflags_decode error %d", __func__, error); else bpem->match_tcpflags_num = error; /* contains the number of slots used */ offset += ret; break; case FLOWSPEC_FRAGMENT: ret = bgp_flowspec_bitmask_decode( BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, nlri_content + offset, len - offset, &bpem->fragment, &error); if (error < 0) flog_err( EC_BGP_FLOWSPEC_PACKET, "%s: flowspec_fragment_type_decode error %d", __func__, error); else bpem->match_fragment_num = error; offset += ret; break; default: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown type %d", __func__, type); } } if (bpem->match_packet_length_num || bpem->match_fragment_num || bpem->match_tcpflags_num || bpem->match_dscp_num || bpem->match_icmp_code_num || bpem->match_icmp_type_num || bpem->match_port_num || bpem->match_src_port_num || bpem->match_dst_port_num || bpem->match_protocol_num || bpem->match_bitmask || bpem->match_flowlabel_num) bpem->type = BGP_PBR_IPSET; else if ((bpem->match_bitmask_iprule & PREFIX_SRC_PRESENT) || (bpem->match_bitmask_iprule & PREFIX_DST_PRESENT)) /* the extracted policy rule may not need an * iptables/ipset filtering. check this may not be * a standard ip rule : permit any to any ( eg) */ bpem->type = BGP_PBR_IPRULE; else bpem->type = BGP_PBR_UNDEFINED; return error; } /* return 1 if FS entry invalid or no NH IP */ bool bgp_flowspec_get_first_nh(struct bgp *bgp, struct bgp_path_info *pi, struct prefix *p, afi_t afi) { struct bgp_pbr_entry_main api; int i; struct bgp_dest *dest = pi->net; struct bgp_pbr_entry_action *api_action; memset(&api, 0, sizeof(api)); if (bgp_pbr_build_and_validate_entry(bgp_dest_get_prefix(dest), pi, &api) < 0) return true; for (i = 0; i < api.action_num; i++) { api_action = &api.actions[i]; if (api_action->action != ACTION_REDIRECT_IP) continue; p->family = afi2family(afi); if (afi == AFI_IP) { p->prefixlen = IPV4_MAX_BITLEN; p->u.prefix4 = api_action->u.zr.redirect_ip_v4; } else { p->prefixlen = IPV6_MAX_BITLEN; memcpy(&p->u.prefix6, &api_action->u.zr.redirect_ip_v6, sizeof(struct in6_addr)); } return false; } return true; }