tcp connection setupの実現(一)


bindの実現:
まずいくつかの住所構造を紹介します.
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;
}