必要とする知識など:
- UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。
クライアント側 connect() システムコール、一回目です。
前回 はクライアント側の bind() システムコールの処理を見てきました。今回はそれに続くconnect() システムコールの処理を見ていきます。
前回は、bind() システムコールでクライアントのソケットに対してクライアント側のポート番号を割り当てました。今回見ていく connect() システムコールですが、例題として使用する client.c の場合、bind() で使用したソケットと同じソケットに対してサーバの IP アドレスとポート番号を割り当てます。
connect() システムコールを実行すると、kernel 内では sys_connect() が呼ばれます。sys_connect() 関数は、/usr/src/sys/kern/uipc_syscalls.c で以下の様に定義されています。
int
sys_connect(td, uap)
struct thread *td;
struct connect_args /* {
int s;
caddr_t name;
int namelen;
} */ *uap;
{
struct sockaddr *sa;
int error;
error = getsockaddr(&sa, uap->name, uap->namelen);
if (error)
return (error);
error = kern_connect(td, uap->s, sa);
free(sa, M_SONAME);
return (error);
}
getsockaddr() は本連載の 十三回目 で説明しているので、詳細は割愛します。
次の kern_connect() は同じ /usr/src/sys/kern/uipc_syscalls.c で以下のように定義されています。
int
kern_connect(td, fd, sa)
struct thread *td;
int fd;
struct sockaddr *sa;
{
struct socket *so;
struct file *fp;
int error;
int interrupted = 0;
AUDIT_ARG_FD(fd);
error = getsock_cap(td->td_proc->p_fd, fd, CAP_CONNECT, &fp, NULL);
if (error)
return (error);
so = fp->f_data;
if (so->so_state & SS_ISCONNECTING) {
error = EALREADY;
goto done1;
}
#ifdef KTRACE
if (KTRPOINT(td, KTR_STRUCT))
ktrsockaddr(sa);
#endif
#ifdef MAC
error = mac_socket_check_connect(td->td_ucred, so, sa);
if (error)
goto bad;
#endif
error = soconnect(so, sa, td);
if (error)
goto bad;
if ((so->so_state & SS_NBIO) && (so->so_state & SS_ISCONNECTING)) {
error = EINPROGRESS;
goto done1;
}
SOCK_LOCK(so);
while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) {
AUDIT_ARG_FD() は本連載の 十三回目 で説明しているので、詳細は割愛します。
getsock_cap() は本連載の 十四回目 で説明しているので、詳細は割愛します。
次の処理に進みます。
so = fp->f_data;
if (so->so_state & SS_ISCONNECTING) {
error = EALREADY;
goto done1;
}
so->so_state はまだ何も設定されていないはずなので、この if 文は偽となります。ちなみに、上記の処理を見ればわかりますが、既に connect() 済みのソケットに対して再び connect() が呼ばれた場合にエラー EALREADY を返却しています。
次の #ifdef KTRACE と #ifdef MAC は割愛します。
次は soconnect() です。
error = soconnect(so, sa, td);
if (error)
goto bad;
soconnect() は /usr/src/sys/kern/uipc_socket.c で以下のように定義されています。
int
soconnect(struct socket *so, struct sockaddr *nam, struct thread *td)
{
int error;
if (so->so_options & SO_ACCEPTCONN)
return (EOPNOTSUPP);
CURVNET_SET(so->so_vnet);
/*
* If protocol is connection-based, can only connect once.
* Otherwise, if connected, try to disconnect first. This allows
* user to disconnect by connecting to, e.g., a null address.
*/
if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) &&
((so->so_proto->pr_flags & PR_CONNREQUIRED) ||
(error = sodisconnect(so)))) {
error = EISCONN;
} else {
/*
* Prevent accumulated error from previous connection from
* biting us.
*/
so->so_error = 0;
error = (*so->so_proto->pr_usrreqs->pru_connect)(so, nam, td);
}
CURVNET_RESTORE();
return (error);
}
so->so_options はまだ何も設定されていないので、
if (so->so_options & SO_ACCEPTCONN)
return (EOPNOTSUPP);
この if 文は偽となります。ちなみにこの if 文は、接続を受け付けるソケット (おそらく当該ソケットに対して、accept() システムコールを呼び出している) に対して connect() システムコールが呼ばれた場合にエラー EOPNOTSUPP を返却するのだと思います。
次の CURVNET_SET()/CURVNET_RESTORE() は、本連載の 九回目 で説明しているので、詳細は割愛します。
次の if 文ですが、
if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) &&
((so->so_proto->pr_flags & PR_CONNREQUIRED) ||
(error = sodisconnect(so)))) {
error = EISCONN;
} else {
コメントにも記述されていますが、当該ソケットが 接続済み or 接続中 の場合でかつ以下のいづれかの条件が真となった場合にエラー EISCONN を返却します。
- コネクションベースのソケットである。
- 接続中のためまずそれを切断しようとするが、その処理に失敗した。
次は上記の if 文が偽の場合には、struct protosw に定義されている pru_connect() 関数を呼び出しています。
} else {
so->so_error = 0;
error = (*so->so_proto->pr_usrreqs->pru_connect)(so, nam, td);
}
so->so_proto->pr_usrreqs->pru_connect には /usr/src/sys/netinet/udp_usrreq.c で以下のように udp_connect() のポインタが格納されています。
#ifdef INET
struct pr_usrreqs udp_usrreqs = {
.pru_abort = udp_abort,
.pru_attach = udp_attach,
.pru_bind = udp_bind,
.pru_connect = udp_connect,
.pru_control = in_control,
.pru_detach = udp_detach,
.pru_disconnect = udp_disconnect,
.pru_peeraddr = in_getpeeraddr,
.pru_send = udp_send,
.pru_soreceive = soreceive_dgram,
.pru_sosend = sosend_dgram,
.pru_shutdown = udp_shutdown,
.pru_sockaddr = in_getsockaddr,
.pru_sosetlabel = in_pcbsosetlabel,
.pru_close = udp_close,
};
#endif /* INET */
udp_connect() は同じ /usr/src/sys/netinet/udp_usrreq.c で、以下のように定義されています。
static int
udp_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
{
struct inpcb *inp;
int error;
struct sockaddr_in *sin;
inp = sotoinpcb(so);
KASSERT(inp != NULL, ("udp_connect: inp == NULL"));
INP_WLOCK(inp);
if (inp->inp_faddr.s_addr != INADDR_ANY) {
INP_WUNLOCK(inp);
return (EISCONN);
}
sin = (struct sockaddr_in *)nam;
error = prison_remote_ip4(td->td_ucred, &sin->sin_addr);
if (error != 0) {
INP_WUNLOCK(inp);
return (error);
}
INP_HASH_WLOCK(&V_udbinfo);
error = in_pcbconnect(inp, nam, td->td_ucred);
INP_HASH_WUNLOCK(&V_udbinfo);
if (error == 0)
soisconnected(so);
INP_WUNLOCK(inp);
return (error);
}
今回はここで終了です。次回は、上記の udp_connect() を見ていきます。