Message ID | 1526690988-29912-33-git-send-email-gvrose8192@gmail.com |
---|---|
State | Accepted |
Headers | show |
Series | Add ERSPAN support | expand |
On Fri, May 18, 2018 at 5:49 PM, Greg Rose <gvrose8192@gmail.com> wrote: > The patch add supports for flow-based erspan options. > The erspan_ver, erspan_idx, erspan_dir, and erspan_hwid can be > set as "flow" so that its value is set by the openflow rule, > instead of statically configured at port creation time. > > Signed-off-by: William Tu <u9012063@gmail.com> > --- > > V2 - A portion of this patch from lib/match.c was folded in a prior > commit "userspace: add erspan tunnel support" as per Ben's > review comments. > --- Thanks for folding the review comments. LGTM Regards, William > lib/netdev-native-tnl.c | 38 +++++++++++++-- > lib/netdev-vport.c | 120 +++++++++++++++++++++++++++++++++-------------- > lib/netdev.h | 5 ++ > ofproto/tunnel.c | 11 +++-- > tests/tunnel-push-pop.at | 10 +++- > tests/tunnel.at | 68 +++++++++++++++++++++++---- > 6 files changed, 198 insertions(+), 54 deletions(-) > > diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c > index c70811e..c97491e 100644 > --- a/lib/netdev-native-tnl.c > +++ b/lib/netdev-native-tnl.c > @@ -628,6 +628,7 @@ netdev_erspan_build_header(const struct netdev *netdev, > struct erspan_base_hdr *ersh; > unsigned int hlen; > uint32_t tun_id; > + int erspan_ver; > uint16_t sid; > > /* XXX: RCUfy tnl_cfg. */ > @@ -645,7 +646,15 @@ netdev_erspan_build_header(const struct netdev *netdev, > sid = (uint16_t) tun_id; > } > > - if (tnl_cfg->erspan_ver == 1) { > + if (tnl_cfg->erspan_ver_flow) { > + erspan_ver = params->flow->tunnel.erspan_ver; > + } else { > + erspan_ver = tnl_cfg->erspan_ver; > + } > + > + if (erspan_ver == 1) { > + ovs_be32 *index; > + > greh->protocol = htons(ETH_TYPE_ERSPAN1); > greh->flags = htons(GRE_SEQ); > ersh->ver = 1; > @@ -654,19 +663,38 @@ netdev_erspan_build_header(const struct netdev *netdev, > put_16aligned_be32(ALIGNED_CAST(ovs_16aligned_be32 *, ersh + 1), > htonl(tnl_cfg->erspan_idx)); > > + index = (ovs_be32 *)(ersh + 1); > + > + if (tnl_cfg->erspan_idx_flow) { > + *index = htonl(params->flow->tunnel.erspan_idx); > + } else { > + *index = htonl(tnl_cfg->erspan_idx); > + } > + > hlen = ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V1_MDSIZE; > - } else if (tnl_cfg->erspan_ver == 2) { > + } else if (erspan_ver == 2) { > + struct erspan_md2 *md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1); > + > greh->protocol = htons(ETH_TYPE_ERSPAN2); > greh->flags = htons(GRE_SEQ); > ersh->ver = 2; > set_sid(ersh, sid); > > - struct erspan_md2 *md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1); > md2->sgt = 0; /* security group tag */ > md2->gra = 0; > put_16aligned_be32(&md2->timestamp, 0); > - set_hwid(md2, tnl_cfg->erspan_hwid); > - md2->dir = tnl_cfg->erspan_dir; > + > + if (tnl_cfg->erspan_hwid_flow) { > + set_hwid(md2, params->flow->tunnel.erspan_hwid); > + } else { > + set_hwid(md2, tnl_cfg->erspan_hwid); > + } > + > + if (tnl_cfg->erspan_dir_flow) { > + md2->dir = params->flow->tunnel.erspan_dir; > + } else { > + md2->dir = tnl_cfg->erspan_dir; > + } > > hlen = ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V2_MDSIZE; > } else { > diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c > index 805f130..1dae7e0 100644 > --- a/lib/netdev-vport.c > +++ b/lib/netdev-vport.c > @@ -545,36 +545,63 @@ set_tunnel_config(struct netdev *dev_, const struct smap *args, char **errp) > tnl_cfg.egress_pkt_mark = strtoul(node->value, NULL, 10); > tnl_cfg.set_egress_pkt_mark = true; > } else if (!strcmp(node->key, "erspan_idx")) { > - tnl_cfg.erspan_idx = strtol(node->value, NULL, 16); > - if (tnl_cfg.erspan_idx & ~ERSPAN_IDX_MASK) { > - ds_put_format(&errors, "%s: invalid erspan index: %s\n", > - name, node->value); > - err = EINVAL; > - goto out; > + if (!strcmp(node->value, "flow")) { > + tnl_cfg.erspan_idx_flow = true; > + } else { > + tnl_cfg.erspan_idx_flow = false; > + tnl_cfg.erspan_idx = strtol(node->value, NULL, 16); > + > + if (tnl_cfg.erspan_idx & ~ERSPAN_IDX_MASK) { > + ds_put_format(&errors, "%s: invalid erspan index: %s\n", > + name, node->value); > + err = EINVAL; > + goto out; > + } > } > } else if (!strcmp(node->key, "erspan_ver")) { > - tnl_cfg.erspan_ver = atoi(node->value); > - if (tnl_cfg.erspan_ver != 1 && tnl_cfg.erspan_ver != 2) { > - ds_put_format(&errors, "%s: invalid erspan version: %s\n", > - name, node->value); > - err = EINVAL; > - goto out; > + if (!strcmp(node->value, "flow")) { > + tnl_cfg.erspan_ver_flow = true; > + tnl_cfg.erspan_idx_flow = true; > + tnl_cfg.erspan_dir_flow = true; > + tnl_cfg.erspan_hwid_flow = true; > + } else { > + tnl_cfg.erspan_ver_flow = false; > + tnl_cfg.erspan_ver = atoi(node->value); > + > + if (tnl_cfg.erspan_ver != 1 && tnl_cfg.erspan_ver != 2) { > + ds_put_format(&errors, "%s: invalid erspan version: %s\n", > + name, node->value); > + err = EINVAL; > + goto out; > + } > } > } else if (!strcmp(node->key, "erspan_dir")) { > - tnl_cfg.erspan_dir = atoi(node->value); > - if (tnl_cfg.erspan_dir != 0 && tnl_cfg.erspan_dir != 1) { > - ds_put_format(&errors, "%s: invalid erspan direction: %s\n", > - name, node->value); > - err = EINVAL; > - goto out; > + if (!strcmp(node->value, "flow")) { > + tnl_cfg.erspan_dir_flow = true; > + } else { > + tnl_cfg.erspan_dir_flow = false; > + tnl_cfg.erspan_dir = atoi(node->value); > + > + if (tnl_cfg.erspan_dir != 0 && tnl_cfg.erspan_dir != 1) { > + ds_put_format(&errors, "%s: invalid erspan direction: %s\n", > + name, node->value); > + err = EINVAL; > + goto out; > + } > } > } else if (!strcmp(node->key, "erspan_hwid")) { > - tnl_cfg.erspan_hwid = strtol(node->value, NULL, 16); > - if (tnl_cfg.erspan_hwid & ~(ERSPAN_HWID_MASK >> 4)) { > - ds_put_format(&errors, "%s: invalid erspan hardware ID: %s\n", > - name, node->value); > - err = EINVAL; > - goto out; > + if (!strcmp(node->value, "flow")) { > + tnl_cfg.erspan_hwid_flow = true; > + } else { > + tnl_cfg.erspan_hwid_flow = false; > + tnl_cfg.erspan_hwid = strtol(node->value, NULL, 16); > + > + if (tnl_cfg.erspan_hwid & ~(ERSPAN_HWID_MASK >> 4)) { > + ds_put_format(&errors, "%s: invalid erspan hardware ID: %s\n", > + name, node->value); > + err = EINVAL; > + goto out; > + } > } > } else { > ds_put_format(&errors, "%s: unknown %s argument '%s'\n", name, > @@ -767,17 +794,40 @@ get_tunnel_config(const struct netdev *dev, struct smap *args) > "%"PRIu32, tnl_cfg.egress_pkt_mark); > } > > - if (tnl_cfg.erspan_idx) { > - smap_add_format(args, "erspan_idx", "0x%x", tnl_cfg.erspan_idx); > - } > - if (tnl_cfg.erspan_ver) { > - smap_add_format(args, "erspan_ver", "%d", tnl_cfg.erspan_ver); > - } > - if (tnl_cfg.erspan_dir) { > - smap_add_format(args, "erspan_dir", "%d", tnl_cfg.erspan_dir); > - } > - if (tnl_cfg.erspan_hwid) { > - smap_add_format(args, "erspan_hwid", "0x%x", tnl_cfg.erspan_hwid); > + if (!strcmp("erspan", type) || !strcmp("ip6erspan", type)) { > + if (tnl_cfg.erspan_ver_flow) { > + /* since version number is not determined, > + * assume print all other as flow > + */ > + smap_add(args, "erspan_ver", "flow"); > + smap_add(args, "erspan_idx", "flow"); > + smap_add(args, "erspan_dir", "flow"); > + smap_add(args, "erspan_hwid", "flow"); > + } else { > + smap_add_format(args, "erspan_ver", "%d", tnl_cfg.erspan_ver); > + > + if (tnl_cfg.erspan_ver == 1) { > + if (tnl_cfg.erspan_idx_flow) { > + smap_add(args, "erspan_idx", "flow"); > + } else { > + smap_add_format(args, "erspan_idx", "0x%x", > + tnl_cfg.erspan_idx); > + } > + } else if (tnl_cfg.erspan_ver == 2) { > + if (tnl_cfg.erspan_dir_flow) { > + smap_add(args, "erspan_dir", "flow"); > + } else { > + smap_add_format(args, "erspan_dir", "%d", > + tnl_cfg.erspan_dir); > + } > + if (tnl_cfg.erspan_hwid_flow) { > + smap_add(args, "erspan_hwid", "flow"); > + } else { > + smap_add_format(args, "erspan_hwid", "0x%x", > + tnl_cfg.erspan_hwid); > + } > + } > + } > } > > return 0; > diff --git a/lib/netdev.h b/lib/netdev.h > index 0c74dfe..71ffdab 100644 > --- a/lib/netdev.h > +++ b/lib/netdev.h > @@ -134,6 +134,11 @@ struct netdev_tunnel_config { > uint8_t erspan_ver; > uint8_t erspan_dir; > uint8_t erspan_hwid; > + > + bool erspan_ver_flow; > + bool erspan_idx_flow; > + bool erspan_dir_flow; > + bool erspan_hwid_flow; > }; > > void netdev_run(void); > diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c > index 2fd6ffb..03f0ab7 100644 > --- a/ofproto/tunnel.c > +++ b/ofproto/tunnel.c > @@ -474,16 +474,19 @@ tnl_port_send(const struct ofport_dpif *ofport, struct flow *flow, > wc->masks.pkt_mark = UINT32_MAX; > } > > - if (cfg->erspan_ver) { > + if (!cfg->erspan_ver_flow) { > flow->tunnel.erspan_ver = cfg->erspan_ver; > } > - if (cfg->erspan_idx) { > + > + if (!cfg->erspan_idx_flow) { > flow->tunnel.erspan_idx = cfg->erspan_idx; > } > - if (cfg->erspan_dir) { > + > + if (!cfg->erspan_dir_flow) { > flow->tunnel.erspan_dir = cfg->erspan_dir; > } > - if (cfg->erspan_hwid) { > + > + if (!cfg->erspan_hwid_flow) { > flow->tunnel.erspan_hwid = cfg->erspan_hwid; > } > > diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at > index a3b284a..b2269df 100644 > --- a/tests/tunnel-push-pop.at > +++ b/tests/tunnel-push-pop.at > @@ -9,6 +9,12 @@ AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=erspan \ > -- add-port int-br t2 -- set Interface t2 type=erspan \ > options:remote_ip=1.1.2.92 options:key=567 options:erspan_ver=2 \ > options:erspan_dir=1 options:erspan_hwid=0x7 ofport_request=3\ > + -- add-port int-br t3 -- set Interface t3 type=erspan \ > + options:remote_ip=flow options:erspan_ver=2 options:key=456 \ > + options:erspan_hwid=flow options:erspan_dir=flow ofport_request=4\ > + -- add-port int-br t4 -- set Interface t4 type=erspan \ > + options:remote_ip=flow options:erspan_ver=2 options:key=56 \ > + options:erspan_ver=flow ofport_request=5\ > ], [0]) > > AT_CHECK([ovs-appctl dpif/show], [0], [dnl > @@ -20,6 +26,8 @@ dummy@ovs-dummy: hit:0 missed:0 > int-br 65534/2: (dummy-internal) > t1 2/3: (erspan: erspan_idx=0x3, erspan_ver=1, key=123, remote_ip=1.1.2.92) > t2 3/3: (erspan: erspan_dir=1, erspan_hwid=0x7, erspan_ver=2, key=567, remote_ip=1.1.2.92) > + t3 4/3: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_ver=2, key=456, remote_ip=flow) > + t4 5/3: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_idx=flow, erspan_ver=flow, key=56, remote_ip=flow) > ]) > > dnl First setup dummy interface IP address, then add the route > @@ -69,7 +77,7 @@ AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl > > AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl > Listening ports: > -erspan_sys (3) ref_cnt=2 > +erspan_sys (3) ref_cnt=4 > ]) > > dnl Check ERSPAN v1 tunnel push > diff --git a/tests/tunnel.at b/tests/tunnel.at > index 315453d..2bc004c 100644 > --- a/tests/tunnel.at > +++ b/tests/tunnel.at > @@ -408,11 +408,42 @@ AT_CLEANUP > > AT_SETUP([tunnel - ERSPAN]) > OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=erspan \ > - options:remote_ip=1.1.1.1 ofport_request=1]) > + options:remote_ip=1.1.1.1 options:key=1 options:erspan_ver=1 \ > + options:erspan_idx=0x0 ofport_request=1 \ > + -- add-port br0 p2 -- set Interface p2 type=erspan \ > + options:remote_ip=1.1.1.1 ofport_request=2 \ > + options:key=flow options:erspan_ver=1 options:erspan_idx=flow \ > + -- add-port br0 p3 -- set Interface p3 type=erspan \ > + options:remote_ip=1.1.1.1 ofport_request=3 \ > + options:key=10 options:erspan_ver=2 options:erspan_dir=flow \ > + options:erspan_hwid=flow \ > + -- add-port br0 p4 -- set Interface p4 type=erspan \ > + options:remote_ip=1.2.3.4 ofport_request=4 \ > + options:key=flow options:erspan_ver=flow\ > + ]) > > AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl > br0 65534/100: (dummy-internal) > - p1 1/1: (erspan: remote_ip=1.1.1.1) > + p1 1/1: (erspan: erspan_idx=0x0, erspan_ver=1, key=1, remote_ip=1.1.1.1) > + p2 2/1: (erspan: erspan_idx=flow, erspan_ver=1, key=flow, remote_ip=1.1.1.1) > + p3 3/1: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_ver=2, key=10, remote_ip=1.1.1.1) > + p4 4/1: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_idx=flow, erspan_ver=flow, key=flow, remote_ip=1.2.3.4) > +]) > + > +dnl Check ERSPAN v1 flow-based tunnel push > +AT_CHECK([ovs-ofctl add-flow br0 "in_port=1, actions=set_tunnel:11,set_field:0x1->tun_erspan_idx,2"]) > + > +dnl Check ERSPAN v2 flow-based tunnel push > +AT_CHECK([ovs-ofctl add-flow br0 "in_port=2, actions=set_field:1->tun_erspan_dir,set_field:0x0->tun_erspan_hwid,3"]) > + > +AT_CHECK([ovs-ofctl add-flow br0 "in_port=3, actions=set_field:2->tun_erspan_ver,set_field:1->tun_erspan_dir,set_field:0x0->tun_erspan_hwid,4"]) > + > +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], > +[0], [dnl > +NXST_FLOW reply: > + in_port=1 actions=set_tunnel:0xb,set_field:0x1->tun_erspan_idx,output:2 > + in_port=2 actions=set_field:1->tun_erspan_dir,set_field:0->tun_erspan_hwid,output:3 > + in_port=3 actions=set_field:2->tun_erspan_ver,set_field:1->tun_erspan_dir,set_field:0->tun_erspan_hwid,output:4 > ]) > > OVS_VSWITCHD_STOP > @@ -523,28 +554,28 @@ AT_CHECK([tail -1 stdout], [0], > dnl receive packet from ERSPAN port with v1 metadata > AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x1,src=1.1.1.1,dst=2.2.2.2,ttl=64,erspan(ver=1,idx=0x7),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) > AT_CHECK([tail -2 stdout], [0], > - [Megaflow: recirc_id=0,eth,ip,tun_id=0x1,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=1,erspan_idx=0x7,tun_flags=+df-csum+key,in_port=3,nw_frag=no > + [Megaflow: recirc_id=0,eth,ip,tun_id=0x1,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=1,tun_erspan_idx=0x7,tun_flags=+df-csum+key,in_port=3,nw_frag=no > Datapath actions: 3 > ]) > > dnl receive packet from ERSPAN port with wrong v1 metadata > AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x1,src=1.1.1.1,dst=2.2.2.2,ttl=64,erspan(ver=1,idx=0xabcd),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) > AT_CHECK([tail -2 stdout], [0], > - [Megaflow: recirc_id=0,eth,ip,tun_id=0x1,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=1,erspan_idx=0xabcd,tun_flags=+df-csum+key,in_port=3,nw_frag=no > + [Megaflow: recirc_id=0,eth,ip,tun_id=0x1,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=1,tun_erspan_idx=0xabcd,tun_flags=+df-csum+key,in_port=3,nw_frag=no > Datapath actions: drop > ]) > > dnl receive packet from ERSPAN port with v2 metadata > AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x2,src=1.1.1.2,dst=2.2.2.2,ttl=64,erspan(ver=2,dir=1,hwid=0x7),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) > AT_CHECK([tail -2 stdout], [0], > - [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=2,erspan_dir=1,erspan_hwid=0x1,tun_flags=+df-csum+key,in_port=4,nw_frag=no > + [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=2,tun_erspan_dir=1,tun_erspan_hwid=0x1,tun_flags=+df-csum+key,in_port=4,nw_frag=no > Datapath actions: 2 > ]) > > dnl receive packet from ERSPAN port with wrong v2 metadata > AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x2,src=1.1.1.2,dst=2.2.2.2,ttl=64,erspan(ver=2,dir=0,hwid=0x17),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) > AT_CHECK([tail -2 stdout], [0], > - [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=2,erspan_dir=0,erspan_hwid=0x1,tun_flags=+df-csum+key,in_port=4,nw_frag=no > + [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=2,tun_erspan_dir=0,tun_erspan_hwid=0x1,tun_flags=+df-csum+key,in_port=4,nw_frag=no > Datapath actions: drop > ]) > > @@ -557,17 +588,36 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], > NXST_FLOW reply: > in_port=1 actions=output:3 > in_port=2 actions=output:4 > - erspan_ver=1,erspan_idx=0x7,in_port=3 actions=output:1 > - erspan_ver=2,in_port=4 actions=output:2 > + tun_erspan_ver=1,tun_erspan_idx=0x7,in_port=3 actions=output:1 > + tun_erspan_ver=2,in_port=4 actions=output:2 > ]) > > dnl this time it won't drop > AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x2,src=1.1.1.2,dst=2.2.2.2,ttl=64,erspan(ver=2,dir=0,hwid=0x17),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) > AT_CHECK([tail -2 stdout], [0], > - [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=2,tun_flags=+df-csum+key,in_port=4,nw_frag=no > + [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=2,tun_flags=+df-csum+key,in_port=4,nw_frag=no > Datapath actions: 2 > ]) > > +dnl flow-based erspan_idx options > +AT_CHECK([ovs-vsctl add-port br0 p5 -- set Interface p5 type=erspan \ > + options:remote_ip=1.1.1.2 ofport_request=5 \ > + options:key=flow options:erspan_ver=1 options:erspan_idx=flow]) > + > +AT_CHECK([ovs-ofctl del-flows br0]) > +AT_CHECK([ovs-ofctl add-flow br0 "in_port=1, actions=set_tunnel:11,set_field:0x7->tun_erspan_idx,5"]) > + > +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], > +[0], [dnl > +NXST_FLOW reply: > + in_port=1 actions=set_tunnel:0xb,set_field:0x7->tun_erspan_idx,output:5 > +]) > + > +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(3),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(tunnel(tun_id=0xb,dst=1.1.1.2,ttl=64,erspan(ver=1,idx=0x7),flags(df|key))),1 > +]) > + > OVS_VSWITCHD_STOP > AT_CLEANUP > > -- > 1.8.3.1 >
diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c index c70811e..c97491e 100644 --- a/lib/netdev-native-tnl.c +++ b/lib/netdev-native-tnl.c @@ -628,6 +628,7 @@ netdev_erspan_build_header(const struct netdev *netdev, struct erspan_base_hdr *ersh; unsigned int hlen; uint32_t tun_id; + int erspan_ver; uint16_t sid; /* XXX: RCUfy tnl_cfg. */ @@ -645,7 +646,15 @@ netdev_erspan_build_header(const struct netdev *netdev, sid = (uint16_t) tun_id; } - if (tnl_cfg->erspan_ver == 1) { + if (tnl_cfg->erspan_ver_flow) { + erspan_ver = params->flow->tunnel.erspan_ver; + } else { + erspan_ver = tnl_cfg->erspan_ver; + } + + if (erspan_ver == 1) { + ovs_be32 *index; + greh->protocol = htons(ETH_TYPE_ERSPAN1); greh->flags = htons(GRE_SEQ); ersh->ver = 1; @@ -654,19 +663,38 @@ netdev_erspan_build_header(const struct netdev *netdev, put_16aligned_be32(ALIGNED_CAST(ovs_16aligned_be32 *, ersh + 1), htonl(tnl_cfg->erspan_idx)); + index = (ovs_be32 *)(ersh + 1); + + if (tnl_cfg->erspan_idx_flow) { + *index = htonl(params->flow->tunnel.erspan_idx); + } else { + *index = htonl(tnl_cfg->erspan_idx); + } + hlen = ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V1_MDSIZE; - } else if (tnl_cfg->erspan_ver == 2) { + } else if (erspan_ver == 2) { + struct erspan_md2 *md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1); + greh->protocol = htons(ETH_TYPE_ERSPAN2); greh->flags = htons(GRE_SEQ); ersh->ver = 2; set_sid(ersh, sid); - struct erspan_md2 *md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1); md2->sgt = 0; /* security group tag */ md2->gra = 0; put_16aligned_be32(&md2->timestamp, 0); - set_hwid(md2, tnl_cfg->erspan_hwid); - md2->dir = tnl_cfg->erspan_dir; + + if (tnl_cfg->erspan_hwid_flow) { + set_hwid(md2, params->flow->tunnel.erspan_hwid); + } else { + set_hwid(md2, tnl_cfg->erspan_hwid); + } + + if (tnl_cfg->erspan_dir_flow) { + md2->dir = params->flow->tunnel.erspan_dir; + } else { + md2->dir = tnl_cfg->erspan_dir; + } hlen = ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V2_MDSIZE; } else { diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c index 805f130..1dae7e0 100644 --- a/lib/netdev-vport.c +++ b/lib/netdev-vport.c @@ -545,36 +545,63 @@ set_tunnel_config(struct netdev *dev_, const struct smap *args, char **errp) tnl_cfg.egress_pkt_mark = strtoul(node->value, NULL, 10); tnl_cfg.set_egress_pkt_mark = true; } else if (!strcmp(node->key, "erspan_idx")) { - tnl_cfg.erspan_idx = strtol(node->value, NULL, 16); - if (tnl_cfg.erspan_idx & ~ERSPAN_IDX_MASK) { - ds_put_format(&errors, "%s: invalid erspan index: %s\n", - name, node->value); - err = EINVAL; - goto out; + if (!strcmp(node->value, "flow")) { + tnl_cfg.erspan_idx_flow = true; + } else { + tnl_cfg.erspan_idx_flow = false; + tnl_cfg.erspan_idx = strtol(node->value, NULL, 16); + + if (tnl_cfg.erspan_idx & ~ERSPAN_IDX_MASK) { + ds_put_format(&errors, "%s: invalid erspan index: %s\n", + name, node->value); + err = EINVAL; + goto out; + } } } else if (!strcmp(node->key, "erspan_ver")) { - tnl_cfg.erspan_ver = atoi(node->value); - if (tnl_cfg.erspan_ver != 1 && tnl_cfg.erspan_ver != 2) { - ds_put_format(&errors, "%s: invalid erspan version: %s\n", - name, node->value); - err = EINVAL; - goto out; + if (!strcmp(node->value, "flow")) { + tnl_cfg.erspan_ver_flow = true; + tnl_cfg.erspan_idx_flow = true; + tnl_cfg.erspan_dir_flow = true; + tnl_cfg.erspan_hwid_flow = true; + } else { + tnl_cfg.erspan_ver_flow = false; + tnl_cfg.erspan_ver = atoi(node->value); + + if (tnl_cfg.erspan_ver != 1 && tnl_cfg.erspan_ver != 2) { + ds_put_format(&errors, "%s: invalid erspan version: %s\n", + name, node->value); + err = EINVAL; + goto out; + } } } else if (!strcmp(node->key, "erspan_dir")) { - tnl_cfg.erspan_dir = atoi(node->value); - if (tnl_cfg.erspan_dir != 0 && tnl_cfg.erspan_dir != 1) { - ds_put_format(&errors, "%s: invalid erspan direction: %s\n", - name, node->value); - err = EINVAL; - goto out; + if (!strcmp(node->value, "flow")) { + tnl_cfg.erspan_dir_flow = true; + } else { + tnl_cfg.erspan_dir_flow = false; + tnl_cfg.erspan_dir = atoi(node->value); + + if (tnl_cfg.erspan_dir != 0 && tnl_cfg.erspan_dir != 1) { + ds_put_format(&errors, "%s: invalid erspan direction: %s\n", + name, node->value); + err = EINVAL; + goto out; + } } } else if (!strcmp(node->key, "erspan_hwid")) { - tnl_cfg.erspan_hwid = strtol(node->value, NULL, 16); - if (tnl_cfg.erspan_hwid & ~(ERSPAN_HWID_MASK >> 4)) { - ds_put_format(&errors, "%s: invalid erspan hardware ID: %s\n", - name, node->value); - err = EINVAL; - goto out; + if (!strcmp(node->value, "flow")) { + tnl_cfg.erspan_hwid_flow = true; + } else { + tnl_cfg.erspan_hwid_flow = false; + tnl_cfg.erspan_hwid = strtol(node->value, NULL, 16); + + if (tnl_cfg.erspan_hwid & ~(ERSPAN_HWID_MASK >> 4)) { + ds_put_format(&errors, "%s: invalid erspan hardware ID: %s\n", + name, node->value); + err = EINVAL; + goto out; + } } } else { ds_put_format(&errors, "%s: unknown %s argument '%s'\n", name, @@ -767,17 +794,40 @@ get_tunnel_config(const struct netdev *dev, struct smap *args) "%"PRIu32, tnl_cfg.egress_pkt_mark); } - if (tnl_cfg.erspan_idx) { - smap_add_format(args, "erspan_idx", "0x%x", tnl_cfg.erspan_idx); - } - if (tnl_cfg.erspan_ver) { - smap_add_format(args, "erspan_ver", "%d", tnl_cfg.erspan_ver); - } - if (tnl_cfg.erspan_dir) { - smap_add_format(args, "erspan_dir", "%d", tnl_cfg.erspan_dir); - } - if (tnl_cfg.erspan_hwid) { - smap_add_format(args, "erspan_hwid", "0x%x", tnl_cfg.erspan_hwid); + if (!strcmp("erspan", type) || !strcmp("ip6erspan", type)) { + if (tnl_cfg.erspan_ver_flow) { + /* since version number is not determined, + * assume print all other as flow + */ + smap_add(args, "erspan_ver", "flow"); + smap_add(args, "erspan_idx", "flow"); + smap_add(args, "erspan_dir", "flow"); + smap_add(args, "erspan_hwid", "flow"); + } else { + smap_add_format(args, "erspan_ver", "%d", tnl_cfg.erspan_ver); + + if (tnl_cfg.erspan_ver == 1) { + if (tnl_cfg.erspan_idx_flow) { + smap_add(args, "erspan_idx", "flow"); + } else { + smap_add_format(args, "erspan_idx", "0x%x", + tnl_cfg.erspan_idx); + } + } else if (tnl_cfg.erspan_ver == 2) { + if (tnl_cfg.erspan_dir_flow) { + smap_add(args, "erspan_dir", "flow"); + } else { + smap_add_format(args, "erspan_dir", "%d", + tnl_cfg.erspan_dir); + } + if (tnl_cfg.erspan_hwid_flow) { + smap_add(args, "erspan_hwid", "flow"); + } else { + smap_add_format(args, "erspan_hwid", "0x%x", + tnl_cfg.erspan_hwid); + } + } + } } return 0; diff --git a/lib/netdev.h b/lib/netdev.h index 0c74dfe..71ffdab 100644 --- a/lib/netdev.h +++ b/lib/netdev.h @@ -134,6 +134,11 @@ struct netdev_tunnel_config { uint8_t erspan_ver; uint8_t erspan_dir; uint8_t erspan_hwid; + + bool erspan_ver_flow; + bool erspan_idx_flow; + bool erspan_dir_flow; + bool erspan_hwid_flow; }; void netdev_run(void); diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c index 2fd6ffb..03f0ab7 100644 --- a/ofproto/tunnel.c +++ b/ofproto/tunnel.c @@ -474,16 +474,19 @@ tnl_port_send(const struct ofport_dpif *ofport, struct flow *flow, wc->masks.pkt_mark = UINT32_MAX; } - if (cfg->erspan_ver) { + if (!cfg->erspan_ver_flow) { flow->tunnel.erspan_ver = cfg->erspan_ver; } - if (cfg->erspan_idx) { + + if (!cfg->erspan_idx_flow) { flow->tunnel.erspan_idx = cfg->erspan_idx; } - if (cfg->erspan_dir) { + + if (!cfg->erspan_dir_flow) { flow->tunnel.erspan_dir = cfg->erspan_dir; } - if (cfg->erspan_hwid) { + + if (!cfg->erspan_hwid_flow) { flow->tunnel.erspan_hwid = cfg->erspan_hwid; } diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at index a3b284a..b2269df 100644 --- a/tests/tunnel-push-pop.at +++ b/tests/tunnel-push-pop.at @@ -9,6 +9,12 @@ AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=erspan \ -- add-port int-br t2 -- set Interface t2 type=erspan \ options:remote_ip=1.1.2.92 options:key=567 options:erspan_ver=2 \ options:erspan_dir=1 options:erspan_hwid=0x7 ofport_request=3\ + -- add-port int-br t3 -- set Interface t3 type=erspan \ + options:remote_ip=flow options:erspan_ver=2 options:key=456 \ + options:erspan_hwid=flow options:erspan_dir=flow ofport_request=4\ + -- add-port int-br t4 -- set Interface t4 type=erspan \ + options:remote_ip=flow options:erspan_ver=2 options:key=56 \ + options:erspan_ver=flow ofport_request=5\ ], [0]) AT_CHECK([ovs-appctl dpif/show], [0], [dnl @@ -20,6 +26,8 @@ dummy@ovs-dummy: hit:0 missed:0 int-br 65534/2: (dummy-internal) t1 2/3: (erspan: erspan_idx=0x3, erspan_ver=1, key=123, remote_ip=1.1.2.92) t2 3/3: (erspan: erspan_dir=1, erspan_hwid=0x7, erspan_ver=2, key=567, remote_ip=1.1.2.92) + t3 4/3: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_ver=2, key=456, remote_ip=flow) + t4 5/3: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_idx=flow, erspan_ver=flow, key=56, remote_ip=flow) ]) dnl First setup dummy interface IP address, then add the route @@ -69,7 +77,7 @@ AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl Listening ports: -erspan_sys (3) ref_cnt=2 +erspan_sys (3) ref_cnt=4 ]) dnl Check ERSPAN v1 tunnel push diff --git a/tests/tunnel.at b/tests/tunnel.at index 315453d..2bc004c 100644 --- a/tests/tunnel.at +++ b/tests/tunnel.at @@ -408,11 +408,42 @@ AT_CLEANUP AT_SETUP([tunnel - ERSPAN]) OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=erspan \ - options:remote_ip=1.1.1.1 ofport_request=1]) + options:remote_ip=1.1.1.1 options:key=1 options:erspan_ver=1 \ + options:erspan_idx=0x0 ofport_request=1 \ + -- add-port br0 p2 -- set Interface p2 type=erspan \ + options:remote_ip=1.1.1.1 ofport_request=2 \ + options:key=flow options:erspan_ver=1 options:erspan_idx=flow \ + -- add-port br0 p3 -- set Interface p3 type=erspan \ + options:remote_ip=1.1.1.1 ofport_request=3 \ + options:key=10 options:erspan_ver=2 options:erspan_dir=flow \ + options:erspan_hwid=flow \ + -- add-port br0 p4 -- set Interface p4 type=erspan \ + options:remote_ip=1.2.3.4 ofport_request=4 \ + options:key=flow options:erspan_ver=flow\ + ]) AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl br0 65534/100: (dummy-internal) - p1 1/1: (erspan: remote_ip=1.1.1.1) + p1 1/1: (erspan: erspan_idx=0x0, erspan_ver=1, key=1, remote_ip=1.1.1.1) + p2 2/1: (erspan: erspan_idx=flow, erspan_ver=1, key=flow, remote_ip=1.1.1.1) + p3 3/1: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_ver=2, key=10, remote_ip=1.1.1.1) + p4 4/1: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_idx=flow, erspan_ver=flow, key=flow, remote_ip=1.2.3.4) +]) + +dnl Check ERSPAN v1 flow-based tunnel push +AT_CHECK([ovs-ofctl add-flow br0 "in_port=1, actions=set_tunnel:11,set_field:0x1->tun_erspan_idx,2"]) + +dnl Check ERSPAN v2 flow-based tunnel push +AT_CHECK([ovs-ofctl add-flow br0 "in_port=2, actions=set_field:1->tun_erspan_dir,set_field:0x0->tun_erspan_hwid,3"]) + +AT_CHECK([ovs-ofctl add-flow br0 "in_port=3, actions=set_field:2->tun_erspan_ver,set_field:1->tun_erspan_dir,set_field:0x0->tun_erspan_hwid,4"]) + +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], +[0], [dnl +NXST_FLOW reply: + in_port=1 actions=set_tunnel:0xb,set_field:0x1->tun_erspan_idx,output:2 + in_port=2 actions=set_field:1->tun_erspan_dir,set_field:0->tun_erspan_hwid,output:3 + in_port=3 actions=set_field:2->tun_erspan_ver,set_field:1->tun_erspan_dir,set_field:0->tun_erspan_hwid,output:4 ]) OVS_VSWITCHD_STOP @@ -523,28 +554,28 @@ AT_CHECK([tail -1 stdout], [0], dnl receive packet from ERSPAN port with v1 metadata AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x1,src=1.1.1.1,dst=2.2.2.2,ttl=64,erspan(ver=1,idx=0x7),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) AT_CHECK([tail -2 stdout], [0], - [Megaflow: recirc_id=0,eth,ip,tun_id=0x1,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=1,erspan_idx=0x7,tun_flags=+df-csum+key,in_port=3,nw_frag=no + [Megaflow: recirc_id=0,eth,ip,tun_id=0x1,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=1,tun_erspan_idx=0x7,tun_flags=+df-csum+key,in_port=3,nw_frag=no Datapath actions: 3 ]) dnl receive packet from ERSPAN port with wrong v1 metadata AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x1,src=1.1.1.1,dst=2.2.2.2,ttl=64,erspan(ver=1,idx=0xabcd),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) AT_CHECK([tail -2 stdout], [0], - [Megaflow: recirc_id=0,eth,ip,tun_id=0x1,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=1,erspan_idx=0xabcd,tun_flags=+df-csum+key,in_port=3,nw_frag=no + [Megaflow: recirc_id=0,eth,ip,tun_id=0x1,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=1,tun_erspan_idx=0xabcd,tun_flags=+df-csum+key,in_port=3,nw_frag=no Datapath actions: drop ]) dnl receive packet from ERSPAN port with v2 metadata AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x2,src=1.1.1.2,dst=2.2.2.2,ttl=64,erspan(ver=2,dir=1,hwid=0x7),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) AT_CHECK([tail -2 stdout], [0], - [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=2,erspan_dir=1,erspan_hwid=0x1,tun_flags=+df-csum+key,in_port=4,nw_frag=no + [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=2,tun_erspan_dir=1,tun_erspan_hwid=0x1,tun_flags=+df-csum+key,in_port=4,nw_frag=no Datapath actions: 2 ]) dnl receive packet from ERSPAN port with wrong v2 metadata AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x2,src=1.1.1.2,dst=2.2.2.2,ttl=64,erspan(ver=2,dir=0,hwid=0x17),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) AT_CHECK([tail -2 stdout], [0], - [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=2,erspan_dir=0,erspan_hwid=0x1,tun_flags=+df-csum+key,in_port=4,nw_frag=no + [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=2,tun_erspan_dir=0,tun_erspan_hwid=0x1,tun_flags=+df-csum+key,in_port=4,nw_frag=no Datapath actions: drop ]) @@ -557,17 +588,36 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], NXST_FLOW reply: in_port=1 actions=output:3 in_port=2 actions=output:4 - erspan_ver=1,erspan_idx=0x7,in_port=3 actions=output:1 - erspan_ver=2,in_port=4 actions=output:2 + tun_erspan_ver=1,tun_erspan_idx=0x7,in_port=3 actions=output:1 + tun_erspan_ver=2,in_port=4 actions=output:2 ]) dnl this time it won't drop AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'recirc_id(0),tunnel(tun_id=0x2,src=1.1.1.2,dst=2.2.2.2,ttl=64,erspan(ver=2,dir=0,hwid=0x17),flags(df|key)),in_port(1),skb_mark(0),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(frag=no)'], [0], [stdout]) AT_CHECK([tail -2 stdout], [0], - [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,erspan_ver=2,tun_flags=+df-csum+key,in_port=4,nw_frag=no + [Megaflow: recirc_id=0,eth,ip,tun_id=0x2,tun_src=1.1.1.2,tun_dst=2.2.2.2,tun_tos=0,tun_erspan_ver=2,tun_flags=+df-csum+key,in_port=4,nw_frag=no Datapath actions: 2 ]) +dnl flow-based erspan_idx options +AT_CHECK([ovs-vsctl add-port br0 p5 -- set Interface p5 type=erspan \ + options:remote_ip=1.1.1.2 ofport_request=5 \ + options:key=flow options:erspan_ver=1 options:erspan_idx=flow]) + +AT_CHECK([ovs-ofctl del-flows br0]) +AT_CHECK([ovs-ofctl add-flow br0 "in_port=1, actions=set_tunnel:11,set_field:0x7->tun_erspan_idx,5"]) + +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip], +[0], [dnl +NXST_FLOW reply: + in_port=1 actions=set_tunnel:0xb,set_field:0x7->tun_erspan_idx,output:5 +]) + +AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(3),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(tunnel(tun_id=0xb,dst=1.1.1.2,ttl=64,erspan(ver=1,idx=0x7),flags(df|key))),1 +]) + OVS_VSWITCHD_STOP AT_CLEANUP
The patch add supports for flow-based erspan options. The erspan_ver, erspan_idx, erspan_dir, and erspan_hwid can be set as "flow" so that its value is set by the openflow rule, instead of statically configured at port creation time. Signed-off-by: William Tu <u9012063@gmail.com> --- V2 - A portion of this patch from lib/match.c was folded in a prior commit "userspace: add erspan tunnel support" as per Ben's review comments. --- lib/netdev-native-tnl.c | 38 +++++++++++++-- lib/netdev-vport.c | 120 +++++++++++++++++++++++++++++++++-------------- lib/netdev.h | 5 ++ ofproto/tunnel.c | 11 +++-- tests/tunnel-push-pop.at | 10 +++- tests/tunnel.at | 68 +++++++++++++++++++++++---- 6 files changed, 198 insertions(+), 54 deletions(-)