FreeBSD kernel SOCKET I/F 探検

第25回 connect() システムコール (1)

2018.10.03

必要とする知識など:

  • 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 を返却します。

  1. コネクションベースのソケットである。
  2. 接続中のためまずそれを切断しようとするが、その処理に失敗した。

次は上記の 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() を見ていきます。

著者プロフィール

asou

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

記事一覧Index