diff mbox series

[v2,net] udp: gso: do not drop small packets when PMTU reduces

Message ID Z5swit7ykNRbJFMS@debian.debian (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series [v2,net] udp: gso: do not drop small packets when PMTU reduces | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag present in non-next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/build_tools success Errors and warnings before: 26 (+1) this patch: 26 (+1)
netdev/cc_maintainers success CCed 11 of 11 maintainers
netdev/build_clang success Errors and warnings before: 5 this patch: 5
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 6 this patch: 6
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 116 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 3 this patch: 3
netdev/source_inline success Was 0 now: 0
netdev/contest fail net-next-2025-01-30--09-00 (tests: 885)

Commit Message

Yan Zhai Jan. 30, 2025, 7:55 a.m. UTC
Commit 4094871db1d6 ("udp: only do GSO if # of segs > 1") avoided GSO
for small packets. But the kernel currently dismisses GSO requests only
after checking MTU/PMTU on gso_size. This means any packets, regardless
of their payload sizes, could be dropped when PMTU becomes smaller than
requested gso_size. We encountered this issue in production and it
caused a reliability problem that new QUIC connection cannot be
established before PMTU cache expired, while non GSO sockets still
worked fine at the same time.

Ideally, do not check any GSO related constraints when payload size is
smaller than requested gso_size, and return EMSGSIZE instead of EINVAL
on MTU/PMTU check failure to be more specific on the error cause.

Fixes: 4094871db1d6 ("udp: only do GSO if # of segs > 1")
Signed-off-by: Yan Zhai <yan@cloudflare.com>
--
v1->v2: add a missing MTU check when fall back to no GSO mode suggested
by Willem de Bruijn <willemdebruijn.kernel@gmail.com>; Fixed up commit
message to be more precise.

v1: https://lore.kernel.org/all/Z5cgWh%2F6bRQm9vVU@debian.debian/
---
 net/ipv4/udp.c                       | 28 +++++++++++++++++++---------
 net/ipv6/udp.c                       | 28 +++++++++++++++++++---------
 tools/testing/selftests/net/udpgso.c | 14 ++++++++++++++
 3 files changed, 52 insertions(+), 18 deletions(-)

Comments

Willem de Bruijn Jan. 30, 2025, 3:35 p.m. UTC | #1
Yan Zhai wrote:
> Commit 4094871db1d6 ("udp: only do GSO if # of segs > 1") avoided GSO
> for small packets. But the kernel currently dismisses GSO requests only
> after checking MTU/PMTU on gso_size. This means any packets, regardless
> of their payload sizes, could be dropped when PMTU becomes smaller than
> requested gso_size. We encountered this issue in production and it
> caused a reliability problem that new QUIC connection cannot be
> established before PMTU cache expired, while non GSO sockets still
> worked fine at the same time.
> 
> Ideally, do not check any GSO related constraints when payload size is
> smaller than requested gso_size, and return EMSGSIZE instead of EINVAL
> on MTU/PMTU check failure to be more specific on the error cause.
> 
> Fixes: 4094871db1d6 ("udp: only do GSO if # of segs > 1")
> Signed-off-by: Yan Zhai <yan@cloudflare.com>
> --
> v1->v2: add a missing MTU check when fall back to no GSO mode suggested
> by Willem de Bruijn <willemdebruijn.kernel@gmail.com>; Fixed up commit
> message to be more precise.
> 
> v1: https://lore.kernel.org/all/Z5cgWh%2F6bRQm9vVU@debian.debian/
> ---
>  net/ipv4/udp.c                       | 28 +++++++++++++++++++---------
>  net/ipv6/udp.c                       | 28 +++++++++++++++++++---------
>  tools/testing/selftests/net/udpgso.c | 14 ++++++++++++++
>  3 files changed, 52 insertions(+), 18 deletions(-)
> 
> diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> index c472c9a57cf6..0b5010238d05 100644
> --- a/net/ipv4/udp.c
> +++ b/net/ipv4/udp.c
> @@ -1141,9 +1141,20 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
>  		const int hlen = skb_network_header_len(skb) +
>  				 sizeof(struct udphdr);
>  
> +		if (datalen <= cork->gso_size) {
> +			/*
> +			 * check MTU again: it's skipped previously when
> +			 * gso_size != 0
> +			 */
> +			if (hlen + datalen > cork->fragsize) {
> +				kfree_skb(skb);
> +				return -EMSGSIZE;
> +			}
> +			goto no_gso;

This is almost the same as the test below.

How about just

    if (hlen + min(cork->gso_size, datalen) > cork->fragsize)

And don't bypass the subsequent checks with a goto, or modify the
rest of the code.

> +		}
>  		if (hlen + cork->gso_size > cork->fragsize) {
>  			kfree_skb(skb);
> -			return -EINVAL;
> +			return -EMSGSIZE;
>  		}
>  		if (datalen > cork->gso_size * UDP_MAX_SEGMENTS) {
>  			kfree_skb(skb);
> @@ -1158,17 +1169,16 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
>  			return -EIO;
>  		}
>  
> -		if (datalen > cork->gso_size) {
> -			skb_shinfo(skb)->gso_size = cork->gso_size;
> -			skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> -			skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
> -								 cork->gso_size);
> +		skb_shinfo(skb)->gso_size = cork->gso_size;
> +		skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> +		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
> +							 cork->gso_size);
>  
> -			/* Don't checksum the payload, skb will get segmented */
> -			goto csum_partial;
> -		}
> +		/* Don't checksum the payload, skb will get segmented */
> +		goto csum_partial;
>  	}
>  
> +no_gso:
>  	if (is_udplite)  				 /*     UDP-Lite      */
>  		csum = udplite_csum(skb);
>  
> diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
> index 6671daa67f4f..d97befa7f80d 100644
> --- a/net/ipv6/udp.c
> +++ b/net/ipv6/udp.c
> @@ -1389,9 +1389,20 @@ static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6,
>  		const int hlen = skb_network_header_len(skb) +
>  				 sizeof(struct udphdr);
>  
> +		if (datalen <= cork->gso_size) {
> +			/*
> +			 * check MTU again: it's skipped previously when
> +			 * gso_size != 0
> +			 */
> +			if (hlen + datalen > cork->fragsize) {
> +				kfree_skb(skb);
> +				return -EMSGSIZE;
> +			}
> +			goto no_gso;
> +		}
>  		if (hlen + cork->gso_size > cork->fragsize) {
>  			kfree_skb(skb);
> -			return -EINVAL;
> +			return -EMSGSIZE;
>  		}
>  		if (datalen > cork->gso_size * UDP_MAX_SEGMENTS) {
>  			kfree_skb(skb);
> @@ -1406,17 +1417,16 @@ static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6,
>  			return -EIO;
>  		}
>  
> -		if (datalen > cork->gso_size) {
> -			skb_shinfo(skb)->gso_size = cork->gso_size;
> -			skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> -			skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
> -								 cork->gso_size);
> +		skb_shinfo(skb)->gso_size = cork->gso_size;
> +		skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> +		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
> +							 cork->gso_size);
>  
> -			/* Don't checksum the payload, skb will get segmented */
> -			goto csum_partial;
> -		}
> +		/* Don't checksum the payload, skb will get segmented */
> +		goto csum_partial;
>  	}
>  
> +no_gso:
>  	if (is_udplite)
>  		csum = udplite_csum(skb);
>  	else if (udp_get_no_check6_tx(sk)) {   /* UDP csum disabled */
> diff --git a/tools/testing/selftests/net/udpgso.c b/tools/testing/selftests/net/udpgso.c
> index 3f2fca02fec5..fb73f1c331fb 100644
> --- a/tools/testing/selftests/net/udpgso.c
> +++ b/tools/testing/selftests/net/udpgso.c
> @@ -102,6 +102,13 @@ struct testcase testcases_v4[] = {
>  		.gso_len = CONST_MSS_V4,
>  		.r_num_mss = 1,
>  	},
> +	{
> +		/* datalen <= MSS < gso_len: will fall back to no GSO */
> +		.tlen = CONST_MSS_V4,
> +		.gso_len = CONST_MSS_V4 + 1,
> +		.r_num_mss = 0,
> +		.r_len_last = CONST_MSS_V4,
> +	},

Please also add a test where datalen > MSS < gso_len (with .tfail = true).

>  	{
>  		/* send a single MSS + 1B */
>  		.tlen = CONST_MSS_V4 + 1,
> @@ -205,6 +212,13 @@ struct testcase testcases_v6[] = {
>  		.gso_len = CONST_MSS_V6,
>  		.r_num_mss = 1,
>  	},
> +	{
> +		/* datalen <= MSS < gso_len: will fall back to no GSO */
> +		.tlen = CONST_MSS_V6,
> +		.gso_len = CONST_MSS_V6 + 1,
> +		.r_num_mss = 0,
> +		.r_len_last = CONST_MSS_V6,
> +	},
>  	{
>  		/* send a single MSS + 1B */
>  		.tlen = CONST_MSS_V6 + 1,
> -- 
> 2.30.2
> 
>
Yan Zhai Jan. 30, 2025, 6:34 p.m. UTC | #2
On Thu, Jan 30, 2025 at 9:35 AM Willem de Bruijn
<willemdebruijn.kernel@gmail.com> wrote:
>
> Yan Zhai wrote:
> > Commit 4094871db1d6 ("udp: only do GSO if # of segs > 1") avoided GSO
> > for small packets. But the kernel currently dismisses GSO requests only
> > after checking MTU/PMTU on gso_size. This means any packets, regardless
> > of their payload sizes, could be dropped when PMTU becomes smaller than
> > requested gso_size. We encountered this issue in production and it
> > caused a reliability problem that new QUIC connection cannot be
> > established before PMTU cache expired, while non GSO sockets still
> > worked fine at the same time.
> >
> > Ideally, do not check any GSO related constraints when payload size is
> > smaller than requested gso_size, and return EMSGSIZE instead of EINVAL
> > on MTU/PMTU check failure to be more specific on the error cause.
> >
> > Fixes: 4094871db1d6 ("udp: only do GSO if # of segs > 1")
> > Signed-off-by: Yan Zhai <yan@cloudflare.com>
> > --
> > v1->v2: add a missing MTU check when fall back to no GSO mode suggested
> > by Willem de Bruijn <willemdebruijn.kernel@gmail.com>; Fixed up commit
> > message to be more precise.
> >
> > v1: https://lore.kernel.org/all/Z5cgWh%2F6bRQm9vVU@debian.debian/
> > ---
> >  net/ipv4/udp.c                       | 28 +++++++++++++++++++---------
> >  net/ipv6/udp.c                       | 28 +++++++++++++++++++---------
> >  tools/testing/selftests/net/udpgso.c | 14 ++++++++++++++
> >  3 files changed, 52 insertions(+), 18 deletions(-)
> >
> > diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> > index c472c9a57cf6..0b5010238d05 100644
> > --- a/net/ipv4/udp.c
> > +++ b/net/ipv4/udp.c
> > @@ -1141,9 +1141,20 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
> >               const int hlen = skb_network_header_len(skb) +
> >                                sizeof(struct udphdr);
> >
> > +             if (datalen <= cork->gso_size) {
> > +                     /*
> > +                      * check MTU again: it's skipped previously when
> > +                      * gso_size != 0
> > +                      */
> > +                     if (hlen + datalen > cork->fragsize) {
> > +                             kfree_skb(skb);
> > +                             return -EMSGSIZE;
> > +                     }
> > +                     goto no_gso;
>
> This is almost the same as the test below.
>
> How about just
>
>     if (hlen + min(cork->gso_size, datalen) > cork->fragsize)
>
> And don't bypass the subsequent checks with a goto, or modify the
> rest of the code.
>
> > +             }
> >               if (hlen + cork->gso_size > cork->fragsize) {
> >                       kfree_skb(skb);
> > -                     return -EINVAL;
> > +                     return -EMSGSIZE;
> >               }
> >               if (datalen > cork->gso_size * UDP_MAX_SEGMENTS) {
> >                       kfree_skb(skb);
> > @@ -1158,17 +1169,16 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
> >                       return -EIO;
> >               }
> >
> > -             if (datalen > cork->gso_size) {
> > -                     skb_shinfo(skb)->gso_size = cork->gso_size;
> > -                     skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> > -                     skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
> > -                                                              cork->gso_size);
> > +             skb_shinfo(skb)->gso_size = cork->gso_size;
> > +             skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> > +             skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
> > +                                                      cork->gso_size);
> >
> > -                     /* Don't checksum the payload, skb will get segmented */
> > -                     goto csum_partial;
> > -             }
> > +             /* Don't checksum the payload, skb will get segmented */
> > +             goto csum_partial;
> >       }
> >
> > +no_gso:
> >       if (is_udplite)                                  /*     UDP-Lite      */
> >               csum = udplite_csum(skb);
> >
> > diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
> > index 6671daa67f4f..d97befa7f80d 100644
> > --- a/net/ipv6/udp.c
> > +++ b/net/ipv6/udp.c
> > @@ -1389,9 +1389,20 @@ static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6,
> >               const int hlen = skb_network_header_len(skb) +
> >                                sizeof(struct udphdr);
> >
> > +             if (datalen <= cork->gso_size) {
> > +                     /*
> > +                      * check MTU again: it's skipped previously when
> > +                      * gso_size != 0
> > +                      */
> > +                     if (hlen + datalen > cork->fragsize) {
> > +                             kfree_skb(skb);
> > +                             return -EMSGSIZE;
> > +                     }
> > +                     goto no_gso;
> > +             }
> >               if (hlen + cork->gso_size > cork->fragsize) {
> >                       kfree_skb(skb);
> > -                     return -EINVAL;
> > +                     return -EMSGSIZE;
> >               }
> >               if (datalen > cork->gso_size * UDP_MAX_SEGMENTS) {
> >                       kfree_skb(skb);
> > @@ -1406,17 +1417,16 @@ static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6,
> >                       return -EIO;
> >               }
> >
> > -             if (datalen > cork->gso_size) {
> > -                     skb_shinfo(skb)->gso_size = cork->gso_size;
> > -                     skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> > -                     skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
> > -                                                              cork->gso_size);
> > +             skb_shinfo(skb)->gso_size = cork->gso_size;
> > +             skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> > +             skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
> > +                                                      cork->gso_size);
> >
> > -                     /* Don't checksum the payload, skb will get segmented */
> > -                     goto csum_partial;
> > -             }
> > +             /* Don't checksum the payload, skb will get segmented */
> > +             goto csum_partial;
> >       }
> >
> > +no_gso:
> >       if (is_udplite)
> >               csum = udplite_csum(skb);
> >       else if (udp_get_no_check6_tx(sk)) {   /* UDP csum disabled */
> > diff --git a/tools/testing/selftests/net/udpgso.c b/tools/testing/selftests/net/udpgso.c
> > index 3f2fca02fec5..fb73f1c331fb 100644
> > --- a/tools/testing/selftests/net/udpgso.c
> > +++ b/tools/testing/selftests/net/udpgso.c
> > @@ -102,6 +102,13 @@ struct testcase testcases_v4[] = {
> >               .gso_len = CONST_MSS_V4,
> >               .r_num_mss = 1,
> >       },
> > +     {
> > +             /* datalen <= MSS < gso_len: will fall back to no GSO */
> > +             .tlen = CONST_MSS_V4,
> > +             .gso_len = CONST_MSS_V4 + 1,
> > +             .r_num_mss = 0,
> > +             .r_len_last = CONST_MSS_V4,
> > +     },
>
> Please also add a test where datalen > MSS < gso_len (with .tfail = true).
>
ACK on both comments.

Thanks
Yan


> >       {
> >               /* send a single MSS + 1B */
> >               .tlen = CONST_MSS_V4 + 1,
> > @@ -205,6 +212,13 @@ struct testcase testcases_v6[] = {
> >               .gso_len = CONST_MSS_V6,
> >               .r_num_mss = 1,
> >       },
> > +     {
> > +             /* datalen <= MSS < gso_len: will fall back to no GSO */
> > +             .tlen = CONST_MSS_V6,
> > +             .gso_len = CONST_MSS_V6 + 1,
> > +             .r_num_mss = 0,
> > +             .r_len_last = CONST_MSS_V6,
> > +     },
> >       {
> >               /* send a single MSS + 1B */
> >               .tlen = CONST_MSS_V6 + 1,
> > --
> > 2.30.2
> >
> >
>
>
diff mbox series

Patch

diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index c472c9a57cf6..0b5010238d05 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -1141,9 +1141,20 @@  static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
 		const int hlen = skb_network_header_len(skb) +
 				 sizeof(struct udphdr);
 
+		if (datalen <= cork->gso_size) {
+			/*
+			 * check MTU again: it's skipped previously when
+			 * gso_size != 0
+			 */
+			if (hlen + datalen > cork->fragsize) {
+				kfree_skb(skb);
+				return -EMSGSIZE;
+			}
+			goto no_gso;
+		}
 		if (hlen + cork->gso_size > cork->fragsize) {
 			kfree_skb(skb);
-			return -EINVAL;
+			return -EMSGSIZE;
 		}
 		if (datalen > cork->gso_size * UDP_MAX_SEGMENTS) {
 			kfree_skb(skb);
@@ -1158,17 +1169,16 @@  static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
 			return -EIO;
 		}
 
-		if (datalen > cork->gso_size) {
-			skb_shinfo(skb)->gso_size = cork->gso_size;
-			skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
-			skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
-								 cork->gso_size);
+		skb_shinfo(skb)->gso_size = cork->gso_size;
+		skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
+		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
+							 cork->gso_size);
 
-			/* Don't checksum the payload, skb will get segmented */
-			goto csum_partial;
-		}
+		/* Don't checksum the payload, skb will get segmented */
+		goto csum_partial;
 	}
 
