FreeBSD kernel SOCKET I/F 探検

第13回 bind() システムコール (1)

2014.08.27

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

bind() システムコール、一回目です。

本連載の 三回目 に掲載した server.c を再度掲載します。

#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>

/**
 * Main
 */
int
main(int argc, char *argv[])
{
       int port;
       int sd;
       int result;
       struct sockaddr_in sa;
       struct sockaddr_in from;
       socklen_t addrlen;
       ssize_t rbytes;
       ssize_t sbytes;
       char buf[1024];

       /* Is not enough argument? */
       if (argc != 2) {
               fprintf(stderr, "Usage: server PortNo\n");
               exit(1);
       }
       port = atoi(argv[1]);

       /* Create a socket */
       sd = socket(PF_INET, SOCK_DGRAM, 0);
       if (sd < 0) {
               perror("socket");
               exit(1);
       }

       /* Bind the port number */
       bzero(&sa, sizeof (sa));
#ifdef __FreeBSD__
       sa.sin_len = sizeof (sa);
#endif /* __FreeBSD__ */
       sa.sin_family = AF_INET;
       sa.sin_port = htons(port);
       result = bind(sd, (struct sockaddr *)&sa, sizeof (sa));
       if (result < 0) {
               perror("bind");
               exit(1);
       }

       while (1) {
               /* Receive data */
               addrlen = sizeof (from);
               rbytes = recvfrom(sd, buf, sizeof (buf), 0, (struct sockaddr *)&from, &addrlen);
               if (rbytes < 0) {
                       perror("recvfrom");
                       continue;
               }
               printf("Received %dbytes\n", rbytes);

               /* Send data */
               sbytes = sendto(sd, buf, rbytes, 0, (struct sockaddr *)&from, addrlen);
               if (sbytes < 0) {
                       perror("sendto");
                       continue;
               }
               if (sbytes != rbytes) {
                       fprintf(stderr, "Length of the transmitted data does not matched: Tried to send %dbytes, Transmitted %dbytes.\n", rbytes, sbytes);
               }
       }

       /* Success */
       return 0;
}

前回で socket() システムコールに該当する kernel の処理 sys_socket() の解析が終了しました。server.c を見ると、socket() システムコールの次は、bind() システムコールにより UDP のポート番号とソケットの対応付けを行ないます。

bind() システムコールを実行すると、kernel 内では sys_bind() が呼ばれます。sys_bind() 関数は、/usr/src/sys/kern/uipc_syscalls.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_syscalls.c?view=markup) で以下の様に定義されています。

sys_bind(td, uap)
        struct thread *td;
        struct bind_args /* {
                int     s;
                caddr_t name;
                int     namelen;
        } */ *uap;
{
        struct sockaddr *sa;
        int error;

        if ((error = getsockaddr(&sa, uap->name, uap->namelen)) != 0)
                return (error);

        error = kern_bind(td, uap->s, sa);
        free(sa, M_SONAME);
        return (error);
}

getsockaddr() は同じ /usr/src/sys/kern/uipc_syscalls.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);
        if (len < offsetof(struct sockaddr, sa_data[0]))
                return (EINVAL);
        sa = malloc(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);
}

最初の二つの if (len... はエラーチェックです。詳細は割愛します。次の malloc(() も MALLOC(3) と大差ないメモリ領域の確保なので、詳細は割愛します。その次の copy() とその後の if (error) ... else ですが、GENERIC kernel カーネルのコンパイル時には COMAPT_OLDSOCK は指定されていないので、この #if defined(COMPAT_OLDSOCK)... の部分を削除すると、以下のようになります。

error = copyin(uaddr, sa, len);
if (error) {
        free(sa, M_SONAME);
} else {
        sa->sa_len = len;
        *namp = sa;
}

copyin() は /usr/src/sys/アーキテクチャー/アーキテクチャー/support.[Ss] で定義されています。これは、各アーキテクチャー毎にアセンブリ言語で記述されています。i386 であれば /usr/src/sys/i386/i386/support.s (http://svnweb.freebsd.org/base/release/9.1.0/sys/i386/i386/support.s?view=markup) です。

この copyin() は、ユーザ空間上にあるデータをカーネル空間にコピーします。

getsockaddr() の第二引数 uaddr には、server.c の bind() の呼び出し (下記)

result = bind(sd, (struct sockaddr *)&sa, sizeof (sa));

の第二引数 &sa が渡されます。ユーザ空間とカーネル空間ではアドレスと実際のメモリの割当が (恐らく) 異なるので、それを copyin() でカーネル空間から見たユーザ空間のアドレスに変換した上で、ユーザ空間からカーネル空間にデータをコピーしているのだと推測します。

結局 getsockaddr() は、malloc() で struct sockaddr 用のメモリ領域を確保し、そこにユーザ空間の struct sockaddr (この場合は server.c の struct sockaddr_in as) をコピーし、そのポインタを第一引数の namp に代入しています。

getsockaddr() から sys_bind() に戻ると、次は kern_bind() の呼び出しです。

error = kern_bind(td, uap->s, sa);

kern_bind() は、sys_bind() の直後で以下のように定義されています。

int
kern_bind(td, fd, sa)
        struct thread *td;
        int fd;
        struct sockaddr *sa;
{
        struct socket *so;
        struct file *fp;
        int error;

        AUDIT_ARG_FD(fd);
        error = getsock_cap(td->td_proc->p_fd, fd, CAP_BIND, &fp, NULL);
        if (error)
                return (error);
        so = fp->f_data;
#ifdef KTRACE
        if (KTRPOINT(td, KTR_STRUCT))
                ktrsockaddr(sa);
#endif
#ifdef MAC
        error = mac_socket_check_bind(td->td_ucred, so, sa);
        if (error == 0)
#endif
                error = sobind(so, sa, td);
        fdrop(fp, td);
        return (error);
}

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

#ifdef AUDIT
...
#define AUDIT_ARG_FD(fd) do {                                           \
        if (AUDITING_TD(curthread))                                     \
                audit_arg_fd((fd));                                     \
} while (0)
...
#else
...
#define AUDIT_ARG_FD(fd)
...
#endif

GENERIC kernel のコンパイル時に AUDIT は定義されていないようなので、uipc_bind() の最初の AUDIT_ARG_FD() は無処理になります。

また、#ifdef KTRACE, #ifdef MAC も無効と考えると、kern_bind() は以下のようになります。

int
kern_bind(td, fd, sa)
        struct thread *td;
        int fd;
        struct sockaddr *sa;
{
        struct socket *so;
        struct file *fp;
        int error;

        error = getsock_cap(td->td_proc->p_fd, fd, CAP_BIND, &fp, NULL);
        if (error)
                return (error);
        so = fp->f_data;
                error = sobind(so, sa, td);
        fdrop(fp, td);
        return (error);
}

kern_bind() の詳細を追い掛けるところですが、今回はここで終了です。

著者プロフィール

asou

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

記事一覧Index