This is a request for comments.
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be made
not to negatively affect competing traffic in an unfair manner.
Note: Current patch set depends on a recently submitted patch for
tcp_skb_cb (tcp: refactor struct tcp_skb_cb: http://patchwork.ozlabs.org/patch/510674)
These patches have been tested with as set of packetdrill scripts located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as in the paper
"Latency and Fairness Trade-Off for Thin Streams using Redundant Data
Bundling in TCP"[2].
[1] http://home.ifi.uio.no/paalh/students/BendikOpstad.pdf
[2] http://home.ifi.uio.no/bendiko/rdb_fairness_tradeoff.pdf
Bendik Rønning Opstad (2):
tcp: Add DPIFL thin stream detection mechanism
tcp: Add Redundant Data Bundling (RDB)
Documentation/networking/ip-sysctl.txt | 23 +++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 9 +-
include/net/tcp.h | 34 ++++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 3 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 35 ++++
net/ipv4/tcp.c | 19 ++-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 11 +-
net/ipv4/tcp_rdb.c | 281 +++++++++++++++++++++++++++++++++
12 files changed, 415 insertions(+), 8 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
--
1.9.1
The existing mechanism for detecting thin streams (tcp_stream_is_thin)
is based on a static limit of less than 4 packets in flight. This treats
streams differently depending on the connections RTT, such that a stream
on a high RTT link may never be considered thin, whereas the same
application would produce a stream that would always be thin in a low RTT
scenario (e.g. data center).
By calculating a dynamic packets in flight limit (DPIFL), the thin stream
detection will be independent of the RTT and treat streams equally based
on the transmission pattern, i.e. the inter-transmission time (ITT).
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 8 ++++++++
include/linux/tcp.h | 6 ++++++
include/net/tcp.h | 20 ++++++++++++++++++++
net/ipv4/sysctl_net_ipv4.c | 9 +++++++++
net/ipv4/tcp.c | 3 +++
5 files changed, 46 insertions(+)
@@ -700,6 +700,14 @@ tcp_thin_dupack - BOOLEAN Documentation/networking/tcp-thin.txt Default: 0+tcp_thin_dpifl_itt_lower_bound - INTEGER+ Controls the lower bound for ITT (inter-transmission time) threshold+ for when a stream is considered thin. The value is specified in+ microseconds, and may not be lower than 10000 (10 ms). This theshold+ is used to calculate a dynamic packets in flight limit (DPIFL) which+ is used to classify whether a stream is thin.+ Default: 10000+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -269,6 +269,12 @@ struct tcp_sock {structsk_buff*lost_skb_hint;structsk_buff*retransmit_skb_hint;+/* The limit used to identify when a stream is thin based in a minimum+*allowedinter-transmissiontime(ITT)inmicroseconds.Thisisused+*todynamicallycalculateamaxpacketsinflightlimit(DPIFL).+*/+intthin_dpifl_itt_lower_bound;+/* OOO segments go in this list. Note that socket lock must be held,*aswedonotusesk_buff_headlock.*/
@@ -42,6 +42,7 @@ static int tcp_syn_retries_min = 1;staticinttcp_syn_retries_max=MAX_TCP_SYNCNT;staticintip_ping_group_range_min[]={0,0};staticintip_ping_group_range_max[]={GID_T_MAX,GID_T_MAX};+staticinttcp_thin_dpifl_itt_lower_bound_min=TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN;/* Update system visible IP port range */staticvoidset_local_port_range(structnet*net,intrange[2])
RDB is a mechanism that enables a TCP sender to bundle redundant
(already sent) data with TCP packets containing new data. By bundling
(retransmitting) already sent data with each TCP packet containing new
data, the connection will be more resistant to sporadic packet loss
which reduces the application layer latency significantly in congested
scenarios.
The main functionality added:
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
o When packets are scheduled for transmission, RDB replaces the SKB to
be sent with a modified SKB containing the redundant data of
previously sent data segments from the TCP output queue.
o RDB will only be used for streams classified as thin by the function
tcp_stream_is_thin_dpifl(). This enforces a lower bound on the ITT
for streams that may benefit from RDB, controlled by the sysctl
variable tcp_thin_dpifl_itt_lower_bound.
RDB is enabled on a connection with the socket option TCP_RDB, or on all
new connections by setting the sysctl variable tcp_rdb=1.
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 15 ++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 14 ++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 3 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 26 +++
net/ipv4/tcp.c | 16 +-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 11 +-
net/ipv4/tcp_rdb.c | 281 +++++++++++++++++++++++++++++++++
12 files changed, 369 insertions(+), 8 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
@@ -708,6 +708,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.+ Default: 0++tcp_rdb_max_bytes - INTEGER+ Enable restriction on how many bytes an RDB packet can contain.+ This is the total amount of payload including the new unsent data.+ Default: 0++tcp_rdb_max_skbs - INTEGER+ Enable restriction on how many previous SKBs in the output queue+ RDB may include data from. A value of 1 will restrict bundling to+ only the data from the last packet that was sent.+ Default: 1+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -771,6 +781,7 @@ struct tcp_skb_cb {union{struct{/* There is space for up to 20 bytes */+__u32rdb_start_seq;/* Start seq of rdb data bundled */}tx;/* only used for outgoing skbs */union{structinet_skb_parmh4;
@@ -115,6 +115,7 @@ enum {#define TCP_CC_INFO 26 /* Get Congestion Control (optional) info */#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */+#define TCP_RDB 29 /* Enable RDB mechanism */structtcp_repair_opt{__u32opt_code;
@@ -2113,9 +2113,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,break;}-if(unlikely(tcp_transmit_skb(sk,skb,1,gfp)))+if(unlikely(tcp_sk(sk)->rdb)){+if(tcp_transmit_rdb_skb(sk,skb,mss_now,gfp))+break;+}elseif(unlikely(tcp_transmit_skb(sk,skb,1,gfp))){break;-+}repair:/* Advance the send_head. This one is sent out.*Thiscallwillincrementpackets_out.
@@ -0,0 +1,281 @@+#include<linux/skbuff.h>+#include<net/tcp.h>++intsysctl_tcp_rdb_max_bytes__read_mostly;+intsysctl_tcp_rdb_max_skbs__read_mostly=1;++/**+*rdb_check_rtx_queue_loss()-Performlossdetectionbyanalysingacks.+*@sk:thesocket.+*@seq_acked:Thesequencenumberthatwasacked.+*+*Return:Thenumberofpacketsthatarepresumedtobelost.+*/+staticintrdb_check_rtx_queue_loss(structsock*sk,u32seq_acked)+{+conststructtcp_sock*tp=tcp_sk(sk);+structsk_buff*skb,*tmp,*prev_skb=NULL;+structsk_buff*send_head=tcp_send_head(sk);+structtcp_skb_cb*scb;+boolfully_acked=true;+intlost_count=0;++tcp_for_write_queue(skb,sk){+if(skb==send_head)+break;++scb=TCP_SKB_CB(skb);++/* Determine how many packets and what bytes were acked, no TSO+*support+*/+if(after(scb->end_seq,tp->snd_una)){+if(tcp_skb_pcount(skb)==1||+!after(tp->snd_una,scb->seq)){+break;+}++/* We do not handle SKBs with gso_segs */+if(tcp_skb_pcount(skb))+break;+fully_acked=false;+}++/* Acks up to this SKB */+if(scb->end_seq==seq_acked){+/* This SKB was sent with RDB data, and acked data on+*previousskb+*/+if(TCP_SKB_CB(skb)->tx.rdb_start_seq!=scb->seq&&+prev_skb){+/* Find how many previous packets were Acked+*(andtherebylost)+*/+tcp_for_write_queue(tmp,sk){+/* We have reached the acked SKB */+if(tmp==skb)+break;+lost_count++;+}+}+break;+}+if(!fully_acked)+break;+prev_skb=skb;+}+returnlost_count;+}++/**+*rdb_in_ack_event()-Initiatelossdetection+*@sk:thesocket+*@flags:Theflags+*/+voidrdb_ack_event(structsock*sk,u32flags)+{+conststructtcp_sock*tp=tcp_sk(sk);++if(rdb_check_rtx_queue_loss(sk,tp->snd_una))+tcp_enter_cwr(sk);+}++/**+*skb_append_data()-CopydatafromanSKBtotheendofanother+*@from_skb:TheSKBtocopydatafrom+*@to_skb:TheSKBtocopydatato+*/+staticintskb_append_data(structsk_buff*from_skb,structsk_buff*to_skb)+{+/* Copy the linear data and the data from the frags into the linear page+*bufferofto_skb.+*/+if(WARN_ON(skb_copy_bits(from_skb,0,+skb_put(to_skb,from_skb->len),+from_skb->len))){+gotofault;+}++TCP_SKB_CB(to_skb)->end_seq=TCP_SKB_CB(from_skb)->end_seq;++if(from_skb->ip_summed==CHECKSUM_PARTIAL)+to_skb->ip_summed=CHECKSUM_PARTIAL;++if(to_skb->ip_summed!=CHECKSUM_PARTIAL)+to_skb->csum=csum_block_add(to_skb->csum,from_skb->csum,+to_skb->len);+return0;+fault:+return-EFAULT;+}++/**+*rdb_build_skb()-BuildsthenewRDBSKBandcopiesallthedataintothe+*linearpagebuffer.+*@sk:thesocket+*@xmit_skb:ThisistheSKBthattcp_write_xmitwantstosend+*@first_skb:ThefirstSKBintheoutputqueuewewillbundle+*@gfp_mask:Thegfp_tallocation+*@bytes_in_rdb_skb:Thetotalnumberofdatabytesforthenewrdb_skb+*(NEW+Redundant)+*+*Return:AnewSKBcontainingredundantdata,orNULLifmemoryallocation+*failed+*/+staticstructsk_buff*rdb_build_skb(conststructsock*sk,+structsk_buff*xmit_skb,+structsk_buff*first_skb,+u32bytes_in_rdb_skb,+gfp_tgfp_mask)+{+structsk_buff*rdb_skb,*tmp_skb;++rdb_skb=sk_stream_alloc_skb((structsock*)sk,+(int)bytes_in_rdb_skb,+gfp_mask,true);+if(!rdb_skb)+returnNULL;+copy_skb_header(rdb_skb,xmit_skb);+rdb_skb->ip_summed=xmit_skb->ip_summed;++TCP_SKB_CB(rdb_skb)->seq=TCP_SKB_CB(first_skb)->seq;+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(rdb_skb)->seq;++tmp_skb=first_skb;++tcp_for_write_queue_from(tmp_skb,sk){+/* Copy data from tmp_skb to rdb_skb */+if(skb_append_data(tmp_skb,rdb_skb))+returnNULL;+/* We are at the last skb that should be included (The unsent+*one)+*/+if(tmp_skb==xmit_skb)+break;+}+returnrdb_skb;+}++/**+*rdb_can_bundle_check()-checkifredundantdatacanbebundled+*@sk:thesocket+*@xmit_skb:TheSKBprocessedfortransmissionbytheoutputengine+*@mss_now:Thecurrentmssvalue+*@bytes_in_rdb_skb:Willcontaintheresultingnumberofbytestobundle+*atexit.+*@skbs_to_bundle_count:ThetotalnumberofSKBstobeinthebundle+*+*Traversestheentirewritequeueandchecksifanyun-ackeddata+*maybebundled.+*+*Return:ThefirstSKBtobeinthebundle,orNULLifnobundling+*/+staticstructsk_buff*rdb_can_bundle_check(conststructsock*sk,+structsk_buff*xmit_skb,+unsignedintmss_now,+u32*bytes_in_rdb_skb,+u32*skbs_to_bundle_count)+{+structsk_buff*first_to_bundle=NULL;+structsk_buff*tmp,*skb=xmit_skb->prev;+u32skbs_in_bundle_count=1;/* 1 to account for current skb */+u32byte_count=xmit_skb->len;++/* We start at the skb before xmit_skb, and go backwards in the list.*/+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+/* Not enough room to bundle data from this SKB */+if((byte_count+skb->len)>mss_now)+break;++if(sysctl_tcp_rdb_max_bytes&&+((byte_count+skb->len)>sysctl_tcp_rdb_max_bytes))+break;++if(sysctl_tcp_rdb_max_skbs&&+(skbs_in_bundle_count>sysctl_tcp_rdb_max_skbs))+break;++byte_count+=skb->len;+skbs_in_bundle_count++;+first_to_bundle=skb;+}+*bytes_in_rdb_skb=byte_count;+*skbs_to_bundle_count=skbs_in_bundle_count;+returnfirst_to_bundle;+}++/**+*create_rdb_skb()-TrytocreateRDBSKB+*@sk:thesocket+*@xmit_skb:TheSKBthatshouldbesent+*@mss_now:CurrentMSS+*@gfp_mask:Thegfp_tallocation+*+*Return:AnewSKBcontainingredundantdata,orNULLifnobundlingcouldbe+*performed+*/+structsk_buff*create_rdb_skb(conststructsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,u32*bytes_in_rdb_skb,+gfp_tgfp_mask)+{+u32skb_in_bundle_count;+structsk_buff*first_to_bundle;++if(skb_queue_is_first(&sk->sk_write_queue,xmit_skb))+returnNULL;++/* No bundling on FIN packet */+if(TCP_SKB_CB(xmit_skb)->tcp_flags&TCPHDR_FIN)+returnNULL;++/* Find number of (previous) SKBs to get data from */+first_to_bundle=rdb_can_bundle_check(sk,xmit_skb,mss_now,+bytes_in_rdb_skb,+&skb_in_bundle_count);+if(!first_to_bundle)+returnNULL;++/* Create an SKB that contains the data from 'skb_in_bundle_count'+*SKBs.+*/+returnrdb_build_skb(sk,xmit_skb,first_to_bundle,+*bytes_in_rdb_skb,gfp_mask);+}++/**+*tcp_transmit_rdb_skb()-TrytocreateandsendanRDBpacket+*@sk:thesocket+*@xmit_skb:TheSKBprocessedfortransmissionbytheoutputengine+*@mss_now:CurrentMSS+*@gfp_mask:Thegfp_tallocation+*+*Return:0ifsuccessfullysentpacket,else!=0+*/+inttcp_transmit_rdb_skb(structsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,gfp_tgfp_mask)+{+conststructtcp_sock*tp=tcp_sk(sk);+structsk_buff*rdb_skb=NULL;+u32bytes_in_rdb_skb=0;/* May be used for statistical purposes */++/* How we detect that RDB was used. When equal, no RDB data was sent */+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(xmit_skb)->seq;++if(tcp_stream_is_thin_dpifl(tp)){+rdb_skb=create_rdb_skb(sk,xmit_skb,mss_now,+&bytes_in_rdb_skb,gfp_mask);+if(!rdb_skb)+gotoxmit_default;++/* Set tstamp for SKB in output queue, because tcp_transmit_skb+*willdothisfortherdb_skbandnottheSKBintheoutput+*queue(xmit_skb).+*/+skb_mstamp_get(&xmit_skb->skb_mstamp);+rdb_skb->skb_mstamp=xmit_skb->skb_mstamp;+returntcp_transmit_skb(sk,rdb_skb,0,gfp_mask);+}+xmit_default:+/* Transmit the unmodified SKB from output queue */+returntcp_transmit_skb(sk,xmit_skb,1,gfp_mask);+}
From: Eric Dumazet <hidden> Date: 2015-10-23 21:44:21
On Fri, 2015-10-23 at 22:50 +0200, Bendik Rønning Opstad wrote:
+/**
+ * tcp_stream_is_thin_dpifl() - Tests if the stream is thin based on dynamic PIF
+ * limit
+ * @tp: the tcp_sock struct
+ *
+ * Return: true if current packets in flight (PIF) count is lower than
+ * the dynamic PIF limit, else false
+ */
+static inline bool tcp_stream_is_thin_dpifl(const struct tcp_sock *tp)
+{
+ u64 dpif_lim = tp->srtt_us >> 3;
+ /* Div by is_thin_min_itt_lim, the minimum allowed ITT
+ * (Inter-transmission time) in usecs.
+ */
+ do_div(dpif_lim, tp->thin_dpifl_itt_lower_bound);
+ return tcp_packets_in_flight(tp) < dpif_lim;
+}
+
This is very strange :
You are using a do_div() while both operands are 32bits. A regular
divide would be ok :
u32 dpif_lim = (tp->srtt_us >> 3) / tp->thin_dpifl_itt_lower_bound;
But then, you can avoid the divide by using a multiply, less expensive :
return (u64)tcp_packets_in_flight(tp) * tp->thin_dpifl_itt_lower_bound <
(tp->srtt_us >> 3);
On Fri, Oct 23, 2015 at 1:50 PM, Bendik Rønning Opstad
[off-list ref] wrote:
This is a request for comments.
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be made
not to negatively affect competing traffic in an unfair manner.
Note: Current patch set depends on a recently submitted patch for
tcp_skb_cb (tcp: refactor struct tcp_skb_cb: http://patchwork.ozlabs.org/patch/510674)
These patches have been tested with as set of packetdrill scripts located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as in the paper
What's the difference between RDB and TCP repacketization
(http://flylib.com/books/en/3.223.1.226/1/) ?
Reading the blog page, I am concerned the amount of
change (esp on fast path) just to bundle new writes during timeout &
retransmit, for a specific type of application? why not just send X
packets with total bytes < MSS on timeout..
From: Jonas Markussen <hidden> Date: 2015-10-24 11:33:36
On 24 Oct 2015, at 08:11, Yuchung Cheng [off-list ref] wrote:
On Fri, Oct 23, 2015 at 1:50 PM, Bendik Rønning Opstad
[off-list ref] wrote:
quoted
This is a request for comments.
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be made
not to negatively affect competing traffic in an unfair manner.
Note: Current patch set depends on a recently submitted patch for
tcp_skb_cb (tcp: refactor struct tcp_skb_cb: http://patchwork.ozlabs.org/patch/510674)
These patches have been tested with as set of packetdrill scripts located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as in the paper
What's the difference between RDB and TCP repacketization
(http://flylib.com/books/en/3.223.1.226/1/) ?
Reading the blog page, I am concerned the amount of
change (esp on fast path) just to bundle new writes during timeout &
retransmit, for a specific type of application? why not just send X
packets with total bytes < MSS on timeout..
Repacketization is only on retransmissions; RDB bundles previously sent segments with the next “normal” transmission instead.
This makes the flow recover the lost segment before a retransmission is triggered by an RTO or fast retransmit.
From: Eric Dumazet <hidden> Date: 2015-10-24 12:57:31
On Sat, 2015-10-24 at 08:00 +0000, Jonas Markussen wrote:
Repacketization is only on retransmissions; RDB bundles previously sent segments with the next “normal” transmission instead.
This makes the flow recover the lost segment before a retransmission is triggered by an RTO or fast retransmit.
Thank you for this very high quality patch submission.
Please give us a few days for proper evaluation.
Thanks !
On Friday, October 23, 2015 02:44:14 PM Eric Dumazet wrote:
On Fri, 2015-10-23 at 22:50 +0200, Bendik Rønning Opstad wrote:
quoted
+/**
+ * tcp_stream_is_thin_dpifl() - Tests if the stream is thin based on dynamic PIF
+ * limit
+ * @tp: the tcp_sock struct
+ *
+ * Return: true if current packets in flight (PIF) count is lower than
+ * the dynamic PIF limit, else false
+ */
+static inline bool tcp_stream_is_thin_dpifl(const struct tcp_sock *tp)
+{
+ u64 dpif_lim = tp->srtt_us >> 3;
+ /* Div by is_thin_min_itt_lim, the minimum allowed ITT
+ * (Inter-transmission time) in usecs.
+ */
+ do_div(dpif_lim, tp->thin_dpifl_itt_lower_bound);
+ return tcp_packets_in_flight(tp) < dpif_lim;
+}
+
This is very strange :
You are using a do_div() while both operands are 32bits. A regular
divide would be ok :
u32 dpif_lim = (tp->srtt_us >> 3) / tp->thin_dpifl_itt_lower_bound;
But then, you can avoid the divide by using a multiply, less expensive :
return (u64)tcp_packets_in_flight(tp) * tp->thin_dpifl_itt_lower_bound <
(tp->srtt_us >> 3);
You are of course correct. Will fix this and use multiply. Thanks.
On Fri, Oct 23, 2015 at 4:50 PM, Bendik Rønning Opstad
[off-list ref] wrote:
quoted hunk
@@ -2409,6 +2412,15 @@ static int do_tcp_setsockopt(struct sock *sk, int level,
...
+ case TCP_RDB:
+ if (val < 0 || val > 1) {
+ err = -EINVAL;
+ } else {
+ tp->rdb = val;
+ tp->nonagle = val;
The semantics of the tp->nonagle bits are already a bit complex. My
sense is that having a setsockopt of TCP_RDB transparently modify the
nagle behavior is going to add more extra complexity and unanticipated
behavior than is warranted given the slight possible gain in
convenience to the app writer. What about a model where the
application user just needs to remember to call
setsockopt(TCP_NODELAY) if they want the TCP_RDB behavior to be
sensible? I see your nice tests at
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b
are already doing that. And my sense is that likewise most
well-engineered "thin stream" apps will already be using
setsockopt(TCP_NODELAY). Is that workable?
neal
From: Andreas Petlund <hidden> Date: 2015-10-26 21:52:40
On 26 Oct 2015, at 15:50, Neal Cardwell [off-list ref] wrote:
On Fri, Oct 23, 2015 at 4:50 PM, Bendik Rønning Opstad
[off-list ref] wrote:
quoted
@@ -2409,6 +2412,15 @@ static int do_tcp_setsockopt(struct sock *sk, int level,
...
quoted
+ case TCP_RDB:
+ if (val < 0 || val > 1) {
+ err = -EINVAL;
+ } else {
+ tp->rdb = val;
+ tp->nonagle = val;
The semantics of the tp->nonagle bits are already a bit complex. My
sense is that having a setsockopt of TCP_RDB transparently modify the
nagle behavior is going to add more extra complexity and unanticipated
behavior than is warranted given the slight possible gain in
convenience to the app writer. What about a model where the
application user just needs to remember to call
setsockopt(TCP_NODELAY) if they want the TCP_RDB behavior to be
sensible? I see your nice tests at
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b
are already doing that. And my sense is that likewise most
well-engineered "thin stream" apps will already be using
setsockopt(TCP_NODELAY). Is that workable?
We have been discussing this a bit back and forth. Your suggestion would be the right thing to keep the nagle semantics less complex and to educate developers in the intrinsics of the transport.
We ended up choosing to implicitly disable nagle since it
1) is incompatible with the logic of RDB.
2) leaving it up to the developer to read the documentation and register the line saying that "failing to set TCP_NODELAY will void the RDB latency gain" will increase the chance of misconfigurations leading to deployment with no effect.
The hope was to help both the well-engineered thin-stream apps and the ones deployed by developers with less detailed knowledge of the transport.
-Andreas
On Mon, Oct 26, 2015 at 2:35 PM, Andreas Petlund [off-list ref] wrote:
quoted
On 26 Oct 2015, at 15:50, Neal Cardwell [off-list ref] wrote:
On Fri, Oct 23, 2015 at 4:50 PM, Bendik Rønning Opstad
[off-list ref] wrote:
quoted
@@ -2409,6 +2412,15 @@ static int do_tcp_setsockopt(struct sock *sk, int level,
...
quoted
+ case TCP_RDB:
+ if (val < 0 || val > 1) {
+ err = -EINVAL;
+ } else {
+ tp->rdb = val;
+ tp->nonagle = val;
The semantics of the tp->nonagle bits are already a bit complex. My
sense is that having a setsockopt of TCP_RDB transparently modify the
nagle behavior is going to add more extra complexity and unanticipated
behavior than is warranted given the slight possible gain in
convenience to the app writer. What about a model where the
application user just needs to remember to call
setsockopt(TCP_NODELAY) if they want the TCP_RDB behavior to be
sensible? I see your nice tests at
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b
are already doing that. And my sense is that likewise most
well-engineered "thin stream" apps will already be using
setsockopt(TCP_NODELAY). Is that workable?
We have been discussing this a bit back and forth. Your suggestion would be the right thing to keep the nagle semantics less complex and to educate developers in the intrinsics of the transport.
We ended up choosing to implicitly disable nagle since it
1) is incompatible with the logic of RDB.
2) leaving it up to the developer to read the documentation and register the line saying that "failing to set TCP_NODELAY will void the RDB latency gain" will increase the chance of misconfigurations leading to deployment with no effect.
The hope was to help both the well-engineered thin-stream apps and the ones deployed by developers with less detailed knowledge of the transport.
but would RDB be voided if this developer turns on RDB then turns on
Nagle later?
From: Jonas Markussen <hidden> Date: 2015-10-27 19:16:39
On 26 Oct 2015, at 22:58, Yuchung Cheng [off-list ref] wrote:
but would RDB be voided if this developer turns on RDB then turns on
Nagle later?
The short answer is answer is "kind of"
My understanding is that Nagle will delay segments until they're
either MSS-sized or until segments "down the pipe" are acknowledged.
As RDB isn't able to bundle if the payload is more than MSS/2, only
an application that that sends data less frequent than an RTT would
still theoretically benefit from RDB even if Nagle is on.
However, in my opinion this is a scenario where Nagle itself is void:
If you transmit more rarely than the RTT, enabling Nagle makes no
difference.
If you transfer more frequent than the RTT, enabling Nagle makes
RDB void.
-Jonas
On Monday, October 26, 2015 02:58:03 PM Yuchung Cheng wrote:
On Mon, Oct 26, 2015 at 2:35 PM, Andreas Petlund [off-list ref] wrote:
quoted
quoted
On 26 Oct 2015, at 15:50, Neal Cardwell [off-list ref] wrote:
On Fri, Oct 23, 2015 at 4:50 PM, Bendik Rønning Opstad
[off-list ref] wrote:
quoted
@@ -2409,6 +2412,15 @@ static int do_tcp_setsockopt(struct sock *sk,
int level,> >
...
quoted
+ case TCP_RDB:
+ if (val < 0 || val > 1) {
+ err = -EINVAL;
+ } else {
+ tp->rdb = val;
+ tp->nonagle = val;
The semantics of the tp->nonagle bits are already a bit complex. My
sense is that having a setsockopt of TCP_RDB transparently modify the
nagle behavior is going to add more extra complexity and unanticipated
behavior than is warranted given the slight possible gain in
convenience to the app writer. What about a model where the
application user just needs to remember to call
setsockopt(TCP_NODELAY) if they want the TCP_RDB behavior to be
sensible? I see your nice tests at
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b
7d8baf703b2c2ac1b> >
are already doing that. And my sense is that likewise most
well-engineered "thin stream" apps will already be using
setsockopt(TCP_NODELAY). Is that workable?
This is definitely workable. I agree that it may not be an ideal solution to
have TCP_RDB disable Nagle, however, it would be useful with a way to easily
enable RDB and disable Nagle.
quoted
We have been discussing this a bit back and forth. Your suggestion would
be the right thing to keep the nagle semantics less complex and to
educate developers in the intrinsics of the transport.
We ended up choosing to implicitly disable nagle since it
1) is incompatible with the logic of RDB.
2) leaving it up to the developer to read the documentation and register
the line saying that "failing to set TCP_NODELAY will void the RDB
latency gain" will increase the chance of misconfigurations leading to
deployment with no effect.
The hope was to help both the well-engineered thin-stream apps and the
ones deployed by developers with less detailed knowledge of the
transport.
but would RDB be voided if this developer turns on RDB then turns on
Nagle later?
It would (to a large degree), but I believe that's ok? The intention with also
disabling Nagle is not to remove control from the application writer, so if
TCP_RDB disables Nagle, they should not be prevented from explicitly enabling
Nagle after enabling RDB.
The idea is to make it as easy as possible for the application writer, and
since Nagle is on by default, it makes sense to change this behavior when the
application has indicated that it values low latencies.
Would a solution with multiple option values to TCP_RDB be acceptable? E.g.
0 = Disable
1 = Enable RDB
2 = Enable RDB and disable Nagle
If the sysctl tcp_rdb accepts the same values, setting the sysctl to 2 would
allow to use and test RDB (with Nagle off) on applications that haven't
explicitly disabled Nagle, which would make the sysctl tcp_rdb even more useful.
Instead of having TCP_RDB modify Nagle, would it be better/acceptable to have a
separate socket option (e.g. TCP_THIN/TCP_THIN_LOW_LATENCY) that enables RDB and
disables Nagle? e.g.
0 = Use default system options?
1 = Enable RDB and disable Nagle
This would separate the modification of Nagle from the TCP_RDB socket option and
make it cleaner?
Such an option could also enable other latency-reducing options like
TCP_THIN_LINEAR_TIMEOUTS and TCP_THIN_DUPACK:
2 = Enable RDB, TCP_THIN_LINEAR_TIMEOUTS, TCP_THIN_DUPACK, and disable Nagle
Bendik
From: David Laight <hidden> Date: 2015-11-02 09:18:39
From: Bendik Rønning Opstad
Sent: 29 October 2015 22:54
...
quoted
quoted
quoted
The semantics of the tp->nonagle bits are already a bit complex. My
sense is that having a setsockopt of TCP_RDB transparently modify the
nagle behavior is going to add more extra complexity and unanticipated
behavior than is warranted given the slight possible gain in
convenience to the app writer. What about a model where the
application user just needs to remember to call
setsockopt(TCP_NODELAY) if they want the TCP_RDB behavior to be
sensible? I see your nice tests at
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b
7d8baf703b2c2ac1b> >
are already doing that. And my sense is that likewise most
well-engineered "thin stream" apps will already be using
setsockopt(TCP_NODELAY). Is that workable?
This is definitely workable. I agree that it may not be an ideal solution to
have TCP_RDB disable Nagle, however, it would be useful with a way to easily
enable RDB and disable Nagle.
If enabling RDB disables Nagle, then what happens when you turn RDB back off?
David
From: David Laight <hidden> Date: 2015-11-02 09:38:11
From: Bendik Rønning Opstad
Sent: 23 October 2015 21:50
RDB is a mechanism that enables a TCP sender to bundle redundant
(already sent) data with TCP packets containing new data. By bundling
(retransmitting) already sent data with each TCP packet containing new
data, the connection will be more resistant to sporadic packet loss
which reduces the application layer latency significantly in congested
scenarios.
What sort of traffic flows do you expect this to help?
An ssh (or similar) connection will get additional data to send,
but that sort of data flow needs Nagle in order to reduce the
number of packets sent.
OTOH it might benefit from including unacked data if the Nagle
timer expires.
Being able to set the Nagle timer on a per-connection basis
(or maybe using something based on the RTT instead of 2 secs)
might make packet loss less problematic.
Data flows that already have Nagle disabled (probably anything that
isn't command-response and isn't unidirectional bulk data) are
likely to generate a lot of packets within the RTT.
Resending unacked data will just eat into available network bandwidth
and could easily make any congestion worse.
I think that means you shouldn't resend data more than once, and/or
should make sure that the resent data isn't a significant overhead
on the packet being sent.
David
On Monday, November 02, 2015 09:37:54 AM David Laight wrote:
From: Bendik Rønning Opstad
quoted
Sent: 23 October 2015 21:50
RDB is a mechanism that enables a TCP sender to bundle redundant
(already sent) data with TCP packets containing new data. By bundling
(retransmitting) already sent data with each TCP packet containing new
data, the connection will be more resistant to sporadic packet loss
which reduces the application layer latency significantly in congested
scenarios.
What sort of traffic flows do you expect this to help?
As mentioned in the cover letter, RDB is aimed at reducing the
latencies for "thin-stream" traffic often produced by
latency-sensitive applications. This blog post describes RDB and the
underlying motivation:
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp
Further information is available in the links referred to in the blog
post.
An ssh (or similar) connection will get additional data to send,
but that sort of data flow needs Nagle in order to reduce the
number of packets sent.
Whether an application needs to reduce the number of packets sent
depends on the perspective of who you ask. If low latency is of high
priority for the application it may need to increase the number of
packets sent by disabling Nagle to reduce the segments sojourn times
on the sender side.
As for SSH clients, it seems OpenSSH disables Nagle for interactive
sessions.
OTOH it might benefit from including unacked data if the Nagle
timer expires.
Being able to set the Nagle timer on a per-connection basis
(or maybe using something based on the RTT instead of 2 secs)
might make packet loss less problematic.
There is no timer for Nagle? The current (Minshall variant)
implementation restricts sending a small segment as long as the
previously transmitted packet was small and is not yet ACKed.
Data flows that already have Nagle disabled (probably anything that
isn't command-response and isn't unidirectional bulk data) are
likely to generate a lot of packets within the RTT.
How many packets such applications need to transmit for optimal
latency varies to a great extent. Packets per RTT is not a very useful
metric in this regard, considering the strict dependency on the RTT.
This is why we propose a dynamic packets in flight limit (DPIFL) that
indirectly relies on the application write frequency, i.e. how often
the application performs write systems calls. This limit is used to
ensure that only applications that write data less frequently than a
certain limit may utilize RDB.
Resending unacked data will just eat into available network bandwidth
and could easily make any congestion worse.
I think that means you shouldn't resend data more than once, and/or
should make sure that the resent data isn't a significant overhead
on the packet being sent.
It is important to remember what type of traffic flows we are
discussing. The applications RDB is aimed at helping produce
application-limited flows that transmit small amounts of data, both in
terms of payload per packet and packets per second.
Analysis of traces from latency-sensitive applications producing
traffic with thin-stream characteristics show inter-transmission times
ranging from a few ms (typically 20-30 ms on average) to many hundred
ms.
(http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp/#thin_streams)
Increasing the amount of transmitted data will certainly contribute to
congestion to some degree, but it is not (necessarily) an unreasonable
trade-off considering the relatively small amounts of data such
applications transmit compared to greedy flows.
RDB does not cause more packets to be sent through the network, as it
uses available "free" space in packets already scheduled for
transmission. With a bundling limitation of only one previous segment,
the bandwidth requirement is doubled - accounting for headers it would
be less.
By increasing the BW requirement for an application that produces
relatively little data, we still end up with a low BW requirement.
The suggested minimum lower bound inter-transmission time is 10 ms,
meaning that when an application writes data more frequently than
every 10 ms (on average) it will not be allowed to utilize RDB.
To what degree RDB affects competing traffic will of course depend on
the link capacity and the number of simultaneous flows utilizing RDB.
We have performed tests to asses how RDB affects competing traffic. In
one of the test scenarios, 10 RDB-enabled thin streams and 10 regular
TCP thin streams compete against 5 greedy TCP flows over a shared
bottleneck limited to 5Mbit/s. The results from this test show that by
only bundling one previous segment with each packet (segment size: 120
bytes), the effect on the the competing thin-stream traffic is modest.
(http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp/#latency_test_with_cross_traffic).
Also relevant to the discussion is the paper "Reducing web latency:
the virtue of gentle aggression, (2013)", and one of the presented
mechanisms (called Proactive) which applies redundancy by transmitting
every packet twice. While doubling the bandwidth requirements when
using Proactive, their measurements show negligible effect on the
baseline traffic because, as they explain, the traffic utilizing the
mechanism (Web service traffic in their case) is only a small amount
of the total traffic passing through their servers.
While RDB and the Proactive mechanism have slightly different
approaches, they aim at solving the same basic problem; the increased
latencies caused by the need for normal retransmissions. By
proactively (re)transmitting redundant data they are able to avoid the
need for normal retransmissions to a great extent, which reduces
application layer latency by alleviating head-of-line blocking on the
receiver.
An important property of RDB is that by only using packets already
scheduled for transmission, a limit is naturally imposed when severe
congestion occurs. As soon as loss is detected, resulting in a
reduction of the CWND (i.e. becomes network limited), new data from
the application will be appended to the SKB in the output queue
containing the newest (unsent) data. Depending on the rate at which the
application produces data and the level of congestion (the size of the
CWND), the new data from the application will eventually fill up the
SKBs such that skb->len >= MSS. The result is that there is no "free"
space available to bundle redundant data, effectively disabling RDB
and enforcing a behavior equal to regular TCP.
Bendik
Thank you for this very high quality patch submission.
Please give us a few days for proper evaluation.
Thanks !
Guys, thank you very much for taking the time to evaluate this.
Since there haven't been any more feedback or comments I'll submit an
RFCv2 with a few changes which includes removing the Nagle
modification.
After discussing the Nagle change on setsockopt we realize that it
should be evaluated more thoroughly, and is better left for a later
patch submission.
Bendik
P.S.
Trimming the CC list to only those who have responded as gmail says
I'm spamming :-)
This is a request for comments.
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be made
not to negatively affect competing traffic in an unfair manner.
Note: Current patch set depends on a recently submitted patch for
tcp_skb_cb (tcp: refactor struct tcp_skb_cb: http://patchwork.ozlabs.org/patch/510674)
These patches have been tested with as set of packetdrill scripts located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as in the paper
"Latency and Fairness Trade-Off for Thin Streams using Redundant Data
Bundling in TCP"[2].
[1] http://home.ifi.uio.no/paalh/students/BendikOpstad.pdf
[2] http://home.ifi.uio.no/bendiko/rdb_fairness_tradeoff.pdf
Changes:
v2:
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Change calculation in tcp_stream_is_thin_dpifl based on
feedback from Eric Dumazet.
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed setting nonagle in do_tcp_setsockopt (TCP_RDB)
to reduce complexity as commented by Neal Cardwell.
* Cleaned up loss detection code in rdb_check_rtx_queue_loss
Bendik Rønning Opstad (2):
tcp: Add DPIFL thin stream detection mechanism
tcp: Add Redundant Data Bundling (RDB)
Documentation/networking/ip-sysctl.txt | 23 +++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 35 +++++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 3 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 35 +++++
net/ipv4/tcp.c | 16 +-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 11 +-
net/ipv4/tcp_rdb.c | 271 +++++++++++++++++++++++++++++++++
12 files changed, 397 insertions(+), 8 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
--
1.9.1
The existing mechanism for detecting thin streams (tcp_stream_is_thin)
is based on a static limit of less than 4 packets in flight. This treats
streams differently depending on the connections RTT, such that a stream
on a high RTT link may never be considered thin, whereas the same
application would produce a stream that would always be thin in a low RTT
scenario (e.g. data center).
By calculating a dynamic packets in flight limit (DPIFL), the thin stream
detection will be independent of the RTT and treat streams equally based
on the transmission pattern, i.e. the inter-transmission time (ITT).
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 8 ++++++++
include/net/tcp.h | 21 +++++++++++++++++++++
net/ipv4/sysctl_net_ipv4.c | 9 +++++++++
net/ipv4/tcp.c | 2 ++
4 files changed, 40 insertions(+)
@@ -700,6 +700,14 @@ tcp_thin_dupack - BOOLEAN Documentation/networking/tcp-thin.txt Default: 0+tcp_thin_dpifl_itt_lower_bound - INTEGER+ Controls the lower bound inter-transmission time (ITT) threshold+ for when a stream is considered thin. The value is specified in+ microseconds, and may not be lower than 10000 (10 ms). Based on+ this threshold, a dynamic packets in flight limit (DPIFL) is+ calculated, which is used to classify whether a stream is thin.+ Default: 10000+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -215,6 +215,8 @@ void tcp_time_wait(struct sock *sk, int state, int timeo);/* TCP thin-stream limits */#define TCP_THIN_LINEAR_RETRIES 6 /* After 6 linear retries, do exp. backoff */+/* Lowest possible DPIFL lower bound ITT is 10 ms (10000 usec) */+#define TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN 10000/* TCP initial congestion window as per draft-hkchu-tcpm-initcwnd-01 */#define TCP_INIT_CWND 10
@@ -274,6 +276,7 @@ extern int sysctl_tcp_workaround_signed_windows;externintsysctl_tcp_slow_start_after_idle;externintsysctl_tcp_thin_linear_timeouts;externintsysctl_tcp_thin_dupack;+externintsysctl_tcp_thin_dpifl_itt_lower_bound;externintsysctl_tcp_early_retrans;externintsysctl_tcp_limit_output_bytes;externintsysctl_tcp_challenge_ack_limit;
@@ -1631,6 +1634,24 @@ static inline bool tcp_stream_is_thin(struct tcp_sock *tp)returntp->packets_out<4&&!tcp_in_initial_slowstart(tp);}+/**+*tcp_stream_is_thin_dpifl()-TestsifthestreamisthinbasedondynamicPIF+*limit+*@tp:thetcp_sockstruct+*+*Return:trueifcurrentpacketsinflight(PIF)countislowerthan+*thedynamicPIFlimit,elsefalse+*/+staticinlinebooltcp_stream_is_thin_dpifl(conststructtcp_sock*tp)+{+/* Calculate the maximum allowed PIF limit by dividing the RTT by+*theminimumallowedinter-transmissiontime(ITT).+*TestsifPIF<RTT/ITT-lower-bound+*/+return(u64)tcp_packets_in_flight(tp)*+sysctl_tcp_thin_dpifl_itt_lower_bound<(tp->srtt_us>>3);+}+/* /proc */enumtcp_seq_states{TCP_SEQ_STATE_LISTENING,
@@ -42,6 +42,7 @@ static int tcp_syn_retries_min = 1;staticinttcp_syn_retries_max=MAX_TCP_SYNCNT;staticintip_ping_group_range_min[]={0,0};staticintip_ping_group_range_max[]={GID_T_MAX,GID_T_MAX};+staticinttcp_thin_dpifl_itt_lower_bound_min=TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN;/* Update system visible IP port range */staticvoidset_local_port_range(structnet*net,intrange[2])
RDB is a mechanism that enables a TCP sender to bundle redundant
(already sent) data with TCP packets containing new data. By bundling
(retransmitting) already sent data with each TCP packet containing new
data, the connection will be more resistant to sporadic packet loss
which reduces the application layer latency significantly in congested
scenarios.
The main functionality added:
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
o When packets are scheduled for transmission, RDB replaces the SKB to
be sent with a modified SKB containing the redundant data of
previously sent data segments from the TCP output queue.
o RDB will only be used for streams classified as thin by the function
tcp_stream_is_thin_dpifl(). This enforces a lower bound on the ITT
for streams that may benefit from RDB, controlled by the sysctl
variable tcp_thin_dpifl_itt_lower_bound.
RDB is enabled on a connection with the socket option TCP_RDB, or on all
new connections by setting the sysctl variable tcp_rdb=1.
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 15 ++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 14 ++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 3 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 26 ++++
net/ipv4/tcp.c | 14 +-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 11 +-
net/ipv4/tcp_rdb.c | 271 +++++++++++++++++++++++++++++++++
12 files changed, 357 insertions(+), 8 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
@@ -708,6 +708,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.+ Default: 0++tcp_rdb_max_bytes - INTEGER+ Enable restriction on how many bytes an RDB packet can contain.+ This is the total amount of payload including the new unsent data.+ Default: 0++tcp_rdb_max_skbs - INTEGER+ Enable restriction on how many previous SKBs in the output queue+ RDB may include data from. A value of 1 will restrict bundling to+ only the data from the last packet that was sent.+ Default: 1+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -772,6 +782,7 @@ struct tcp_skb_cb {union{struct{/* There is space for up to 20 bytes */+__u32rdb_start_seq;/* Start seq of rdb data bundled */}tx;/* only used for outgoing skbs */union{structinet_skb_parmh4;
@@ -115,6 +115,7 @@ enum {#define TCP_CC_INFO 26 /* Get Congestion Control (optional) info */#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */+#define TCP_RDB 29 /* Enable RDB mechanism */structtcp_repair_opt{__u32opt_code;
@@ -2113,9 +2113,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,break;}-if(unlikely(tcp_transmit_skb(sk,skb,1,gfp)))+if(unlikely(tcp_sk(sk)->rdb)){+if(tcp_transmit_rdb_skb(sk,skb,mss_now,gfp))+break;+}elseif(unlikely(tcp_transmit_skb(sk,skb,1,gfp))){break;-+}repair:/* Advance the send_head. This one is sent out.*Thiscallwillincrementpackets_out.
@@ -0,0 +1,271 @@+#include<linux/skbuff.h>+#include<net/tcp.h>++intsysctl_tcp_rdb_max_bytes__read_mostly;+intsysctl_tcp_rdb_max_skbs__read_mostly=1;++/**+*rdb_check_rtx_queue_loss()-Performlossdetectionbyanalysingacks.+*@sk:thesocket.+*+*Return:Thenumberofpacketsthatarepresumedtobelost.+*/+staticunsignedintrdb_check_rtx_queue_loss(structsock*sk)+{+structsk_buff*skb,*tmp;+structtcp_skb_cb*scb;+u32seq_acked=tcp_sk(sk)->snd_una;+unsignedintpackets_lost=0;++tcp_for_write_queue(skb,sk){+if(skb==tcp_send_head(sk))+break;++scb=TCP_SKB_CB(skb);+/* The ACK acknowledges parts of the data in this SKB.+*Canbecausedby:+*-TSO:WeabortasRDBisnotusedonSKBssplitacross+*multiplepacketsonlowerlayersasthesearegreater+*thanoneMSS.+*-Retranscollapse:We'vehadaretrans,solosshasalready+*beendetected.+*/+if(after(scb->end_seq,seq_acked)){+break;+/* The ACKed packet */+}elseif(scb->end_seq==seq_acked){+/* This SKB was sent with no RDB data, or no prior+*unackedSKBsinoutputqueue,sobreakhere.+*/+if(scb->tx.rdb_start_seq==scb->seq||+skb_queue_is_first(&sk->sk_write_queue,skb))+break;+/* Find number of prior SKBs who's data was bundled in+*this(ACKed)SKB.Wepresumeanyredundantdata+*coveringpreviousSKB'sareduetoloss.(An+*exceptionwouldbereordering).+*/+skb=skb->prev;+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+if(!before(TCP_SKB_CB(skb)->seq,scb->tx.rdb_start_seq))+packets_lost++;+else+break;+}+break;+}+}+returnpackets_lost;+}++/**+*rdb_ack_event()-Initiatelossdetection+*@sk:thesocket+*@flags:Theflags+*/+voidrdb_ack_event(structsock*sk,u32flags)+{+if(rdb_check_rtx_queue_loss(sk))+tcp_enter_cwr(sk);+}++/**+*skb_append_data()-CopydatafromanSKBtotheendofanother+*@from_skb:TheSKBtocopydatafrom+*@to_skb:TheSKBtocopydatato+*+*Return:0onsuccess,elseerror+*/+staticintskb_append_data(structsk_buff*from_skb,structsk_buff*to_skb)+{+/* Copy the linear data and the data from the frags into the linear page+*bufferofto_skb.+*/+if(WARN_ON(skb_copy_bits(from_skb,0,+skb_put(to_skb,from_skb->len),+from_skb->len))){+gotofault;+}++TCP_SKB_CB(to_skb)->end_seq=TCP_SKB_CB(from_skb)->end_seq;++if(from_skb->ip_summed==CHECKSUM_PARTIAL)+to_skb->ip_summed=CHECKSUM_PARTIAL;++if(to_skb->ip_summed!=CHECKSUM_PARTIAL)+to_skb->csum=csum_block_add(to_skb->csum,from_skb->csum,+to_skb->len);+return0;+fault:+return-EFAULT;+}++/**+*rdb_build_skb()-BuildsthenewRDBSKBandcopiesallthedataintothe+*linearpagebuffer.+*@sk:thesocket+*@xmit_skb:ThisistheSKBthattcp_write_xmitwantstosend+*@first_skb:ThefirstSKBintheoutputqueuewewillbundle+*@gfp_mask:Thegfp_tallocation+*@bytes_in_rdb_skb:Thetotalnumberofdatabytesforthenewrdb_skb+*(NEW+Redundant)+*+*Return:AnewSKBcontainingredundantdata,orNULLifmemoryallocation+*failed+*/+staticstructsk_buff*rdb_build_skb(conststructsock*sk,+structsk_buff*xmit_skb,+structsk_buff*first_skb,+u32bytes_in_rdb_skb,+gfp_tgfp_mask)+{+structsk_buff*rdb_skb,*tmp_skb;++rdb_skb=sk_stream_alloc_skb((structsock*)sk,+(int)bytes_in_rdb_skb,+gfp_mask,true);+if(!rdb_skb)+returnNULL;+copy_skb_header(rdb_skb,xmit_skb);+rdb_skb->ip_summed=xmit_skb->ip_summed;++TCP_SKB_CB(rdb_skb)->seq=TCP_SKB_CB(first_skb)->seq;+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(rdb_skb)->seq;++tmp_skb=first_skb;++tcp_for_write_queue_from(tmp_skb,sk){+/* Copy data from tmp_skb to rdb_skb */+if(skb_append_data(tmp_skb,rdb_skb))+returnNULL;+/* We are at the last skb that should be included (The unsent+*one)+*/+if(tmp_skb==xmit_skb)+break;+}+returnrdb_skb;+}++/**+*rdb_can_bundle_test()-testifredundantdatacanbebundled+*@sk:thesocket+*@xmit_skb:TheSKBprocessedfortransmissionbytheoutputengine+*@mss_now:Thecurrentmssvalue+*@bytes_in_rdb_skb:Willcontaintheresultingnumberofbytestobundle+*atexit.+*@skbs_to_bundle_count:ThetotalnumberofSKBstobeinthebundle+*+*Traversestheentirewritequeueandchecksifanyun-ackeddata+*maybebundled.+*+*Return:ThefirstSKBtobeinthebundle,orNULLifnobundling+*/+staticstructsk_buff*rdb_can_bundle_test(conststructsock*sk,+structsk_buff*xmit_skb,+unsignedintmss_now,+u32*bytes_in_rdb_skb,+u32*skbs_to_bundle_count)+{+structsk_buff*first_to_bundle=NULL;+structsk_buff*tmp,*skb=xmit_skb->prev;+u32skbs_in_bundle_count=1;/* 1 to account for current skb */+u32byte_count=xmit_skb->len;++/* We start at the skb before xmit_skb, and go backwards in the list.*/+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+/* Not enough room to bundle data from this SKB */+if((byte_count+skb->len)>mss_now)+break;++if(sysctl_tcp_rdb_max_bytes&&+((byte_count+skb->len)>sysctl_tcp_rdb_max_bytes))+break;++if(sysctl_tcp_rdb_max_skbs&&+(skbs_in_bundle_count>sysctl_tcp_rdb_max_skbs))+break;++byte_count+=skb->len;+skbs_in_bundle_count++;+first_to_bundle=skb;+}+*bytes_in_rdb_skb=byte_count;+*skbs_to_bundle_count=skbs_in_bundle_count;+returnfirst_to_bundle;+}++/**+*create_rdb_skb()-TrytocreateanRDBSKB+*@sk:thesocket+*@xmit_skb:TheSKBfromtheoutputqueuetobesent+*@mss_now:CurrentMSS+*@gfp_mask:Thegfp_tallocation+*+*Return:AnewSKBcontainingredundantdata,orNULLifnobundlingcouldbe+*performed+*/+structsk_buff*create_rdb_skb(conststructsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,u32*bytes_in_rdb_skb,+gfp_tgfp_mask)+{+u32skb_in_bundle_count;+structsk_buff*first_to_bundle;++if(skb_queue_is_first(&sk->sk_write_queue,xmit_skb))+returnNULL;++/* No bundling on FIN packet */+if(TCP_SKB_CB(xmit_skb)->tcp_flags&TCPHDR_FIN)+returnNULL;++/* Find number of (previous) SKBs to get data from */+first_to_bundle=rdb_can_bundle_test(sk,xmit_skb,mss_now,+bytes_in_rdb_skb,+&skb_in_bundle_count);+if(!first_to_bundle)+returnNULL;++/* Create an SKB that contains the data from 'skb_in_bundle_count'+*SKBs.+*/+returnrdb_build_skb(sk,xmit_skb,first_to_bundle,+*bytes_in_rdb_skb,gfp_mask);+}++/**+*tcp_transmit_rdb_skb()-TrytocreateandsendanRDBpacket+*@sk:thesocket+*@xmit_skb:TheSKBprocessedfortransmissionbytheoutputengine+*@mss_now:CurrentMSS+*@gfp_mask:Thegfp_tallocation+*+*Return:0ifsuccessfullysentpacket,elseerror+*/+inttcp_transmit_rdb_skb(structsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,gfp_tgfp_mask)+{+structsk_buff*rdb_skb=NULL;+u32bytes_in_rdb_skb=0;/* May be used for statistical purposes */++/* How we detect that RDB was used. When equal, no RDB data was sent */+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(xmit_skb)->seq;++if(tcp_stream_is_thin_dpifl(tcp_sk(sk))){+rdb_skb=create_rdb_skb(sk,xmit_skb,mss_now,+&bytes_in_rdb_skb,gfp_mask);+if(!rdb_skb)+gotoxmit_default;++/* Set tstamp for SKB in output queue, because tcp_transmit_skb+*willdothisfortherdb_skbandnottheSKBintheoutput+*queue(xmit_skb).+*/+skb_mstamp_get(&xmit_skb->skb_mstamp);+rdb_skb->skb_mstamp=xmit_skb->skb_mstamp;+returntcp_transmit_skb(sk,rdb_skb,0,gfp_mask);+}+xmit_default:+/* Transmit the unmodified SKB from output queue */+returntcp_transmit_skb(sk,xmit_skb,1,gfp_mask);+}
From: Eric Dumazet <hidden> Date: 2015-11-23 17:43:24
On Mon, 2015-11-23 at 17:26 +0100, Bendik Rønning Opstad wrote:
+
+tcp_rdb_max_skbs - INTEGER
+ Enable restriction on how many previous SKBs in the output queue
+ RDB may include data from. A value of 1 will restrict bundling to
+ only the data from the last packet that was sent.
+ Default: 1
+
skb is an internal thing. I would rather not expose a sysctl with such
name.
Can be multi segment or not (if GSO/TSO is enabled)
So even '1' skb can have very different content, from 1 byte to ~64 KB
On Mon, 2015-11-23 at 17:26 +0100, Bendik Rønning Opstad wrote:
quoted
quoted
+
+tcp_rdb_max_skbs - INTEGER
+ Enable restriction on how many previous SKBs in the output queue
+ RDB may include data from. A value of 1 will restrict bundling to
+ only the data from the last packet that was sent.
+ Default: 1
+
skb is an internal thing. I would rather not expose a sysctl with such
name.
Can be multi segment or not (if GSO/TSO is enabled)
So even '1' skb can have very different content, from 1 byte to ~64 KB
I see your point about not exposing the internal naming. What about
tcp_rdb_max_packets?
The existing mechanism for detecting thin streams (tcp_stream_is_thin)
is based on a static limit of less than 4 packets in flight. This treats
streams differently depending on the connections RTT, such that a stream
on a high RTT link may never be considered thin, whereas the same
application would produce a stream that would always be thin in a low RTT
scenario (e.g. data center).
By calculating a dynamic packets in flight limit (DPIFL), the thin stream
detection will be independent of the RTT and treat streams equally based
on the transmission pattern, i.e. the inter-transmission time (ITT).
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 8 ++++++++
include/net/tcp.h | 21 +++++++++++++++++++++
net/ipv4/sysctl_net_ipv4.c | 9 +++++++++
net/ipv4/tcp.c | 2 ++
4 files changed, 40 insertions(+)
@@ -708,6 +708,14 @@ tcp_thin_dupack - BOOLEAN Documentation/networking/tcp-thin.txt Default: 0+tcp_thin_dpifl_itt_lower_bound - INTEGER+ Controls the lower bound inter-transmission time (ITT) threshold+ for when a stream is considered thin. The value is specified in+ microseconds, and may not be lower than 10000 (10 ms). Based on+ this threshold, a dynamic packets in flight limit (DPIFL) is+ calculated, which is used to classify whether a stream is thin.+ Default: 10000+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -215,6 +215,8 @@ void tcp_time_wait(struct sock *sk, int state, int timeo);/* TCP thin-stream limits */#define TCP_THIN_LINEAR_RETRIES 6 /* After 6 linear retries, do exp. backoff */+/* Lowest possible DPIFL lower bound ITT is 10 ms (10000 usec) */+#define TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN 10000/* TCP initial congestion window as per rfc6928 */#define TCP_INIT_CWND 10
@@ -271,6 +273,7 @@ extern int sysctl_tcp_workaround_signed_windows;externintsysctl_tcp_slow_start_after_idle;externintsysctl_tcp_thin_linear_timeouts;externintsysctl_tcp_thin_dupack;+externintsysctl_tcp_thin_dpifl_itt_lower_bound;externintsysctl_tcp_early_retrans;externintsysctl_tcp_limit_output_bytes;externintsysctl_tcp_challenge_ack_limit;
@@ -1649,6 +1652,24 @@ static inline bool tcp_stream_is_thin(struct tcp_sock *tp)returntp->packets_out<4&&!tcp_in_initial_slowstart(tp);}+/**+*tcp_stream_is_thin_dpifl()-TestsifthestreamisthinbasedondynamicPIF+*limit+*@tp:thetcp_sockstruct+*+*Return:trueifcurrentpacketsinflight(PIF)countislowerthan+*thedynamicPIFlimit,elsefalse+*/+staticinlinebooltcp_stream_is_thin_dpifl(conststructtcp_sock*tp)+{+/* Calculate the maximum allowed PIF limit by dividing the RTT by+*theminimumallowedinter-transmissiontime(ITT).+*TestsifPIF<RTT/ITT-lower-bound+*/+return(u64)tcp_packets_in_flight(tp)*+sysctl_tcp_thin_dpifl_itt_lower_bound<(tp->srtt_us>>3);+}+/* /proc */enumtcp_seq_states{TCP_SEQ_STATE_LISTENING,
@@ -41,6 +41,7 @@ static int tcp_syn_retries_min = 1;staticinttcp_syn_retries_max=MAX_TCP_SYNCNT;staticintip_ping_group_range_min[]={0,0};staticintip_ping_group_range_max[]={GID_T_MAX,GID_T_MAX};+staticinttcp_thin_dpifl_itt_lower_bound_min=TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN;/* Update system visible IP port range */staticvoidset_local_port_range(structnet*net,intrange[2])
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be
made not to negatively affect competing traffic in an unfair manner.
Note: Current patch set depends on the patch "tcp: refactor struct tcp_skb_cb"
(http://patchwork.ozlabs.org/patch/510674)
These patches have also been tested with as set of packetdrill scripts
located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as
in the paper "Latency and Fairness Trade-Off for Thin Streams using
Redundant Data Bundling in TCP"[2].
[1] http://home.ifi.uio.no/paalh/students/BendikOpstad.pdf
[2] http://home.ifi.uio.no/bendiko/rdb_fairness_tradeoff.pdf
Changes:
v3 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Changed name of sysctl variable from tcp_rdb_max_skbs to
tcp_rdb_max_packets after comment from Eric Dumazet about
not exposing internal (kernel) names like skb.
* Formatting and function docs fixes
v2 (RFC/PATCH):
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Change calculation in tcp_stream_is_thin_dpifl based on
feedback from Eric Dumazet.
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed setting nonagle in do_tcp_setsockopt (TCP_RDB)
to reduce complexity as commented by Neal Cardwell.
* Cleaned up loss detection code in rdb_check_rtx_queue_loss
v1 (RFC/PATCH)
Bendik Rønning Opstad (2):
tcp: Add DPIFL thin stream detection mechanism
tcp: Add Redundant Data Bundling (RDB)
Documentation/networking/ip-sysctl.txt | 23 +++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 35 +++++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 3 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 35 +++++
net/ipv4/tcp.c | 16 +-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 11 +-
net/ipv4/tcp_rdb.c | 273 +++++++++++++++++++++++++++++++++
12 files changed, 399 insertions(+), 8 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
--
1.9.1
RDB is a mechanism that enables a TCP sender to bundle redundant
(already sent) data with TCP packets containing new data. By bundling
(retransmitting) already sent data with each TCP packet containing new
data, the connection will be more resistant to sporadic packet loss
which reduces the application layer latency significantly in congested
scenarios.
The main functionality added:
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
o When packets are scheduled for transmission, RDB replaces the SKB to
be sent with a modified SKB containing the redundant data of
previously sent data segments from the TCP output queue.
o RDB will only be used for streams classified as thin by the function
tcp_stream_is_thin_dpifl(). This enforces a lower bound on the ITT
for streams that may benefit from RDB, controlled by the sysctl
variable tcp_thin_dpifl_itt_lower_bound.
RDB is enabled on a connection with the socket option TCP_RDB, or on all
new connections by setting the sysctl variable tcp_rdb=1.
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 15 ++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 14 ++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 3 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 26 ++++
net/ipv4/tcp.c | 14 +-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 11 +-
net/ipv4/tcp_rdb.c | 273 +++++++++++++++++++++++++++++++++
12 files changed, 359 insertions(+), 8 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.+ Default: 0++tcp_rdb_max_bytes - INTEGER+ Enable restriction on how many bytes an RDB packet can contain.+ This is the total amount of payload including the new unsent data.+ Default: 0++tcp_rdb_max_packets - INTEGER+ Enable restriction on how many previous packets in the output queue+ RDB may include data from. A value of 1 will restrict bundling to+ only the data from the last packet that was sent.+ Default: 1+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -770,6 +780,7 @@ struct tcp_skb_cb {union{struct{/* There is space for up to 20 bytes */+__u32rdb_start_seq;/* Start seq of rdb data bundled */}tx;/* only used for outgoing skbs */union{structinet_skb_parmh4;
@@ -115,6 +115,7 @@ enum {#define TCP_CC_INFO 26 /* Get Congestion Control (optional) info */#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */+#define TCP_RDB 29 /* Enable RDB mechanism */structtcp_repair_opt{__u32opt_code;
@@ -2113,9 +2113,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,break;}-if(unlikely(tcp_transmit_skb(sk,skb,1,gfp)))+if(unlikely(tcp_sk(sk)->rdb)){+if(tcp_transmit_rdb_skb(sk,skb,mss_now,gfp))+break;+}elseif(unlikely(tcp_transmit_skb(sk,skb,1,gfp))){break;-+}repair:/* Advance the send_head. This one is sent out.*Thiscallwillincrementpackets_out.
@@ -0,0 +1,273 @@+#include<linux/skbuff.h>+#include<net/tcp.h>++intsysctl_tcp_rdb_max_bytes__read_mostly;+intsysctl_tcp_rdb_max_packets__read_mostly=1;++/**+*rdb_check_rtx_queue_loss()-performlossdetectionbyanalysingacks.+*@sk:socket.+*+*Return:Thenumberofpacketsthatarepresumedtobelost.+*/+staticunsignedintrdb_check_rtx_queue_loss(structsock*sk)+{+structsk_buff*skb,*tmp;+structtcp_skb_cb*scb;+u32seq_acked=tcp_sk(sk)->snd_una;+unsignedintpackets_lost=0;++tcp_for_write_queue(skb,sk){+if(skb==tcp_send_head(sk))+break;++scb=TCP_SKB_CB(skb);+/* The ACK acknowledges parts of the data in this SKB.+*Canbecausedby:+*-TSO:WeabortasRDBisnotusedonSKBssplitacross+*multiplepacketsonlowerlayersasthesearegreater+*thanoneMSS.+*-Retranscollapse:We'vehadaretrans,solosshasalready+*beendetected.+*/+if(after(scb->end_seq,seq_acked)){+break;+/* The ACKed packet */+}elseif(scb->end_seq==seq_acked){+/* This SKB was sent with no RDB data, or no prior+*unackedSKBsinoutputqueue,sobreakhere.+*/+if(scb->tx.rdb_start_seq==scb->seq||+skb_queue_is_first(&sk->sk_write_queue,skb))+break;+/* Find number of prior SKBs who's data was bundled in+*this(ACKed)SKB.Wepresumeanyredundantdata+*coveringpreviousSKB'sareduetoloss.(An+*exceptionwouldbereordering).+*/+skb=skb->prev;+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+if(!before(TCP_SKB_CB(skb)->seq,scb->tx.rdb_start_seq))+packets_lost++;+else+break;+}+break;+}+}+returnpackets_lost;+}++/**+*rdb_ack_event()-initiatelossdetection+*@sk:socket+*@flags:flags+*/+voidrdb_ack_event(structsock*sk,u32flags)+{+if(rdb_check_rtx_queue_loss(sk))+tcp_enter_cwr(sk);+}++/**+*skb_append_data()-copydatafromanSKBtotheendofanother+*@from_skb:theSKBtocopydatafrom+*@to_skb:theSKBtocopydatato+*+*Return:0onsuccess,elseerror+*/+staticintskb_append_data(structsk_buff*from_skb,structsk_buff*to_skb)+{+/* Copy the linear data and the data from the frags into the linear page+*bufferofto_skb.+*/+if(WARN_ON(skb_copy_bits(from_skb,0,+skb_put(to_skb,from_skb->len),+from_skb->len))){+gotofault;+}++TCP_SKB_CB(to_skb)->end_seq=TCP_SKB_CB(from_skb)->end_seq;++if(from_skb->ip_summed==CHECKSUM_PARTIAL)+to_skb->ip_summed=CHECKSUM_PARTIAL;++if(to_skb->ip_summed!=CHECKSUM_PARTIAL)+to_skb->csum=csum_block_add(to_skb->csum,from_skb->csum,+to_skb->len);+return0;+fault:+return-EFAULT;+}++/**+*rdb_build_skb()-buildthenewRDBSKBandcopiesallthedataintothe+*linearpagebuffer.+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@first_skb:thefirstSKBintheoutputqueuetobebundled+*@bytes_in_rdb_skb:thetotalnumberofdatabytesforthenewrdb_skb+*(NEW+Redundant)+*@gfp_mask:gfp_tallocation+*+*Return:AnewSKBcontainingredundantdata,orNULLifmemoryallocation+*failed+*/+staticstructsk_buff*rdb_build_skb(conststructsock*sk,+structsk_buff*xmit_skb,+structsk_buff*first_skb,+u32bytes_in_rdb_skb,+gfp_tgfp_mask)+{+structsk_buff*rdb_skb,*tmp_skb;++rdb_skb=sk_stream_alloc_skb((structsock*)sk,+(int)bytes_in_rdb_skb,+gfp_mask,true);+if(!rdb_skb)+returnNULL;+copy_skb_header(rdb_skb,xmit_skb);+rdb_skb->ip_summed=xmit_skb->ip_summed;++TCP_SKB_CB(rdb_skb)->seq=TCP_SKB_CB(first_skb)->seq;+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(rdb_skb)->seq;++tmp_skb=first_skb;++tcp_for_write_queue_from(tmp_skb,sk){+/* Copy data from tmp_skb to rdb_skb */+if(skb_append_data(tmp_skb,rdb_skb))+returnNULL;+/* We are at the last skb that should be included (The unsent+*one)+*/+if(tmp_skb==xmit_skb)+break;+}+returnrdb_skb;+}++/**+*rdb_can_bundle_test()-testifredundantdatacanbebundled+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@mss_now:currentmssvalue+*@bytes_in_rdb_skb:storethetotalnumberofpayloadbytesinthe+*RDBSKBifbundlingcanbeperformed.+*@skbs_to_bundle_count:thetotalnumberofSKBstobeinthebundle+*+*Traversetheoutputqueueandcheckifanyun-ackeddatamaybe+*bundled.+*+*Return:ThefirstSKBtobeinthebundle,orNULLifnobundling+*/+staticstructsk_buff*rdb_can_bundle_test(conststructsock*sk,+structsk_buff*xmit_skb,+unsignedintmss_now,+u32*bytes_in_rdb_skb,+u32*skbs_to_bundle_count)+{+structsk_buff*first_to_bundle=NULL;+structsk_buff*tmp,*skb=xmit_skb->prev;+u32skbs_in_bundle_count=1;/* 1 to account for current skb */+u32byte_count=xmit_skb->len;++/* We start at the skb before xmit_skb, and go backwards in the list.*/+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+/* Not enough room to bundle data from this SKB */+if((byte_count+skb->len)>mss_now)+break;++if(sysctl_tcp_rdb_max_bytes&&+((byte_count+skb->len)>sysctl_tcp_rdb_max_bytes))+break;++if(sysctl_tcp_rdb_max_packets&&+(skbs_in_bundle_count>sysctl_tcp_rdb_max_packets))+break;++byte_count+=skb->len;+skbs_in_bundle_count++;+first_to_bundle=skb;+}+*bytes_in_rdb_skb=byte_count;+*skbs_to_bundle_count=skbs_in_bundle_count;+returnfirst_to_bundle;+}++/**+*create_rdb_skb()-trytocreateanRDBSKB+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@mss_now:currentmssvalue+*@bytes_in_rdb_skb:storethetotalnumberofpayloadbytesinthe+*RDBSKBifbundlingcanbeperformed.+*@gfp_mask:gfp_tallocation+*+*Return:AnewSKBcontainingredundantdata,orNULLifnobundlingcouldbe+*performed+*/+structsk_buff*create_rdb_skb(conststructsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,u32*bytes_in_rdb_skb,+gfp_tgfp_mask)+{+u32skb_in_bundle_count;+structsk_buff*first_to_bundle;++if(skb_queue_is_first(&sk->sk_write_queue,xmit_skb))+returnNULL;++/* No bundling on FIN packet */+if(TCP_SKB_CB(xmit_skb)->tcp_flags&TCPHDR_FIN)+returnNULL;++/* Find number of (previous) SKBs to get data from */+first_to_bundle=rdb_can_bundle_test(sk,xmit_skb,mss_now,+bytes_in_rdb_skb,+&skb_in_bundle_count);+if(!first_to_bundle)+returnNULL;++/* Create an SKB that contains the data from 'skb_in_bundle_count'+*SKBs.+*/+returnrdb_build_skb(sk,xmit_skb,first_to_bundle,+*bytes_in_rdb_skb,gfp_mask);+}++/**+*tcp_transmit_rdb_skb()-trytocreateandsendanRDBpacket+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@mss_now:currentmssvalue+*@gfp_mask:gfp_tallocation+*+*Return:0ifsuccessfullysentpacket,elseerror+*/+inttcp_transmit_rdb_skb(structsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,gfp_tgfp_mask)+{+structsk_buff*rdb_skb=NULL;+u32bytes_in_rdb_skb=0;/* May be used for statistical purposes */++/* How we detect that RDB was used. When equal, no RDB data was sent */+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(xmit_skb)->seq;++if(tcp_stream_is_thin_dpifl(tcp_sk(sk))){+rdb_skb=create_rdb_skb(sk,xmit_skb,mss_now,+&bytes_in_rdb_skb,gfp_mask);+if(!rdb_skb)+gotoxmit_default;++/* Set tstamp for SKB in output queue, because tcp_transmit_skb+*willdothisfortherdb_skbandnottheSKBintheoutput+*queue(xmit_skb).+*/+skb_mstamp_get(&xmit_skb->skb_mstamp);+rdb_skb->skb_mstamp=xmit_skb->skb_mstamp;+returntcp_transmit_skb(sk,rdb_skb,0,gfp_mask);+}+xmit_default:+/* Transmit the unmodified SKB from output queue */+returntcp_transmit_skb(sk,xmit_skb,1,gfp_mask);+}
From: Eric Dumazet <hidden> Date: 2016-02-02 20:35:09
On Tue, 2016-02-02 at 20:23 +0100, Bendik Rønning Opstad wrote:
RDB is a mechanism that enables a TCP sender to bundle redundant
(already sent) data with TCP packets containing new data. By bundling
(retransmitting) already sent data with each TCP packet containing new
data, the connection will be more resistant to sporadic packet loss
which reduces the application layer latency significantly in congested
scenarios.
The main functionality added:
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
o When packets are scheduled for transmission, RDB replaces the SKB to
be sent with a modified SKB containing the redundant data of
previously sent data segments from the TCP output queue.
Really this looks very complicated.
Why not simply append the new skb content to prior one ?
skb_still_in_host_queue(sk, prior_skb) would also tell you if the skb is
really available (ie its clone not sitting/waiting in a qdisc on the
host)
Note : select_size() always allocate skb with SKB_WITH_OVERHEAD(2048 -
MAX_TCP_HEADER) available bytes in skb->data.
Also note that tcp_collapse_retrans() is very similar to your needs. You
might simply expand it.
On Tue, Feb 2, 2016 at 9:35 PM, Eric Dumazet [off-list ref] wrote:
On Tue, 2016-02-02 at 20:23 +0100, Bendik Rønning Opstad wrote:
quoted
o When packets are scheduled for transmission, RDB replaces the SKB to
be sent with a modified SKB containing the redundant data of
previously sent data segments from the TCP output queue.
Really this looks very complicated.
Can you be more specific?
Why not simply append the new skb content to prior one ?
It's not clear to me what you mean. At what stage in the output engine
do you refer to?
We want to avoid modifying the data of the SKBs in the output queue,
therefore we allocate a new SKB (This SKB is named rdb_skb in the code).
The header and payload of the first SKB containing data we want to
redundantly transmit is then copied. Then the payload of the SKBs following
next in the output queue is appended onto the rdb_skb. The last payload
that is appended is from the first SKB with unsent data, i.e. the
sk_send_head.
Would you suggest a different approach?
skb_still_in_host_queue(sk, prior_skb) would also tell you if the skb is
really available (ie its clone not sitting/waiting in a qdisc on the
host)
Where do you suggest this should be used?
Note : select_size() always allocate skb with SKB_WITH_OVERHEAD(2048 -
MAX_TCP_HEADER) available bytes in skb->data.
Sure, rdb_build_skb() could use this instead of the calculated
bytes_in_rdb_skb.
Also note that tcp_collapse_retrans() is very similar to your needs. You
might simply expand it.
The functionality shared is the copying of data from one SKB to another, as
well as adjusting sequence numbers and checksum. Unlinking SKBs from the
output queue, modifying the data of SKBs in the output queue, and changing
retrans hints is not shared.
To reduce code duplication, the function skb_append_data in tcp_rdb.c could
be moved to tcp_output.c, and then be called from tcp_collapse_retrans.
Is it something like this you had in mind?
Bendik
From: Eric Dumazet <hidden> Date: 2016-02-03 19:34:45
On Wed, 2016-02-03 at 19:17 +0100, Bendik Rønning Opstad wrote:
On Tue, Feb 2, 2016 at 9:35 PM, Eric Dumazet [off-list ref] wrote:
quoted
On Tue, 2016-02-02 at 20:23 +0100, Bendik Rønning Opstad wrote:
quoted
o When packets are scheduled for transmission, RDB replaces the SKB to
be sent with a modified SKB containing the redundant data of
previously sent data segments from the TCP output queue.
Really this looks very complicated.
Can you be more specific?
A lot of code added, needing maintenance cost for years to come.
quoted
Why not simply append the new skb content to prior one ?
It's not clear to me what you mean. At what stage in the output engine
do you refer to?
We want to avoid modifying the data of the SKBs in the output queue,
Why ? We already do that, as I pointed out.
therefore we allocate a new SKB (This SKB is named rdb_skb in the code).
The header and payload of the first SKB containing data we want to
redundantly transmit is then copied. Then the payload of the SKBs following
next in the output queue is appended onto the rdb_skb. The last payload
that is appended is from the first SKB with unsent data, i.e. the
sk_send_head.
Would you suggest a different approach?
quoted
skb_still_in_host_queue(sk, prior_skb) would also tell you if the skb is
really available (ie its clone not sitting/waiting in a qdisc on the
host)
Where do you suggest this should be used?
To detect if appending data to prior skb is possible.
If the prior packet is still in qdisc, no change is allowed,
and it is fine : DRB should not trigger anyway.
quoted
Note : select_size() always allocate skb with SKB_WITH_OVERHEAD(2048 -
MAX_TCP_HEADER) available bytes in skb->data.
Sure, rdb_build_skb() could use this instead of the calculated
bytes_in_rdb_skb.
Point is : small packets already have tail room in skb->head
When RDB decides a packet should be merged into the prior one, you can
simply copy payload into the tailroom, then free the skb.
No skb allocations are needed, only freeing.
RDB could be implemented in a more concise way.
Eric, thank you for the feedback!
On Wed, Feb 3, 2016 at 8:34 PM, Eric Dumazet [off-list ref] wrote:
On Wed, 2016-02-03 at 19:17 +0100, Bendik Rønning Opstad wrote:
quoted
On Tue, Feb 2, 2016 at 9:35 PM, Eric Dumazet [off-list ref]
wrote:
quoted
quoted
Really this looks very complicated.
Can you be more specific?
A lot of code added, needing maintenance cost for years to come.
Yes, that is understandable.
quoted
quoted
Why not simply append the new skb content to prior one ?
It's not clear to me what you mean. At what stage in the output engine
do you refer to?
We want to avoid modifying the data of the SKBs in the output queue,
Why ? We already do that, as I pointed out.
I suspect that we might be talking past each other. It wasn't clear to
me that we were discussing how to implement this in a different way.
The current retrans collapse functionality only merges SKBs that
contain data that has already been sent and is about to be
retransmitted.
This differs significantly from RDB, which combines both already
transmitted data and unsent data in the same packet without changing
how the data is stored (and the state tracked) in the output queue.
Another difference is that RDB includes un-ACKed data that is not
considered lost.
quoted
therefore we allocate a new SKB (This SKB is named rdb_skb in the code).
The header and payload of the first SKB containing data we want to
redundantly transmit is then copied. Then the payload of the SKBs following
next in the output queue is appended onto the rdb_skb. The last payload
that is appended is from the first SKB with unsent data, i.e. the
sk_send_head.
Would you suggest a different approach?
quoted
skb_still_in_host_queue(sk, prior_skb) would also tell you if the skb is
really available (ie its clone not sitting/waiting in a qdisc on the
host)
Where do you suggest this should be used?
To detect if appending data to prior skb is possible.
I see. As the implementation intentionally avoids modifying SKBs in
the output queue, this was not obvious.
If the prior packet is still in qdisc, no change is allowed,
and it is fine : DRB should not trigger anyway.
Actually, whether the data in the prior SKB is on the wire or is still
on the host (in qdisc/driver queue) is not relevant. RDB always wants
to redundantly resend the data if there is room in the packet, because
the previous packet may become lost.
quoted
quoted
Note : select_size() always allocate skb with SKB_WITH_OVERHEAD(2048 -
MAX_TCP_HEADER) available bytes in skb->data.
Sure, rdb_build_skb() could use this instead of the calculated
bytes_in_rdb_skb.
Point is : small packets already have tail room in skb->head
Yes, I'm aware of that. But we do not allocate new SKBs because we
think the existing SKBs do not have enough space available. We do it
to avoid modifications to the SKBs in the output queue.
When RDB decides a packet should be merged into the prior one, you can
simply copy payload into the tailroom, then free the skb.
No skb allocations are needed, only freeing.
It wasn't clear to me that you suggest a completely different
implementation approach altogether.
As I understand you, the approach you suggest is as follows:
1. An SKB containing unsent data is processed for transmission (lets
call it T_SKB)
2. Check if the previous SKB (lets call it P_SKB) (containing sent but
un-ACKed data) has available (tail) room for the payload contained
in T_SKB.
3. If room in P_SKB:
* Copy the unsent data from T_SKB to P_SKB by appending it to the
linear data and update sequence numbers.
* Remove T_SKB (which contains only the new and unsent data) from
the output queue.
* Transmit P_SKB, which now contains some already sent data and some
unsent data.
If I have misunderstood, can you please elaborate in detail what you
mean?
If this is the approach you suggest, I can think of some potential
downsides that require further considerations:
1) ACK-accounting will work differently
When the previous SKB (P_SKB) is modified by appending the data of the
next SKB (T_SKB), what should happen when an incoming ACK acknowledges
the data that was sent in the original transmission (before the SKB
was modified), but not the data that was appended later?
tcp_clean_rtx_queue currently handles partially ACKed SKBs due to TSO,
in which case the tcp_skb_pcount(skb) > 1. So this function would need
to be modified to handle this for RDB modified SKBs in the queue,
where all the data is located in the linear data buffer (no GSO segs).
How should SACK and retrans flags be handled when one SKB in the
output queue can represent multiple transmitted packets?
2) Timestamps and RTT measurements
How should RTT measurements work when you don't have a timestamp for
the data that was newly appended to the existing SKB containing sent
but un-ACKed data? Or should the skb->skb_mstamp be updated when the
SKB with newly appended data is sent again? That would make any RTT
measurements based on ACKs on the originally sent packet unusable.
3) Retransmit and lost SKB hints
Appending unsent data to SKBs with sent data will affect the usage of
tp->retransmit_skb_hint and tp->lost_skb_hint. As these variables
contain pointers to SKBs in the output queue, it is implied that all
the data in an SKB has the same state, such as retransmitted or lost.
4) RDB's loss accounting
RDB detects loss by looking at how many segments that are ACKed. If an
incoming ACK acknowledges data in multiples SKBs, we can infer that
loss has occurred (ignoring the possibility of reordering). With the
approach you suggest, we lose the information about how many packets
we originally had, and how much of the payload was redundant
(considering SKBs are updated with new data and sent out again). We
would need additional variables in order to keep track of this.
5) Forced bundling on retransmissions
Since the SKBs in the output queue are modified to contain redundant
data, retransmissions of the SKBs will necessarily only contain the
redundant data unless the SKBs are modified before the retransmission.
6) Configuring how much is bundled becomes complex
When previous SKBs are to be used by appending the new data to be
sent, it is no longer possible to configure the amount of data to
bundle. We are forced to bundle all the data in the previous SKB.
Say we have 3 SKBs in the queue, with unsent segments 1, 2, 3:
[1] [2] [3]
Send 1:
[1] ->
Try to send 2, but first merge 2 with 1:
[1,2] [3]
Send merged SKB:
[1,2] ->
When we want to send segment 3, we are forced to bundle both 1 and 2.
Try to send 3, but first merge 3 with 1,2.
[1,2,3]
Send merged SKB:
[1,2,3] ->
Transmitting only 2,3 in a packet then becomes difficult without
additional logic for RDB record keeping.
RDB could be implemented in a more concise way.
I'm open for suggestions to improvements. However, I can't see how the
suggested approach (as I've understood it) can be implemented without
making extensive modifications to the current TCP engine. Having one
SKB represent multiple packets, where each packet contains different
data and possibly in different states (retransmitted/lost), seems very
complex.
By avoiding any modifications to the output queue we ensure the
default code branch is completely unaffected, avoiding any special
handling in multiple locations in the codebase.
Regards,
Bendik
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be
made not to negatively affect competing traffic in an unfair manner.
Note: Current patch set depends on the patch "tcp: refactor struct tcp_skb_cb"
(http://patchwork.ozlabs.org/patch/510674)
These patches have also been tested with as set of packetdrill scripts
located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as
in the paper "Latency and Fairness Trade-Off for Thin Streams using
Redundant Data Bundling in TCP"[2].
[1] http://home.ifi.uio.no/paalh/students/BendikOpstad.pdf
[2] http://home.ifi.uio.no/bendiko/rdb_fairness_tradeoff.pdf
Changes:
v4 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Moved skb_append_data() to tcp_output.c and call this
function from tcp_collapse_retrans() as well.
* Merged functionality of create_rdb_skb() into
tcp_transmit_rdb_skb()
* Removed one parameter from rdb_can_bundle_test()
v3 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Changed name of sysctl variable from tcp_rdb_max_skbs to
tcp_rdb_max_packets after comment from Eric Dumazet about
not exposing internal (kernel) names like skb.
* Formatting and function docs fixes
v2 (RFC/PATCH):
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Change calculation in tcp_stream_is_thin_dpifl based on
feedback from Eric Dumazet.
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed setting nonagle in do_tcp_setsockopt (TCP_RDB)
to reduce complexity as commented by Neal Cardwell.
* Cleaned up loss detection code in rdb_check_rtx_queue_loss
v1 (RFC/PATCH)
Bendik Rønning Opstad (2):
tcp: Add DPIFL thin stream detection mechanism
tcp: Add Redundant Data Bundling (RDB)
Documentation/networking/ip-sysctl.txt | 23 ++++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 36 ++++++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 3 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 35 ++++++
net/ipv4/tcp.c | 16 ++-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 49 +++++---
net/ipv4/tcp_rdb.c | 215 +++++++++++++++++++++++++++++++++
12 files changed, 365 insertions(+), 23 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
--
1.9.1
The existing mechanism for detecting thin streams (tcp_stream_is_thin)
is based on a static limit of less than 4 packets in flight. This treats
streams differently depending on the connections RTT, such that a stream
on a high RTT link may never be considered thin, whereas the same
application would produce a stream that would always be thin in a low RTT
scenario (e.g. data center).
By calculating a dynamic packets in flight limit (DPIFL), the thin stream
detection will be independent of the RTT and treat streams equally based
on the transmission pattern, i.e. the inter-transmission time (ITT).
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 8 ++++++++
include/net/tcp.h | 21 +++++++++++++++++++++
net/ipv4/sysctl_net_ipv4.c | 9 +++++++++
net/ipv4/tcp.c | 2 ++
4 files changed, 40 insertions(+)
@@ -708,6 +708,14 @@ tcp_thin_dupack - BOOLEAN Documentation/networking/tcp-thin.txt Default: 0+tcp_thin_dpifl_itt_lower_bound - INTEGER+ Controls the lower bound inter-transmission time (ITT) threshold+ for when a stream is considered thin. The value is specified in+ microseconds, and may not be lower than 10000 (10 ms). Based on+ this threshold, a dynamic packets in flight limit (DPIFL) is+ calculated, which is used to classify whether a stream is thin.+ Default: 10000+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -215,6 +215,8 @@ void tcp_time_wait(struct sock *sk, int state, int timeo);/* TCP thin-stream limits */#define TCP_THIN_LINEAR_RETRIES 6 /* After 6 linear retries, do exp. backoff */+/* Lowest possible DPIFL lower bound ITT is 10 ms (10000 usec) */+#define TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN 10000/* TCP initial congestion window as per rfc6928 */#define TCP_INIT_CWND 10
@@ -264,6 +266,7 @@ extern int sysctl_tcp_workaround_signed_windows;externintsysctl_tcp_slow_start_after_idle;externintsysctl_tcp_thin_linear_timeouts;externintsysctl_tcp_thin_dupack;+externintsysctl_tcp_thin_dpifl_itt_lower_bound;externintsysctl_tcp_early_retrans;externintsysctl_tcp_limit_output_bytes;externintsysctl_tcp_challenge_ack_limit;
@@ -1645,6 +1648,24 @@ static inline bool tcp_stream_is_thin(struct tcp_sock *tp)returntp->packets_out<4&&!tcp_in_initial_slowstart(tp);}+/**+*tcp_stream_is_thin_dpifl()-TestsifthestreamisthinbasedondynamicPIF+*limit+*@tp:thetcp_sockstruct+*+*Return:trueifcurrentpacketsinflight(PIF)countislowerthan+*thedynamicPIFlimit,elsefalse+*/+staticinlinebooltcp_stream_is_thin_dpifl(conststructtcp_sock*tp)+{+/* Calculate the maximum allowed PIF limit by dividing the RTT by+*theminimumallowedinter-transmissiontime(ITT).+*TestsifPIF<RTT/ITT-lower-bound+*/+return(u64)tcp_packets_in_flight(tp)*+sysctl_tcp_thin_dpifl_itt_lower_bound<(tp->srtt_us>>3);+}+/* /proc */enumtcp_seq_states{TCP_SEQ_STATE_LISTENING,
@@ -41,6 +41,7 @@ static int tcp_syn_retries_min = 1;staticinttcp_syn_retries_max=MAX_TCP_SYNCNT;staticintip_ping_group_range_min[]={0,0};staticintip_ping_group_range_max[]={GID_T_MAX,GID_T_MAX};+staticinttcp_thin_dpifl_itt_lower_bound_min=TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN;/* Update system visible IP port range */staticvoidset_local_port_range(structnet*net,intrange[2])
RDB is a mechanism that enables a TCP sender to bundle redundant
(already sent) data with TCP packets containing new data. By bundling
(retransmitting) already sent data with each TCP packet containing new
data, the connection will be more resistant to sporadic packet loss
which reduces the application layer latency significantly in congested
scenarios.
The main functionality added:
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
o When packets are scheduled for transmission, RDB replaces the SKB to
be sent with a modified SKB containing the redundant data of
previously sent data segments from the TCP output queue.
o RDB will only be used for streams classified as thin by the function
tcp_stream_is_thin_dpifl(). This enforces a lower bound on the ITT
for streams that may benefit from RDB, controlled by the sysctl
variable tcp_thin_dpifl_itt_lower_bound.
RDB is enabled on a connection with the socket option TCP_RDB, or on all
new connections by setting the sysctl net.ipv4.tcp_rdb=1
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 15 +++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 15 +++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 3 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 26 ++++
net/ipv4/tcp.c | 14 ++-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 49 +++++---
net/ipv4/tcp_rdb.c | 215 +++++++++++++++++++++++++++++++++
12 files changed, 325 insertions(+), 23 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.+ Default: 0++tcp_rdb_max_bytes - INTEGER+ Enable restriction on how many bytes an RDB packet can contain.+ This is the total amount of payload including the new unsent data.+ Default: 0++tcp_rdb_max_packets - INTEGER+ Enable restriction on how many previous packets in the output queue+ RDB may include data from. A value of 1 will restrict bundling to+ only the data from the last packet that was sent.+ Default: 1+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -763,6 +774,7 @@ struct tcp_skb_cb {union{struct{/* There is space for up to 20 bytes */+__u32rdb_start_seq;/* Start seq of rdb data bundled */}tx;/* only used for outgoing skbs */union{structinet_skb_parmh4;
@@ -115,6 +115,7 @@ enum {#define TCP_CC_INFO 26 /* Get Congestion Control (optional) info */#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */+#define TCP_RDB 29 /* Enable RDB mechanism */structtcp_repair_opt{__u32opt_code;
@@ -3534,6 +3534,9 @@ static inline void tcp_in_ack_event(struct sock *sk, u32 flags)if(icsk->icsk_ca_ops->in_ack_event)icsk->icsk_ca_ops->in_ack_event(sk,flags);++if(unlikely(tcp_sk(sk)->rdb))+rdb_ack_event(sk,flags);}/* Congestion control has updated the cwnd already. So if we're in
@@ -2110,9 +2110,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,break;}-if(unlikely(tcp_transmit_skb(sk,skb,1,gfp)))+if(unlikely(tcp_sk(sk)->rdb)){+if(tcp_transmit_rdb_skb(sk,skb,mss_now,gfp))+break;+}elseif(unlikely(tcp_transmit_skb(sk,skb,1,gfp))){break;-+}repair:/* Advance the send_head. This one is sent out.*Thiscallwillincrementpackets_out.
@@ -2439,15 +2442,33 @@ u32 __tcp_select_window(struct sock *sk)returnwindow;}+/**+*skb_append_data()-copydatafromanSKBtotheendofanother+*updateendsequencenumberandchecksum+*@from_skb:theSKBtocopydatafrom+*@to_skb:theSKBtocopydatato+*/+voidskb_append_data(structsk_buff*from_skb,structsk_buff*to_skb)+{+skb_copy_from_linear_data(from_skb,skb_put(to_skb,from_skb->len),+from_skb->len);+/* Update sequence range on original skb. */+TCP_SKB_CB(to_skb)->end_seq=TCP_SKB_CB(from_skb)->end_seq;++if(from_skb->ip_summed==CHECKSUM_PARTIAL)+to_skb->ip_summed=CHECKSUM_PARTIAL;++if(to_skb->ip_summed!=CHECKSUM_PARTIAL)+to_skb->csum=csum_block_add(to_skb->csum,from_skb->csum,+to_skb->len);+}+EXPORT_SYMBOL(skb_append_data);+/* Collapses two adjacent SKB's during retransmission. */staticvoidtcp_collapse_retrans(structsock*sk,structsk_buff*skb){structtcp_sock*tp=tcp_sk(sk);structsk_buff*next_skb=tcp_write_queue_next(sk,skb);-intskb_size,next_skb_size;--skb_size=skb->len;-next_skb_size=next_skb->len;BUG_ON(tcp_skb_pcount(skb)!=1||tcp_skb_pcount(next_skb)!=1);
@@ -2455,17 +2476,7 @@ static void tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)tcp_unlink_write_queue(next_skb,sk);-skb_copy_from_linear_data(next_skb,skb_put(skb,next_skb_size),-next_skb_size);--if(next_skb->ip_summed==CHECKSUM_PARTIAL)-skb->ip_summed=CHECKSUM_PARTIAL;--if(skb->ip_summed!=CHECKSUM_PARTIAL)-skb->csum=csum_block_add(skb->csum,next_skb->csum,skb_size);--/* Update sequence range on original skb. */-TCP_SKB_CB(skb)->end_seq=TCP_SKB_CB(next_skb)->end_seq;+skb_append_data(next_skb,skb);/* Merge over control information. This moves PSH/FIN etc. over */TCP_SKB_CB(skb)->tcp_flags|=TCP_SKB_CB(next_skb)->tcp_flags;
@@ -0,0 +1,215 @@+#include<linux/skbuff.h>+#include<net/tcp.h>++intsysctl_tcp_rdb_max_bytes__read_mostly;+intsysctl_tcp_rdb_max_packets__read_mostly=1;++/**+*rdb_check_rtx_queue_loss()-performlossdetectionbyanalysingACKs.+*@sk:socket.+*+*Return:Thenumberofpacketsthatarepresumedtobelost.+*/+staticunsignedintrdb_check_rtx_queue_loss(structsock*sk)+{+structsk_buff*skb,*tmp;+structtcp_skb_cb*scb;+u32seq_acked=tcp_sk(sk)->snd_una;+unsignedintpackets_lost=0;++tcp_for_write_queue(skb,sk){+if(skb==tcp_send_head(sk))+break;++scb=TCP_SKB_CB(skb);+/* The ACK acknowledges parts of the data in this SKB.+*Canbecausedby:+*-TSO:WeabortasRDBisnotusedonSKBssplitacross+*multiplepacketsonlowerlayersasthesearegreater+*thanoneMSS.+*-Retranscollapse:We'vehadaretrans,solosshasalready+*beendetected.+*/+if(after(scb->end_seq,seq_acked)){+break;+/* The ACKed packet */+}elseif(scb->end_seq==seq_acked){+/* This SKB was sent with no RDB data, or no prior+*unackedSKBsinoutputqueue,sobreakhere.+*/+if(scb->tx.rdb_start_seq==scb->seq||+skb_queue_is_first(&sk->sk_write_queue,skb))+break;+/* Find number of prior SKBs who's data was bundled in+*this(ACKed)SKB.Wepresumeanyredundantdata+*coveringpreviousSKB'sareduetoloss.(An+*exceptionwouldbereordering).+*/+skb=skb->prev;+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+if(!before(TCP_SKB_CB(skb)->seq,scb->tx.rdb_start_seq))+packets_lost++;+else+break;+}+break;+}+}+returnpackets_lost;+}++/**+*rdb_ack_event()-initiatelossdetection+*@sk:socket+*@flags:flags+*/+voidrdb_ack_event(structsock*sk,u32flags)+{+if(rdb_check_rtx_queue_loss(sk))+tcp_enter_cwr(sk);+}++/**+*rdb_build_skb()-buildthenewRDBSKBandcopyallthedataintothe+*linearpagebuffer.+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionintheoutputengine+*@first_skb:thefirstSKBintheoutputqueuetobebundled+*@bytes_in_rdb_skb:thetotalnumberofdatabytesforthenewrdb_skb+*(NEW+Redundant)+*@gfp_mask:gfp_tallocation+*+*Return:AnewSKBcontainingredundantdata,orNULLifmemoryallocation+*failed+*/+staticstructsk_buff*rdb_build_skb(conststructsock*sk,+structsk_buff*xmit_skb,+structsk_buff*first_skb,+u32bytes_in_rdb_skb,+gfp_tgfp_mask)+{+structsk_buff*rdb_skb,*tmp_skb=first_skb;++rdb_skb=sk_stream_alloc_skb((structsock*)sk,+(int)bytes_in_rdb_skb,+gfp_mask,true);+if(!rdb_skb)+returnNULL;+copy_skb_header(rdb_skb,xmit_skb);+rdb_skb->ip_summed=xmit_skb->ip_summed;+TCP_SKB_CB(rdb_skb)->seq=TCP_SKB_CB(first_skb)->seq;+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(rdb_skb)->seq;++tcp_for_write_queue_from(tmp_skb,sk){+/* Copy data from tmp_skb to rdb_skb */+skb_append_data(tmp_skb,rdb_skb);++/* We are at the last skb that should be included (The unsent+*one)+*/+if(tmp_skb==xmit_skb)+break;+}+returnrdb_skb;+}++/**+*rdb_can_bundle_test()-testifredundantdatacanbebundled+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@mss_now:currentmssvalue+*@bytes_in_rdb_skb:storethetotalnumberofpayloadbytesinthe+*RDBSKBifbundlingcanbeperformed.+*+*Traversetheoutputqueueandcheckifanyun-ackeddatamaybe+*bundled.+*+*Return:ThefirstSKBtobeinthebundle,orNULLifnobundling+*/+staticstructsk_buff*rdb_can_bundle_test(conststructsock*sk,+structsk_buff*xmit_skb,+unsignedintmss_now,+u32*bytes_in_rdb_skb)+{+structsk_buff*first_to_bundle=NULL;+structsk_buff*tmp,*skb=xmit_skb->prev;+u32skbs_in_bundle_count=1;/* Start on 1 to account for xmit_skb */+u32total_payload=xmit_skb->len;++/* We start at xmit_skb->prev, and go backwards. */+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+if((total_payload+skb->len)>mss_now)+break;++if(sysctl_tcp_rdb_max_bytes&&+((total_payload+skb->len)>sysctl_tcp_rdb_max_bytes))+break;++if(sysctl_tcp_rdb_max_packets&&+(skbs_in_bundle_count>sysctl_tcp_rdb_max_packets))+break;++total_payload+=skb->len;+skbs_in_bundle_count++;+first_to_bundle=skb;+}+*bytes_in_rdb_skb=total_payload;+returnfirst_to_bundle;+}++/**+*tcp_transmit_rdb_skb()-trytocreateandsendanRDBpacket+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@mss_now:currentmssvalue+*@gfp_mask:gfp_tallocation+*+*IfanRDBpacketcouldnotbecreatedandsent,transmittheoriginal+*xmit_skb.+*+*Return:0ifsuccessfullysentpacket,elseerror+*/+inttcp_transmit_rdb_skb(structsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,gfp_tgfp_mask)+{+structsk_buff*rdb_skb=NULL;+structsk_buff*first_to_bundle;+u32bytes_in_rdb_skb=0;++/* How we detect that RDB was used. When equal, no RDB data was sent */+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(xmit_skb)->seq;++if(!tcp_stream_is_thin_dpifl(tcp_sk(sk)))+gotoxmit_default;++/* No bundling if first in queue, or on FIN packet */+if(skb_queue_is_first(&sk->sk_write_queue,xmit_skb)||+(TCP_SKB_CB(xmit_skb)->tcp_flags&TCPHDR_FIN))+gotoxmit_default;++/* Find number of (previous) SKBs to get data from */+first_to_bundle=rdb_can_bundle_test(sk,xmit_skb,mss_now,+&bytes_in_rdb_skb);+if(!first_to_bundle)+gotoxmit_default;++/* Create an SKB that contains redundant data starting from+*first_to_bundle.+*/+rdb_skb=rdb_build_skb(sk,xmit_skb,first_to_bundle,+bytes_in_rdb_skb,gfp_mask);+if(!rdb_skb)+gotoxmit_default;++/* Set tstamp for SKB in output queue, because tcp_transmit_skb+*willdothisfortherdb_skbandnottheSKBintheoutput+*queue(xmit_skb).+*/+skb_mstamp_get(&xmit_skb->skb_mstamp);+rdb_skb->skb_mstamp=xmit_skb->skb_mstamp;+returntcp_transmit_skb(sk,rdb_skb,0,gfp_mask);++xmit_default:+/* Transmit the unmodified SKB from output queue */+returntcp_transmit_skb(sk,xmit_skb,1,gfp_mask);+}
From: Eric Dumazet <hidden> Date: 2016-02-18 15:18:09
On mar., 2016-02-16 at 14:51 +0100, Bendik Rønning Opstad wrote:
quoted hunk
RDB is a mechanism that enables a TCP sender to bundle redundant
(already sent) data with TCP packets containing new data. By bundling
(retransmitting) already sent data with each TCP packet containing new
data, the connection will be more resistant to sporadic packet loss
which reduces the application layer latency significantly in congested
scenarios.
-static void copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
+void copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
{
__copy_skb_header(new, old);
Why are you exporting this ? tcp is statically linked into vmlinux.
+/**
+ * skb_append_data() - copy data from an SKB to the end of another
+ * update end sequence number and checksum
+ * @from_skb: the SKB to copy data from
+ * @to_skb: the SKB to copy data to
+ */
+void skb_append_data(struct sk_buff *from_skb, struct sk_buff *to_skb)
+{
+ skb_copy_from_linear_data(from_skb, skb_put(to_skb, from_skb->len),
+ from_skb->len);
+ /* Update sequence range on original skb. */
+ TCP_SKB_CB(to_skb)->end_seq = TCP_SKB_CB(from_skb)->end_seq;
+
+ if (from_skb->ip_summed == CHECKSUM_PARTIAL)
+ to_skb->ip_summed = CHECKSUM_PARTIAL;
+
+ if (to_skb->ip_summed != CHECKSUM_PARTIAL)
+ to_skb->csum = csum_block_add(to_skb->csum, from_skb->csum,
+ to_skb->len);
+}
+EXPORT_SYMBOL(skb_append_data);
Same remark here.
And this is really a tcp helper, you should add a tcp_ prefix.
About rdb_build_skb() : I do not see where you make sure
@bytes_in_rdb_skb is not too big ?
tcp_rdb_max_bytes & tcp_rdb_max_packets seem to have no .extra2 upper
limit, so a user could do something really stupid and attempt to crash
the kernel.
Presumably I would use SKB_MAX_HEAD(MAX_TCP_HEADER) so that we do not
try high order page allocation.
Why are you exporting this ? tcp is statically linked into vmlinux.
Ah, this is actually leftover from the earlier module based
implementation of RDB. Will remove.
quoted
+EXPORT_SYMBOL(skb_append_data);
Same remark here.
Will remove.
And this is really a tcp helper, you should add a tcp_ prefix.
Certainly.
About rdb_build_skb() : I do not see where you make sure
@bytes_in_rdb_skb is not too big ?
The number of previous SKBs in the queue to copy data from is given
by rdb_can_bundle_test(), which tests if total payload does not
exceed the MSS. Only if there is room (within the MSS) will it test
the sysctl options to further restrict bundling:
+ /* We start at xmit_skb->prev, and go backwards. */
+ tcp_for_write_queue_reverse_from_safe(skb, tmp, sk) {
+ if ((total_payload + skb->len) > mss_now)
+ break;
+
+ if (sysctl_tcp_rdb_max_bytes &&
+ ((total_payload + skb->len) > sysctl_tcp_rdb_max_bytes))
+ break;
I'll combine these two to (total_payload + skb->len) > max_payload
tcp_rdb_max_bytes & tcp_rdb_max_packets seem to have no .extra2 upper
limit, so a user could do something really stupid and attempt to crash
the kernel.
Those sysctl additions are actually a bit buggy, specifically the
proc_handlers.
Is it not sufficient to ensure that 0 is the lowest possible value?
The max payload limit is really min(mss_now, sysctl_tcp_rdb_max_bytes),
so if sysctl_tcp_rdb_max_bytes or sysctl_tcp_rdb_max_packets are set too
large, bundling will simply be limited by the MSS.
Presumably I would use SKB_MAX_HEAD(MAX_TCP_HEADER) so that we do not
try high order page allocation.
Do you suggest something like this?:
bytes_in_rdb_skb = min_t(u32, bytes_in_rdb_skb, SKB_MAX_HEAD(MAX_TCP_HEADER));
Is this necessary when bytes_in_rdb_skb will always contain exactly
the required number of bytes for the payload of the (RDB) packet,
which will never be greater than mss_now?
Or is it aimed at scenarios where the page size is so small that
allocating to an MSS (of e.g. 1460) will require high order page
allocation?
Thanks for looking over the code!
Bendik
The existing mechanism for detecting thin streams,
tcp_stream_is_thin(), is based on a static limit of less than 4
packets in flight. This treats streams differently depending on the
connection's RTT, such that a stream on a high RTT link may never be
considered thin, whereas the same application would produce a stream
that would always be thin in a low RTT scenario (e.g. data center).
By calculating a dynamic packets in flight limit (DPIFL), the thin
stream detection will be independent of the RTT and treat streams
equally based on the transmission pattern, i.e. the inter-transmission
time (ITT).
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 8 ++++++++
include/net/tcp.h | 21 +++++++++++++++++++++
net/ipv4/sysctl_net_ipv4.c | 9 +++++++++
net/ipv4/tcp.c | 2 ++
4 files changed, 40 insertions(+)
@@ -708,6 +708,14 @@ tcp_thin_dupack - BOOLEAN Documentation/networking/tcp-thin.txt Default: 0+tcp_thin_dpifl_itt_lower_bound - INTEGER+ Controls the lower bound inter-transmission time (ITT) threshold+ for when a stream is considered thin. The value is specified in+ microseconds, and may not be lower than 10000 (10 ms). Based on+ this threshold, a dynamic packets in flight limit (DPIFL) is+ calculated, which is used to classify whether a stream is thin.+ Default: 10000+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -215,6 +215,8 @@ void tcp_time_wait(struct sock *sk, int state, int timeo);/* TCP thin-stream limits */#define TCP_THIN_LINEAR_RETRIES 6 /* After 6 linear retries, do exp. backoff */+/* Lowest possible DPIFL lower bound ITT is 10 ms (10000 usec) */+#define TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN 10000/* TCP initial congestion window as per rfc6928 */#define TCP_INIT_CWND 10
@@ -264,6 +266,7 @@ extern int sysctl_tcp_workaround_signed_windows;externintsysctl_tcp_slow_start_after_idle;externintsysctl_tcp_thin_linear_timeouts;externintsysctl_tcp_thin_dupack;+externintsysctl_tcp_thin_dpifl_itt_lower_bound;externintsysctl_tcp_early_retrans;externintsysctl_tcp_limit_output_bytes;externintsysctl_tcp_challenge_ack_limit;
@@ -1645,6 +1648,24 @@ static inline bool tcp_stream_is_thin(struct tcp_sock *tp)returntp->packets_out<4&&!tcp_in_initial_slowstart(tp);}+/**+*tcp_stream_is_thin_dpifl()-TestsifthestreamisthinbasedondynamicPIF+*limit+*@tp:thetcp_sockstruct+*+*Return:trueifcurrentpacketsinflight(PIF)countislowerthan+*thedynamicPIFlimit,elsefalse+*/+staticinlinebooltcp_stream_is_thin_dpifl(conststructtcp_sock*tp)+{+/* Calculate the maximum allowed PIF limit by dividing the RTT by+*theminimumallowedinter-transmissiontime(ITT).+*TestsifPIF<RTT/ITT-lower-bound+*/+return(u64)tcp_packets_in_flight(tp)*+sysctl_tcp_thin_dpifl_itt_lower_bound<(tp->srtt_us>>3);+}+/* /proc */enumtcp_seq_states{TCP_SEQ_STATE_LISTENING,
@@ -41,6 +41,7 @@ static int tcp_syn_retries_min = 1;staticinttcp_syn_retries_max=MAX_TCP_SYNCNT;staticintip_ping_group_range_min[]={0,0};staticintip_ping_group_range_max[]={GID_T_MAX,GID_T_MAX};+staticinttcp_thin_dpifl_itt_lower_bound_min=TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN;/* Update system visible IP port range */staticvoidset_local_port_range(structnet*net,intrange[2])
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be
made not to negatively affect competing traffic in an unfair manner.
Note: Current patch set depends on the patch "tcp: refactor struct tcp_skb_cb"
(http://patchwork.ozlabs.org/patch/510674)
These patches have also been tested with as set of packetdrill scripts
located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as
in the paper "Latency and Fairness Trade-Off for Thin Streams using
Redundant Data Bundling in TCP"[2].
[1] http://home.ifi.uio.no/paalh/students/BendikOpstad.pdf
[2] http://home.ifi.uio.no/bendiko/rdb_fairness_tradeoff.pdf
Changes:
v5 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed two unnecessary EXPORT_SYMOBOLs (Thanks Eric)
* Renamed skb_append_data() to tcp_skb_append_data() (Thanks Eric)
* Fixed bugs in additions to ipv4_table (sysctl_net_ipv4.c)
* Merged the two if tests for max payload of RDB packet in
rdb_can_bundle_test()
* Renamed rdb_check_rtx_queue_loss() to rdb_detect_loss()
and restructured to reduce indentation.
* Improved docs
* Revised commit message to be more detailed.
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Fixed bug in additions to ipv4_table (sysctl_net_ipv4.c)
v4 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Moved skb_append_data() to tcp_output.c and call this
function from tcp_collapse_retrans() as well.
* Merged functionality of create_rdb_skb() into
tcp_transmit_rdb_skb()
* Removed one parameter from rdb_can_bundle_test()
v3 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Changed name of sysctl variable from tcp_rdb_max_skbs to
tcp_rdb_max_packets after comment from Eric Dumazet about
not exposing internal (kernel) names like skb.
* Formatting and function docs fixes
v2 (RFC/PATCH):
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Change calculation in tcp_stream_is_thin_dpifl based on
feedback from Eric Dumazet.
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed setting nonagle in do_tcp_setsockopt (TCP_RDB)
to reduce complexity as commented by Neal Cardwell.
* Cleaned up loss detection code in rdb_check_rtx_queue_loss
v1 (RFC/PATCH)
Bendik Rønning Opstad (2):
tcp: Add DPIFL thin stream detection mechanism
tcp: Add Redundant Data Bundling (RDB)
Documentation/networking/ip-sysctl.txt | 23 ++++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 36 ++++++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 2 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 34 +++++
net/ipv4/tcp.c | 16 ++-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 47 ++++---
net/ipv4/tcp_rdb.c | 225 +++++++++++++++++++++++++++++++++
12 files changed, 371 insertions(+), 23 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
--
1.9.1
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games,
remote control systems, and VoIP, produce traffic with thin-stream
characteristics, characterized by small packets and relatively high
inter-transmission times (ITT). When experiencing packet loss, such
latency-sensitive applications are heavily penalized by the need to
retransmit lost packets, which increases the latency by a minimum of
one RTT for the lost packet. Packets coming after a lost packet are
held back due to head-of-line blocking, causing increased delays for
all data segments until the lost packet has been retransmitted.
RDB enables a TCP sender to bundle redundant (already sent) data with
TCP packets containing small segments of new data. By resending
un-ACKed data from the output queue in packets with new data, RDB
reduces the need to retransmit data segments on connections
experiencing sporadic packet loss. By avoiding a retransmit, RDB
evades the latency increase of at least one RTT for the lost packet,
as well as alleviating head-of-line blocking for the packets following
the lost packet. This makes the TCP connection more resistant to
latency fluctuations, and reduces the application layer latency
significantly in lossy environments.
Main functionality added:
o When a packet is scheduled for transmission, RDB builds and
transmits a new SKB containing both the unsent data as well as
data of previously sent packets from the TCP output queue.
o RDB will only be used for streams classified as thin by the
function tcp_stream_is_thin_dpifl(). This enforces a lower bound
on the ITT for streams that may benefit from RDB, controlled by
the sysctl variable net.ipv4.tcp_thin_dpifl_itt_lower_bound.
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
RDB can be enabled on a connection with the socket option TCP_RDB, or
on all new connections by setting the sysctl variable
net.ipv4.tcp_rdb=1
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 15 +++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 15 +++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 2 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 25 ++++
net/ipv4/tcp.c | 14 +-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 47 ++++---
net/ipv4/tcp_rdb.c | 225 +++++++++++++++++++++++++++++++++
12 files changed, 331 insertions(+), 23 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.+ Default: 0++tcp_rdb_max_bytes - INTEGER+ Enable restriction on how many bytes an RDB packet can contain.+ This is the total amount of payload including the new unsent data.+ Default: 0++tcp_rdb_max_packets - INTEGER+ Enable restriction on how many previous packets in the output queue+ RDB may include data from. A value of 1 will restrict bundling to+ only the data from the last packet that was sent.+ Default: 1+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -763,6 +774,7 @@ struct tcp_skb_cb {union{struct{/* There is space for up to 20 bytes */+__u32rdb_start_seq;/* Start seq of rdb data bundled */}tx;/* only used for outgoing skbs */union{structinet_skb_parmh4;
@@ -115,6 +115,7 @@ enum {#define TCP_CC_INFO 26 /* Get Congestion Control (optional) info */#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */+#define TCP_RDB 29 /* Enable RDB mechanism */structtcp_repair_opt{__u32opt_code;
@@ -3537,6 +3537,9 @@ static inline void tcp_in_ack_event(struct sock *sk, u32 flags)if(icsk->icsk_ca_ops->in_ack_event)icsk->icsk_ca_ops->in_ack_event(sk,flags);++if(unlikely(tcp_sk(sk)->rdb))+rdb_ack_event(sk,flags);}/* Congestion control has updated the cwnd already. So if we're in
@@ -2110,9 +2110,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,break;}-if(unlikely(tcp_transmit_skb(sk,skb,1,gfp)))+if(unlikely(tcp_sk(sk)->rdb)){+if(tcp_transmit_rdb_skb(sk,skb,mss_now,gfp))+break;+}elseif(unlikely(tcp_transmit_skb(sk,skb,1,gfp))){break;-+}repair:/* Advance the send_head. This one is sent out.*Thiscallwillincrementpackets_out.
@@ -2439,15 +2442,31 @@ u32 __tcp_select_window(struct sock *sk)returnwindow;}+/**+*tcp_skb_append_data()-copylineardatafromanSKBtotheendofanother+*andupdateendsequencenumberandchecksum+*@from_skb:theSKBtocopydatafrom+*@to_skb:theSKBtocopydatato+*/+voidtcp_skb_append_data(structsk_buff*from_skb,structsk_buff*to_skb)+{+skb_copy_from_linear_data(from_skb,skb_put(to_skb,from_skb->len),+from_skb->len);+TCP_SKB_CB(to_skb)->end_seq=TCP_SKB_CB(from_skb)->end_seq;++if(from_skb->ip_summed==CHECKSUM_PARTIAL)+to_skb->ip_summed=CHECKSUM_PARTIAL;++if(to_skb->ip_summed!=CHECKSUM_PARTIAL)+to_skb->csum=csum_block_add(to_skb->csum,from_skb->csum,+to_skb->len);+}+/* Collapses two adjacent SKB's during retransmission. */staticvoidtcp_collapse_retrans(structsock*sk,structsk_buff*skb){structtcp_sock*tp=tcp_sk(sk);structsk_buff*next_skb=tcp_write_queue_next(sk,skb);-intskb_size,next_skb_size;--skb_size=skb->len;-next_skb_size=next_skb->len;BUG_ON(tcp_skb_pcount(skb)!=1||tcp_skb_pcount(next_skb)!=1);
@@ -2455,17 +2474,7 @@ static void tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)tcp_unlink_write_queue(next_skb,sk);-skb_copy_from_linear_data(next_skb,skb_put(skb,next_skb_size),-next_skb_size);--if(next_skb->ip_summed==CHECKSUM_PARTIAL)-skb->ip_summed=CHECKSUM_PARTIAL;--if(skb->ip_summed!=CHECKSUM_PARTIAL)-skb->csum=csum_block_add(skb->csum,next_skb->csum,skb_size);--/* Update sequence range on original skb. */-TCP_SKB_CB(skb)->end_seq=TCP_SKB_CB(next_skb)->end_seq;+tcp_skb_append_data(next_skb,skb);/* Merge over control information. This moves PSH/FIN etc. over */TCP_SKB_CB(skb)->tcp_flags|=TCP_SKB_CB(next_skb)->tcp_flags;
@@ -0,0 +1,225 @@+#include<linux/skbuff.h>+#include<net/tcp.h>++intsysctl_tcp_rdb_max_bytes__read_mostly;+intsysctl_tcp_rdb_max_packets__read_mostly=1;++/**+*rdb_detect_loss()-performlossdetectionbyanalysingACKs+*@sk:socket+*+*TraversetheoutputqueueandcheckiftheACKedpacketisanRDBpacketand+*iftheredundantdatacoversoneormoreun-ACKedSKBs.IftheincomingACK+*acknowledgesmultipleSKBs,wecanpresumepacketlosshasoccurred.+*+*WecaninferpacketlossthiswaybecausewecanexpectoneACKper+*transmitteddatapacket,asdelayedACKsaredisabledwhenahostreceives+*packetswherethesequencenumberisnottheexpectedsequencenumber.+*+*Return:Thenumberofpacketsthatarepresumedtobelost+*/+staticunsignedintrdb_detect_loss(structsock*sk)+{+structsk_buff*skb,*tmp;+structtcp_skb_cb*scb;+u32seq_acked=tcp_sk(sk)->snd_una;+unsignedintpackets_lost=0;++tcp_for_write_queue(skb,sk){+if(skb==tcp_send_head(sk))+break;++scb=TCP_SKB_CB(skb);+/* The ACK acknowledges parts of the data in this SKB.+*Canbecausedby:+*-TSO:WeabortasRDBisnotusedonSKBssplitacross+*multiplepacketsonlowerlayersasthesearegreater+*thanoneMSS.+*-Retranscollapse:We'vehadaretrans,solosshasalready+*beendetected.+*/+if(after(scb->end_seq,seq_acked))+break;+elseif(scb->end_seq!=seq_acked)+continue;++/* We have found the ACKed packet */++/* This packet was sent with no redundant data, or no prior+*un-ACKedSKBsisintheoutputqueue,sobreakhere.+*/+if(scb->tx.rdb_start_seq==scb->seq||+skb_queue_is_first(&sk->sk_write_queue,skb))+break;+/* Find number of prior SKBs whose data was bundled in this+*(ACKed)SKB.Wepresumeanyredundantdatacoveringprevious+*SKB'sareduetoloss.(Anexceptionwouldbereordering).+*/+skb=skb->prev;+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+if(before(TCP_SKB_CB(skb)->seq,scb->tx.rdb_start_seq))+break;+packets_lost++;+}+break;+}+returnpackets_lost;+}++/**+*rdb_ack_event()-initiatelossdetection+*@sk:socket+*@flags:flags+*/+voidrdb_ack_event(structsock*sk,u32flags)+{+if(rdb_detect_loss(sk))+tcp_enter_cwr(sk);+}++/**+*rdb_build_skb()-buildanewRDBSKBandcopyredundant+unsentdatato+*thelinearpagebuffer+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionintheoutputengine+*@first_skb:thefirstSKBintheoutputqueuetobebundled+*@bytes_in_rdb_skb:thetotalnumberofdatabytesforthenewrdb_skb+*(NEW+Redundant)+*@gfp_mask:gfp_tallocation+*+*Return:AnewSKBcontainingredundantdata,orNULLifmemoryallocation+*failed+*/+staticstructsk_buff*rdb_build_skb(conststructsock*sk,+structsk_buff*xmit_skb,+structsk_buff*first_skb,+u32bytes_in_rdb_skb,+gfp_tgfp_mask)+{+structsk_buff*rdb_skb,*tmp_skb=first_skb;++rdb_skb=sk_stream_alloc_skb((structsock*)sk,+(int)bytes_in_rdb_skb,+gfp_mask,false);+if(!rdb_skb)+returnNULL;+copy_skb_header(rdb_skb,xmit_skb);+rdb_skb->ip_summed=xmit_skb->ip_summed;+TCP_SKB_CB(rdb_skb)->seq=TCP_SKB_CB(first_skb)->seq;+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(rdb_skb)->seq;++/* Start on first_skb and append payload from each SKB in the output+*queueontordb_skbuntilwereachxmit_skb.+*/+tcp_for_write_queue_from(tmp_skb,sk){+tcp_skb_append_data(tmp_skb,rdb_skb);++/* We reached xmit_skb, containing the unsent data */+if(tmp_skb==xmit_skb)+break;+}+returnrdb_skb;+}++/**+*rdb_can_bundle_test()-testifredundantdatacanbebundled+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@max_payload:themaximumallowedpayloadbytesfortheRDBSKB+*@bytes_in_rdb_skb:storethetotalnumberofpayloadbytesinthe+*RDBSKBifbundlingcanbeperformed+*+*Traversetheoutputqueueandcheckifanyun-ackeddatamaybe+*bundled.+*+*Return:ThefirstSKBtobeinthebundle,orNULLifnobundling+*/+staticstructsk_buff*rdb_can_bundle_test(conststructsock*sk,+structsk_buff*xmit_skb,+unsignedintmax_payload,+u32*bytes_in_rdb_skb)+{+structsk_buff*first_to_bundle=NULL;+structsk_buff*tmp,*skb=xmit_skb->prev;+u32skbs_in_bundle_count=1;/* Start on 1 to account for xmit_skb */+u32total_payload=xmit_skb->len;++if(sysctl_tcp_rdb_max_bytes)+max_payload=min_t(unsignedint,max_payload,+sysctl_tcp_rdb_max_bytes);++/* We start at xmit_skb->prev, and go backwards */+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+/* Including data from this SKB would exceed payload limit */+if((total_payload+skb->len)>max_payload)+break;++if(sysctl_tcp_rdb_max_packets&&+(skbs_in_bundle_count>sysctl_tcp_rdb_max_packets))+break;++total_payload+=skb->len;+skbs_in_bundle_count++;+first_to_bundle=skb;+}+*bytes_in_rdb_skb=total_payload;+returnfirst_to_bundle;+}++/**+*tcp_transmit_rdb_skb()-trytocreateandsendanRDBpacket+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@mss_now:currentmssvalue+*@gfp_mask:gfp_tallocation+*+*IfanRDBpacketcouldnotbecreatedandsent,transmitthe+*originalunmodifiedSKB(xmit_skb).+*+*Return:0ifsuccessfullysentpacket,elseerror+*/+inttcp_transmit_rdb_skb(structsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,gfp_tgfp_mask)+{+structsk_buff*rdb_skb=NULL;+structsk_buff*first_to_bundle;+u32bytes_in_rdb_skb=0;++/* How we detect that RDB was used. When equal, no RDB data was sent */+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(xmit_skb)->seq;++if(!tcp_stream_is_thin_dpifl(tcp_sk(sk)))+gotoxmit_default;++/* No bundling if first in queue, or on FIN packet */+if(skb_queue_is_first(&sk->sk_write_queue,xmit_skb)||+(TCP_SKB_CB(xmit_skb)->tcp_flags&TCPHDR_FIN))+gotoxmit_default;++/* Find number of (previous) SKBs to get data from */+first_to_bundle=rdb_can_bundle_test(sk,xmit_skb,mss_now,+&bytes_in_rdb_skb);+if(!first_to_bundle)+gotoxmit_default;++/* Create an SKB that contains redundant data starting from+*first_to_bundle.+*/+rdb_skb=rdb_build_skb(sk,xmit_skb,first_to_bundle,+bytes_in_rdb_skb,gfp_mask);+if(!rdb_skb)+gotoxmit_default;++/* Set skb_mstamp for the SKB in the output queue (xmit_skb) containing+*theyetunsentdata.Normallythiswouldbedoneby+*tcp_transmit_skb(),butaswepassinrdb_skbinstead,xmit_skb's+*timestampwillnotbetouched.+*/+skb_mstamp_get(&xmit_skb->skb_mstamp);+rdb_skb->skb_mstamp=xmit_skb->skb_mstamp;+returntcp_transmit_skb(sk,rdb_skb,0,gfp_mask);++xmit_default:+/* Transmit the unmodified SKB from output queue */+returntcp_transmit_skb(sk,xmit_skb,1,gfp_mask);+}
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be
made not to negatively affect competing traffic in an unfair manner.
Note: Current patch set depends on the patch "tcp: refactor struct tcp_skb_cb"
(http://patchwork.ozlabs.org/patch/510674)
These patches have also been tested with as set of packetdrill scripts
located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as
in the paper "Latency and Fairness Trade-Off for Thin Streams using
Redundant Data Bundling in TCP"[2].
[1] http://home.ifi.uio.no/paalh/students/BendikOpstad.pdf
[2] http://home.ifi.uio.no/bendiko/rdb_fairness_tradeoff.pdf
Changes:
v6 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Renamed rdb_ack_event() to tcp_rdb_ack_event() (Thanks DaveM)
* Minor doc changes
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Minor doc changes
v5 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed two unnecessary EXPORT_SYMOBOLs (Thanks Eric)
* Renamed skb_append_data() to tcp_skb_append_data() (Thanks Eric)
* Fixed bugs in additions to ipv4_table (sysctl_net_ipv4.c)
* Merged the two if tests for max payload of RDB packet in
rdb_can_bundle_test()
* Renamed rdb_check_rtx_queue_loss() to rdb_detect_loss()
and restructured to reduce indentation.
* Improved docs
* Revised commit message to be more detailed.
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Fixed bug in additions to ipv4_table (sysctl_net_ipv4.c)
v4 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Moved skb_append_data() to tcp_output.c and call this
function from tcp_collapse_retrans() as well.
* Merged functionality of create_rdb_skb() into
tcp_transmit_rdb_skb()
* Removed one parameter from rdb_can_bundle_test()
v3 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Changed name of sysctl variable from tcp_rdb_max_skbs to
tcp_rdb_max_packets after comment from Eric Dumazet about
not exposing internal (kernel) names like skb.
* Formatting and function docs fixes
v2 (RFC/PATCH):
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Change calculation in tcp_stream_is_thin_dpifl based on
feedback from Eric Dumazet.
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed setting nonagle in do_tcp_setsockopt (TCP_RDB)
to reduce complexity as commented by Neal Cardwell.
* Cleaned up loss detection code in rdb_check_rtx_queue_loss
v1 (RFC/PATCH)
Bendik Rønning Opstad (2):
tcp: Add DPIFL thin stream detection mechanism
tcp: Add Redundant Data Bundling (RDB)
Documentation/networking/ip-sysctl.txt | 23 ++++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 36 ++++++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 2 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 34 +++++
net/ipv4/tcp.c | 16 ++-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 48 ++++---
net/ipv4/tcp_rdb.c | 228 +++++++++++++++++++++++++++++++++
12 files changed, 375 insertions(+), 23 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
--
1.9.1
The existing mechanism for detecting thin streams,
tcp_stream_is_thin(), is based on a static limit of less than 4
packets in flight. This treats streams differently depending on the
connection's RTT, such that a stream on a high RTT link may never be
considered thin, whereas the same application would produce a stream
that would always be thin in a low RTT scenario (e.g. data center).
By calculating a dynamic packets in flight limit (DPIFL), the thin
stream detection will be independent of the RTT and treat streams
equally based on the transmission pattern, i.e. the inter-transmission
time (ITT).
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 8 ++++++++
include/net/tcp.h | 21 +++++++++++++++++++++
net/ipv4/sysctl_net_ipv4.c | 9 +++++++++
net/ipv4/tcp.c | 2 ++
4 files changed, 40 insertions(+)
@@ -708,6 +708,14 @@ tcp_thin_dupack - BOOLEAN Documentation/networking/tcp-thin.txt Default: 0+tcp_thin_dpifl_itt_lower_bound - INTEGER+ Controls the lower bound inter-transmission time (ITT) threshold+ for when a stream is considered thin. The value is specified in+ microseconds, and may not be lower than 10000 (10 ms). Based on+ this threshold, a dynamic packets in flight limit (DPIFL) is+ calculated, which is used to classify whether a stream is thin.+ Default: 10000+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -215,6 +215,8 @@ void tcp_time_wait(struct sock *sk, int state, int timeo);/* TCP thin-stream limits */#define TCP_THIN_LINEAR_RETRIES 6 /* After 6 linear retries, do exp. backoff */+/* Lowest possible DPIFL lower bound ITT is 10 ms (10000 usec) */+#define TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN 10000/* TCP initial congestion window as per rfc6928 */#define TCP_INIT_CWND 10
@@ -264,6 +266,7 @@ extern int sysctl_tcp_workaround_signed_windows;externintsysctl_tcp_slow_start_after_idle;externintsysctl_tcp_thin_linear_timeouts;externintsysctl_tcp_thin_dupack;+externintsysctl_tcp_thin_dpifl_itt_lower_bound;externintsysctl_tcp_early_retrans;externintsysctl_tcp_limit_output_bytes;externintsysctl_tcp_challenge_ack_limit;
@@ -1645,6 +1648,24 @@ static inline bool tcp_stream_is_thin(struct tcp_sock *tp)returntp->packets_out<4&&!tcp_in_initial_slowstart(tp);}+/**+*tcp_stream_is_thin_dpifl()-Testifthestreamisthinbasedon+*dynamicPIFlimit(DPIFL)+*@tp:thetcp_sockstruct+*+*Return:trueifcurrentpacketsinflight(PIF)countislowerthan+*thedynamicPIFlimit,elsefalse+*/+staticinlinebooltcp_stream_is_thin_dpifl(conststructtcp_sock*tp)+{+/* Calculate the maximum allowed PIF limit by dividing the RTT by+*theminimumallowedinter-transmissiontime(ITT).+*TestsifPIF<RTT/ITT-lower-bound+*/+return(u64)tcp_packets_in_flight(tp)*+sysctl_tcp_thin_dpifl_itt_lower_bound<(tp->srtt_us>>3);+}+/* /proc */enumtcp_seq_states{TCP_SEQ_STATE_LISTENING,
@@ -41,6 +41,7 @@ static int tcp_syn_retries_min = 1;staticinttcp_syn_retries_max=MAX_TCP_SYNCNT;staticintip_ping_group_range_min[]={0,0};staticintip_ping_group_range_max[]={GID_T_MAX,GID_T_MAX};+staticinttcp_thin_dpifl_itt_lower_bound_min=TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN;/* Update system visible IP port range */staticvoidset_local_port_range(structnet*net,intrange[2])
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games,
remote control systems, and VoIP, produce traffic with thin-stream
characteristics, characterized by small packets and relatively high
inter-transmission times (ITT). When experiencing packet loss, such
latency-sensitive applications are heavily penalized by the need to
retransmit lost packets, which increases the latency by a minimum of
one RTT for the lost packet. Packets coming after a lost packet are
held back due to head-of-line blocking, causing increased delays for
all data segments until the lost packet has been retransmitted.
RDB enables a TCP sender to bundle redundant (already sent) data with
TCP packets containing small segments of new data. By resending
un-ACKed data from the output queue in packets with new data, RDB
reduces the need to retransmit data segments on connections
experiencing sporadic packet loss. By avoiding a retransmit, RDB
evades the latency increase of at least one RTT for the lost packet,
as well as alleviating head-of-line blocking for the packets following
the lost packet. This makes the TCP connection more resistant to
latency fluctuations, and reduces the application layer latency
significantly in lossy environments.
Main functionality added:
o When a packet is scheduled for transmission, RDB builds and
transmits a new SKB containing both the unsent data as well as
data of previously sent packets from the TCP output queue.
o RDB will only be used for streams classified as thin by the
function tcp_stream_is_thin_dpifl(). This enforces a lower bound
on the ITT for streams that may benefit from RDB, controlled by
the sysctl variable net.ipv4.tcp_thin_dpifl_itt_lower_bound.
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
RDB can be enabled on a connection with the socket option TCP_RDB, or
on all new connections by setting the sysctl variable
net.ipv4.tcp_rdb=1
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 15 +++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 15 +++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 2 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 25 ++++
net/ipv4/tcp.c | 14 +-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 48 ++++---
net/ipv4/tcp_rdb.c | 228 +++++++++++++++++++++++++++++++++
12 files changed, 335 insertions(+), 23 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.+ Default: 0++tcp_rdb_max_bytes - INTEGER+ Enable restriction on how many bytes an RDB packet can contain.+ This is the total amount of payload including the new unsent data.+ Default: 0++tcp_rdb_max_packets - INTEGER+ Enable restriction on how many previous packets in the output queue+ RDB may include data from. A value of 1 will restrict bundling to+ only the data from the last packet that was sent.+ Default: 1+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -763,6 +774,7 @@ struct tcp_skb_cb {union{struct{/* There is space for up to 20 bytes */+__u32rdb_start_seq;/* Start seq of rdb data */}tx;/* only used for outgoing skbs */union{structinet_skb_parmh4;
@@ -115,6 +115,7 @@ enum {#define TCP_CC_INFO 26 /* Get Congestion Control (optional) info */#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */+#define TCP_RDB 29 /* Enable Redundant Data Bundling mechanism */structtcp_repair_opt{__u32opt_code;
@@ -3537,6 +3537,9 @@ static inline void tcp_in_ack_event(struct sock *sk, u32 flags)if(icsk->icsk_ca_ops->in_ack_event)icsk->icsk_ca_ops->in_ack_event(sk,flags);++if(unlikely(tcp_sk(sk)->rdb))+tcp_rdb_ack_event(sk,flags);}/* Congestion control has updated the cwnd already. So if we're in
@@ -2110,9 +2110,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,break;}-if(unlikely(tcp_transmit_skb(sk,skb,1,gfp)))+if(unlikely(tcp_sk(sk)->rdb)){+if(tcp_transmit_rdb_skb(sk,skb,mss_now,gfp))+break;+}elseif(unlikely(tcp_transmit_skb(sk,skb,1,gfp))){break;-+}repair:/* Advance the send_head. This one is sent out.*Thiscallwillincrementpackets_out.
@@ -2439,15 +2442,32 @@ u32 __tcp_select_window(struct sock *sk)returnwindow;}+/**+*tcp_skb_append_data()-copythelineardatafromanSKBtotheend+*ofanotherandupdateendsequencenumber+*andchecksum+*@from_skb:theSKBtocopydatafrom+*@to_skb:theSKBtocopydatato+*/+voidtcp_skb_append_data(structsk_buff*from_skb,structsk_buff*to_skb)+{+skb_copy_from_linear_data(from_skb,skb_put(to_skb,from_skb->len),+from_skb->len);+TCP_SKB_CB(to_skb)->end_seq=TCP_SKB_CB(from_skb)->end_seq;++if(from_skb->ip_summed==CHECKSUM_PARTIAL)+to_skb->ip_summed=CHECKSUM_PARTIAL;++if(to_skb->ip_summed!=CHECKSUM_PARTIAL)+to_skb->csum=csum_block_add(to_skb->csum,from_skb->csum,+to_skb->len);+}+/* Collapses two adjacent SKB's during retransmission. */staticvoidtcp_collapse_retrans(structsock*sk,structsk_buff*skb){structtcp_sock*tp=tcp_sk(sk);structsk_buff*next_skb=tcp_write_queue_next(sk,skb);-intskb_size,next_skb_size;--skb_size=skb->len;-next_skb_size=next_skb->len;BUG_ON(tcp_skb_pcount(skb)!=1||tcp_skb_pcount(next_skb)!=1);
@@ -2455,17 +2475,7 @@ static void tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)tcp_unlink_write_queue(next_skb,sk);-skb_copy_from_linear_data(next_skb,skb_put(skb,next_skb_size),-next_skb_size);--if(next_skb->ip_summed==CHECKSUM_PARTIAL)-skb->ip_summed=CHECKSUM_PARTIAL;--if(skb->ip_summed!=CHECKSUM_PARTIAL)-skb->csum=csum_block_add(skb->csum,next_skb->csum,skb_size);--/* Update sequence range on original skb. */-TCP_SKB_CB(skb)->end_seq=TCP_SKB_CB(next_skb)->end_seq;+tcp_skb_append_data(next_skb,skb);/* Merge over control information. This moves PSH/FIN etc. over */TCP_SKB_CB(skb)->tcp_flags|=TCP_SKB_CB(next_skb)->tcp_flags;
@@ -0,0 +1,228 @@+#include<linux/skbuff.h>+#include<net/tcp.h>++intsysctl_tcp_rdb_max_bytes__read_mostly;+intsysctl_tcp_rdb_max_packets__read_mostly=1;++/**+*rdb_detect_loss()-performRDBlossdetectionbyanalysingACKs+*@sk:socket+*+*TraversetheoutputqueueandcheckiftheACKedpacketisanRDB+*packetandiftheredundantdatacoversoneormoreun-ACKedSKBs.+*IftheincomingACKacknowledgesmultipleSKBs,wecanpresume+*packetlosshasoccurred.+*+*WecaninferpacketlossthiswaybecausewecanexpectoneACKper+*transmitteddatapacket,asdelayedACKsaredisabledwhenahost+*receivespacketswherethesequencenumberisnottheexpected+*sequencenumber.+*+*Return:Thenumberofpacketsthatarepresumedtobelost+*/+staticunsignedintrdb_detect_loss(structsock*sk)+{+structsk_buff*skb,*tmp;+structtcp_skb_cb*scb;+u32seq_acked=tcp_sk(sk)->snd_una;+unsignedintpackets_lost=0;++tcp_for_write_queue(skb,sk){+if(skb==tcp_send_head(sk))+break;++scb=TCP_SKB_CB(skb);+/* The ACK acknowledges parts of the data in this SKB.+*Canbecausedby:+*-TSO:WeabortasRDBisnotusedonSKBssplitacross+*multiplepacketsonlowerlayersasthesearegreater+*thanoneMSS.+*-Retranscollapse:We'vehadaretrans,solosshasalready+*beendetected.+*/+if(after(scb->end_seq,seq_acked))+break;+elseif(scb->end_seq!=seq_acked)+continue;++/* We have found the ACKed packet */++/* This packet was sent with no redundant data, or no prior+*un-ACKedSKBsisintheoutputqueue,sobreakhere.+*/+if(scb->tx.rdb_start_seq==scb->seq||+skb_queue_is_first(&sk->sk_write_queue,skb))+break;+/* Find number of prior SKBs whose data was bundled in this+*(ACKed)SKB.Wepresumeanyredundantdatacoveringprevious+*SKB'sareduetoloss.(Anexceptionwouldbereordering).+*/+skb=skb->prev;+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+if(before(TCP_SKB_CB(skb)->seq,scb->tx.rdb_start_seq))+break;+packets_lost++;+}+break;+}+returnpackets_lost;+}++/**+*tcp_rdb_ack_event()-initiateRDBlossdetection+*@sk:socket+*@flags:flags+*/+voidtcp_rdb_ack_event(structsock*sk,u32flags)+{+if(rdb_detect_loss(sk))+tcp_enter_cwr(sk);+}++/**+*rdb_build_skb()-buildanewRDBSKBandcopyredundant+unsent+*datatothelinearpagebuffer+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionintheoutputengine+*@first_skb:thefirstSKBintheoutputqueuetobebundled+*@bytes_in_rdb_skb:thetotalnumberofdatabytesforthenew+*rdb_skb(NEW+Redundant)+*@gfp_mask:gfp_tallocation+*+*Return:AnewSKBcontainingredundantdata,orNULLifmemory+*allocationfailed+*/+staticstructsk_buff*rdb_build_skb(conststructsock*sk,+structsk_buff*xmit_skb,+structsk_buff*first_skb,+u32bytes_in_rdb_skb,+gfp_tgfp_mask)+{+structsk_buff*rdb_skb,*tmp_skb=first_skb;++rdb_skb=sk_stream_alloc_skb((structsock*)sk,+(int)bytes_in_rdb_skb,+gfp_mask,false);+if(!rdb_skb)+returnNULL;+copy_skb_header(rdb_skb,xmit_skb);+rdb_skb->ip_summed=xmit_skb->ip_summed;+TCP_SKB_CB(rdb_skb)->seq=TCP_SKB_CB(first_skb)->seq;+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(rdb_skb)->seq;++/* Start on first_skb and append payload from each SKB in the output+*queueontordb_skbuntilwereachxmit_skb.+*/+tcp_for_write_queue_from(tmp_skb,sk){+tcp_skb_append_data(tmp_skb,rdb_skb);++/* We reached xmit_skb, containing the unsent data */+if(tmp_skb==xmit_skb)+break;+}+returnrdb_skb;+}++/**+*rdb_can_bundle_test()-testifredundantdatacanbebundled+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@max_payload:themaximumallowedpayloadbytesfortheRDBSKB+*@bytes_in_rdb_skb:storethetotalnumberofpayloadbytesinthe+*RDBSKBifbundlingcanbeperformed+*+*Traversetheoutputqueueandcheckifanyun-ackeddatamaybe+*bundled.+*+*Return:ThefirstSKBtobeinthebundle,orNULLifnobundling+*/+staticstructsk_buff*rdb_can_bundle_test(conststructsock*sk,+structsk_buff*xmit_skb,+unsignedintmax_payload,+u32*bytes_in_rdb_skb)+{+structsk_buff*first_to_bundle=NULL;+structsk_buff*tmp,*skb=xmit_skb->prev;+u32skbs_in_bundle_count=1;/* Start on 1 to account for xmit_skb */+u32total_payload=xmit_skb->len;++if(sysctl_tcp_rdb_max_bytes)+max_payload=min_t(unsignedint,max_payload,+sysctl_tcp_rdb_max_bytes);++/* We start at xmit_skb->prev, and go backwards */+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+/* Including data from this SKB would exceed payload limit */+if((total_payload+skb->len)>max_payload)+break;++if(sysctl_tcp_rdb_max_packets&&+(skbs_in_bundle_count>sysctl_tcp_rdb_max_packets))+break;++total_payload+=skb->len;+skbs_in_bundle_count++;+first_to_bundle=skb;+}+*bytes_in_rdb_skb=total_payload;+returnfirst_to_bundle;+}++/**+*tcp_transmit_rdb_skb()-trytocreateandsendanRDBpacket+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@mss_now:currentmssvalue+*@gfp_mask:gfp_tallocation+*+*IfanRDBpacketcouldnotbecreatedandsent,transmitthe+*originalunmodifiedSKB(xmit_skb).+*+*Return:0ifsuccessfullysentpacket,elseerrorfrom+*tcp_transmit_skb+*/+inttcp_transmit_rdb_skb(structsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,gfp_tgfp_mask)+{+structsk_buff*rdb_skb=NULL;+structsk_buff*first_to_bundle;+u32bytes_in_rdb_skb=0;++/* How we detect that RDB was used. When equal, no RDB data was sent */+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(xmit_skb)->seq;++if(!tcp_stream_is_thin_dpifl(tcp_sk(sk)))+gotoxmit_default;++/* No bundling if first in queue, or on FIN packet */+if(skb_queue_is_first(&sk->sk_write_queue,xmit_skb)||+(TCP_SKB_CB(xmit_skb)->tcp_flags&TCPHDR_FIN))+gotoxmit_default;++/* Find number of (previous) SKBs to get data from */+first_to_bundle=rdb_can_bundle_test(sk,xmit_skb,mss_now,+&bytes_in_rdb_skb);+if(!first_to_bundle)+gotoxmit_default;++/* Create an SKB that contains redundant data starting from+*first_to_bundle.+*/+rdb_skb=rdb_build_skb(sk,xmit_skb,first_to_bundle,+bytes_in_rdb_skb,gfp_mask);+if(!rdb_skb)+gotoxmit_default;++/* Set skb_mstamp for the SKB in the output queue (xmit_skb) containing+*theyetunsentdata.Normallythiswouldbedoneby+*tcp_transmit_skb(),butaswepassinrdb_skbinstead,xmit_skb's+*timestampwillnotbetouched.+*/+skb_mstamp_get(&xmit_skb->skb_mstamp);+rdb_skb->skb_mstamp=xmit_skb->skb_mstamp;+returntcp_transmit_skb(sk,rdb_skb,0,gfp_mask);++xmit_default:+/* Transmit the unmodified SKB from output queue */+returntcp_transmit_skb(sk,xmit_skb,1,gfp_mask);+}
I read the paper. I think the underlying idea is neat. but the
implementation is little heavy-weight that requires changes on fast
path (tcp_write_xmit) and space in skb control blocks. ultimately this
patch is meant for a small set of specific applications.
In my mental model (please correct me if I am wrong), losses on these
thin streams would mostly resort to RTOs instead of fast recovery, due
to the bursty nature of Internet losses. The HOLB comes from RTO only
retransmit the first (tiny) unacked packet while a small of new data is
readily available. But since Linux congestion control is packet-based,
and loss cwnd is 1, the new data needs to wait until the 1st packet is
acked which is for another RTT.
Instead what if we only perform RDB on the (first and recurring) RTO
retransmission?
PS. I don't understand how (old) RDB can masquerade the losses by
skipping DUPACKs. Perhaps an example helps. Suppose we send 4 packets
and the last 3 were (s)acked. We perform RDB to send a packet that has
previous 4 payloads + 1 new byte. The sender still gets the loss
information?
Changes:
v6 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Renamed rdb_ack_event() to tcp_rdb_ack_event() (Thanks DaveM)
* Minor doc changes
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Minor doc changes
v5 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed two unnecessary EXPORT_SYMOBOLs (Thanks Eric)
* Renamed skb_append_data() to tcp_skb_append_data() (Thanks Eric)
* Fixed bugs in additions to ipv4_table (sysctl_net_ipv4.c)
* Merged the two if tests for max payload of RDB packet in
rdb_can_bundle_test()
* Renamed rdb_check_rtx_queue_loss() to rdb_detect_loss()
and restructured to reduce indentation.
* Improved docs
* Revised commit message to be more detailed.
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Fixed bug in additions to ipv4_table (sysctl_net_ipv4.c)
v4 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Moved skb_append_data() to tcp_output.c and call this
function from tcp_collapse_retrans() as well.
* Merged functionality of create_rdb_skb() into
tcp_transmit_rdb_skb()
* Removed one parameter from rdb_can_bundle_test()
v3 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Changed name of sysctl variable from tcp_rdb_max_skbs to
tcp_rdb_max_packets after comment from Eric Dumazet about
not exposing internal (kernel) names like skb.
* Formatting and function docs fixes
v2 (RFC/PATCH):
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Change calculation in tcp_stream_is_thin_dpifl based on
feedback from Eric Dumazet.
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed setting nonagle in do_tcp_setsockopt (TCP_RDB)
to reduce complexity as commented by Neal Cardwell.
* Cleaned up loss detection code in rdb_check_rtx_queue_loss
v1 (RFC/PATCH)
Bendik Rønning Opstad (2):
tcp: Add DPIFL thin stream detection mechanism
tcp: Add Redundant Data Bundling (RDB)
Documentation/networking/ip-sysctl.txt | 23 ++++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 36 ++++++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 2 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 34 +++++
net/ipv4/tcp.c | 16 ++-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 48 ++++---
net/ipv4/tcp_rdb.c | 228 +++++++++++++++++++++++++++++++++
12 files changed, 375 insertions(+), 23 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
--
1.9.1
From: Jonas Markussen <hidden> Date: 2016-03-10 02:02:15
On 10 Mar 2016, at 01:20, Yuchung Cheng [off-list ref] wrote:
PS. I don't understand how (old) RDB can masquerade the losses by
skipping DUPACKs. Perhaps an example helps. Suppose we send 4 packets
and the last 3 were (s)acked. We perform RDB to send a packet that has
previous 4 payloads + 1 new byte. The sender still gets the loss
information?
If I’ve understood you correctly, you’re talking about sending 4
packets and the first one is lost?
In this case, RDB will not only bundle on the last/new packet but also
as it sends packet 2 (which will contain 1+2), packet 3 (1+2+3)
and packet 4 (1+2+3+4).
So the fact that packet 1 was lost is masqueraded when it is
recovered by packet 2 and there won’t be any gap in the SACK window
indicating that packet 1 was lost.
Best regards,
Jonas Markussen
On Wed, Mar 9, 2016 at 5:45 PM, Jonas Markussen [off-list ref] wrote:
quoted
On 10 Mar 2016, at 01:20, Yuchung Cheng [off-list ref] wrote:
PS. I don't understand how (old) RDB can masquerade the losses by
skipping DUPACKs. Perhaps an example helps. Suppose we send 4 packets
and the last 3 were (s)acked. We perform RDB to send a packet that has
previous 4 payloads + 1 new byte. The sender still gets the loss
information?
If I’ve understood you correctly, you’re talking about sending 4
packets and the first one is lost?
In this case, RDB will not only bundle on the last/new packet but also
as it sends packet 2 (which will contain 1+2), packet 3 (1+2+3)
and packet 4 (1+2+3+4).
So the fact that packet 1 was lost is masqueraded when it is
recovered by packet 2 and there won’t be any gap in the SACK window
indicating that packet 1 was lost.
I see. Thanks for the clarification.
So my question is still if thin-stream app has enough inflight to use
ack-triggered recovery. i.e., it has to send at least twice within an
RTT.
Also have you tested this with non-Linux receivers? Thanks.
From: Jonas Markussen <hidden> Date: 2016-03-12 09:23:37
On 10 Mar 2016, at 03:27, Yuchung Cheng [off-list ref] wrote:
So my question is still if thin-stream app has enough inflight to use
ack-triggered recovery. i.e., it has to send at least twice within an
RTT.
I see. The thin-stream app must send twice before an RTO in order to
use ACK-triggered recovery, My understanding is that the RTO timer
in many cases can be many times the RTT, e.g., for low-RTT networks
where TCP streams defaults to the default minimal RTT value (200 ms).
Of course, the advantage of RDB is greater when the RTT is high.
The benefit of RDB over other mechanisms that improve how quick
thin-streams are able to discover and recover loss, such as the
tcp_thin_dupack and tcp_early_retrans sysctls, is that the sender
using RDB will already have recovered the lost packet by the time a
regular TCP connection detects the packet loss (from DUPACKs) and
reacts accordingly. This reduces the recovery time by at least one
RTT since it avoids the retransmission all together.
Another impacting mechanism here is delayed ACKs, which also
may affect how long it takes before (S)ACKS arrive at the sender.
My understanding is that delayed ACKs will be disabled when
the incoming packet’s is not the expected sequence number, as is
the case for RDB having packets where old and new data is combined.
This improves ACK feedback for the thin-streams using RDB.
Also have you tested this with non-Linux receivers? Thanks.
We have tested the current version with FreeBSD v10 and Windows 10.
The old version was tested with Windows, FreeBSD, OS X and Linux
back in 2010.
We argue that any TCP implementation complying to the RFCs must
be able to handle segments combining old and new data in the
same way they handle TCP repacketization on retransmissions
(tcp_retrans_collapse).
I read the paper. I think the underlying idea is neat. but the
implementation is little heavy-weight that requires changes on fast
path (tcp_write_xmit) and space in skb control blocks.
Yuchung, thank you for taking the time to review the patch submission
and read the paper.
I must admit I was not particularly happy about the extra if-test on the
fast path, and I fully understand the wish to keep the fast path as
simple and clean as possible.
However, is the performance hit that significant considering the branch
prediction hint for the non-RDB path?
The extra variable needed in the SKB CB does not require increasing the
CB buffer size due to the "tcp: refactor struct tcp_skb_cb" patch:
http://patchwork.ozlabs.org/patch/510674 and uses only some of the space
made available in the outgoing SKBs' CB. Therefore I hoped the extra
variable would be acceptable.
ultimately this
patch is meant for a small set of specific applications.
Yes, the RDB mechanism is aimed at a limited set of applications,
specifically time-dependent applications that produce non-greedy,
application limited (thin) flows. However, our hope is that RDB may
greatly improve TCP's position as a viable alternative for applications
transmitting latency sensitive data.
In my mental model (please correct me if I am wrong), losses on these
thin streams would mostly resort to RTOs instead of fast recovery, due
to the bursty nature of Internet losses.
This depends on the transmission pattern of the applications, which
varies to a great deal, also between the different types of
time-dependent applications that produce thin streams. For short flows,
(bursty) loss at the end will result in an RTO (if TLP does not probe),
but the thin streams are often long lived, and the applications
producing them continue to write small data segments to the socket at
intervals of tens to hundreds of milliseconds.
What controls if an RTO and not fast retransmit will resend the packet,
is the number of PIFs, which directly correlates to how often the
application writes data to the socket in relation to the RTT. As long as
the number of packets successfully completing a round trip before the
RTO is >= the dupACK threshold, they will not depend on RTOs (not
considering TLP). Early retransmit and the TCP_THIN_DUPACK socket option
will also affect the likelihood of RTOs vs fast retransmits.
The HOLB comes from RTO only
retransmit the first (tiny) unacked packet while a small of new data is
readily available. But since Linux congestion control is packet-based,
and loss cwnd is 1, the new data needs to wait until the 1st packet is
acked which is for another RTT.
If I understand you correctly, you are referring to HOLB on the sender
side, which is the extra delay on new data that is held back when the
connection is CWND-limited. In the paper, we refer to this extra delay
as increased sojourn times for the outgoing data segments.
We do not include this additional sojourn time for the segments on the
sender side in the ACK Latency plots (Fig. 4 in the paper). This is
simply because the pcap traces contain the timestamps when the packets
are sent, and not when the segments are added to the output queue.
When we refer to the HOLB effect in the paper as well as the thesis, we
refer to the extra delays (sojourn times) on the receiver side where
segments are held back (not made available to user space) due to gaps in
the sequence range when packets are lost (we had no reordering).
So, when considering the increased delays due to HOLB on the receiver
side, HOLB is not at all limited to RTOs. Actually, it's mostly not due
to RTOs in the tests we've run, however, this also depends very much on
the transmission pattern of the application as well as loss levels.
In general, HOLB on the receiver side will affect any flow that
transmits a packet with new data after a packet is lost (sender may not
know yet), where the lost packet has not already been retransmitted.
Consider a sender application that performs write calls every 30 ms on a
150 ms RTT link. It will need a CWND that allows 5-6 PIFs to be able to
transmit all new data segments with no extra sojourn times on the sender
side.
When one packet is lost, the next 5 packets that are sent will be held
back on the receiver side due to the missing segment (HOLB). In the best
case scenario, the first dupACK triggers a fast retransmit around the
same time as the fifth packet (after the lost packet) is sent. In that
case, the first segment sent after the lost segment is held back on the
receiver for 150 ms (the time it takes for the dupACK to reach the
sender, and the fast retrans to arrive at the receiver). The second is
held back 120 ms, the third 90 ms, the fourth 60 ms, an the fifth 30 ms.
All of this extra delay is added before the sender even knows there was
a loss. How it decides to react to the loss signal (dupACKs) will
further decide how much extra delays will be added in addition to the
delays already inflicted on the segments by the HOLB.
Instead what if we only perform RDB on the (first and recurring) RTO
retransmission?
That will change RDB from being a proactive mechanism, to being
reactive, i.e. change how the sender responds to the loss signal. The
problem is that by this point (when the sender has received the loss
signal), the HOLB on the receiver side has already caused significant
increases to the application layer latency.
The reason the RDB streams (in red) in fig. 4 in the paper get such low
latencies is because there are almost no retransmissions. With 10%
uniform loss, the latency for 90% of the packets is not affected at all.
The latency for most of the lost segments is only increased by 30 ms,
which is when the next RDB packet arrives at the receiver with the lost
segment bundled in the payload.
For the regular TCP streams (blue), the latency for 40% of the segments
is affected, where almost 30% of the segments have additional delays of
150 ms or more.
It is important to note that the increases to the latencies for the
regular TCP streams compared to the RDB streams are solely due to HOLB
on the receiver side.
The longer the RTT, the greater the gains are by using RDB, considering
the best case scenario of minimum one RTT required for a retransmission.
As such, RDB will reduce the latencies the most for those that also need
it the most.
However, even with an RTT of 20 ms, an application writing a data
segment every 10 ms will still get significant latency reductions simply
because a retransmission will require a minimum of 20 ms, compared to
the 10 ms it takes for the next RDB packet to arrive at the receiver.
Bendik
From: Eric Dumazet <hidden> Date: 2016-03-14 21:15:43
On Thu, 2016-03-03 at 19:06 +0100, Bendik Rønning Opstad wrote:
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games,
remote control systems, and VoIP, produce traffic with thin-stream
characteristics, characterized by small packets and relatively high
inter-transmission times (ITT). When experiencing packet loss, such
latency-sensitive applications are heavily penalized by the need to
retransmit lost packets, which increases the latency by a minimum of
one RTT for the lost packet. Packets coming after a lost packet are
held back due to head-of-line blocking, causing increased delays for
all data segments until the lost packet has been retransmitted.
Acked-by: Eric Dumazet <edumazet@google.com>
Note that RDB probably should get some SNMP counters,
so that we get an idea of how many times a loss could be repaired.
Ideally, if the path happens to be lossless, all these pro active
bundles are overhead. Might be useful to make RDB conditional to
tp->total_retrans or something.
On Thu, Mar 3, 2016 at 10:06 AM, Bendik Rønning Opstad
[off-list ref] wrote:
quoted hunk
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games,
remote control systems, and VoIP, produce traffic with thin-stream
characteristics, characterized by small packets and relatively high
inter-transmission times (ITT). When experiencing packet loss, such
latency-sensitive applications are heavily penalized by the need to
retransmit lost packets, which increases the latency by a minimum of
one RTT for the lost packet. Packets coming after a lost packet are
held back due to head-of-line blocking, causing increased delays for
all data segments until the lost packet has been retransmitted.
RDB enables a TCP sender to bundle redundant (already sent) data with
TCP packets containing small segments of new data. By resending
un-ACKed data from the output queue in packets with new data, RDB
reduces the need to retransmit data segments on connections
experiencing sporadic packet loss. By avoiding a retransmit, RDB
evades the latency increase of at least one RTT for the lost packet,
as well as alleviating head-of-line blocking for the packets following
the lost packet. This makes the TCP connection more resistant to
latency fluctuations, and reduces the application layer latency
significantly in lossy environments.
Main functionality added:
o When a packet is scheduled for transmission, RDB builds and
transmits a new SKB containing both the unsent data as well as
data of previously sent packets from the TCP output queue.
o RDB will only be used for streams classified as thin by the
function tcp_stream_is_thin_dpifl(). This enforces a lower bound
on the ITT for streams that may benefit from RDB, controlled by
the sysctl variable net.ipv4.tcp_thin_dpifl_itt_lower_bound.
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
RDB can be enabled on a connection with the socket option TCP_RDB, or
on all new connections by setting the sysctl variable
net.ipv4.tcp_rdb=1
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 15 +++
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 3 +-
include/net/tcp.h | 15 +++
include/uapi/linux/tcp.h | 1 +
net/core/skbuff.c | 2 +-
net/ipv4/Makefile | 3 +-
net/ipv4/sysctl_net_ipv4.c | 25 ++++
net/ipv4/tcp.c | 14 +-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_output.c | 48 ++++---
net/ipv4/tcp_rdb.c | 228 +++++++++++++++++++++++++++++++++
12 files changed, 335 insertions(+), 23 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.
Please describe RDB briefly, perhaps with a pointer to your paper.
I suggest have three level of controls:
0: disable RDB completely
1: enable indiv. thin-stream conn. to use RDB via TCP_RDB socket
options
2: enable RDB on all thin-stream conn. by default
currently it only provides mode 1 and 2. but there may be cases where
the administrator wants to disallow it (e.g., broken middle-boxes).
+ Default: 0
+
+tcp_rdb_max_bytes - INTEGER
+ Enable restriction on how many bytes an RDB packet can contain.
+ This is the total amount of payload including the new unsent data.
+ Default: 0
+
+tcp_rdb_max_packets - INTEGER
+ Enable restriction on how many previous packets in the output queue
+ RDB may include data from. A value of 1 will restrict bundling to
+ only the data from the last packet that was sent.
+ Default: 1
why two metrics on redundancy? It also seems better to
allow individual socket to select the redundancy level (e.g.,
setsockopt TCP_RDB=3 means <=3 pkts per bundle) vs a global setting.
This requires more bits in tcp_sock but 2-3 more is suffice.
/
quoted hunk
+
tcp_limit_output_bytes - INTEGER
Controls TCP Small Queue limit per tcp socket.
TCP bulk sender tends to increase packets in flight until it
@@ -763,6 +774,7 @@ struct tcp_skb_cb {union{struct{/* There is space for up to 20 bytes */+__u32rdb_start_seq;/* Start seq of rdb data */}tx;/* only used for outgoing skbs */union{structinet_skb_parmh4;
@@ -115,6 +115,7 @@ enum {#define TCP_CC_INFO 26 /* Get Congestion Control (optional) info */#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */+#define TCP_RDB 29 /* Enable Redundant Data Bundling mechanism */structtcp_repair_opt{__u32opt_code;
@@ -3537,6 +3537,9 @@ static inline void tcp_in_ack_event(struct sock *sk, u32 flags)if(icsk->icsk_ca_ops->in_ack_event)icsk->icsk_ca_ops->in_ack_event(sk,flags);++if(unlikely(tcp_sk(sk)->rdb))+tcp_rdb_ack_event(sk,flags);}/* Congestion control has updated the cwnd already. So if we're in
@@ -2110,9 +2110,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,break;}-if(unlikely(tcp_transmit_skb(sk,skb,1,gfp)))+if(unlikely(tcp_sk(sk)->rdb)){+if(tcp_transmit_rdb_skb(sk,skb,mss_now,gfp))+break;+}elseif(unlikely(tcp_transmit_skb(sk,skb,1,gfp))){break;-+}repair:/* Advance the send_head. This one is sent out.*Thiscallwillincrementpackets_out.
@@ -2439,15 +2442,32 @@ u32 __tcp_select_window(struct sock *sk)returnwindow;}+/**+*tcp_skb_append_data()-copythelineardatafromanSKBtotheend+*ofanotherandupdateendsequencenumber+*andchecksum+*@from_skb:theSKBtocopydatafrom+*@to_skb:theSKBtocopydatato+*/+voidtcp_skb_append_data(structsk_buff*from_skb,structsk_buff*to_skb)+{+skb_copy_from_linear_data(from_skb,skb_put(to_skb,from_skb->len),+from_skb->len);+TCP_SKB_CB(to_skb)->end_seq=TCP_SKB_CB(from_skb)->end_seq;++if(from_skb->ip_summed==CHECKSUM_PARTIAL)+to_skb->ip_summed=CHECKSUM_PARTIAL;++if(to_skb->ip_summed!=CHECKSUM_PARTIAL)+to_skb->csum=csum_block_add(to_skb->csum,from_skb->csum,+to_skb->len);+}+/* Collapses two adjacent SKB's during retransmission. */staticvoidtcp_collapse_retrans(structsock*sk,structsk_buff*skb){structtcp_sock*tp=tcp_sk(sk);structsk_buff*next_skb=tcp_write_queue_next(sk,skb);-intskb_size,next_skb_size;--skb_size=skb->len;-next_skb_size=next_skb->len;BUG_ON(tcp_skb_pcount(skb)!=1||tcp_skb_pcount(next_skb)!=1);
@@ -2455,17 +2475,7 @@ static void tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)tcp_unlink_write_queue(next_skb,sk);-skb_copy_from_linear_data(next_skb,skb_put(skb,next_skb_size),-next_skb_size);--if(next_skb->ip_summed==CHECKSUM_PARTIAL)-skb->ip_summed=CHECKSUM_PARTIAL;--if(skb->ip_summed!=CHECKSUM_PARTIAL)-skb->csum=csum_block_add(skb->csum,next_skb->csum,skb_size);--/* Update sequence range on original skb. */-TCP_SKB_CB(skb)->end_seq=TCP_SKB_CB(next_skb)->end_seq;+tcp_skb_append_data(next_skb,skb);/* Merge over control information. This moves PSH/FIN etc. over */TCP_SKB_CB(skb)->tcp_flags|=TCP_SKB_CB(next_skb)->tcp_flags;
@@ -0,0 +1,228 @@+#include<linux/skbuff.h>+#include<net/tcp.h>++intsysctl_tcp_rdb_max_bytes__read_mostly;+intsysctl_tcp_rdb_max_packets__read_mostly=1;++/**+*rdb_detect_loss()-performRDBlossdetectionbyanalysingACKs+*@sk:socket+*+*TraversetheoutputqueueandcheckiftheACKedpacketisanRDB+*packetandiftheredundantdatacoversoneormoreun-ACKedSKBs.+*IftheincomingACKacknowledgesmultipleSKBs,wecanpresume+*packetlosshasoccurred.+*+*WecaninferpacketlossthiswaybecausewecanexpectoneACKper+*transmitteddatapacket,asdelayedACKsaredisabledwhenahost+*receivespacketswherethesequencenumberisnottheexpected+*sequencenumber.+*+*Return:Thenumberofpacketsthatarepresumedtobelost+*/+staticunsignedintrdb_detect_loss(structsock*sk)+{+structsk_buff*skb,*tmp;+structtcp_skb_cb*scb;+u32seq_acked=tcp_sk(sk)->snd_una;+unsignedintpackets_lost=0;++tcp_for_write_queue(skb,sk){+if(skb==tcp_send_head(sk))+break;++scb=TCP_SKB_CB(skb);+/* The ACK acknowledges parts of the data in this SKB.+*Canbecausedby:+*-TSO:WeabortasRDBisnotusedonSKBssplitacross+*multiplepacketsonlowerlayersasthesearegreater+*thanoneMSS.+*-Retranscollapse:We'vehadaretrans,solosshasalready+*beendetected.+*/+if(after(scb->end_seq,seq_acked))+break;+elseif(scb->end_seq!=seq_acked)+continue;++/* We have found the ACKed packet */++/* This packet was sent with no redundant data, or no prior+*un-ACKedSKBsisintheoutputqueue,sobreakhere.+*/+if(scb->tx.rdb_start_seq==scb->seq||+skb_queue_is_first(&sk->sk_write_queue,skb))+break;+/* Find number of prior SKBs whose data was bundled in this+*(ACKed)SKB.Wepresumeanyredundantdatacoveringprevious+*SKB'sareduetoloss.(Anexceptionwouldbereordering).+*/+skb=skb->prev;+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+if(before(TCP_SKB_CB(skb)->seq,scb->tx.rdb_start_seq))+break;+packets_lost++;
since we only care if there is packet loss or not, we can return early here?
+{
+ if (rdb_detect_loss(sk))
+ tcp_enter_cwr(sk);
+}
+
+/**
+ * rdb_build_skb() - build a new RDB SKB and copy redundant + unsent
+ * data to the linear page buffer
+ * @sk: socket
+ * @xmit_skb: the SKB processed for transmission in the output engine
+ * @first_skb: the first SKB in the output queue to be bundled
+ * @bytes_in_rdb_skb: the total number of data bytes for the new
+ * rdb_skb (NEW + Redundant)
+ * @gfp_mask: gfp_t allocation
+ *
+ * Return: A new SKB containing redundant data, or NULL if memory
+ * allocation failed
+ */
+static struct sk_buff *rdb_build_skb(const struct sock *sk,
+ struct sk_buff *xmit_skb,
+ struct sk_buff *first_skb,
+ u32 bytes_in_rdb_skb,
+ gfp_t gfp_mask)
+{
+ struct sk_buff *rdb_skb, *tmp_skb = first_skb;
+
+ rdb_skb = sk_stream_alloc_skb((struct sock *)sk,
+ (int)bytes_in_rdb_skb,
+ gfp_mask, false);
+ if (!rdb_skb)
+ return NULL;
+ copy_skb_header(rdb_skb, xmit_skb);
+ rdb_skb->ip_summed = xmit_skb->ip_summed;
+ TCP_SKB_CB(rdb_skb)->seq = TCP_SKB_CB(first_skb)->seq;
+ TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq = TCP_SKB_CB(rdb_skb)->seq;
+
+ /* Start on first_skb and append payload from each SKB in the output
+ * queue onto rdb_skb until we reach xmit_skb.
+ */
+ tcp_for_write_queue_from(tmp_skb, sk) {
+ tcp_skb_append_data(tmp_skb, rdb_skb);
+
+ /* We reached xmit_skb, containing the unsent data */
+ if (tmp_skb == xmit_skb)
+ break;
+ }
+ return rdb_skb;
+}
+
+/**
+ * rdb_can_bundle_test() - test if redundant data can be bundled
+ * @sk: socket
+ * @xmit_skb: the SKB processed for transmission by the output engine
+ * @max_payload: the maximum allowed payload bytes for the RDB SKB
+ * @bytes_in_rdb_skb: store the total number of payload bytes in the
+ * RDB SKB if bundling can be performed
+ *
+ * Traverse the output queue and check if any un-acked data may be
+ * bundled.
+ *
+ * Return: The first SKB to be in the bundle, or NULL if no bundling
+ */
+static struct sk_buff *rdb_can_bundle_test(const struct sock *sk,
+ struct sk_buff *xmit_skb,
+ unsigned int max_payload,
+ u32 *bytes_in_rdb_skb)
+{
+ struct sk_buff *first_to_bundle = NULL;
+ struct sk_buff *tmp, *skb = xmit_skb->prev;
+ u32 skbs_in_bundle_count = 1; /* Start on 1 to account for xmit_skb */
+ u32 total_payload = xmit_skb->len;
+
+ if (sysctl_tcp_rdb_max_bytes)
+ max_payload = min_t(unsigned int, max_payload,
+ sysctl_tcp_rdb_max_bytes);
+
+ /* We start at xmit_skb->prev, and go backwards */
+ tcp_for_write_queue_reverse_from_safe(skb, tmp, sk) {
+ /* Including data from this SKB would exceed payload limit */
+ if ((total_payload + skb->len) > max_payload)
+ break;
+
+ if (sysctl_tcp_rdb_max_packets &&
+ (skbs_in_bundle_count > sysctl_tcp_rdb_max_packets))
+ break;
+
+ total_payload += skb->len;
+ skbs_in_bundle_count++;
+ first_to_bundle = skb;
+ }
+ *bytes_in_rdb_skb = total_payload;
+ return first_to_bundle;
+}
+
+/**
+ * tcp_transmit_rdb_skb() - try to create and send an RDB packet
+ * @sk: socket
+ * @xmit_skb: the SKB processed for transmission by the output engine
+ * @mss_now: current mss value
+ * @gfp_mask: gfp_t allocation
+ *
+ * If an RDB packet could not be created and sent, transmit the
+ * original unmodified SKB (xmit_skb).
+ *
+ * Return: 0 if successfully sent packet, else error from
+ * tcp_transmit_skb
+ */
+int tcp_transmit_rdb_skb(struct sock *sk, struct sk_buff *xmit_skb,
+ unsigned int mss_now, gfp_t gfp_mask)
+{
+ struct sk_buff *rdb_skb = NULL;
+ struct sk_buff *first_to_bundle;
+ u32 bytes_in_rdb_skb = 0;
+
+ /* How we detect that RDB was used. When equal, no RDB data was sent */
+ TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq = TCP_SKB_CB(xmit_skb)->seq;
+
+ if (!tcp_stream_is_thin_dpifl(tcp_sk(sk)))
During loss recovery tcp inflight fluctuates and would like to trigger
this check even for non-thin-stream connections. Since the loss
already occurs, RDB can only take advantage from limited-transmit,
which it likely does not have (b/c its a thin-stream). It might be
checking if the state is open.
+ goto xmit_default;
+
+ /* No bundling if first in queue, or on FIN packet */
+ if (skb_queue_is_first(&sk->sk_write_queue, xmit_skb) ||
+ (TCP_SKB_CB(xmit_skb)->tcp_flags & TCPHDR_FIN))
seems there are still benefit to bundle packets up to FIN?
+ goto xmit_default;
+
+ /* Find number of (previous) SKBs to get data from */
+ first_to_bundle = rdb_can_bundle_test(sk, xmit_skb, mss_now,
+ &bytes_in_rdb_skb);
+ if (!first_to_bundle)
+ goto xmit_default;
+
+ /* Create an SKB that contains redundant data starting from
+ * first_to_bundle.
+ */
+ rdb_skb = rdb_build_skb(sk, xmit_skb, first_to_bundle,
+ bytes_in_rdb_skb, gfp_mask);
+ if (!rdb_skb)
+ goto xmit_default;
+
+ /* Set skb_mstamp for the SKB in the output queue (xmit_skb) containing
+ * the yet unsent data. Normally this would be done by
+ * tcp_transmit_skb(), but as we pass in rdb_skb instead, xmit_skb's
+ * timestamp will not be touched.
+ */
+ skb_mstamp_get(&xmit_skb->skb_mstamp);
+ rdb_skb->skb_mstamp = xmit_skb->skb_mstamp;
+ return tcp_transmit_skb(sk, rdb_skb, 0, gfp_mask);
+
+xmit_default:
+ /* Transmit the unmodified SKB from output queue */
+ return tcp_transmit_skb(sk, xmit_skb, 1, gfp_mask);
+}
--
1.9.1
since RDB will cause DSACKs, and we only blindly count DSACKs to
perform CWND undo. How does RDB handle that false positives?
On Sun, Mar 13, 2016 at 4:18 PM, Bendik Rønning Opstad
[off-list ref] wrote:
On 03/10/2016 01:20 AM, Yuchung Cheng wrote:
quoted
I read the paper. I think the underlying idea is neat. but the
implementation is little heavy-weight that requires changes on fast
path (tcp_write_xmit) and space in skb control blocks.
Yuchung, thank you for taking the time to review the patch submission
and read the paper.
I must admit I was not particularly happy about the extra if-test on the
fast path, and I fully understand the wish to keep the fast path as
simple and clean as possible.
However, is the performance hit that significant considering the branch
prediction hint for the non-RDB path?
The extra variable needed in the SKB CB does not require increasing the
CB buffer size due to the "tcp: refactor struct tcp_skb_cb" patch:
http://patchwork.ozlabs.org/patch/510674 and uses only some of the space
made available in the outgoing SKBs' CB. Therefore I hoped the extra
variable would be acceptable.
quoted
ultimately this
patch is meant for a small set of specific applications.
Yes, the RDB mechanism is aimed at a limited set of applications,
specifically time-dependent applications that produce non-greedy,
application limited (thin) flows. However, our hope is that RDB may
greatly improve TCP's position as a viable alternative for applications
transmitting latency sensitive data.
quoted
In my mental model (please correct me if I am wrong), losses on these
thin streams would mostly resort to RTOs instead of fast recovery, due
to the bursty nature of Internet losses.
This depends on the transmission pattern of the applications, which
varies to a great deal, also between the different types of
time-dependent applications that produce thin streams. For short flows,
(bursty) loss at the end will result in an RTO (if TLP does not probe),
but the thin streams are often long lived, and the applications
producing them continue to write small data segments to the socket at
intervals of tens to hundreds of milliseconds.
What controls if an RTO and not fast retransmit will resend the packet,
is the number of PIFs, which directly correlates to how often the
application writes data to the socket in relation to the RTT. As long as
the number of packets successfully completing a round trip before the
RTO is >= the dupACK threshold, they will not depend on RTOs (not
considering TLP). Early retransmit and the TCP_THIN_DUPACK socket option
will also affect the likelihood of RTOs vs fast retransmits.
quoted
The HOLB comes from RTO only
retransmit the first (tiny) unacked packet while a small of new data is
readily available. But since Linux congestion control is packet-based,
and loss cwnd is 1, the new data needs to wait until the 1st packet is
acked which is for another RTT.
If I understand you correctly, you are referring to HOLB on the sender
side, which is the extra delay on new data that is held back when the
connection is CWND-limited. In the paper, we refer to this extra delay
as increased sojourn times for the outgoing data segments.
We do not include this additional sojourn time for the segments on the
sender side in the ACK Latency plots (Fig. 4 in the paper). This is
simply because the pcap traces contain the timestamps when the packets
are sent, and not when the segments are added to the output queue.
When we refer to the HOLB effect in the paper as well as the thesis, we
refer to the extra delays (sojourn times) on the receiver side where
segments are held back (not made available to user space) due to gaps in
the sequence range when packets are lost (we had no reordering).
So, when considering the increased delays due to HOLB on the receiver
side, HOLB is not at all limited to RTOs. Actually, it's mostly not due
to RTOs in the tests we've run, however, this also depends very much on
the transmission pattern of the application as well as loss levels.
In general, HOLB on the receiver side will affect any flow that
transmits a packet with new data after a packet is lost (sender may not
know yet), where the lost packet has not already been retransmitted.
OK that makes sense.
I left some detailed comments on the actual patches. I would encourage
to submit an IETF draft to gather feedback from tcpm b/c the feature
seems portable.
Consider a sender application that performs write calls every 30 ms on a
150 ms RTT link. It will need a CWND that allows 5-6 PIFs to be able to
transmit all new data segments with no extra sojourn times on the sender
side.
When one packet is lost, the next 5 packets that are sent will be held
back on the receiver side due to the missing segment (HOLB). In the best
case scenario, the first dupACK triggers a fast retransmit around the
same time as the fifth packet (after the lost packet) is sent. In that
case, the first segment sent after the lost segment is held back on the
receiver for 150 ms (the time it takes for the dupACK to reach the
sender, and the fast retrans to arrive at the receiver). The second is
held back 120 ms, the third 90 ms, the fourth 60 ms, an the fifth 30 ms.
All of this extra delay is added before the sender even knows there was
a loss. How it decides to react to the loss signal (dupACKs) will
further decide how much extra delays will be added in addition to the
delays already inflicted on the segments by the HOLB.
quoted
Instead what if we only perform RDB on the (first and recurring) RTO
retransmission?
That will change RDB from being a proactive mechanism, to being
reactive, i.e. change how the sender responds to the loss signal. The
problem is that by this point (when the sender has received the loss
signal), the HOLB on the receiver side has already caused significant
increases to the application layer latency.
The reason the RDB streams (in red) in fig. 4 in the paper get such low
latencies is because there are almost no retransmissions. With 10%
uniform loss, the latency for 90% of the packets is not affected at all.
The latency for most of the lost segments is only increased by 30 ms,
which is when the next RDB packet arrives at the receiver with the lost
segment bundled in the payload.
For the regular TCP streams (blue), the latency for 40% of the segments
is affected, where almost 30% of the segments have additional delays of
150 ms or more.
It is important to note that the increases to the latencies for the
regular TCP streams compared to the RDB streams are solely due to HOLB
on the receiver side.
The longer the RTT, the greater the gains are by using RDB, considering
the best case scenario of minimum one RTT required for a retransmission.
As such, RDB will reduce the latencies the most for those that also need
it the most.
However, even with an RTT of 20 ms, an application writing a data
segment every 10 ms will still get significant latency reductions simply
because a retransmission will require a minimum of 20 ms, compared to
the 10 ms it takes for the next RDB packet to arrive at the receiver.
Bendik
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.
Please describe RDB briefly, perhaps with a pointer to your paper.
I suggest have three level of controls:
0: disable RDB completely
1: enable indiv. thin-stream conn. to use RDB via TCP_RDB socket
options
2: enable RDB on all thin-stream conn. by default
currently it only provides mode 1 and 2. but there may be cases where
the administrator wants to disallow it (e.g., broken middle-boxes).
quoted
+ Default: 0
A per route setting to enable or disable tcp_rdb, overriding
the global setting, could also be useful to the administrator.
Just a suggestion for potential followup work.
-Bill
From: Rick Jones <hidden> Date: 2016-03-15 01:04:16
On 03/14/2016 02:15 PM, Eric Dumazet wrote:
On Thu, 2016-03-03 at 19:06 +0100, Bendik Rønning Opstad wrote:
quoted
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games,
remote control systems, and VoIP, produce traffic with thin-stream
characteristics, characterized by small packets and relatively high
inter-transmission times (ITT). When experiencing packet loss, such
latency-sensitive applications are heavily penalized by the need to
retransmit lost packets, which increases the latency by a minimum of
one RTT for the lost packet. Packets coming after a lost packet are
held back due to head-of-line blocking, causing increased delays for
all data segments until the lost packet has been retransmitted.
Acked-by: Eric Dumazet <edumazet@google.com>
Note that RDB probably should get some SNMP counters,
so that we get an idea of how many times a loss could be repaired.
And some idea of the duplication seen by receivers, assuming there isn't
already a counter for such a thing in Linux.
happy benchmarking,
rick jones
Ideally, if the path happens to be lossless, all these pro active
bundles are overhead. Might be useful to make RDB conditional to
tp->total_retrans or something.
On Mon, Mar 14, 2016 at 6:04 PM, Rick Jones [off-list ref] wrote:
On 03/14/2016 02:15 PM, Eric Dumazet wrote:
quoted
On Thu, 2016-03-03 at 19:06 +0100, Bendik Rønning Opstad wrote:
quoted
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games,
remote control systems, and VoIP, produce traffic with thin-stream
characteristics, characterized by small packets and relatively high
inter-transmission times (ITT). When experiencing packet loss, such
latency-sensitive applications are heavily penalized by the need to
retransmit lost packets, which increases the latency by a minimum of
one RTT for the lost packet. Packets coming after a lost packet are
held back due to head-of-line blocking, causing increased delays for
all data segments until the lost packet has been retransmitted.
Acked-by: Eric Dumazet <edumazet@google.com>
Note that RDB probably should get some SNMP counters,
so that we get an idea of how many times a loss could be repaired.
And some idea of the duplication seen by receivers, assuming there isn't already a counter for such a thing in Linux.
We sort of track that in the awkwardly named LINUX_MIB_DELAYEDACKLOST
happy benchmarking,
rick jones
quoted
Ideally, if the path happens to be lossless, all these pro active
bundles are overhead. Might be useful to make RDB conditional to
tp->total_retrans or something.
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.
Please describe RDB briefly, perhaps with a pointer to your paper.
Ah, yes, that description may have been a bit too brief...
What about pointing to tcp-thin.txt in the brief description, and
rewrite tcp-thin.txt with a more detailed description of RDB along
with a paper reference?
I suggest have three level of controls:
0: disable RDB completely
1: enable indiv. thin-stream conn. to use RDB via TCP_RDB socket
options
2: enable RDB on all thin-stream conn. by default
currently it only provides mode 1 and 2. but there may be cases where
the administrator wants to disallow it (e.g., broken middle-boxes).
Good idea. Will change this.
quoted
+ Default: 0
+
+tcp_rdb_max_bytes - INTEGER
+ Enable restriction on how many bytes an RDB packet can contain.
+ This is the total amount of payload including the new unsent data.
+ Default: 0
+
+tcp_rdb_max_packets - INTEGER
+ Enable restriction on how many previous packets in the output queue
+ RDB may include data from. A value of 1 will restrict bundling to
+ only the data from the last packet that was sent.
+ Default: 1
why two metrics on redundancy?
We have primarily used the packet based limit in our tests. This is
also the most important knob as it directly controls how many lost
packets each RDB packet may recover.
We believe that the byte based limit can also be useful because it
allows more fine grained control on how much impact RDB can have on
the increased bandwidth requirements of the flows. If an application
writes 700 bytes per write call, the bandwidth increase can be quite
significant (even with a 1 packet bundling limit) if we consider a
scenario with thousands of RDB streams.
In some of our experiments with many simultaneous thin streams, where
we set up a bottleneck rate limited by a htb with pfifo queue, we
observed considerable difference in loss rates depending on how many
bytes (packets) were allowed to be bundled with each packet. This is
partly why we recommend a default bundling limit of 1 packet.
By limiting the total payload size of RDB packets to e.g. 100 bytes,
only the smallest segments will benefit from RDB, while the segments
that would increase the bandwidth requirements the most, will not.
While a very large number of RDB streams from one sender may be a
corner case, we still think this sysctl knob can be valuable for a
sysadmin that finds himself in such a situation.
It also seems better to
allow individual socket to select the redundancy level (e.g.,
setsockopt TCP_RDB=3 means <=3 pkts per bundle) vs a global setting.
This requires more bits in tcp_sock but 2-3 more is suffice.
Most certainly. We decided not to implement this for the patch to keep
it as simple as possible, however, we surely prefer to have this
functionality included if possible.
quoted
+static unsigned int rdb_detect_loss(struct sock *sk)
+{
since we only care if there is packet loss or not, we can return early here?
Yes, I considered that, and as long as the number of packets presumed
to be lost is not needed, that will suffice. However, could this not
be useful for statistical purposes?
This is also relevant to the comment from Eric on SNMP counters for
how many times losses could be repaired by RDB?
+int tcp_transmit_rdb_skb(struct sock *sk, struct sk_buff *xmit_skb,
+ unsigned int mss_now, gfp_t gfp_mask)
+{
+ struct sk_buff *rdb_skb = NULL;
+ struct sk_buff *first_to_bundle;
+ u32 bytes_in_rdb_skb = 0;
+
+ /* How we detect that RDB was used. When equal, no RDB data was sent */
+ TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq = TCP_SKB_CB(xmit_skb)->seq;
quoted
+
+ if (!tcp_stream_is_thin_dpifl(tcp_sk(sk)))
During loss recovery tcp inflight fluctuates and would like to trigger
this check even for non-thin-stream connections.
Good point.
Since the loss
already occurs, RDB can only take advantage from limited-transmit,
which it likely does not have (b/c its a thin-stream). It might be
checking if the state is open.
You mean to test for open state to avoid calling rdb_can_bundle_test()
unnecessarily if we (presume to) know it cannot bundle anyway? That
makes sense, however, I would like to do some tests on whether "state
!= open" is a good indicator on when bundling is not possible.
quoted
+ goto xmit_default;
+
+ /* No bundling if first in queue, or on FIN packet */
+ if (skb_queue_is_first(&sk->sk_write_queue, xmit_skb) ||
+ (TCP_SKB_CB(xmit_skb)->tcp_flags & TCPHDR_FIN))
seems there are still benefit to bundle packets up to FIN?
I was close to removing the FIN test, but decided to not remove it
until I could verify that it will not cause any issues on some TCP
receivers. If/(Since?) you are certain it will not cause any issues, I
will remove it.
since RDB will cause DSACKs, and we only blindly count DSACKs to
perform CWND undo. How does RDB handle that false positives?
That is a very good question. The simple answer is that the
implementation does not handle any such false positives, which I
expect can result in incorrectly undoing CWND reduction in some cases.
This gets a bit complicated, so I'll have to do some more testing on
this to verify with certainty when it happens.
When there is no loss, and each RDB packet arriving at the receiver
contains both already received and new data, the receiver will respond
with an ACK that acknowledges new data (moves snd_una), with the SACK
field populated with the already received sequence range (DSACK).
The DSACKs in these incoming ACKs are not counted (tp->undo_retrans--)
unless tp->undo_marker has been set by tcp_init_undo(), which is
called by either tcp_enter_loss() or tcp_enter_recovery(). However,
whenever a loss is detected by rdb_detect_loss(), tcp_enter_cwr() is
called, which disables CWND undo. Therefore, I believe the incorrect
counting of DSACKs from ACKs on RDB packets will only be a problem
after the regular loss detection mechanisms (Fast Retransmit/RTO) have
been triggered (i.e. we are in either TCP_CA_Recovery or TCP_CA_Loss).
We have recorded the CWND values for both RDB and non-RDB streams in
our experiments, and have not found any obvious red flags when
analysing the results, so I presume (hope may be more precise) this is
not a major issue we have missed. Nevertheless, I will investigate
this in detail and get back to you.
Thank you for the detailed comments.
Bendik
OK that makes sense.
I left some detailed comments on the actual patches. I would encourage
to submit an IETF draft to gather feedback from tcpm b/c the feature
seems portable.
Thank you for the suggestion, we appreciate the confidence. We have
had in mind to eventually pursue a standardization process, but have
been unsure about how a mechanism that actively introduces redundancy
would be received by the IETF. It may now be the right time to propose
the RDB mechanism, and we will aim to present an IEFT draft in the
near future.
Bendik
Acked-by: Eric Dumazet <edumazet@google.com>
Note that RDB probably should get some SNMP counters,
so that we get an idea of how many times a loss could be repaired.
Good idea. Simply count how many times an RDB packet successfully
repaired loss? Note that this can be one or more lost packets. When
bundling N packets, the RDB packet can repair up to N losses in the
previous N packets that were sent.
Which list should this be added to? snmp4_tcp_list?
Any other counters that would be useful? Total number of RDB packets
transmitted?
Ideally, if the path happens to be lossless, all these pro active
bundles are overhead. Might be useful to make RDB conditional to
tp->total_retrans or something.
Yes, that is a good point. We have discussed this (for years really),
but have not had the opportunity to investigate it in-depth. Having
such a condition hard coded is not ideal, as it very much depends on
the use case if bundling from the beginning is desirable. In most
cases, this is probably a fair compromise, but preferably we would
have some logic/settings to control how the bundling rate can be
dynamically adjusted in response to certain events, defined by a set
of given metrics.
A conservative (default) setting would not do bundling until loss has
been registered, and could also check against some smoothed loss
indicator such that a certain amount of loss must have occurred within
a specific time frame to allow bundling. This could be useful in cases
where the network congestion varies greatly depending on such as the
time of day/night.
In a scenario where minimal application layer latency is very
important, but only sporadic (single) packet loss is expected to
regularly occur, always bundling one previous packet may be both
sufficient and desirable.
In the end, the best settings for an application/service depends on
the degree to which application layer latency (both minimal and
variations) affects the QoE.
There are many possibilities to consider in this regard, and I expect
we will not have this question fully explored any time soon. Most
importantly, we should ensure that such logic can easily be added
later on without breaking backwards compatibility.
Suggestions and comments on this are very welcome.
Bendik
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.
Please describe RDB briefly, perhaps with a pointer to your paper.
Ah, yes, that description may have been a bit too brief...
What about pointing to tcp-thin.txt in the brief description, and
rewrite tcp-thin.txt with a more detailed description of RDB along
with a paper reference?
+1
quoted
I suggest have three level of controls:
0: disable RDB completely
1: enable indiv. thin-stream conn. to use RDB via TCP_RDB socket
options
2: enable RDB on all thin-stream conn. by default
currently it only provides mode 1 and 2. but there may be cases where
the administrator wants to disallow it (e.g., broken middle-boxes).
Good idea. Will change this.
quoted
quoted
+ Default: 0
+
+tcp_rdb_max_bytes - INTEGER
+ Enable restriction on how many bytes an RDB packet can contain.
+ This is the total amount of payload including the new unsent data.
+ Default: 0
+
+tcp_rdb_max_packets - INTEGER
+ Enable restriction on how many previous packets in the output queue
+ RDB may include data from. A value of 1 will restrict bundling to
+ only the data from the last packet that was sent.
+ Default: 1
why two metrics on redundancy?
We have primarily used the packet based limit in our tests. This is
also the most important knob as it directly controls how many lost
packets each RDB packet may recover.
We believe that the byte based limit can also be useful because it
allows more fine grained control on how much impact RDB can have on
the increased bandwidth requirements of the flows. If an application
writes 700 bytes per write call, the bandwidth increase can be quite
significant (even with a 1 packet bundling limit) if we consider a
scenario with thousands of RDB streams.
In some of our experiments with many simultaneous thin streams, where
we set up a bottleneck rate limited by a htb with pfifo queue, we
observed considerable difference in loss rates depending on how many
bytes (packets) were allowed to be bundled with each packet. This is
partly why we recommend a default bundling limit of 1 packet.
By limiting the total payload size of RDB packets to e.g. 100 bytes,
only the smallest segments will benefit from RDB, while the segments
that would increase the bandwidth requirements the most, will not.
While a very large number of RDB streams from one sender may be a
corner case, we still think this sysctl knob can be valuable for a
sysadmin that finds himself in such a situation.
These nice comments would be useful in the sysctl descriptions.
quoted
It also seems better to
allow individual socket to select the redundancy level (e.g.,
setsockopt TCP_RDB=3 means <=3 pkts per bundle) vs a global setting.
This requires more bits in tcp_sock but 2-3 more is suffice.
Most certainly. We decided not to implement this for the patch to keep
it as simple as possible, however, we surely prefer to have this
functionality included if possible.
quoted
quoted
+static unsigned int rdb_detect_loss(struct sock *sk)
+{
since we only care if there is packet loss or not, we can return early here?
Yes, I considered that, and as long as the number of packets presumed
to be lost is not needed, that will suffice. However, could this not
be useful for statistical purposes?
This is also relevant to the comment from Eric on SNMP counters for
how many times losses could be repaired by RDB?
+int tcp_transmit_rdb_skb(struct sock *sk, struct sk_buff *xmit_skb,
+ unsigned int mss_now, gfp_t gfp_mask)
+{
+ struct sk_buff *rdb_skb = NULL;
+ struct sk_buff *first_to_bundle;
+ u32 bytes_in_rdb_skb = 0;
+
+ /* How we detect that RDB was used. When equal, no RDB data was sent */
+ TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq = TCP_SKB_CB(xmit_skb)->seq;
quoted
+
+ if (!tcp_stream_is_thin_dpifl(tcp_sk(sk)))
During loss recovery tcp inflight fluctuates and would like to trigger
this check even for non-thin-stream connections.
Good point.
quoted
Since the loss
already occurs, RDB can only take advantage from limited-transmit,
which it likely does not have (b/c its a thin-stream). It might be
checking if the state is open.
You mean to test for open state to avoid calling rdb_can_bundle_test()
unnecessarily if we (presume to) know it cannot bundle anyway? That
makes sense, however, I would like to do some tests on whether "state
!= open" is a good indicator on when bundling is not possible.
quoted
quoted
+ goto xmit_default;
+
+ /* No bundling if first in queue, or on FIN packet */
+ if (skb_queue_is_first(&sk->sk_write_queue, xmit_skb) ||
+ (TCP_SKB_CB(xmit_skb)->tcp_flags & TCPHDR_FIN))
seems there are still benefit to bundle packets up to FIN?
I was close to removing the FIN test, but decided to not remove it
until I could verify that it will not cause any issues on some TCP
receivers. If/(Since?) you are certain it will not cause any issues, I
will remove it.
quoted
since RDB will cause DSACKs, and we only blindly count DSACKs to
perform CWND undo. How does RDB handle that false positives?
That is a very good question. The simple answer is that the
implementation does not handle any such false positives, which I
expect can result in incorrectly undoing CWND reduction in some cases.
This gets a bit complicated, so I'll have to do some more testing on
this to verify with certainty when it happens.
When there is no loss, and each RDB packet arriving at the receiver
contains both already received and new data, the receiver will respond
with an ACK that acknowledges new data (moves snd_una), with the SACK
field populated with the already received sequence range (DSACK).
The DSACKs in these incoming ACKs are not counted (tp->undo_retrans--)
unless tp->undo_marker has been set by tcp_init_undo(), which is
called by either tcp_enter_loss() or tcp_enter_recovery(). However,
whenever a loss is detected by rdb_detect_loss(), tcp_enter_cwr() is
called, which disables CWND undo. Therefore, I believe the incorrect
thanks for the clarification. it might worth a short comment on why we
use tcp_enter_cwr() (to disable undo)
counting of DSACKs from ACKs on RDB packets will only be a problem
after the regular loss detection mechanisms (Fast Retransmit/RTO) have
been triggered (i.e. we are in either TCP_CA_Recovery or TCP_CA_Loss).
We have recorded the CWND values for both RDB and non-RDB streams in
our experiments, and have not found any obvious red flags when
analysing the results, so I presume (hope may be more precise) this is
not a major issue we have missed. Nevertheless, I will investigate
this in detail and get back to you.
Thank you for the detailed comments.
Bendik
@@ -716,6 +716,21 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Enable RDB for all new TCP connections.
Please describe RDB briefly, perhaps with a pointer to your paper.
Ah, yes, that description may have been a bit too brief...
What about pointing to tcp-thin.txt in the brief description, and
rewrite tcp-thin.txt with a more detailed description of RDB along
with a paper reference?
+1
quoted
quoted
I suggest have three level of controls:
0: disable RDB completely
1: enable indiv. thin-stream conn. to use RDB via TCP_RDB socket
options
2: enable RDB on all thin-stream conn. by default
currently it only provides mode 1 and 2. but there may be cases where
the administrator wants to disallow it (e.g., broken middle-boxes).
Good idea. Will change this.
I have implemented your suggestion in the next patch.
quoted
quoted
It also seems better to
allow individual socket to select the redundancy level (e.g.,
setsockopt TCP_RDB=3 means <=3 pkts per bundle) vs a global setting.
This requires more bits in tcp_sock but 2-3 more is suffice.
Most certainly. We decided not to implement this for the patch to keep
it as simple as possible, however, we surely prefer to have this
functionality included if possible.
Next patch version has a socket option to allow modifying the different
RDB settings.
quoted
quoted
quoted
+int tcp_transmit_rdb_skb(struct sock *sk, struct sk_buff *xmit_skb,
+ unsigned int mss_now, gfp_t gfp_mask)
+{
+ struct sk_buff *rdb_skb = NULL;
+ struct sk_buff *first_to_bundle;
+ u32 bytes_in_rdb_skb = 0;
+
+ /* How we detect that RDB was used. When equal, no RDB data was sent */
+ TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq = TCP_SKB_CB(xmit_skb)->seq;
quoted
+
+ if (!tcp_stream_is_thin_dpifl(tcp_sk(sk)))
During loss recovery tcp inflight fluctuates and would like to trigger
this check even for non-thin-stream connections.
Good point.
quoted
Since the loss
already occurs, RDB can only take advantage from limited-transmit,
which it likely does not have (b/c its a thin-stream). It might be
checking if the state is open.
You mean to test for open state to avoid calling rdb_can_bundle_test()
unnecessarily if we (presume to) know it cannot bundle anyway? That
makes sense, however, I would like to do some tests on whether "state
!= open" is a good indicator on when bundling is not possible.
When testing this I found that bundling can often be performed when
not in Open state. For the most part in CWR mode, but also the other
modes, so this does not seem like a good indicator.
The only problem with tcp_stream_is_thin_dpifl() triggering for
non-thin streams in loss recovery would be the performance penalty of
calling rdb_can_bundle_test(). It would not be able to bundle anyways
since the previous SKB would contain >= mss worth of data.
The most reliable test is to check available space in the previous
SKB, i.e. if (xmit_skb->prev->len == mss_now). Do you suggest, for
performance reasons, to do this before the call to
tcp_stream_is_thin_dpifl()?
quoted
quoted
since RDB will cause DSACKs, and we only blindly count DSACKs to
perform CWND undo. How does RDB handle that false positives?
That is a very good question. The simple answer is that the
implementation does not handle any such false positives, which I
expect can result in incorrectly undoing CWND reduction in some cases.
This gets a bit complicated, so I'll have to do some more testing on
this to verify with certainty when it happens.
When there is no loss, and each RDB packet arriving at the receiver
contains both already received and new data, the receiver will respond
with an ACK that acknowledges new data (moves snd_una), with the SACK
field populated with the already received sequence range (DSACK).
The DSACKs in these incoming ACKs are not counted (tp->undo_retrans--)
unless tp->undo_marker has been set by tcp_init_undo(), which is
called by either tcp_enter_loss() or tcp_enter_recovery(). However,
whenever a loss is detected by rdb_detect_loss(), tcp_enter_cwr() is
called, which disables CWND undo. Therefore, I believe the incorrect
thanks for the clarification. it might worth a short comment on why we
use tcp_enter_cwr() (to disable undo)
quoted
counting of DSACKs from ACKs on RDB packets will only be a problem
after the regular loss detection mechanisms (Fast Retransmit/RTO) have
been triggered (i.e. we are in either TCP_CA_Recovery or TCP_CA_Loss).
We have recorded the CWND values for both RDB and non-RDB streams in
our experiments, and have not found any obvious red flags when
analysing the results, so I presume (hope may be more precise) this is
not a major issue we have missed. Nevertheless, I will investigate
this in detail and get back to you.
I've looked into this and tried to figure out in which cases this is
actually a problem, but I have failed to find any.
One scenario I considered is when an RDB packet is sent right after a
retransmit, which would result in DSACK in the ACK in response to the
RDB packet.
With a bundling limit of 1 packet, two packets must be lost for RDB to
fail to repair the loss, causing dupACKs. So if three packets are sent,
where the first two are lost, the last packet will cause a dupACK,
resulting in a fast retransmit (and entering recovery which calls
tcp_init_undo()).
By writing new data to the socket right after the fast retransmit,
a new RDB packet is built with some old data that was just
retransmitted.
On the ACK on the fast retransmit the state is changed from Recovery
to Open. The next incoming ACK (on the RDB packet) will contain a DSACK
range, but it will not be considered dubious (tcp_ack_is_dubious())
since "!(flag & FLAG_NOT_DUP)" is false (new data was acked), state is
Open, and "flag & FLAG_CA_ALERT" evaluates to false.
Feel free to suggest scenarios (as detailed as possible) with the
potential to cause such false positives, and I'll test them with
packetdrill.
Bendik
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games and
remote desktop, produce traffic with thin-stream characteristics,
characterized by small packets and a relatively high ITT. By bundling
already sent data in packets with new data, RDB alleviates head-of-line
blocking by reducing the need to retransmit data segments when packets
are lost. RDB is a continuation on the work on latency improvements for
TCP in Linux, previously resulting in two thin-stream mechanisms in the
Linux kernel
(https://github.com/torvalds/linux/blob/master/Documentation/networking/tcp-thin.txt).
The RDB implementation has been thoroughly tested, and shows
significant latency reductions when packet loss occurs[1]. The tests
show that, by imposing restrictions on the bundling rate, it can be
made not to negatively affect competing traffic in an unfair manner.
These patches have also been tested with a set of packetdrill scripts
located at
https://github.com/bendikro/packetdrill/tree/master/gtests/net/packetdrill/tests/linux/rdb
(The tests require patching packetdrill with a new socket option:
https://github.com/bendikro/packetdrill/commit/9916b6c53e33dd04329d29b7d8baf703b2c2ac1b)
Detailed info about the RDB mechanism can be found at
http://mlab.no/blog/2015/10/redundant-data-bundling-in-tcp, as well as
in the paper "Latency and Fairness Trade-Off for Thin Streams using
Redundant Data Bundling in TCP"[2].
[1] http://home.ifi.uio.no/paalh/students/BendikOpstad.pdf
[2] http://home.ifi.uio.no/bendiko/rdb_fairness_tradeoff.pdf
Changes:
v7 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Changed sysctl_tcp_rdb to accept three values (Thanks Yuchung):
- 0: Disable system wide (RDB cannot be enabled with TCP_RDB socket option)
- 1: Allow enabling RDB with TCP_RDB socket option.
- 2: Enable RDB by default on all TCP sockets and allow to modify with TCP_RDB
* Added sysctl tcp_rdb_wait_congestion to control if RDB by default should
wait for congestion before bundling. (Ref. comment by Eric on lossless conns)
* Changed socket options to modify per-socket RDB settings:
- Added flags to TCP_RDB to allow bundling without waiting for loss with
TCP_RDB_BUNDLE_IMMEDIATE.
- Added socket option TCP_RDB_MAX_BYTES: Set max bytes per RDB packet.
- Added socket option TCP_RDB_MAX_PACKETS: Set max packets allowed to be
bundled by RDB.
* Added SNMP counter LINUX_MIB_TCPRDBLOSSREPAIRS to count the occurences
where RDB repaired a loss (Thanks Eric).
* Bundle on FIN packets (Thanks Yuchung).
* Updated docs in Documentation/networking/{ip-sysctl.txt,tcp-thin.txt}
* Removed flags parameter from tcp_rdb_ack_event()
* Changed sysctl knobs to using network namespace.
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Changed sysctl knobs to using network namespace
v6 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Renamed rdb_ack_event() to tcp_rdb_ack_event() (Thanks DaveM)
* Minor doc changes
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Minor doc changes
v5 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed two unnecessary EXPORT_SYMOBOLs (Thanks Eric)
* Renamed skb_append_data() to tcp_skb_append_data() (Thanks Eric)
* Fixed bugs in additions to ipv4_table (sysctl_net_ipv4.c)
* Merged the two if tests for max payload of RDB packet in
rdb_can_bundle_test()
* Renamed rdb_check_rtx_queue_loss() to rdb_detect_loss()
and restructured to reduce indentation.
* Improved docs
* Revised commit message to be more detailed.
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Fixed bug in additions to ipv4_table (sysctl_net_ipv4.c)
v4 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Moved skb_append_data() to tcp_output.c and call this
function from tcp_collapse_retrans() as well.
* Merged functionality of create_rdb_skb() into
tcp_transmit_rdb_skb()
* Removed one parameter from rdb_can_bundle_test()
v3 (PATCH):
* tcp-Add-Redundant-Data-Bundling-RDB:
* Changed name of sysctl variable from tcp_rdb_max_skbs to
tcp_rdb_max_packets after comment from Eric Dumazet about
not exposing internal (kernel) names like skb.
* Formatting and function docs fixes
v2 (RFC/PATCH):
* tcp-Add-DPIFL-thin-stream-detection-mechanism:
* Change calculation in tcp_stream_is_thin_dpifl based on
feedback from Eric Dumazet.
* tcp-Add-Redundant-Data-Bundling-RDB:
* Removed setting nonagle in do_tcp_setsockopt (TCP_RDB)
to reduce complexity as commented by Neal Cardwell.
* Cleaned up loss detection code in rdb_check_rtx_queue_loss
v1 (RFC/PATCH)
Bendik Rønning Opstad (2):
tcp: Add DPIFL thin stream detection mechanism
tcp: Add Redundant Data Bundling (RDB)
Documentation/networking/ip-sysctl.txt | 43 ++++++
Documentation/networking/tcp-thin.txt | 188 ++++++++++++++++++++------
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 11 +-
include/net/netns/ipv4.h | 6 +
include/net/tcp.h | 33 +++++
include/uapi/linux/snmp.h | 1 +
include/uapi/linux/tcp.h | 10 ++
net/core/skbuff.c | 2 +-
net/ipv4/Makefile | 3 +-
net/ipv4/proc.c | 1 +
net/ipv4/sysctl_net_ipv4.c | 43 ++++++
net/ipv4/tcp.c | 42 +++++-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_ipv4.c | 6 +
net/ipv4/tcp_output.c | 49 ++++---
net/ipv4/tcp_rdb.c | 240 +++++++++++++++++++++++++++++++++
17 files changed, 619 insertions(+), 63 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
--
2.1.4
The existing mechanism for detecting thin streams,
tcp_stream_is_thin(), is based on a static limit of less than 4
packets in flight. This treats streams differently depending on the
connection's RTT, such that a stream on a high RTT link may never be
considered thin, whereas the same application would produce a stream
that would always be thin in a low RTT scenario (e.g. data center).
By calculating a dynamic packets in flight limit (DPIFL), the thin
stream detection will be independent of the RTT and treat streams
equally based on the transmission pattern, i.e. the inter-transmission
time (ITT).
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 8 ++++++++
include/net/netns/ipv4.h | 1 +
include/net/tcp.h | 21 +++++++++++++++++++++
net/ipv4/sysctl_net_ipv4.c | 9 +++++++++
net/ipv4/tcp_ipv4.c | 1 +
5 files changed, 40 insertions(+)
@@ -718,6 +718,14 @@ tcp_thin_dupack - BOOLEAN Documentation/networking/tcp-thin.txt Default: 0+tcp_thin_dpifl_itt_lower_bound - INTEGER+ Controls the lower bound inter-transmission time (ITT) threshold+ for when a stream is considered thin. The value is specified in+ microseconds, and may not be lower than 10000 (10 ms). Based on+ this threshold, a dynamic packets in flight limit (DPIFL) is+ calculated, which is used to classify whether a stream is thin.+ Default: 10000+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -214,6 +214,8 @@ void tcp_time_wait(struct sock *sk, int state, int timeo);/* TCP thin-stream limits */#define TCP_THIN_LINEAR_RETRIES 6 /* After 6 linear retries, do exp. backoff */+/* Lowest possible DPIFL lower bound ITT is 10 ms (10000 usec) */+#define TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN 10000/* TCP initial congestion window as per rfc6928 */#define TCP_INIT_CWND 10
@@ -1652,6 +1654,25 @@ static inline bool tcp_stream_is_thin(struct tcp_sock *tp)returntp->packets_out<4&&!tcp_in_initial_slowstart(tp);}+/**+*tcp_stream_is_thin_dpifl()-Testifthestreamisthinbasedon+*dynamicPIFlimit(DPIFL)+*@sk:socket+*+*Return:trueifcurrentpacketsinflight(PIF)countislowerthan+*thedynamicPIFlimit,elsefalse+*/+staticinlinebooltcp_stream_is_thin_dpifl(conststructsock*sk)+{+/* Calculate the maximum allowed PIF limit by dividing the RTT by+*theminimumallowedinter-transmissiontime(ITT).+*TestsifPIF<RTT/ITT-lower-bound+*/+return(u64)tcp_packets_in_flight(tcp_sk(sk))*+sock_net(sk)->ipv4.sysctl_tcp_thin_dpifl_itt_lower_bound<+(tcp_sk(sk)->srtt_us>>3);+}+/* /proc */enumtcp_seq_states{TCP_SEQ_STATE_LISTENING,
@@ -41,6 +41,7 @@ static int tcp_syn_retries_min = 1;staticinttcp_syn_retries_max=MAX_TCP_SYNCNT;staticintip_ping_group_range_min[]={0,0};staticintip_ping_group_range_max[]={GID_T_MAX,GID_T_MAX};+staticinttcp_thin_dpifl_itt_lower_bound_min=TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN;/* Update system visible IP port range */staticvoidset_local_port_range(structnet*net,intrange[2])
@@ -2412,6 +2412,7 @@ static int __net_init tcp_sk_init(struct net *net)net->ipv4.sysctl_tcp_orphan_retries=0;net->ipv4.sysctl_tcp_fin_timeout=TCP_FIN_TIMEOUT;net->ipv4.sysctl_tcp_notsent_lowat=UINT_MAX;+net->ipv4.sysctl_tcp_thin_dpifl_itt_lower_bound=TCP_THIN_DPIFL_ITT_LOWER_BOUND_MIN;return0;fail:
Redundant Data Bundling (RDB) is a mechanism for TCP aimed at reducing
the latency for applications sending time-dependent data.
Latency-sensitive applications or services, such as online games,
remote control systems, and VoIP, produce traffic with thin-stream
characteristics, characterized by small packets and relatively high
inter-transmission times (ITT). When experiencing packet loss, such
latency-sensitive applications are heavily penalized by the need to
retransmit lost packets, which increases the latency by a minimum of
one RTT for the lost packet. Packets coming after a lost packet are
held back due to head-of-line blocking, causing increased delays for
all data segments until the lost packet has been retransmitted.
RDB enables a TCP sender to bundle redundant (already sent) data with
TCP packets containing small segments of new data. By resending
un-ACKed data from the output queue in packets with new data, RDB
reduces the need to retransmit data segments on connections
experiencing sporadic packet loss. By avoiding a retransmit, RDB
evades the latency increase of at least one RTT for the lost packet,
as well as alleviating head-of-line blocking for the packets following
the lost packet. This makes the TCP connection more resistant to
latency fluctuations, and reduces the application layer latency
significantly in lossy environments.
Main functionality added:
o When a packet is scheduled for transmission, RDB builds and
transmits a new SKB containing both the unsent data as well as
data of previously sent packets from the TCP output queue.
o RDB will only be used for streams classified as thin by the
function tcp_stream_is_thin_dpifl(). This enforces a lower bound
on the ITT for streams that may benefit from RDB, controlled by
the sysctl variable net.ipv4.tcp_thin_dpifl_itt_lower_bound.
o Loss detection of hidden loss events: When bundling redundant data
with each packet, packet loss can be hidden from the TCP engine due
to lack of dupACKs. This is because the loss is "repaired" by the
redundant data in the packet coming after the lost packet. Based on
incoming ACKs, such hidden loss events are detected, and CWR state
is entered.
RDB can be enabled on a connection with the socket option TCP_RDB or
on all new connections by setting the sysctl variable
net.ipv4.tcp_rdb=2
Cc: Andreas Petlund <redacted>
Cc: Carsten Griwodz <redacted>
Cc: Pål Halvorsen <redacted>
Cc: Jonas Markussen <redacted>
Cc: Kristian Evensen <redacted>
Cc: Kenneth Klette Jonassen <redacted>
Signed-off-by: Bendik Rønning Opstad <redacted>
---
Documentation/networking/ip-sysctl.txt | 35 +++++
Documentation/networking/tcp-thin.txt | 188 ++++++++++++++++++++------
include/linux/skbuff.h | 1 +
include/linux/tcp.h | 11 +-
include/net/netns/ipv4.h | 5 +
include/net/tcp.h | 12 ++
include/uapi/linux/snmp.h | 1 +
include/uapi/linux/tcp.h | 10 ++
net/core/skbuff.c | 2 +-
net/ipv4/Makefile | 3 +-
net/ipv4/proc.c | 1 +
net/ipv4/sysctl_net_ipv4.c | 34 +++++
net/ipv4/tcp.c | 42 +++++-
net/ipv4/tcp_input.c | 3 +
net/ipv4/tcp_ipv4.c | 5 +
net/ipv4/tcp_output.c | 49 ++++---
net/ipv4/tcp_rdb.c | 240 +++++++++++++++++++++++++++++++++
17 files changed, 579 insertions(+), 63 deletions(-)
create mode 100644 net/ipv4/tcp_rdb.c
@@ -726,6 +726,41 @@ tcp_thin_dpifl_itt_lower_bound - INTEGER calculated, which is used to classify whether a stream is thin. Default: 10000+tcp_rdb - BOOLEAN+ Controls the use of the Redundant Data Bundling (RDB) mechanism+ for TCP connections.++ RDB is a TCP mechanism aimed at reducing the latency for+ applications transmitting time-dependent data. By bundling already+ sent data in packets with new data, RDB alleviates head-of-line+ blocking on the receiver side by reducing the need to retransmit+ data segments when packets are lost. See tcp-thin.txt for further+ details.+ Possible values:+ 0 - Disable RDB system wide, i.e. disallow enabling RDB on TCP+ sockets with the socket option TCP_RDB.+ 1 - Allow enabling/disabling RDB with socket option TCP_RDB.+ 2 - Set RDB to be enabled by default for all new TCP connections+ and allow modifying socket with socket option TCP_RDB.+ Default: 1++tcp_rdb_await_congestion - BOOLEAN+ Controls whether an RDB-enabled connection, by default, should+ postpone bundling until congestion has been detected.++tcp_rdb_max_bytes - INTEGER+ Enable restriction on how many bytes an RDB packet can contain.+ This is the total amount of payload including the new unsent data.+ A value of 0 will disable bytes based limitation.+ Default: 0++tcp_rdb_max_packets - INTEGER+ Enable restriction on how many previous packets in the output queue+ RDB may include data from. A value of 1 will restrict bundling to+ only the data from the last packet that was sent.+ A value of 0 will disable packet based limitation.+ Default: 1+ tcp_limit_output_bytes - INTEGER Controls TCP Small Queue limit per tcp socket. TCP bulk sender tends to increase packets in flight until it
@@ -1,47 +1,159 @@ Thin-streams and TCP-====================+-----------------------+ A wide range of Internet-based services that use reliable transport-protocols display what we call thin-stream properties. This means-that the application sends data with such a low rate that the-retransmission mechanisms of the transport protocol are not fully-effective. In time-dependent scenarios (like online games, control-systems, stock trading etc.) where the user experience depends-on the data delivery latency, packet loss can be devastating for-the service quality. Extreme latencies are caused by TCP's-dependency on the arrival of new data from the application to trigger-retransmissions effectively through fast retransmit instead of-waiting for long timeouts.+protocols display what we call thin-stream properties. Traffic with+thin-stream characteristics, characterized by small packets and a+relatively high inter-transmission time (ITT), is often produced by+latency-sensitive applications or services that rely on minimal+latencies.++In time-dependent scenarios (like online games, remote desktop,+control systems, stock trading etc.) where the user experience depends+on the data delivery latency, packet loss can be devastating for the+service quality.++Applications with a low write frequency, i.e. that write to the socket+with with a low rate resulting in few packets in flight (PIF), render+the retransmission mechanisms of the transport protocol ineffective.+Thin streams experience increased latencies due to TCP's dependency on+the arrival of dupACKs to trigger retransmissions effectively through+fast retransmit instead of waiting for long timeouts. After analysing a large number of time-dependent interactive-applications, we have seen that they often produce thin streams-and also stay with this traffic pattern throughout its entire-lifespan. The combination of time-dependency and the fact that the-streams provoke high latencies when using TCP is unfortunate.+applications, we have seen that they often produce thin streams and+also stay with this traffic pattern throughout its entire lifespan.+The combination of time-dependency and the fact that the streams+provoke high latencies when using TCP is unfortunate.++In order to reduce application-layer latency when packets are lost, a+set of mechanisms have been made, which address these latency issues+for thin streams.++Two reactive mechanisms will reduce the time it takes to trigger+retransmits when a stream has less than four PIFs:++* TCP_THIN_DUPACK: Do Fast Retransmit on the first dupACK.++* TCP_THIN_LINEAR_TIMEOUTS: Instead of exponential backoff after RTOs,+ perform up to 6 (TCP_THIN_LINEAR_RETRIES) linear timeouts before+ initiating exponential backoff.++The threshold of 4 PIFs is used because when there are less than 4+PIFs, the three dupACKs usually required to trigger a fast retransmit+may not be produced, rendering the stream prone to experience high+retransmission latencies.-In order to reduce application-layer latency when packets are lost,-a set of mechanisms has been made, which address these latency issues-for thin streams. In short, if the kernel detects a thin stream,-the retransmission mechanisms are modified in the following manner:+Redundant Data Bundling+***********************-1) If the stream is thin, fast retransmit on the first dupACK.-2) If the stream is thin, do not apply exponential backoff.+Redundant Data Bundling (RDB) is a mechanism aimed at reducing the+latency for applications sending time-dependent data by proactively+retransmitting un-ACKed segments. By bundling (retransmitting) already+sent data with packets containing new data, the connection will be+more resistant to sporadic packet loss which reduces the application+layer latency significantly in congested scenarios.-These enhancements are applied only if the stream is detected as-thin. This is accomplished by defining a threshold for the number-of packets in flight. If there are less than 4 packets in flight,-fast retransmissions can not be triggered, and the stream is prone-to experience high retransmission latencies.+Retransmitting data segments before they are known to be lost is a+proactive approach at preventing increased latencies when packets are+lost. By bundling redundant data before the retransmission mechanisms+are triggered, RDB is very effective at alleviating head-of-line+blocking on the receiving side, simply by reducing the need to perform+regular retransmissions.++With RDB enabled, an application that writes less frequently than the+limit defined by the sysctl tcp_thin_dpifl_itt_lower_bound will be+allowed to bundle.++Using the thin-stream mechanisms+******************************** Since these mechanisms are targeted at time-dependent applications,-they must be specifically activated by the application using the-TCP_THIN_LINEAR_TIMEOUTS and TCP_THIN_DUPACK IOCTLS or the-tcp_thin_linear_timeouts and tcp_thin_dupack sysctls. Both-modifications are turned off by default.--References-==========-More information on the modifications, as well as a wide range of-experimental data can be found here:-"Improving latency for interactive, thin-stream applications over-reliable transport"-http://simula.no/research/nd/publications/Simula.nd.477/simula_pdf_file+they are by default off.++The socket options TCP_THIN_DUPACK and TCP_THIN_LINEAR_TIMEOUTS can be+used to enable the mechanisms on a socket. Alternatively, they can be+enabled system-wide by setting the sysctl variables+net.ipv4.tcp_thin_dupack and net.ipv4.tcp_thin_linear_timeouts to 1.++Using RDB+=========++By default, applications are allowed to enable RDB on a socket with+the socket option TCP_RDB. By setting the sysctl net.ipv4.tcp_rdb=0,+application are not allowed to enable RDB on a socket. For testing+purposes, it is possible to enable RDB system-wide for all new TCP+connections by setting net.ipv4.tcp_rdb=2.++For RDB to be fully efficient, Nagle must be disabled with the socket+option TCP_NODELAY.+++Limitations on how much is bundled+==================================++Applying limitations on how much RDB may bundle can help control how+RDB affects the bandwidth usage and effects on competing traffic. With+few active RDB enabled streams, the total increase of bandwidth usage+and negative effect on competing traffic will be minimal, unless the+total bandwidth capacity is very limited.++In scenarios with many RDB enabled streams, the total effect may+become significant, which may justify imposing limitations on RDB.++The two sysctls tcp_rdb_max_bytes and tcp_rdb_max_packets contain the+default values used to limit how much can be bundled with each packet.++tcp_rdb_max_bytes limits the payload size of an RDB packet which is+the size including both the new (unsent) data as well as the already+sent data. tcp_rdb_max_packets specifies the number of packets that+may be bundled with each RDB packet. This is the most important knob+as it directly controls how many lost packets each RDB packet may+recover.++If more fine grained control is required, tcp_rdb_max_bytes is useful+to control how much impact RDB can have on the increased bandwidth+requirements of the flows. If an application writes 700 bytes per+write call, the bandwidth increase can be quite significant (even with+a 1 packet bundling limit) if we consider a scenario with thousands of+RDB streams.++By limiting the total payload size of RDB packets to e.g. 100 bytes,+only the smallest segments will benefit from RDB, while the segments+that would increase the bandwidth requirements the most, will not.++tcp_rdb_max_packets defaults to 1 as that allows RDB to recover from+sporadic packet loss while still affecting competing traffic to a+small degree[2].++The sysctl tcp_rdb_await_congestion specifies whether a connection+should bundle only after congestion has been detected.++The default bundling limitations defined by the sysctl variables may+be overridden with the socket options TCP_RDB_MAX_BYTES and+TCP_RDB_MAX_PACKETS. To ensure bundling is performed immediately+instead of waiting until after packet loss, pass the following flags+to TCP_RDB socket option: (TCP_RDB_ENABLE | TCP_RDB_BUNDLE_IMMEDIATE).+++Further reading+***********************++[1] provides information on the modifications thin_dupack and+thin_linear_timeouts, as well as a wide range of experimental data++[2] presents RDB and the motivation behind the mechanism. [3] provides+a detailed overview of the RDB mechanism and the experiments performed+to test the effects of RDB.++[1] "Improving latency for interactive, thin-stream applications over+ reliable transport"+ http://urn.nb.no/URN:NBN:no-24274++[2] "Latency and fairness trade-off for thin streams using redundant+ data bundling in TCP."+ http://dx.doi.org/10.1109/LCN.2015.7366322++[3] "Taming Redundant Data Bundling: Balancing fairness and latency+ for redundant bundling in TCP"+ http://urn.nb.no/URN:NBN:no-48283
@@ -213,11 +213,12 @@ struct tcp_sock {}rack;u16advmss;/* Advertised MSS */u8unused;-u8nonagle:4,/* Disable Nagle algorithm? */+u8nonagle:3,/* Disable Nagle algorithm? */thin_lto:1,/* Use linear timeouts for thin streams */thin_dupack:1,/* Fast retransmit on first dupack */repair:1,-frto:1;/* F-RTO (RFC5682) activated in CA_Loss */+frto:1,/* F-RTO (RFC5682) activated in CA_Loss */+is_cwnd_limited:1;/* forward progress limited by snd_cwnd? */u8repair_queue;u8do_early_retrans:1,/* Enable RFC5827 early-retransmit */syn_data:1,/* SYN includes data */
@@ -225,7 +226,11 @@ struct tcp_sock {syn_fastopen_exp:1,/* SYN includes Fast Open exp. option */syn_data_acked:1,/* data in SYN is acked by SYN-ACK */save_syn:1,/* Save headers of SYN packet */-is_cwnd_limited:1;/* forward progress limited by snd_cwnd? */+rdb:1,/* Redundant Data Bundling enabled */+rdb_await_congestion:1;/* RDB wait to bundle until next loss */++u16rdb_max_bytes;/* Max payload bytes in an RDB packet */+u16rdb_max_packets;/* Max packets allowed to be bundled by RDB */u32tlp_high_seq;/* snd_nxt at the time of TLP retransmit. *//* RTT measurement */
@@ -770,6 +778,7 @@ struct tcp_skb_cb {struct{/* There is space for up to 20 bytes */__u32in_flight;/* Bytes in flight when packet sent */+__u32rdb_start_seq;/* Start seq of RDB data */}tx;/* only used for outgoing skbs */union{structinet_skb_parmh4;
@@ -115,6 +115,9 @@ enum {#define TCP_CC_INFO 26 /* Get Congestion Control (optional) info */#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */+#define TCP_RDB 29 /* Enable Redundant Data Bundling mechanism */+#define TCP_RDB_MAX_BYTES 30 /* Max payload bytes in an RDB packet */+#define TCP_RDB_MAX_PACKETS 31 /* Max packets allowed to be bundled by RDB */structtcp_repair_opt{__u32opt_code;
@@ -214,4 +217,11 @@ struct tcp_md5sig {__u8tcpm_key[TCP_MD5SIG_MAXKEYLEN];/* key (binary) */};+/*+*TCP_RDBsocketoptionflags+*/+#define TCP_RDB_DISABLE 0 /* Disble RDB */+#define TCP_RDB_ENABLE 1 /* Enable RDB */+#define TCP_RDB_BUNDLE_IMMEDIATE 2 /* Force immediate bundling (Do not wait for congestion) */+#endif /* _UAPI_LINUX_TCP_H */
@@ -3540,6 +3540,9 @@ static inline void tcp_in_ack_event(struct sock *sk, u32 flags)if(icsk->icsk_ca_ops->in_ack_event)icsk->icsk_ca_ops->in_ack_event(sk,flags);++if(unlikely(tcp_sk(sk)->rdb))+tcp_rdb_ack_event(sk);}/* Congestion control has updated the cwnd already. So if we're in
@@ -2395,6 +2395,11 @@ static int __net_init tcp_sk_init(struct net *net)net->ipv4.sysctl_tcp_ecn=2;net->ipv4.sysctl_tcp_ecn_fallback=1;+net->ipv4.sysctl_tcp_rdb=1;+net->ipv4.sysctl_tcp_rdb_await_congestion=1;+net->ipv4.sysctl_tcp_rdb_max_bytes=0;+net->ipv4.sysctl_tcp_rdb_max_packets=1;+net->ipv4.sysctl_tcp_base_mss=TCP_BASE_MSS;net->ipv4.sysctl_tcp_probe_threshold=TCP_PROBE_THRESHOLD;net->ipv4.sysctl_tcp_probe_interval=TCP_PROBE_INTERVAL;
@@ -2129,9 +2129,12 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,break;}-if(unlikely(tcp_transmit_skb(sk,skb,1,gfp)))+if(unlikely(tcp_sk(sk)->rdb)){+if(tcp_transmit_rdb_skb(sk,skb,mss_now,gfp))+break;+}elseif(unlikely(tcp_transmit_skb(sk,skb,1,gfp))){break;-+}repair:/* Advance the send_head. This one is sent out.*Thiscallwillincrementpackets_out.
@@ -2472,15 +2475,33 @@ void tcp_skb_collapse_tstamp(struct sk_buff *skb,}}+/**+*tcp_skb_append_data()-copythelineardatafromanSKBtotheend+*ofanotherandupdateendsequencenumber+*andchecksum+*@from_skb:theSKBtocopydatafrom+*@to_skb:theSKBtocopydatato+*/+voidtcp_skb_append_data(structsk_buff*from_skb,structsk_buff*to_skb)+{+skb_copy_from_linear_data(from_skb,skb_put(to_skb,from_skb->len),+from_skb->len);+TCP_SKB_CB(to_skb)->end_seq=TCP_SKB_CB(from_skb)->end_seq;++if(from_skb->ip_summed==CHECKSUM_PARTIAL)+to_skb->ip_summed=CHECKSUM_PARTIAL;++if(to_skb->ip_summed!=CHECKSUM_PARTIAL)+to_skb->csum=csum_block_add(to_skb->csum,from_skb->csum,+to_skb->len);++}+/* Collapses two adjacent SKB's during retransmission. */staticvoidtcp_collapse_retrans(structsock*sk,structsk_buff*skb){structtcp_sock*tp=tcp_sk(sk);structsk_buff*next_skb=tcp_write_queue_next(sk,skb);-intskb_size,next_skb_size;--skb_size=skb->len;-next_skb_size=next_skb->len;BUG_ON(tcp_skb_pcount(skb)!=1||tcp_skb_pcount(next_skb)!=1);
@@ -2488,17 +2509,7 @@ static void tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)tcp_unlink_write_queue(next_skb,sk);-skb_copy_from_linear_data(next_skb,skb_put(skb,next_skb_size),-next_skb_size);--if(next_skb->ip_summed==CHECKSUM_PARTIAL)-skb->ip_summed=CHECKSUM_PARTIAL;--if(skb->ip_summed!=CHECKSUM_PARTIAL)-skb->csum=csum_block_add(skb->csum,next_skb->csum,skb_size);--/* Update sequence range on original skb. */-TCP_SKB_CB(skb)->end_seq=TCP_SKB_CB(next_skb)->end_seq;+tcp_skb_append_data(next_skb,skb);/* Merge over control information. This moves PSH/FIN etc. over */TCP_SKB_CB(skb)->tcp_flags|=TCP_SKB_CB(next_skb)->tcp_flags;
@@ -0,0 +1,240 @@+#include<linux/skbuff.h>+#include<net/tcp.h>++/**+*rdb_detect_loss()-performRDBlossdetectionbyanalysingACKs+*@sk:socket+*+*TraversetheoutputqueueandcheckiftheACKedpacketisanRDB+*packetandiftheredundantdatacoversoneormoreun-ACKedSKBs.+*IftheincomingACKacknowledgesmultipleSKBs,wecanpresume+*packetlosshasoccurred.+*+*WecaninferpacketlossthiswaybecausewecanexpectoneACKper+*transmitteddatapacket,asdelayedACKsaredisabledwhenahost+*receivespacketswherethesequencenumberisnottheexpected+*sequencenumber.+*+*Return:1ifpacketloss,else0+*/+staticunsignedintrdb_detect_loss(structsock*sk)+{+structsk_buff*skb,*tmp;+structtcp_skb_cb*scb;+u32seq_acked=tcp_sk(sk)->snd_una;++tcp_for_write_queue(skb,sk){+if(skb==tcp_send_head(sk))+break;++scb=TCP_SKB_CB(skb);+/* The ACK acknowledges parts of the data in this SKB.+*Canbecausedby:+*-TSO:WeabortasRDBisnotusedonSKBssplitacross+*multiplepacketsonlowerlayersasthesearegreater+*thanoneMSS.+*-Retranscollapse:We'vehadaretrans,solosshasalready+*beendetected.+*/+if(after(scb->end_seq,seq_acked))+break;+elseif(scb->end_seq!=seq_acked)+continue;++/* We have found the ACKed packet */++/* This packet was sent with no redundant data, or no prior+*un-ACKedSKBsisintheoutputqueue,sobreakhere.+*/+if(scb->tx.rdb_start_seq==scb->seq||+skb_queue_is_first(&sk->sk_write_queue,skb))+break;+/* Find number of prior SKBs whose data was bundled in this+*(ACKed)SKB.Wepresumeanyredundantdatacoveringprevious+*SKB'sareduetoloss.(Anexceptionwouldbereordering).+*/+skb=skb->prev;+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+if(before(TCP_SKB_CB(skb)->seq,scb->tx.rdb_start_seq))+break;+return1;+}+break;+}+return0;+}++/**+*tcp_rdb_ack_event()-initiateRDBlossdetection+*@sk:socket+*+*WhenRDBisabletorepairapacketloss,thelosseventishidden+*fromtheregularlossdetectionmechanisms.ToensureRDBstreams+*behavefairlytowardscompetingTCPtraffic,wecalltcp_enter_cwr()+*toentercongestionwindowreductionstate.+*tcp_enter_cwr()disablesundoingtheCWNDreduction,whichavoids+*incorrectlyundoingthereductionlateron.+*/+voidtcp_rdb_ack_event(structsock*sk)+{+unsignedintlost=rdb_detect_loss(sk);+if(lost){+tcp_enter_cwr(sk);+NET_INC_STATS(sock_net(sk),LINUX_MIB_TCPRDBLOSSREPAIRS);+}+}++/**+*rdb_build_skb()-buildanewRDBSKBandcopyredundant+unsent+*datatothelinearpagebuffer+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionintheoutputengine+*@first_skb:thefirstSKBintheoutputqueuetobebundled+*@bytes_in_rdb_skb:thetotalnumberofdatabytesforthenew+*rdb_skb(NEW+Redundant)+*@gfp_mask:gfp_tallocation+*+*Return:AnewSKBcontainingredundantdata,orNULLifmemory+*allocationfailed+*/+staticstructsk_buff*rdb_build_skb(conststructsock*sk,+structsk_buff*xmit_skb,+structsk_buff*first_skb,+u32bytes_in_rdb_skb,+gfp_tgfp_mask)+{+structsk_buff*rdb_skb,*tmp_skb=first_skb;++rdb_skb=sk_stream_alloc_skb((structsock*)sk,+(int)bytes_in_rdb_skb,+gfp_mask,false);+if(!rdb_skb)+returnNULL;+copy_skb_header(rdb_skb,xmit_skb);+rdb_skb->ip_summed=xmit_skb->ip_summed;+TCP_SKB_CB(rdb_skb)->seq=TCP_SKB_CB(first_skb)->seq;+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(rdb_skb)->seq;++/* Start on first_skb and append payload from each SKB in the output+*queueontordb_skbuntilwereachxmit_skb.+*/+tcp_for_write_queue_from(tmp_skb,sk){+tcp_skb_append_data(tmp_skb,rdb_skb);++/* We reached xmit_skb, containing the unsent data */+if(tmp_skb==xmit_skb)+break;+}+returnrdb_skb;+}++/**+*rdb_can_bundle_test()-testifredundantdatacanbebundled+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@max_payload:themaximumallowedpayloadbytesfortheRDBSKB+*@bytes_in_rdb_skb:storethetotalnumberofpayloadbytesinthe+*RDBSKBifbundlingcanbeperformed+*+*Traversetheoutputqueueandcheckifanyun-ackeddatamaybe+*bundled.+*+*Return:ThefirstSKBtobeinthebundle,orNULLifnobundling+*/+staticstructsk_buff*rdb_can_bundle_test(conststructsock*sk,+structsk_buff*xmit_skb,+unsignedintmax_payload,+u32*bytes_in_rdb_skb)+{+structtcp_sock*tp=tcp_sk(sk);+structsk_buff*first_to_bundle=NULL;+structsk_buff*tmp,*skb=xmit_skb->prev;+u32skbs_in_bundle_count=1;/* Start on 1 to account for xmit_skb */+u32total_payload=xmit_skb->len;++if(tp->rdb_max_bytes)+max_payload=min_t(unsignedint,max_payload,+tp->rdb_max_bytes);++/* We start at xmit_skb->prev, and go backwards */+tcp_for_write_queue_reverse_from_safe(skb,tmp,sk){+/* Including data from this SKB would exceed payload limit */+if((total_payload+skb->len)>max_payload)+break;++if(tp->rdb_max_packets&&+(skbs_in_bundle_count>tp->rdb_max_packets))+break;++total_payload+=skb->len;+skbs_in_bundle_count++;+first_to_bundle=skb;+}+*bytes_in_rdb_skb=total_payload;+returnfirst_to_bundle;+}++/**+*tcp_transmit_rdb_skb()-trytocreateandsendanRDBpacket+*@sk:socket+*@xmit_skb:theSKBprocessedfortransmissionbytheoutputengine+*@mss_now:currentmssvalue+*@gfp_mask:gfp_tallocation+*+*IfanRDBpacketcouldnotbecreatedandsent,transmitthe+*originalunmodifiedSKB(xmit_skb).+*+*Return:0ifsuccessfullysentpacket,elseerrorfrom+*tcp_transmit_skb+*/+inttcp_transmit_rdb_skb(structsock*sk,structsk_buff*xmit_skb,+unsignedintmss_now,gfp_tgfp_mask)+{+structsk_buff*rdb_skb=NULL;+structsk_buff*first_to_bundle;+u32bytes_in_rdb_skb=0;++/* How we detect that RDB was used. When equal, no RDB data was sent */+TCP_SKB_CB(xmit_skb)->tx.rdb_start_seq=TCP_SKB_CB(xmit_skb)->seq;++/* We must wait for a retransmission to occur before bundling */+if(tcp_sk(sk)->rdb_await_congestion){+if(tcp_in_initial_slowstart(tcp_sk(sk)))+gotoxmit_default;+tcp_sk(sk)->rdb_await_congestion=0;+}++if(!tcp_stream_is_thin_dpifl(sk))+gotoxmit_default;++/* No bundling if first in queue */+if(skb_queue_is_first(&sk->sk_write_queue,xmit_skb))+gotoxmit_default;++/* Find number of (previous) SKBs to get data from */+first_to_bundle=rdb_can_bundle_test(sk,xmit_skb,mss_now,+&bytes_in_rdb_skb);+if(!first_to_bundle)+gotoxmit_default;++/* Create an SKB that contains redundant data starting from+*first_to_bundle.+*/+rdb_skb=rdb_build_skb(sk,xmit_skb,first_to_bundle,+bytes_in_rdb_skb,gfp_mask);+if(!rdb_skb)+gotoxmit_default;++/* Set skb_mstamp for the SKB in the output queue (xmit_skb) containing+*theyetunsentdata.Normallythiswouldbedoneby+*tcp_transmit_skb(),butaswepassinrdb_skbinstead,xmit_skb's+*timestampwillnotbetouched.+*/+skb_mstamp_get(&xmit_skb->skb_mstamp);+rdb_skb->skb_mstamp=xmit_skb->skb_mstamp;+returntcp_transmit_skb(sk,rdb_skb,0,gfp_mask);++xmit_default:+/* Transmit the unmodified SKB from output queue */+returntcp_transmit_skb(sk,xmit_skb,1,gfp_mask);+}