]> granicus.if.org Git - strace/blobdiff - socketutils.c
Introduce HAVE_STRUCT_TCB_EXT_ARG macro
[strace] / socketutils.c
index d5cddf0c7f71ab73b12a027fa7da9e424b80bef5..5d8d3ed98802b9c17be185c0c17e238f0f5daa49 100644 (file)
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2014 Zubin Mithra <zubin.mithra@gmail.com>
+ * Copyright (c) 2014-2016 Dmitry V. Levin <ldv@altlinux.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
 #include "defs.h"
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <linux/netlink.h>
 #include <linux/sock_diag.h>
 #include <linux/inet_diag.h>
+#include <linux/unix_diag.h>
+#include <linux/netlink_diag.h>
+#include <linux/rtnetlink.h>
+#include "xlat/netlink_protocols.h"
+
+#if !defined NETLINK_SOCK_DIAG && defined NETLINK_INET_DIAG
+# define NETLINK_SOCK_DIAG NETLINK_INET_DIAG
+#endif
+
+#include <sys/un.h>
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX sizeof(((struct sockaddr_un *) 0)->sun_path)
+#endif
+
+typedef struct {
+       unsigned long inode;
+       char *details;
+} cache_entry;
+
+#define CACHE_SIZE 1024U
+static cache_entry cache[CACHE_SIZE];
+#define CACHE_MASK (CACHE_SIZE - 1)
+
+static int
+cache_and_print_inode_details(const unsigned long inode, char *const details)
+{
+       cache_entry *e = &cache[inode & CACHE_MASK];
+       free(e->details);
+       e->inode = inode;
+       e->details = details;
+
+       tprints(details);
+       return 1;
+}
+
+bool
+print_sockaddr_by_inode_cached(const unsigned long inode)
+{
+       const cache_entry *const e = &cache[inode & CACHE_MASK];
+       if (e && inode == e->inode) {
+               tprints(e->details);
+               return true;
+       }
+       return false;
+}
 
 static bool
-send_query(const int fd, const int family, const int proto)
+send_query(const int fd, void *req, size_t req_size)
 {
-       struct sockaddr_nl nladdr;
-       struct {
-               struct nlmsghdr nlh;
-               struct inet_diag_req_v2 idr;
-       } req;
+       struct sockaddr_nl nladdr = {
+               .nl_family = AF_NETLINK
+       };
        struct iovec iov = {
-               .iov_base = &req,
-               .iov_len = sizeof(req)
+               .iov_base = req,
+               .iov_len = req_size
        };
-       struct msghdr msg = {
-               .msg_name = (void*)&nladdr,
+       const struct msghdr msg = {
+               .msg_name = &nladdr,
                .msg_namelen = sizeof(nladdr),
                .msg_iov = &iov,
                .msg_iovlen = 1
        };
 
-       memset(&nladdr, 0, sizeof(nladdr));
-       nladdr.nl_family = AF_NETLINK;
-
-       memset(&req, 0, sizeof(req));
-       req.nlh.nlmsg_len = sizeof(req);
-       req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
-       req.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
-       req.idr.sdiag_family = family;
-       req.idr.sdiag_protocol = proto;
-       req.idr.idiag_states = -1;
-
        for (;;) {
                if (sendmsg(fd, &msg, 0) < 0) {
                        if (errno == EINTR)
@@ -47,13 +107,38 @@ send_query(const int fd, const int family, const int proto)
 }
 
 static bool
-parse_response(const struct inet_diag_msg *diag_msg, const unsigned long inode)
+inet_send_query(const int fd, const int family, const int proto)
+{
+       struct {
+               const struct nlmsghdr nlh;
+               const struct inet_diag_req_v2 idr;
+       } req = {
+               .nlh = {
+                       .nlmsg_len = sizeof(req),
+                       .nlmsg_type = SOCK_DIAG_BY_FAMILY,
+                       .nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST
+               },
+               .idr = {
+                       .sdiag_family = family,
+                       .sdiag_protocol = proto,
+                       .idiag_states = -1
+               }
+       };
+       return send_query(fd, &req, sizeof(req));
+}
+
+static int
+inet_parse_response(const char *const proto_name, const void *const data,
+                   const int data_len, const unsigned long inode)
 {
+       const struct inet_diag_msg *const diag_msg = data;
        static const char zero_addr[sizeof(struct in6_addr)];
        socklen_t addr_size, text_size;
 
+       if (data_len < (int) NLMSG_LENGTH(sizeof(*diag_msg)))
+               return -1;
        if (diag_msg->idiag_inode != inode)
-               return false;
+               return 0;
 
        switch(diag_msg->idiag_family) {
                case AF_INET:
@@ -65,14 +150,15 @@ parse_response(const struct inet_diag_msg *diag_msg, const unsigned long inode)
                        text_size = INET6_ADDRSTRLEN;
                        break;
                default:
-                       return false;
+                       return -1;
        }
 
        char src_buf[text_size];
+       char *details;
 
        if (!inet_ntop(diag_msg->idiag_family, diag_msg->id.idiag_src,
                       src_buf, text_size))
-               return false;
+               return -1;
 
        if (diag_msg->id.idiag_dport ||
            memcmp(zero_addr, diag_msg->id.idiag_dst, addr_size)) {
@@ -80,93 +166,320 @@ parse_response(const struct inet_diag_msg *diag_msg, const unsigned long inode)
 
                if (!inet_ntop(diag_msg->idiag_family, diag_msg->id.idiag_dst,
                               dst_buf, text_size))
-                       return false;
+                       return -1;
 
-               tprintf("%s:%u->%s:%u",
-                       src_buf, ntohs(diag_msg->id.idiag_sport),
-                       dst_buf, ntohs(diag_msg->id.idiag_dport));
+               if (asprintf(&details, "%s:[%s:%u->%s:%u]", proto_name,
+                            src_buf, ntohs(diag_msg->id.idiag_sport),
+                            dst_buf, ntohs(diag_msg->id.idiag_dport)) < 0)
+                       return false;
        } else {
-               tprintf("%s:%u", src_buf, ntohs(diag_msg->id.idiag_sport));
+               if (asprintf(&details, "%s:[%s:%u]", proto_name, src_buf,
+                            ntohs(diag_msg->id.idiag_sport)) < 0)
+                       return false;
        }
 
-       return true;
+       return cache_and_print_inode_details(inode, details);
 }
 
 static bool
-receive_responses(const int fd, const unsigned long inode)
+receive_responses(const int fd, const unsigned long inode,
+                 const char *proto_name,
+                 int (* parser) (const char *, const void *,
+                                 int, unsigned long))
 {
-       static char buf[8192];
-       struct sockaddr_nl nladdr;
+       static union {
+               struct nlmsghdr hdr;
+               long buf[8192 / sizeof(long)];
+       } hdr_buf;
+
+       struct sockaddr_nl nladdr = {
+               .nl_family = AF_NETLINK
+       };
        struct iovec iov = {
-               .iov_base = buf,
-               .iov_len = sizeof(buf)
+               .iov_base = hdr_buf.buf,
+               .iov_len = sizeof(hdr_buf.buf)
        };
-
-       memset(&nladdr, 0, sizeof(nladdr));
-       nladdr.nl_family = AF_NETLINK;
+       int flags = 0;
 
        for (;;) {
-               ssize_t ret;
-               struct nlmsghdr *h;
                struct msghdr msg = {
-                       .msg_name = (void*)&nladdr,
+                       .msg_name = &nladdr,
                        .msg_namelen = sizeof(nladdr),
                        .msg_iov = &iov,
-                       .msg_iovlen = 1,
-                       .msg_control = NULL,
-                       .msg_controllen = 0,
-                       .msg_flags = 0
+                       .msg_iovlen = 1
                };
 
-               ret = recvmsg(fd, &msg, 0);
+               ssize_t ret = recvmsg(fd, &msg, flags);
                if (ret < 0) {
                        if (errno == EINTR)
                                continue;
                        return false;
                }
-               if (!ret)
+
+               const struct nlmsghdr *h = &hdr_buf.hdr;
+               if (!NLMSG_OK(h, ret))
                        return false;
-               for (h = (struct nlmsghdr*)buf;
-                    NLMSG_OK(h, ret);
-                    h = NLMSG_NEXT(h, ret)) {
-                       switch (h->nlmsg_type) {
-                               case NLMSG_DONE:
-                               case NLMSG_ERROR:
-                                       return false;
-                       }
-                       if (parse_response(NLMSG_DATA(h), inode))
+               for (; NLMSG_OK(h, ret); h = NLMSG_NEXT(h, ret)) {
+                       if (h->nlmsg_type != SOCK_DIAG_BY_FAMILY)
+                               return false;
+                       const int rc = parser(proto_name, NLMSG_DATA(h),
+                                             h->nlmsg_len, inode);
+                       if (rc > 0)
                                return true;
+                       if (rc < 0)
+                               return false;
+               }
+               flags = MSG_DONTWAIT;
+       }
+}
+
+static bool
+inet_print(const int fd, const int family, const int protocol,
+          const unsigned long inode, const char *proto_name)
+{
+       return inet_send_query(fd, family, protocol)
+               && receive_responses(fd, inode, proto_name, inet_parse_response);
+}
+
+static bool
+unix_send_query(const int fd, const unsigned long inode)
+{
+       struct {
+               const struct nlmsghdr nlh;
+               const struct unix_diag_req udr;
+       } req = {
+               .nlh = {
+                       .nlmsg_len = sizeof(req),
+                       .nlmsg_type = SOCK_DIAG_BY_FAMILY,
+                       .nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST
+               },
+               .udr = {
+                       .sdiag_family = AF_UNIX,
+                       .udiag_ino = inode,
+                       .udiag_states = -1,
+                       .udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER
+               }
+       };
+       return send_query(fd, &req, sizeof(req));
+}
+
+static int
+unix_parse_response(const char *proto_name, const void *data,
+                   const int data_len, const unsigned long inode)
+{
+       const struct unix_diag_msg *diag_msg = data;
+       struct rtattr *attr;
+       int rta_len = data_len - NLMSG_LENGTH(sizeof(*diag_msg));
+       uint32_t peer = 0;
+       size_t path_len = 0;
+       char path[UNIX_PATH_MAX + 1];
+
+       if (rta_len < 0)
+               return -1;
+       if (diag_msg->udiag_ino != inode)
+               return 0;
+       if (diag_msg->udiag_family != AF_UNIX)
+               return -1;
+
+       for (attr = (struct rtattr *) (diag_msg + 1);
+            RTA_OK(attr, rta_len);
+            attr = RTA_NEXT(attr, rta_len)) {
+               switch (attr->rta_type) {
+               case UNIX_DIAG_NAME:
+                       if (!path_len) {
+                               path_len = RTA_PAYLOAD(attr);
+                               if (path_len > UNIX_PATH_MAX)
+                                       path_len = UNIX_PATH_MAX;
+                               memcpy(path, RTA_DATA(attr), path_len);
+                               path[path_len] = '\0';
+                       }
+                       break;
+               case UNIX_DIAG_PEER:
+                       if (RTA_PAYLOAD(attr) >= 4)
+                               peer = *(uint32_t *) RTA_DATA(attr);
+                       break;
                }
        }
+
+       /*
+        * print obtained information in the following format:
+        * "UNIX:[" SELF_INODE [ "->" PEER_INODE ][ "," SOCKET_FILE ] "]"
+        */
+       if (!peer && !path_len)
+               return -1;
+
+       char peer_str[3 + sizeof(peer) * 3];
+       if (peer)
+               snprintf(peer_str, sizeof(peer_str), "->%u", peer);
+       else
+               peer_str[0] = '\0';
+
+       const char *path_str;
+       if (path_len) {
+               char *outstr = alloca(4 * path_len + 4);
+
+               outstr[0] = ',';
+               if (path[0] == '\0') {
+                       outstr[1] = '@';
+                       string_quote(path + 1, outstr + 2,
+                                    path_len - 1, QUOTE_0_TERMINATED);
+               } else {
+                       string_quote(path, outstr + 1,
+                                    path_len, QUOTE_0_TERMINATED);
+               }
+               path_str = outstr;
+       } else {
+               path_str = "";
+       }
+
+       char *details;
+       if (asprintf(&details, "%s:[%lu%s%s]", proto_name, inode,
+                    peer_str, path_str) < 0)
+               return -1;
+
+       return cache_and_print_inode_details(inode, details);
+}
+
+static bool
+netlink_send_query(const int fd, const unsigned long inode)
+{
+       struct {
+               const struct nlmsghdr nlh;
+               const struct netlink_diag_req ndr;
+       } req = {
+               .nlh = {
+                       .nlmsg_len = sizeof(req),
+                       .nlmsg_type = SOCK_DIAG_BY_FAMILY,
+                       .nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST
+               },
+               .ndr = {
+                       .sdiag_family = AF_NETLINK,
+                       .sdiag_protocol = NDIAG_PROTO_ALL,
+                       .ndiag_show = NDIAG_SHOW_MEMINFO
+               }
+       };
+       return send_query(fd, &req, sizeof(req));
+}
+
+static int
+netlink_parse_response(const char *proto_name, const void *data,
+                   const int data_len, const unsigned long inode)
+{
+       const struct netlink_diag_msg *const diag_msg = data;
+       const char *netlink_proto;
+       char *details;
+
+       if (data_len < (int) NLMSG_LENGTH(sizeof(*diag_msg)))
+               return -1;
+       if (diag_msg->ndiag_ino != inode)
+               return 0;
+
+       if (diag_msg->ndiag_family != AF_NETLINK)
+               return -1;
+
+       netlink_proto = xlookup(netlink_protocols,
+                               diag_msg->ndiag_protocol);
+
+       if (netlink_proto) {
+               static const char netlink_prefix[] = "NETLINK_";
+               const size_t netlink_prefix_len =
+                       sizeof(netlink_prefix) -1;
+               if (strncmp(netlink_proto, netlink_prefix,
+                           netlink_prefix_len) == 0)
+                       netlink_proto += netlink_prefix_len;
+               if (asprintf(&details, "%s:[%s:%u]", proto_name,
+                            netlink_proto, diag_msg->ndiag_portid) < 0)
+                       return -1;
+       } else {
+               if (asprintf(&details, "%s:[%u]", proto_name,
+                            (unsigned) diag_msg->ndiag_protocol) < 0)
+                       return -1;
+       }
+
+       return cache_and_print_inode_details(inode, details);
+}
+
+static bool
+unix_print(const int fd, const unsigned long inode)
+{
+       return unix_send_query(fd, inode)
+               && receive_responses(fd, inode, "UNIX", unix_parse_response);
+}
+
+static bool
+tcp_v4_print(const int fd, const unsigned long inode)
+{
+       return inet_print(fd, AF_INET, IPPROTO_TCP, inode, "TCP");
+}
+
+static bool
+udp_v4_print(const int fd, const unsigned long inode)
+{
+       return inet_print(fd, AF_INET, IPPROTO_UDP, inode, "UDP");
+}
+
+static bool
+tcp_v6_print(const int fd, const unsigned long inode)
+{
+       return inet_print(fd, AF_INET6, IPPROTO_TCP, inode, "TCPv6");
+}
+
+static bool
+udp_v6_print(const int fd, const unsigned long inode)
+{
+       return inet_print(fd, AF_INET6, IPPROTO_UDP, inode, "UDPv6");
+}
+
+static bool
+netlink_print(const int fd, const unsigned long inode)
+{
+       return netlink_send_query(fd, inode)
+               && receive_responses(fd, inode, "NETLINK",
+                                    netlink_parse_response);
 }
 
 /* Given an inode number of a socket, print out the details
  * of the ip address and port. */
 bool
-print_sockaddr_by_inode(const unsigned long inode)
+print_sockaddr_by_inode(const unsigned long inode, const char *const proto_name)
 {
-       const int families[] = {AF_INET, AF_INET6};
-       const int protocols[] = {IPPROTO_TCP, IPPROTO_UDP};
-       const size_t flen = ARRAY_SIZE(families);
-       const size_t plen = ARRAY_SIZE(protocols);
-       size_t fi, pi;
-       int fd;
+       static const struct {
+               const char *const name;
+               bool (*const print)(int, unsigned long);
+       } protocols[] = {
+               { "TCP", tcp_v4_print },
+               { "UDP", udp_v4_print },
+               { "TCPv6", tcp_v6_print },
+               { "UDPv6", udp_v6_print },
+               { "UNIX", unix_print },
+               { "NETLINK", netlink_print }
+       };
 
-       fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG);
+       const int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
        if (fd < 0)
                return false;
+       bool r = false;
+       unsigned int i;
 
-       for (fi = 0; fi < flen; ++fi) {
-               for (pi = 0; pi < plen; ++pi) {
-                       if (!send_query(fd, families[fi], protocols[pi]))
-                               continue;
-                       if (receive_responses(fd, inode)) {
-                               close(fd);
-                               return true;
+       if (proto_name) {
+               for (i = 0; i < ARRAY_SIZE(protocols); ++i) {
+                       if (strcmp(proto_name, protocols[i].name) == 0) {
+                               r = protocols[i].print(fd, inode);
+                               break;
                        }
                }
+
+               if (!r) {
+                       tprintf("%s:[%lu]", proto_name, inode);
+                       r = true;
+               }
+       } else {
+               for (i = 0; i < ARRAY_SIZE(protocols); ++i) {
+                       if ((r = protocols[i].print(fd, inode)))
+                               break;
+               }
        }
 
        close(fd);
-       return false;
+       return r;
 }