diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h index 6cca50155ad..323d158037a 100644 --- a/datapath/linux/compat/include/linux/openvswitch.h +++ b/datapath/linux/compat/include/linux/openvswitch.h @@ -618,7 +618,7 @@ struct ovs_action_hash { }; #ifndef __KERNEL__ -#define TNL_PUSH_HEADER_SIZE 128 +#define TNL_PUSH_HEADER_SIZE 512 /* * struct ovs_action_push_tnl - %OVS_ACTION_ATTR_TUNNEL_PUSH diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c index ea9abf9e7a7..259d0ed4df5 100644 --- a/lib/netdev-vport.c +++ b/lib/netdev-vport.c @@ -1199,6 +1199,7 @@ netdev_geneve_pop_header(struct dp_packet *packet) struct flow_tnl *tnl = &md->tunnel; struct genevehdr *gnh; unsigned int hlen; + int err; memset(md, 0, sizeof *md); if (GENEVE_BASE_HLEN > dp_packet_size(packet)) { @@ -1224,12 +1225,6 @@ netdev_geneve_pop_header(struct dp_packet *packet) return EINVAL; } - if (gnh->opt_len && gnh->critical) { - VLOG_WARN_RL(&err_rl, "unknown geneve critical options: %"PRIu8" bytes\n", - gnh->opt_len * 4); - return EINVAL; - } - if (gnh->proto_type != htons(ETH_TYPE_TEB)) { VLOG_WARN_RL(&err_rl, "unknown geneve encapsulated protocol: %#x\n", ntohs(gnh->proto_type)); @@ -1240,6 +1235,13 @@ netdev_geneve_pop_header(struct dp_packet *packet) tnl->tun_id = htonll(ntohl(get_16aligned_be32(&gnh->vni)) >> 8); tnl->flags |= FLOW_TNL_F_KEY; + err = tun_metadata_from_geneve_header(gnh->options, gnh->opt_len * 4, + &tnl->metadata); + if (err) { + VLOG_WARN_RL(&err_rl, "invalid geneve options"); + return err; + } + dp_packet_reset_packet(packet, hlen); return 0; @@ -1253,6 +1255,8 @@ netdev_geneve_build_header(const struct netdev *netdev, struct netdev_vport *dev = netdev_vport_cast(netdev); struct netdev_tunnel_config *tnl_cfg; struct genevehdr *gnh; + int opt_len; + bool crit_opt; /* XXX: RCUfy tnl_cfg. */ ovs_mutex_lock(&dev->mutex); @@ -1260,12 +1264,19 @@ netdev_geneve_build_header(const struct netdev *netdev, gnh = udp_build_header(tnl_cfg, tnl_flow, data); - gnh->oam = !!(tnl_flow->tunnel.flags & FLOW_TNL_F_OAM); - gnh->proto_type = htons(ETH_TYPE_TEB); put_16aligned_be32(&gnh->vni, htonl(ntohll(tnl_flow->tunnel.tun_id) << 8)); ovs_mutex_unlock(&dev->mutex); - data->header_len = GENEVE_BASE_HLEN; + + opt_len = tun_metadata_to_geneve_header(&tnl_flow->tunnel.metadata, + gnh->options, &crit_opt); + + gnh->opt_len = opt_len / 4; + gnh->oam = !!(tnl_flow->tunnel.flags & FLOW_TNL_F_OAM); + gnh->critical = crit_opt ? 1 : 0; + gnh->proto_type = htons(ETH_TYPE_TEB); + + data->header_len = GENEVE_BASE_HLEN + opt_len; data->tnl_type = OVS_VPORT_TYPE_GENEVE; return 0; } diff --git a/lib/odp-util.c b/lib/odp-util.c index efdc65179e5..c70ee0f24ae 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -69,6 +69,17 @@ static void format_odp_key_attr(const struct nlattr *a, const struct hmap *portno_names, struct ds *ds, bool verbose); +struct geneve_scan { + struct geneve_opt d[63]; + int len; +}; + +static int scan_geneve(const char *s, struct geneve_scan *key, + struct geneve_scan *mask); +static void format_geneve_opts(const struct geneve_opt *opt, + const struct geneve_opt *mask, int opts_len, + struct ds *, bool verbose); + static struct nlattr *generate_all_wildcard_mask(const struct attr_len_tbl tbl[], int max, struct ofpbuf *, const struct nlattr *key); @@ -581,9 +592,19 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data) gnh = format_udp_tnl_push_header(ds, ip); - ds_put_format(ds, "geneve(%svni=0x%"PRIx32")", + ds_put_format(ds, "geneve(%s%svni=0x%"PRIx32, gnh->oam ? "oam," : "", + gnh->critical ? "crit," : "", ntohl(get_16aligned_be32(&gnh->vni)) >> 8); + + if (gnh->opt_len) { + ds_put_cstr(ds, ",options("); + format_geneve_opts(gnh->options, NULL, gnh->opt_len * 4, + ds, false); + ds_put_char(ds, ')'); + } + + ds_put_char(ds, ')'); } else if (data->tnl_type == OVS_VPORT_TYPE_GRE) { const struct gre_base_hdr *greh; ovs_16aligned_be32 *options; @@ -939,17 +960,41 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data) struct genevehdr *gnh = (struct genevehdr *) (udp + 1); memset(gnh, 0, sizeof *gnh); + header_len = sizeof *eth + sizeof *ip + + sizeof *udp + sizeof *gnh; + if (ovs_scan_len(s, &n, "oam,")) { gnh->oam = 1; } - if (!ovs_scan_len(s, &n, "vni=0x%"SCNx32"))", &vni)) { + if (ovs_scan_len(s, &n, "crit,")) { + gnh->critical = 1; + } + if (!ovs_scan_len(s, &n, "vni=%"SCNi32, &vni)) { return -EINVAL; } + if (ovs_scan_len(s, &n, ",options(")) { + struct geneve_scan options; + int len; + + memset(&options, 0, sizeof options); + len = scan_geneve(s + n, &options, NULL); + if (!len) { + return -EINVAL; + } + + memcpy(gnh->options, options.d, options.len); + gnh->opt_len = options.len / 4; + header_len += options.len; + + n += len; + } + if (!ovs_scan_len(s, &n, "))")) { + return -EINVAL; + } + gnh->proto_type = htons(ETH_TYPE_TEB); put_16aligned_be32(&gnh->vni, htonl(vni << 8)); tnl_type = OVS_VPORT_TYPE_GENEVE; - header_len = sizeof *eth + sizeof *ip + - sizeof *udp + sizeof *gnh; } else { return -EINVAL; } @@ -1873,21 +1918,10 @@ format_odp_tun_vxlan_opt(const struct nlattr *attr, #define MASK(PTR, FIELD) PTR ? &PTR->FIELD : NULL static void -format_odp_tun_geneve(const struct nlattr *attr, - const struct nlattr *mask_attr, struct ds *ds, - bool verbose) +format_geneve_opts(const struct geneve_opt *opt, + const struct geneve_opt *mask, int opts_len, + struct ds *ds, bool verbose) { - int opts_len = nl_attr_get_size(attr); - const struct geneve_opt *opt = nl_attr_get(attr); - const struct geneve_opt *mask = mask_attr ? - nl_attr_get(mask_attr) : NULL; - - if (mask && nl_attr_get_size(attr) != nl_attr_get_size(mask_attr)) { - ds_put_format(ds, "value len %"PRIuSIZE" different from mask len %"PRIuSIZE, - nl_attr_get_size(attr), nl_attr_get_size(mask_attr)); - return; - } - while (opts_len > 0) { unsigned int len; uint8_t data_len, data_len_mask; @@ -1937,6 +1971,25 @@ format_odp_tun_geneve(const struct nlattr *attr, }; } +static void +format_odp_tun_geneve(const struct nlattr *attr, + const struct nlattr *mask_attr, struct ds *ds, + bool verbose) +{ + int opts_len = nl_attr_get_size(attr); + const struct geneve_opt *opt = nl_attr_get(attr); + const struct geneve_opt *mask = mask_attr ? + nl_attr_get(mask_attr) : NULL; + + if (mask && nl_attr_get_size(attr) != nl_attr_get_size(mask_attr)) { + ds_put_format(ds, "value len %"PRIuSIZE" different from mask len %"PRIuSIZE, + nl_attr_get_size(attr), nl_attr_get_size(mask_attr)); + return; + } + + format_geneve_opts(opt, mask, opts_len, ds, verbose); +} + static void format_odp_tun_attr(const struct nlattr *attr, const struct nlattr *mask_attr, struct ds *ds, bool verbose) @@ -2875,11 +2928,6 @@ scan_vxlan_gbp(const char *s, uint32_t *key, uint32_t *mask) return 0; } -struct geneve_scan { - struct geneve_opt d[63]; - int len; -}; - static int scan_geneve(const char *s, struct geneve_scan *key, struct geneve_scan *mask) { diff --git a/lib/packets.h b/lib/packets.h index 04ee914d271..c401d4e693f 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -754,6 +754,7 @@ static inline bool dl_type_is_ip_any(ovs_be16 dl_type) /* Tunnel header */ #define GENEVE_MAX_OPT_SIZE 124 +#define GENEVE_TOT_OPT_SIZE 252 #define GENEVE_CRIT_OPT_TYPE (1 << 7) diff --git a/lib/tun-metadata.c b/lib/tun-metadata.c index d9ce9f2768d..7d82fb76246 100644 --- a/lib/tun-metadata.c +++ b/lib/tun-metadata.c @@ -578,55 +578,15 @@ tun_metadata_del_entry(struct tun_table *map, uint8_t idx) memset(&entry->loc, 0, sizeof entry->loc); } -int -tun_metadata_from_geneve_nlattr(const struct nlattr *attr, - const struct nlattr *flow_attrs, - size_t flow_attr_len, - const struct tun_metadata *flow_metadata, - struct tun_metadata *metadata) +static int +tun_metadata_from_geneve__(struct tun_table *map, const struct geneve_opt *opt, + const struct geneve_opt *flow_opt, int opts_len, + struct tun_metadata *metadata) { - bool is_mask = !!flow_attrs; - struct tun_table *map; - const struct nlattr *flow; - int opts_len; - const struct geneve_opt *flow_opt; - const struct geneve_opt *opt = nl_attr_get(attr); - - if (!is_mask) { - map = ovsrcu_get(struct tun_table *, &metadata_tab); - metadata->tab = map; - } else { - map = flow_metadata->tab; - } - if (!map) { return 0; } - if (is_mask) { - const struct nlattr *tnl_key; - int mask_len = nl_attr_get_size(attr); - - tnl_key = nl_attr_find__(flow_attrs, flow_attr_len, OVS_KEY_ATTR_TUNNEL); - if (!tnl_key) { - return mask_len ? EINVAL : 0; - } - - flow = nl_attr_find_nested(tnl_key, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS); - if (!flow) { - return mask_len ? EINVAL : 0; - } - - if (mask_len != nl_attr_get_size(flow)) { - return EINVAL; - } - } else { - flow = attr; - } - - opts_len = nl_attr_get_size(flow); - flow_opt = nl_attr_get(flow); - while (opts_len > 0) { int len; struct tun_meta_entry *entry; @@ -662,27 +622,74 @@ tun_metadata_from_geneve_nlattr(const struct nlattr *attr, return 0; } -void -tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow, - struct ofpbuf *b) +int +tun_metadata_from_geneve_nlattr(const struct nlattr *attr, + const struct nlattr *flow_attrs, + size_t flow_attr_len, + const struct tun_metadata *flow_metadata, + struct tun_metadata *metadata) { struct tun_table *map; - size_t nlattr_offset; - int i; + bool is_mask = !!flow_attrs; + const struct nlattr *flow; - if (!flow->opt_map) { - return; + if (is_mask) { + const struct nlattr *tnl_key; + int mask_len = nl_attr_get_size(attr); + + tnl_key = nl_attr_find__(flow_attrs, flow_attr_len, OVS_KEY_ATTR_TUNNEL); + if (!tnl_key) { + return mask_len ? EINVAL : 0; + } + + flow = nl_attr_find_nested(tnl_key, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS); + if (!flow) { + return mask_len ? EINVAL : 0; + } + + if (mask_len != nl_attr_get_size(flow)) { + return EINVAL; + } + } else { + flow = attr; } + if (!is_mask) { + map = ovsrcu_get(struct tun_table *, &metadata_tab); + metadata->tab = map; + } else { + map = flow_metadata->tab; + } + + return tun_metadata_from_geneve__(map, nl_attr_get(attr), nl_attr_get(flow), + nl_attr_get_size(flow), metadata); +} + +int +tun_metadata_from_geneve_header(const struct geneve_opt *opts, int opt_len, + struct tun_metadata *metadata) +{ + struct tun_table *map; + + map = ovsrcu_get(struct tun_table *, &metadata_tab); + metadata->tab = map; + + return tun_metadata_from_geneve__(map, opts, opts, opt_len, metadata); +} + +static void +tun_metadata_to_geneve__(const struct tun_metadata *flow, struct ofpbuf *b, + bool *crit_opt) +{ + struct tun_table *map; + int i; + map = flow->tab; if (!map) { map = ovsrcu_get(struct tun_table *, &metadata_tab); } - /* For all intents and purposes, the Geneve options are nested - * attributes even if this doesn't show up directly to netlink. It's - * similar enough that we can use the same mechanism. */ - nlattr_offset = nl_msg_start_nested(b, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS); + *crit_opt = false; ULLONG_FOR_EACH_1 (i, flow->opt_map) { struct tun_meta_entry *entry = &map->entries[i]; @@ -698,11 +705,43 @@ tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow, opt->r3 = 0; memcpy_from_metadata(opt + 1, flow, &entry->loc); + *crit_opt |= !!(opt->type & GENEVE_CRIT_OPT_TYPE); } +} + +void +tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow, + struct ofpbuf *b) +{ + size_t nlattr_offset; + bool crit_opt; + + if (!flow->opt_map) { + return; + } + + /* For all intents and purposes, the Geneve options are nested + * attributes even if this doesn't show up directly to netlink. It's + * similar enough that we can use the same mechanism. */ + nlattr_offset = nl_msg_start_nested(b, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS); + + tun_metadata_to_geneve__(flow, b, &crit_opt); nl_msg_end_nested(b, nlattr_offset); } +int +tun_metadata_to_geneve_header(const struct tun_metadata *flow, + struct geneve_opt *opts, bool *crit_opt) +{ + struct ofpbuf b; + + ofpbuf_use_stack(&b, opts, GENEVE_TOT_OPT_SIZE); + tun_metadata_to_geneve__(flow, &b, crit_opt); + + return b.size; +} + void tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key, const struct tun_metadata *mask, diff --git a/lib/tun-metadata.h b/lib/tun-metadata.h index 76a72e8eeca..a1fbc0ab64e 100644 --- a/lib/tun-metadata.h +++ b/lib/tun-metadata.h @@ -30,6 +30,7 @@ union mf_value; struct ofputil_geneve_table_mod; struct ofputil_geneve_table_reply; struct tun_table; +struct geneve_opt; #define TUN_METADATA_NUM_OPTS 64 #define TUN_METADATA_TOT_OPT_SIZE 256 @@ -95,12 +96,18 @@ int tun_metadata_from_geneve_nlattr(const struct nlattr *attr, size_t flow_attr_len, const struct tun_metadata *flow_metadata, struct tun_metadata *metadata); +int tun_metadata_from_geneve_header(const struct geneve_opt *, int opt_len, + struct tun_metadata *metadata); + void tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow, struct ofpbuf *); void tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key, const struct tun_metadata *mask, const struct tun_metadata *flow, struct ofpbuf *); +int tun_metadata_to_geneve_header(const struct tun_metadata *flow, + struct geneve_opt *, bool *crit_opt); + void tun_metadata_to_nx_match(struct ofpbuf *b, enum ofp_version oxm, const struct match *); void tun_metadata_match_format(struct ds *, const struct match *); diff --git a/tests/odp.at b/tests/odp.at index 4dcf0b0fb81..090b976d246 100644 --- a/tests/odp.at +++ b/tests/odp.at @@ -291,6 +291,7 @@ tnl_push(tnl_port(4),header(size=42,type=3,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:1 tnl_push(tnl_port(4),header(size=46,type=3,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x40),gre((flags=0xa000,proto=0x6558),csum=0x0,key=0x1e241)),out_port(1)) tnl_push(tnl_port(6),header(size=50,type=4,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0x8000000,vni=0x1c7)),out_port(1)) tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(oam,vni=0x1c7)),out_port(1)) +tnl_push(tnl_port(6),header(size=58,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x1c7,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(1)) tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x1c7)),out_port(1)) ]) AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at index d9e84962f1f..bd95c8e5094 100644 --- a/tests/tunnel-push-pop.at +++ b/tests/tunnel-push-pop.at @@ -98,6 +98,14 @@ AT_CHECK([tail -1 stdout], [0], [Datapath actions: tnl_push(tnl_port(6081),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)) ]) +dnl Check Geneve tunnel push with options +AT_CHECK([ovs-ofctl add-geneve-map int-br "{class=0xffff,type=0x80,len=4}->tun_metadata0"]) +AT_CHECK([ovs-ofctl add-flow int-br "actions=set_field:1.1.2.92->tun_dst,set_field:0xa->tun_metadata0,5"]) +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], + [Datapath actions: tnl_push(tnl_port(6081),header(size=58,type=5,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x7b,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(100)) +]) + dnl Check decapsulation of GRE packet AT_CHECK([ovs-appctl netdev-dummy/receive p0 '001b213cac30001b213cab6408004500007e79464000402f99080101025c0101025820006558000001c8fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637']) ovs-appctl time/warp 1000 @@ -114,5 +122,18 @@ AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port 3'], [0], [dnl port 3: rx pkts=1, bytes=98, drop=0, errs=0, frame=0, over=0, crc=0 ]) +dnl Check decapsulation of Geneve packet with options +AT_CHECK([ovs-ofctl del-flows int-br]) +AT_CHECK([ovs-ofctl add-flow int-br "tun_metadata0=0xa/0xf,actions=5"]) +AT_CHECK([ovs-appctl netdev-dummy/receive p0 '001b213cac30001b213cab64080045000096794640004011ba630101025c01010258308817c1008200000400655800007b00ffff80010000000affff00010000000bfe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637']) + +ovs-appctl time/warp 1000 +AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port 5'], [0], [dnl + port 5: rx pkts=1, bytes=98, drop=0, errs=0, frame=0, over=0, crc=0 +]) +AT_CHECK([ovs-appctl dpif/dump-flows int-br], [0], [dnl +tunnel(tun_id=0x7b,src=1.1.2.92,dst=1.1.2.88,ttl=64,geneve({class=0xffff,type=0x80,len=4,0xa/0xf}),flags(-df-csum+key)),skb_mark(0),recirc_id(0),in_port(6081),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop +]) + OVS_VSWITCHD_STOP AT_CLEANUP