tcp connection setupの実現(一)
bindの実現:
まずいくつかの住所構造を紹介します.
struct sockaddrは実は基本的な住所構造に相当しています.他の構造は全部直接sockaddrに転送できます.例えば、sa_familyはPF_ですINETの時、sa_dataはポート番号とipアドレスを含んでいます.
一つ目はinet_ですhashinfoは主にtcpのbind hash bucket(tcpの初期化関数ではtcpuhashinfoを初期化します.そしてtcpurotでは構造体hにtcpuhashinfoを支払います.それに対応して私達はsockの中のsockuucommonドメインを通じてこの値にアクセスできます.)を分析します.
結合されたポートが0の場合、すなわち新しいポートを割り当てるためにケネルが必要である.
1まずシステムのport範囲を得る.
2 ランダムにポートを割り当てます.
3 bhashから現在ランダムに割り当てられているポートのチェーンを取得します.
4このチェーンを巡回します.このポートが既に使われているなら、ポート番号をプラスして、現在使用されていないポートを見つけるまで循環します.つまり、bhashに存在しないポーリングです.
5新しいinet_を作成しますbind_bucketをbhashに挿入します.
ポートが指定されている場合.
1 bhashから現在指定されているポートに対応するinet_をhash値(portから計算される)から取得する.bind_ブロック構造
2 bhashに存在するならば、このポートはすでに使用されていると説明したので、このポートがreuseに許可されているかどうかを判断する必要がある.
3が存在しないなら、上記の第5部と同じです.
中にはinet_connection私たちは以前にも紹介しました.icsk_を含みます.accept_queueのドメインは、このドメインはrequest uです.sock_queueタイプです.まずこの構造を見に行きます.
request_sock_queueはrequest_を表しています.sock列.tcpでは半接続列(SYNuRECVD状態)と接続完了列(established状態)があることを知っています.この二つはsynを受け取ったばかりで、握手が完成するのを待っています.一つはすでに3回握手しました.acceptが読みに来るのを待っています.
ここでは、synが分節して来るごとに、request_を新設します.sock構造は、listen_に加えられています.sockのrequest_そして、握手を3回したら、request_に入れます.sock_queueのrskq_accept_headとrskq_accept_テーラーの列の中で.acceptの時にこの列から直接読み取りました.
ここにはもう一つの概念があります.それはbacklogです.linuxでは、backlogのサイズはすでに接続された列のサイズを指します.半接続の列との和ではなく、半オープン接続の大きさはbacklogと同じぐらいです.
半開きの列の最大長さはbacklogによって計算されます.これを後で紹介します.
だから直接にinet_に来ました.listenの実現は、この関数は主にいくつかの合法的な判断を行い、その後inet_を呼び出します.csk_listen_startは関連ドメインを処理します.
その主な仕事は、新たにlisten socketを割り当てて、inet_に加入することです.connectionsockのicsk_accept_queu eドメインのlisten_optで.現在使用されているポートを判断します.最終的に戻ります.
まずいくつかの住所構造を紹介します.
struct sockaddrは実は基本的な住所構造に相当しています.他の構造は全部直接sockaddrに転送できます.例えば、sa_familyはPF_ですINETの時、sa_dataはポート番号とipアドレスを含んでいます.
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
次はsockaddr_です.inは、すべてのipv 4のアドレス構造を表しています.彼もsockaddrのサブクラスに相当します.struct sockaddr_in {
sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
ここにはカーネルの比較的新しい地質構造があります.storageでは、彼はすべてのタイプのインターフェース構造を収容することができます.例えば、ipv 4、ipv 6.は強制的に整列されており、sockaddrに比べて見られます.struct __kernel_sockaddr_storage {
unsigned short ss_family; /* address family */
/// .
char __data[_K_SS_MAXSIZE - sizeof(unsigned short)];
/* space to achieve desired size, */
/* _SS_MAXSIZE value minus size of ss_family */
} __attribute__ ((aligned(_K_SS_ALIGNSIZE))); /* force desired alignment */
次にいくつかのbindに関するデータ構造を見ます.一つ目はinet_ですhashinfoは主にtcpのbind hash bucket(tcpの初期化関数ではtcpuhashinfoを初期化します.そしてtcpurotでは構造体hにtcpuhashinfoを支払います.それに対応して私達はsockの中のsockuucommonドメインを通じてこの値にアクセスできます.)を分析します.
struct inet_hashinfo {
/* This is for sockets with full identity only. Sockets here will
* always be without wildcards and will have the following invariant:
*
* TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE
*
* TIME_WAIT sockets use a separate chain (twchain).
*/
/// .
struct inet_ehash_bucket *ehash;
rwlock_t *ehash_locks;
unsigned int ehash_size;
unsigned int ehash_locks_mask;
/* Ok, let's try this, I give up, we do need a local binding
* TCP hash as well as the others for fast bind/connect.
*/
/// . bhash hash , inet_bind_bucket, .
struct inet_bind_hashbucket *bhash;
unsigned int bhash_size;
/* Note : 4 bytes padding on 64 bit arches */
/* All sockets in TCP_LISTEN state will be in here. This is the only
* table where wildcard'd TCP sockets can exist. Hash function here
* is just local port number.
*/
///listening_hash listen socket.
struct hlist_head listening_hash[INET_LHTABLE_SIZE];
/* All the above members are written once at bootup and
* never written again _or_ are predominantly read-access.
*
* Now align to a new cache line as all the following members
* are often dirty.
*/
rwlock_t lhash_lock ____cacheline_aligned;
atomic_t lhash_users;
wait_queue_head_t lhash_wait;
struct kmem_cache *bind_bucket_cachep;
};
struct inet et et uehashbucketはTCPのすべてのtcp状態を管理します.ESTABLISHEDとTCP_CLOSE間のsocket.ここで注意してください.twchainはTIME_にあることを表しています.WAITのsocket.struct inet_ehash_bucket {
struct hlist_head chain;
struct hlist_head twchain;
};
inetbind_bucket構造は使用するポートごとの情報です.最終的にはbhashチェーンにリンクされます.struct inet_bind_bucket {
struct net *ib_net;
///
unsigned short port;
/// .
signed short fastreuse;
/// inet_bind_bucket .
struct hlist_node node;
/// socket
struct hlist_head owners;
};
最後の構造はtcp_です.彼はtcpにいますinitでは初期化され、tcp_initはinet_ですinitで初期化されました.そしてtcp_hashinfoはtcp_に割り当てられます.protoとsockのskprotドメイン
struct inet_hashinfo __cacheline_aligned tcp_hashinfo = {
.lhash_lock = __RW_LOCK_UNLOCKED(tcp_hashinfo.lhash_lock),
.lhash_users = ATOMIC_INIT(0),
.lhash_wait = __WAIT_QUEUE_HEAD_INITIALIZER(tcp_hashinfo.lhash_wait),
};
そしてビッドの実現を見にきて、ビッド対応のシステム呼び出しはsys_です.ビッド:
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed;
/// fd socket, .
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
/// .
err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);
if (err >= 0) {
err = security_socket_bind(sock,
(struct sockaddr *)&address,
addrlen);
if (!err)
/// inet_bind .
err = sock->ops->bind(sock,
(struct sockaddr *)
&address, addrlen);
}
/// socket file .
fput_light(sock->file, fput_needed);
}
return err;
}
sockfd_lookup_lightは主にfd対応のsocketを検索します.static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
struct file *file;
struct socket *sock;
*err = -EBADF;
/// fd file
file = fget_light(fd, fput_needed);
if (file) {
/// sock_map_fd sock_attach_fd file private socket, socket.
sock = sock_from_file(file, err);
if (sock)
return sock;
fput_light(file, *fput_needed);
}
return NULL;
}
そしてinet_を見に来ましたbindの実現.int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
/// . socket inet_sock.
struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
struct sock *sk = sock->sk;
struct inet_sock *inet = inet_sk(sk);
unsigned short snum;
int chk_addr_ret;
int err;
/* If the socket has its own bind function then use it. (RAW) */
if (sk->sk_prot->bind) {
err = sk->sk_prot->bind(sk, uaddr, addr_len);
goto out;
}
err = -EINVAL;
if (addr_len < sizeof(struct sockaddr_in))
goto out;
/// , .
chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);
err = -EADDRNOTAVAIL;
/// .
if (!sysctl_ip_nonlocal_bind &&
!inet->freebind &&
addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
chk_addr_ret != RTN_LOCAL &&
chk_addr_ret != RTN_MULTICAST &&
chk_addr_ret != RTN_BROADCAST)
goto out;
/// .
snum = ntohs(addr->sin_port);
err = -EACCES;
/// prot_sock(1024) root . .capable .
if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
goto out;
/* We keep a pair of addresses. rcv_saddr is the one
* used by hash lookups, and saddr is used for transmit.
*
* In the BSD API these are the same except where it
* would be illegal to use them (multicast/broadcast) in
* which case the sending device address is used.
*/
lock_sock(sk);
/* Check these errors (active socket, double bind). */
err = -EINVAL;
/// close. close , socket bind . num raw socket 0
if (sk->sk_state != TCP_CLOSE || inet->num)
goto out_release_sock;
/// .rcv_saddr hash , saddr ip (ip ).
inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;
/// , saddr.
if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
inet->saddr = 0; /* Use device */
/// get_port , . get_port tcp , inet_csk_get_port, .
if (sk->sk_prot->get_port(sk, snum)) {
inet->saddr = inet->rcv_saddr = 0;
err = -EADDRINUSE;
goto out_release_sock;
}
/// . .
if (inet->rcv_saddr)
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
if (snum)
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
///
inet->sport = htons(inet->num);
/// , 0
inet->daddr = 0;
inet->dport = 0;
sk_dst_reset(sk);
err = 0;
out_release_sock:
release_sock(sk);
out:
return err;
}
ここでまずinet_を紹介します.csk_ゲットするportの流れ結合されたポートが0の場合、すなわち新しいポートを割り当てるためにケネルが必要である.
1まずシステムのport範囲を得る.
2 ランダムにポートを割り当てます.
3 bhashから現在ランダムに割り当てられているポートのチェーンを取得します.
4このチェーンを巡回します.このポートが既に使われているなら、ポート番号をプラスして、現在使用されていないポートを見つけるまで循環します.つまり、bhashに存在しないポーリングです.
5新しいinet_を作成しますbind_bucketをbhashに挿入します.
ポートが指定されている場合.
1 bhashから現在指定されているポートに対応するinet_をhash値(portから計算される)から取得する.bind_ブロック構造
2 bhashに存在するならば、このポートはすでに使用されていると説明したので、このポートがreuseに許可されているかどうかを判断する必要がある.
3が存在しないなら、上記の第5部と同じです.
int inet_csk_get_port(struct sock *sk, unsigned short snum)
{
struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
struct inet_bind_hashbucket *head;
struct hlist_node *node;
struct inet_bind_bucket *tb;
int ret;
struct net *net = sock_net(sk);
local_bh_disable();
if (!snum) {
/// 0, .
int remaining, rover, low, high;
/// .
inet_get_local_port_range(&low, &high);
remaining = (high - low) + 1;
rover = net_random() % remaining + low;
/// .
do {
/// key, inet_bind_bucket
head = &hashinfo->bhash[inet_bhashfn(net, rover,
hashinfo->bhash_size)];
spin_lock(&head->lock);
inet_bind_bucket_for_each(tb, node, &head->chain)
if (tb->ib_net == net && tb->port == rover)
/// , 1, .
goto next;
break;
next:
spin_unlock(&head->lock);
/// , ( , ), .
if (++rover > high)
rover = low;
} while (--remaining > 0);
/* Exhausted local port range during search? It is not
* possible for us to be holding one of the bind hash
* locks if this test triggers, because if 'remaining'
* drops to zero, we broke out of the do/while loop at
* the top level, not from the 'break;' statement.
*/
ret = 1;
if (remaining <= 0)
goto fail;
/// .
snum = rover;
} else {
/// . , .
head = &hashinfo->bhash[inet_bhashfn(net, snum,
hashinfo->bhash_size)];
spin_lock(&head->lock);
inet_bind_bucket_for_each(tb, node, &head->chain)
if (tb->ib_net == net && tb->port == snum)
goto tb_found;
}
tb = NULL;
goto tb_not_found;
tb_found:
/// . socket .
if (!hlist_empty(&tb->owners)) {
///fastreuse 0 socket socket , reuse port. socket TCP_LISTEN, .
if (tb->fastreuse > 0 &&
sk->sk_reuse && sk->sk_state != TCP_LISTEN) {
goto success;
} else {
ret = 1;
/// , inet_csk_bind_conflict. socket, ip . , .
if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb))
goto fail_unlock;
}
}
tb_not_found:
ret = 1;
/// inet_bind_bucket, bhash.
if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,
net, head, snum)) == NULL)
goto fail_unlock;
if (hlist_empty(&tb->owners)) {
/// fastreuse, listen socket .
if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)
tb->fastreuse = 1;
else
tb->fastreuse = 0;
} else if (tb->fastreuse &&
(!sk->sk_reuse || sk->sk_state == TCP_LISTEN))
tb->fastreuse = 0;
success:
/// socket ower .
if (!inet_csk(sk)->icsk_bind_hash)
inet_bind_hash(sk, tb, snum);
WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);
ret = 0;
fail_unlock:
spin_unlock(&head->lock);
fail:
local_bh_enable();
return ret;
}
listenのコードを見る前に、私達も先に関連データ構造を見にきます.中にはinet_connection私たちは以前にも紹介しました.icsk_を含みます.accept_queueのドメインは、このドメインはrequest uです.sock_queueタイプです.まずこの構造を見に行きます.
request_sock_queueはrequest_を表しています.sock列.tcpでは半接続列(SYNuRECVD状態)と接続完了列(established状態)があることを知っています.この二つはsynを受け取ったばかりで、握手が完成するのを待っています.一つはすでに3回握手しました.acceptが読みに来るのを待っています.
ここでは、synが分節して来るごとに、request_を新設します.sock構造は、listen_に加えられています.sockのrequest_そして、握手を3回したら、request_に入れます.sock_queueのrskq_accept_headとrskq_accept_テーラーの列の中で.acceptの時にこの列から直接読み取りました.
struct request_sock_queue {
/// , .
struct request_sock *rskq_accept_head;
struct request_sock *rskq_accept_tail;
rwlock_t syn_wait_lock;
u8 rskq_defer_accept;
/* 3 bytes hole, try to pack */
/// listen_socket .
struct listen_sock *listen_opt;
};
listen_sockはlistening状態のsocketを表します.struct listen_sock {
///log_2 of maximal queued SYNs/REQUESTs , .
u8 max_qlen_log;
/* 3 bytes hole, try to use */
/// .
int qlen;
/// , syn/ack ( syn/ack ) .
int qlen_young;
int clock_hand;
u32 hash_rnd;
/// syn_backlog( )
u32 nr_table_entries;
/// .
struct request_sock *syn_table[0];
};
最後にrequest_を見に来ました.sockは、tcpの両方の転送に必要ないくつかのドメインを保存しています.例えば、ウィンドウサイズ、エンドレート、エンドパケット番号などの値です.struct request_sock {
struct request_sock *dl_next; /* Must be first member! */
///mss .
u16 mss;
u8 retrans;
u8 cookie_ts; /* syncookie: encode tcpopts in timestamp */
/* The following two fields can be easily recomputed I think -AK */
u32 window_clamp; /* window clamp at creation time */
/// .
u32 rcv_wnd; /* rcv_wnd offered first time */
u32 ts_recent;
unsigned long expires;
/// ack .
const struct request_sock_ops *rsk_ops;
struct sock *sk;
u32 secid;
u32 peer_secid;
};
listenの対応のシステム呼び出しはsys_です.listen、それはまずsockfd_を通します.lookup_lightは該当のsocketを検索してinet_を呼び出します.listenは、大体の流れはbindと似ていますが、中間的に呼び出したのはinet_です.listenですここにはもう一つの概念があります.それはbacklogです.linuxでは、backlogのサイズはすでに接続された列のサイズを指します.半接続の列との和ではなく、半オープン接続の大きさはbacklogと同じぐらいです.
半開きの列の最大長さはbacklogによって計算されます.これを後で紹介します.
だから直接にinet_に来ました.listenの実現は、この関数は主にいくつかの合法的な判断を行い、その後inet_を呼び出します.csk_listen_startは関連ドメインを処理します.
int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char old_state;
int err;
lock_sock(sk);
err = -EINVAL;
/// ( ) socket .
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto out;
old_state = sk->sk_state;
/// close listen.
if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
goto out;
/* Really, if the socket is already in listen state
* we can only allow the backlog to be adjusted.
*/
/// listen , .
if (old_state != TCP_LISTEN) {
err = inet_csk_listen_start(sk, backlog);
if (err)
goto out;
}
/// backlog sk_max_ack_backlog, .
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
release_sock(sk);
return err;
}
そしてinet_を見に来ましたcsk_listen_startの実現その主な仕事は、新たにlisten socketを割り当てて、inet_に加入することです.connectionsockのicsk_accept_queu eドメインのlisten_optで.現在使用されているポートを判断します.最終的に戻ります.
int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
{
struct inet_sock *inet = inet_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
/// listen socket.
int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
if (rc != 0)
return rc;
/// ack_backlog 0.
sk->sk_max_ack_backlog = 0;
sk->sk_ack_backlog = 0;
inet_csk_delack_init(sk);
/* There is race window here: we announce ourselves listening,
* but this transition is still not validated by get_port().
* It is OK, because this socket enters to hash table only
* after validation is complete.
*/
/// .
sk->sk_state = TCP_LISTEN;
///get_port . , , listen .
if (!sk->sk_prot->get_port(sk, inet->num)) {
// , sport, inet_hashinfo( ) listening_hash hash .
inet->sport = htons(inet->num);
sk_dst_reset(sk);
/// __inet_hash .
sk->sk_prot->hash(sk);
return 0;
}
/// , .
sk->sk_state = TCP_CLOSE;
__reqsk_queue_destroy(&icsk->icsk_accept_queue);
return -EADDRINUSE;
}
最後にreqskを見に来ました.queue.allocの実現:
/// .
int sysctl_max_syn_backlog = 256;
int reqsk_queue_alloc(struct request_sock_queue *queue,
unsigned int nr_table_entries)
{
size_t lopt_size = sizeof(struct listen_sock);
struct listen_sock *lopt;
/// nr_table_entries( listen backlog) sysctl_max_syn_backlog .
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
/// nr_table_entries 8.
nr_table_entries = max_t(u32, nr_table_entries, 8);
/// nr_table_entries 2
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
/// listen_sock .
lopt_size += nr_table_entries * sizeof(struct request_sock *);
if (lopt_size > PAGE_SIZE)
lopt = __vmalloc(lopt_size,
GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
PAGE_KERNEL);
else
lopt = kzalloc(lopt_size, GFP_KERNEL);
if (lopt == NULL)
return -ENOMEM;
/// max_qlen_log , 3, nr_table_entries 2 log..
for (lopt->max_qlen_log = 3;
(1 << lopt->max_qlen_log) < nr_table_entries;
lopt->max_qlen_log++);
get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
rwlock_init(&queue->syn_wait_lock);
queue->rskq_accept_head = NULL;
/// nr_table_entries .
lopt->nr_table_entries = nr_table_entries;
write_lock_bh(&queue->syn_wait_lock);
/// listen_socket queue->listen_opt
queue->listen_opt = lopt;
write_unlock_bh(&queue->syn_wait_lock);
return 0;
}