bind(2) における sin_lan の謎


TCP ソケットでの bind(2) では、アドレス構造体として sockaddr_in 構造体を使います(IPv4 の場合)。 この sockaddr_in 構造体ですが、FreeBSD の場合、 /usr/include/netinet/in.h で以下のように定義されてます。


  /*
   * Socket address, internet style.
   */
  struct sockaddr_in {
          u_char  sin_len;
          u_char  sin_family;
          u_short sin_port;
          struct  in_addr sin_addr;
          char    sin_zero[8];
  };

ソケットプログラミングに慣れた人なら、sin_len というメンバを、見慣れないメンバと感じるかもしれません。 Solaris8 の sockaddr_in 構造体には sin_len は含まれません。 Linux や HP-UX の sockaddr_in 構造体も同様です。

W.Richard Stevens の『UNIX ネットワークプログラミング 第 2 版 Vol.1』によると、『長さメンバ sin_len は、OSI プロトコルのサポートが導入された 4.3BSD-Reno で追加された』のだそうです。 ただし『ソケットアドレス構造体の長さメンバはすべてのベンダーがサポートしているわけではなく、 Posix.1g でもこのメンバは要求されていない』とも述べられています。

さて、この sin_len はどうやら、 アドレス構造体のサイズを示すメンバとして用いられるようですが、 ソケット API でアドレス構造体を使用するシステムコールの引数には、 必ず、アドレス構造体のサイズが含まれています。 例えば、bind(2) の第 3 引数は、第 2 引数で示すアドレス構造体のサイズを指定します。

では、実際のところ、sin_len が有効に機能するのか、 それとも多くの OS 同様引数渡しのサイズが有効になるのか、ソースを追って確認してみましょう。

bind(2) の入り口は /usr/src/sys/kern/uipc_syscall.cbind() です。bind() は以下のようになっています。


  int
  bind(p, uap)
          struct proc *p;
          register struct bind_args /* {
                  int     s;
                  caddr_t name;
                  int     namelen;
          } */ *uap;
  {
          struct file *fp;
          struct sockaddr *sa;
          int error;
  
          error = holdsock(p->p_fd, uap->s, &fp);
          if (error)
                  return (error);
          error = getsockaddr(&sa, uap->name, uap->namelen);
          if (error) {
                  fdrop(fp, p);
                  return (error);
          }
          error = sobind((struct socket *)fp->f_data, sa, p);
          FREE(sa, M_SONAME);
          fdrop(fp, p);
          return (error);
  }

bind() から getsockaddr() が呼ばれます。 この時、getsockaddr() の第 2 引数が bind(2) の第 2 引数、 getsockaddr() の第 3 引数が bind(2) の第 3 引数に対応しています。

getsockaddr() は bind() と同じく /usr/src/sys/kern/uipc_syscall.c に含まれており、 ソースは以下のようになっています。


  int
  getsockaddr(namp, uaddr, len)
          struct sockaddr **namp;
          caddr_t uaddr;
          size_t len;
  {
          struct sockaddr *sa;
          int error;
  
          if (len > SOCK_MAXADDRLEN)
                  return ENAMETOOLONG;
          MALLOC(sa, struct sockaddr *, len, M_SONAME, M_WAITOK);
          error = copyin(uaddr, sa, len);
          if (error) {
                  FREE(sa, M_SONAME);
          } else {
  #if defined(COMPAT_OLDSOCK) && BYTE_ORDER != BIG_ENDIAN
                  if (sa->sa_family == 0 && sa->sa_len < AF_MAX)
                          sa->sa_family = sa->sa_len;
  #endif
                  sa->sa_len = len;
                  *namp = sa;
          }
          return error;
  }

最後の方で sa->len に第 3 引数で受け取った len、 すなわち bind(2) の第 3 引数をアドレス構造体の sin_len として採用しています。 ですので、bind(2) 実行時に sockaddr_in 構造体の sin_len を設定するのは意味がありません。

TCP ソケットでの bind(2) 処理で実際にこの sin_len が参照されるのは、 /usr/src/sys/netinet/in_pcb.cin_pcbbind() の以下の処理になります。


  int
  in_pcbbind(inp, nam, p)
          register struct inpcb *inp;
          struct sockaddr *nam;
          struct proc *p;
  {
  
    ... (snip) ...
  
          if (nam) {
                  sin = (struct sockaddr_in *)nam;
                  if (nam->sa_len != sizeof (*sin))
                          return (EINVAL);

sa_len (つまり sin_len) の値が適切でなかった場合、 in_pcbbind() が EINVAL で return するので、bind(2) が EINVAL エラーになります。

というわけで、FreeBSD の bind(2) での sin_len には意味がありません。 おそらく、過去のアプリケーションのソースコードレベルでの互換性のために残されたメンバだと思われますが、 これから新規にソケットプログラミングを行う場合は、無視した方が良いでしょう。


TetsuoSTREAMS > FreeBSD > bind(2) における sin_len の謎