|
@@ -0,0 +1,186 @@
|
|
|
+/*
|
|
|
+ * vsock sock_diag(7) module
|
|
|
+ *
|
|
|
+ * Copyright (C) 2017 Red Hat, Inc.
|
|
|
+ * Author: Stefan Hajnoczi <stefanha@redhat.com>
|
|
|
+ *
|
|
|
+ * This program is free software; you can redistribute it and/or modify it
|
|
|
+ * under the terms of the GNU General Public License as published by the Free
|
|
|
+ * Software Foundation version 2 and no later version.
|
|
|
+ *
|
|
|
+ * This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
+ * more details.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/sock_diag.h>
|
|
|
+#include <linux/vm_sockets_diag.h>
|
|
|
+#include <net/af_vsock.h>
|
|
|
+
|
|
|
+static int sk_diag_fill(struct sock *sk, struct sk_buff *skb,
|
|
|
+ u32 portid, u32 seq, u32 flags)
|
|
|
+{
|
|
|
+ struct vsock_sock *vsk = vsock_sk(sk);
|
|
|
+ struct vsock_diag_msg *rep;
|
|
|
+ struct nlmsghdr *nlh;
|
|
|
+
|
|
|
+ nlh = nlmsg_put(skb, portid, seq, SOCK_DIAG_BY_FAMILY, sizeof(*rep),
|
|
|
+ flags);
|
|
|
+ if (!nlh)
|
|
|
+ return -EMSGSIZE;
|
|
|
+
|
|
|
+ rep = nlmsg_data(nlh);
|
|
|
+ rep->vdiag_family = AF_VSOCK;
|
|
|
+
|
|
|
+ /* Lock order dictates that sk_lock is acquired before
|
|
|
+ * vsock_table_lock, so we cannot lock here. Simply don't take
|
|
|
+ * sk_lock; sk is guaranteed to stay alive since vsock_table_lock is
|
|
|
+ * held.
|
|
|
+ */
|
|
|
+ rep->vdiag_type = sk->sk_type;
|
|
|
+ rep->vdiag_state = sk->sk_state;
|
|
|
+ rep->vdiag_shutdown = sk->sk_shutdown;
|
|
|
+ rep->vdiag_src_cid = vsk->local_addr.svm_cid;
|
|
|
+ rep->vdiag_src_port = vsk->local_addr.svm_port;
|
|
|
+ rep->vdiag_dst_cid = vsk->remote_addr.svm_cid;
|
|
|
+ rep->vdiag_dst_port = vsk->remote_addr.svm_port;
|
|
|
+ rep->vdiag_ino = sock_i_ino(sk);
|
|
|
+
|
|
|
+ sock_diag_save_cookie(sk, rep->vdiag_cookie);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int vsock_diag_dump(struct sk_buff *skb, struct netlink_callback *cb)
|
|
|
+{
|
|
|
+ struct vsock_diag_req *req;
|
|
|
+ struct vsock_sock *vsk;
|
|
|
+ unsigned int bucket;
|
|
|
+ unsigned int last_i;
|
|
|
+ unsigned int table;
|
|
|
+ struct net *net;
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ req = nlmsg_data(cb->nlh);
|
|
|
+ net = sock_net(skb->sk);
|
|
|
+
|
|
|
+ /* State saved between calls: */
|
|
|
+ table = cb->args[0];
|
|
|
+ bucket = cb->args[1];
|
|
|
+ i = last_i = cb->args[2];
|
|
|
+
|
|
|
+ /* TODO VMCI pending sockets? */
|
|
|
+
|
|
|
+ spin_lock_bh(&vsock_table_lock);
|
|
|
+
|
|
|
+ /* Bind table (locally created sockets) */
|
|
|
+ if (table == 0) {
|
|
|
+ while (bucket < ARRAY_SIZE(vsock_bind_table)) {
|
|
|
+ struct list_head *head = &vsock_bind_table[bucket];
|
|
|
+
|
|
|
+ i = 0;
|
|
|
+ list_for_each_entry(vsk, head, bound_table) {
|
|
|
+ struct sock *sk = sk_vsock(vsk);
|
|
|
+
|
|
|
+ if (!net_eq(sock_net(sk), net))
|
|
|
+ continue;
|
|
|
+ if (i < last_i)
|
|
|
+ goto next_bind;
|
|
|
+ if (!(req->vdiag_states & (1 << sk->sk_state)))
|
|
|
+ goto next_bind;
|
|
|
+ if (sk_diag_fill(sk, skb,
|
|
|
+ NETLINK_CB(cb->skb).portid,
|
|
|
+ cb->nlh->nlmsg_seq,
|
|
|
+ NLM_F_MULTI) < 0)
|
|
|
+ goto done;
|
|
|
+next_bind:
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ last_i = 0;
|
|
|
+ bucket++;
|
|
|
+ }
|
|
|
+
|
|
|
+ table++;
|
|
|
+ bucket = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Connected table (accepted connections) */
|
|
|
+ while (bucket < ARRAY_SIZE(vsock_connected_table)) {
|
|
|
+ struct list_head *head = &vsock_connected_table[bucket];
|
|
|
+
|
|
|
+ i = 0;
|
|
|
+ list_for_each_entry(vsk, head, connected_table) {
|
|
|
+ struct sock *sk = sk_vsock(vsk);
|
|
|
+
|
|
|
+ /* Skip sockets we've already seen above */
|
|
|
+ if (__vsock_in_bound_table(vsk))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (!net_eq(sock_net(sk), net))
|
|
|
+ continue;
|
|
|
+ if (i < last_i)
|
|
|
+ goto next_connected;
|
|
|
+ if (!(req->vdiag_states & (1 << sk->sk_state)))
|
|
|
+ goto next_connected;
|
|
|
+ if (sk_diag_fill(sk, skb,
|
|
|
+ NETLINK_CB(cb->skb).portid,
|
|
|
+ cb->nlh->nlmsg_seq,
|
|
|
+ NLM_F_MULTI) < 0)
|
|
|
+ goto done;
|
|
|
+next_connected:
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ last_i = 0;
|
|
|
+ bucket++;
|
|
|
+ }
|
|
|
+
|
|
|
+done:
|
|
|
+ spin_unlock_bh(&vsock_table_lock);
|
|
|
+
|
|
|
+ cb->args[0] = table;
|
|
|
+ cb->args[1] = bucket;
|
|
|
+ cb->args[2] = i;
|
|
|
+
|
|
|
+ return skb->len;
|
|
|
+}
|
|
|
+
|
|
|
+static int vsock_diag_handler_dump(struct sk_buff *skb, struct nlmsghdr *h)
|
|
|
+{
|
|
|
+ int hdrlen = sizeof(struct vsock_diag_req);
|
|
|
+ struct net *net = sock_net(skb->sk);
|
|
|
+
|
|
|
+ if (nlmsg_len(h) < hdrlen)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (h->nlmsg_flags & NLM_F_DUMP) {
|
|
|
+ struct netlink_dump_control c = {
|
|
|
+ .dump = vsock_diag_dump,
|
|
|
+ };
|
|
|
+ return netlink_dump_start(net->diag_nlsk, skb, h, &c);
|
|
|
+ }
|
|
|
+
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+}
|
|
|
+
|
|
|
+static const struct sock_diag_handler vsock_diag_handler = {
|
|
|
+ .family = AF_VSOCK,
|
|
|
+ .dump = vsock_diag_handler_dump,
|
|
|
+};
|
|
|
+
|
|
|
+static int __init vsock_diag_init(void)
|
|
|
+{
|
|
|
+ return sock_diag_register(&vsock_diag_handler);
|
|
|
+}
|
|
|
+
|
|
|
+static void __exit vsock_diag_exit(void)
|
|
|
+{
|
|
|
+ sock_diag_unregister(&vsock_diag_handler);
|
|
|
+}
|
|
|
+
|
|
|
+module_init(vsock_diag_init);
|
|
|
+module_exit(vsock_diag_exit);
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
+MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG,
|
|
|
+ 40 /* AF_VSOCK */);
|