如下圖所示,TCP窗口擴張係數選項長度爲3個字節,在TCP握手階段的SYN報文和SYN+ACK報文中攜帶,其旨在通告對發送和接收窗口擴張功能的支持,以及通告擴張係數。注意前者意味着只有在SYN報文中通告了對WSopt選項的支持時,回覆的SYN+ACK報文才可攜帶WSopt選項,否則,SYN+ACK報文中不可攜帶WSopt。在SYN或者SYN+ACK報文中的通告的窗口值都是不能進行擴張的。
擴張係數shift.cnt表示窗口值左移的位數值。
+---------+---------+---------+
| Kind=3 |Length=3 |shift.cnt|
+---------+---------+---------+
x
擴張係數
擴張係數的最大值定義爲14。
/* Maximal number of window scale according to RFC1323 */
#define TCP_MAX_WSCALE 14U
內核中TCP使用32bit的變量snd_wnd保存窗口的值,故除去TCP頭部字段的16位窗口值後,擴張係數的最大值爲16。但是,如果整個窗口值爲32bit,即整個4G的空間,會導致任意序號的值都位於窗口空間內,而無法確定報文是重複的報文,還是新報文。即,假如下一個要接收的序號爲100,此時收到一個序號爲90的報文,其在窗口範圍內,將判斷其實之前的重複報文,還是新報文。
100
|
0 |----------------------------| 4G
|
90
TCP在判斷一個報文的新舊時,依據此報文的序號是否位於窗口左側開始的231範圍(整個32bit空間的一半,2G)的空間內。所以發送端窗口的左側一定不能夠比接收端窗口右側大231,否則,發送的數據報文可能被當做重複報文而丟棄。同樣,發送端窗口的右側也不能夠比接收端窗口的左側大231,否則,如果最右側的報文先到達接收端,將判定爲重複報文而丟棄。故,TCP最大的窗口值應小於231的值,WSopt選項中的擴展係數最大爲14,即:
must be less than 2**31, or
max window < 2**30
發送WSopt
如下爲初始窗口選擇函數tcp_select_initial_window,窗口的鉗制值window_clamp最大爲U16_MAX << TCP_MAX_WSCALE,即0x3fffc000,爲1G-16K。
void tcp_select_initial_window(const struct sock *sk, int __space, __u32 mss,
__u32 *rcv_wnd, __u32 *window_clamp,
int wscale_ok, __u8 *rcv_wscale, __u32 init_rcv_wnd)
{
unsigned int space = (__space < 0 ? 0 : __space);
if (*window_clamp == 0) /* If no clamp set the clamp to the max possible scaled window */
(*window_clamp) = (U16_MAX << TCP_MAX_WSCALE);
space = min(*window_clamp, space);
/* Quantize space offering to a multiple of mss if possible. */
if (space > mss)
space = rounddown(space, mss);
if (sock_net(sk)->ipv4.sysctl_tcp_workaround_signed_windows)
(*rcv_wnd) = min(space, MAX_TCP_WINDOW);
else
(*rcv_wnd) = min_t(u32, space, U16_MAX);
if (init_rcv_wnd)
*rcv_wnd = min(*rcv_wnd, init_rcv_wnd * mss);
以上可見TCP標準頭部的窗口字段rcv_wnd最大值爲U16_MAX,更大的窗口值就需要WSopt選項來實現。前提是如果對端支持WSopt選項,即wscale_ok爲真,首先計算最大可用的窗口空間,取值space、sysctl_tcp_rmem[2]和sysctl_rmem_max三者之間的最大值,之後,確定此值不超過窗口鉗制值,否則使用鉗制值。
*rcv_wscale = 0;
if (wscale_ok) {
/* Set window scaling on max possible window */
space = max_t(u32, space, sock_net(sk)->ipv4.sysctl_tcp_rmem[2]);
space = max_t(u32, space, sysctl_rmem_max);
space = min_t(u32, space, *window_clamp);
*rcv_wscale = clamp_t(int, ilog2(space) - 15, 0, TCP_MAX_WSCALE);
如下發送函數__tcp_transmit_skb可見,對於設置有SYN標誌的TCP報文,窗口值不做擴展。但是對於設置有SYN標誌的報文,使用tcp_syn_options添加TCP選項字段,否則,由函數tcp_established_options添加選項,在前者中將添加WSopt選項;而在後者連接建立狀態中,可添加MD5、timestamp和sack選項。
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
else
tcp_options_size = tcp_established_options(sk, skb, &opts, &md5);
tcp_options_write((__be32 *)(th + 1), tp, &opts);
skb_shinfo(skb)->gso_type = sk->sk_gso_type;
if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {
th->window = htons(tcp_select_window(sk));
tcp_ecn_send(sk, skb, th, tcp_header_size);
} else {
/* RFC1323: The window in SYN & SYN/ACK segments is never scaled.
*/
th->window = htons(min(tp->rcv_wnd, 65535U));
}
對於未設置SYN標誌的報文,使用函數tcp_select_window選擇窗口值,如下所示,其將選擇的窗口值右移rcv_wscale位。
static u16 tcp_select_window(struct sock *sk)
{
/* RFC1323 scaling applied */
new_win >>= tp->rx_opt.rcv_wscale;
如下tcp_syn_options函數,如果系統開啓了TCP窗口擴張功能,發送WSopt選項數據,擴張係數爲在以上函數tcp_select_initial_window中確定的值。
static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
struct tcp_out_options *opts, struct tcp_md5sig_key **md5)
{
if (likely(sock_net(sk)->ipv4.sysctl_tcp_window_scaling)) {
opts->ws = tp->rx_opt.rcv_wscale;
opts->options |= OPTION_WSCALE;
remaining -= TCPOLEN_WSCALE_ALIGNED;
}
接收WSopt
如果接收到的TCP報文頭部長度值大於TCP頭部結構tcphdr的長度,表明包含TCP選項數據。如下函數tcp_parse_options負責解析選項,這裏看一下WSopt數據,只有在報文的TCP頭部設置了SYN標誌,並且當前狀態不是連接建立狀態,而且本地開啓了窗口擴張功能(sysctl_tcp_window_scaling默認爲1)的情況下,才需要解析WSopt數據。
void tcp_parse_options(const struct net *net, const struct sk_buff *skb,
struct tcp_options_received *opt_rx, int estab,...)
{
const struct tcphdr *th = tcp_hdr(skb);
int length = (th->doff * 4) - sizeof(struct tcphdr);
while (length > 0) {
switch (opcode) {
default:
switch (opcode) {
case TCPOPT_WINDOW:
if (opsize == TCPOLEN_WINDOW && th->syn &&
!estab && net->ipv4.sysctl_tcp_window_scaling) {
__u8 snd_wscale = *(__u8 *)ptr;
opt_rx->wscale_ok = 1;
if (snd_wscale > TCP_MAX_WSCALE) {
net_info_ratelimited("%s: Illegal window scaling value %d > %u received\n",
__func__, snd_wscale, TCP_MAX_WSCALE);
snd_wscale = TCP_MAX_WSCALE;
}
opt_rx->snd_wscale = snd_wscale;
}
break;
如果其中通告的擴張係數值大於最大值TCP_MAX_WSCALE(14),打印警告信息,並將擴張係數限定在最大值TCP_MAX_WSCALE。另外,這裏將wscale_ok設置爲真,表明對端支持WSopt選項。
對於TCP的客戶端,如果接收到的回覆報文SYN+ACK中沒有包含WSopt選項數據,表明服務端不支持窗口擴張,此情況下,本地也將禁止窗口擴張功能,將snd_wscale和rcv_wscale清零。另外,RFC1323中規定SYN與SYNACK報文中的窗口值不擴張,snd_wnd直接取值TCP頭部中的16bit窗口值。
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
tcp_parse_options(sock_net(sk), skb, &tp->rx_opt, 0, &foc);
if (th->ack) {
...
/* RFC1323: The window in SYN & SYN/ACK segments is never scaled.
*/
tp->snd_wnd = ntohs(th->window);
if (!tp->rx_opt.wscale_ok) {
tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0;
tp->window_clamp = min(tp->window_clamp, 65535U);
}
在服務端創建子套接口時,如果客戶端不支持窗口擴張,將子套接口的snd_wscale和rcv_wscale清零,如下tcp_create_openreq_child函數所示。TCP發送窗口snd_wnd的計算爲TCP頭部的16bit的窗口值左移擴張係數snd_wscale。
struct sock *tcp_create_openreq_child(const struct sock *sk, struct request_sock *req, struct sk_buff *skb)
{
newtp->rx_opt.wscale_ok = ireq->wscale_ok;
if (newtp->rx_opt.wscale_ok) {
newtp->rx_opt.snd_wscale = ireq->snd_wscale;
newtp->rx_opt.rcv_wscale = ireq->rcv_wscale;
} else {
newtp->rx_opt.snd_wscale = newtp->rx_opt.rcv_wscale = 0;
newtp->window_clamp = min(newtp->window_clamp, 65535U);
}
newtp->snd_wnd = ntohs(tcp_hdr(skb)->window) << newtp->rx_opt.snd_wscale;
newtp->max_window = newtp->snd_wnd;
內核版本 5.0