AF_UNIX(dgram)ソケット


sock_register()でnet_families[unix_family_ops.family=PF_UNIX]=unix_family_opsとされ、sockeシステムコールはAF_UNIXでnet_families[PF_UNIX].unix_create()がコールされ、sock->typeに応じたdgram/stream等のコールバックがsock->opsに設定されます。
#define AF_UNIX         1    
#define PF_UNIX         AF_UNIX

static const struct net_proto_family unix_family_ops = {
       .family = PF_UNIX,
       .create = unix_create,
       .owner  = THIS_MODULE,
};

static int __init af_unix_init(void)
{
       int rc = -1;
       struct sk_buff *dummy_skb;

       BUILD_BUG_ON(sizeof(struct unix_skb_parms) > sizeof(dummy_skb->cb));

       rc = proto_register(&unix_proto, 1);
       if (rc != 0) {
               printk(KERN_CRIT "%s: Cannot create unix_sock SLAB cache!\n",
                      __func__);
               goto out;
       }

       sock_register(&unix_family_ops);
       register_pernet_subsys(&unix_net_ops);
out:
       return rc;
}

int sock_register(const struct net_proto_family *ops)
{
       int err;

       if (ops->family >= NPROTO) {
               printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family,
                      NPROTO);
               return -ENOBUFS;
       }

       spin_lock(&net_family_lock);
       if (rcu_dereference_protected(net_families[ops->family],
                                     lockdep_is_held(&net_family_lock)))
               err = -EEXIST;
       else {
               rcu_assign_pointer(net_families[ops->family], ops);
               err = 0;
       }
       spin_unlock(&net_family_lock);

       printk(KERN_INFO "NET: Registered protocol family %d\n", ops->family);
       return err;
}
sockeシステムコールで、typeがSOCK_RAW/SOCK_DGRAMなら、sock->ops = &unix_dgram_opsとなります。.accept/.listen等はエラーで、AF_UNIXのSOCK_RAW/SOCK_DGRAMはサーバ/クラアントで作成した元ソケット同士のやり取り故、かかる実装は必要ありません。
static const struct proto_ops unix_dgram_ops = {
       .family =       PF_UNIX,
       .owner =        THIS_MODULE,
       .release =      unix_release,
       .bind =         unix_bind,
       .connect =      unix_dgram_connect,
       .socketpair =   unix_socketpair,
       .accept =       sock_no_accept,
       .getname =      unix_getname,
       .poll =         unix_dgram_poll,
       .ioctl =        unix_ioctl,
       .listen =       sock_no_listen,
       .shutdown =     unix_shutdown,
       .setsockopt =   sock_no_setsockopt,
       .getsockopt =   sock_no_getsockopt,
       .sendmsg =      unix_dgram_sendmsg,
       .recvmsg =      unix_dgram_recvmsg,
       .mmap =         sock_no_mmap,
       .sendpage =     sock_no_sendpage,
};

static int unix_create(struct net *net, struct socket *sock, int protocol,
                      int kern)
{
       if (protocol && protocol != PF_UNIX)
               return -EPROTONOSUPPORT;

       sock->state = SS_UNCONNECTED;

       switch (sock->type) {
       case SOCK_STREAM:
               sock->ops = &unix_stream_ops;
               break;
       case SOCK_RAW:
               sock->type = SOCK_DGRAM;
       case SOCK_DGRAM:
               sock->ops = &unix_dgram_ops;
               break;
       case SOCK_SEQPACKET:
               sock->ops = &unix_seqpacket_ops;
               break;
       default:
               return -ESOCKTNOSUPPORT;
       }

       return unix_create1(net, sock) ? 0 : -ENOMEM;
}
bindシステムコールはサーバとするソケットを登録します。ソケット.bindコールバックで、ファイルinode番号をインデックスとするサイズ255のunix_socket_table[dentry->d_inode->i_ino & (UNIX_HASH_SIZE-1)]をヘッドとするリストにサーバソケットをリストします。クライアントはそのinode番号によりunix_socket_table[]からサーバソケットを取得します。
#define UNIX_HASH_SIZE  256

