/* BGP FlowSpec Utilities
 * Portions:
 *     Copyright (C) 2017 ChinaTelecom SDN Group
 *     Copyright (C) 2018 6WIND
 *
 * FRRouting is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * FRRouting is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; see the file COPYING; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "zebra.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(struct prefix));
			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(struct prefix));
	/* 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) {
			char str[BGP_FLOWSPEC_STRING_DISPLAY_MAX];
			int ret;

			prefix2str(&prefix_local, str,
				   BGP_FLOWSPEC_STRING_DISPLAY_MAX);
			ret = snprintf(display, BGP_FLOWSPEC_STRING_DISPLAY_MAX,
				       "%s/off %u",
				       str, 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) {
			if (prefix_local.family == AF_INET)
				PREFIX_COPY_IPV4(prefix, &prefix_local)
			else
				PREFIX_COPY_IPV6(prefix, &prefix_local)
		}
		break;
	case BGP_FLOWSPEC_VALIDATE_ONLY:
	default:
		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:
		default:
			/* 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:
		default:
			/* 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\n",
				 __func__, type);
		}
	}
	if (bpem->match_packet_length_num || bpem->match_fragment_num ||
	    bpem->match_tcpflags_num || bpem->match_dscp_num ||
	    bpem->match_packet_length_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(struct bgp_pbr_entry_main));
	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;
}
