iproute_lwtunnel: add options support for geneve metadata

This patch is to add LWTUNNEL_IP(6)_OPTS and LWTUNNEL_IP_OPTS_GENEVE's
parse and print to implement geneve options support in iproute_lwtunnel.

Options are expressed as class:type:data and multiple options may be
listed using a comma delimiter, class and type are numbers and data
is a hex string.

With this patch, users can add and dump geneve options like:

  # ip netns add a
  # ip netns add b
  # ip -n a link add eth0 type veth peer name eth0 netns b
  # ip -n a link set eth0 up; ip -n b link set eth0 up
  # ip -n a addr add 10.1.0.1/24 dev eth0
  # ip -n b addr add 10.1.0.2/24 dev eth0
  # ip -n b link add geneve1 type geneve id 1 remote 10.1.0.1 ttl 64
  # ip -n b addr add 1.1.1.1/24 dev geneve1
  # ip -n b link set geneve1 up
  # ip -n b route add 2.1.1.0/24 dev geneve1
  # ip -n a link add geneve1 type geneve external
  # ip -n a addr add 2.1.1.1/24 dev geneve1
  # ip -n a link set geneve1 up
  # ip -n a route add 1.1.1.0/24 encap ip id 1 geneve_opts \
    1:1:1212121234567890,1:1:1212121234567890,1:1:1212121234567890 \
    dst 10.1.0.2 dev geneve1
  # ip -n a route show
  # ip netns exec a ping 1.1.1.1 -c 1

   1.1.1.0/24  encap ip id 1 src 0.0.0.0 dst 10.1.0.2 ttl 0 tos 0
     geneve_opts 1:1:1212121234567890,1:1:1212121234567890 ...

   PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
   64 bytes from 1.1.1.1: icmp_seq=1 ttl=64 time=0.079 ms

v1->v2:
  - improve the changelog.
  - use PRINT_ANY to support dumping with json format.
v2->v3:
  - implement proper JSON array for opts instead of just bunch of strings.
v3->v4:
  - keep the same format between input and output, json and non json.
  - print class and type as uint and print data as hex string.

Signed-off-by: Xin Long <lucien.xin@gmail.com>
Signed-off-by: David Ahern <dsahern@gmail.com>
This commit is contained in:
Xin Long 2020-04-27 18:27:45 +08:00 committed by David Ahern
parent 081d6c310d
commit ca7614d4c6
1 changed files with 172 additions and 2 deletions

View File