static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
       struct sock *sk = sock->sk;
       struct net *net = sock_net(sk);
       struct unix_sock *u = unix_sk(sk);
       struct sockaddr_un *sunaddr = (struct sockaddr_un *)uaddr;
       char *sun_path = sunaddr->sun_path;
       struct dentry *dentry = NULL;
       struct path path;
       int err;
       unsigned hash;
       struct unix_address *addr;
       struct hlist_head *list;

       err = -EINVAL;
       if (sunaddr->sun_family != AF_UNIX)
               goto out;

       if (addr_len == sizeof(short)) {
               err = unix_autobind(sock);
               goto out;
       }

       err = unix_mkname(sunaddr, addr_len, &hash);
       if (err < 0)
               goto out;
       addr_len = err;

       mutex_lock(&u->readlock);

       err = -EINVAL;
       if (u->addr)
               goto out_up;

       err = -ENOMEM;
       addr = kmalloc(sizeof(*addr)+addr_len, GFP_KERNEL);
       if (!addr)
               goto out_up;

       memcpy(addr->name, sunaddr, addr_len);
       addr->len = addr_len;
       addr->hash = hash ^ sk->sk_type;
       atomic_set(&addr->refcnt, 1);

       if (sun_path[0]) {
               umode_t mode;
               err = 0;

               dentry = kern_path_create(AT_FDCWD, sun_path, &path, 0);
               err = PTR_ERR(dentry);
               if (IS_ERR(dentry))
                       goto out_mknod_parent;

               mode = S_IFSOCK |
                      (SOCK_INODE(sock)->i_mode & ~current_umask());
               err = mnt_want_write(path.mnt);
               if (err)
                       goto out_mknod_dput;
               err = security_path_mknod(&path, dentry, mode, 0);
               if (err)
                       goto out_mknod_drop_write;
               err = vfs_mknod(path.dentry->d_inode, dentry, mode, 0);
out_mknod_drop_write:
               mnt_drop_write(path.mnt);
               if (err)
                       goto out_mknod_dput;
               mutex_unlock(&path.dentry->d_inode->i_mutex);
               dput(path.dentry);
               path.dentry = dentry;

               addr->hash = UNIX_HASH_SIZE;
       }

       spin_lock(&unix_table_lock);

       if (!sun_path[0]) {
               err = -EADDRINUSE;
               if (__unix_find_socket_byname(net, sunaddr, addr_len,
                                             sk->sk_type, hash)) {
                       unix_release_addr(addr);
                       goto out_unlock;
               }

               list = &unix_socket_table[addr->hash];
       } else {
               list = &unix_socket_table[dentry->d_inode->i_ino & (UNIX_HASH_SIZE-1)];
               u->dentry = path.dentry;
               u->mnt    = path.mnt;
       }

       err = 0;
       __unix_remove_socket(sk);
       u->addr = addr;
       __unix_insert_socket(list, sk);

out_unlock:
       spin_unlock(&unix_table_lock);
out_up:
       mutex_unlock(&u->readlock);
out:
       return err;

out_mknod_dput:
       dput(dentry);
       mutex_unlock(&path.dentry->d_inode->i_mutex);
       path_put(&path);
out_mknod_parent:
       if (err == -EEXIST)
               err = -EADDRINUSE;
       unix_release_addr(addr);
       goto out_up;
}
connectシステムコールからコールされ、クライアントソケットsock->sk->peerに、unix_find_other()でファイルinodeから取得したサーバソケット(other)を設定します。多重接続は不可で接続済のソケットは削除され無効となります。
static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
                             int alen, int flags)
{
       struct sock *sk = sock->sk;
       struct net *net = sock_net(sk);
       struct sockaddr_un *sunaddr = (struct sockaddr_un *)addr;
       struct sock *other;
       unsigned hash;
       int err;

       if (addr->sa_family != AF_UNSPEC) {
               err = unix_mkname(sunaddr, alen, &hash);
               if (err < 0)
                       goto out;
               alen = err;

               if (test_bit(SOCK_PASSCRED, &sock->flags) &&
                   !unix_sk(sk)->addr && (err = unix_autobind(sock)) != 0)
                       goto out;

restart:
               other = unix_find_other(net, sunaddr, alen, sock->type, hash, &err);
               if (!other)
                       goto out;

               unix_state_double_lock(sk, other);

               if (sock_flag(other, SOCK_DEAD)) {
                       unix_state_double_unlock(sk, other);
                       sock_put(other);
                       goto restart;
               }

               err = -EPERM;
               if (!unix_may_send(sk, other))
                       goto out_unlock;

               err = security_unix_may_send(sk->sk_socket, other->sk_socket);
               if (err)
                       goto out_unlock;

       } else {
               other = NULL;
               unix_state_double_lock(sk, other);
       }

       if (unix_peer(sk)) {
               struct sock *old_peer = unix_peer(sk);
               unix_peer(sk) = other;
               unix_state_double_unlock(sk, other);

               if (other != old_peer)
                       unix_dgram_disconnected(sk, old_peer);
               sock_put(old_peer);
       } else {
               unix_peer(sk) = other;
               unix_state_double_unlock(sk, other);
       }
       return 0;

out_unlock:
       unix_state_double_unlock(sk, other);
       sock_put(other);
out:
       return err;
}

