FreeBSD kernel SOCKET I/F 探検

第16回 bind() システムコール (4)

2015.02.18 (2015.05.28更新)

必要とする知識など:
  • UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。

bind() システムコール、四回目です。sys_bind() ->kern_bind() -> sobind() -> udp_bind() -> in_pcbbind() -> in_pcbbind_setup() を呼び出したところです。

in_pcbbind_setup() は、/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_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);
...
以下略

変数の定義の後の最初の処理

INP_LOCK_ASSERT(inp);
INP_HASH_LOCK_ASSERT(pcbinfo);

の INP_LOCK_ASSER() と INP_HASH_LOCK_ASSERT() は、/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_LOCK_ASSERT(inp)    rw_assert(&(inp)->inp_lock, RA_LOCKED)
#define INP_HASH_LOCK_ASSERT(ipi)       rw_assert(&(ipi)->ipi_hash_lock, \
                                            RA_LOCKED)

INP_LOCK_ASSER() と INP_HASH_LOCK_ASSERT() は始めて扱いますが、今までに出てきた他の LOCK_ASSERT() と同様に、ロックを保持していることを確認しているものと推測します。

次の処理に進みます。

if (TAILQ_EMPTY(&V_in_ifaddrhead)) /* XXX broken! */
        return (EADDRNOTAVAIL);
laddr.s_addr = *laddrp;
if (nam != NULL && laddr.s_addr != INADDR_ANY)
        return (EINVAL);
if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0)
        lookupflags = INPLOOKUP_WILDCARD;
if (nam == NULL) {
        if ((error = prison_local_ip4(cred, &laddr)) != 0)
                return (error);
} else {
        sin = (struct sockaddr_in *)nam;
        if (nam->sa_len != sizeof (*sin))
                return (EINVAL);

最初の if 文から、順番に見ていきます。

if (TAILQ_EMPTY(&V_in_ifaddrhead)) /* XXX broken! */
        return (EADDRNOTAVAIL);

TAILQ_EMPTY() は、/usr/src/sys/sys/queue.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/queue.h?view=markup) で以下のように定義されています。

#define TAILQ_EMPTY(head)       ((head)->tqh_first == NULL)

V_in_ifaddrhead は、/usr/src/sys/netinet/in_var.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_var.h?view=markup) で以下のように定義されています。

#define V_in_ifaddrhead         VNET(in_ifaddrhead)

VNET() は、/usr/src/sys/net/vnet.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/net/vnet.h?view=markup) で定義されています。VNET の詳細は割愛しますが、上記の if 文は以下の様に展開されます。

if (((&(in_ifaddrhead))->tqh_first == ((void *)0)))

in_ifaddrhead は、/usr/src/sys/netinet/in_var.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_var.h?view=markup) で以下のように定義されています。

/*
 * Hash table for IP addresses.
 */
TAILQ_HEAD(in_ifaddrhead, in_ifaddr);

TAILQ_HEAD() は、/usr/src/sys/sys/queue.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/queue.h?view=markup) で以下のように定義されています。

#define TAILQ_HEAD(name, type)                                          \
struct name {                                                           \
        struct type *tqh_first; /* first element */                     \
        struct type **tqh_last; /* addr of last next element */         \
        TRACEBUF                                                        \
}

上記の in_var.h の TAILQ_HEAD() の定義

/*
 * Hash table for IP addresses.
 */
TAILQ_HEAD(in_ifaddrhead, in_ifaddr);

は、以下のように展開されます。

struct in_ifaddrhead { struct in_ifaddr *tqh_first; struct in_ifaddr **tqh_last; };

要するに、TAILQ_HEAD(in_ifaddrhead, in_ifaddr); は、struct in_ifaddr のキューを定義しています。

struct in_ifaddr は、in_ifaddrhead と同様に /usr/src/sys/netinet/in_var.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in_var.h?view=markup) で以下のように定義されています。

/*
 * Interface address, Internet version.  One of these structures
 * is allocated for each Internet address on an interface.
 * The ifaddr structure contains the protocol-independent part
 * of the structure and is assumed to be first.
 */
struct in_ifaddr {
        struct  ifaddr ia_ifa;          /* protocol-independent info */
#define ia_ifp          ia_ifa.ifa_ifp
#define ia_flags        ia_ifa.ifa_flags
                                        /* ia_subnet{,mask} in host order */
        u_long  ia_subnet;              /* subnet address */
        u_long  ia_subnetmask;          /* mask of subnet */
        LIST_ENTRY(in_ifaddr) ia_hash;  /* entry in bucket of inet addresses */
        TAILQ_ENTRY(in_ifaddr) ia_link; /* list of internet addresses */
        struct  sockaddr_in ia_addr;    /* reserve space for interface name */
        struct  sockaddr_in ia_dstaddr; /* reserve space for broadcast addr */
#define ia_broadaddr    ia_dstaddr
        struct  sockaddr_in ia_sockmask; /* reserve space for general netmask */
};

struct in_ifaddr は、Network Interface Card (NIC) に割り当てられているネットワークアドレスを管理する構造体のようです。恐らく、ifconfig(8) コマンドで NIC にネットワークアドレスを設定すると、struct in_ifaddr 用の領域を新たに確保し、in_ifaddrhead で管理するキューに登録されるものと思われます。

話を in_pcbbind_setup() の処理に戻します。

if (TAILQ_EMPTY(&V_in_ifaddrhead)) /* XXX broken! */
        return (EADDRNOTAVAIL);

この処理は、struct in_ifaddr のキューが空、要するに全ての NIC にネットワークアドレスが割り当てられていないの場合 EADDRNOTAVAIL を返却するものと思われます。

次の処理に進みます。laddr.s_addr への代入の次の if 文です。

laddr.s_addr = *laddrp;
if (nam != NULL && laddr.s_addr != INADDR_ANY)
        return (EINVAL);

nam には、bind(2) システムコールの第二引数 struct sockaddr *addr が渡されます。laddrp には、struct inpcb の inp_laddr.s_addr のポインタが渡されます。

struct inpcb は、本連載の 十一回目 で解説していますが、inp_laddr には何も代入されていないので、struct inpcb 用の領域を確保した際に実行した bzero() により 0 に初期化されたままになっています。

INADDR_ANY は、/usr/src/sys/netinet/in.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in.h?view=markup) で以下のように定義されています。

#define INADDR_ANY              (u_int32_t)0x00000000

上記の if 文の意味は、「ソケットにアドレスが割り当てようとした (nam != NULL) 際に既にアドレスが割り当てられていた (laddr.s_addr != IN_ADDR_ANY) ならば真」となります。

今回の場合、nam は NULL ではなく且つソケットにアドレスは割り当てられていないので、この if 文は偽となります。

次の if 文は見たままなので、詳細は割愛します。

if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0)
        lookupflags = INPLOOKUP_WILDCARD;

