With this patch, ovs-vswitchd uses flow based tunneling exclusively. I.E. each kind of tunnel shares a single tunnel backer in the datapath. Tunnel headers are set by userspace using the ipv4_tunnel datapath action. There are still some significant pieces of work to do, but the basic building blocks are there to begin testing.
Signed-off-by: Jesse Gross <je...@nicira.com> Signed-off-by: Ethan Jackson <et...@nicira.com> --- NEWS | 2 + lib/dpif-linux.c | 42 ++++- lib/netdev-vport.c | 405 +++++++++++++++--------------------------------- lib/netdev-vport.h | 1 - lib/netdev.c | 7 + lib/netdev.h | 1 + lib/odp-util.c | 17 +- lib/odp-util.h | 4 +- ofproto/ofproto-dpif.c | 248 +++++++++++++++++++++-------- tests/ofproto-dpif.at | 19 --- 10 files changed, 375 insertions(+), 371 deletions(-) diff --git a/NEWS b/NEWS index d30c47d..4f7ffe8 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,8 @@ post-v1.9.0 retire that meaning of ANY in favor of the OpenFlow 1.1 meaning. - Inheritance of the Don't Fragment bit in IP tunnels (df_inherit) is no longer supported. + - Tunneling requires the version of the kernel module paired Open vSwitch + 1.9.0 or later. v1.9.0 - xx xxx xxxx diff --git a/lib/dpif-linux.c b/lib/dpif-linux.c index 19ae565..0968340 100644 --- a/lib/dpif-linux.c +++ b/lib/dpif-linux.c @@ -387,6 +387,44 @@ dpif_linux_get_stats(const struct dpif *dpif_, struct dpif_dp_stats *stats) return error; } +static const char * +get_vport_type(const struct dpif_linux_vport *vport) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); + + switch (vport->type) { + case OVS_VPORT_TYPE_NETDEV: + return "system"; + + case OVS_VPORT_TYPE_INTERNAL: + return "internal"; + + case OVS_VPORT_TYPE_PATCH: + return "patch"; + + case OVS_VPORT_TYPE_GRE: + return "gre"; + + case OVS_VPORT_TYPE_GRE64: + return "gre64"; + + case OVS_VPORT_TYPE_CAPWAP: + return "capwap"; + + case OVS_VPORT_TYPE_VXLAN: + return "vxlan"; + + case OVS_VPORT_TYPE_UNSPEC: + case OVS_VPORT_TYPE_FT_GRE: + case __OVS_VPORT_TYPE_MAX: + break; + } + + VLOG_WARN_RL(&rl, "dp%d: port `%s' has unsupported type %u", + vport->dp_ifindex, vport->name, (unsigned int) vport->type); + return "unknown"; +} + static int dpif_linux_port_add(struct dpif *dpif_, struct netdev *netdev, uint32_t *port_nop) @@ -481,7 +519,7 @@ dpif_linux_port_query__(const struct dpif *dpif, uint32_t port_no, error = ENODEV; } else if (dpif_port) { dpif_port->name = xstrdup(reply.name); - dpif_port->type = xstrdup(netdev_vport_get_netdev_type(&reply)); + dpif_port->type = xstrdup(get_vport_type(&reply)); dpif_port->port_no = reply.port_no; } ofpbuf_delete(buf); @@ -583,7 +621,7 @@ dpif_linux_port_dump_next(const struct dpif *dpif OVS_UNUSED, void *state_, } dpif_port->name = CONST_CAST(char *, vport.name); - dpif_port->type = CONST_CAST(char *, netdev_vport_get_netdev_type(&vport)); + dpif_port->type = CONST_CAST(char *, get_vport_type(&vport)); dpif_port->port_no = vport.port_no; return 0; } diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c index 48637d1..bf2bf6a 100644 --- a/lib/netdev-vport.c +++ b/lib/netdev-vport.c @@ -69,22 +69,13 @@ struct netdev_vport { struct vport_class { enum ovs_vport_type type; struct netdev_class netdev_class; - int (*parse_config)(const char *name, const char *type, - const struct smap *args, struct ofpbuf *options, - struct netdev_tunnel_config *tnl_cfg); - int (*unparse_config)(const char *name, const char *type, - const struct nlattr *options, size_t options_len, - struct smap *args); }; static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); static int netdev_vport_create(const struct netdev_class *, const char *, struct netdev_dev **); -static void netdev_vport_poll_notify(const struct netdev *); -static int tnl_port_config_from_nlattr(const struct nlattr *options, - size_t options_len, - struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]); +static void netdev_vport_poll_notify(struct netdev_dev_vport *); static bool is_vport_class(const struct netdev_class *class) @@ -152,62 +143,6 @@ netdev_vport_get_vport_type(const struct netdev *netdev) : OVS_VPORT_TYPE_UNSPEC); } -static uint32_t -get_u32_or_zero(const struct nlattr *a) -{ - return a ? nl_attr_get_u32(a) : 0; -} - -const char * -netdev_vport_get_netdev_type(const struct dpif_linux_vport *vport) -{ - struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]; - - switch (vport->type) { - case OVS_VPORT_TYPE_UNSPEC: - break; - - case OVS_VPORT_TYPE_NETDEV: - return "system"; - - case OVS_VPORT_TYPE_INTERNAL: - return "internal"; - - case OVS_VPORT_TYPE_PATCH: - return "patch"; - - case OVS_VPORT_TYPE_GRE: - if (tnl_port_config_from_nlattr(vport->options, vport->options_len, - a)) { - break; - } - return (get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]) & TNL_F_IPSEC - ? "ipsec_gre" : "gre"); - - case OVS_VPORT_TYPE_GRE64: - if (tnl_port_config_from_nlattr(vport->options, vport->options_len, - a)) { - break; - } - return (get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]) & TNL_F_IPSEC - ? "ipsec_gre64" : "gre64"); - - case OVS_VPORT_TYPE_CAPWAP: - return "capwap"; - - case OVS_VPORT_TYPE_VXLAN: - return "vxlan"; - - case OVS_VPORT_TYPE_FT_GRE: - case __OVS_VPORT_TYPE_MAX: - break; - } - - VLOG_WARN_RL(&rl, "dp%d: port `%s' has unsupported type %u", - vport->dp_ifindex, vport->name, (unsigned int) vport->type); - return "unknown"; -} - static int netdev_vport_create(const struct netdev_class *netdev_class, const char *name, struct netdev_dev **netdev_devp) @@ -255,88 +190,12 @@ netdev_vport_close(struct netdev *netdev_) } static int -netdev_vport_get_config(struct netdev_dev *dev_, struct smap *args) -{ - const struct netdev_class *netdev_class = netdev_dev_get_class(dev_); - const struct vport_class *vport_class = vport_class_cast(netdev_class); - struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_); - const char *name = netdev_dev_get_name(dev_); - int error; - - if (!dev->options) { - struct dpif_linux_vport reply; - struct ofpbuf *buf; - - error = dpif_linux_vport_get(name, &reply, &buf); - if (error) { - VLOG_ERR_RL(&rl, "%s: vport query failed (%s)", - name, strerror(error)); - return error; - } - - dev->options = ofpbuf_clone_data(reply.options, reply.options_len); - ofpbuf_delete(buf); - } - - error = vport_class->unparse_config(name, netdev_class->type, - dev->options->data, - dev->options->size, - args); - if (error) { - VLOG_ERR_RL(&rl, "%s: failed to parse kernel config (%s)", - name, strerror(error)); - } - return error; -} - -static int -netdev_vport_set_config(struct netdev_dev *dev_, const struct smap *args) -{ - const struct netdev_class *netdev_class = netdev_dev_get_class(dev_); - const struct vport_class *vport_class = vport_class_cast(netdev_class); - struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_); - const char *name = netdev_dev_get_name(dev_); - struct netdev_tunnel_config tnl_cfg; - struct ofpbuf *options; - int error; - - options = ofpbuf_new(64); - error = vport_class->parse_config(name, netdev_dev_get_type(dev_), - args, options, &tnl_cfg); - if (!error - && (!dev->options - || options->size != dev->options->size - || memcmp(options->data, dev->options->data, options->size))) { - struct dpif_linux_vport vport; - - dpif_linux_vport_init(&vport); - vport.cmd = OVS_VPORT_CMD_SET; - vport.name = name; - vport.options = options->data; - vport.options_len = options->size; - error = dpif_linux_vport_transact(&vport, NULL, NULL); - if (!error || error == ENODEV) { - /* Either reconfiguration succeeded or this vport is not installed - * in the kernel (e.g. it hasn't been added to a dpif yet with - * dpif_port_add()). */ - ofpbuf_delete(dev->options); - dev->options = options; - dev->tnl_cfg = tnl_cfg; - options = NULL; - error = 0; - } - } - ofpbuf_delete(options); - - return error; -} - -static int netdev_vport_set_etheraddr(struct netdev *netdev, const uint8_t mac[ETH_ADDR_LEN]) { - memcpy(netdev_vport_get_dev(netdev)->etheraddr, mac, ETH_ADDR_LEN); - netdev_vport_poll_notify(netdev); + struct netdev_dev_vport *dev = netdev_vport_get_dev(netdev); + memcpy(dev->etheraddr, mac, ETH_ADDR_LEN); + netdev_vport_poll_notify(dev); return 0; } @@ -456,10 +315,8 @@ netdev_vport_wait(void) /* Helper functions. */ static void -netdev_vport_poll_notify(const struct netdev *netdev) +netdev_vport_poll_notify(struct netdev_dev_vport *ndv) { - struct netdev_dev_vport *ndv = netdev_vport_get_dev(netdev); - ndv->change_seq++; if (!ndv->change_seq) { ndv->change_seq++; @@ -496,16 +353,16 @@ parse_key(const struct smap *args, const char *name, } static int -parse_tunnel_config(const char *name, const char *type, - const struct smap *args, struct ofpbuf *options, - struct netdev_tunnel_config *tnl_cfg_) +set_tunnel_config(struct netdev_dev *dev_, const struct smap *args) { + struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_); + const char *name = netdev_dev_get_name(dev_); + const char *type = netdev_dev_get_type(dev_); + bool ipsec_mech_set, needs_dst_port, has_csum; struct netdev_tunnel_config tnl_cfg; struct smap_node *node; - uint8_t flags; - flags = TNL_F_DF_DEFAULT; has_csum = strstr(type, "gre"); ipsec_mech_set = false; memset(&tnl_cfg, 0, sizeof tnl_cfg); @@ -516,9 +373,6 @@ parse_tunnel_config(const char *name, const char *type, needs_dst_port = !strcmp(type, "vxlan"); tnl_cfg.ipsec = strstr(type, "ipsec"); - if (tnl_cfg.ipsec) { - flags |= TNL_F_IPSEC; - } tnl_cfg.dont_fragment = true; SMAP_FOR_EACH (node, args) { @@ -538,14 +392,12 @@ parse_tunnel_config(const char *name, const char *type, } } else if (!strcmp(node->key, "tos")) { if (!strcmp(node->value, "inherit")) { - flags |= TNL_F_TOS_INHERIT; tnl_cfg.tos_inherit = true; } else { char *endptr; int tos; tos = strtol(node->value, &endptr, 0); if (*endptr == '\0' && tos == (tos & IP_DSCP_MASK)) { - nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TOS, tos); tnl_cfg.tos = tos; } else { VLOG_WARN("%s: invalid TOS %s", name, node->value); @@ -553,24 +405,18 @@ parse_tunnel_config(const char *name, const char *type, } } else if (!strcmp(node->key, "ttl")) { if (!strcmp(node->value, "inherit")) { - flags |= TNL_F_TTL_INHERIT; tnl_cfg.ttl_inherit = true; } else { - nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TTL, atoi(node->value)); tnl_cfg.ttl = atoi(node->value); } } else if (!strcmp(node->key, "dst_port") && needs_dst_port) { tnl_cfg.dst_port = htons(atoi(node->value)); - nl_msg_put_u16(options, OVS_TUNNEL_ATTR_DST_PORT, - atoi(node->value)); } else if (!strcmp(node->key, "csum") && has_csum) { if (!strcmp(node->value, "true")) { - flags |= TNL_F_CSUM; tnl_cfg.csum = true; } } else if (!strcmp(node->key, "df_default")) { if (!strcmp(node->value, "false")) { - flags &= ~TNL_F_DF_DEFAULT; tnl_cfg.dont_fragment = false; } } else if (!strcmp(node->key, "peer_cert") && tnl_cfg.ipsec) { @@ -611,7 +457,6 @@ parse_tunnel_config(const char *name, const char *type, /* Add a default destination port for VXLAN if none specified. */ if (needs_dst_port && !tnl_cfg.dst_port) { - nl_msg_put_u16(options, OVS_TUNNEL_ATTR_DST_PORT, VXLAN_DST_PORT); tnl_cfg.dst_port = htons(VXLAN_DST_PORT); } @@ -647,14 +492,11 @@ parse_tunnel_config(const char *name, const char *type, name, type); return EINVAL; } - nl_msg_put_be32(options, OVS_TUNNEL_ATTR_DST_IPV4, tnl_cfg.ip_dst); if (tnl_cfg.ip_src) { if (ip_is_multicast(tnl_cfg.ip_dst)) { VLOG_WARN("%s: remote_ip is multicast, ignoring local_ip", name); tnl_cfg.ip_src = 0; - } else { - nl_msg_put_be32(options, OVS_TUNNEL_ATTR_SRC_IPV4, tnl_cfg.ip_src); } } @@ -665,147 +507,104 @@ parse_tunnel_config(const char *name, const char *type, tnl_cfg.in_key = parse_key(args, "in_key", &tnl_cfg.in_key_present, &tnl_cfg.in_key_flow); - if (tnl_cfg.in_key_present && !tnl_cfg.in_key_flow) { - nl_msg_put_be64(options, OVS_TUNNEL_ATTR_IN_KEY, tnl_cfg.in_key); - } tnl_cfg.out_key = parse_key(args, "out_key", &tnl_cfg.out_key_present, &tnl_cfg.out_key_flow); - if (!tnl_cfg.out_key_flow) { - nl_msg_put_be64(options, OVS_TUNNEL_ATTR_OUT_KEY, tnl_cfg.out_key); - } - nl_msg_put_u32(options, OVS_TUNNEL_ATTR_FLAGS, flags); - - *tnl_cfg_ = tnl_cfg; - return 0; -} - -static int -tnl_port_config_from_nlattr(const struct nlattr *options, size_t options_len, - struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]) -{ - static const struct nl_policy ovs_tunnel_policy[] = { - [OVS_TUNNEL_ATTR_FLAGS] = { .type = NL_A_U32, .optional = true }, - [OVS_TUNNEL_ATTR_DST_IPV4] = { .type = NL_A_BE32, .optional = true }, - [OVS_TUNNEL_ATTR_SRC_IPV4] = { .type = NL_A_BE32, .optional = true }, - [OVS_TUNNEL_ATTR_IN_KEY] = { .type = NL_A_BE64, .optional = true }, - [OVS_TUNNEL_ATTR_OUT_KEY] = { .type = NL_A_BE64, .optional = true }, - [OVS_TUNNEL_ATTR_TOS] = { .type = NL_A_U8, .optional = true }, - [OVS_TUNNEL_ATTR_TTL] = { .type = NL_A_U8, .optional = true }, - [OVS_TUNNEL_ATTR_DST_PORT] = { .type = NL_A_U16, .optional = true }, - }; - struct ofpbuf buf; + dev->tnl_cfg = tnl_cfg; + netdev_vport_poll_notify(dev); - ofpbuf_use_const(&buf, options, options_len); - if (!nl_policy_parse(&buf, 0, ovs_tunnel_policy, - a, ARRAY_SIZE(ovs_tunnel_policy))) { - return EINVAL; - } return 0; } -static uint64_t -get_be64_or_zero(const struct nlattr *a) -{ - return a ? ntohll(nl_attr_get_be64(a)) : 0; -} - static int -unparse_tunnel_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED, - const struct nlattr *options, size_t options_len, - struct smap *args) +get_tunnel_config(struct netdev_dev *dev, struct smap *args) { - struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]; - uint32_t flags; - int error; - - error = tnl_port_config_from_nlattr(options, options_len, a); - if (error) { - return error; - } + const struct netdev_tunnel_config *tnl_cfg = + &netdev_dev_vport_cast(dev)->tnl_cfg; - if (a[OVS_TUNNEL_ATTR_DST_IPV4]) { - ovs_be32 daddr = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_DST_IPV4]); - smap_add_format(args, "remote_ip", IP_FMT, IP_ARGS(daddr)); + if (tnl_cfg->ip_dst) { + smap_add_format(args, "remote_ip", IP_FMT, IP_ARGS(tnl_cfg->ip_dst)); } - if (a[OVS_TUNNEL_ATTR_SRC_IPV4]) { - ovs_be32 saddr = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_SRC_IPV4]); - smap_add_format(args, "local_ip", IP_FMT, IP_ARGS(saddr)); + if (tnl_cfg->ip_src) { + smap_add_format(args, "local_ip", IP_FMT, IP_ARGS(tnl_cfg->ip_src)); } - if (!a[OVS_TUNNEL_ATTR_IN_KEY] && !a[OVS_TUNNEL_ATTR_OUT_KEY]) { + if (tnl_cfg->in_key_flow && tnl_cfg->out_key_flow) { smap_add(args, "key", "flow"); + } else if (tnl_cfg->in_key_present && tnl_cfg->out_key_present + && tnl_cfg->in_key == tnl_cfg->out_key) { + smap_add_format(args, "key", "%"PRIu64, ntohll(tnl_cfg->in_key)); } else { - uint64_t in_key = get_be64_or_zero(a[OVS_TUNNEL_ATTR_IN_KEY]); - uint64_t out_key = get_be64_or_zero(a[OVS_TUNNEL_ATTR_OUT_KEY]); - - if (in_key && in_key == out_key) { - smap_add_format(args, "key", "%"PRIu64, in_key); - } else { - if (!a[OVS_TUNNEL_ATTR_IN_KEY]) { - smap_add(args, "in_key", "flow"); - } else if (in_key) { - smap_add_format(args, "in_key", "%"PRIu64, in_key); - } + if (tnl_cfg->in_key_flow) { + smap_add(args, "in_key", "flow"); + } else if (tnl_cfg->in_key_present) { + smap_add_format(args, "in_key", "%"PRIu64, + ntohll(tnl_cfg->in_key)); + } - if (!a[OVS_TUNNEL_ATTR_OUT_KEY]) { - smap_add(args, "out_key", "flow"); - } else if (out_key) { - smap_add_format(args, "out_key", "%"PRIu64, out_key); - } + if (tnl_cfg->out_key_flow) { + smap_add(args, "out_key", "flow"); + } else if (tnl_cfg->out_key_present) { + smap_add_format(args, "out_key", "%"PRIu64, + ntohll(tnl_cfg->out_key)); } } - flags = get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]); - - if (flags & TNL_F_TTL_INHERIT) { + if (tnl_cfg->ttl_inherit) { smap_add(args, "ttl", "inherit"); - } else if (a[OVS_TUNNEL_ATTR_TTL]) { - int ttl = nl_attr_get_u8(a[OVS_TUNNEL_ATTR_TTL]); - smap_add_format(args, "ttl", "%d", ttl); + } else if (tnl_cfg->ttl != DEFAULT_TTL) { + smap_add_format(args, "ttl", "%"PRIu8, tnl_cfg->ttl); } - if (flags & TNL_F_TOS_INHERIT) { + if (tnl_cfg->tos_inherit) { smap_add(args, "tos", "inherit"); - } else if (a[OVS_TUNNEL_ATTR_TOS]) { - int tos = nl_attr_get_u8(a[OVS_TUNNEL_ATTR_TOS]); - smap_add_format(args, "tos", "0x%x", tos); + } else if (tnl_cfg->tos) { + smap_add_format(args, "tos", "0x%x", tnl_cfg->tos); } - if (a[OVS_TUNNEL_ATTR_DST_PORT]) { - uint16_t dst_port = nl_attr_get_u16(a[OVS_TUNNEL_ATTR_DST_PORT]); + if (tnl_cfg->dst_port) { + uint16_t dst_port = ntohs(tnl_cfg->dst_port); if (dst_port != VXLAN_DST_PORT) { smap_add_format(args, "dst_port", "%d", dst_port); } } - if (flags & TNL_F_CSUM) { + if (tnl_cfg->csum) { smap_add(args, "csum", "true"); } - if (flags & TNL_F_DF_INHERIT) { - /* Shouldn't happen as "df_inherit" is no longer supported. However, - * for completeness we report it if it's there. */ - smap_add(args, "df_inherit", "true"); - } - if (!(flags & TNL_F_DF_DEFAULT)) { + + if (!tnl_cfg->dont_fragment) { smap_add(args, "df_default", "false"); } return 0; } +static const char * +get_tunnel_dpif_port(struct netdev_dev *dev) +{ + const char *type = netdev_dev_get_type(dev); + + if (strstr(type, "gre64")) { + return "gre64"; + } else if (strstr(type, "gre")) { + return "gre"; + } else { + return type; + } +} + static int -parse_patch_config(const char *name, const char *type OVS_UNUSED, - const struct smap *args, struct ofpbuf *options, - struct netdev_tunnel_config *tnl_cfg) +set_patch_config(struct netdev_dev *dev_, const struct smap *args) { + struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_); + const char *name = netdev_dev_get_name(dev_); + struct ofpbuf *options; const char *peer; - memset(tnl_cfg, 0, sizeof *tnl_cfg); - peer = smap_get(args, "peer"); if (!peer) { VLOG_ERR("%s: patch type requires valid 'peer' argument", name); @@ -827,27 +626,68 @@ parse_patch_config(const char *name, const char *type OVS_UNUSED, return EINVAL; } + options = ofpbuf_new(0); nl_msg_put_string(options, OVS_PATCH_ATTR_PEER, peer); + if (!dev->options + || options->size != dev->options->size + || memcmp(options->data, dev->options->data, options->size)) { + struct dpif_linux_vport vport; + int error; + + dpif_linux_vport_init(&vport); + vport.cmd = OVS_VPORT_CMD_SET; + vport.name = name; + vport.options = options->data; + vport.options_len = options->size; + + error = dpif_linux_vport_transact(&vport, NULL, NULL); + if (error && error != ENODEV) { + return error; + } + + /* Either reconfiguration succeeded or this vport is not installed in + * the kernel (e.g. it hasn't been added to a dpif yet with + * dpif_port_add()). */ + ofpbuf_delete(dev->options); + dev->options = options; + options = NULL; + } + ofpbuf_delete(options); + return 0; } static int -unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED, - const struct nlattr *options, size_t options_len, - struct smap *args) +get_patch_config(struct netdev_dev *dev_, struct smap *args) { static const struct nl_policy ovs_patch_policy[] = { [OVS_PATCH_ATTR_PEER] = { .type = NL_A_STRING, .max_len = IFNAMSIZ, .optional = false } }; + struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_); + const char *name = netdev_dev_get_name(dev_); struct nlattr *a[ARRAY_SIZE(ovs_patch_policy)]; - struct ofpbuf buf; - ofpbuf_use_const(&buf, options, options_len); - if (!nl_policy_parse(&buf, 0, ovs_patch_policy, + if (!dev->options) { + struct dpif_linux_vport reply; + struct ofpbuf *buf; + int error; + + error = dpif_linux_vport_get(name, &reply, &buf); + if (error) { + VLOG_ERR_RL(&rl, "%s: vport query failed (%s)", + name, strerror(error)); + return error; + } + + dev->options = ofpbuf_clone_data(reply.options, reply.options_len); + ofpbuf_delete(buf); + } + + if (!nl_policy_parse(dev->options, 0, ovs_patch_policy, a, ARRAY_SIZE(ovs_patch_policy))) { return EINVAL; } @@ -856,16 +696,18 @@ unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED, return 0; } -#define VPORT_FUNCTIONS(GET_TUNNEL_CONFIG, GET_STATUS) \ +#define VPORT_FUNCTIONS(GET_DPIF_PORT, GET_CONFIG, \ + SET_CONFIG, GET_TUNNEL_CONFIG, \ + GET_STATUS) \ NULL, \ netdev_vport_run, \ netdev_vport_wait, \ \ netdev_vport_create, \ netdev_vport_destroy, \ - NULL, /* get_dpif_port */ \ - netdev_vport_get_config, \ - netdev_vport_set_config, \ + GET_DPIF_PORT, \ + GET_CONFIG, \ + SET_CONFIG, \ GET_TUNNEL_CONFIG, \ \ netdev_vport_open, \ @@ -919,9 +761,11 @@ unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED, #define TUNNEL_CLASS(NAME, VPORT_TYPE) \ { VPORT_TYPE, \ - { NAME, VPORT_FUNCTIONS(get_netdev_tunnel_config, \ - tunnel_get_status) }, \ - parse_tunnel_config, unparse_tunnel_config } + { NAME, VPORT_FUNCTIONS(get_tunnel_dpif_port, \ + get_tunnel_config, \ + set_tunnel_config, \ + get_netdev_tunnel_config, \ + tunnel_get_status) }} void netdev_vport_register(void) @@ -935,8 +779,11 @@ netdev_vport_register(void) TUNNEL_CLASS("vxlan", OVS_VPORT_TYPE_VXLAN), { OVS_VPORT_TYPE_PATCH, - { "patch", VPORT_FUNCTIONS(NULL, NULL) }, - parse_patch_config, unparse_patch_config } + { "patch", VPORT_FUNCTIONS(NULL, + get_patch_config, + set_patch_config, + NULL, + NULL) }} }; int i; diff --git a/lib/netdev-vport.h b/lib/netdev-vport.h index 31c1198..dbe7c81 100644 --- a/lib/netdev-vport.h +++ b/lib/netdev-vport.h @@ -29,7 +29,6 @@ void netdev_vport_register(void); const struct ofpbuf *netdev_vport_get_options(const struct netdev *); enum ovs_vport_type netdev_vport_get_vport_type(const struct netdev *); -const char *netdev_vport_get_netdev_type(const struct dpif_linux_vport *); int netdev_vport_get_stats(const struct netdev *, struct netdev_stats *); diff --git a/lib/netdev.c b/lib/netdev.c index 94e3468..dafdeb6 100644 --- a/lib/netdev.c +++ b/lib/netdev.c @@ -1453,6 +1453,13 @@ netdev_get_dpif_port(const struct netdev *netdev) return port ? port : netdev_get_name(netdev); } +const char * +netdev_get_type_from_name(const char *name) +{ + const struct netdev_dev *dev = netdev_dev_from_name(name); + return dev ? netdev_dev_get_type(dev) : NULL; +} + struct netdev_dev * netdev_get_dev(const struct netdev *netdev) { diff --git a/lib/netdev.h b/lib/netdev.h index 942afc6..e9a1b48 100644 --- a/lib/netdev.h +++ b/lib/netdev.h @@ -128,6 +128,7 @@ const struct netdev_tunnel_config * const char *netdev_get_name(const struct netdev *); const char *netdev_get_type(const struct netdev *); const char *netdev_get_dpif_port(const struct netdev *); +const char *netdev_get_type_from_name(const char *name); int netdev_get_mtu(const struct netdev *, int *mtup); int netdev_set_mtu(const struct netdev *, int mtu); int netdev_get_ifindex(const struct netdev *); diff --git a/lib/odp-util.c b/lib/odp-util.c index e2f21da..0d773bd 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc. + * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2006,8 +2006,14 @@ commit_set_action(struct ofpbuf *odp_actions, enum ovs_key_attr key_type, nl_msg_end_nested(odp_actions, offset); } -static void -commit_set_tunnel_action(const struct flow *flow, struct flow *base, +/* If any of the flow key data that ODP actions can modify are different in + * 'base->tunnel' and 'flow->tunnel', appends a set_tunnel ODP action to + * 'odp_actions' that change the flow tunneling information in key from + * 'base->tunnel' into 'flow->tunnel', and then changes 'base->tunnel' in the + * same way. In other words, operates the same as commit_odp_actions(), but + * only on tunneling information. */ +void +commit_odp_tunnel_action(const struct flow *flow, struct flow *base, struct ofpbuf *odp_actions) { if (!memcmp(&base->tunnel, &flow->tunnel, sizeof base->tunnel)) { @@ -2210,12 +2216,13 @@ commit_set_skb_mark_action(const struct flow *flow, struct flow *base, } /* If any of the flow key data that ODP actions can modify are different in * 'base' and 'flow', appends ODP actions to 'odp_actions' that change the flow - * key from 'base' into 'flow', and then changes 'base' the same way. */ + * key from 'base' into 'flow', and then changes 'base' the same way. Does not + * commit set_tunnel actions. Users should call commit_odp_tunnel_action() + * in addition to this function if needed. */ void commit_odp_actions(const struct flow *flow, struct flow *base, struct ofpbuf *odp_actions) { - commit_set_tunnel_action(flow, base, odp_actions); commit_set_ether_addr_action(flow, base, odp_actions); commit_vlan_action(flow, base, odp_actions); commit_set_nw_action(flow, base, odp_actions); diff --git a/lib/odp-util.h b/lib/odp-util.h index 9d38f33..5ee9708 100644 --- a/lib/odp-util.h +++ b/lib/odp-util.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc. + * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,6 +106,8 @@ enum odp_key_fitness odp_flow_key_to_flow(const struct nlattr *, size_t, struct flow *); const char *odp_key_fitness_to_string(enum odp_key_fitness); +void commit_odp_tunnel_action(const struct flow *, struct flow *base, + struct ofpbuf *odp_actions); void commit_odp_actions(const struct flow *, struct flow *base, struct ofpbuf *odp_actions); diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 5b86d46..7e0d1a1 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -51,6 +51,7 @@ #include "simap.h" #include "smap.h" #include "timer.h" +#include "tunnel.h" #include "unaligned.h" #include "unixctl.h" #include "vlan-bitmap.h" @@ -673,8 +674,9 @@ struct ofproto_dpif { struct hmap realdev_vid_map; /* (realdev,vid) -> vlandev. */ struct hmap vlandev_map; /* vlandev -> (realdev,vid). */ - /* Ports. */ - struct sset ports; /* Set of port names. */ + struct sset ports; /* Set of non-tunnel port names. */ + struct sset tnls; /* Set of tunnel port names. */ + struct sset tnl_backers; /* Set of dpif ports backing tunnels. */ struct sset port_poll_set; /* Queued names for port_poll() reply. */ int port_poll_errno; /* Last errno for port_poll() reply. */ }; @@ -702,6 +704,7 @@ static struct ofport_dpif *get_odp_port(const struct ofproto_dpif *, static void ofproto_trace(struct ofproto_dpif *, const struct flow *, const struct ofpbuf *, ovs_be16 initial_tci, struct ds *); +static bool may_dpif_port_del(struct ofproto_dpif *, struct ofport_dpif *); /* Packet processing. */ static void update_learning_table(struct ofproto_dpif *, @@ -839,6 +842,14 @@ type_run(const char *type) goto next; } + HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, + &all_ofproto_dpifs) { + if (sset_contains(&ofproto->ports, devname) + || sset_contains(&ofproto->tnl_backers, devname)) { + goto next; + } + } + ofproto = lookup_ofproto_dpif_by_port_name(devname); if (dpif_port_query_by_name(backer->dpif, devname, &port)) { /* The port was removed. If we know the datapath, @@ -1120,6 +1131,8 @@ construct(struct ofproto *ofproto_) hmap_init(&ofproto->realdev_vid_map); sset_init(&ofproto->ports); + sset_init(&ofproto->tnls); + sset_init(&ofproto->tnl_backers); sset_init(&ofproto->port_poll_set); ofproto->port_poll_errno = 0; @@ -1264,6 +1277,8 @@ destruct(struct ofproto *ofproto_) hmap_destroy(&ofproto->realdev_vid_map); sset_destroy(&ofproto->ports); + sset_destroy(&ofproto->tnls); + sset_destroy(&ofproto->tnl_backers); sset_destroy(&ofproto->port_poll_set); close_dpif_backer(ofproto->backer); @@ -1290,6 +1305,10 @@ run(struct ofproto *ofproto_) struct ofbundle *bundle; int error; + if (tnl_run()) { + ofproto->need_revalidate = true; + } + if (!clogged) { complete_operations(ofproto); } @@ -1523,7 +1542,7 @@ port_construct(struct ofport *port_) port->carrier_seq = netdev_get_carrier_resets(port->up.netdev); error = dpif_port_query_by_name(ofproto->backer->dpif, - netdev_get_name(port->up.netdev), + netdev_get_dpif_port(port->up.netdev), &dpif_port); if (error) { return error; @@ -1531,12 +1550,19 @@ port_construct(struct ofport *port_) port->odp_port = dpif_port.port_no; - /* Sanity-check that a mapping doesn't already exist. This - * shouldn't happen. */ - if (odp_port_to_ofp_port(ofproto, port->odp_port) != OFPP_NONE) { - VLOG_ERR("port %s already has an OpenFlow port number\n", - dpif_port.name); - return EBUSY; + if (!netdev_get_tunnel_config(port->up.netdev)) { + /* Sanity-check that a mapping doesn't already exist. This + * shouldn't happen for non-tunnel ports. */ + if (odp_port_to_ofp_port(ofproto, port->odp_port) != OFPP_NONE) { + VLOG_ERR("port %s already has an OpenFlow port number\n", + dpif_port.name); + return EBUSY; + } + } + + error = tnl_port_add(&port->up, port->odp_port); + if (error) { + return error; } hmap_insert(&ofproto->backer->odp_to_ofport_map, &port->odp_port_node, @@ -1554,17 +1580,22 @@ port_destruct(struct ofport *port_) { struct ofport_dpif *port = ofport_dpif_cast(port_); struct ofproto_dpif *ofproto = ofproto_dpif_cast(port->up.ofproto); + const char *dp_port_name = netdev_get_dpif_port(port->up.netdev); const char *devname = netdev_get_name(port->up.netdev); - if (dpif_port_exists(ofproto->backer->dpif, devname)) { + if (dpif_port_exists(ofproto->backer->dpif, dp_port_name) + && may_dpif_port_del(ofproto, port)) { /* The underlying device is still there, so delete it. This * happens when the ofproto is being destroyed, since the caller * assumes that removal of attached ports will happen as part of * destruction. */ dpif_port_del(ofproto->backer->dpif, port->odp_port); + sset_find_and_delete(&ofproto->tnl_backers, dp_port_name); } + tnl_port_del(port_); sset_find_and_delete(&ofproto->ports, devname); + sset_find_and_delete(&ofproto->tnls, devname); hmap_remove(&ofproto->backer->odp_to_ofport_map, &port->odp_port_node); ofproto->need_revalidate = REV_RECONFIGURE; bundle_remove(port_); @@ -2874,6 +2905,26 @@ port_query_by_name(const struct ofproto *ofproto_, const char *devname, struct dpif_port dpif_port; int error; + if (sset_contains(&ofproto->tnls, devname)) { + const struct ofport *ofport; + const char *type; + + /* We may be called before ofproto->up.port_by_name is populated with + * the appropriate ofport. For this reason, we must get the name and + * type from the netdev layer directly. */ + type = netdev_get_type_from_name(devname); + if (type) { + ofproto_port->name = xstrdup(devname); + ofproto_port->type = xstrdup(type); + + ofport = shash_find_data(&ofproto->up.port_by_name, devname); + ofproto_port->ofp_port = ofport ? ofport->ofp_port : OFPP_NONE; + return 0; + } else { + return ENODEV; + } + } + if (!sset_contains(&ofproto->ports, devname)) { return ENODEV; } @@ -2889,34 +2940,77 @@ static int port_add(struct ofproto *ofproto_, struct netdev *netdev) { struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_); - uint32_t odp_port = UINT32_MAX; - int error; + const char *dp_port_name = netdev_get_dpif_port(netdev); + const char *devname = netdev_get_name(netdev); - error = dpif_port_add(ofproto->backer->dpif, netdev, &odp_port); - if (!error) { - sset_add(&ofproto->ports, netdev_get_name(netdev)); + if (!dpif_port_exists(ofproto->backer->dpif, dp_port_name)) { + int error = dpif_port_add(ofproto->backer->dpif, netdev, NULL); + if (error) { + return error; + } } - return error; + + if (netdev_get_tunnel_config(netdev)) { + sset_add(&ofproto->tnls, devname); + sset_add(&ofproto->tnl_backers, dp_port_name); + } else { + sset_add(&ofproto->ports, devname); + } + return 0; +} + +static bool +may_dpif_port_del(struct ofproto_dpif *ofproto, struct ofport_dpif *ofport) +{ + struct ofproto_dpif *ofproto_iter; + struct ofport *ofport_up; + + if (!netdev_get_tunnel_config(ofport->up.netdev)) { + return true; + } + + HMAP_FOR_EACH (ofproto_iter, all_ofproto_dpifs_node, &all_ofproto_dpifs) { + if (ofproto->backer != ofproto_iter->backer) { + continue; + } + + HMAP_FOR_EACH (ofport_up, hmap_node, &ofproto_iter->up.ports) { + if (&ofport->up == ofport_up) { + continue; + } + + if (!strcmp(netdev_get_dpif_port(ofport->up.netdev), + netdev_get_dpif_port(ofport_up->netdev))) { + return false; + } + } + } + + return true; } static int port_del(struct ofproto *ofproto_, uint16_t ofp_port) { struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_); - uint32_t odp_port = ofp_port_to_odp_port(ofproto, ofp_port); + struct ofport_dpif *ofport = get_ofp_port(ofproto, ofp_port); int error = 0; - if (odp_port != OFPP_NONE) { - error = dpif_port_del(ofproto->backer->dpif, odp_port); + if (!ofport) { + return 0; } - if (!error) { - struct ofport_dpif *ofport = get_ofp_port(ofproto, ofp_port); - if (ofport) { + + sset_find_and_delete(&ofproto->tnls, netdev_get_name(ofport->up.netdev)); + if (may_dpif_port_del(ofproto, ofport)) { + error = dpif_port_del(ofproto->backer->dpif, ofport->odp_port); + if (!error) { /* The caller is going to close ofport->up.netdev. If this is a * bonded port, then the bond is using that netdev, so remove it * from the bond. The client will need to reconfigure everything * after deleting ports, so then the slave will get re-added. */ bundle_remove(&ofport->up); + sset_find_and_delete(&ofproto->tnl_backers, + netdev_get_dpif_port(ofport->up.netdev)); } } return error; @@ -2982,6 +3076,7 @@ ofproto_update_local_port_stats(const struct ofproto *ofproto_, struct port_dump_state { uint32_t bucket; uint32_t offset; + bool tnls; }; static int @@ -2989,22 +3084,21 @@ port_dump_start(const struct ofproto *ofproto_ OVS_UNUSED, void **statep) { struct port_dump_state *state; - *statep = state = xmalloc(sizeof *state); - state->bucket = 0; - state->offset = 0; + *statep = state = xzalloc(sizeof *state); return 0; } static int -port_dump_next(const struct ofproto *ofproto_ OVS_UNUSED, void *state_, +port_dump_next(const struct ofproto *ofproto_, void *state_, struct ofproto_port *port) { struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_); struct port_dump_state *state = state_; + const struct sset *sset; struct sset_node *node; - while ((node = sset_at_position(&ofproto->ports, &state->bucket, - &state->offset))) { + sset = state->tnls ? &ofproto->tnls : &ofproto->ports; + while ((node = sset_at_position(sset, &state->bucket, &state->offset))) { int error; error = port_query_by_name(ofproto_, node->name, port); @@ -3013,6 +3107,13 @@ port_dump_next(const struct ofproto *ofproto_ OVS_UNUSED, void *state_, } } + if (!state->tnls) { + state->tnls = true; + state->bucket = 0; + state->offset = 0; + return port_dump_next(ofproto_, state_, port); + } + return EOF; } @@ -3416,6 +3517,10 @@ handle_flow_miss(struct flow_miss *miss, struct flow_miss_op *ops, * odp_flow_key_to_flow(). (This differs from the value returned in * flow->vlan_tci only for packets received on VLAN splinters.) * + * Similarly, this function also includes some logic to help with tunnels. It + * may modify 'flow' as necessary to make the tunneling implementation + * transparent to the upcall processing logic. + * * Returns 0 if successful, ENODEV if the parsed flow has no associated ofport, * or some other positive errno if there are other problems. */ static int @@ -3443,44 +3548,51 @@ ofproto_receive(const struct dpif_backer *backer, struct ofpbuf *packet, *odp_in_port = flow->in_port; } - port = odp_port_to_ofport(backer, flow->in_port); - if (!port) { - flow->in_port = OFPP_NONE; - error = ofproto ? ENODEV : 0; - goto exit; + if (tnl_port_should_receive(flow)) { + const struct ofport *ofport = tnl_port_receive(flow); + if (!ofport) { + error = ENODEV; + goto exit; + } + port = ofport_dpif_cast(ofport); + fitness = fitness == ODP_FIT_PERFECT ? ODP_FIT_TOO_MUCH : fitness; + } else { + port = odp_port_to_ofport(backer, flow->in_port); + if (!port) { + flow->in_port = OFPP_NONE; + error = ofproto ? ENODEV : 0; + goto exit; + } + + flow->in_port = port->up.ofp_port; + if (vsp_adjust_flow(ofproto_dpif_cast(port->up.ofproto), flow)) { + if (packet) { + /* Make the packet resemble the flow, so that it gets sent to + * an OpenFlow controller properly, so that it looks correct + * for sFlow, and so that flow_extract() will get the correct + * vlan_tci if it is called on 'packet'. + * + * The allocated space inside 'packet' probably also contains + * 'key', that is, both 'packet' and 'key' are probably part of + * a struct dpif_upcall (see the large comment on that + * structure definition), so pushing data on 'packet' is in + * general not a good idea since it could overwrite 'key' or + * free it as a side effect. However, it's OK in this special + * case because we know that 'packet' is inside a Netlink + * attribute: pushing 4 bytes will just overwrite the 4-byte + * "struct nlattr", which is fine since we don't need that + * header anymore. */ + eth_push_vlan(packet, flow->vlan_tci); + } + fitness = fitness == ODP_FIT_PERFECT ? ODP_FIT_TOO_MUCH : fitness; + } } + error = 0; if (ofproto) { *ofproto = ofproto_dpif_cast(port->up.ofproto); } - flow->in_port = port->up.ofp_port; - if (vsp_adjust_flow(ofproto_dpif_cast(port->up.ofproto), flow)) { - if (packet) { - /* Make the packet resemble the flow, so that it gets sent to an - * OpenFlow controller properly, so that it looks correct for - * sFlow, and so that flow_extract() will get the correct vlan_tci - * if it is called on 'packet'. - * - * The allocated space inside 'packet' probably also contains - * 'key', that is, both 'packet' and 'key' are probably part of a - * struct dpif_upcall (see the large comment on that structure - * definition), so pushing data on 'packet' is in general not a - * good idea since it could overwrite 'key' or free it as a side - * effect. However, it's OK in this special case because we know - * that 'packet' is inside a Netlink attribute: pushing 4 bytes - * will just overwrite the 4-byte "struct nlattr", which is fine - * since we don't need that header anymore. */ - eth_push_vlan(packet, flow->vlan_tci); - } - - /* Let the caller know that we can't reproduce 'key' from 'flow'. */ - if (fitness == ODP_FIT_PERFECT) { - fitness = ODP_FIT_TOO_MUCH; - } - } - error = 0; - exit: if (fitnessp) { *fitnessp = fitness; @@ -5399,6 +5511,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port, const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port); uint32_t odp_port = ofp_port_to_odp_port(ctx->ofproto, ofp_port); ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci; + ovs_be64 flow_tun_id = ctx->flow.tunnel.tun_id; uint8_t flow_nw_tos = ctx->flow.nw_tos; struct priority_to_dscp *pdscp; uint32_t out_port; @@ -5420,10 +5533,16 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port, ctx->flow.nw_tos |= pdscp->dscp; } - out_port = vsp_realdev_to_vlandev(ctx->ofproto, odp_port, - ctx->flow.vlan_tci); - if (out_port != odp_port) { - ctx->flow.vlan_tci = htons(0); + if (!tnl_port_send(&ofport->up, &ctx->flow, &odp_port)) { + out_port = vsp_realdev_to_vlandev(ctx->ofproto, odp_port, + ctx->flow.vlan_tci); + if (out_port != odp_port) { + ctx->flow.vlan_tci = htons(0); + } + } else { + out_port = odp_port; + commit_odp_tunnel_action(&ctx->flow, &ctx->base_flow, + ctx->odp_actions); } commit_odp_actions(&ctx->flow, &ctx->base_flow, ctx->odp_actions); nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_OUTPUT, out_port); @@ -5431,6 +5550,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port, ctx->sflow_odp_port = odp_port; ctx->sflow_n_outputs++; ctx->nf_output_iface = ofp_port; + ctx->flow.tunnel.tun_id = flow_tun_id; ctx->flow.vlan_tci = flow_vlan_tci; ctx->flow.nw_tos = flow_nw_tos; } diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index fd66d24..eea7fac 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -212,25 +212,6 @@ AT_CHECK([tail -1 stdout], [0], OVS_VSWITCHD_STOP AT_CLEANUP -AT_SETUP([ofproto-dpif - set_tunnel]) -OVS_VSWITCHD_START -ADD_OF_PORTS([br0], [1], [2], [3], [4], [5], [90]) -AT_DATA([flows.txt], [dnl -in_port=90 actions=resubmit:1,resubmit:2,resubmit:3,resubmit:4,resubmit:5 -in_port=1 actions=set_tunnel:1,output:1 -in_port=2 actions=set_tunnel:1,output:2 -in_port=3 actions=set_tunnel:2,set_tunnel:3,output:3 -in_port=4 actions=set_tunnel:4,set_tunnel:3,output:4 -in_port=5 actions=set_tunnel:5 -]) -AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) -AT_CHECK([ovs-appctl ofproto/trace br0 'tun_id(0x1),in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout]) -AT_CHECK([tail -1 stdout], [0], - [Datapath actions: set(tun_id(0x1)),1,2,set(tun_id(0x3)),3,4 -]) -OVS_VSWITCHD_STOP -AT_CLEANUP - AT_SETUP([ofproto-dpif - controller]) OVS_VSWITCHD_START([dnl add-port br0 p1 -- set Interface p1 type=dummy -- 1.7.9.5 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev