- 必要とする知識など:
- 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() の呼び出しから見ていきます。