static struct sock *unix_find_other(struct net *net,
                                   struct sockaddr_un *sunname, int len,
                                   int type, unsigned hash, int *error)
{
       struct sock *u;
       struct path path;
       int err = 0;

       if (sunname->sun_path[0]) {
               struct inode *inode;
               err = kern_path(sunname->sun_path, LOOKUP_FOLLOW, &path);
               if (err)
                       goto fail;
               inode = path.dentry->d_inode;
               err = inode_permission(inode, MAY_WRITE);
               if (err)
                       goto put_fail;

               err = -ECONNREFUSED;
               if (!S_ISSOCK(inode->i_mode))
                       goto put_fail;
               u = unix_find_socket_byinode(inode);
               if (!u)
                       goto put_fail;

               if (u->sk_type == type)
                       touch_atime(path.mnt, path.dentry);

               path_put(&path);

               err = -EPROTOTYPE;
               if (u->sk_type != type) {
                       sock_put(u);
                       goto fail;
               }
       } else {
               err = -ECONNREFUSED;
               u = unix_find_socket_byname(net, sunname, len, type, hash);
               if (u) {
                       struct dentry *dentry;
                       dentry = unix_sk(u)->dentry;
                       if (dentry)
                               touch_atime(unix_sk(u)->mnt, dentry);
               } else
                       goto fail;
       }
       return u;

put_fail:
       path_put(&path);
fail:
       *error = err;
       return NULL;
}
送信先ソケットファイルが無ければ、 sk->peerのconnectシステムコールで接続されたソケット、そうでなければ、指定された送信先ソケットファイルでの掛かるインデックスunix_socket_table[]のソケットに送信データブロックをリストします。
static int unix_dgram_sendmsg(struct kiocb *kiocb, struct socket *sock,
                             struct msghdr *msg, size_t len)
{
       struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
       struct sock *sk = sock->sk;
       struct net *net = sock_net(sk);
       struct unix_sock *u = unix_sk(sk);
       struct sockaddr_un *sunaddr = msg->msg_name;
       struct sock *other = NULL;
       int namelen = 0;
       int err;
       unsigned hash;
       struct sk_buff *skb;
       long timeo;
       struct scm_cookie tmp_scm;
       int max_level;

       if (NULL == siocb->scm)
               siocb->scm = &tmp_scm;
       wait_for_unix_gc();
       err = scm_send(sock, msg, siocb->scm);
       if (err < 0)
               return err;

       err = -EOPNOTSUPP;
       if (msg->msg_flags&MSG_OOB)
               goto out;

       if (msg->msg_namelen) {
               err = unix_mkname(sunaddr, msg->msg_namelen, &hash);
               if (err < 0)
                       goto out;
               namelen = err;
       } else {
               sunaddr = NULL;
               err = -ENOTCONN;
               other = unix_peer_get(sk);
               if (!other)
                       goto out;
       }

       if (test_bit(SOCK_PASSCRED, &sock->flags) && !u->addr
           && (err = unix_autobind(sock)) != 0)
               goto out;

       err = -EMSGSIZE;
       if (len > sk->sk_sndbuf - 32)
               goto out;

       skb = sock_alloc_send_skb(sk, len, msg->msg_flags&MSG_DONTWAIT, &err);
       if (skb == NULL)
               goto out;

       err = unix_scm_to_skb(siocb->scm, skb, true);
       if (err < 0)
               goto out_free;
       max_level = err + 1;
       unix_get_secdata(siocb->scm, skb);

