Merge branch 'udp-gro'
Paolo Abeni says:
====================
udp: implement GRO support
This series implements GRO support for UDP sockets, as the RX counterpart
of commit bec1f6f697
("udp: generate gso with UDP_SEGMENT").
The core functionality is implemented by the second patch, introducing a new
sockopt to enable UDP_GRO, while patch 3 implements support for passing the
segment size to the user space via a new cmsg.
UDP GRO performs a socket lookup for each ingress packets and aggregate datagram
directed to UDP GRO enabled sockets with constant l4 tuple.
UDP GRO packets can land on non GRO-enabled sockets, e.g. due to iptables NAT
rules, and that could potentially confuse existing applications.
The solution adopted here is to de-segment the GRO packet before enqueuing
as needed. Since we must cope with packet reinsertion after de-segmentation,
the relevant code is factored-out in ipv4 and ipv6 specific helpers and exposed
to UDP usage.
While the current code can probably be improved, this safeguard ,implemented in
the patches 4-7, allows future enachements to enable UDP GSO offload on more
virtual devices eventually even on forwarded packets.
The last 4 for patches implement some performance and functional self-tests,
re-using the existing udpgso infrastructure. The problematic scenario described
above is explicitly tested.
This revision of the series try to address the feedback provided by Willem and
Subash on previous iteration.
====================
Acked-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
cab6949bf7
|
@ -49,7 +49,13 @@ struct udp_sock {
|
||||||
unsigned int corkflag; /* Cork is required */
|
unsigned int corkflag; /* Cork is required */
|
||||||
__u8 encap_type; /* Is this an Encapsulation socket? */
|
__u8 encap_type; /* Is this an Encapsulation socket? */
|
||||||
unsigned char no_check6_tx:1,/* Send zero UDP6 checksums on TX? */
|
unsigned char no_check6_tx:1,/* Send zero UDP6 checksums on TX? */
|
||||||
no_check6_rx:1;/* Allow zero UDP6 checksums on RX? */
|
no_check6_rx:1,/* Allow zero UDP6 checksums on RX? */
|
||||||
|
encap_enabled:1, /* This socket enabled encap
|
||||||
|
* processing; UDP tunnels and
|
||||||
|
* different encapsulation layer set
|
||||||
|
* this
|
||||||
|
*/
|
||||||
|
gro_enabled:1; /* Can accept GRO packets */
|
||||||
/*
|
/*
|
||||||
* Following member retains the information to create a UDP header
|
* Following member retains the information to create a UDP header
|
||||||
* when the socket is uncorked.
|
* when the socket is uncorked.
|
||||||
|
@ -115,6 +121,23 @@ static inline bool udp_get_no_check6_rx(struct sock *sk)
|
||||||
return udp_sk(sk)->no_check6_rx;
|
return udp_sk(sk)->no_check6_rx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void udp_cmsg_recv(struct msghdr *msg, struct sock *sk,
|
||||||
|
struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
int gso_size;
|
||||||
|
|
||||||
|
if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) {
|
||||||
|
gso_size = skb_shinfo(skb)->gso_size;
|
||||||
|
put_cmsg(msg, SOL_UDP, UDP_GRO, sizeof(gso_size), &gso_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool udp_unexpected_gso(struct sock *sk, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
return !udp_sk(sk)->gro_enabled && skb_is_gso(skb) &&
|
||||||
|
skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4;
|
||||||
|
}
|
||||||
|
|
||||||
#define udp_portaddr_for_each_entry(__sk, list) \
|
#define udp_portaddr_for_each_entry(__sk, list) \
|
||||||
hlist_for_each_entry(__sk, list, __sk_common.skc_portaddr_node)
|
hlist_for_each_entry(__sk, list, __sk_common.skc_portaddr_node)
|
||||||
|
|
||||||
|
|
|
@ -155,6 +155,7 @@ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
|
||||||
void ip_list_rcv(struct list_head *head, struct packet_type *pt,
|
void ip_list_rcv(struct list_head *head, struct packet_type *pt,
|
||||||
struct net_device *orig_dev);
|
struct net_device *orig_dev);
|
||||||
int ip_local_deliver(struct sk_buff *skb);
|
int ip_local_deliver(struct sk_buff *skb);
|
||||||
|
void ip_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int proto);
|
||||||
int ip_mr_input(struct sk_buff *skb);
|
int ip_mr_input(struct sk_buff *skb);
|
||||||
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb);
|
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb);
|
||||||
int ip_mc_output(struct net *net, struct sock *sk, struct sk_buff *skb);
|
int ip_mc_output(struct net *net, struct sock *sk, struct sk_buff *skb);
|
||||||
|
|
|
@ -975,6 +975,8 @@ int ip6_output(struct net *net, struct sock *sk, struct sk_buff *skb);
|
||||||
int ip6_forward(struct sk_buff *skb);
|
int ip6_forward(struct sk_buff *skb);
|
||||||
int ip6_input(struct sk_buff *skb);
|
int ip6_input(struct sk_buff *skb);
|
||||||
int ip6_mc_input(struct sk_buff *skb);
|
int ip6_mc_input(struct sk_buff *skb);
|
||||||
|
void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
|
||||||
|
bool have_final);
|
||||||
|
|
||||||
int __ip6_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);
|
int __ip6_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);
|
||||||
int ip6_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);
|
int ip6_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);
|
||||||
|
|
|
@ -417,17 +417,24 @@ static inline int copy_linear_skb(struct sk_buff *skb, int len, int off,
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_IPV6)
|
#if IS_ENABLED(CONFIG_IPV6)
|
||||||
#define __UDPX_INC_STATS(sk, field) \
|
#define __UDPX_MIB(sk, ipv4) \
|
||||||
do { \
|
({ \
|
||||||
if ((sk)->sk_family == AF_INET) \
|
ipv4 ? (IS_UDPLITE(sk) ? sock_net(sk)->mib.udplite_statistics : \
|
||||||
__UDP_INC_STATS(sock_net(sk), field, 0); \
|
sock_net(sk)->mib.udp_statistics) : \
|
||||||
else \
|
(IS_UDPLITE(sk) ? sock_net(sk)->mib.udplite_stats_in6 : \
|
||||||
__UDP6_INC_STATS(sock_net(sk), field, 0); \
|
sock_net(sk)->mib.udp_stats_in6); \
|
||||||
} while (0)
|
})
|
||||||
#else
|
#else
|
||||||
#define __UDPX_INC_STATS(sk, field) __UDP_INC_STATS(sock_net(sk), field, 0)
|
#define __UDPX_MIB(sk, ipv4) \
|
||||||
|
({ \
|
||||||
|
IS_UDPLITE(sk) ? sock_net(sk)->mib.udplite_statistics : \
|
||||||
|
sock_net(sk)->mib.udp_statistics; \
|
||||||
|
})
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define __UDPX_INC_STATS(sk, field) \
|
||||||
|
__SNMP_INC_STATS(__UDPX_MIB(sk, (sk)->sk_family == AF_INET), field)
|
||||||
|
|
||||||
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
||||||
struct udp_seq_afinfo {
|
struct udp_seq_afinfo {
|
||||||
sa_family_t family;
|
sa_family_t family;
|
||||||
|
@ -461,4 +468,26 @@ DECLARE_STATIC_KEY_FALSE(udpv6_encap_needed_key);
|
||||||
void udpv6_encap_enable(void);
|
void udpv6_encap_enable(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
|
||||||
|
struct sk_buff *skb, bool ipv4)
|
||||||
|
{
|
||||||
|
struct sk_buff *segs;
|
||||||
|
|
||||||
|
/* the GSO CB lays after the UDP one, no need to save and restore any
|
||||||
|
* CB fragment
|
||||||
|
*/
|
||||||
|
segs = __skb_gso_segment(skb, NETIF_F_SG, false);
|
||||||
|
if (unlikely(IS_ERR_OR_NULL(segs))) {
|
||||||
|
int segs_nr = skb_shinfo(skb)->gso_segs;
|
||||||
|
|
||||||
|
atomic_add(segs_nr, &sk->sk_drops);
|
||||||
|
SNMP_ADD_STATS(__UDPX_MIB(sk, ipv4), UDP_MIB_INERRORS, segs_nr);
|
||||||
|
kfree_skb(skb);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
consume_skb(skb);
|
||||||
|
return segs;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* _UDP_H */
|
#endif /* _UDP_H */
|
||||||
|
|
|
@ -165,6 +165,12 @@ static inline int udp_tunnel_handle_offloads(struct sk_buff *skb, bool udp_csum)
|
||||||
|
|
||||||
static inline void udp_tunnel_encap_enable(struct socket *sock)
|
static inline void udp_tunnel_encap_enable(struct socket *sock)
|
||||||
{
|
{
|
||||||
|
struct udp_sock *up = udp_sk(sock->sk);
|
||||||
|
|
||||||
|
if (up->encap_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
up->encap_enabled = 1;
|
||||||
#if IS_ENABLED(CONFIG_IPV6)
|
#if IS_ENABLED(CONFIG_IPV6)
|
||||||
if (sock->sk->sk_family == PF_INET6)
|
if (sock->sk->sk_family == PF_INET6)
|
||||||
ipv6_stub->udpv6_encap_enable();
|
ipv6_stub->udpv6_encap_enable();
|
||||||
|
|
|
@ -33,6 +33,7 @@ struct udphdr {
|
||||||
#define UDP_NO_CHECK6_TX 101 /* Disable sending checksum for UDP6X */
|
#define UDP_NO_CHECK6_TX 101 /* Disable sending checksum for UDP6X */
|
||||||
#define UDP_NO_CHECK6_RX 102 /* Disable accpeting checksum for UDP6 */
|
#define UDP_NO_CHECK6_RX 102 /* Disable accpeting checksum for UDP6 */
|
||||||
#define UDP_SEGMENT 103 /* Set GSO segmentation size */
|
#define UDP_SEGMENT 103 /* Set GSO segmentation size */
|
||||||
|
#define UDP_GRO 104 /* This socket can receive UDP GRO packets */
|
||||||
|
|
||||||
/* UDP encapsulation types */
|
/* UDP encapsulation types */
|
||||||
#define UDP_ENCAP_ESPINUDP_NON_IKE 1 /* draft-ietf-ipsec-nat-t-ike-00/01 */
|
#define UDP_ENCAP_ESPINUDP_NON_IKE 1 /* draft-ietf-ipsec-nat-t-ike-00/01 */
|
||||||
|
|
|
@ -188,27 +188,20 @@ bool ip_call_ra_chain(struct sk_buff *skb)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
|
void ip_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int protocol)
|
||||||
{
|
{
|
||||||
__skb_pull(skb, skb_network_header_len(skb));
|
|
||||||
|
|
||||||
rcu_read_lock();
|
|
||||||
{
|
|
||||||
int protocol = ip_hdr(skb)->protocol;
|
|
||||||
const struct net_protocol *ipprot;
|
const struct net_protocol *ipprot;
|
||||||
int raw;
|
int raw, ret;
|
||||||
|
|
||||||
resubmit:
|
resubmit:
|
||||||
raw = raw_local_deliver(skb, protocol);
|
raw = raw_local_deliver(skb, protocol);
|
||||||
|
|
||||||
ipprot = rcu_dereference(inet_protos[protocol]);
|
ipprot = rcu_dereference(inet_protos[protocol]);
|
||||||
if (ipprot) {
|
if (ipprot) {
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!ipprot->no_policy) {
|
if (!ipprot->no_policy) {
|
||||||
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
|
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
|
||||||
kfree_skb(skb);
|
kfree_skb(skb);
|
||||||
goto out;
|
return;
|
||||||
}
|
}
|
||||||
nf_reset(skb);
|
nf_reset(skb);
|
||||||
}
|
}
|
||||||
|
@ -232,7 +225,13 @@ static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out:
|
|
||||||
|
static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
__skb_pull(skb, skb_network_header_len(skb));
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
ip_protocol_deliver_rcu(net, skb, ip_hdr(skb)->protocol);
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -115,6 +115,7 @@
|
||||||
#include "udp_impl.h"
|
#include "udp_impl.h"
|
||||||
#include <net/sock_reuseport.h>
|
#include <net/sock_reuseport.h>
|
||||||
#include <net/addrconf.h>
|
#include <net/addrconf.h>
|
||||||
|
#include <net/udp_tunnel.h>
|
||||||
|
|
||||||
struct udp_table udp_table __read_mostly;
|
struct udp_table udp_table __read_mostly;
|
||||||
EXPORT_SYMBOL(udp_table);
|
EXPORT_SYMBOL(udp_table);
|
||||||
|
@ -1710,6 +1711,10 @@ try_again:
|
||||||
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
||||||
*addr_len = sizeof(*sin);
|
*addr_len = sizeof(*sin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (udp_sk(sk)->gro_enabled)
|
||||||
|
udp_cmsg_recv(msg, sk, skb);
|
||||||
|
|
||||||
if (inet->cmsg_flags)
|
if (inet->cmsg_flags)
|
||||||
ip_cmsg_recv_offset(msg, sk, skb, sizeof(struct udphdr), off);
|
ip_cmsg_recv_offset(msg, sk, skb, sizeof(struct udphdr), off);
|
||||||
|
|
||||||
|
@ -1901,7 +1906,7 @@ EXPORT_SYMBOL(udp_encap_enable);
|
||||||
* Note that in the success and error cases, the skb is assumed to
|
* Note that in the success and error cases, the skb is assumed to
|
||||||
* have either been requeued or freed.
|
* have either been requeued or freed.
|
||||||
*/
|
*/
|
||||||
static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
struct udp_sock *up = udp_sk(sk);
|
struct udp_sock *up = udp_sk(sk);
|
||||||
int is_udplite = IS_UDPLITE(sk);
|
int is_udplite = IS_UDPLITE(sk);
|
||||||
|
@ -2004,6 +2009,27 @@ drop:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct sk_buff *next, *segs;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (likely(!udp_unexpected_gso(sk, skb)))
|
||||||
|
return udp_queue_rcv_one_skb(sk, skb);
|
||||||
|
|
||||||
|
BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_SGO_CB_OFFSET);
|
||||||
|
__skb_push(skb, -skb_mac_offset(skb));
|
||||||
|
segs = udp_rcv_segment(sk, skb, true);
|
||||||
|
for (skb = segs; skb; skb = next) {
|
||||||
|
next = skb->next;
|
||||||
|
__skb_pull(skb, skb_transport_offset(skb));
|
||||||
|
ret = udp_queue_rcv_one_skb(sk, skb);
|
||||||
|
if (ret > 0)
|
||||||
|
ip_protocol_deliver_rcu(dev_net(skb->dev), skb, -ret);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* For TCP sockets, sk_rx_dst is protected by socket lock
|
/* For TCP sockets, sk_rx_dst is protected by socket lock
|
||||||
* For UDP, we use xchg() to guard against concurrent changes.
|
* For UDP, we use xchg() to guard against concurrent changes.
|
||||||
*/
|
*/
|
||||||
|
@ -2395,12 +2421,16 @@ void udp_destroy_sock(struct sock *sk)
|
||||||
bool slow = lock_sock_fast(sk);
|
bool slow = lock_sock_fast(sk);
|
||||||
udp_flush_pending_frames(sk);
|
udp_flush_pending_frames(sk);
|
||||||
unlock_sock_fast(sk, slow);
|
unlock_sock_fast(sk, slow);
|
||||||
if (static_branch_unlikely(&udp_encap_needed_key) && up->encap_type) {
|
if (static_branch_unlikely(&udp_encap_needed_key)) {
|
||||||
|
if (up->encap_type) {
|
||||||
void (*encap_destroy)(struct sock *sk);
|
void (*encap_destroy)(struct sock *sk);
|
||||||
encap_destroy = READ_ONCE(up->encap_destroy);
|
encap_destroy = READ_ONCE(up->encap_destroy);
|
||||||
if (encap_destroy)
|
if (encap_destroy)
|
||||||
encap_destroy(sk);
|
encap_destroy(sk);
|
||||||
}
|
}
|
||||||
|
if (up->encap_enabled)
|
||||||
|
static_branch_disable(&udp_encap_needed_key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2444,7 +2474,9 @@ int udp_lib_setsockopt(struct sock *sk, int level, int optname,
|
||||||
/* FALLTHROUGH */
|
/* FALLTHROUGH */
|
||||||
case UDP_ENCAP_L2TPINUDP:
|
case UDP_ENCAP_L2TPINUDP:
|
||||||
up->encap_type = val;
|
up->encap_type = val;
|
||||||
udp_encap_enable();
|
lock_sock(sk);
|
||||||
|
udp_tunnel_encap_enable(sk->sk_socket);
|
||||||
|
release_sock(sk);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
err = -ENOPROTOOPT;
|
err = -ENOPROTOOPT;
|
||||||
|
@ -2466,6 +2498,14 @@ int udp_lib_setsockopt(struct sock *sk, int level, int optname,
|
||||||
up->gso_size = val;
|
up->gso_size = val;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case UDP_GRO:
|
||||||
|
lock_sock(sk);
|
||||||
|
if (valbool)
|
||||||
|
udp_tunnel_encap_enable(sk->sk_socket);
|
||||||
|
up->gro_enabled = valbool;
|
||||||
|
release_sock(sk);
|
||||||
|
break;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UDP-Lite's partial checksum coverage (RFC 3828).
|
* UDP-Lite's partial checksum coverage (RFC 3828).
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -343,6 +343,54 @@ out:
|
||||||
return segs;
|
return segs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define UDP_GRO_CNT_MAX 64
|
||||||
|
static struct sk_buff *udp_gro_receive_segment(struct list_head *head,
|
||||||
|
struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct udphdr *uh = udp_hdr(skb);
|
||||||
|
struct sk_buff *pp = NULL;
|
||||||
|
struct udphdr *uh2;
|
||||||
|
struct sk_buff *p;
|
||||||
|
|
||||||
|
/* requires non zero csum, for symmetry with GSO */
|
||||||
|
if (!uh->check) {
|
||||||
|
NAPI_GRO_CB(skb)->flush = 1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pull encapsulating udp header */
|
||||||
|
skb_gro_pull(skb, sizeof(struct udphdr));
|
||||||
|
skb_gro_postpull_rcsum(skb, uh, sizeof(struct udphdr));
|
||||||
|
|
||||||
|
list_for_each_entry(p, head, list) {
|
||||||
|
if (!NAPI_GRO_CB(p)->same_flow)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uh2 = udp_hdr(p);
|
||||||
|
|
||||||
|
/* Match ports only, as csum is always non zero */
|
||||||
|
if ((*(u32 *)&uh->source != *(u32 *)&uh2->source)) {
|
||||||
|
NAPI_GRO_CB(p)->same_flow = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminate the flow on len mismatch or if it grow "too much".
|
||||||
|
* Under small packet flood GRO count could elsewhere grow a lot
|
||||||
|
* leading to execessive truesize values
|
||||||
|
*/
|
||||||
|
if (!skb_gro_receive(p, skb) &&
|
||||||
|
NAPI_GRO_CB(p)->count >= UDP_GRO_CNT_MAX)
|
||||||
|
pp = p;
|
||||||
|
else if (uh->len != uh2->len)
|
||||||
|
pp = p;
|
||||||
|
|
||||||
|
return pp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mismatch, but we never need to flush */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
struct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb,
|
struct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb,
|
||||||
struct udphdr *uh, udp_lookup_t lookup)
|
struct udphdr *uh, udp_lookup_t lookup)
|
||||||
{
|
{
|
||||||
|
@ -353,23 +401,27 @@ struct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb,
|
||||||
int flush = 1;
|
int flush = 1;
|
||||||
struct sock *sk;
|
struct sock *sk;
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
sk = (*lookup)(skb, uh->source, uh->dest);
|
||||||
|
if (!sk)
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
|
if (udp_sk(sk)->gro_enabled) {
|
||||||
|
pp = call_gro_receive(udp_gro_receive_segment, head, skb);
|
||||||
|
rcu_read_unlock();
|
||||||
|
return pp;
|
||||||
|
}
|
||||||
|
|
||||||
if (NAPI_GRO_CB(skb)->encap_mark ||
|
if (NAPI_GRO_CB(skb)->encap_mark ||
|
||||||
(skb->ip_summed != CHECKSUM_PARTIAL &&
|
(skb->ip_summed != CHECKSUM_PARTIAL &&
|
||||||
NAPI_GRO_CB(skb)->csum_cnt == 0 &&
|
NAPI_GRO_CB(skb)->csum_cnt == 0 &&
|
||||||
!NAPI_GRO_CB(skb)->csum_valid))
|
!NAPI_GRO_CB(skb)->csum_valid) ||
|
||||||
goto out;
|
!udp_sk(sk)->gro_receive)
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
/* mark that this skb passed once through the tunnel gro layer */
|
/* mark that this skb passed once through the tunnel gro layer */
|
||||||
NAPI_GRO_CB(skb)->encap_mark = 1;
|
NAPI_GRO_CB(skb)->encap_mark = 1;
|
||||||
|
|
||||||
rcu_read_lock();
|
|
||||||
sk = (*lookup)(skb, uh->source, uh->dest);
|
|
||||||
|
|
||||||
if (sk && udp_sk(sk)->gro_receive)
|
|
||||||
goto unflush;
|
|
||||||
goto out_unlock;
|
|
||||||
|
|
||||||
unflush:
|
|
||||||
flush = 0;
|
flush = 0;
|
||||||
|
|
||||||
list_for_each_entry(p, head, list) {
|
list_for_each_entry(p, head, list) {
|
||||||
|
@ -394,7 +446,6 @@ unflush:
|
||||||
|
|
||||||
out_unlock:
|
out_unlock:
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
out:
|
|
||||||
skb_gro_flush_final(skb, pp, flush);
|
skb_gro_flush_final(skb, pp, flush);
|
||||||
return pp;
|
return pp;
|
||||||
}
|
}
|
||||||
|
@ -427,6 +478,19 @@ flush:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int udp_gro_complete_segment(struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct udphdr *uh = udp_hdr(skb);
|
||||||
|
|
||||||
|
skb->csum_start = (unsigned char *)uh - skb->head;
|
||||||
|
skb->csum_offset = offsetof(struct udphdr, check);
|
||||||
|
skb->ip_summed = CHECKSUM_PARTIAL;
|
||||||
|
|
||||||
|
skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count;
|
||||||
|
skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_L4;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int udp_gro_complete(struct sk_buff *skb, int nhoff,
|
int udp_gro_complete(struct sk_buff *skb, int nhoff,
|
||||||
udp_lookup_t lookup)
|
udp_lookup_t lookup)
|
||||||
{
|
{
|
||||||
|
@ -437,16 +501,21 @@ int udp_gro_complete(struct sk_buff *skb, int nhoff,
|
||||||
|
|
||||||
uh->len = newlen;
|
uh->len = newlen;
|
||||||
|
|
||||||
/* Set encapsulation before calling into inner gro_complete() functions
|
|
||||||
* to make them set up the inner offsets.
|
|
||||||
*/
|
|
||||||
skb->encapsulation = 1;
|
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
sk = (*lookup)(skb, uh->source, uh->dest);
|
sk = (*lookup)(skb, uh->source, uh->dest);
|
||||||
if (sk && udp_sk(sk)->gro_complete)
|
if (sk && udp_sk(sk)->gro_enabled) {
|
||||||
|
err = udp_gro_complete_segment(skb);
|
||||||
|
} else if (sk && udp_sk(sk)->gro_complete) {
|
||||||
|
skb_shinfo(skb)->gso_type = uh->check ? SKB_GSO_UDP_TUNNEL_CSUM
|
||||||
|
: SKB_GSO_UDP_TUNNEL;
|
||||||
|
|
||||||
|
/* Set encapsulation before calling into inner gro_complete()
|
||||||
|
* functions to make them set up the inner offsets.
|
||||||
|
*/
|
||||||
|
skb->encapsulation = 1;
|
||||||
err = udp_sk(sk)->gro_complete(sk, skb,
|
err = udp_sk(sk)->gro_complete(sk, skb,
|
||||||
nhoff + sizeof(struct udphdr));
|
nhoff + sizeof(struct udphdr));
|
||||||
|
}
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
if (skb->remcsum_offload)
|
if (skb->remcsum_offload)
|
||||||
|
@ -461,13 +530,9 @@ static int udp4_gro_complete(struct sk_buff *skb, int nhoff)
|
||||||
const struct iphdr *iph = ip_hdr(skb);
|
const struct iphdr *iph = ip_hdr(skb);
|
||||||
struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
|
struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
|
||||||
|
|
||||||
if (uh->check) {
|
if (uh->check)
|
||||||
skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL_CSUM;
|
|
||||||
uh->check = ~udp_v4_check(skb->len - nhoff, iph->saddr,
|
uh->check = ~udp_v4_check(skb->len - nhoff, iph->saddr,
|
||||||
iph->daddr, 0);
|
iph->daddr, 0);
|
||||||
} else {
|
|
||||||
skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return udp_gro_complete(skb, nhoff, udp4_lib_lookup_skb);
|
return udp_gro_complete(skb, nhoff, udp4_lib_lookup_skb);
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,28 +319,26 @@ void ipv6_list_rcv(struct list_head *head, struct packet_type *pt,
|
||||||
/*
|
/*
|
||||||
* Deliver the packet to the host
|
* Deliver the packet to the host
|
||||||
*/
|
*/
|
||||||
|
void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
|
||||||
|
bool have_final)
|
||||||
static int ip6_input_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
|
|
||||||
{
|
{
|
||||||
const struct inet6_protocol *ipprot;
|
const struct inet6_protocol *ipprot;
|
||||||
struct inet6_dev *idev;
|
struct inet6_dev *idev;
|
||||||
unsigned int nhoff;
|
unsigned int nhoff;
|
||||||
int nexthdr;
|
|
||||||
bool raw;
|
bool raw;
|
||||||
bool have_final = false;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse extension headers
|
* Parse extension headers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
rcu_read_lock();
|
|
||||||
resubmit:
|
resubmit:
|
||||||
idev = ip6_dst_idev(skb_dst(skb));
|
idev = ip6_dst_idev(skb_dst(skb));
|
||||||
|
nhoff = IP6CB(skb)->nhoff;
|
||||||
|
if (!have_final) {
|
||||||
if (!pskb_pull(skb, skb_transport_offset(skb)))
|
if (!pskb_pull(skb, skb_transport_offset(skb)))
|
||||||
goto discard;
|
goto discard;
|
||||||
nhoff = IP6CB(skb)->nhoff;
|
|
||||||
nexthdr = skb_network_header(skb)[nhoff];
|
nexthdr = skb_network_header(skb)[nhoff];
|
||||||
|
}
|
||||||
|
|
||||||
resubmit_final:
|
resubmit_final:
|
||||||
raw = raw6_local_deliver(skb, nexthdr);
|
raw = raw6_local_deliver(skb, nexthdr);
|
||||||
|
@ -423,13 +421,19 @@ resubmit_final:
|
||||||
consume_skb(skb);
|
consume_skb(skb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rcu_read_unlock();
|
return;
|
||||||
return 0;
|
|
||||||
|
|
||||||
discard:
|
discard:
|
||||||
__IP6_INC_STATS(net, idev, IPSTATS_MIB_INDISCARDS);
|
__IP6_INC_STATS(net, idev, IPSTATS_MIB_INDISCARDS);
|
||||||
rcu_read_unlock();
|
|
||||||
kfree_skb(skb);
|
kfree_skb(skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ip6_input_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
rcu_read_lock();
|
||||||
|
ip6_protocol_deliver_rcu(net, skb, 0, false);
|
||||||
|
rcu_read_unlock();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -417,6 +417,9 @@ try_again:
|
||||||
*addr_len = sizeof(*sin6);
|
*addr_len = sizeof(*sin6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (udp_sk(sk)->gro_enabled)
|
||||||
|
udp_cmsg_recv(msg, sk, skb);
|
||||||
|
|
||||||
if (np->rxopt.all)
|
if (np->rxopt.all)
|
||||||
ip6_datagram_recv_common_ctl(sk, msg, skb);
|
ip6_datagram_recv_common_ctl(sk, msg, skb);
|
||||||
|
|
||||||
|
@ -551,7 +554,7 @@ void udpv6_encap_enable(void)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(udpv6_encap_enable);
|
EXPORT_SYMBOL(udpv6_encap_enable);
|
||||||
|
|
||||||
static int udpv6_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
static int udpv6_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
struct udp_sock *up = udp_sk(sk);
|
struct udp_sock *up = udp_sk(sk);
|
||||||
int is_udplite = IS_UDPLITE(sk);
|
int is_udplite = IS_UDPLITE(sk);
|
||||||
|
@ -634,6 +637,28 @@ drop:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int udpv6_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
struct sk_buff *next, *segs;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (likely(!udp_unexpected_gso(sk, skb)))
|
||||||
|
return udpv6_queue_rcv_one_skb(sk, skb);
|
||||||
|
|
||||||
|
__skb_push(skb, -skb_mac_offset(skb));
|
||||||
|
segs = udp_rcv_segment(sk, skb, false);
|
||||||
|
for (skb = segs; skb; skb = next) {
|
||||||
|
next = skb->next;
|
||||||
|
__skb_pull(skb, skb_transport_offset(skb));
|
||||||
|
|
||||||
|
ret = udpv6_queue_rcv_one_skb(sk, skb);
|
||||||
|
if (ret > 0)
|
||||||
|
ip6_protocol_deliver_rcu(dev_net(skb->dev), skb, ret,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static bool __udp_v6_is_mcast_sock(struct net *net, struct sock *sk,
|
static bool __udp_v6_is_mcast_sock(struct net *net, struct sock *sk,
|
||||||
__be16 loc_port, const struct in6_addr *loc_addr,
|
__be16 loc_port, const struct in6_addr *loc_addr,
|
||||||
__be16 rmt_port, const struct in6_addr *rmt_addr,
|
__be16 rmt_port, const struct in6_addr *rmt_addr,
|
||||||
|
@ -1456,12 +1481,16 @@ void udpv6_destroy_sock(struct sock *sk)
|
||||||
udp_v6_flush_pending_frames(sk);
|
udp_v6_flush_pending_frames(sk);
|
||||||
release_sock(sk);
|
release_sock(sk);
|
||||||
|
|
||||||
if (static_branch_unlikely(&udpv6_encap_needed_key) && up->encap_type) {
|
if (static_branch_unlikely(&udpv6_encap_needed_key)) {
|
||||||
|
if (up->encap_type) {
|
||||||
void (*encap_destroy)(struct sock *sk);
|
void (*encap_destroy)(struct sock *sk);
|
||||||
encap_destroy = READ_ONCE(up->encap_destroy);
|
encap_destroy = READ_ONCE(up->encap_destroy);
|
||||||
if (encap_destroy)
|
if (encap_destroy)
|
||||||
encap_destroy(sk);
|
encap_destroy(sk);
|
||||||
}
|
}
|
||||||
|
if (up->encap_enabled)
|
||||||
|
static_branch_disable(&udpv6_encap_needed_key);
|
||||||
|
}
|
||||||
|
|
||||||
inet6_destroy_sock(sk);
|
inet6_destroy_sock(sk);
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,13 +147,9 @@ static int udp6_gro_complete(struct sk_buff *skb, int nhoff)
|
||||||
const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
|
const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
|
||||||
struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
|
struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
|
||||||
|
|
||||||
if (uh->check) {
|
if (uh->check)
|
||||||
skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL_CSUM;
|
|
||||||
uh->check = ~udp_v6_check(skb->len - nhoff, &ipv6h->saddr,
|
uh->check = ~udp_v6_check(skb->len - nhoff, &ipv6h->saddr,
|
||||||
&ipv6h->daddr, 0);
|
&ipv6h->daddr, 0);
|
||||||
} else {
|
|
||||||
skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_TUNNEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return udp_gro_complete(skb, nhoff, udp6_lib_lookup_skb);
|
return udp_gro_complete(skb, nhoff, udp6_lib_lookup_skb);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,8 @@ TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test
|
||||||
test_lwt_seg6local.o sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
|
test_lwt_seg6local.o sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
|
||||||
get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
|
get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
|
||||||
test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o \
|
test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o \
|
||||||
test_sk_lookup_kern.o test_xdp_vlan.o test_queue_map.o test_stack_map.o
|
test_sk_lookup_kern.o test_xdp_vlan.o test_queue_map.o test_stack_map.o \
|
||||||
|
xdp_dummy.o
|
||||||
|
|
||||||
# Order correspond to 'make run_tests' order
|
# Order correspond to 'make run_tests' order
|
||||||
TEST_PROGS := test_kmod.sh \
|
TEST_PROGS := test_kmod.sh \
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
#define KBUILD_MODNAME "xdp_dummy"
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include "bpf_helpers.h"
|
||||||
|
|
||||||
|
SEC("xdp_dummy")
|
||||||
|
int xdp_dummy_prog(struct xdp_md *ctx)
|
||||||
|
{
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
|
@ -7,6 +7,7 @@ CFLAGS += -I../../../../usr/include/
|
||||||
TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetlink.sh
|
TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetlink.sh
|
||||||
TEST_PROGS += fib_tests.sh fib-onlink-tests.sh pmtu.sh udpgso.sh ip_defrag.sh
|
TEST_PROGS += fib_tests.sh fib-onlink-tests.sh pmtu.sh udpgso.sh ip_defrag.sh
|
||||||
TEST_PROGS += udpgso_bench.sh fib_rule_tests.sh msg_zerocopy.sh psock_snd.sh
|
TEST_PROGS += udpgso_bench.sh fib_rule_tests.sh msg_zerocopy.sh psock_snd.sh
|
||||||
|
TEST_PROGS += udpgro_bench.sh udpgro.sh
|
||||||
TEST_PROGS_EXTENDED := in_netns.sh
|
TEST_PROGS_EXTENDED := in_netns.sh
|
||||||
TEST_GEN_FILES = socket
|
TEST_GEN_FILES = socket
|
||||||
TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy
|
TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
#
|
||||||
|
# Run a series of udpgro functional tests.
|
||||||
|
|
||||||
|
readonly PEER_NS="ns-peer-$(mktemp -u XXXXXX)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local -r jobs="$(jobs -p)"
|
||||||
|
local -r ns="$(ip netns list|grep $PEER_NS)"
|
||||||
|
|
||||||
|
[ -n "${jobs}" ] && kill -1 ${jobs} 2>/dev/null
|
||||||
|
[ -n "$ns" ] && ip netns del $ns 2>/dev/null
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
cfg_veth() {
|
||||||
|
ip netns add "${PEER_NS}"
|
||||||
|
ip -netns "${PEER_NS}" link set lo up
|
||||||
|
ip link add type veth
|
||||||
|
ip link set dev veth0 up
|
||||||
|
ip addr add dev veth0 192.168.1.2/24
|
||||||
|
ip addr add dev veth0 2001:db8::2/64 nodad
|
||||||
|
|
||||||
|
ip link set dev veth1 netns "${PEER_NS}"
|
||||||
|
ip -netns "${PEER_NS}" addr add dev veth1 192.168.1.1/24
|
||||||
|
ip -netns "${PEER_NS}" addr add dev veth1 2001:db8::1/64 nodad
|
||||||
|
ip -netns "${PEER_NS}" link set dev veth1 up
|
||||||
|
ip -n "${PEER_NS}" link set veth1 xdp object ../bpf/xdp_dummy.o section xdp_dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
run_one() {
|
||||||
|
# use 'rx' as separator between sender args and receiver args
|
||||||
|
local -r all="$@"
|
||||||
|
local -r tx_args=${all%rx*}
|
||||||
|
local -r rx_args=${all#*rx}
|
||||||
|
|
||||||
|
cfg_veth
|
||||||
|
|
||||||
|
ip netns exec "${PEER_NS}" ./udpgso_bench_rx ${rx_args} && \
|
||||||
|
echo "ok" || \
|
||||||
|
echo "failed" &
|
||||||
|
|
||||||
|
# Hack: let bg programs complete the startup
|
||||||
|
sleep 0.1
|
||||||
|
./udpgso_bench_tx ${tx_args}
|
||||||
|
wait $(jobs -p)
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test() {
|
||||||
|
local -r args=$@
|
||||||
|
|
||||||
|
printf " %-40s" "$1"
|
||||||
|
./in_netns.sh $0 __subprocess $2 rx -G -r $3
|
||||||
|
}
|
||||||
|
|
||||||
|
run_one_nat() {
|
||||||
|
# use 'rx' as separator between sender args and receiver args
|
||||||
|
local addr1 addr2 pid family="" ipt_cmd=ip6tables
|
||||||
|
local -r all="$@"
|
||||||
|
local -r tx_args=${all%rx*}
|
||||||
|
local -r rx_args=${all#*rx}
|
||||||
|
|
||||||
|
if [[ ${tx_args} = *-4* ]]; then
|
||||||
|
ipt_cmd=iptables
|
||||||
|
family=-4
|
||||||
|
addr1=192.168.1.1
|
||||||
|
addr2=192.168.1.3/24
|
||||||
|
else
|
||||||
|
addr1=2001:db8::1
|
||||||
|
addr2="2001:db8::3/64 nodad"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cfg_veth
|
||||||
|
ip -netns "${PEER_NS}" addr add dev veth1 ${addr2}
|
||||||
|
|
||||||
|
# fool the GRO engine changing the destination address ...
|
||||||
|
ip netns exec "${PEER_NS}" $ipt_cmd -t nat -I PREROUTING -d ${addr1} -j DNAT --to-destination ${addr2%/*}
|
||||||
|
|
||||||
|
# ... so that GRO will match the UDP_GRO enabled socket, but packets
|
||||||
|
# will land on the 'plain' one
|
||||||
|
ip netns exec "${PEER_NS}" ./udpgso_bench_rx -G ${family} -b ${addr1} -n 0 &
|
||||||
|
pid=$!
|
||||||
|
ip netns exec "${PEER_NS}" ./udpgso_bench_rx ${family} -b ${addr2%/*} ${rx_args} && \
|
||||||
|
echo "ok" || \
|
||||||
|
echo "failed"&
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
./udpgso_bench_tx ${tx_args}
|
||||||
|
kill -INT $pid
|
||||||
|
wait $(jobs -p)
|
||||||
|
}
|
||||||
|
|
||||||
|
run_nat_test() {
|
||||||
|
local -r args=$@
|
||||||
|
|
||||||
|
printf " %-40s" "$1"
|
||||||
|
./in_netns.sh $0 __subprocess_nat $2 rx -r $3
|
||||||
|
}
|
||||||
|
|
||||||
|
run_all() {
|
||||||
|
local -r core_args="-l 4"
|
||||||
|
local -r ipv4_args="${core_args} -4 -D 192.168.1.1"
|
||||||
|
local -r ipv6_args="${core_args} -6 -D 2001:db8::1"
|
||||||
|
|
||||||
|
echo "ipv4"
|
||||||
|
run_test "no GRO" "${ipv4_args} -M 10 -s 1400" "-4 -n 10 -l 1400"
|
||||||
|
|
||||||
|
# explicitly check we are not receiving UDP_SEGMENT cmsg (-S -1)
|
||||||
|
# when GRO does not take place
|
||||||
|
run_test "no GRO chk cmsg" "${ipv4_args} -M 10 -s 1400" "-4 -n 10 -l 1400 -S -1"
|
||||||
|
|
||||||
|
# the GSO packets are aggregated because:
|
||||||
|
# * veth schedule napi after each xmit
|
||||||
|
# * segmentation happens in BH context, veth napi poll is delayed after
|
||||||
|
# the transmission of the last segment
|
||||||
|
run_test "GRO" "${ipv4_args} -M 1 -s 14720 -S 0 " "-4 -n 1 -l 14720"
|
||||||
|
run_test "GRO chk cmsg" "${ipv4_args} -M 1 -s 14720 -S 0 " "-4 -n 1 -l 14720 -S 1472"
|
||||||
|
run_test "GRO with custom segment size" "${ipv4_args} -M 1 -s 14720 -S 500 " "-4 -n 1 -l 14720"
|
||||||
|
run_test "GRO with custom segment size cmsg" "${ipv4_args} -M 1 -s 14720 -S 500 " "-4 -n 1 -l 14720 -S 500"
|
||||||
|
|
||||||
|
run_nat_test "bad GRO lookup" "${ipv4_args} -M 1 -s 14720 -S 0" "-n 10 -l 1472"
|
||||||
|
|
||||||
|
echo "ipv6"
|
||||||
|
run_test "no GRO" "${ipv6_args} -M 10 -s 1400" "-n 10 -l 1400"
|
||||||
|
run_test "no GRO chk cmsg" "${ipv6_args} -M 10 -s 1400" "-n 10 -l 1400 -S -1"
|
||||||
|
run_test "GRO" "${ipv6_args} -M 1 -s 14520 -S 0" "-n 1 -l 14520"
|
||||||
|
run_test "GRO chk cmsg" "${ipv6_args} -M 1 -s 14520 -S 0" "-n 1 -l 14520 -S 1452"
|
||||||
|
run_test "GRO with custom segment size" "${ipv6_args} -M 1 -s 14520 -S 500" "-n 1 -l 14520"
|
||||||
|
run_test "GRO with custom segment size cmsg" "${ipv6_args} -M 1 -s 14520 -S 500" "-n 1 -l 14520 -S 500"
|
||||||
|
|
||||||
|
run_nat_test "bad GRO lookup" "${ipv6_args} -M 1 -s 14520 -S 0" "-n 10 -l 1452"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f ../bpf/xdp_dummy.o ]; then
|
||||||
|
echo "Missing xdp_dummy helper. Build bpf selftest first"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
run_all
|
||||||
|
elif [[ $1 == "__subprocess" ]]; then
|
||||||
|
shift
|
||||||
|
run_one $@
|
||||||
|
elif [[ $1 == "__subprocess_nat" ]]; then
|
||||||
|
shift
|
||||||
|
run_one_nat $@
|
||||||
|
fi
|
|
@ -0,0 +1,95 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
#
|
||||||
|
# Run a series of udpgro benchmarks
|
||||||
|
|
||||||
|
readonly PEER_NS="ns-peer-$(mktemp -u XXXXXX)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local -r jobs="$(jobs -p)"
|
||||||
|
local -r ns="$(ip netns list|grep $PEER_NS)"
|
||||||
|
|
||||||
|
[ -n "${jobs}" ] && kill -INT ${jobs} 2>/dev/null
|
||||||
|
[ -n "$ns" ] && ip netns del $ns 2>/dev/null
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
run_one() {
|
||||||
|
# use 'rx' as separator between sender args and receiver args
|
||||||
|
local -r all="$@"
|
||||||
|
local -r tx_args=${all%rx*}
|
||||||
|
local rx_args=${all#*rx}
|
||||||
|
|
||||||
|
[[ "${tx_args}" == *"-4"* ]] && rx_args="${rx_args} -4"
|
||||||
|
|
||||||
|
ip netns add "${PEER_NS}"
|
||||||
|
ip -netns "${PEER_NS}" link set lo up
|
||||||
|
ip link add type veth
|
||||||
|
ip link set dev veth0 up
|
||||||
|
ip addr add dev veth0 192.168.1.2/24
|
||||||
|
ip addr add dev veth0 2001:db8::2/64 nodad
|
||||||
|
|
||||||
|
ip link set dev veth1 netns "${PEER_NS}"
|
||||||
|
ip -netns "${PEER_NS}" addr add dev veth1 192.168.1.1/24
|
||||||
|
ip -netns "${PEER_NS}" addr add dev veth1 2001:db8::1/64 nodad
|
||||||
|
ip -netns "${PEER_NS}" link set dev veth1 up
|
||||||
|
|
||||||
|
ip -n "${PEER_NS}" link set veth1 xdp object ../bpf/xdp_dummy.o section xdp_dummy
|
||||||
|
ip netns exec "${PEER_NS}" ./udpgso_bench_rx ${rx_args} -r &
|
||||||
|
ip netns exec "${PEER_NS}" ./udpgso_bench_rx -t ${rx_args} -r &
|
||||||
|
|
||||||
|
# Hack: let bg programs complete the startup
|
||||||
|
sleep 0.1
|
||||||
|
./udpgso_bench_tx ${tx_args}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_in_netns() {
|
||||||
|
local -r args=$@
|
||||||
|
|
||||||
|
./in_netns.sh $0 __subprocess ${args}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_udp() {
|
||||||
|
local -r args=$@
|
||||||
|
|
||||||
|
echo "udp gso - over veth touching data"
|
||||||
|
run_in_netns ${args} -S 0 rx
|
||||||
|
|
||||||
|
echo "udp gso and gro - over veth touching data"
|
||||||
|
run_in_netns ${args} -S 0 rx -G
|
||||||
|
}
|
||||||
|
|
||||||
|
run_tcp() {
|
||||||
|
local -r args=$@
|
||||||
|
|
||||||
|
echo "tcp - over veth touching data"
|
||||||
|
run_in_netns ${args} -t rx
|
||||||
|
}
|
||||||
|
|
||||||
|
run_all() {
|
||||||
|
local -r core_args="-l 4"
|
||||||
|
local -r ipv4_args="${core_args} -4 -D 192.168.1.1"
|
||||||
|
local -r ipv6_args="${core_args} -6 -D 2001:db8::1"
|
||||||
|
|
||||||
|
echo "ipv4"
|
||||||
|
run_tcp "${ipv4_args}"
|
||||||
|
run_udp "${ipv4_args}"
|
||||||
|
|
||||||
|
echo "ipv6"
|
||||||
|
run_tcp "${ipv4_args}"
|
||||||
|
run_udp "${ipv6_args}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f ../bpf/xdp_dummy.o ]; then
|
||||||
|
echo "Missing xdp_dummy helper. Build bpf selftest first"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
run_all
|
||||||
|
elif [[ $1 == "__subprocess" ]]; then
|
||||||
|
shift
|
||||||
|
run_one $@
|
||||||
|
else
|
||||||
|
run_in_netns $@
|
||||||
|
fi
|
|
@ -34,7 +34,7 @@ run_udp() {
|
||||||
run_in_netns ${args}
|
run_in_netns ${args}
|
||||||
|
|
||||||
echo "udp gso"
|
echo "udp gso"
|
||||||
run_in_netns ${args} -S
|
run_in_netns ${args} -S 0
|
||||||
}
|
}
|
||||||
|
|
||||||
run_tcp() {
|
run_tcp() {
|
||||||
|
|
|
@ -31,9 +31,21 @@
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifndef UDP_GRO
|
||||||
|
#define UDP_GRO 104
|
||||||
|
#endif
|
||||||
|
|
||||||
static int cfg_port = 8000;
|
static int cfg_port = 8000;
|
||||||
static bool cfg_tcp;
|
static bool cfg_tcp;
|
||||||
static bool cfg_verify;
|
static bool cfg_verify;
|
||||||
|
static bool cfg_read_all;
|
||||||
|
static bool cfg_gro_segment;
|
||||||
|
static int cfg_family = PF_INET6;
|
||||||
|
static int cfg_alen = sizeof(struct sockaddr_in6);
|
||||||
|
static int cfg_expected_pkt_nr;
|
||||||
|
static int cfg_expected_pkt_len;
|
||||||
|
static int cfg_expected_gso_size;
|
||||||
|
static struct sockaddr_storage cfg_bind_addr;
|
||||||
|
|
||||||
static bool interrupted;
|
static bool interrupted;
|
||||||
static unsigned long packets, bytes;
|
static unsigned long packets, bytes;
|
||||||
|
@ -44,6 +56,29 @@ static void sigint_handler(int signum)
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void setup_sockaddr(int domain, const char *str_addr, void *sockaddr)
|
||||||
|
{
|
||||||
|
struct sockaddr_in6 *addr6 = (void *) sockaddr;
|
||||||
|
struct sockaddr_in *addr4 = (void *) sockaddr;
|
||||||
|
|
||||||
|
switch (domain) {
|
||||||
|
case PF_INET:
|
||||||
|
addr4->sin_family = AF_INET;
|
||||||
|
addr4->sin_port = htons(cfg_port);
|
||||||
|
if (inet_pton(AF_INET, str_addr, &(addr4->sin_addr)) != 1)
|
||||||
|
error(1, 0, "ipv4 parse error: %s", str_addr);
|
||||||
|
break;
|
||||||
|
case PF_INET6:
|
||||||
|
addr6->sin6_family = AF_INET6;
|
||||||
|
addr6->sin6_port = htons(cfg_port);
|
||||||
|
if (inet_pton(AF_INET6, str_addr, &(addr6->sin6_addr)) != 1)
|
||||||
|
error(1, 0, "ipv6 parse error: %s", str_addr);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error(1, 0, "illegal domain");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned long gettimeofday_ms(void)
|
static unsigned long gettimeofday_ms(void)
|
||||||
{
|
{
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
@ -63,6 +98,8 @@ static void do_poll(int fd)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
ret = poll(&pfd, 1, 10);
|
ret = poll(&pfd, 1, 10);
|
||||||
|
if (interrupted)
|
||||||
|
break;
|
||||||
if (ret == -1)
|
if (ret == -1)
|
||||||
error(1, errno, "poll");
|
error(1, errno, "poll");
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
|
@ -70,15 +107,14 @@ static void do_poll(int fd)
|
||||||
if (pfd.revents != POLLIN)
|
if (pfd.revents != POLLIN)
|
||||||
error(1, errno, "poll: 0x%x expected 0x%x\n",
|
error(1, errno, "poll: 0x%x expected 0x%x\n",
|
||||||
pfd.revents, POLLIN);
|
pfd.revents, POLLIN);
|
||||||
} while (!ret && !interrupted);
|
} while (!ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int do_socket(bool do_tcp)
|
static int do_socket(bool do_tcp)
|
||||||
{
|
{
|
||||||
struct sockaddr_in6 addr = {0};
|
|
||||||
int fd, val;
|
int fd, val;
|
||||||
|
|
||||||
fd = socket(PF_INET6, cfg_tcp ? SOCK_STREAM : SOCK_DGRAM, 0);
|
fd = socket(cfg_family, cfg_tcp ? SOCK_STREAM : SOCK_DGRAM, 0);
|
||||||
if (fd == -1)
|
if (fd == -1)
|
||||||
error(1, errno, "socket");
|
error(1, errno, "socket");
|
||||||
|
|
||||||
|
@ -89,10 +125,7 @@ static int do_socket(bool do_tcp)
|
||||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)))
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)))
|
||||||
error(1, errno, "setsockopt reuseport");
|
error(1, errno, "setsockopt reuseport");
|
||||||
|
|
||||||
addr.sin6_family = PF_INET6;
|
if (bind(fd, (void *)&cfg_bind_addr, cfg_alen))
|
||||||
addr.sin6_port = htons(cfg_port);
|
|
||||||
addr.sin6_addr = in6addr_any;
|
|
||||||
if (bind(fd, (void *) &addr, sizeof(addr)))
|
|
||||||
error(1, errno, "bind");
|
error(1, errno, "bind");
|
||||||
|
|
||||||
if (do_tcp) {
|
if (do_tcp) {
|
||||||
|
@ -102,6 +135,8 @@ static int do_socket(bool do_tcp)
|
||||||
error(1, errno, "listen");
|
error(1, errno, "listen");
|
||||||
|
|
||||||
do_poll(accept_fd);
|
do_poll(accept_fd);
|
||||||
|
if (interrupted)
|
||||||
|
exit(0);
|
||||||
|
|
||||||
fd = accept(accept_fd, NULL, NULL);
|
fd = accept(accept_fd, NULL, NULL);
|
||||||
if (fd == -1)
|
if (fd == -1)
|
||||||
|
@ -164,51 +199,123 @@ static void do_verify_udp(const char *data, int len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int recv_msg(int fd, char *buf, int len, int *gso_size)
|
||||||
|
{
|
||||||
|
char control[CMSG_SPACE(sizeof(uint16_t))] = {0};
|
||||||
|
struct msghdr msg = {0};
|
||||||
|
struct iovec iov = {0};
|
||||||
|
struct cmsghdr *cmsg;
|
||||||
|
uint16_t *gsosizeptr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
iov.iov_base = buf;
|
||||||
|
iov.iov_len = len;
|
||||||
|
|
||||||
|
msg.msg_iov = &iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
|
||||||
|
msg.msg_control = control;
|
||||||
|
msg.msg_controllen = sizeof(control);
|
||||||
|
|
||||||
|
*gso_size = -1;
|
||||||
|
ret = recvmsg(fd, &msg, MSG_TRUNC | MSG_DONTWAIT);
|
||||||
|
if (ret != -1) {
|
||||||
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
|
||||||
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
||||||
|
if (cmsg->cmsg_level == SOL_UDP
|
||||||
|
&& cmsg->cmsg_type == UDP_GRO) {
|
||||||
|
gsosizeptr = (uint16_t *) CMSG_DATA(cmsg);
|
||||||
|
*gso_size = *gsosizeptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/* Flush all outstanding datagrams. Verify first few bytes of each. */
|
/* Flush all outstanding datagrams. Verify first few bytes of each. */
|
||||||
static void do_flush_udp(int fd)
|
static void do_flush_udp(int fd)
|
||||||
{
|
{
|
||||||
static char rbuf[ETH_DATA_LEN];
|
static char rbuf[ETH_MAX_MTU];
|
||||||
int ret, len, budget = 256;
|
int ret, len, gso_size, budget = 256;
|
||||||
|
|
||||||
len = cfg_verify ? sizeof(rbuf) : 0;
|
len = cfg_read_all ? sizeof(rbuf) : 0;
|
||||||
while (budget--) {
|
while (budget--) {
|
||||||
/* MSG_TRUNC will make return value full datagram length */
|
/* MSG_TRUNC will make return value full datagram length */
|
||||||
|
if (!cfg_expected_gso_size)
|
||||||
ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT);
|
ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT);
|
||||||
|
else
|
||||||
|
ret = recv_msg(fd, rbuf, len, &gso_size);
|
||||||
if (ret == -1 && errno == EAGAIN)
|
if (ret == -1 && errno == EAGAIN)
|
||||||
return;
|
break;
|
||||||
if (ret == -1)
|
if (ret == -1)
|
||||||
error(1, errno, "recv");
|
error(1, errno, "recv");
|
||||||
if (len) {
|
if (cfg_expected_pkt_len && ret != cfg_expected_pkt_len)
|
||||||
|
error(1, 0, "recv: bad packet len, got %d,"
|
||||||
|
" expected %d\n", ret, cfg_expected_pkt_len);
|
||||||
|
if (len && cfg_verify) {
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
error(1, errno, "recv: 0 byte datagram\n");
|
error(1, errno, "recv: 0 byte datagram\n");
|
||||||
|
|
||||||
do_verify_udp(rbuf, ret);
|
do_verify_udp(rbuf, ret);
|
||||||
}
|
}
|
||||||
|
if (cfg_expected_gso_size && cfg_expected_gso_size != gso_size)
|
||||||
|
error(1, 0, "recv: bad gso size, got %d, expected %d "
|
||||||
|
"(-1 == no gso cmsg))\n", gso_size,
|
||||||
|
cfg_expected_gso_size);
|
||||||
|
|
||||||
packets++;
|
packets++;
|
||||||
bytes += ret;
|
bytes += ret;
|
||||||
|
if (cfg_expected_pkt_nr && packets >= cfg_expected_pkt_nr)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void usage(const char *filepath)
|
static void usage(const char *filepath)
|
||||||
{
|
{
|
||||||
error(1, 0, "Usage: %s [-tv] [-p port]", filepath);
|
error(1, 0, "Usage: %s [-Grtv] [-b addr] [-p port] [-l pktlen] [-n packetnr] [-S gsosize]", filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parse_opts(int argc, char **argv)
|
static void parse_opts(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
while ((c = getopt(argc, argv, "ptv")) != -1) {
|
/* bind to any by default */
|
||||||
|
setup_sockaddr(PF_INET6, "::", &cfg_bind_addr);
|
||||||
|
while ((c = getopt(argc, argv, "4b:Gl:n:p:rS:tv")) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
|
case '4':
|
||||||
|
cfg_family = PF_INET;
|
||||||
|
cfg_alen = sizeof(struct sockaddr_in);
|
||||||
|
setup_sockaddr(PF_INET, "0.0.0.0", &cfg_bind_addr);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
setup_sockaddr(cfg_family, optarg, &cfg_bind_addr);
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
cfg_gro_segment = true;
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
cfg_expected_pkt_len = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
cfg_expected_pkt_nr = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
cfg_port = htons(strtoul(optarg, NULL, 0));
|
cfg_port = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
cfg_read_all = true;
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
cfg_expected_gso_size = strtol(optarg, NULL, 0);
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
cfg_tcp = true;
|
cfg_tcp = true;
|
||||||
break;
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
cfg_verify = true;
|
cfg_verify = true;
|
||||||
|
cfg_read_all = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,12 +330,23 @@ static void parse_opts(int argc, char **argv)
|
||||||
static void do_recv(void)
|
static void do_recv(void)
|
||||||
{
|
{
|
||||||
unsigned long tnow, treport;
|
unsigned long tnow, treport;
|
||||||
int fd;
|
int fd, loop = 0;
|
||||||
|
|
||||||
fd = do_socket(cfg_tcp);
|
fd = do_socket(cfg_tcp);
|
||||||
|
|
||||||
|
if (cfg_gro_segment && !cfg_tcp) {
|
||||||
|
int val = 1;
|
||||||
|
if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val)))
|
||||||
|
error(1, errno, "setsockopt UDP_GRO");
|
||||||
|
}
|
||||||
|
|
||||||
treport = gettimeofday_ms() + 1000;
|
treport = gettimeofday_ms() + 1000;
|
||||||
do {
|
do {
|
||||||
|
/* force termination after the second poll(); this cope both
|
||||||
|
* with sender slower than receiver and missing packet errors
|
||||||
|
*/
|
||||||
|
if (cfg_expected_pkt_nr && loop++)
|
||||||
|
interrupted = true;
|
||||||
do_poll(fd);
|
do_poll(fd);
|
||||||
|
|
||||||
if (cfg_tcp)
|
if (cfg_tcp)
|
||||||
|
@ -249,6 +367,10 @@ static void do_recv(void)
|
||||||
|
|
||||||
} while (!interrupted);
|
} while (!interrupted);
|
||||||
|
|
||||||
|
if (cfg_expected_pkt_nr && (packets != cfg_expected_pkt_nr))
|
||||||
|
error(1, 0, "wrong packet number! got %ld, expected %d\n",
|
||||||
|
packets, cfg_expected_pkt_nr);
|
||||||
|
|
||||||
if (close(fd))
|
if (close(fd))
|
||||||
error(1, errno, "close");
|
error(1, errno, "close");
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,8 @@ static bool cfg_segment;
|
||||||
static bool cfg_sendmmsg;
|
static bool cfg_sendmmsg;
|
||||||
static bool cfg_tcp;
|
static bool cfg_tcp;
|
||||||
static bool cfg_zerocopy;
|
static bool cfg_zerocopy;
|
||||||
|
static int cfg_msg_nr;
|
||||||
|
static uint16_t cfg_gso_size;
|
||||||
|
|
||||||
static socklen_t cfg_alen;
|
static socklen_t cfg_alen;
|
||||||
static struct sockaddr_storage cfg_dst_addr;
|
static struct sockaddr_storage cfg_dst_addr;
|
||||||
|
@ -205,14 +207,14 @@ static void send_udp_segment_cmsg(struct cmsghdr *cm)
|
||||||
|
|
||||||
cm->cmsg_level = SOL_UDP;
|
cm->cmsg_level = SOL_UDP;
|
||||||
cm->cmsg_type = UDP_SEGMENT;
|
cm->cmsg_type = UDP_SEGMENT;
|
||||||
cm->cmsg_len = CMSG_LEN(sizeof(cfg_mss));
|
cm->cmsg_len = CMSG_LEN(sizeof(cfg_gso_size));
|
||||||
valp = (void *)CMSG_DATA(cm);
|
valp = (void *)CMSG_DATA(cm);
|
||||||
*valp = cfg_mss;
|
*valp = cfg_gso_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int send_udp_segment(int fd, char *data)
|
static int send_udp_segment(int fd, char *data)
|
||||||
{
|
{
|
||||||
char control[CMSG_SPACE(sizeof(cfg_mss))] = {0};
|
char control[CMSG_SPACE(sizeof(cfg_gso_size))] = {0};
|
||||||
struct msghdr msg = {0};
|
struct msghdr msg = {0};
|
||||||
struct iovec iov = {0};
|
struct iovec iov = {0};
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -241,7 +243,7 @@ static int send_udp_segment(int fd, char *data)
|
||||||
|
|
||||||
static void usage(const char *filepath)
|
static void usage(const char *filepath)
|
||||||
{
|
{
|
||||||
error(1, 0, "Usage: %s [-46cmStuz] [-C cpu] [-D dst ip] [-l secs] [-p port] [-s sendsize]",
|
error(1, 0, "Usage: %s [-46cmtuz] [-C cpu] [-D dst ip] [-l secs] [-m messagenr] [-p port] [-s sendsize] [-S gsosize]",
|
||||||
filepath);
|
filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +252,7 @@ static void parse_opts(int argc, char **argv)
|
||||||
int max_len, hdrlen;
|
int max_len, hdrlen;
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
while ((c = getopt(argc, argv, "46cC:D:l:mp:s:Stuz")) != -1) {
|
while ((c = getopt(argc, argv, "46cC:D:l:mM:p:s:S:tuz")) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '4':
|
case '4':
|
||||||
if (cfg_family != PF_UNSPEC)
|
if (cfg_family != PF_UNSPEC)
|
||||||
|
@ -279,6 +281,9 @@ static void parse_opts(int argc, char **argv)
|
||||||
case 'm':
|
case 'm':
|
||||||
cfg_sendmmsg = true;
|
cfg_sendmmsg = true;
|
||||||
break;
|
break;
|
||||||
|
case 'M':
|
||||||
|
cfg_msg_nr = strtoul(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
cfg_port = strtoul(optarg, NULL, 0);
|
cfg_port = strtoul(optarg, NULL, 0);
|
||||||
break;
|
break;
|
||||||
|
@ -286,6 +291,7 @@ static void parse_opts(int argc, char **argv)
|
||||||
cfg_payload_len = strtoul(optarg, NULL, 0);
|
cfg_payload_len = strtoul(optarg, NULL, 0);
|
||||||
break;
|
break;
|
||||||
case 'S':
|
case 'S':
|
||||||
|
cfg_gso_size = strtoul(optarg, NULL, 0);
|
||||||
cfg_segment = true;
|
cfg_segment = true;
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
|
@ -317,6 +323,8 @@ static void parse_opts(int argc, char **argv)
|
||||||
|
|
||||||
cfg_mss = ETH_DATA_LEN - hdrlen;
|
cfg_mss = ETH_DATA_LEN - hdrlen;
|
||||||
max_len = ETH_MAX_MTU - hdrlen;
|
max_len = ETH_MAX_MTU - hdrlen;
|
||||||
|
if (!cfg_gso_size)
|
||||||
|
cfg_gso_size = cfg_mss;
|
||||||
|
|
||||||
if (cfg_payload_len > max_len)
|
if (cfg_payload_len > max_len)
|
||||||
error(1, 0, "payload length %u exceeds max %u",
|
error(1, 0, "payload length %u exceeds max %u",
|
||||||
|
@ -392,10 +400,12 @@ int main(int argc, char **argv)
|
||||||
else
|
else
|
||||||
num_sends += send_udp(fd, buf[i]);
|
num_sends += send_udp(fd, buf[i]);
|
||||||
num_msgs++;
|
num_msgs++;
|
||||||
|
|
||||||
if (cfg_zerocopy && ((num_msgs & 0xF) == 0))
|
if (cfg_zerocopy && ((num_msgs & 0xF) == 0))
|
||||||
flush_zerocopy(fd);
|
flush_zerocopy(fd);
|
||||||
|
|
||||||
|
if (cfg_msg_nr && num_msgs >= cfg_msg_nr)
|
||||||
|
break;
|
||||||
|
|
||||||
tnow = gettimeofday_ms();
|
tnow = gettimeofday_ms();
|
||||||
if (tnow > treport) {
|
if (tnow > treport) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
|
|
Loading…
Reference in New Issue