- Today
- Total
Byeo
socket system call 3 (tcp_v4_init_sock) 본문
이전 포스트: https://byeo.tistory.com/entry/socket-system-call-2
socket system call 2
이전 포스트: https://byeo.tistory.com/entry/socket-system-call socket system call 1 int socket(int domain, int type, int protocol); socket 시스템 콜의 동작을 한 번 들여다 봅니다! https://man7.org/linux/man-pages/man2/socket.2.html linu
byeo.tistory.com
inet_create까지의 발걸음
6. tcp_v4_init_sock
// net/ipv4/tcp_ipv4.c/tcp_v4_init_sock() :2236
static int tcp_v4_init_sock(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
tcp_init_sock(sk);
icsk->icsk_af_ops = &ipv4_specific;
#ifdef CONFIG_TCP_MD5SIG
tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific;
#endif
return 0;
}
inet_create에서 sk->sk_prot->init(sk);가 연결된 함수포인터는 tcp_v4_init_sock이었습니다. 이 함수는 살펴보면 별 일은 안합니다.
가장 먼저, tcp_init_sock을 호출하네요. 이 함수는 net/ipv4/tcp.c 안에 있습니다.
6-1. tcp_init_sock(sk)
이 함수는 tcp_ipv6에서도 호출하는 함수로, 주석에 적혀있듯 Address-family에 independent한 초기화 함수입니다.
// net/ipv4/tcp.c/tcp_init_sock() :411
/* Address-family independent initialization for a tcp_sock.
*
* NOTE: A lot of things set to zero explicitly by call to
* sk_alloc() so need not be done here.
*/
void tcp_init_sock(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
tp->out_of_order_queue = RB_ROOT;
sk->tcp_rtx_queue = RB_ROOT;
tcp_init_xmit_timers(sk);
INIT_LIST_HEAD(&tp->tsq_node);
INIT_LIST_HEAD(&tp->tsorted_sent_queue);
icsk->icsk_rto = TCP_TIMEOUT_INIT;
icsk->icsk_rto_min = TCP_RTO_MIN;
icsk->icsk_delack_max = TCP_DELACK_MAX;
tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT);
minmax_reset(&tp->rtt_min, tcp_jiffies32, ~0U);
/* So many TCP implementations out there (incorrectly) count the
* initial SYN frame in their delayed-ACK and congestion control
* algorithms that we must have the following bandaid to talk
* efficiently to them. -DaveM
*/
tp->snd_cwnd = TCP_INIT_CWND;
/* There's a bubble in the pipe until at least the first ACK. */
tp->app_limited = ~0U;
/* See draft-stevens-tcpca-spec-01 for discussion of the
* initialization of these values.
*/
tp->snd_ssthresh = TCP_INFINITE_SSTHRESH;
tp->snd_cwnd_clamp = ~0;
tp->mss_cache = TCP_MSS_DEFAULT;
tp->reordering = sock_net(sk)->ipv4.sysctl_tcp_reordering;
tcp_assign_congestion_control(sk);
tp->tsoffset = 0;
tp->rack.reo_wnd_steps = 1;
sk->sk_write_space = sk_stream_write_space;
sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
icsk->icsk_sync_mss = tcp_sync_mss;
WRITE_ONCE(sk->sk_sndbuf, sock_net(sk)->ipv4.sysctl_tcp_wmem[1]);
WRITE_ONCE(sk->sk_rcvbuf, sock_net(sk)->ipv4.sysctl_tcp_rmem[1]);
sk_sockets_allocated_inc(sk);
sk->sk_route_forced_caps = NETIF_F_GSO;
}
EXPORT_SYMBOL(tcp_init_sock);
- tp->out_of_order_queue = RB_ROOT
- sk->tcp_rtx_queue = RB_ROOT
RB_ROOT는 red-black tree 구조체를 나타냅니다.
(1) tcp_timer init
// net/ipv4/tcp_timer.c/tcp_init_xmit_timers() :787
void tcp_init_xmit_timers(struct sock *sk)
{
inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,
&tcp_keepalive_timer);
hrtimer_init(&tcp_sk(sk)->pacing_timer, CLOCK_MONOTONIC,
HRTIMER_MODE_ABS_PINNED_SOFT);
tcp_sk(sk)->pacing_timer.function = tcp_pace_kick;
hrtimer_init(&tcp_sk(sk)->compressed_ack_timer, CLOCK_MONOTONIC,
HRTIMER_MODE_REL_PINNED_SOFT);
tcp_sk(sk)->compressed_ack_timer.function = tcp_compressed_ack_kick;
}
tcp하면 timer를 절대 빼놓을 수 없죠. Packet 송신 후에 상대로부터 ACK를 일정시간 내에 받지 못했다면 신뢰성있는 송신을 보장하기 위해 조치를 취해야 합니다. 이 때 timer가 필요하죠.
이 함수는 그 timer를 초기화 하는 함수로 보입니다.
// net/ipv4/inet_connection_sock.c/inet_csk_init_xmit_timers() :558
void inet_csk_init_xmit_timers(struct sock *sk,
void (*retransmit_handler)(struct timer_list *t),
void (*delack_handler)(struct timer_list *t),
void (*keepalive_handler)(struct timer_list *t))
{
struct inet_connection_sock *icsk = inet_csk(sk);
timer_setup(&icsk->icsk_retransmit_timer, retransmit_handler, 0);
timer_setup(&icsk->icsk_delack_timer, delack_handler, 0);
timer_setup(&sk->sk_timer, keepalive_handler, 0);
icsk->icsk_pending = icsk->icsk_ack.pending = 0;
}
초기화하는 timer는 3개로 보입니다.
- icsk_retransmit_timer (재전송 타이머)
- icsk_delack_timer (delayed-ACK 타이머)
- sk_timer (tcp keepalive 타이머, packet이 흐르지 않더라도 유효 통신으로 간주할 시간)
각 timer에는 함수포인터가 연결됩니다. 타이머의 동작 원리는 나중에 다룰 때가 있으면 다시 돌아오겠습니다.. 아마 TCP CC를 볼 즈음에 돌아오겠네요.
(2) tcp_init_sock: 값 초기화
// net/ipv4/tcp.c/tcp_init_sock() :411
/* Address-family independent initialization for a tcp_sock.
*
* NOTE: A lot of things set to zero explicitly by call to
* sk_alloc() so need not be done here.
*/
void tcp_init_sock(struct sock *sk)
{
...
icsk->icsk_rto = TCP_TIMEOUT_INIT;
icsk->icsk_rto_min = TCP_RTO_MIN;
icsk->icsk_delack_max = TCP_DELACK_MAX;
tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT);
minmax_reset(&tp->rtt_min, tcp_jiffies32, ~0U);
timer와 관련된 몇 가지 값을 초기화합니다. TCP_TIMEOUT_INIT, TCP_RTO_MIN, TCP_DELACK_MAX는 다음에 정의되어 있습니다.
// include/net/tcp.h:131
#define TCP_DELACK_MAX ((unsigned)(HZ/5)) /* maximal time to delay before sending an ACK */
#if HZ >= 100
#define TCP_DELACK_MIN ((unsigned)(HZ/25)) /* minimal time to delay before sending an ACK */
#define TCP_ATO_MIN ((unsigned)(HZ/25))
#else
...
#endif
#define TCP_RTO_MAX ((unsigned)(120*HZ))
#define TCP_RTO_MIN ((unsigned)(HZ/5))
#define TCP_TIMEOUT_MIN (2U) /* Min timeout for TCP timers in jiffies */
#define TCP_TIMEOUT_INIT ((unsigned)(1*HZ)) /* RFC6298 2.1 initial RTO value */
HZ와 관련하여 간단히 조사해본 결과, Ubuntu 22.04는 CONFIG_HZ=1000에 의해 HZ는 1000으로 설정됩니다. 그리고 jiffies라는 변수가 존재하며, 이 값은 1초에 HZ만큼 증가합니다. 1
따라서 TCP_TIMEOUT_INIT은 1초임을 의미하네요. 실제로 RFC 6298 2.1에도 그렇게 쓰여져 있습니다. 역사적으로는 3초였다가 줄었다고 해요.
(2.1) Until a round-trip time (RTT) measurement has been made for a segment sent between the sender and receiver, the sender SHOULD set RTO <- 1 second, though the "backing off" on repeated retransmission discussed in (5.5) still applies. Note that the previous version of this document used an initial RTO of 3 seconds [PA00]. A TCP implementation MAY still use this value (or any other value > 1 second). This change in the lower bound on the initial RTO is discussed in further detail in Appendix A.
mdev_us는 RTT의 편차 중간값을 보관하는 변수입니다. jiffies_to_usecs(TCP_TIMEOUT_INIT)은 함수명으로 바로 추정할 수 있겠죠? 1초를 microsecond로 환산한 1,000,000이 저장될 것 같네요.
(3) tcp_init_sock: Congestion Control-related variables 초기
// net/ipv4/tcp.c/tcp_init_sock() :411
/* Address-family independent initialization for a tcp_sock.
*
* NOTE: A lot of things set to zero explicitly by call to
* sk_alloc() so need not be done here.
*/
void tcp_init_sock(struct sock *sk)
{
...
/* So many TCP implementations out there (incorrectly) count the
* initial SYN frame in their delayed-ACK and congestion control
* algorithms that we must have the following bandaid to talk
* efficiently to them. -DaveM
*/
tp->snd_cwnd = TCP_INIT_CWND;
/* There's a bubble in the pipe until at least the first ACK. */
tp->app_limited = ~0U;
/* See draft-stevens-tcpca-spec-01 for discussion of the
* initialization of these values.
*/
tp->snd_ssthresh = TCP_INFINITE_SSTHRESH;
tp->snd_cwnd_clamp = ~0;
tp->mss_cache = TCP_MSS_DEFAULT;
tp->reordering = sock_net(sk)->ipv4.sysctl_tcp_reordering;
tcp_assign_congestion_control(sk);
tp->tsoffset = 0;
tp->rack.reo_wnd_steps = 1;
학부 수업에서 등장하는 congestion control과 관련된 변수들이 초기화되는 모습입니다.
// include/net/tcp.h
#define TCP_MSS_DEFAULT 536U /* IPv4 (RFC1122, RFC2581) */
...
/* TCP initial congestion window as per rfc6928 */
#define TCP_INIT_CWND 10
...
#define TCP_INFINITE_SSTHRESH 0x7fffffff
TCP Congestion window size 초기화는 10으로 이루어집니다.
그리고 초기 ssthresh (slow start threadshold)는 최대값으로 초기화되는 것을 볼 수 있습니다.
TCP의 초기 mss size는 536 bytes로 설정됩니다. IPv6의 경우 1,220 bytes입니다. Wikipedia , RFC1122 2 3
여기서 드는 질문 하나
우리는 일반적으로 MTU가 1500 bytes까지라고 알고 있는데, default MSS 536 bytes는 너무 작은 것 아닌가요?
There have been some assumptions made about using other than the default size for datagrams with some unfortunate results.
HOSTS MUST NOT SEND DATAGRAMS LARGER THAN 576 OCTETS UNLESS THEY HAVE SPECIFIC KNOWLEDGE THAT THE DESTINATION HOST IS PREPARED TO ACCEPT LARGER DATAGRAMS.
This is a long established rule.
To resolve the ambiguity in the TCP Maximum Segment Size option definition the following rule is established:
THE TCP MAXIMUM SEGMENT SIZE IS THE IP MAXIMUM DATAGRAM SIZE MINUS FORTY.
The default IP Maximum Datagram Size is 576.
The default TCP Maximum Segment Size is 536.
역사적으로 576 bytes가 기본 datagram size로 책정이 된 듯 합니다. 그래서 상대방이 576 bytes 이상을 받을 수 있으리라고 보장할 수 없고, 40 bytes를 뺀 536 bytes가 default MSS라고 명시되어 있습니다.
대신, 3-way handshake를 실행할 때 (SYN을 보낼 때), host의 MSS가 536이 아니라면, 자신이 사용할 수 있는 MSS값을 TCP option에 기재하라고 명시되어 있습니다. 만약 이 값에대한 교환이 이뤄지지 않았다면, MSS를 536으로 간주합니다.
4.2.2.6 Maximum Segment Size Option:
RFC-793 Section 3.1 TCP MUST implement both sending and receiving the Maximum Segment Size option [TCP:4]. TCP SHOULD send an MSS (Maximum Segment Size) option in every SYN segment when its receive MSS differs from the default 536, and MAY send it always. If an MSS option is not received at connection setup, TCP MUST assume a default send MSS of 536 (576-40) [TCP:4].
다시 돌아와서..
tp->reordering은 namespace에 지정되어 있는 ipv4 sysctl관련 옵션 (tcp_reordering)을 넣어줍니다.
그리고 tcp_assign_congestion_control은 사용 가능한 tcp CC 알고리즘을 선택하여 부여합니다.
// net/ipv4/tcp_cong.c/tcp_assign_congestion_control() :156
/* Assign choice of congestion control. */
void tcp_assign_congestion_control(struct sock *sk)
{
struct net *net = sock_net(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcp_congestion_ops *ca;
rcu_read_lock();
ca = rcu_dereference(net->ipv4.tcp_congestion_control);
if (unlikely(!bpf_try_module_get(ca, ca->owner)))
ca = &tcp_reno;
icsk->icsk_ca_ops = ca;
rcu_read_unlock();
memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv));
if (ca->flags & TCP_CONG_NEEDS_ECN)
INET_ECN_xmit(sk);
else
INET_ECN_dontxmit(sk);
}
제가 작업하는 linux에서 살펴보니 cubic으로 설정되어 있네요.
// net/ipv4/tcp.c/tcp_init_sock() :411
/* Address-family independent initialization for a tcp_sock.
*
* NOTE: A lot of things set to zero explicitly by call to
* sk_alloc() so need not be done here.
*/
void tcp_init_sock(struct sock *sk)
{
...
sk->sk_write_space = sk_stream_write_space;
sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
icsk->icsk_sync_mss = tcp_sync_mss;
WRITE_ONCE(sk->sk_sndbuf, sock_net(sk)->ipv4.sysctl_tcp_wmem[1]);
WRITE_ONCE(sk->sk_rcvbuf, sock_net(sk)->ipv4.sysctl_tcp_rmem[1]);
sk_sockets_allocated_inc(sk);
sk->sk_route_forced_caps = NETIF_F_GSO;
이제 남은 코드에서는 몇 가지 함수포인터 (sk_stream_write_space, tcp_sync_mss)를 지정해주고, sock sk에 bufsize를 저장합니다.
여기서 가운데 값이 저 변수에 들어가겠군요.
tcp_v4_init_sock 에서...
/* NOTE: A lot of things set to zero explicitly by call to
* sk_alloc() so need not be done here.
*/
static int tcp_v4_init_sock(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
tcp_init_sock(sk);
icsk->icsk_af_ops = &ipv4_specific;
#ifdef CONFIG_TCP_MD5SIG
tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific;
#endif
return 0;
}
다시 돌아와서 마지막으로 icsk->icsk_af_ops = &ipv4_specific을 실행하고 tcp_v4_init_sock은 끝납니다.
값 체크해보기
우리가 따라온 흐름대로 값들이 들어가있는지 직접 출력해봅시다.
흐름도
이제 socket system call은 어느정도 다 온 것 같아요.
- https://www.oreilly.com/library/view/linux-device-drivers/9781785280009/4041820a-bbe4-4502-8ef9-d1913e133332.xhtml#:~:text=In%20other%20words%2C%20HZ%20represents,incremented%20HZ%20times%20every%20second. [본문으로]
- https://en.wikipedia.org/wiki/Maximum_segment_size [본문으로]
- https://datatracker.ietf.org/doc/html/rfc1122#page-85 [본문으로]
- https://www.rfc-editor.org/rfc/rfc879.html [본문으로]
- https://datatracker.ietf.org/doc/html/rfc1122#page-85 [본문으로]
'프로그래밍 (Programming) > 네트워크 스택' 카테고리의 다른 글
BSD Socket API (0) | 2024.04.17 |
---|---|
socket system call 4 (sock_map_fd) (0) | 2024.04.14 |
socket system call 2 (inet_create) (0) | 2024.03.31 |
socket system call 1 (syscall_socket ~ __sock_create) (0) | 2024.03.31 |
추가한 System Call을 함수 명으로 호출하기 (glibc) (0) | 2024.03.22 |