В дальнейшем мы будем следовать наиболее типичному пути кода и игнорировать проблемы, возникающие из-за потери пакетов, повторной передачи и использования нетипичных функций, таких как быстрое открытие TCP (TFO в комментариях к коду).
Вызов accept обрабатывается intet_csk_accept
, который вызывает reqsk_queue_remove
, чтобы получить сокет из очереди приема &icsk->icsk_accept_queue
из прослушивающего сокета:
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
struct request_sock *req;
struct sock *newsk;
int error;
lock_sock(sk);
[...]
req = reqsk_queue_remove(queue, sk);
newsk = req->sk;
[...]
return newsk;
[...]
}
В reqsk_queue_remove
он использует rskq_accept_head
и rskq_accept_tail
, чтобы вытащить сокет из очереди и вызвать sk_acceptq_removed
:
static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue,
struct sock *parent)
{
struct request_sock *req;
spin_lock_bh(&queue->rskq_lock);
req = queue->rskq_accept_head;
if (req) {
sk_acceptq_removed(parent);
WRITE_ONCE(queue->rskq_accept_head, req->dl_next);
if (queue->rskq_accept_head == NULL)
queue->rskq_accept_tail = NULL;
}
spin_unlock_bh(&queue->rskq_lock);
return req;
}
И sk_acceptq_removed
сокращает длину очереди сокетов, ожидающих приема в sk_ack_backlog
:
static inline void sk_acceptq_removed(struct sock *sk)
{
WRITE_ONCE(sk->sk_ack_backlog, sk->sk_ack_backlog - 1);
}
Это, я думаю, полностью понимает спрашивающий. Теперь давайте посмотрим, что происходит, когда получено SYN, и когда приходит последний ACK 3WH.
Сначала получение SYN. Опять же, давайте предположим, что файлы cookie TFO и SYN не используются, и посмотрим на наиболее распространенный путь (по крайней мере, не при наличии SYN-потока).
SYN обрабатывается в tcp_conn_request
, где запрос на соединение (не полноценный сокет) сохраняется (мы скоро увидим), вызывая inet_csk_reqsk_queue_hash_add
, а затем вызывая send_synack
для ответа на SYN:
int tcp_conn_request(struct request_sock_ops *rsk_ops,
const struct tcp_request_sock_ops *af_ops,
struct sock *sk, struct sk_buff *skb)
{
[...]
if (!want_cookie)
inet_csk_reqsk_queue_hash_add(sk, req,
tcp_timeout_init((struct sock *)req));
af_ops->send_synack(sk, dst, &fl, req, &foc,
!want_cookie ? TCP_SYNACK_NORMAL :
TCP_SYNACK_COOKIE);
[...]
return 0;
[...]
}
inet_csk_reqsk_queue_hash_add
звонки reqsk_queue_hash_req
и inet_csk_reqsk_queue_added
для сохранения запроса.
void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
unsigned long timeout)
{
reqsk_queue_hash_req(req, timeout);
inet_csk_reqsk_queue_added(sk);
}
reqsk_queue_hash_req
помещает запрос в eha sh.
static void reqsk_queue_hash_req(struct request_sock *req,
unsigned long timeout)
{
[...]
inet_ehash_insert(req_to_sk(req), NULL);
[...]
}
И затем inet_csk_reqsk_queue_added
вызывает reqsk_queue_added
с icsk_accept_queue
:
static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}
Что увеличивает qlen
(не sk_ack_backlog
):
static inline void reqsk_queue_added(struct request_sock_queue *queue)
{
atomic_inc(&queue->young);
atomic_inc(&queue->qlen);
}
eha sh - это то место, где все Сокеты ESTABLISHED и TIMEWAIT были сохранены, а в последнее время в них хранится «очередь» SYN.
Обратите внимание, что на самом деле нет никакой цели хранить поступившие запросы на соединение в надлежащей очереди. Их порядок не имеет значения (последние ACK могут поступать в любом порядке), и, перемещая их из прослушивающего сокета, нет необходимости блокировать прослушивающий сокет для обработки последнего ACK.
См. этот коммит для кода, который произвел это изменение.
Наконец, мы можем наблюдать, как запрос удаляется из eha sh и добавляется как полный сокет в очередь приема.
Последний ACK 3WH обрабатывается tcp_check_req
, который создает полный дочерний сокет, а затем вызывает inet_csk_complete_hashdance
:
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
bool fastopen, bool *req_stolen)
{
[...]
/* 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.
*/
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL,
req, &own_req);
[...]
return inet_csk_complete_hashdance(sk, child, req, own_req);
[...]
}
Затем inet_csk_complete_hashdance
вызывает inet_csk_reqsk_queue_drop
и reqsk_queue_removed
на request и inet_csk_reqsk_queue_add
для дочернего элемента:
struct sock *inet_csk_complete_hashdance(struct sock *sk, struct sock *child,
struct request_sock *req, bool own_req)
{
if (own_req) {
inet_csk_reqsk_queue_drop(sk, req);
reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req);
if (inet_csk_reqsk_queue_add(sk, req, child))
return child;
}
[...]
}
inet_csk_reqsk_queue_drop
вызывает reqsk_queue_unlink
, который удаляет запрос из eha sh, и reqsk_queue_removed
, который уменьшает qlen:
void inet_csk_reqsk_queue_drop(struct sock *sk, struct request_sock *req)
{
if (reqsk_queue_unlink(req)) {
reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req);
reqsk_put(req);
}
}
Наконец, inet_csk_reqsk_queue_add
добавляет полный сокет в очередь приема.
struct sock *inet_csk_reqsk_queue_add(struct sock *sk,
struct request_sock *req,
struct sock *child)
{
struct request_sock_queue *queue = &inet_csk(sk)->icsk_accept_queue;
spin_lock(&queue->rskq_lock);
if (unlikely(sk->sk_state != TCP_LISTEN)) {
inet_child_forget(sk, req, child);
child = NULL;
} else {
req->sk = child;
req->dl_next = NULL;
if (queue->rskq_accept_head == NULL)
WRITE_ONCE(queue->rskq_accept_head, req);
else
queue->rskq_accept_tail->dl_next = req;
queue->rskq_accept_tail = req;
sk_acceptq_added(sk);
}
spin_unlock(&queue->rskq_lock);
return child;
}
TL; DR он находится в eha sh, а количество таких SYN составляет qlen
(а не sk_ack_backlog
, который содержит количество сокетов в очереди приема).