次の if 文は、nam != NULL なので else に進みます。

      if (nam == NULL) {
              if ((error = prison_local_ip4(cred, &laddr)) != 0)
                      return (error);
      } else {
              sin = (struct sockaddr_in *)nam;
              if (nam->sa_len != sizeof (*sin))
                      return (EINVAL);
#ifdef notdef
                /*
                 * We should check the family, but old programs
                 * incorrectly fail to initialize it.
                 */
                if (sin->sin_family != AF_INET)
                        return (EAFNOSUPPORT);
#endif
                error = prison_local_ip4(cred, &sin->sin_addr);
                if (error)
                        return (error);

sin への代入直後の

if (nam->sa_len != sizeof (*sin))
        return (EINVAL);

nam->sa_len は server.c で以下のように sizeof (sa) が代入されています。

        struct sockaddr_in sa;

#ifdef __FreeBSD__
        sa.sin_len = sizeof (sa);
#endif /* __FreeBSD__ */

また、if 文中の sizeof (*sin) で使用している sin は、本関数の最初の方で以下のように定義されています。

struct sockaddr_in *sin;

したがって、

if (nam->sa_len != sizeof (*sin))
        return (EINVAL);

この if 文は偽となります。

この if 文の直後の #ifdef notdef ~ #endif ですが、恐らく nodef は定義されていないと思いますので、割愛します。次の prison_local_ip4() は、/usr/src/sys/kern/kern_jail.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/kern_jail.c?view=markup) で以下のように定義されています。

/*
 * Make sure our (source) address is set to something meaningful to this
 * jail.
 *
 * Returns 0 if jail doesn't restrict IPv4 or if address belongs to jail,
 * EADDRNOTAVAIL if the address doesn't belong, or EAFNOSUPPORT if the jail
 * doesn't allow IPv4.  Address passed in in NBO and returned in NBO.
 */
int
prison_local_ip4(struct ucred *cred, struct in_addr *ia)

コメントの「Make sure ...」の末尾の「jail」とは、FreeBSD 上で仮想マシンのようなものを提供する仕組みです。Linux の Docker に似たようなものだと思います。コメントから察するに、bind(2) で割り当てようとしているネットワークアドレスを jail 側で使用しているか確認しているのではないかと思います。

prison_local_ip4() の詳細は割愛します。次の処理です。

if (sin->sin_port != *lportp) {
        /* Don't allow the port to change. */
        if (*lportp != 0)
                return (EINVAL);
        lport = sin->sin_port;
}

sin->sin_port には server プログラム起動時の第一引数で指定したポート番号が格納されています。lportp には struct inpcb のinp_lport へのポインタが渡されています。上で出てきた inp_laddr.s_addr と同様に inp_lport には何も代入していないので、struct inpcb 領域を確保した際の bzero() により 0 で初期化されています。従って、上記の if 文は、

既にポート番号を割り当てられているソケットのポート番号を変更しようと
した場合に EINVAL を返却する。

という意味になると思います。

次の処理を掲載します。

/* NB: lport is left as 0 if the port isn't being changed. */
if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) {
        /*
         * Treat SO_REUSEADDR as SO_REUSEPORT for multicast;
         * allow complete duplication of binding if
         * SO_REUSEPORT is set, or if SO_REUSEADDR is set
         * and a multicast address is bound on both
         * new and duplicated sockets.
         */
        if (so->so_options & SO_REUSEADDR)
                reuseport = SO_REUSEADDR|SO_REUSEPORT;
} else if (sin->sin_addr.s_addr != INADDR_ANY) {
        sin->sin_port = 0;              /* yech... */
        bzero(&sin->sin_zero, sizeof(sin->sin_zero));
        /*
         * Is the address a local IP address?
         * If INP_BINDANY is set, then the socket may be bound
         * to any endpoint address, local or not.
         */
        if ((inp->inp_flags & INP_BINDANY) == 0 &&
            ifa_ifwithaddr_check((struct sockaddr *)sin) == 0)
                return (EADDRNOTAVAIL);
}
laddr = sin->sin_addr;

最初の if 文で使用している IN_MULTICAST() は、/usr/src/sys/netinet/in.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/netinet/in.h?view=markup) で以下のように定義されています。

#define IN_CLASSD(i)            (((u_int32_t)(i) & 0xf0000000) == 0xe0000000)
#define IN_MULTICAST(i)         IN_CLASSD(i)

見たままですが、bind(2) システムコールの第二引数で渡した struct sockaddr *addr がマルチキャストアドレスであるか否かを判断しています。今回は、sin->sin_addr.s_addr には 0 を指定しているので、この if 文は偽となります。つぎの else if ですが、

} else if (sin->sin_addr.s_addr != INADDR_ANY) {

sin->sin_addr.s_addr は 0 なので、この if 文も偽となります。

今回はここで終了します。次回は、prison_local_ip4() の呼び出しから見ていきます。

著者プロフィール

asou

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

記事一覧Index