syn重发_SYN Queue和Accept Queue

一个Queue还是两个Queue

IPC/IP栈对一个处于LISTEN状态的socket有两种实现backlog queue的方式。

一个Queue

一个Queue能够包含两种状态的Connections:

SYN RECEIVED

ESTABLISHED

只有ESTABLISHED状态的Connection才能被应用通过syscall accept()方法获取到。

因此,该队列的长度由listen()的参数backlog决定。

一个SYN Queue 和 一个Accept Queue

这种情况下,处于SYN RECEIVED状态的connection将会被添加到SYN queue,然后当连接状态变成ESTABLISHED时再把其移动到Accept queue中。

因此,Accept queue只存储等待accept()调用的connection。

不像前一个示例,linsten() syscall的参数backlog此时决定了Accept queue的大小。

BSD

BSD选择了一个queue作为了它的实现(尽管事实上它内部使用了两个queue)。

当queue满时,它会简单的丢弃SYN包,并让客户端重试,而不会向接收到的SYN包发送SYN/ACK包。

Linux

从Linux 2.2起,Linux使用了两个queue:

backlog表示Accept queue的最大长度

/proc/sys/net/ipv4/tcp_max_syn_backlog表示SYN queue的最大长度;新内核使用的是/proc/sys/net/core/somaxconn(也叫net.core.somaxconn)

从客户端的角度来看,一个connection在接收到SYN/ACK包后,其状态就成为了ESTABLISHED。

当SYN Queue满了

当接收到一个SYN包但SYN Queue满时:

若设置 net.ipv4.tcp_syncookies = 0 ,则直接丢弃当前 SYN 包;

若设置 net.ipv4.tcp_syncookies = 1 ,则令 want_cookie = 1 继续后面的处理;若 Accept queue 已满,并且 qlen_young 的值大于 1 ,则直接丢弃当前 SYN 包。req_young_len是SYN Queue中还没有重新发送的SYN/ACK包的连接数。

若 Accept queue 未满,或者 qlen_young 的值未大于 1 ,则输出 “possible SYN flooding on port %d. Sending cookies.\n”,生成 syncookie 并在 SYN,ACK 中带上。

以下源码摘自linux-2.6.32版本

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)

{

...

#ifdef CONFIG_SYN_COOKIES

int want_cookie = 0;

#else

#define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */

#endif

...

/* TW buckets are converted to open requests without

* limitations, they conserve resources and peer is

* evidently real one.

*/

// 判定 SYN queue 是否已满

if (inet_csk_reqsk_queue_is_full(sk) && !isn) {

#ifdef CONFIG_SYN_COOKIES

if (sysctl_tcp_syncookies) { // SYN queue 已满,并且设置了 net.ipv4.tcp_syncookies = 1 ,则设置 want_cookie = 1 以便后续处理

want_cookie = 1;

} else

#endif

goto drop; // 否则,直接丢弃当前 SYN 包

}

/* Accept backlog is full. If we have already queued enough

* of warm entries in syn queue, drop request. It is better than

* clogging syn queue with openreqs with exponentially increasing

* timeout.

*/

// 若此时 accept queue 也已满,并且 qlen_young 的值大于 1(即保存在 SYN queue 中未进行 SYN,ACK 重传的连接超过 1 个)

// 则直接丢弃当前 SYN 包(相当于针对 SYN 进行了速率限制)

if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)

goto drop;

...

// 若 accept queue 未满,或者 qlen_young 的值未大于 1

if (want_cookie) {

#ifdef CONFIG_SYN_COOKIES

syn_flood_warning(skb); // 输出 "possible SYN flooding on port %d. Sending cookies.\n"

req->cookie_ts = tmp_opt.tstamp_ok; // 为当前 socket 设置启用 cookie 标识

#endif

// 生成 syncookie

isn = cookie_v4_init_sequence(sk, skb, &req->mss);

} else if (!isn) {

...

}

// 保存 syncookie 值

tcp_rsk(req)->snt_isn = isn;

