From 9dd748cd49d15b7e90a7a65de53d431a2c515c86 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 31 Jan 2019 17:13:07 +0100 Subject: [PATCH] tc: f_flower: add geneve option match support to flower Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1654761 Upstream Status: iproute2.git commit 56155d4df86d4 commit 56155d4df86d489c4207444c8a90ce4e0e22e49f Author: Pieter Jansen van Vuuren Date: Fri Sep 28 16:03:39 2018 +0200 tc: f_flower: add geneve option match support to flower Allow matching on options in Geneve tunnel headers. The options can be described in the form CLASS:TYPE:DATA/CLASS_MASK:TYPE_MASK:DATA_MASK, where CLASS is represented as a 16bit hexadecimal value, TYPE as an 8bit hexadecimal value and DATA as a variable length hexadecimal value. e.g. # ip link add name geneve0 type geneve dstport 0 external # tc qdisc add dev geneve0 ingress # tc filter add dev geneve0 protocol ip parent ffff: \ flower \ enc_src_ip 10.0.99.192 \ enc_dst_ip 10.0.99.193 \ enc_key_id 11 \ geneve_opts 0102:80:1122334421314151/ffff:ff:ffffffffffffffff \ ip_proto udp \ action mirred egress redirect dev eth1 Signed-off-by: Pieter Jansen van Vuuren Signed-off-by: Simon Horman Signed-off-by: David Ahern --- man/man8/tc-flower.8 | 13 ++- tc/f_flower.c | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+), 1 deletion(-) diff --git a/man/man8/tc-flower.8 b/man/man8/tc-flower.8 index f917f24..276b527 100644 --- a/man/man8/tc-flower.8 +++ b/man/man8/tc-flower.8 @@ -74,6 +74,8 @@ flower \- flow based traffic control filter .IR TOS " | " .B enc_ttl .IR TTL " | " +.B geneve_opts +.IR OPTIONS " | " .BR ip_flags .IR IP_FLAGS .SH DESCRIPTION @@ -260,6 +262,8 @@ bits is assumed. .BI enc_tos " NUMBER" .TQ .BI enc_ttl " NUMBER" +.TQ +.BI geneve_opts " OPTIONS" Match on IP tunnel metadata. Key id .I NUMBER is a 32 bit tunnel key id (e.g. VNI for VXLAN tunnel). @@ -272,7 +276,14 @@ is a 16 bit UDP dst port. Tos .I NUMBER is an 8 bit tos (dscp+ecn) value, ttl .I NUMBER -is an 8 bit time-to-live value. +is an 8 bit time-to-live value. geneve_opts +.I OPTIONS +must be a valid list of comma-separated geneve options where each option +consists of a key optionally followed by a slash and corresponding mask. If +the masks is missing, \fBtc\fR assumes a full-length match. The options can +be described in the form CLASS:TYPE:DATA/CLASS_MASK:TYPE_MASK:DATA_MASK, +where CLASS is represented as a 16bit hexadecimal value, TYPE as an 8bit +hexadecimal value and DATA as a variable length hexadecimal value. .TP .BI ip_flags " IP_FLAGS" .I IP_FLAGS diff --git a/tc/f_flower.c b/tc/f_flower.c index cd102f2..43102c8 100644 --- a/tc/f_flower.c +++ b/tc/f_flower.c @@ -76,6 +76,7 @@ static void explain(void) " enc_key_id [ KEY-ID ] |\n" " enc_tos MASKED-IP_TOS |\n" " enc_ttl MASKED-IP_TTL |\n" + " geneve_opts MASKED-OPTIONS |\n" " ip_flags IP-FLAGS | \n" " enc_dst_port [ port_number ] }\n" " FILTERID := X:Y:Z\n" @@ -580,6 +581,179 @@ static int flower_parse_enc_port(char *str, int type, struct nlmsghdr *n) return 0; } +static int flower_parse_geneve_opts(char *str, struct nlmsghdr *n) +{ + struct rtattr *nest; + char *token; + int i, err; + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS_GENEVE); + + i = 1; + token = strsep(&str, ":"); + while (token) { + switch (i) { + case TCA_FLOWER_KEY_ENC_OPT_GENEVE_CLASS: + { + __be16 opt_class; + + if (!strlen(token)) + break; + err = get_be16(&opt_class, token, 16); + if (err) + return err; + + addattr16(n, MAX_MSG, i, opt_class); + break; + } + case TCA_FLOWER_KEY_ENC_OPT_GENEVE_TYPE: + { + __u8 opt_type; + + if (!strlen(token)) + break; + err = get_u8(&opt_type, token, 16); + if (err) + return err; + + addattr8(n, MAX_MSG, i, opt_type); + break; + } + case TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA: + { + size_t token_len = strlen(token); + __u8 *opts; + + if (!token_len) + break; + opts = malloc(token_len / 2); + if (!opts) + return -1; + if (hex2mem(token, opts, token_len / 2) < 0) { + free(opts); + return -1; + } + addattr_l(n, MAX_MSG, i, opts, token_len / 2); + free(opts); + + break; + } + default: + fprintf(stderr, "Unknown \"geneve_opts\" type\n"); + return -1; + } + + token = strsep(&str, ":"); + i++; + } + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_enc_opt_part(char *str, struct nlmsghdr *n) +{ + char *token; + int err; + + token = strsep(&str, ","); + while (token) { + err = flower_parse_geneve_opts(token, n); + if (err) + return err; + + token = strsep(&str, ","); + } + + return 0; +} + +static int flower_check_enc_opt_key(char *key) +{ + int key_len, col_cnt = 0; + + key_len = strlen(key); + while ((key = strchr(key, ':'))) { + if (strlen(key) == key_len) + return -1; + + key_len = strlen(key) - 1; + col_cnt++; + key++; + } + + if (col_cnt != 2 || !key_len) + return -1; + + return 0; +} + +static int flower_parse_enc_opts(char *str, struct nlmsghdr *n) +{ + char key[XATTR_SIZE_MAX], mask[XATTR_SIZE_MAX]; + int data_len, key_len, mask_len, err; + char *token, *slash; + struct rtattr *nest; + + key_len = 0; + mask_len = 0; + token = strsep(&str, ","); + while (token) { + slash = strchr(token, '/'); + if (slash) + *slash = '\0'; + + if ((key_len + strlen(token) > XATTR_SIZE_MAX) || + flower_check_enc_opt_key(token)) + return -1; + + strcpy(&key[key_len], token); + key_len += strlen(token) + 1; + key[key_len - 1] = ','; + + if (!slash) { + /* Pad out mask when not provided */ + if (mask_len + strlen(token) > XATTR_SIZE_MAX) + return -1; + + data_len = strlen(rindex(token, ':')); + sprintf(&mask[mask_len], "ffff:ff:"); + mask_len += 8; + memset(&mask[mask_len], 'f', data_len - 1); + mask_len += data_len; + mask[mask_len - 1] = ','; + token = strsep(&str, ","); + continue; + } + + if (mask_len + strlen(slash + 1) > XATTR_SIZE_MAX) + return -1; + + strcpy(&mask[mask_len], slash + 1); + mask_len += strlen(slash + 1) + 1; + mask[mask_len - 1] = ','; + + *slash = '/'; + token = strsep(&str, ","); + } + key[key_len - 1] = '\0'; + mask[mask_len - 1] = '\0'; + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS); + err = flower_parse_enc_opt_part(key, n); + if (err) + return err; + addattr_nest_end(n, nest); + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS_MASK); + err = flower_parse_enc_opt_part(mask, n); + if (err) + return err; + addattr_nest_end(n, nest); + + return 0; +} + static int flower_parse_opt(struct filter_util *qu, char *handle, int argc, char **argv, struct nlmsghdr *n) { @@ -994,6 +1168,13 @@ static int flower_parse_opt(struct filter_util *qu, char *handle, fprintf(stderr, "Illegal \"enc_ttl\"\n"); return -1; } + } else if (matches(*argv, "geneve_opts") == 0) { + NEXT_ARG(); + ret = flower_parse_enc_opts(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"geneve_opts\"\n"); + return -1; + } } else if (matches(*argv, "action") == 0) { NEXT_ARG(); ret = parse_action(&argc, &argv, TCA_FLOWER_ACT, n); @@ -1291,6 +1472,105 @@ static void flower_print_key_id(const char *name, struct rtattr *attr) print_uint(PRINT_ANY, name, namefrm, rta_getattr_be32(attr)); } +static void flower_print_geneve_opts(const char *name, struct rtattr *attr, + char *strbuf) +{ + struct rtattr *tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX + 1]; + int ii, data_len, offset = 0, slen = 0; + struct rtattr *i = RTA_DATA(attr); + int rem = RTA_PAYLOAD(attr); + __u8 type, data_r[rem]; + char data[rem * 2 + 1]; + __u16 class; + + open_json_array(PRINT_JSON, name); + while (rem) { + parse_rtattr(tb, TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX, i, rem); + class = rta_getattr_be16(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_CLASS]); + type = rta_getattr_u8(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_TYPE]); + data_len = RTA_PAYLOAD(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA]); + hexstring_n2a(RTA_DATA(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA]), + data_len, data, sizeof(data)); + hex2mem(data, data_r, data_len); + offset += data_len + 20; + rem -= data_len + 20; + i = RTA_DATA(attr) + offset; + + open_json_object(NULL); + print_uint(PRINT_JSON, "class", NULL, class); + print_uint(PRINT_JSON, "type", NULL, type); + open_json_array(PRINT_JSON, "data"); + for (ii = 0; ii < data_len; ii++) + print_uint(PRINT_JSON, NULL, NULL, data_r[ii]); + close_json_array(PRINT_JSON, "data"); + close_json_object(); + + slen += sprintf(strbuf + slen, "%04x:%02x:%s", + class, type, data); + if (rem) + slen += sprintf(strbuf + slen, ","); + } + close_json_array(PRINT_JSON, name); +} + +static void flower_print_geneve_parts(const char *name, struct rtattr *attr, + char *key, char *mask) +{ + char *namefrm = "\n geneve_opt %s"; + char *key_token, *mask_token, *out; + int len; + + out = malloc(RTA_PAYLOAD(attr) * 4 + 3); + if (!out) + return; + + len = 0; + key_token = strsep(&key, ","); + mask_token = strsep(&mask, ","); + while (key_token) { + len += sprintf(&out[len], "%s/%s,", key_token, mask_token); + mask_token = strsep(&mask, ","); + key_token = strsep(&key, ","); + } + + out[len - 1] = '\0'; + print_string(PRINT_FP, name, namefrm, out); + free(out); +} + +static void flower_print_enc_opts(const char *name, struct rtattr *attr, + struct rtattr *mask_attr) +{ + struct rtattr *key_tb[TCA_FLOWER_KEY_ENC_OPTS_MAX + 1]; + struct rtattr *msk_tb[TCA_FLOWER_KEY_ENC_OPTS_MAX + 1]; + char *key, *msk; + + if (!attr) + return; + + key = malloc(RTA_PAYLOAD(attr) * 2 + 1); + if (!key) + return; + + msk = malloc(RTA_PAYLOAD(attr) * 2 + 1); + if (!msk) + goto err_key_free; + + parse_rtattr_nested(key_tb, TCA_FLOWER_KEY_ENC_OPTS_MAX, attr); + flower_print_geneve_opts("geneve_opt_key", + key_tb[TCA_FLOWER_KEY_ENC_OPTS_GENEVE], key); + + parse_rtattr_nested(msk_tb, TCA_FLOWER_KEY_ENC_OPTS_MAX, mask_attr); + flower_print_geneve_opts("geneve_opt_mask", + msk_tb[TCA_FLOWER_KEY_ENC_OPTS_GENEVE], msk); + + flower_print_geneve_parts(name, attr, key, msk); + + free(msk); +err_key_free: + free(key); +} + static void flower_print_masked_u8(const char *name, struct rtattr *attr, struct rtattr *mask_attr, const char *(*value_to_str)(__u8 value)) @@ -1489,6 +1769,8 @@ static int flower_print_opt(struct filter_util *qu, FILE *f, tb[TCA_FLOWER_KEY_ENC_IP_TOS_MASK]); flower_print_ip_attr("enc_ttl", tb[TCA_FLOWER_KEY_ENC_IP_TTL], tb[TCA_FLOWER_KEY_ENC_IP_TTL_MASK]); + flower_print_enc_opts("enc_opt", tb[TCA_FLOWER_KEY_ENC_OPTS], + tb[TCA_FLOWER_KEY_ENC_OPTS_MASK]); flower_print_matching_flags("ip_flags", FLOWER_IP_FLAGS, tb[TCA_FLOWER_KEY_FLAGS], -- 1.8.3.1