]> granicus.if.org Git - strace/blobdiff - socketutils.c
tests: enhance test coverage of evdev ioctl
[strace] / socketutils.c
index ff0255941b12ab29f61e4379cd9d455db1d5ccb3..a646b5b2ce9ab0ad09bbc5ea846e47afb58c3ead 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 2014 Zubin Mithra <zubin.mithra@gmail.com>
  * Copyright (c) 2014-2016 Dmitry V. Levin <ldv@altlinux.org>
+ * Copyright (c) 2014-2018 The strace developers.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
-#include <linux/netlink.h>
+#include "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
+#if HAVE_LINUX_GENETLINK_H
+#include <linux/genetlink.h>
 #endif
 
 #include <sys/un.h>
@@ -47,6 +46,8 @@
 # define UNIX_PATH_MAX sizeof(((struct sockaddr_un *) 0)->sun_path)
 #endif
 
+#include "xstring.h"
+
 typedef struct {
        unsigned long inode;
        char *details;
@@ -57,30 +58,36 @@ 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_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)
+static const char *
+get_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 (e && inode == e->inode) ? e->details : NULL;
+}
+
+static bool
+print_sockaddr_by_inode_cached(const unsigned long inode)
+{
+       const char *const details = get_sockaddr_by_inode_cached(inode);
+       if (details) {
+               tprints(details);
                return true;
        }
        return false;
 }
 
 static bool
-send_query(const int fd, void *req, size_t req_size)
+send_query(struct tcb *tcp, const int fd, void *req, size_t req_size)
 {
        struct sockaddr_nl nladdr = {
                .nl_family = AF_NETLINK
@@ -107,7 +114,8 @@ send_query(const int fd, void *req, size_t req_size)
 }
 
 static bool
-inet_send_query(const int fd, const int family, const int proto)
+inet_send_query(struct tcb *tcp, const int fd, const int family,
+               const int proto)
 {
        struct {
                const struct nlmsghdr nlh;
@@ -124,13 +132,14 @@ inet_send_query(const int fd, const int family, const int proto)
                        .idiag_states = -1
                }
        };
-       return send_query(fd, &req, sizeof(req));
+       return send_query(tcp, 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)
+inet_parse_response(const void *const data, const int data_len,
+                   const unsigned long inode, void *opaque_data)
 {
+       const char *const proto_name = opaque_data;
        const struct inet_diag_msg *const diag_msg = data;
        static const char zero_addr[sizeof(struct in6_addr)];
        socklen_t addr_size, text_size;
@@ -140,7 +149,7 @@ inet_parse_response(const char *const proto_name, const void *const data,
        if (diag_msg->idiag_inode != inode)
                return 0;
 
-       switch(diag_msg->idiag_family) {
+       switch (diag_msg->idiag_family) {
                case AF_INET:
                        addr_size = sizeof(struct in_addr);
                        text_size = INET_ADDRSTRLEN;
@@ -156,6 +165,10 @@ inet_parse_response(const char *const proto_name, const void *const data,
        char src_buf[text_size];
        char *details;
 
+       /* open/closing brackets for IPv6 addresses */
+       const char *ob = diag_msg->idiag_family == AF_INET6 ? "[" : "";
+       const char *cb = diag_msg->idiag_family == AF_INET6 ? "]" : "";
+
        if (!inet_ntop(diag_msg->idiag_family, diag_msg->id.idiag_src,
                       src_buf, text_size))
                return -1;
@@ -168,24 +181,27 @@ inet_parse_response(const char *const proto_name, const void *const data,
                               dst_buf, text_size))
                        return -1;
 
-               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)
+               if (asprintf(&details, "%s:[%s%s%s:%u->%s%s%s:%u]", proto_name,
+                            ob, src_buf, cb, ntohs(diag_msg->id.idiag_sport),
+                            ob, dst_buf, cb, ntohs(diag_msg->id.idiag_dport))
+                   < 0)
                        return false;
        } else {
-               if (asprintf(&details, "%s:[%s:%u]", proto_name, src_buf,
+               if (asprintf(&details, "%s:[%s%s%s:%u]",
+                            proto_name, ob, src_buf, cb,
                             ntohs(diag_msg->id.idiag_sport)) < 0)
                        return false;
        }
 
-       return cache_and_print_inode_details(inode, details);
+       return cache_inode_details(inode, details);
 }
 
 static bool
-receive_responses(const int fd, const unsigned long inode,
-                 const char *proto_name,
-                 int (* parser) (const char *, const void *,
-                                 int, unsigned long))
+receive_responses(struct tcb *tcp, const int fd, const unsigned long inode,
+                 const unsigned long expected_msg_type,
+                 int (*parser)(const void *, int,
+                               unsigned long, void *),
+                 void *opaque_data)
 {
        static union {
                struct nlmsghdr hdr;
@@ -220,10 +236,10 @@ receive_responses(const int fd, const unsigned long inode,
                if (!NLMSG_OK(h, ret))
                        return false;
                for (; NLMSG_OK(h, ret); h = NLMSG_NEXT(h, ret)) {
-                       if (h->nlmsg_type != SOCK_DIAG_BY_FAMILY)
+                       if (h->nlmsg_type != expected_msg_type)
                                return false;
-                       const int rc = parser(proto_name, NLMSG_DATA(h),
-                                             h->nlmsg_len, inode);
+                       const int rc = parser(NLMSG_DATA(h),
+                                             h->nlmsg_len, inode, opaque_data);
                        if (rc > 0)
                                return true;
                        if (rc < 0)
@@ -234,16 +250,15 @@ receive_responses(const int fd, const unsigned long inode,
 }
 
 static bool
-inet_print(const int fd, const int family, const int protocol,
-          const unsigned long inode, const char *proto_name)
+unix_send_query(struct tcb *tcp, const int fd, const unsigned long inode)
 {
-       return inet_send_query(fd, family, protocol)
-               && receive_responses(fd, inode, proto_name, inet_parse_response);
-}
+       /*
+        * The kernel bug was fixed in mainline by commit v4.5-rc6~35^2~11
+        * and backported to stable/linux-4.4.y by commit v4.4.4~297.
+        */
+       const uint16_t dump_flag =
+               os_release < KERNEL_VERSION(4, 4, 4) ? NLM_F_DUMP : 0;
 
-static bool
-unix_send_query(const int fd, const unsigned long inode)
-{
        struct {
                const struct nlmsghdr nlh;
                const struct unix_diag_req udr;
@@ -251,22 +266,24 @@ unix_send_query(const int fd, const unsigned long inode)
                .nlh = {
                        .nlmsg_len = sizeof(req),
                        .nlmsg_type = SOCK_DIAG_BY_FAMILY,
-                       .nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST
+                       .nlmsg_flags = NLM_F_REQUEST | dump_flag
                },
                .udr = {
                        .sdiag_family = AF_UNIX,
                        .udiag_ino = inode,
                        .udiag_states = -1,
-                       .udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER
+                       .udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER,
+                       .udiag_cookie = { ~0U, ~0U }
                }
        };
-       return send_query(fd, &req, sizeof(req));
+       return send_query(tcp, fd, &req, sizeof(req));
 }
 
 static int
-unix_parse_response(const char *proto_name, const void *data,
-                   const int data_len, const unsigned long inode)
+unix_parse_response(const void *data, const int data_len,
+                   const unsigned long inode, void *opaque_data)
 {
+       const char *proto_name = opaque_data;
        const struct unix_diag_msg *diag_msg = data;
        struct rtattr *attr;
        int rta_len = data_len - NLMSG_LENGTH(sizeof(*diag_msg));
@@ -310,7 +327,7 @@ unix_parse_response(const char *proto_name, const void *data,
 
        char peer_str[3 + sizeof(peer) * 3];
        if (peer)
-               snprintf(peer_str, sizeof(peer_str), "->%u", peer);
+               xsprintf(peer_str, "->%u", peer);
        else
                peer_str[0] = '\0';
 
@@ -322,10 +339,10 @@ unix_parse_response(const char *proto_name, const void *data,
                if (path[0] == '\0') {
                        outstr[1] = '@';
                        string_quote(path + 1, outstr + 2,
-                                    path_len - 1, QUOTE_0_TERMINATED);
+                                    path_len - 1, QUOTE_0_TERMINATED, NULL);
                } else {
                        string_quote(path, outstr + 1,
-                                    path_len, QUOTE_0_TERMINATED);
+                                    path_len, QUOTE_0_TERMINATED, NULL);
                }
                path_str = outstr;
        } else {
@@ -337,11 +354,11 @@ unix_parse_response(const char *proto_name, const void *data,
                     peer_str, path_str) < 0)
                return -1;
 
-       return cache_and_print_inode_details(inode, details);
+       return cache_inode_details(inode, details);
 }
 
 static bool
-netlink_send_query(const int fd, const unsigned long inode)
+netlink_send_query(struct tcb *tcp, const int fd, const unsigned long inode)
 {
        struct {
                const struct nlmsghdr nlh;
@@ -354,17 +371,17 @@ netlink_send_query(const int fd, const unsigned long inode)
                },
                .ndr = {
                        .sdiag_family = AF_NETLINK,
-                       .sdiag_protocol = NDIAG_PROTO_ALL,
-                       .ndiag_show = NDIAG_SHOW_MEMINFO
+                       .sdiag_protocol = NDIAG_PROTO_ALL
                }
        };
-       return send_query(fd, &req, sizeof(req));
+       return send_query(tcp, fd, &req, sizeof(req));
 }
 
 static int
-netlink_parse_response(const char *proto_name, const void *data,
-                   const int data_len, const unsigned long inode)
+netlink_parse_response(const void *data, const int data_len,
+                      const unsigned long inode, void *opaque_data)
 {
+       const char *proto_name = opaque_data;
        const struct netlink_diag_msg *const diag_msg = data;
        const char *netlink_proto;
        char *details;
@@ -381,12 +398,7 @@ netlink_parse_response(const char *proto_name, const void *data,
                                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;
+               netlink_proto = STR_STRIP_PREFIX(netlink_proto, "NETLINK_");
                if (asprintf(&details, "%s:[%s:%u]", proto_name,
                             netlink_proto, diag_msg->ndiag_portid) < 0)
                        return -1;
@@ -396,58 +408,71 @@ netlink_parse_response(const char *proto_name, const void *data,
                        return -1;
        }
 
-       return cache_and_print_inode_details(inode, details);
+       return cache_inode_details(inode, details);
 }
 
-static bool
-unix_print(const int fd, const unsigned long inode)
+static const char *
+unix_get(struct tcb *tcp, const int fd, const unsigned long inode)
 {
-       return unix_send_query(fd, inode)
-               && receive_responses(fd, inode, "UNIX", unix_parse_response);
+       return unix_send_query(tcp, fd, inode)
+               && receive_responses(tcp, fd, inode, SOCK_DIAG_BY_FAMILY,
+                                    unix_parse_response, (void *) "UNIX")
+               ? get_sockaddr_by_inode_cached(inode) : NULL;
 }
 
-static bool
-tcp_v4_print(const int fd, const unsigned long inode)
+static const char *
+inet_get(struct tcb *tcp, const int fd, const int family, const int protocol,
+        const unsigned long inode, const char *proto_name)
 {
-       return inet_print(fd, AF_INET, IPPROTO_TCP, inode, "TCP");
+       return inet_send_query(tcp, fd, family, protocol)
+               && receive_responses(tcp, fd, inode, SOCK_DIAG_BY_FAMILY,
+                                    inet_parse_response, (void *) proto_name)
+               ? get_sockaddr_by_inode_cached(inode) : NULL;
 }
 
-static bool
-udp_v4_print(const int fd, const unsigned long inode)
+static const char *
+tcp_v4_get(struct tcb *tcp, const int fd, const unsigned long inode)
 {
-       return inet_print(fd, AF_INET, IPPROTO_UDP, inode, "UDP");
+       return inet_get(tcp, fd, AF_INET, IPPROTO_TCP, inode, "TCP");
 }
 
-static bool
-tcp_v6_print(const int fd, const unsigned long inode)
+static const char *
+udp_v4_get(struct tcb *tcp, const int fd, const unsigned long inode)
 {
-       return inet_print(fd, AF_INET6, IPPROTO_TCP, inode, "TCPv6");
+       return inet_get(tcp, fd, AF_INET, IPPROTO_UDP, inode, "UDP");
 }
 
-static bool
-udp_v6_print(const int fd, const unsigned long inode)
+static const char *
+tcp_v6_get(struct tcb *tcp, const int fd, const unsigned long inode)
 {
-       return inet_print(fd, AF_INET6, IPPROTO_UDP, inode, "UDPv6");
+       return inet_get(tcp, fd, AF_INET6, IPPROTO_TCP, inode, "TCPv6");
 }
 
-static bool
-netlink_print(const int fd, const unsigned long inode)
+static const char *
+udp_v6_get(struct tcb *tcp, const int fd, const unsigned long inode)
+{
+       return inet_get(tcp, fd, AF_INET6, IPPROTO_UDP, inode, "UDPv6");
+}
+
+static const char *
+netlink_get(struct tcb *tcp, const int fd, const unsigned long inode)
 {
-       return netlink_send_query(fd, inode)
-               && receive_responses(fd, inode, "NETLINK",
-                                    netlink_parse_response);
+       return netlink_send_query(tcp, fd, inode)
+               && receive_responses(tcp, fd, inode, SOCK_DIAG_BY_FAMILY,
+                                    netlink_parse_response, (void *) "NETLINK")
+               ? get_sockaddr_by_inode_cached(inode) : NULL;
 }
 
 static const struct {
        const char *const name;
-       bool (*const print)(int, unsigned long);
+       const char * (*const get)(struct tcb *, int, unsigned long);
 } protocols[] = {
-       [SOCK_PROTO_UNIX] = { "UNIX", unix_print },
-       [SOCK_PROTO_TCP] = { "TCP", tcp_v4_print },
-       [SOCK_PROTO_UDP] = { "UDP", udp_v4_print },
-       [SOCK_PROTO_TCPv6] = { "TCPv6", tcp_v6_print },
-       [SOCK_PROTO_UDPv6] = { "UDPv6", udp_v6_print },
-       [SOCK_PROTO_NETLINK] = { "NETLINK", netlink_print }
+       [SOCK_PROTO_UNIX] = { "UNIX", unix_get },
+       [SOCK_PROTO_TCP] = { "TCP", tcp_v4_get },
+       [SOCK_PROTO_UDP] = { "UDP", udp_v4_get },
+       [SOCK_PROTO_TCPv6] = { "TCPv6", tcp_v6_get },
+       [SOCK_PROTO_UDPv6] = { "UDPv6", udp_v6_get },
+       [SOCK_PROTO_NETLINK] = { "NETLINK", netlink_get }
 };
 
 enum sock_proto
@@ -462,39 +487,180 @@ get_proto_by_name(const char *const name)
        return SOCK_PROTO_UNKNOWN;
 }
 
-/* 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, const enum sock_proto proto)
+static const char *
+get_sockaddr_by_inode_uncached(struct tcb *tcp, const unsigned long inode,
+                              const enum sock_proto proto)
 {
        if ((unsigned int) proto >= ARRAY_SIZE(protocols) ||
-           (proto != SOCK_PROTO_UNKNOWN && !protocols[proto].print))
-               return false;
+           (proto != SOCK_PROTO_UNKNOWN && !protocols[proto].get))
+               return NULL;
 
        const int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
        if (fd < 0)
-               return false;
-       bool r = false;
+               return NULL;
+       const char *details = NULL;
 
        if (proto != SOCK_PROTO_UNKNOWN) {
-               r = protocols[proto].print(fd, inode);
-               if (!r) {
-                       tprintf("%s:[%lu]", protocols[proto].name, inode);
-                       r = true;
-               }
+               details = protocols[proto].get(tcp, fd, inode);
        } else {
                unsigned int i;
                for (i = (unsigned int) SOCK_PROTO_UNKNOWN + 1;
                     i < ARRAY_SIZE(protocols); ++i) {
-                       if (!protocols[i].print)
+                       if (!protocols[i].get)
                                continue;
-                       r = protocols[i].print(fd, inode);
-                       if (r)
+                       details = protocols[i].get(tcp, fd, inode);
+                       if (details)
                                break;
                }
        }
 
        close(fd);
-       return r;
+       return details;
+}
+
+static bool
+print_sockaddr_by_inode_uncached(struct tcb *tcp, const unsigned long inode,
+                                const enum sock_proto proto)
+{
+       const char *details = get_sockaddr_by_inode_uncached(tcp, inode, proto);
+
+       if (details) {
+               tprints(details);
+               return true;
+       }
+
+       if ((unsigned int) proto < ARRAY_SIZE(protocols) &&
+           protocols[proto].name) {
+               tprintf("%s:[%lu]", protocols[proto].name, inode);
+               return true;
+       }
+
+       return false;
+}
+
+/* Given an inode number of a socket, return its protocol details.  */
+const char *
+get_sockaddr_by_inode(struct tcb *const tcp, const int fd,
+                     const unsigned long inode)
+{
+       const char *details = get_sockaddr_by_inode_cached(inode);
+       return details ? details :
+               get_sockaddr_by_inode_uncached(tcp, inode, getfdproto(tcp, fd));
+}
+
+/* Given an inode number of a socket, print out its protocol details.  */
+bool
+print_sockaddr_by_inode(struct tcb *const tcp, const int fd,
+                       const unsigned long inode)
+{
+       return print_sockaddr_by_inode_cached(inode) ? true :
+               print_sockaddr_by_inode_uncached(tcp, inode,
+                                                getfdproto(tcp, fd));
+}
+
+#ifdef HAVE_LINUX_GENETLINK_H
+/*
+ * Managing the cache for decoding communications of Netlink GENERIC protocol
+ *
+ * As name shown Netlink GENERIC protocol is generic protocol. The
+ * numbers of msg types used in the protocol are not defined
+ * statically. Kernel defines them on demand.  So the xlat converted
+ * from header files doesn't help for decoding the protocol. Following
+ * codes are building xlat(dyxlat) at runtime.
+ */
+static bool
+genl_send_dump_families(struct tcb *tcp, const int fd)
+{
+       struct {
+               const struct nlmsghdr nlh;
+               struct genlmsghdr gnlh;
+       } req = {
+               .nlh = {
+                       .nlmsg_len = sizeof(req),
+                       .nlmsg_type = GENL_ID_CTRL,
+                       .nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+               },
+               .gnlh = {
+                       .cmd = CTRL_CMD_GETFAMILY,
+               }
+       };
+       return send_query(tcp, fd, &req, sizeof(req));
 }
+
+static int
+genl_parse_families_response(const void *const data,
+                            const int data_len, const unsigned long inode,
+                            void *opaque_data)
+{
+       struct dyxlat *const dyxlat = opaque_data;
+       const struct genlmsghdr *const gnlh = data;
+       struct rtattr *attr;
+       int rta_len = data_len - NLMSG_LENGTH(sizeof(*gnlh));
+
+       char *name = NULL;
+       unsigned int name_len = 0;
+       uint16_t *id = NULL;
+
+       if (rta_len < 0)
+               return -1;
+       if (gnlh->cmd != CTRL_CMD_NEWFAMILY)
+               return -1;
+       if (gnlh->version != 2)
+               return -1;
+
+       for (attr = (struct rtattr *) (gnlh + 1);
+            RTA_OK(attr, rta_len);
+            attr = RTA_NEXT(attr, rta_len)) {
+               switch (attr->rta_type) {
+               case CTRL_ATTR_FAMILY_NAME:
+                       if (!name) {
+                               name = RTA_DATA(attr);
+                               name_len = RTA_PAYLOAD(attr);
+                       }
+                       break;
+               case CTRL_ATTR_FAMILY_ID:
+                       if (!id && RTA_PAYLOAD(attr) == sizeof(*id))
+                               id = RTA_DATA(attr);
+                       break;
+               }
+
+               if (name && id) {
+                       dyxlat_add_pair(dyxlat, *id, name, name_len);
+                       name = NULL;
+                       id = NULL;
+               }
+       }
+
+       return 0;
+}
+
+const struct xlat *
+genl_families_xlat(struct tcb *tcp)
+{
+       static struct dyxlat *dyxlat;
+
+       if (!dyxlat) {
+               dyxlat = dyxlat_alloc(32);
+
+               int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+               if (fd < 0)
+                       goto out;
+
+               if (genl_send_dump_families(tcp, fd))
+                       receive_responses(tcp, fd, 0, GENL_ID_CTRL,
+                                         genl_parse_families_response, dyxlat);
+               close(fd);
+       }
+
+out:
+       return dyxlat_get(dyxlat);
+}
+
+#else /* !HAVE_LINUX_GENETLINK_H */
+
+const struct xlat *
+genl_families_xlat(struct tcb *tcp)
+{
+       return NULL;
+}
+#endif