+no_gso:
 	if (is_udplite)  				 /*     UDP-Lite      */
 		csum = udplite_csum(skb);
 
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 6671daa67f4f..d97befa7f80d 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -1389,9 +1389,20 @@  static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6,
 		const int hlen = skb_network_header_len(skb) +
 				 sizeof(struct udphdr);
 
+		if (datalen <= cork->gso_size) {
+			/*
+			 * check MTU again: it's skipped previously when
+			 * gso_size != 0
+			 */
+			if (hlen + datalen > cork->fragsize) {
+				kfree_skb(skb);
+				return -EMSGSIZE;
+			}
+			goto no_gso;
+		}
 		if (hlen + cork->gso_size > cork->fragsize) {
 			kfree_skb(skb);
-			return -EINVAL;
+			return -EMSGSIZE;
 		}
 		if (datalen > cork->gso_size * UDP_MAX_SEGMENTS) {
 			kfree_skb(skb);
@@ -1406,17 +1417,16 @@  static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6,
 			return -EIO;
 		}
 
-		if (datalen > cork->gso_size) {
-			skb_shinfo(skb)->gso_size = cork->gso_size;
-			skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
-			skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
-								 cork->gso_size);
+		skb_shinfo(skb)->gso_size = cork->gso_size;
+		skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
+		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
+							 cork->gso_size);
 