       skb_reset_transport_header(skb);
       err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
       if (err)
               goto out_free;

       timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);

restart:
       if (!other) {
               err = -ECONNRESET;
               if (sunaddr == NULL)
                       goto out_free;

               other = unix_find_other(net, sunaddr, namelen, sk->sk_type,
                                       hash, &err);
               if (other == NULL)
                       goto out_free;
       }

       if (sk_filter(other, skb) < 0) {
               err = len;
               goto out_free;
       }

       unix_state_lock(other);
       err = -EPERM;
       if (!unix_may_send(sk, other))
               goto out_unlock;

       if (sock_flag(other, SOCK_DEAD)) {
               unix_state_unlock(other);
               sock_put(other);

               err = 0;
               unix_state_lock(sk);
               if (unix_peer(sk) == other) {
                       unix_peer(sk) = NULL;
                       unix_state_unlock(sk);

                       unix_dgram_disconnected(sk, other);
                       sock_put(other);
                       err = -ECONNREFUSED;
               } else {
                       unix_state_unlock(sk);
               }

               other = NULL;
               if (err)
                       goto out_free;
               goto restart;
       }

       err = -EPIPE;
       if (other->sk_shutdown & RCV_SHUTDOWN)
               goto out_unlock;

       if (sk->sk_type != SOCK_SEQPACKET) {
               err = security_unix_may_send(sk->sk_socket, other->sk_socket);
               if (err)
                       goto out_unlock;
       }

       if (unix_peer(other) != sk && unix_recvq_full(other)) {
               if (!timeo) {
                       err = -EAGAIN;
                       goto out_unlock;
               }

               timeo = unix_wait_for_peer(other, timeo);

               err = sock_intr_errno(timeo);
               if (signal_pending(current))
                       goto out_free;

               goto restart;
       }

       if (sock_flag(other, SOCK_RCVTSTAMP))
               __net_timestamp(skb);
       maybe_add_creds(skb, sock, other);
       skb_queue_tail(&other->sk_receive_queue, skb);
       if (max_level > unix_sk(other)->recursion_level)
               unix_sk(other)->recursion_level = max_level;
       unix_state_unlock(other);
       other->sk_data_ready(other, len);
       sock_put(other);
       scm_destroy(siocb->scm);
       return len;

out_unlock:
       unix_state_unlock(other);
out_free:
       kfree_skb(skb);
out:
       if (other)
               sock_put(other);
       scm_destroy(siocb->scm);
       return err;
}

補足

streamタイプではconnect/access毎に新たに新規ソケットを作成し、そのソケット間でのやり取りとなります。

bindコールバックでUNIXファイル作成前に、unix_mkname()でファイル名の先頭がNULLなら、inode番号でなく、そのサイズ長でのチェックサムをインデックスとしてunix_socket_table[]リストヘッドにサーバソケットをリストします。推測ですがループバックデバイスで、ポート番号をファイル名とするチェックサムによるPF_UNIXでの実装故かと思います・・・。
static int unix_mkname(struct sockaddr_un *sunaddr, int len, unsigned *hashp)
{
       if (len <= sizeof(short) || len > sizeof(*sunaddr))
               return -EINVAL;
       if (!sunaddr || sunaddr->sun_family != AF_UNIX)
               return -EINVAL;
       if (sunaddr->sun_path[0]) {
               ((char *)sunaddr)[len] = 0;
               len = strlen(sunaddr->sun_path)+1+sizeof(short);
               return len;
       }

       *hashp = unix_hash_fold(csum_partial(sunaddr, len, 0));
       return len;
}

static inline unsigned unix_hash_fold(__wsum n)
{
       unsigned hash = (__force unsigned)n;
       hash ^= hash>>16;
       hash ^= hash>>8;
       return hash&(UNIX_HASH_SIZE-1);
}

__wsum csum_partial(const void *buff, int len, __wsum wsum)
{
       unsigned int sum = (__force unsigned int)wsum;
       unsigned int result = do_csum(buff, len);

       result += sum;
       if (sum > result)
               result += 1;
       return (__force __wsum)result;
}


最終更新 2016/09/20 14:30:23 - north
(2016/09/20 14:30:23 作成)


検索

アクセス数
3689169
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。