- 必要とする知識など:
- UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。
bind() システムコール、三回目です。sys_bind() ->kern_bind() -> getsock_cap() と呼び出し、getsock_cap() から kern_bind() に戻ったところです。
kern_bind() に戻ると sobind() を呼び出します。
error = sobind(so, sa, td);
sobind() は、/usr/src/sys/kern/uipc_socket.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_socket.c?view=markup) で以下のように定義されています。
int
sobind(struct socket *so, struct sockaddr *nam, struct thread *td)
{
int error;
CURVNET_SET(so->so_vnet);
error = (*so->so_proto->pr_usrreqs->pru_bind)(so, nam, td);
CURVNET_RESTORE();
return error;
}
CURVNET_SET()/CURVNET_RESTORE() は、本連載の 九回目 で説明しているので、詳細は割愛します。
so->so_proto->pr_userreq->pru_bind には udp_bind() のポインタが格納されています。udp_bind() は、/usr/src/sys/netinet/udp_usrreq.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/udp_usrreq.c?view=markup) で以下のように定義されています。
static int
udp_bind(struct socket *so, struct sockaddr *nam, struct thread *td)
{
struct inpcb *inp;
int error;
inp = sotoinpcb(so);
KASSERT(inp != NULL, ("udp_bind: inp == NULL"));
INP_WLOCK(inp);
INP_HASH_WLOCK(&V_udbinfo);
error = in_pcbbind(inp, nam, td->td_ucred);
INP_HASH_WUNLOCK(&V_udbinfo);
INP_WUNLOCK(inp);
return (error);
}
sotoinpcb() は、本連載の 十二回目 で説明しているので、詳細は割愛します。INP_WLOCK()/INP_WUNLOCK() は、本連載の 十一回目 と 十二回目 で説明しているので、詳細は割愛します。INP_HASH_WLOCK()/INP_HASH_WUNLOCK() は INP_WLOCK()/INP_WUNLOCK() とほぼ似たようなことを実行しているものと判断し、詳細は割愛します。
結局、udp_bind() の処理は、in_pcbbind() が肝となります。in_pcbbind() は、/usr/src/sys/netinet/in_pcb.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.c?view=markup) で以下のように定義されています。
int
in_pcbbind(struct inpcb *inp, struct sockaddr *nam, struct ucred *cred)
{
int anonport, error;
INP_WLOCK_ASSERT(inp);
INP_HASH_WLOCK_ASSERT(inp->inp_pcbinfo);
if (inp->inp_lport != 0 || inp->inp_laddr.s_addr != INADDR_ANY)
return (EINVAL);
anonport = inp->inp_lport == 0 && (nam == NULL ||
((struct sockaddr_in *)nam)->sin_port == 0);
error = in_pcbbind_setup(inp, nam, &inp->inp_laddr.s_addr,
&inp->inp_lport, cred);
if (error)
return (error);
if (in_pcbinshash(inp) != 0) {
inp->inp_laddr.s_addr = INADDR_ANY;
inp->inp_lport = 0;
return (EAGAIN);
}
if (anonport)
inp->inp_flags |= INP_ANONPORT;
return (0);
}
INP_WLOCK_ASSERT() と INP_HASH_WLOCK_ASSERT() の詳細は割愛します。
inp->inp_lport の inp_lport と inp->inp_laddr のinp_laddr は、/usr/src/sys/netinet/in_pcb.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_pcb.h?view=markup) で以下のように定義されています。
#define inp_lport inp_inc.inc_lport
#define inc_lport inc_ie.ie_lport
#define inp_laddr inp_inc.inc_laddr
#define inc_laddr inc_ie.ie_laddr
#define ie_laddr ie_dependladdr.ie46_local.ia46_addr4
これを展開すると、inp->inp_lport は inp->inp_inc.inc_ie.ie_lport および inp->inp_inc.inc_ie.ie_dependladdr.ie46_local.ia46_alld4 とります。inp が示す struct inpcb は、本連載の 十一回目 で in_pcballoc() で uma_zalloc() の直後に bzero() にて初期化されているので、ie_lport および ie_laddr は 0 です。以上のことから、
if (inp->inp_lport != 0 || inp->inp_laddr.s_addr != INADDR_ANY)
return (EINVAL);
この if 文の条件は偽なので次に進みます。
anonport = inp->inp_lport == 0 && (nam == NULL ||
((struct sockaddr_in *)nam)->sin_port == 0);
inp->inp_lport は 0 ですが、nam には sys_bind() から呼び出した getsockaddr() で malloc() した領域のポインタが格納されており、nam->sin_port には server.c が呼び出した bind() システムコールに渡したポート番号が格納されています。bind() に渡すポート番号は、ユーザが server プロセスの起動時に指定するので、0 を指定すれば anonport は真、0 以外を指定すればanonport は偽になります。
ちなみに anonport は、anonymous port (匿名ポート) のことだと思います。bind() システムコールを呼び出す際、ポート番号に 0 を指定すると、kernel が未使用のポート番号を割り当ててくれるので、ここではその判断を行なっているものと推測します。
次は in_pcbbind_setup() の呼び出しです。
error = in_pcbbind_setup(inp, nam, &inp->inp_laddr.s_addr,
&inp->inp_lport, cred);
in_pcbbind_setup() は、in_pucbind() と同 /usr/src/sys/netinet/in_pcb.c で以下のように定義されています。
int
in_pcbbind_setup(struct inpcb *inp, struct sockaddr *nam, in_addr_t *laddrp,
u_short *lportp, struct ucred *cred)
{
struct socket *so = inp->inp_socket;
struct sockaddr_in *sin;
struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
struct in_addr laddr;
u_short lport = 0;
int lookupflags = 0, reuseport = (so->so_options & SO_REUSEPORT);
int error;
/*
* No state changes, so read locks are sufficient here.
*/
INP_LOCK_ASSERT(inp);
INP_HASH_LOCK_ASSERT(pcbinfo);
...
以下略
今回はここで終了です。次回から、in_pcbbind_setup() 関数の処理を見ていきます。