-			/* Don't checksum the payload, skb will get segmented */
-			goto csum_partial;
-		}
+		/* Don't checksum the payload, skb will get segmented */
+		goto csum_partial;
 	}
 
+no_gso:
 	if (is_udplite)
 		csum = udplite_csum(skb);
 	else if (udp_get_no_check6_tx(sk)) {   /* UDP csum disabled */
diff --git a/tools/testing/selftests/net/udpgso.c b/tools/testing/selftests/net/udpgso.c
index 3f2fca02fec5..fb73f1c331fb 100644
--- a/tools/testing/selftests/net/udpgso.c
+++ b/tools/testing/selftests/net/udpgso.c
@@ -102,6 +102,13 @@  struct testcase testcases_v4[] = {
 		.gso_len = CONST_MSS_V4,
 		.r_num_mss = 1,
 	},
+	{
+		/* datalen <= MSS < gso_len: will fall back to no GSO */
+		.tlen = CONST_MSS_V4,
+		.gso_len = CONST_MSS_V4 + 1,
+		.r_num_mss = 0,
+		.r_len_last = CONST_MSS_V4,
+	},
 	{
 		/* send a single MSS + 1B */
 		.tlen = CONST_MSS_V4 + 1,
@@ -205,6 +212,13 @@  struct testcase testcases_v6[] = {
 		.gso_len = CONST_MSS_V6,
 		.r_num_mss = 1,
 	},
+	{
+		/* datalen <= MSS < gso_len: will fall back to no GSO */
+		.tlen = CONST_MSS_V6,
+		.gso_len = CONST_MSS_V6 + 1,
+		.r_num_mss = 0,
+		.r_len_last = CONST_MSS_V6,
+	},
 	{
 		/* send a single MSS + 1B */
 		.tlen = CONST_MSS_V6 + 1,