// 回复 SYN,ACK ,若之前设置了 net.ipv4.tcp_syncookies = 1 则会释放对应的 socket 结构

if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)

goto drop_and_free;

// 启动 SYN,ACK 重传定时器

inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);

return 0;

drop_and_release:

dst_release(dst);

drop_and_free:

reqsk_free(req);

drop:

return 0;

}

当Accept Queue满了

当接收到一个ACK包但Accept queue满了时:

若设置 tcp_abort_on_overflow = 1 ,则 TCP 协议栈回复 RST 包,并直接从 SYN queue 中删除该连接信息,表示废弃这个握手过程和这个连接;

若设置 tcp_abort_on_overflow = 0 ,则 TCP 协议栈将该连接标记为 acked ,但仍保留在 SYN queue 中,并启动 timer 以便重发 SYN,ACK 包;当 SYN,ACK 的重传次数超过 net.ipv4.tcp_synack_retries 设置的值时,再将该连接从 SYN queue 中删除;

通常把tcp_abort_on_overflow设置为0可以利于应对突发流量,当 TCP 全连接队列满导致服务器丢掉了 ACK,与此同时,客户端的连接状态却是 ESTABLISHED,进程就在建立好的连接上发送请求。只要服务器没有为请求回复 ACK,请求就会被多次重发。如果服务器上的进程只是短暂的繁忙造成 accept 队列满,那么当 TCP 全连接队列有空位时,再次接收到的请求报文由于含有 ACK,仍然会触发服务器端成功建立连接。

tcp_abort_on_overflow 参数位置位于/proc/sys/net/ipv4/tcp_abort_on_overflow

但是,如果Accept queue满时,内核也会对SYN包接收速率强加一个限制:如果太多的SYN包被接受,其中将有一些会被抛弃。

此时,由客户端决定是否重发SYN包。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44/*

*Process an incoming packet for SYN_RECV sockets represented

*as a request_sock.

*/

struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,

struct request_sock *req,

struct request_sock **prev)

{

...

/* OK, ACK is valid, create big socket and

* feed this segment to it. It will repeat all

* the tests. THIS SEGMENT MUST MOVE SOCKET TO

* ESTABLISHED STATE. If it will be dropped after

* socket is created, wait for troubles.

*/

// 调用 net/ipv4/tcp_ipv4.c 中的 tcp_v4_syn_recv_sock 函数

// 判定 accept queue 是否已经满,若已满,则返回的 child 为 NULL

child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);

if (child == NULL)

goto listen_overflow;

// 在 accept queue 未满的情况下,将 ESTABLISHED 连接从 SYN queue 搬移到 accept queue 中

inet_csk_reqsk_queue_unlink(sk, req, prev);

inet_csk_reqsk_queue_removed(sk, req);

inet_csk_reqsk_queue_add(sk, req, child);

return child;

listen_overflow:

// 若 accept queue 已满,但设置的是 net.ipv4.tcp_abort_on_overflow = 0

if (!sysctl_tcp_abort_on_overflow) {

inet_rsk(req)->acked = 1; // 则只标记为 acked ,直接返回,相当于忽略当前 ACK

return NULL;

}

embryonic_reset:

// 若 accept queue 已满,但设置的是 net.ipv4.tcp_abort_on_overflow = 1

NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS); // 变更统计信息

if (!(flg & TCP_FLAG_RST))

req->rsk_ops->send_reset(sk, skb); // 发送 RST

inet_csk_reqsk_queue_drop(sk, req, prev); // 直接从 SYN queue 中删除该连接信息

return NULL;

}

使用单个Queue的缺点

导致单个queue满的两个主要因素:

应用调用accept()不够快,使得建立ESTABLISHED的connection塞满了queue。

服务端与客户端之间的RTT(round-trip time,往返延时)比较大,处于SYN RECEIVED状态的connection塞满了queue。

而使用了两个queue,则SYN queue可以看出要传输的ACK包,Accept queue可以看出应用需要处理的连接数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值