X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=socketutils.c;h=a646b5b2ce9ab0ad09bbc5ea846e47afb58c3ead;hb=ed29ac33bbe541cc10c51bef57cd76011df39e5d;hp=ff0255941b12ab29f61e4379cd9d455db1d5ccb3;hpb=802cc28f399951da292c1ece6d4fe039470525fa;p=strace diff --git a/socketutils.c b/socketutils.c index ff025594..a646b5b2 100644 --- a/socketutils.c +++ b/socketutils.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2014 Zubin Mithra * Copyright (c) 2014-2016 Dmitry V. Levin + * Copyright (c) 2014-2018 The strace developers. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,16 +31,14 @@ #include #include #include -#include +#include "netlink.h" #include #include #include #include #include -#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 #endif #include @@ -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