FreeBSD kernel SOCKET I/F 探検

第15回 bind() システムコール (3)

2014.11.06 (2015.05.28更新)

必要とする知識など:
  • 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() 関数の処理を見ていきます。

著者プロフィール

asou

好きな OS は、FreeBSD です。が、最近購入した Mac mini がなかなか快適なので、Mac でいいかなと思うようになってきました。

記事一覧Index