You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
961 lines
28 KiB
961 lines
28 KiB
/* SPDX-License-Identifier: LGPL-2.1-only */
|
|
/*
|
|
* lib/idiag/idiagnl_msg_obj.c Inet Diag Message Object
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
|
|
*/
|
|
|
|
#include <netlink-private/netlink.h>
|
|
#include <netlink/hashtable.h>
|
|
#include <netlink/idiag/msg.h>
|
|
#include <netlink/idiag/meminfo.h>
|
|
#include <netlink/idiag/vegasinfo.h>
|
|
#include <linux/inet_diag.h>
|
|
|
|
|
|
/** @cond SKIP */
|
|
#define IDIAGNL_ATTR_FAMILY (0x1 << 1)
|
|
#define IDIAGNL_ATTR_STATE (0x1 << 2)
|
|
#define IDIAGNL_ATTR_TIMER (0x1 << 3)
|
|
#define IDIAGNL_ATTR_RETRANS (0x1 << 4)
|
|
#define IDIAGNL_ATTR_SPORT (0x1 << 5)
|
|
#define IDIAGNL_ATTR_DPORT (0x1 << 6)
|
|
#define IDIAGNL_ATTR_SRC (0x1 << 7)
|
|
#define IDIAGNL_ATTR_DST (0x1 << 8)
|
|
#define IDIAGNL_ATTR_IFINDEX (0x1 << 9)
|
|
#define IDIAGNL_ATTR_EXPIRES (0x1 << 10)
|
|
#define IDIAGNL_ATTR_RQUEUE (0x1 << 11)
|
|
#define IDIAGNL_ATTR_WQUEUE (0x1 << 12)
|
|
#define IDIAGNL_ATTR_UID (0x1 << 13)
|
|
#define IDIAGNL_ATTR_INODE (0x1 << 14)
|
|
#define IDIAGNL_ATTR_TOS (0x1 << 15)
|
|
#define IDIAGNL_ATTR_TCLASS (0x1 << 16)
|
|
#define IDIAGNL_ATTR_SHUTDOWN (0x1 << 17)
|
|
#define IDIAGNL_ATTR_CONG (0x1 << 18)
|
|
#define IDIAGNL_ATTR_MEMINFO (0x1 << 19)
|
|
#define IDIAGNL_ATTR_VEGASINFO (0x1 << 20)
|
|
#define IDIAGNL_ATTR_TCPINFO (0x1 << 21)
|
|
#define IDIAGNL_ATTR_SKMEMINFO (0x1 << 22)
|
|
|
|
#define _INET_DIAG_ALL ((1<<(INET_DIAG_MAX+1))-1)
|
|
/** @endcond */
|
|
|
|
/**
|
|
* @ingroup idiag
|
|
* @defgroup idiagnl_msg Inet Diag Messages
|
|
*
|
|
* @details
|
|
* @idiagnl_doc{idiagnl_msg, Inet Diag Message Documentation}
|
|
* @{
|
|
*/
|
|
struct idiagnl_msg *idiagnl_msg_alloc(void)
|
|
{
|
|
return (struct idiagnl_msg *) nl_object_alloc(&idiagnl_msg_obj_ops);
|
|
}
|
|
|
|
void idiagnl_msg_get(struct idiagnl_msg *msg)
|
|
{
|
|
nl_object_get((struct nl_object *) msg);
|
|
}
|
|
|
|
void idiagnl_msg_put(struct idiagnl_msg *msg)
|
|
{
|
|
nl_object_put((struct nl_object *) msg);
|
|
}
|
|
|
|
static struct nl_cache_ops idiagnl_msg_ops;
|
|
|
|
static int idiagnl_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
|
|
struct nlmsghdr *nlh, struct nl_parser_param *pp)
|
|
{
|
|
struct idiagnl_msg *msg = NULL;
|
|
int err = 0;
|
|
|
|
if ((err = idiagnl_msg_parse(nlh, &msg)) < 0)
|
|
return err;
|
|
|
|
err = pp->pp_cb((struct nl_object *) msg, pp);
|
|
idiagnl_msg_put(msg);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int idiagnl_request_update(struct nl_cache *cache, struct nl_sock *sk)
|
|
{
|
|
int family = cache->c_iarg1;
|
|
int states = cache->c_iarg2;
|
|
|
|
/* idiagnl_send_simple()'s "ext" argument is u16, which is too small for _INET_DIAG_ALL,
|
|
* which is more than 16 bits on recent kernels.
|
|
*
|
|
* Actually, internally idiagnl_send_simple() sets "struct inet_diag_req"'s "idiag_ext"
|
|
* field, which is only 8 bits. So, it's even worse.
|
|
*
|
|
* FIXME: this probably should be fixed (by adding idiagnl_send_simple2() function), but for
|
|
* the moment it means we cannot request more than 0xFF.
|
|
*/
|
|
|
|
return idiagnl_send_simple(sk, 0, family, states, (uint16_t) _INET_DIAG_ALL);
|
|
}
|
|
|
|
static struct nl_cache_ops idiagnl_msg_ops = {
|
|
.co_name = "idiag/idiag",
|
|
.co_hdrsize = sizeof(struct inet_diag_msg),
|
|
.co_msgtypes = {
|
|
{ TCPDIAG_GETSOCK, NL_ACT_NEW, "new" },
|
|
{ DCCPDIAG_GETSOCK, NL_ACT_NEW, "new" },
|
|
END_OF_MSGTYPES_LIST,
|
|
},
|
|
.co_protocol = NETLINK_INET_DIAG,
|
|
.co_request_update = idiagnl_request_update,
|
|
.co_msg_parser = idiagnl_msg_parser,
|
|
.co_obj_ops = &idiagnl_msg_obj_ops,
|
|
};
|
|
|
|
static void __init idiagnl_init(void)
|
|
{
|
|
nl_cache_mngt_register(&idiagnl_msg_ops);
|
|
}
|
|
|
|
static void __exit idiagnl_exit(void)
|
|
{
|
|
nl_cache_mngt_unregister(&idiagnl_msg_ops);
|
|
}
|
|
|
|
/**
|
|
* @name Cache Management
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* Build an inetdiag cache to hold socket state information.
|
|
* @arg sk Netlink socket
|
|
* @arg family The address family to query
|
|
* @arg states Socket states to query
|
|
* @arg result Result pointer
|
|
*
|
|
* @note The caller is responsible for destroying and free the cache after using
|
|
* it.
|
|
* @return 0 on success of a negative error code.
|
|
*/
|
|
int idiagnl_msg_alloc_cache(struct nl_sock *sk, int family, int states,
|
|
struct nl_cache **result)
|
|
{
|
|
struct nl_cache *cache = NULL;
|
|
int err;
|
|
|
|
if (!(cache = nl_cache_alloc(&idiagnl_msg_ops)))
|
|
return -NLE_NOMEM;
|
|
|
|
cache->c_iarg1 = family;
|
|
cache->c_iarg2 = states;
|
|
|
|
if (sk && (err = nl_cache_refill(sk, cache)) < 0) {
|
|
free(cache);
|
|
return err;
|
|
}
|
|
|
|
*result = cache;
|
|
return 0;
|
|
}
|
|
|
|
/** @} */
|
|
|
|
/**
|
|
* @name Attributes
|
|
* @{
|
|
*/
|
|
|
|
uint8_t idiagnl_msg_get_family(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_family;
|
|
}
|
|
|
|
void idiagnl_msg_set_family(struct idiagnl_msg *msg, uint8_t family)
|
|
{
|
|
msg->idiag_family = family;
|
|
msg->ce_mask |= IDIAGNL_ATTR_FAMILY;
|
|
}
|
|
|
|
uint8_t idiagnl_msg_get_state(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_state;
|
|
}
|
|
|
|
void idiagnl_msg_set_state(struct idiagnl_msg *msg, uint8_t state)
|
|
{
|
|
msg->idiag_state = state;
|
|
msg->ce_mask |= IDIAGNL_ATTR_STATE;
|
|
}
|
|
|
|
uint8_t idiagnl_msg_get_timer(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_timer;
|
|
}
|
|
|
|
void idiagnl_msg_set_timer(struct idiagnl_msg *msg, uint8_t timer)
|
|
{
|
|
msg->idiag_timer = timer;
|
|
msg->ce_mask |= IDIAGNL_ATTR_TIMER;
|
|
}
|
|
|
|
uint8_t idiagnl_msg_get_retrans(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_retrans;
|
|
}
|
|
|
|
void idiagnl_msg_set_retrans(struct idiagnl_msg *msg, uint8_t retrans)
|
|
{
|
|
msg->idiag_retrans = retrans;
|
|
msg->ce_mask |= IDIAGNL_ATTR_RETRANS;
|
|
}
|
|
|
|
uint16_t idiagnl_msg_get_sport(struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_sport;
|
|
}
|
|
|
|
void idiagnl_msg_set_sport(struct idiagnl_msg *msg, uint16_t port)
|
|
{
|
|
msg->idiag_sport = port;
|
|
msg->ce_mask |= IDIAGNL_ATTR_SPORT;
|
|
}
|
|
|
|
uint16_t idiagnl_msg_get_dport(struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_dport;
|
|
}
|
|
|
|
void idiagnl_msg_set_dport(struct idiagnl_msg *msg, uint16_t port)
|
|
{
|
|
msg->idiag_dport = port;
|
|
msg->ce_mask |= IDIAGNL_ATTR_DPORT;
|
|
}
|
|
|
|
struct nl_addr *idiagnl_msg_get_src(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_src;
|
|
}
|
|
|
|
int idiagnl_msg_set_src(struct idiagnl_msg *msg, struct nl_addr *addr)
|
|
{
|
|
if (msg->idiag_src)
|
|
nl_addr_put(msg->idiag_src);
|
|
|
|
nl_addr_get(addr);
|
|
msg->idiag_src = addr;
|
|
msg->ce_mask |= IDIAGNL_ATTR_SRC;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct nl_addr *idiagnl_msg_get_dst(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_dst;
|
|
}
|
|
|
|
int idiagnl_msg_set_dst(struct idiagnl_msg *msg, struct nl_addr *addr)
|
|
{
|
|
if (msg->idiag_dst)
|
|
nl_addr_put(msg->idiag_dst);
|
|
|
|
nl_addr_get(addr);
|
|
msg->idiag_dst = addr;
|
|
msg->ce_mask |= IDIAGNL_ATTR_DST;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t idiagnl_msg_get_ifindex(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_ifindex;
|
|
}
|
|
|
|
void idiagnl_msg_set_ifindex(struct idiagnl_msg *msg, uint32_t ifindex)
|
|
{
|
|
msg->idiag_ifindex = ifindex;
|
|
msg->ce_mask |= IDIAGNL_ATTR_IFINDEX;
|
|
}
|
|
|
|
uint32_t idiagnl_msg_get_expires(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_expires;
|
|
}
|
|
|
|
void idiagnl_msg_set_expires(struct idiagnl_msg *msg, uint32_t expires)
|
|
{
|
|
msg->idiag_expires = expires;
|
|
msg->ce_mask |= IDIAGNL_ATTR_EXPIRES;
|
|
}
|
|
|
|
uint32_t idiagnl_msg_get_rqueue(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_rqueue;
|
|
}
|
|
|
|
void idiagnl_msg_set_rqueue(struct idiagnl_msg *msg, uint32_t rqueue)
|
|
{
|
|
msg->idiag_rqueue = rqueue;
|
|
msg->ce_mask |= IDIAGNL_ATTR_RQUEUE;
|
|
}
|
|
|
|
uint32_t idiagnl_msg_get_wqueue(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_wqueue;
|
|
}
|
|
|
|
void idiagnl_msg_set_wqueue(struct idiagnl_msg *msg, uint32_t wqueue)
|
|
{
|
|
msg->idiag_wqueue = wqueue;
|
|
msg->ce_mask |= IDIAGNL_ATTR_WQUEUE;
|
|
}
|
|
|
|
uint32_t idiagnl_msg_get_uid(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_uid;
|
|
}
|
|
|
|
void idiagnl_msg_set_uid(struct idiagnl_msg *msg, uint32_t uid)
|
|
{
|
|
msg->idiag_uid = uid;
|
|
msg->ce_mask |= IDIAGNL_ATTR_UID;
|
|
}
|
|
|
|
uint32_t idiagnl_msg_get_inode(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_inode;
|
|
}
|
|
|
|
void idiagnl_msg_set_inode(struct idiagnl_msg *msg, uint32_t inode)
|
|
{
|
|
msg->idiag_inode = inode;
|
|
msg->ce_mask |= IDIAGNL_ATTR_INODE;
|
|
}
|
|
|
|
uint8_t idiagnl_msg_get_tos(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_tos;
|
|
}
|
|
|
|
void idiagnl_msg_set_tos(struct idiagnl_msg *msg, uint8_t tos)
|
|
{
|
|
msg->idiag_tos = tos;
|
|
msg->ce_mask |= IDIAGNL_ATTR_TOS;
|
|
}
|
|
|
|
uint8_t idiagnl_msg_get_tclass(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_tclass;
|
|
}
|
|
|
|
void idiagnl_msg_set_tclass(struct idiagnl_msg *msg, uint8_t tclass)
|
|
{
|
|
msg->idiag_tclass = tclass;
|
|
msg->ce_mask |= IDIAGNL_ATTR_TCLASS;
|
|
}
|
|
|
|
uint8_t idiagnl_msg_get_shutdown(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_shutdown;
|
|
}
|
|
|
|
void idiagnl_msg_set_shutdown(struct idiagnl_msg *msg, uint8_t shutdown)
|
|
{
|
|
msg->idiag_shutdown = shutdown;
|
|
msg->ce_mask |= IDIAGNL_ATTR_SHUTDOWN;
|
|
}
|
|
|
|
char *idiagnl_msg_get_cong(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_cong;
|
|
}
|
|
|
|
void idiagnl_msg_set_cong(struct idiagnl_msg *msg, char *cong)
|
|
{
|
|
free (msg->idiag_cong);
|
|
msg->idiag_cong = strdup(cong);
|
|
msg->ce_mask |= IDIAGNL_ATTR_CONG;
|
|
}
|
|
|
|
struct idiagnl_meminfo *idiagnl_msg_get_meminfo(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_meminfo;
|
|
}
|
|
|
|
void idiagnl_msg_set_meminfo(struct idiagnl_msg *msg, struct idiagnl_meminfo *minfo)
|
|
{
|
|
if (msg->idiag_meminfo)
|
|
idiagnl_meminfo_put(msg->idiag_meminfo);
|
|
|
|
idiagnl_meminfo_get(minfo);
|
|
msg->idiag_meminfo = minfo;
|
|
msg->ce_mask |= IDIAGNL_ATTR_MEMINFO;
|
|
}
|
|
|
|
struct idiagnl_vegasinfo *idiagnl_msg_get_vegasinfo(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_vegasinfo;
|
|
}
|
|
|
|
void idiagnl_msg_set_vegasinfo(struct idiagnl_msg *msg, struct idiagnl_vegasinfo *vinfo)
|
|
{
|
|
if (msg->idiag_vegasinfo)
|
|
idiagnl_vegasinfo_put(msg->idiag_vegasinfo);
|
|
|
|
idiagnl_vegasinfo_get(vinfo);
|
|
msg->idiag_vegasinfo = vinfo;
|
|
msg->ce_mask |= IDIAGNL_ATTR_VEGASINFO;
|
|
}
|
|
|
|
struct tcp_info idiagnl_msg_get_tcpinfo(const struct idiagnl_msg *msg)
|
|
{
|
|
return msg->idiag_tcpinfo;
|
|
}
|
|
|
|
void idiagnl_msg_set_tcpinfo(struct idiagnl_msg *msg, struct tcp_info *tinfo)
|
|
{
|
|
memcpy(&msg->idiag_tcpinfo, tinfo, sizeof(struct tcp_info));
|
|
msg->ce_mask |= IDIAGNL_ATTR_TCPINFO;
|
|
}
|
|
|
|
/** @} */
|
|
|
|
static void idiag_msg_dump_line(struct nl_object *a, struct nl_dump_params *p)
|
|
{
|
|
struct idiagnl_msg *msg = (struct idiagnl_msg *) a;
|
|
char buf[64] = { 0 };
|
|
|
|
nl_dump_line(p, "family: %s ", nl_af2str(msg->idiag_family, buf, sizeof(buf)));
|
|
nl_dump(p, "src: %s:%d ", nl_addr2str(msg->idiag_src, buf, sizeof(buf)),
|
|
ntohs(msg->idiag_sport));
|
|
nl_dump(p, "dst: %s:%d ", nl_addr2str(msg->idiag_dst, buf, sizeof(buf)),
|
|
ntohs(msg->idiag_dport));
|
|
nl_dump(p, "iif: %d ", msg->idiag_ifindex);
|
|
nl_dump(p, "\n");
|
|
}
|
|
|
|
static void idiag_msg_dump_details(struct nl_object *a, struct nl_dump_params *p)
|
|
{
|
|
struct idiagnl_msg *msg = (struct idiagnl_msg *) a;
|
|
char buf[64], buf2[64];
|
|
|
|
nl_dump(p, "\nfamily: %s\n", nl_af2str(msg->idiag_family, buf, sizeof(buf)));
|
|
nl_dump(p, "state: %s\n",
|
|
idiagnl_state2str(msg->idiag_state, buf, sizeof(buf)));
|
|
nl_dump(p, "timer (%s, %s, retransmits: %d)\n",
|
|
idiagnl_timer2str(msg->idiag_timer, buf, sizeof(buf)),
|
|
nl_msec2str(msg->idiag_expires, buf2, sizeof(buf2)),
|
|
msg->idiag_retrans);
|
|
|
|
nl_dump(p, "source: %s:%d\n", nl_addr2str(msg->idiag_src, buf, sizeof(buf)),
|
|
ntohs(msg->idiag_sport));
|
|
nl_dump(p, "destination: %s:%d\n", nl_addr2str(msg->idiag_dst, buf, sizeof(buf)),
|
|
ntohs(msg->idiag_dport));
|
|
|
|
nl_dump(p, "ifindex: %d\n", msg->idiag_ifindex);
|
|
nl_dump(p, "rqueue: %-6d wqueue: %-6d\n", msg->idiag_rqueue, msg->idiag_wqueue);
|
|
nl_dump(p, "uid %d\n", msg->idiag_uid);
|
|
nl_dump(p, "inode %d\n", msg->idiag_inode);
|
|
if (msg->idiag_shutdown) {
|
|
nl_dump(p, "socket shutdown: %s\n",
|
|
idiagnl_shutdown2str(msg->idiag_shutdown,
|
|
buf, sizeof(buf)));
|
|
}
|
|
|
|
nl_dump(p, "tos: 0x%x\n", msg->idiag_tos);
|
|
nl_dump(p, "traffic class: %d\n", msg->idiag_tclass);
|
|
nl_dump(p, "congestion algorithm: %s\n", msg->idiag_cong ? msg->idiag_cong : "");
|
|
}
|
|
|
|
static void idiag_msg_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
|
|
{
|
|
struct idiagnl_msg *msg = (struct idiagnl_msg *) obj;
|
|
char buf[64];
|
|
|
|
idiag_msg_dump_details(obj, p);
|
|
|
|
nl_dump(p, "tcp info: [\n");
|
|
nl_dump(p, "\tsocket state: %s\n",
|
|
idiagnl_state2str(msg->idiag_tcpinfo.tcpi_state,
|
|
buf, sizeof(buf)));
|
|
nl_dump(p, "\ttcp state: %s\n",
|
|
idiagnl_tcpstate2str(msg->idiag_tcpinfo.tcpi_ca_state,
|
|
buf, sizeof(buf)));
|
|
nl_dump(p, "\tretransmits: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_retransmits);
|
|
nl_dump(p, "\tprobes: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_probes);
|
|
nl_dump(p, "\tbackoff: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_backoff);
|
|
nl_dump(p, "\toptions: %s\n",
|
|
idiagnl_tcpopts2str(msg->idiag_tcpinfo.tcpi_options,
|
|
buf, sizeof(buf)));
|
|
nl_dump(p, "\tsnd_wscale: %d\n", msg->idiag_tcpinfo.tcpi_snd_wscale);
|
|
nl_dump(p, "\trcv_wscale: %d\n", msg->idiag_tcpinfo.tcpi_rcv_wscale);
|
|
nl_dump(p, "\trto: %d\n", msg->idiag_tcpinfo.tcpi_rto);
|
|
nl_dump(p, "\tato: %d\n", msg->idiag_tcpinfo.tcpi_ato);
|
|
nl_dump(p, "\tsnd_mss: %s\n", nl_size2str(msg->idiag_tcpinfo.tcpi_snd_mss,
|
|
buf, sizeof(buf)));
|
|
nl_dump(p, "\trcv_mss: %s\n", nl_size2str(msg->idiag_tcpinfo.tcpi_rcv_mss,
|
|
buf, sizeof(buf)));
|
|
nl_dump(p, "\tunacked: %d\n", msg->idiag_tcpinfo.tcpi_unacked);
|
|
nl_dump(p, "\tsacked: %d\n", msg->idiag_tcpinfo.tcpi_sacked);
|
|
|
|
nl_dump(p, "\tlost: %d\n", msg->idiag_tcpinfo.tcpi_lost);
|
|
nl_dump(p, "\tretransmit segments: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_retrans);
|
|
nl_dump(p, "\tfackets: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_fackets);
|
|
nl_dump(p, "\tlast data sent: %s\n",
|
|
nl_msec2str(msg->idiag_tcpinfo.tcpi_last_data_sent, buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\tlast ack sent: %s\n",
|
|
nl_msec2str(msg->idiag_tcpinfo.tcpi_last_ack_sent, buf, sizeof(buf)));
|
|
nl_dump(p, "\tlast data recv: %s\n",
|
|
nl_msec2str(msg->idiag_tcpinfo.tcpi_last_data_recv, buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\tlast ack recv: %s\n",
|
|
nl_msec2str(msg->idiag_tcpinfo.tcpi_last_ack_recv, buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\tpath mtu: %s\n",
|
|
nl_size2str(msg->idiag_tcpinfo.tcpi_pmtu, buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\trcv ss threshold: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_rcv_ssthresh);
|
|
nl_dump(p, "\tsmoothed round trip time: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_rtt);
|
|
nl_dump(p, "\tround trip time variation: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_rttvar);
|
|
nl_dump(p, "\tsnd ss threshold: %s\n",
|
|
nl_size2str(msg->idiag_tcpinfo.tcpi_snd_ssthresh, buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\tsend congestion window: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_snd_cwnd);
|
|
nl_dump(p, "\tadvertised mss: %s\n",
|
|
nl_size2str(msg->idiag_tcpinfo.tcpi_advmss, buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\treordering: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_reordering);
|
|
nl_dump(p, "\trcv rround trip time: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_rcv_rtt);
|
|
nl_dump(p, "\treceive queue space: %s\n",
|
|
nl_size2str(msg->idiag_tcpinfo.tcpi_rcv_space, buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\ttotal retransmits: %d\n",
|
|
msg->idiag_tcpinfo.tcpi_total_retrans);
|
|
nl_dump(p, "]\n");
|
|
|
|
if (msg->idiag_meminfo) {
|
|
nl_dump(p, "meminfo: [\n");
|
|
nl_dump(p, "\trmem: %s\n",
|
|
nl_size2str(msg->idiag_meminfo->idiag_rmem,
|
|
buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\twmem: %s\n",
|
|
nl_size2str(msg->idiag_meminfo->idiag_wmem,
|
|
buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\tfmem: %s\n",
|
|
nl_size2str(msg->idiag_meminfo->idiag_fmem,
|
|
buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "\ttmem: %s\n",
|
|
nl_size2str(msg->idiag_meminfo->idiag_tmem,
|
|
buf,
|
|
sizeof(buf)));
|
|
nl_dump(p, "]\n");
|
|
}
|
|
|
|
if (msg->idiag_vegasinfo) {
|
|
nl_dump(p, "vegasinfo: [\n");
|
|
nl_dump(p, "\tvegas enabled: %d\n",
|
|
msg->idiag_vegasinfo->tcpv_enabled);
|
|
if (msg->idiag_vegasinfo->tcpv_enabled) {
|
|
nl_dump(p, "\trtt cnt: %d",
|
|
msg->idiag_vegasinfo->tcpv_rttcnt);
|
|
nl_dump(p, "\trtt (propagation delay): %d",
|
|
msg->idiag_vegasinfo->tcpv_rtt);
|
|
nl_dump(p, "\tmin rtt: %d",
|
|
msg->idiag_vegasinfo->tcpv_minrtt);
|
|
}
|
|
nl_dump(p, "]\n");
|
|
}
|
|
|
|
if (msg->ce_mask & IDIAGNL_ATTR_MEMINFO) {
|
|
nl_dump(p, "skmeminfo: [\n");
|
|
nl_dump(p, "\trmem alloc: %d\n",
|
|
msg->idiag_skmeminfo[SK_MEMINFO_RMEM_ALLOC]);
|
|
nl_dump(p, "\trcv buf: %s\n",
|
|
nl_size2str(msg->idiag_skmeminfo[SK_MEMINFO_RCVBUF],
|
|
buf, sizeof(buf)));
|
|
nl_dump(p, "\twmem alloc: %d\n",
|
|
msg->idiag_skmeminfo[SK_MEMINFO_WMEM_ALLOC]);
|
|
nl_dump(p, "\tsnd buf: %s\n",
|
|
nl_size2str(msg->idiag_skmeminfo[SK_MEMINFO_SNDBUF],
|
|
buf, sizeof(buf)));
|
|
nl_dump(p, "\tfwd alloc: %d\n",
|
|
msg->idiag_skmeminfo[SK_MEMINFO_FWD_ALLOC]);
|
|
nl_dump(p, "\twmem queued: %s\n",
|
|
nl_size2str(msg->idiag_skmeminfo[SK_MEMINFO_WMEM_QUEUED],
|
|
buf, sizeof(buf)));
|
|
nl_dump(p, "\topt mem: %d\n",
|
|
msg->idiag_skmeminfo[SK_MEMINFO_OPTMEM]);
|
|
nl_dump(p, "\tbacklog: %d\n",
|
|
msg->idiag_skmeminfo[SK_MEMINFO_BACKLOG]);
|
|
nl_dump(p, "]\n\n");
|
|
}
|
|
}
|
|
|
|
static void idiagnl_msg_free(struct nl_object *a)
|
|
{
|
|
struct idiagnl_msg *msg = (struct idiagnl_msg *) a;
|
|
if (a == NULL)
|
|
return;
|
|
|
|
free(msg->idiag_cong);
|
|
nl_addr_put(msg->idiag_src);
|
|
nl_addr_put(msg->idiag_dst);
|
|
idiagnl_meminfo_put(msg->idiag_meminfo);
|
|
idiagnl_vegasinfo_put(msg->idiag_vegasinfo);
|
|
}
|
|
|
|
static int idiagnl_msg_clone(struct nl_object *_dst, struct nl_object *_src)
|
|
{
|
|
struct idiagnl_msg *dst = (struct idiagnl_msg *) _dst;
|
|
struct idiagnl_msg *src = (struct idiagnl_msg *) _src;
|
|
|
|
dst->idiag_cong = NULL;
|
|
dst->idiag_src = NULL;
|
|
dst->idiag_dst = NULL;
|
|
dst->idiag_meminfo = NULL;
|
|
dst->idiag_vegasinfo = NULL;
|
|
dst->ce_mask &= ~(IDIAGNL_ATTR_CONG |
|
|
IDIAGNL_ATTR_SRC |
|
|
IDIAGNL_ATTR_DST |
|
|
IDIAGNL_ATTR_MEMINFO |
|
|
IDIAGNL_ATTR_VEGASINFO);
|
|
|
|
if (src->idiag_cong) {
|
|
if (!(dst->idiag_cong = strdup(src->idiag_cong)))
|
|
return -NLE_NOMEM;
|
|
dst->ce_mask |= IDIAGNL_ATTR_CONG;
|
|
}
|
|
|
|
if (src->idiag_src) {
|
|
if (!(dst->idiag_src = nl_addr_clone(src->idiag_src)))
|
|
return -NLE_NOMEM;
|
|
dst->ce_mask |= IDIAGNL_ATTR_SRC;
|
|
}
|
|
|
|
if (src->idiag_dst) {
|
|
if (!(dst->idiag_dst = nl_addr_clone(src->idiag_dst)))
|
|
return -NLE_NOMEM;
|
|
dst->ce_mask |= IDIAGNL_ATTR_DST;
|
|
}
|
|
|
|
if (src->idiag_meminfo) {
|
|
if (!(dst->idiag_meminfo = (struct idiagnl_meminfo *) nl_object_clone((struct nl_object *) src->idiag_meminfo)))
|
|
return -NLE_NOMEM;
|
|
dst->ce_mask |= IDIAGNL_ATTR_MEMINFO;
|
|
}
|
|
|
|
if (src->idiag_vegasinfo) {
|
|
if (!(dst->idiag_vegasinfo = (struct idiagnl_vegasinfo *) nl_object_clone((struct nl_object *) src->idiag_vegasinfo)))
|
|
return -NLE_NOMEM;
|
|
dst->ce_mask |= IDIAGNL_ATTR_VEGASINFO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct nla_policy ext_policy[INET_DIAG_MAX+1] = {
|
|
[INET_DIAG_MEMINFO] = { .minlen = sizeof(struct inet_diag_meminfo) },
|
|
[INET_DIAG_INFO] = { .minlen = sizeof(struct tcp_info) },
|
|
[INET_DIAG_VEGASINFO] = { .minlen = sizeof(struct tcpvegas_info) },
|
|
[INET_DIAG_CONG] = { .type = NLA_STRING },
|
|
[INET_DIAG_TOS] = { .type = NLA_U8 },
|
|
[INET_DIAG_TCLASS] = { .type = NLA_U8 },
|
|
/* Older kernel doesn't have SK_MEMINFO_BACKLOG */
|
|
[INET_DIAG_SKMEMINFO] = { .minlen = (sizeof(uint32_t) * (SK_MEMINFO_OPTMEM + 1)) },
|
|
[INET_DIAG_SHUTDOWN] = { .type = NLA_U8 },
|
|
};
|
|
|
|
int idiagnl_msg_parse(struct nlmsghdr *nlh, struct idiagnl_msg **result)
|
|
{
|
|
struct idiagnl_msg *msg = NULL;
|
|
struct inet_diag_msg *raw_msg = NULL;
|
|
struct nl_addr *src = NULL, *dst = NULL;
|
|
struct nlattr *tb[INET_DIAG_MAX+1];
|
|
int err = 0;
|
|
|
|
msg = idiagnl_msg_alloc();
|
|
if (!msg)
|
|
goto errout_nomem;
|
|
|
|
err = nlmsg_parse(nlh, sizeof(struct inet_diag_msg), tb, INET_DIAG_MAX,
|
|
ext_policy);
|
|
if (err < 0)
|
|
goto errout;
|
|
|
|
raw_msg = nlmsg_data(nlh);
|
|
msg->idiag_family = raw_msg->idiag_family;
|
|
msg->idiag_state = raw_msg->idiag_state;
|
|
msg->idiag_timer = raw_msg->idiag_timer;
|
|
msg->idiag_retrans = raw_msg->idiag_retrans;
|
|
msg->idiag_expires = raw_msg->idiag_expires;
|
|
msg->idiag_rqueue = raw_msg->idiag_rqueue;
|
|
msg->idiag_wqueue = raw_msg->idiag_wqueue;
|
|
msg->idiag_uid = raw_msg->idiag_uid;
|
|
msg->idiag_inode = raw_msg->idiag_inode;
|
|
msg->idiag_sport = raw_msg->id.idiag_sport;
|
|
msg->idiag_dport = raw_msg->id.idiag_dport;
|
|
msg->idiag_ifindex = raw_msg->id.idiag_if;
|
|
|
|
msg->ce_mask = (IDIAGNL_ATTR_FAMILY |
|
|
IDIAGNL_ATTR_STATE |
|
|
IDIAGNL_ATTR_TIMER |
|
|
IDIAGNL_ATTR_RETRANS |
|
|
IDIAGNL_ATTR_EXPIRES |
|
|
IDIAGNL_ATTR_RQUEUE |
|
|
IDIAGNL_ATTR_WQUEUE |
|
|
IDIAGNL_ATTR_UID |
|
|
IDIAGNL_ATTR_INODE |
|
|
IDIAGNL_ATTR_SPORT |
|
|
IDIAGNL_ATTR_DPORT |
|
|
IDIAGNL_ATTR_IFINDEX);
|
|
|
|
dst = nl_addr_build(raw_msg->idiag_family, raw_msg->id.idiag_dst,
|
|
sizeof(raw_msg->id.idiag_dst));
|
|
if (!dst)
|
|
goto errout_nomem;
|
|
|
|
err = idiagnl_msg_set_dst(msg, dst);
|
|
if (err < 0)
|
|
goto errout;
|
|
|
|
nl_addr_put(dst);
|
|
|
|
src = nl_addr_build(raw_msg->idiag_family, raw_msg->id.idiag_src,
|
|
sizeof(raw_msg->id.idiag_src));
|
|
if (!src)
|
|
goto errout_nomem;
|
|
|
|
err = idiagnl_msg_set_src(msg, src);
|
|
if (err < 0)
|
|
goto errout;
|
|
|
|
nl_addr_put(src);
|
|
|
|
if (tb[INET_DIAG_TOS]) {
|
|
msg->idiag_tos = nla_get_u8(tb[INET_DIAG_TOS]);
|
|
msg->ce_mask |= IDIAGNL_ATTR_TOS;
|
|
}
|
|
|
|
if (tb[INET_DIAG_TCLASS]) {
|
|
msg->idiag_tclass = nla_get_u8(tb[INET_DIAG_TCLASS]);
|
|
msg->ce_mask |= IDIAGNL_ATTR_TCLASS;
|
|
}
|
|
|
|
if (tb[INET_DIAG_SHUTDOWN]) {
|
|
msg->idiag_shutdown = nla_get_u8(tb[INET_DIAG_SHUTDOWN]);
|
|
msg->ce_mask |= IDIAGNL_ATTR_SHUTDOWN;
|
|
}
|
|
|
|
if (tb[INET_DIAG_CONG]) {
|
|
msg->idiag_cong = nla_strdup(tb[INET_DIAG_CONG]);
|
|
msg->ce_mask |= IDIAGNL_ATTR_CONG;
|
|
}
|
|
|
|
if (tb[INET_DIAG_INFO]) {
|
|
nla_memcpy(&msg->idiag_tcpinfo, tb[INET_DIAG_INFO],
|
|
sizeof(msg->idiag_tcpinfo));
|
|
msg->ce_mask |= IDIAGNL_ATTR_TCPINFO;
|
|
}
|
|
|
|
if (tb[INET_DIAG_MEMINFO]) {
|
|
struct idiagnl_meminfo *minfo = idiagnl_meminfo_alloc();
|
|
struct inet_diag_meminfo *raw_minfo = NULL;
|
|
|
|
if (!minfo)
|
|
goto errout_nomem;
|
|
|
|
raw_minfo = (struct inet_diag_meminfo *)
|
|
nla_data(tb[INET_DIAG_MEMINFO]);
|
|
|
|
idiagnl_meminfo_set_rmem(minfo, raw_minfo->idiag_rmem);
|
|
idiagnl_meminfo_set_wmem(minfo, raw_minfo->idiag_wmem);
|
|
idiagnl_meminfo_set_fmem(minfo, raw_minfo->idiag_fmem);
|
|
idiagnl_meminfo_set_tmem(minfo, raw_minfo->idiag_tmem);
|
|
|
|
msg->idiag_meminfo = minfo;
|
|
msg->ce_mask |= IDIAGNL_ATTR_MEMINFO;
|
|
}
|
|
|
|
if (tb[INET_DIAG_VEGASINFO]) {
|
|
struct idiagnl_vegasinfo *vinfo = idiagnl_vegasinfo_alloc();
|
|
struct tcpvegas_info *raw_vinfo = NULL;
|
|
|
|
if (!vinfo)
|
|
goto errout_nomem;
|
|
|
|
raw_vinfo = (struct tcpvegas_info *)
|
|
nla_data(tb[INET_DIAG_VEGASINFO]);
|
|
|
|
idiagnl_vegasinfo_set_enabled(vinfo, raw_vinfo->tcpv_enabled);
|
|
idiagnl_vegasinfo_set_rttcnt(vinfo, raw_vinfo->tcpv_rttcnt);
|
|
idiagnl_vegasinfo_set_rtt(vinfo, raw_vinfo->tcpv_rtt);
|
|
idiagnl_vegasinfo_set_minrtt(vinfo, raw_vinfo->tcpv_minrtt);
|
|
|
|
msg->idiag_vegasinfo = vinfo;
|
|
msg->ce_mask |= IDIAGNL_ATTR_VEGASINFO;
|
|
}
|
|
|
|
if (tb[INET_DIAG_SKMEMINFO]) {
|
|
nla_memcpy(&msg->idiag_skmeminfo, tb[INET_DIAG_SKMEMINFO],
|
|
sizeof(msg->idiag_skmeminfo));
|
|
msg->ce_mask |= IDIAGNL_ATTR_SKMEMINFO;
|
|
}
|
|
|
|
*result = msg;
|
|
return 0;
|
|
|
|
errout:
|
|
idiagnl_msg_put(msg);
|
|
return err;
|
|
|
|
errout_nomem:
|
|
err = -NLE_NOMEM;
|
|
goto errout;
|
|
}
|
|
|
|
static const struct trans_tbl idiagnl_attrs[] = {
|
|
__ADD(IDIAGNL_ATTR_FAMILY, family),
|
|
__ADD(IDIAGNL_ATTR_STATE, state),
|
|
__ADD(IDIAGNL_ATTR_TIMER, timer),
|
|
__ADD(IDIAGNL_ATTR_RETRANS, retrans),
|
|
__ADD(IDIAGNL_ATTR_SPORT, sport),
|
|
__ADD(IDIAGNL_ATTR_DPORT, dport),
|
|
__ADD(IDIAGNL_ATTR_SRC, src),
|
|
__ADD(IDIAGNL_ATTR_DST, dst),
|
|
__ADD(IDIAGNL_ATTR_IFINDEX, ifindex),
|
|
__ADD(IDIAGNL_ATTR_EXPIRES, expires),
|
|
__ADD(IDIAGNL_ATTR_RQUEUE, rqueue),
|
|
__ADD(IDIAGNL_ATTR_WQUEUE, wqueue),
|
|
__ADD(IDIAGNL_ATTR_UID, uid),
|
|
__ADD(IDIAGNL_ATTR_INODE, inode),
|
|
__ADD(IDIAGNL_ATTR_TOS, tos),
|
|
__ADD(IDIAGNL_ATTR_TCLASS, tclass),
|
|
__ADD(IDIAGNL_ATTR_SHUTDOWN, shutdown),
|
|
__ADD(IDIAGNL_ATTR_CONG, cong),
|
|
__ADD(IDIAGNL_ATTR_MEMINFO, meminfo),
|
|
__ADD(IDIAGNL_ATTR_VEGASINFO, vegasinfo),
|
|
__ADD(IDIAGNL_ATTR_TCPINFO, tcpinfo),
|
|
__ADD(IDIAGNL_ATTR_SKMEMINFO, skmeminfo),
|
|
};
|
|
|
|
static char *_idiagnl_attrs2str(int attrs, char *buf, size_t len)
|
|
{
|
|
return __flags2str(attrs, buf, len, idiagnl_attrs,
|
|
ARRAY_SIZE(idiagnl_attrs));
|
|
}
|
|
|
|
static uint64_t idiagnl_compare(struct nl_object *_a, struct nl_object *_b,
|
|
uint64_t attrs, int flags)
|
|
{
|
|
struct idiagnl_msg *a = (struct idiagnl_msg *) _a;
|
|
struct idiagnl_msg *b = (struct idiagnl_msg *) _b;
|
|
uint64_t diff = 0;
|
|
|
|
#define _DIFF(ATTR, EXPR) ATTR_DIFF(attrs, IDIAGNL_ATTR_##ATTR, a, b, EXPR)
|
|
diff |= _DIFF(FAMILY, a->idiag_family != b->idiag_family);
|
|
diff |= _DIFF(STATE, a->idiag_state != b->idiag_state);
|
|
diff |= _DIFF(TIMER, a->idiag_timer != b->idiag_timer);
|
|
diff |= _DIFF(RETRANS, a->idiag_retrans != b->idiag_retrans);
|
|
diff |= _DIFF(SPORT, a->idiag_sport != b->idiag_sport);
|
|
diff |= _DIFF(DPORT, a->idiag_dport != b->idiag_dport);
|
|
diff |= _DIFF(SRC, nl_addr_cmp (a->idiag_src, b->idiag_src));
|
|
diff |= _DIFF(DST, nl_addr_cmp (a->idiag_dst, b->idiag_dst));
|
|
diff |= _DIFF(IFINDEX, a->idiag_ifindex != b->idiag_ifindex);
|
|
diff |= _DIFF(EXPIRES, a->idiag_expires != b->idiag_expires);
|
|
diff |= _DIFF(RQUEUE, a->idiag_rqueue != b->idiag_rqueue);
|
|
diff |= _DIFF(WQUEUE, a->idiag_wqueue != b->idiag_wqueue);
|
|
diff |= _DIFF(UID, a->idiag_uid != b->idiag_uid);
|
|
diff |= _DIFF(INODE, a->idiag_inode != b->idiag_inode);
|
|
diff |= _DIFF(TOS, a->idiag_tos != b->idiag_tos);
|
|
diff |= _DIFF(TCLASS, a->idiag_tclass != b->idiag_tclass);
|
|
diff |= _DIFF(SHUTDOWN, a->idiag_shutdown != b->idiag_shutdown);
|
|
diff |= _DIFF(CONG, strcmp(a->idiag_cong, b->idiag_cong));
|
|
diff |= _DIFF(MEMINFO, nl_object_diff((struct nl_object *) a->idiag_meminfo, (struct nl_object *) b->idiag_meminfo));
|
|
diff |= _DIFF(VEGASINFO, nl_object_diff((struct nl_object *) a->idiag_vegasinfo, (struct nl_object *) b->idiag_vegasinfo));
|
|
diff |= _DIFF(TCPINFO, memcmp(&a->idiag_tcpinfo, &b->idiag_tcpinfo, sizeof(a->idiag_tcpinfo)));
|
|
diff |= _DIFF(SKMEMINFO, memcmp(a->idiag_skmeminfo, b->idiag_skmeminfo, sizeof(a->idiag_skmeminfo)));
|
|
#undef _DIFF
|
|
return diff;
|
|
}
|
|
|
|
static void idiagnl_keygen(struct nl_object *obj, uint32_t *hashkey,
|
|
uint32_t table_sz)
|
|
{
|
|
struct idiagnl_msg *msg = (struct idiagnl_msg *)obj;
|
|
unsigned int key_sz;
|
|
struct idiagnl_hash_key {
|
|
uint8_t family;
|
|
uint32_t src_hash;
|
|
uint32_t dst_hash;
|
|
uint16_t sport;
|
|
uint16_t dport;
|
|
} __attribute__((packed)) key;
|
|
|
|
key_sz = sizeof(key);
|
|
key.family = msg->idiag_family;
|
|
key.src_hash = 0;
|
|
key.dst_hash = 0;
|
|
key.sport = msg->idiag_sport;
|
|
key.dport = msg->idiag_dport;
|
|
|
|
if (msg->idiag_src) {
|
|
key.src_hash = nl_hash (nl_addr_get_binary_addr(msg->idiag_src),
|
|
nl_addr_get_len(msg->idiag_src), 0);
|
|
}
|
|
if (msg->idiag_dst) {
|
|
key.dst_hash = nl_hash (nl_addr_get_binary_addr(msg->idiag_dst),
|
|
nl_addr_get_len(msg->idiag_dst), 0);
|
|
}
|
|
|
|
*hashkey = nl_hash(&key, key_sz, 0) % table_sz;
|
|
|
|
NL_DBG(5, "idiagnl %p key (fam %d src_hash %d dst_hash %d sport %d dport %d) keysz %d, hash 0x%x\n",
|
|
msg, key.family, key.src_hash, key.dst_hash, key.sport, key.dport, key_sz, *hashkey);
|
|
|
|
return;
|
|
}
|
|
|
|
/** @cond SKIP */
|
|
struct nl_object_ops idiagnl_msg_obj_ops = {
|
|
.oo_name = "idiag/idiag_msg",
|
|
.oo_size = sizeof(struct idiagnl_msg),
|
|
.oo_free_data = idiagnl_msg_free,
|
|
.oo_clone = idiagnl_msg_clone,
|
|
.oo_dump = {
|
|
[NL_DUMP_LINE] = idiag_msg_dump_line,
|
|
[NL_DUMP_DETAILS] = idiag_msg_dump_details,
|
|
[NL_DUMP_STATS] = idiag_msg_dump_stats,
|
|
},
|
|
.oo_compare = idiagnl_compare,
|
|
.oo_keygen = idiagnl_keygen,
|
|
.oo_attrs2str = _idiagnl_attrs2str,
|
|
.oo_id_attrs = (IDIAGNL_ATTR_FAMILY |
|
|
IDIAGNL_ATTR_SRC |
|
|
IDIAGNL_ATTR_DST |
|
|
IDIAGNL_ATTR_SPORT |
|
|
IDIAGNL_ATTR_DPORT),
|
|
};
|
|
/** @endcond */
|
|
|
|
/** @} */
|