If VLAN acceleration is used when the kernel receives a packet then the outer-most VLAN tag will not be present in the packet when it is received by netdev-linux. Rather, it will be present in auxdata.
This patch uses recvmsg() instead of recv() to read auxdata for each packet and if the vlan_tid is set then it is added to the packet. Adding the vlan_tid makes use of headroom available in the buffer parameter of rx_recv. Co-authored-by: Ben Pfaff <b...@nicira.com> Signed-off-by: Simon Horman <ho...@verge.net.au> -- v5 * As provided by Ben Pfaff - Include local copy of struct tpacket_auxdata and related #defines to avoid depending on the kernel headers used for the build. * Include update of documentation of rx_recv which was previously included in an earlier patch of the series. v4 * Rebase * Make aux variable in netdev_linux_rx_recv_sock() const * Use TP_STATUS_VLAN_VALID to allow vlan 0 to be read from auxdata * Use TP_STATUS_VLAN_TPID_VALID allow the VLAN tpid to be read from auxdata v3 * Remove unnecessary cast * On success return 0 and increment buffer->size * Limit to reading one VLAN from auxdata v2 * Updated to use eth_push_vlan() to efficiently push the VLAN if headroom is available. This is possible now there is a struct ofpbuf * to work with. * Various cleanups --- include/sparse/sys/socket.h | 1 + lib/netdev-linux.c | 160 +++++++++++++++++++++++++++++++++++++++----- lib/netdev-provider.h | 10 +++ 3 files changed, 154 insertions(+), 17 deletions(-) diff --git a/include/sparse/sys/socket.h b/include/sparse/sys/socket.h index 75ee43c..3212bf4 100644 --- a/include/sparse/sys/socket.h +++ b/include/sparse/sys/socket.h @@ -87,6 +87,7 @@ enum { }; enum { + SOL_PACKET, SOL_SOCKET }; diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c index 106c18a..4bd4bfc 100644 --- a/lib/netdev-linux.c +++ b/lib/netdev-linux.c @@ -20,11 +20,11 @@ #include <errno.h> #include <fcntl.h> -#include <arpa/inet.h> #include <inttypes.h> #include <linux/filter.h> #include <linux/gen_stats.h> #include <linux/if_ether.h> +#include <linux/if_packet.h> #include <linux/if_tun.h> #include <linux/types.h> #include <linux/ethtool.h> @@ -37,10 +37,8 @@ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> -#include <netpacket/packet.h> #include <net/if.h> #include <net/if_arp.h> -#include <net/if_packet.h> #include <net/route.h> #include <netinet/in.h> #include <poll.h> @@ -109,6 +107,33 @@ COVERAGE_DEFINE(netdev_set_ethtool); #define TC_RTAB_SIZE 1024 #endif +/* Linux 2.6.21 introduced struct tpacket_auxdata. + * Linux 2.6.27 added the tp_vlan_tci member. + * Linux 3.0 defined TP_STATUS_VLAN_VALID. + * Linux 3.13 repurposed a padding member for tp_vlan_tpid and defined + * TP_STATUS_VLAN_TPID_VALID. + * + * With all this churn it's easiest to unconditionally define a replacement + * structure that has everything we want. + */ +#ifndef TP_STATUS_VLAN_VALID +#define TP_STATUS_VLAN_VALID (1 << 4) +#endif +#ifndef TP_STATUS_VLAN_TPID_VALID +#define TP_STATUS_VLAN_TPID_VALID (1 << 6) +#endif +#undef tpacket_auxdata +#define tpacket_auxdata rpl_tpacket_auxdata +struct tpacket_auxdata { + uint32_t tp_status; + uint32_t tp_len; + uint32_t tp_snaplen; + uint16_t tp_mac; + uint16_t tp_net; + uint16_t tp_vlan_tci; + uint16_t tp_vlan_tpid; +}; + enum { VALID_IFINDEX = 1 << 0, VALID_ETHERADDR = 1 << 1, @@ -763,7 +788,7 @@ netdev_linux_rx_construct(struct netdev_rx *rx_) rx->fd = netdev->tap_fd; } else { struct sockaddr_ll sll; - int ifindex; + int ifindex, val; /* Result of tcpdump -dd inbound */ static const struct sock_filter filt[] = { { 0x28, 0, 0, 0xfffff004 }, /* ldh [0] */ @@ -783,6 +808,16 @@ netdev_linux_rx_construct(struct netdev_rx *rx_) goto error; } + val = 1; + error = setsockopt(rx->fd, SOL_PACKET, PACKET_AUXDATA, + &val, sizeof val); + if (error) { + error = errno; + VLOG_ERR("%s: failed to mark socket for auxdata (%s)", + netdev_get_name(netdev_), ovs_strerror(error)); + goto error; + } + /* Set non-blocking mode. */ error = set_nonblocking(rx->fd); if (error) { @@ -799,7 +834,7 @@ netdev_linux_rx_construct(struct netdev_rx *rx_) memset(&sll, 0, sizeof sll); sll.sll_family = AF_PACKET; sll.sll_ifindex = ifindex; - sll.sll_protocol = (OVS_FORCE unsigned short int) htons(ETH_P_ALL); + sll.sll_protocol = htons(ETH_P_ALL); if (bind(rx->fd, (struct sockaddr *) &sll, sizeof sll) < 0) { error = errno; VLOG_ERR("%s: failed to bind raw socket (%s)", @@ -847,31 +882,122 @@ netdev_linux_rx_dealloc(struct netdev_rx *rx_) free(rx); } +static ovs_be16 +auxdata_to_vlan_tpid(const struct tpacket_auxdata *aux) +{ + if (aux->tp_status & TP_STATUS_VLAN_TPID_VALID) { + return htons(aux->tp_vlan_tpid); + } else { + return htons(ETH_TYPE_VLAN); + } +} + +static bool +auxdata_has_vlan_tci(const struct tpacket_auxdata *aux) +{ + return aux->tp_vlan_tci || aux->tp_status & TP_STATUS_VLAN_VALID; +} + static int -netdev_linux_rx_recv(struct netdev_rx *rx_, struct ofpbuf *buffer) +netdev_linux_rx_recv_sock(int fd, struct ofpbuf *buffer) { - struct netdev_rx_linux *rx = netdev_rx_linux_cast(rx_); + size_t size; ssize_t retval; - size_t size = ofpbuf_tailroom(buffer); + struct iovec iov; + struct cmsghdr *cmsg; + union { + struct cmsghdr cmsg; + char buffer[CMSG_SPACE(sizeof(struct tpacket_auxdata))]; + } cmsg_buffer; + struct msghdr msgh; + + /* Reserve headroom for a single VLAN tag */ + ofpbuf_reserve(buffer, VLAN_HEADER_LEN); + size = ofpbuf_tailroom(buffer); + + iov.iov_base = buffer->data; + iov.iov_len = size; + msgh.msg_name = NULL; + msgh.msg_namelen = 0; + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = &cmsg_buffer; + msgh.msg_controllen = sizeof cmsg_buffer; + msgh.msg_flags = 0; do { - retval = (rx->is_tap - ? read(rx->fd, buffer->data, size) - : recv(rx->fd, buffer->data, size, MSG_TRUNC)); + retval = recvmsg(fd, &msgh, MSG_TRUNC); } while (retval < 0 && errno == EINTR); if (retval < 0) { - if (errno != EAGAIN) { - VLOG_WARN_RL(&rl, "error receiving Ethernet packet on %s: %s", - ovs_strerror(errno), netdev_rx_get_name(rx_)); + return errno; + } else if (retval > size) { + return EMSGSIZE; + } + + buffer->size += retval; + + for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg; cmsg = CMSG_NXTHDR(&msgh, cmsg)) { + const struct tpacket_auxdata *aux; + + if (cmsg->cmsg_level != SOL_PACKET + || cmsg->cmsg_type != PACKET_AUXDATA + || cmsg->cmsg_len < CMSG_LEN(sizeof(struct tpacket_auxdata))) { + continue; + } + + aux = (struct tpacket_auxdata *)(void *)CMSG_DATA(cmsg); + if (auxdata_has_vlan_tci(aux)) { + if (retval < ETH_ADDR_LEN) { + return EINVAL; + } + + eth_push_vlan(buffer, auxdata_to_vlan_tpid(aux), + htons(aux->tp_vlan_tci)); + break; } + } + + return 0; +} + +static int +netdev_linux_rx_recv_tap(int fd, struct ofpbuf *buffer) +{ + ssize_t retval; + size_t size = ofpbuf_tailroom(buffer); + + do { + retval = read(fd, buffer->data, size); + } while (retval < 0 && errno == EINTR); + + if (retval < 0) { return errno; } else if (retval > size) { return EMSGSIZE; - } else { - buffer->size += retval; - return 0; } + + buffer->size += retval; + return 0; +} + +static int +netdev_linux_rx_recv(struct netdev_rx *rx_, struct ofpbuf *buffer) +{ + struct netdev_rx_linux *rx = netdev_rx_linux_cast(rx_); + int retval; + + retval = (rx->is_tap + ? netdev_linux_rx_recv_tap(rx->fd, buffer) + : netdev_linux_rx_recv_sock(rx->fd, buffer)); + if (retval) { + if (retval != EAGAIN && retval != EMSGSIZE) { + VLOG_WARN_RL(&rl, "error receiving Ethernet packet on %s: %s", + ovs_strerror(errno), netdev_rx_get_name(rx_)); + } + } + + return retval; } static void diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h index 1dcc1f4..673d3ab 100644 --- a/lib/netdev-provider.h +++ b/lib/netdev-provider.h @@ -643,6 +643,16 @@ struct netdev_class { * Must return EMSGSIZE, and discard the packet, if the received packet * is longer than 'ofpbuf_tailroom(buffer)'. * + * Implementations may make use of VLAN_HEADER_LEN bytes of tailroom to + * add a VLAN header which is obtained out-of-band to the packet. If + * this occurs then VLAN_HEADER_LEN bytes of tailroom will no longer be + * available for the packet, otherwise it may be used for the packet + * itself. + * + * It is advised that the tailroom of 'buffer' should be + * VLAN_HEADER_LEN bytes longer than the MTU to allow space for an + * out-of-band VLAN header to be added to the packet. + * * This function may be set to null if it would always return EOPNOTSUPP * anyhow. */ int (*rx_recv)(struct netdev_rx *rx, struct ofpbuf *buffer); -- 1.8.4 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev