Up until now DPDK packets (DPBUF_DPDK) could call dp_packet_resize__() without any constraint, leading to call OVS_NOT_REACHED() and premature termination of the OvS process if one single packet didn't have enough tailroom or headroom space for a specific operation, such as pushing or popping a header.
To fix this, two functions are introduced, dp_packet_is_tailroom_avail() and dp_packet_is_headroom_avail(), which are now used to check if there's enough tailroom or headroom space, respectively, before proceeding with calling the dp_packet_prealloc_tailroom() and dp_packet_prealloc_headroom() functions, thus avoiding the call to dp_packet_resize__() and OVS_NOT_REACHED() for DPDK packets. Since put_uninit() and push_uninit() may now return NULL for DPDK packets, the places where these operations may be called from a DPDK packet now check for NULL and log a message where appropriate. For example, eth_push_vlan() in packets.c, now returns 1 if the push_uninit() operation on the packet fails. Later on, the caller, odp_execute_actions(), drops the packet upon checking that the return is different from 0 (error). CC: Anju Thomas <[email protected]> Reported-at: https://mail.openvswitch.org/pipermail/ovs-dev/2018-May/346649.html Signed-off-by: Tiago Lam <[email protected]> --- lib/dp-packet.c | 126 +++++++++++++++++++++++++++++++++++++++++------- lib/netdev-native-tnl.c | 24 +++++++-- lib/netdev-native-tnl.h | 6 +-- lib/netdev-provider.h | 2 +- lib/netdev.c | 16 ++++-- lib/odp-execute.c | 49 +++++++++++++++---- lib/packets.c | 38 +++++++++++++-- lib/packets.h | 8 +-- 8 files changed, 224 insertions(+), 45 deletions(-) diff --git a/lib/dp-packet.c b/lib/dp-packet.c index 443c225..058b653 100644 --- a/lib/dp-packet.c +++ b/lib/dp-packet.c @@ -239,6 +239,9 @@ dp_packet_resize__(struct dp_packet *b, size_t new_headroom, size_t new_tailroom switch (b->source) { case DPBUF_DPDK: + /* DPDK mbufs have a fixed layout, meaning they can't be resized per + * say. As a result, this operation shouldn't be called for DPBUF_DPDK + * packets */ OVS_NOT_REACHED(); case DPBUF_MALLOC: @@ -273,9 +276,27 @@ dp_packet_resize__(struct dp_packet *b, size_t new_headroom, size_t new_tailroom } } -/* Ensures that 'b' has room for at least 'size' bytes at its tail end, - * reallocating and copying its data if necessary. Its headroom, if any, is - * preserved. */ +static bool +dp_packet_is_tailroom_avail(struct dp_packet *b, size_t size) +{ +#ifdef DPDK_NETDEV + if (b->source == DPBUF_DPDK) { + if (size > dp_packet_tailroom(b)) { + return false; + } + + return true; + } +#endif + return true; +} + +/* For non-DPDK packets, ensures that 'b' has room for at least 'size' bytes at + * its tail end, reallocating and copying its data if necessary. Its headroom, + * if any, is preserved. + * + * For DPDK packets, no reallocation is performed. The caller is responsible + * for ensuring there's enough tailroom space available. */ void dp_packet_prealloc_tailroom(struct dp_packet *b, size_t size) { @@ -284,9 +305,27 @@ dp_packet_prealloc_tailroom(struct dp_packet *b, size_t size) } } -/* Ensures that 'b' has room for at least 'size' bytes at its head, - * reallocating and copying its data if necessary. Its tailroom, if any, is - * preserved. */ +static bool +dp_packet_is_headroom_avail(struct dp_packet *b, size_t size) +{ +#ifdef DPDK_NETDEV + if (b->source == DPBUF_DPDK) { + if (size > dp_packet_headroom(b)) { + return false; + } + + return true; + } +#endif + return true; +} + +/* For non-DPDK packets, ensures that 'b' has room for at least 'size' bytes at + * its head, reallocating and copying its data if necessary. Its tailroom, if + * any, is preserved. + * + * For DPDK packets, no reallocation is performed. The caller is responsible + * for ensuring there's enough headroom space available. */ void dp_packet_prealloc_headroom(struct dp_packet *b, size_t size) { @@ -315,11 +354,19 @@ dp_packet_shift(struct dp_packet *b, int delta) /* Appends 'size' bytes of data to the tail end of 'b', reallocating and * copying its data if necessary. Returns a pointer to the first byte of the - * new data, which is left uninitialized. */ + * new data, which is left uninitialized. + * + * For DPDK packets, no reallocation is performed and NULL may be returned if + * 'size' is bigger than the available tailroom space. */ void * dp_packet_put_uninit(struct dp_packet *b, size_t size) { void *p; + + if (!dp_packet_is_tailroom_avail(b, size)) { + return NULL; + } + dp_packet_prealloc_tailroom(b, size); p = dp_packet_tail(b); dp_packet_set_size(b, dp_packet_size(b) + size); @@ -328,22 +375,34 @@ dp_packet_put_uninit(struct dp_packet *b, size_t size) /* Appends 'size' zeroed bytes to the tail end of 'b'. Data in 'b' is * reallocated and copied if necessary. Returns a pointer to the first byte of - * the data's location in the dp_packet. */ + * the data's location in the dp_packet. + * + * For DPDK packets, no reallocation is performed and NULL may be returned if + * 'size' is bigger than the available tailroom space. */ void * dp_packet_put_zeros(struct dp_packet *b, size_t size) { void *dst = dp_packet_put_uninit(b, size); + if (!dst) { + return NULL; + } memset(dst, 0, size); return dst; } /* Appends the 'size' bytes of data in 'p' to the tail end of 'b'. Data in 'b' * is reallocated and copied if necessary. Returns a pointer to the first - * byte of the data's location in the dp_packet. */ + * byte of the data's location in the dp_packet. + * + * For DPDK packets, no reallocation is performed and NULL may be returned if + * 'size' is bigger than the available tailroom space. */ void * dp_packet_put(struct dp_packet *b, const void *p, size_t size) { void *dst = dp_packet_put_uninit(b, size); + if (!dst) { + return NULL; + } memcpy(dst, p, size); return dst; } @@ -370,7 +429,9 @@ dp_packet_put_hex(struct dp_packet *b, const char *s, size_t *n) return CONST_CAST(char *, s); } - dp_packet_put(b, &byte, 1); + if (!dp_packet_put(b, &byte, 1)) { + return NULL; + } s += 2; } } @@ -399,10 +460,19 @@ dp_packet_reserve_with_tailroom(struct dp_packet *b, size_t headroom, /* Prefixes 'size' bytes to the head end of 'b', reallocating and copying its * data if necessary. Returns a pointer to the first byte of the data's - * location in the dp_packet. The new data is left uninitialized. */ + * location in the dp_packet. The new data is left uninitialized. + * It may return NULL for DPDK packets, if 'size' is bigger than the available + * tailroom space + * + * For DPDK packets, no reallocation is performed and NULL may be returned if + * 'size' is bigger than the available headroom space. */ void * dp_packet_push_uninit(struct dp_packet *b, size_t size) { + if (!dp_packet_is_headroom_avail(b, size)) { + return NULL; + } + dp_packet_prealloc_headroom(b, size); dp_packet_set_data(b, (char*)dp_packet_data(b) - size); dp_packet_set_size(b, dp_packet_size(b) + size); @@ -411,22 +481,34 @@ dp_packet_push_uninit(struct dp_packet *b, size_t size) /* Prefixes 'size' zeroed bytes to the head end of 'b', reallocating and * copying its data if necessary. Returns a pointer to the first byte of the - * data's location in the dp_packet. */ + * data's location in the dp_packet. + * + * For DPDK packets, no reallocation is performed and NULL may be returned if + * 'size' is bigger than the available headroom space. */ void * dp_packet_push_zeros(struct dp_packet *b, size_t size) { void *dst = dp_packet_push_uninit(b, size); + if (!dst) { + return NULL; + } memset(dst, 0, size); return dst; } /* Copies the 'size' bytes starting at 'p' to the head end of 'b', reallocating * and copying its data if necessary. Returns a pointer to the first byte of - * the data's location in the dp_packet. */ + * the data's location in the dp_packet. + * + * For DPDK packets, no reallocation is performed and NULL may be returned if + * 'size' is bigger than the available headroom space. */ void * dp_packet_push(struct dp_packet *b, const void *p, size_t size) { void *dst = dp_packet_push_uninit(b, size); + if (!dst) { + return NULL; + } memcpy(dst, p, size); return dst; } @@ -463,12 +545,17 @@ dp_packet_adjust_layer_offset(uint16_t *offset, int increment) /* Adjust the size of the l2_5 portion of the dp_packet, updating the l2 * pointer and the layer offsets. The caller is responsible for - * modifying the contents. */ + * modifying the contents. + * + * For DPDK packets, NULL may be returned if 'increment' is bigger than the + * available headroom space. */ void * dp_packet_resize_l2_5(struct dp_packet *b, int increment) { if (increment >= 0) { - dp_packet_push_uninit(b, increment); + if (!dp_packet_push_uninit(b, increment)) { + return NULL; + } } else { dp_packet_pull(b, -increment); } @@ -482,11 +569,16 @@ dp_packet_resize_l2_5(struct dp_packet *b, int increment) /* Adjust the size of the l2 portion of the dp_packet, updating the l2 * pointer and the layer offsets. The caller is responsible for - * modifying the contents. */ + * modifying the contents. + * + * For DPDK packets, NULL may be returned if 'increment' is bigger than the + * available headroom space. */ void * dp_packet_resize_l2(struct dp_packet *b, int increment) { - dp_packet_resize_l2_5(b, increment); + if (!dp_packet_resize_l2_5(b, increment)) { + return NULL; + } dp_packet_adjust_layer_offset(&b->l2_5_ofs, increment); return dp_packet_data(b); } diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c index 56baaa2..d1e0f5f 100644 --- a/lib/netdev-native-tnl.c +++ b/lib/netdev-native-tnl.c @@ -152,6 +152,10 @@ netdev_tnl_push_ip_header(struct dp_packet *packet, struct ovs_16aligned_ip6_hdr *ip6; eth = dp_packet_push_uninit(packet, size); + if (!eth) { + VLOG_WARN_RL(&err_rl, "failed to push ip header"); + return NULL; + } *ip_tot_size = dp_packet_size(packet) - sizeof (struct eth_header); memcpy(eth, header, size); @@ -214,7 +218,7 @@ udp_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl, } -void +int netdev_tnl_push_udp_header(const struct netdev *netdev OVS_UNUSED, struct dp_packet *packet, const struct ovs_action_push_tnl *data) @@ -223,6 +227,9 @@ netdev_tnl_push_udp_header(const struct netdev *netdev OVS_UNUSED, int ip_tot_size; udp = netdev_tnl_push_ip_header(packet, data->header, data->header_len, &ip_tot_size); + if (!udp) { + return 1; + } /* set udp src port */ udp->udp_src = netdev_tnl_get_src_port(packet); @@ -243,6 +250,8 @@ netdev_tnl_push_udp_header(const struct netdev *netdev OVS_UNUSED, udp->udp_csum = htons(0xffff); } } + + return 0; } static void * @@ -435,7 +444,7 @@ err: return NULL; } -void +int netdev_gre_push_header(const struct netdev *netdev, struct dp_packet *packet, const struct ovs_action_push_tnl *data) @@ -446,6 +455,9 @@ netdev_gre_push_header(const struct netdev *netdev, int ip_tot_size; greh = netdev_tnl_push_ip_header(packet, data->header, data->header_len, &ip_tot_size); + if (!greh) { + return 1; + } if (greh->flags & htons(GRE_CSUM)) { ovs_be16 *csum_opt = (ovs_be16 *) (greh + 1); @@ -460,6 +472,8 @@ netdev_gre_push_header(const struct netdev *netdev, tnl_cfg = &dev->tnl_cfg; put_16aligned_be32(seq_opt, htonl(tnl_cfg->seqno++)); } + + return 0; } int @@ -588,7 +602,7 @@ err: return NULL; } -void +int netdev_erspan_push_header(const struct netdev *netdev, struct dp_packet *packet, const struct ovs_action_push_tnl *data) @@ -602,6 +616,9 @@ netdev_erspan_push_header(const struct netdev *netdev, greh = netdev_tnl_push_ip_header(packet, data->header, data->header_len, &ip_tot_size); + if (!greh) { + return 1; + } /* update GRE seqno */ tnl_cfg = &dev->tnl_cfg; @@ -614,6 +631,7 @@ netdev_erspan_push_header(const struct netdev *netdev, md2 = ALIGNED_CAST(struct erspan_md2 *, ersh + 1); put_16aligned_be32(&md2->timestamp, get_erspan_ts(ERSPAN_100US)); } + return 0; } int diff --git a/lib/netdev-native-tnl.h b/lib/netdev-native-tnl.h index 5dc0012..7c36229 100644 --- a/lib/netdev-native-tnl.h +++ b/lib/netdev-native-tnl.h @@ -33,7 +33,7 @@ netdev_gre_build_header(const struct netdev *netdev, struct ovs_action_push_tnl *data, const struct netdev_tnl_build_header_params *params); -void +int netdev_gre_push_header(const struct netdev *netdev, struct dp_packet *packet, const struct ovs_action_push_tnl *data); @@ -45,14 +45,14 @@ netdev_erspan_build_header(const struct netdev *netdev, struct ovs_action_push_tnl *data, const struct netdev_tnl_build_header_params *p); -void +int netdev_erspan_push_header(const struct netdev *netdev, struct dp_packet *packet, const struct ovs_action_push_tnl *data); struct dp_packet * netdev_erspan_pop_header(struct dp_packet *packet); -void +int netdev_tnl_push_udp_header(const struct netdev *netdev, struct dp_packet *packet, const struct ovs_action_push_tnl *data); diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h index 1a572f5..6c43a1f 100644 --- a/lib/netdev-provider.h +++ b/lib/netdev-provider.h @@ -314,7 +314,7 @@ struct netdev_class { * flow. Push header is called for packet to build header specific to * a packet on actual transmit. It uses partial header build by * build_header() which is passed as data. */ - void (*push_header)(const struct netdev *, + int (*push_header)(const struct netdev *, struct dp_packet *packet, const struct ovs_action_push_tnl *data); diff --git a/lib/netdev.c b/lib/netdev.c index 82ffeb9..277b76d 100644 --- a/lib/netdev.c +++ b/lib/netdev.c @@ -857,9 +857,19 @@ netdev_push_header(const struct netdev *netdev, const struct ovs_action_push_tnl *data) { struct dp_packet *packet; - DP_PACKET_BATCH_FOR_EACH (i, packet, batch) { - netdev->netdev_class->push_header(netdev, packet, data); - pkt_metadata_init(&packet->md, data->out_port); + bool drop = false; + const size_t cnt = dp_packet_batch_size(batch); + + size_t i; + DP_PACKET_BATCH_REFILL_FOR_EACH (i, cnt, packet, batch) { + drop = netdev->netdev_class->push_header(netdev, packet, data); + if (drop) { + dp_packet_delete(packet); + } else { + pkt_metadata_init(&packet->md, data->out_port); + /* Re-inject packet */ + dp_packet_batch_refill(batch, packet, i); + } } return 0; diff --git a/lib/odp-execute.c b/lib/odp-execute.c index 5831d1f..bfca79c 100644 --- a/lib/odp-execute.c +++ b/lib/odp-execute.c @@ -780,9 +780,17 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal, case OVS_ACTION_ATTR_PUSH_VLAN: { const struct ovs_action_push_vlan *vlan = nl_attr_get(a); - DP_PACKET_BATCH_FOR_EACH (i, packet, batch) { - eth_push_vlan(packet, vlan->vlan_tpid, vlan->vlan_tci); + size_t i; + const size_t num = dp_packet_batch_size(batch); + + DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) { + if (!eth_push_vlan(packet, vlan->vlan_tpid, vlan->vlan_tci)) { + dp_packet_batch_refill(batch, packet, i); + } else { + dp_packet_delete(packet); + } } + break; } @@ -795,9 +803,17 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal, case OVS_ACTION_ATTR_PUSH_MPLS: { const struct ovs_action_push_mpls *mpls = nl_attr_get(a); - DP_PACKET_BATCH_FOR_EACH (i, packet, batch) { - push_mpls(packet, mpls->mpls_ethertype, mpls->mpls_lse); + size_t i; + const size_t num = dp_packet_batch_size(batch); + + DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) { + if (!push_mpls(packet, mpls->mpls_ethertype, mpls->mpls_lse)) { + dp_packet_batch_refill(batch, packet, i); + } else { + dp_packet_delete(packet); + } } + break; } @@ -858,10 +874,18 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal, case OVS_ACTION_ATTR_PUSH_ETH: { const struct ovs_action_push_eth *eth = nl_attr_get(a); - DP_PACKET_BATCH_FOR_EACH (i, packet, batch) { - push_eth(packet, ð->addresses.eth_dst, - ð->addresses.eth_src); + size_t i; + const size_t num = dp_packet_batch_size(batch); + + DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) { + if (!push_eth(packet, ð->addresses.eth_dst, + ð->addresses.eth_src)) { + dp_packet_batch_refill(batch, packet, i); + } else { + dp_packet_delete(packet); + } } + break; } @@ -876,8 +900,15 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal, struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer); nsh_reset_ver_flags_ttl_len(nsh_hdr); odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr, NSH_HDR_MAX_LEN); - DP_PACKET_BATCH_FOR_EACH (i, packet, batch) { - push_nsh(packet, nsh_hdr); + size_t i; + const size_t num = dp_packet_batch_size(batch); + + DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) { + if (!push_nsh(packet, nsh_hdr)) { + dp_packet_batch_refill(batch, packet, i); + } else { + dp_packet_delete(packet); + } } break; } diff --git a/lib/packets.c b/lib/packets.c index 38bfb60..9f6282c 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -34,6 +34,10 @@ #include "odp-util.h" #include "dp-packet.h" #include "unaligned.h" +#include "openvswitch/vlog.h" + +VLOG_DEFINE_THIS_MODULE(packets); +static struct vlog_rate_limit err_rl = VLOG_RATE_LIMIT_INIT(60, 5); const struct in6_addr in6addr_exact = IN6ADDR_EXACT_INIT; const struct in6_addr in6addr_all_hosts = IN6ADDR_ALL_HOSTS_INIT; @@ -210,16 +214,22 @@ compose_rarp(struct dp_packet *b, const struct eth_addr eth_src) * packet. Ignores the CFI bit of 'tci' using 0 instead. * * Also adjusts the layer offsets accordingly. */ -void +int eth_push_vlan(struct dp_packet *packet, ovs_be16 tpid, ovs_be16 tci) { struct vlan_eth_header *veh; /* Insert new 802.1Q header. */ veh = dp_packet_resize_l2(packet, VLAN_HEADER_LEN); + if (!veh) { + VLOG_WARN_RL(&err_rl, "failed to push vlan header"); + return 1; + } memmove(veh, (char *)veh + VLAN_HEADER_LEN, 2 * ETH_ADDR_LEN); veh->veth_type = tpid; veh->veth_tci = tci & htons(~VLAN_CFI); + + return 0; } /* Removes outermost VLAN header (if any is present) from 'packet'. @@ -240,7 +250,7 @@ eth_pop_vlan(struct dp_packet *packet) } /* Push Ethernet header onto 'packet' assuming it is layer 3 */ -void +int push_eth(struct dp_packet *packet, const struct eth_addr *dst, const struct eth_addr *src) { @@ -248,10 +258,16 @@ push_eth(struct dp_packet *packet, const struct eth_addr *dst, ovs_assert(packet->packet_type != htonl(PT_ETH)); eh = dp_packet_resize_l2(packet, ETH_HEADER_LEN); + if (!eh) { + VLOG_WARN_RL(&err_rl, "failed to push eth header"); + return 1; + } eh->eth_dst = *dst; eh->eth_src = *src; eh->eth_type = pt_ns_type_be(packet->packet_type); packet->packet_type = htonl(PT_ETH); + + return 0; } /* Removes Ethernet header, including VLAN header, from 'packet'. @@ -369,14 +385,14 @@ set_mpls_lse(struct dp_packet *packet, ovs_be32 mpls_lse) /* Push MPLS label stack entry 'lse' onto 'packet' as the outermost MPLS * header. If 'packet' does not already have any MPLS labels, then its * Ethertype is changed to 'ethtype' (which must be an MPLS Ethertype). */ -void +int push_mpls(struct dp_packet *packet, ovs_be16 ethtype, ovs_be32 lse) { char * header; size_t len; if (!eth_type_mpls(ethtype)) { - return; + return 0; } if (!is_mpls(packet)) { @@ -389,8 +405,14 @@ push_mpls(struct dp_packet *packet, ovs_be16 ethtype, ovs_be32 lse) /* Push new MPLS shim header onto packet. */ len = packet->l2_5_ofs; header = dp_packet_resize_l2_5(packet, MPLS_HLEN); + if (!header) { + VLOG_WARN_RL(&err_rl, "failed to push mpls header"); + return 1; + } memmove(header, header + MPLS_HLEN, len); memcpy(header + len, &lse, sizeof lse); + + return 0; } /* If 'packet' is an MPLS packet, removes its outermost MPLS label stack entry. @@ -414,7 +436,7 @@ pop_mpls(struct dp_packet *packet, ovs_be16 ethtype) } } -void +int push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src) { struct nsh_hdr *nsh; @@ -439,11 +461,17 @@ push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src) } nsh = (struct nsh_hdr *) dp_packet_push_uninit(packet, length); + if (!nsh) { + VLOG_WARN_RL(&err_rl, "failed to push nsh header"); + return 1; + } memcpy(nsh, nsh_hdr_src, length); nsh->next_proto = next_proto; packet->packet_type = htonl(PT_NSH); dp_packet_reset_offsets(packet); packet->l3_ofs = 0; + + return 0; } bool diff --git a/lib/packets.h b/lib/packets.h index 7645a9d..d8c0795 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -330,7 +330,7 @@ bool eth_addr_from_string(const char *, struct eth_addr *); void compose_rarp(struct dp_packet *, const struct eth_addr); -void eth_push_vlan(struct dp_packet *, ovs_be16 tpid, ovs_be16 tci); +int eth_push_vlan(struct dp_packet *, ovs_be16 tpid, ovs_be16 tci); void eth_pop_vlan(struct dp_packet *); const char *eth_from_hex(const char *hex, struct dp_packet **packetp); @@ -338,7 +338,7 @@ void eth_format_masked(const struct eth_addr ea, const struct eth_addr *mask, struct ds *s); void set_mpls_lse(struct dp_packet *, ovs_be32 label); -void push_mpls(struct dp_packet *packet, ovs_be16 ethtype, ovs_be32 lse); +int push_mpls(struct dp_packet *packet, ovs_be16 ethtype, ovs_be32 lse); void pop_mpls(struct dp_packet *, ovs_be16 ethtype); void set_mpls_lse_ttl(ovs_be32 *lse, uint8_t ttl); @@ -438,11 +438,11 @@ struct eth_header { }; BUILD_ASSERT_DECL(ETH_HEADER_LEN == sizeof(struct eth_header)); -void push_eth(struct dp_packet *packet, const struct eth_addr *dst, +int push_eth(struct dp_packet *packet, const struct eth_addr *dst, const struct eth_addr *src); void pop_eth(struct dp_packet *packet); -void push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src); +int push_nsh(struct dp_packet *packet, const struct nsh_hdr *nsh_hdr_src); bool pop_nsh(struct dp_packet *packet); #define LLC_DSAP_SNAP 0xaa -- 2.7.4 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