@ -294,6 +294,54 @@ static void print_encap_mpls(FILE *fp, struct rtattr *encap)
rta_getattr_u8(tb[MPLS_IPTUNNEL_TTL]));
}
static void lwtunnel_print_geneve_opts(struct rtattr *attr)
{
struct rtattr *tb[LWTUNNEL_IP_OPT_GENEVE_MAX + 1];
struct rtattr *i = RTA_DATA(attr);
int rem = RTA_PAYLOAD(attr);
char *name = "geneve_opts";
int data_len, offset = 0;
char data[rem * 2 + 1];
__u16 class;
__u8 type;
print_nl();
print_string(PRINT_FP, name, "\t%s ", name);
open_json_array(PRINT_JSON, name);
while (rem) {
parse_rtattr(tb, LWTUNNEL_IP_OPT_GENEVE_MAX, i, rem);
class = rta_getattr_be16(tb[LWTUNNEL_IP_OPT_GENEVE_CLASS]);
type = rta_getattr_u8(tb[LWTUNNEL_IP_OPT_GENEVE_TYPE]);
data_len = RTA_PAYLOAD(tb[LWTUNNEL_IP_OPT_GENEVE_DATA]);
hexstring_n2a(RTA_DATA(tb[LWTUNNEL_IP_OPT_GENEVE_DATA]),
data_len, data, sizeof(data));
offset += data_len + 20;
rem -= data_len + 20;
i = RTA_DATA(attr) + offset;
open_json_object(NULL);
print_uint(PRINT_ANY, "class", "%u", class);
print_uint(PRINT_ANY, "type", ":%u", type);
if (rem)
print_string(PRINT_ANY, "data", ":%s,", data);
else
print_string(PRINT_ANY, "data", ":%s ", data);
close_json_object();
}
close_json_array(PRINT_JSON, name);
}
static void lwtunnel_print_opts(struct rtattr *attr)
{
struct rtattr *tb_opt[LWTUNNEL_IP_OPTS_MAX + 1];
parse_rtattr_nested(tb_opt, LWTUNNEL_IP_OPTS_MAX, attr);
if (tb_opt[LWTUNNEL_IP_OPTS_GENEVE])
lwtunnel_print_geneve_opts(tb_opt[LWTUNNEL_IP_OPTS_GENEVE]);
}
static void print_encap_ip(FILE *fp, struct rtattr *encap)
{
struct rtattr *tb[LWTUNNEL_IP_MAX+1];
@ -332,6 +380,9 @@ static void print_encap_ip(FILE *fp, struct rtattr *encap)
if (flags & TUNNEL_SEQ)
print_bool(PRINT_ANY, "seq", "seq ", true);
}
if (tb[LWTUNNEL_IP_OPTS])
lwtunnel_print_opts(tb[LWTUNNEL_IP_OPTS]);
}
static void print_encap_ila(FILE *fp, struct rtattr *encap)
@ -404,6 +455,9 @@ static void print_encap_ip6(FILE *fp, struct rtattr *encap)
if (flags & TUNNEL_SEQ)
print_bool(PRINT_ANY, "seq", "seq ", true);
}
if (tb[LWTUNNEL_IP6_OPTS])
lwtunnel_print_opts(tb[LWTUNNEL_IP6_OPTS]);
}
static void print_encap_bpf(FILE *fp, struct rtattr *encap)
@ -798,11 +852,97 @@ static int parse_encap_mpls(struct rtattr *rta, size_t len,
return 0;
}
static int lwtunnel_parse_geneve_opt(char *str, size_t len, struct rtattr *rta)
{
struct rtattr *nest;
char *token;
int i, err;
nest = rta_nest(rta, len, LWTUNNEL_IP_OPTS_GENEVE | NLA_F_NESTED);
i = 1;
token = strsep(&str, ":");
while (token) {
switch (i) {
case LWTUNNEL_IP_OPT_GENEVE_CLASS:
{
__be16 opt_class;
if (!strlen(token))
break;
err = get_be16(&opt_class, token, 0);
if (err)
return err;
rta_addattr16(rta, len, i, opt_class);
break;
}
case LWTUNNEL_IP_OPT_GENEVE_TYPE:
{
__u8 opt_type;
if (!strlen(token))
break;
err = get_u8(&opt_type, token, 0);
if (err)
return err;
rta_addattr8(rta, len, i, opt_type);
break;
}
case LWTUNNEL_IP_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;
}
rta_addattr_l(rta, len, i, opts, token_len / 2);
free(opts);
break;
}
default:
fprintf(stderr, "Unknown \"geneve_opts\" type\n");
return -1;
}
token = strsep(&str, ":");
i++;
}
rta_nest_end(rta, nest);
return 0;
}
static int lwtunnel_parse_geneve_opts(char *str, size_t len, struct rtattr *rta)
{
char *token;
int err;
token = strsep(&str, ",");
while (token) {
err = lwtunnel_parse_geneve_opt(token, len, rta);
if (err)
return err;
token = strsep(&str, ",");
}
return 0;
}
static int parse_encap_ip(struct rtattr *rta, size_t len,
int *argcp, char ***argvp)
{
int id_ok = 0, dst_ok = 0, src_ok = 0, tos_ok = 0, ttl_ok = 0;
int key_ok = 0, csum_ok = 0, seq_ok = 0;
int key_ok = 0, csum_ok = 0, seq_ok = 0, opts_ok = 0;
char **argv = *argvp;
int argc = *argcp;
int ret = 0;
@ -854,6 +994,21 @@ static int parse_encap_ip(struct rtattr *rta, size_t len,
if (get_u8(&ttl, *argv, 0))
invarg("\"ttl\" value is invalid\n", *argv);
ret = rta_addattr8(rta, len, LWTUNNEL_IP_TTL, ttl);
} else if (strcmp(*argv, "geneve_opts") == 0) {
struct rtattr *nest;
if (opts_ok++)
duparg2("opts", *argv);
NEXT_ARG();
nest = rta_nest(rta, len,
LWTUNNEL_IP_OPTS | NLA_F_NESTED);
ret = lwtunnel_parse_geneve_opts(*argv, len, rta);
if (ret)
invarg("\"geneve_opts\" value is invalid\n",
*argv);
rta_nest_end(rta, nest);
} else if (strcmp(*argv, "key") == 0) {
if (key_ok++)
duparg2("key", *argv);
@ -969,7 +1124,7 @@ static int parse_encap_ip6(struct rtattr *rta, size_t len,
int *argcp, char ***argvp)
{
int id_ok = 0, dst_ok = 0, src_ok = 0, tos_ok = 0, ttl_ok = 0;
int key_ok = 0, csum_ok = 0, seq_ok = 0;
int key_ok = 0, csum_ok = 0, seq_ok = 0, opts_ok = 0;
char **argv = *argvp;
int argc = *argcp;
int ret = 0;
@ -1023,6 +1178,21 @@ static int parse_encap_ip6(struct rtattr *rta, size_t len,
*argv);
ret = rta_addattr8(rta, len, LWTUNNEL_IP6_HOPLIMIT,
hoplimit);
} else if (strcmp(*argv, "geneve_opts") == 0) {
struct rtattr *nest;
if (opts_ok++)
duparg2("opts", *argv);
NEXT_ARG();
nest = rta_nest(rta, len,
LWTUNNEL_IP_OPTS | NLA_F_NESTED);
ret = lwtunnel_parse_geneve_opts(*argv, len, rta);
if (ret)
invarg("\"geneve_opts\" value is invalid\n",
*argv);
rta_nest_end(rta, nest);
} else if (strcmp(*argv, "key") == 0) {
if (key_ok++)
duparg2("key", *argv);