Merge branch 'sctp-diag'

Xin Long says:

====================
sctp: support sctp_diag in kernel

This patchset will add sctp_diag module to implement diag interface on
sctp in kernel.

For a listening sctp endpoint, we will just dump it's ep info.
For a sctp connection, we will the assoc info and it's ep info.

The ss dump will looks like:

[iproute2]# ./misc/ss --sctp  -n -l
State      Recv-Q Send-Q   Local Address:Port       Peer Address:Port
LISTEN     0      128      172.16.254.254:8888      *:*
LISTEN     0      5        127.0.0.1:1234           *:*
LISTEN     0      5        127.0.0.1:1234           *:*
  - ESTAB  0      0        127.0.0.1%lo:1234        127.0.0.1:4321
LISTEN     0      128      172.16.254.254:8888      *:*
  - ESTAB  0      0        172.16.254.254%eth1:8888 172.16.253.253:8888
  - ESTAB  0      0        172.16.254.254%eth1:8888 172.16.1.1:8888
  - ESTAB  0      0        172.16.254.254%eth1:8888 172.16.1.2:8888
  - ESTAB  0      0        172.16.254.254%eth1:8888 172.16.2.1:8888
  - ESTAB  0      0        172.16.254.254%eth1:8888 172.16.2.2:8888
  - ESTAB  0      0        172.16.254.254%eth1:8888 172.16.3.1:8888
  - ESTAB  0      0        172.16.254.254%eth1:8888 172.16.3.2:8888
LISTEN     0      0        127.0.0.1:4321           *:*
  - ESTAB  0      0        127.0.0.1%lo:4321        127.0.0.1:1234

The entries with '- ESTAB' are the assocs, some of them may belong to
the same endpoint. So we will dump the parent endpoint first, like the
entry with 'LISTEN'. then dump the assocs. ep and assocs entries will
be dumped in right order so that ss can show them in tree format easily.

Besides, this patchset also simplifies sctp proc codes, cause it has
some similar codes with sctp diag in sctp transport traversal.

v1->v2:
  1. inet_diag_get_handler needs to return it as const.
  2. merge 5/7 into 2/7 of v1.

v2->v3:
  do some improvements and fixes in patch 1-4, see the details in
  each patch's comment.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2016-04-15 17:29:37 -04:00
commit 4e811b1e11
9 changed files with 867 additions and 116 deletions

View File

@ -705,4 +705,71 @@ typedef struct sctp_auth_chunk {
sctp_authhdr_t auth_hdr;
} __packed sctp_auth_chunk_t;
struct sctp_info {
__u32 sctpi_tag;
__u32 sctpi_state;
__u32 sctpi_rwnd;
__u16 sctpi_unackdata;
__u16 sctpi_penddata;
__u16 sctpi_instrms;
__u16 sctpi_outstrms;
__u32 sctpi_fragmentation_point;
__u32 sctpi_inqueue;
__u32 sctpi_outqueue;
__u32 sctpi_overall_error;
__u32 sctpi_max_burst;
__u32 sctpi_maxseg;
__u32 sctpi_peer_rwnd;
__u32 sctpi_peer_tag;
__u8 sctpi_peer_capable;
__u8 sctpi_peer_sack;
__u16 __reserved1;
/* assoc status info */
__u64 sctpi_isacks;
__u64 sctpi_osacks;
__u64 sctpi_opackets;
__u64 sctpi_ipackets;
__u64 sctpi_rtxchunks;
__u64 sctpi_outofseqtsns;
__u64 sctpi_idupchunks;
__u64 sctpi_gapcnt;
__u64 sctpi_ouodchunks;
__u64 sctpi_iuodchunks;
__u64 sctpi_oodchunks;
__u64 sctpi_iodchunks;
__u64 sctpi_octrlchunks;
__u64 sctpi_ictrlchunks;
/* primary transport info */
struct sockaddr_storage sctpi_p_address;
__s32 sctpi_p_state;
__u32 sctpi_p_cwnd;
__u32 sctpi_p_srtt;
__u32 sctpi_p_rto;
__u32 sctpi_p_hbinterval;
__u32 sctpi_p_pathmaxrxt;
__u32 sctpi_p_sackdelay;
__u32 sctpi_p_sackfreq;
__u32 sctpi_p_ssthresh;
__u32 sctpi_p_partial_bytes_acked;
__u32 sctpi_p_flight_size;
__u16 sctpi_p_error;
__u16 __reserved2;
/* sctp sock info */
__u32 sctpi_s_autoclose;
__u32 sctpi_s_adaptation_ind;
__u32 sctpi_s_pd_point;
__u8 sctpi_s_nodelay;
__u8 sctpi_s_disable_fragments;
__u8 sctpi_s_v4mapped;
__u8 sctpi_s_frag_interleave;
};
struct sctp_infox {
struct sctp_info *sctpinfo;
struct sctp_association *asoc;
};
#endif /* __LINUX_SCTP_H__ */

View File

@ -116,6 +116,22 @@ extern struct percpu_counter sctp_sockets_allocated;
int sctp_asconf_mgmt(struct sctp_sock *, struct sctp_sockaddr_entry *);
struct sk_buff *sctp_skb_recv_datagram(struct sock *, int, int, int *);
int sctp_transport_walk_start(struct rhashtable_iter *iter);
void sctp_transport_walk_stop(struct rhashtable_iter *iter);
struct sctp_transport *sctp_transport_get_next(struct net *net,
struct rhashtable_iter *iter);
struct sctp_transport *sctp_transport_get_idx(struct net *net,
struct rhashtable_iter *iter, int pos);
int sctp_transport_lookup_process(int (*cb)(struct sctp_transport *, void *),
struct net *net,
const union sctp_addr *laddr,
const union sctp_addr *paddr, void *p);
int sctp_for_each_transport(int (*cb)(struct sctp_transport *, void *),
struct net *net, int pos, void *p);
int sctp_for_each_endpoint(int (*cb)(struct sctp_endpoint *, void *), void *p);
int sctp_get_sctp_info(struct sock *sk, struct sctp_association *asoc,
struct sctp_info *info);
/*
* sctp/primitive.c
*/

View File

@ -113,6 +113,8 @@ enum {
INET_DIAG_DCTCPINFO,
INET_DIAG_PROTOCOL, /* response attribute only */
INET_DIAG_SKV6ONLY,
INET_DIAG_LOCALS,
INET_DIAG_PEERS,
};
#define INET_DIAG_MAX INET_DIAG_SKV6ONLY

View File

@ -66,7 +66,7 @@ static void inet_diag_unlock_handler(const struct inet_diag_handler *handler)
mutex_unlock(&inet_diag_table_mutex);
}
static void inet_diag_msg_common_fill(struct inet_diag_msg *r, struct sock *sk)
void inet_diag_msg_common_fill(struct inet_diag_msg *r, struct sock *sk)
{
r->idiag_family = sk->sk_family;
@ -89,6 +89,7 @@ static void inet_diag_msg_common_fill(struct inet_diag_msg *r, struct sock *sk)
r->id.idiag_dst[0] = sk->sk_daddr;
}
}
EXPORT_SYMBOL_GPL(inet_diag_msg_common_fill);
static size_t inet_sk_attr_size(void)
{
@ -104,36 +105,11 @@ static size_t inet_sk_attr_size(void)
+ 64;
}
int inet_sk_diag_fill(struct sock *sk, struct inet_connection_sock *icsk,
struct sk_buff *skb, const struct inet_diag_req_v2 *req,
struct user_namespace *user_ns,
u32 portid, u32 seq, u16 nlmsg_flags,
const struct nlmsghdr *unlh)
int inet_diag_msg_attrs_fill(struct sock *sk, struct sk_buff *skb,
struct inet_diag_msg *r, int ext,
struct user_namespace *user_ns)
{
const struct inet_sock *inet = inet_sk(sk);
const struct tcp_congestion_ops *ca_ops;
const struct inet_diag_handler *handler;
int ext = req->idiag_ext;
struct inet_diag_msg *r;
struct nlmsghdr *nlh;
struct nlattr *attr;
void *info = NULL;
handler = inet_diag_table[req->sdiag_protocol];
BUG_ON(!handler);
nlh = nlmsg_put(skb, portid, seq, unlh->nlmsg_type, sizeof(*r),
nlmsg_flags);
if (!nlh)
return -EMSGSIZE;
r = nlmsg_data(nlh);
BUG_ON(!sk_fullsock(sk));
inet_diag_msg_common_fill(r, sk);
r->idiag_state = sk->sk_state;
r->idiag_timer = 0;
r->idiag_retrans = 0;
if (nla_put_u8(skb, INET_DIAG_SHUTDOWN, sk->sk_shutdown))
goto errout;
@ -161,6 +137,45 @@ int inet_sk_diag_fill(struct sock *sk, struct inet_connection_sock *icsk,
r->idiag_uid = from_kuid_munged(user_ns, sock_i_uid(sk));
r->idiag_inode = sock_i_ino(sk);
return 0;
errout:
return 1;
}
EXPORT_SYMBOL_GPL(inet_diag_msg_attrs_fill);
int inet_sk_diag_fill(struct sock *sk, struct inet_connection_sock *icsk,
struct sk_buff *skb, const struct inet_diag_req_v2 *req,
struct user_namespace *user_ns,
u32 portid, u32 seq, u16 nlmsg_flags,
const struct nlmsghdr *unlh)
{
const struct tcp_congestion_ops *ca_ops;
const struct inet_diag_handler *handler;
int ext = req->idiag_ext;
struct inet_diag_msg *r;
struct nlmsghdr *nlh;
struct nlattr *attr;
void *info = NULL;
handler = inet_diag_table[req->sdiag_protocol];
BUG_ON(!handler);
nlh = nlmsg_put(skb, portid, seq, unlh->nlmsg_type, sizeof(*r),
nlmsg_flags);
if (!nlh)
return -EMSGSIZE;
r = nlmsg_data(nlh);
BUG_ON(!sk_fullsock(sk));
inet_diag_msg_common_fill(r, sk);
r->idiag_state = sk->sk_state;
r->idiag_timer = 0;
r->idiag_retrans = 0;
if (inet_diag_msg_attrs_fill(sk, skb, r, ext, user_ns))
goto errout;
if (ext & (1 << (INET_DIAG_MEMINFO - 1))) {
struct inet_diag_meminfo minfo = {
.idiag_rmem = sk_rmem_alloc_get(sk),

View File

@ -99,5 +99,9 @@ config SCTP_COOKIE_HMAC_SHA1
select CRYPTO_HMAC if SCTP_COOKIE_HMAC_SHA1
select CRYPTO_SHA1 if SCTP_COOKIE_HMAC_SHA1
config INET_SCTP_DIAG
depends on INET_DIAG
def_tristate INET_DIAG
endif # IP_SCTP

View File

@ -4,6 +4,7 @@
obj-$(CONFIG_IP_SCTP) += sctp.o
obj-$(CONFIG_NET_SCTPPROBE) += sctp_probe.o
obj-$(CONFIG_INET_SCTP_DIAG) += sctp_diag.o
sctp-y := sm_statetable.o sm_statefuns.o sm_sideeffect.o \
protocol.o endpointola.o associola.o \

View File

@ -280,83 +280,38 @@ void sctp_eps_proc_exit(struct net *net)
struct sctp_ht_iter {
struct seq_net_private p;
struct rhashtable_iter hti;
int start_fail;
};
static struct sctp_transport *sctp_transport_get_next(struct seq_file *seq)
static void *sctp_transport_seq_start(struct seq_file *seq, loff_t *pos)
{
struct sctp_ht_iter *iter = seq->private;
struct sctp_transport *t;
int err = sctp_transport_walk_start(&iter->hti);
t = rhashtable_walk_next(&iter->hti);
for (; t; t = rhashtable_walk_next(&iter->hti)) {
if (IS_ERR(t)) {
if (PTR_ERR(t) == -EAGAIN)
continue;
break;
}
if (net_eq(sock_net(t->asoc->base.sk), seq_file_net(seq)) &&
t->asoc->peer.primary_path == t)
break;
if (err) {
iter->start_fail = 1;
return ERR_PTR(err);
}
return t;
return sctp_transport_get_idx(seq_file_net(seq), &iter->hti, *pos);
}
static struct sctp_transport *sctp_transport_get_idx(struct seq_file *seq,
loff_t pos)
{
void *obj = SEQ_START_TOKEN;
while (pos && (obj = sctp_transport_get_next(seq)) && !IS_ERR(obj))
pos--;
return obj;
}
static int sctp_transport_walk_start(struct seq_file *seq)
{
struct sctp_ht_iter *iter = seq->private;
int err;
err = rhashtable_walk_init(&sctp_transport_hashtable, &iter->hti,
GFP_KERNEL);
if (err)
return err;
err = rhashtable_walk_start(&iter->hti);
return err == -EAGAIN ? 0 : err;
}
static void sctp_transport_walk_stop(struct seq_file *seq)
static void sctp_transport_seq_stop(struct seq_file *seq, void *v)
{
struct sctp_ht_iter *iter = seq->private;
rhashtable_walk_stop(&iter->hti);
rhashtable_walk_exit(&iter->hti);
if (iter->start_fail)
return;
sctp_transport_walk_stop(&iter->hti);
}
static void *sctp_assocs_seq_start(struct seq_file *seq, loff_t *pos)
static void *sctp_transport_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
int err = sctp_transport_walk_start(seq);
struct sctp_ht_iter *iter = seq->private;
if (err)
return ERR_PTR(err);
return sctp_transport_get_idx(seq, *pos);
}
static void sctp_assocs_seq_stop(struct seq_file *seq, void *v)
{
sctp_transport_walk_stop(seq);
}
static void *sctp_assocs_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
++*pos;
return sctp_transport_get_next(seq);
return sctp_transport_get_next(seq_file_net(seq), &iter->hti);
}
/* Display sctp associations (/proc/net/sctp/assocs). */
@ -417,9 +372,9 @@ static int sctp_assocs_seq_show(struct seq_file *seq, void *v)
}
static const struct seq_operations sctp_assoc_ops = {
.start = sctp_assocs_seq_start,
.next = sctp_assocs_seq_next,
.stop = sctp_assocs_seq_stop,
.start = sctp_transport_seq_start,
.next = sctp_transport_seq_next,
.stop = sctp_transport_seq_stop,
.show = sctp_assocs_seq_show,
};
@ -456,28 +411,6 @@ void sctp_assocs_proc_exit(struct net *net)
remove_proc_entry("assocs", net->sctp.proc_net_sctp);
}
static void *sctp_remaddr_seq_start(struct seq_file *seq, loff_t *pos)
{
int err = sctp_transport_walk_start(seq);
if (err)
return ERR_PTR(err);
return sctp_transport_get_idx(seq, *pos);
}
static void *sctp_remaddr_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
++*pos;
return sctp_transport_get_next(seq);
}
static void sctp_remaddr_seq_stop(struct seq_file *seq, void *v)
{
sctp_transport_walk_stop(seq);
}
static int sctp_remaddr_seq_show(struct seq_file *seq, void *v)
{
struct sctp_association *assoc;
@ -551,9 +484,9 @@ static int sctp_remaddr_seq_show(struct seq_file *seq, void *v)
}
static const struct seq_operations sctp_remaddr_ops = {
.start = sctp_remaddr_seq_start,
.next = sctp_remaddr_seq_next,
.stop = sctp_remaddr_seq_stop,
.start = sctp_transport_seq_start,
.next = sctp_transport_seq_next,
.stop = sctp_transport_seq_stop,
.show = sctp_remaddr_seq_show,
};

497
net/sctp/sctp_diag.c Normal file
View File

@ -0,0 +1,497 @@
#include <linux/module.h>
#include <linux/inet_diag.h>
#include <linux/sock_diag.h>
#include <net/sctp/sctp.h>
extern void inet_diag_msg_common_fill(struct inet_diag_msg *r,
struct sock *sk);
extern int inet_diag_msg_attrs_fill(struct sock *sk, struct sk_buff *skb,
struct inet_diag_msg *r, int ext,
struct user_namespace *user_ns);
static void sctp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
void *info);
/* define some functions to make asoc/ep fill look clean */
static void inet_diag_msg_sctpasoc_fill(struct inet_diag_msg *r,
struct sock *sk,
struct sctp_association *asoc)
{
union sctp_addr laddr, paddr;
struct dst_entry *dst;
laddr = list_entry(asoc->base.bind_addr.address_list.next,
struct sctp_sockaddr_entry, list)->a;
paddr = asoc->peer.primary_path->ipaddr;
dst = asoc->peer.primary_path->dst;
r->idiag_family = sk->sk_family;
r->id.idiag_sport = htons(asoc->base.bind_addr.port);
r->id.idiag_dport = htons(asoc->peer.port);
r->id.idiag_if = dst ? dst->dev->ifindex : 0;
sock_diag_save_cookie(sk, r->id.idiag_cookie);
#if IS_ENABLED(CONFIG_IPV6)
if (sk->sk_family == AF_INET6) {
*(struct in6_addr *)r->id.idiag_src = laddr.v6.sin6_addr;
*(struct in6_addr *)r->id.idiag_dst = paddr.v6.sin6_addr;
} else
#endif
{
memset(&r->id.idiag_src, 0, sizeof(r->id.idiag_src));
memset(&r->id.idiag_dst, 0, sizeof(r->id.idiag_dst));
r->id.idiag_src[0] = laddr.v4.sin_addr.s_addr;
r->id.idiag_dst[0] = paddr.v4.sin_addr.s_addr;
}
r->idiag_state = asoc->state;
r->idiag_timer = SCTP_EVENT_TIMEOUT_T3_RTX;
r->idiag_retrans = asoc->rtx_data_chunks;
#define EXPIRES_IN_MS(tmo) DIV_ROUND_UP((tmo - jiffies) * 1000, HZ)
r->idiag_expires =
EXPIRES_IN_MS(asoc->timeouts[SCTP_EVENT_TIMEOUT_T3_RTX]);
#undef EXPIRES_IN_MS
}
static int inet_diag_msg_sctpladdrs_fill(struct sk_buff *skb,
struct list_head *address_list)
{
struct sctp_sockaddr_entry *laddr;
int addrlen = sizeof(struct sockaddr_storage);
int addrcnt = 0;
struct nlattr *attr;
void *info = NULL;
list_for_each_entry_rcu(laddr, address_list, list)
addrcnt++;
attr = nla_reserve(skb, INET_DIAG_LOCALS, addrlen * addrcnt);
if (!attr)
return -EMSGSIZE;
info = nla_data(attr);
list_for_each_entry_rcu(laddr, address_list, list) {
memcpy(info, &laddr->a, addrlen);
info += addrlen;
}
return 0;
}
static int inet_diag_msg_sctpaddrs_fill(struct sk_buff *skb,
struct sctp_association *asoc)
{
int addrlen = sizeof(struct sockaddr_storage);
struct sctp_transport *from;
struct nlattr *attr;
void *info = NULL;
attr = nla_reserve(skb, INET_DIAG_PEERS,
addrlen * asoc->peer.transport_count);
if (!attr)
return -EMSGSIZE;
info = nla_data(attr);
list_for_each_entry(from, &asoc->peer.transport_addr_list,
transports) {
memcpy(info, &from->ipaddr, addrlen);
info += addrlen;
}
return 0;
}
/* sctp asoc/ep fill*/
static int inet_sctp_diag_fill(struct sock *sk, struct sctp_association *asoc,
struct sk_buff *skb,
const struct inet_diag_req_v2 *req,
struct user_namespace *user_ns,
int portid, u32 seq, u16 nlmsg_flags,
const struct nlmsghdr *unlh)
{
struct sctp_endpoint *ep = sctp_sk(sk)->ep;
struct list_head *addr_list;
struct inet_diag_msg *r;
struct nlmsghdr *nlh;
int ext = req->idiag_ext;
struct sctp_infox infox;
void *info = NULL;
nlh = nlmsg_put(skb, portid, seq, unlh->nlmsg_type, sizeof(*r),
nlmsg_flags);
if (!nlh)
return -EMSGSIZE;
r = nlmsg_data(nlh);
BUG_ON(!sk_fullsock(sk));
if (asoc) {
inet_diag_msg_sctpasoc_fill(r, sk, asoc);
} else {
inet_diag_msg_common_fill(r, sk);
r->idiag_state = sk->sk_state;
r->idiag_timer = 0;
r->idiag_retrans = 0;
}
if (inet_diag_msg_attrs_fill(sk, skb, r, ext, user_ns))
goto errout;
if (ext & (1 << (INET_DIAG_SKMEMINFO - 1))) {
u32 mem[SK_MEMINFO_VARS];
int amt;
if (asoc && asoc->ep->sndbuf_policy)
amt = asoc->sndbuf_used;
else
amt = sk_wmem_alloc_get(sk);
mem[SK_MEMINFO_WMEM_ALLOC] = amt;
mem[SK_MEMINFO_RMEM_ALLOC] = sk_rmem_alloc_get(sk);
mem[SK_MEMINFO_RCVBUF] = sk->sk_rcvbuf;
mem[SK_MEMINFO_SNDBUF] = sk->sk_sndbuf;
mem[SK_MEMINFO_FWD_ALLOC] = sk->sk_forward_alloc;
mem[SK_MEMINFO_WMEM_QUEUED] = sk->sk_wmem_queued;
mem[SK_MEMINFO_OPTMEM] = atomic_read(&sk->sk_omem_alloc);
mem[SK_MEMINFO_BACKLOG] = sk->sk_backlog.len;
mem[SK_MEMINFO_DROPS] = atomic_read(&sk->sk_drops);
if (nla_put(skb, INET_DIAG_SKMEMINFO, sizeof(mem), &mem) < 0)
goto errout;
}
if (ext & (1 << (INET_DIAG_INFO - 1))) {
struct nlattr *attr;
attr = nla_reserve(skb, INET_DIAG_INFO,
sizeof(struct sctp_info));
if (!attr)
goto errout;
info = nla_data(attr);
}
infox.sctpinfo = (struct sctp_info *)info;
infox.asoc = asoc;
sctp_diag_get_info(sk, r, &infox);
addr_list = asoc ? &asoc->base.bind_addr.address_list
: &ep->base.bind_addr.address_list;
if (inet_diag_msg_sctpladdrs_fill(skb, addr_list))
goto errout;
if (asoc && (ext & (1 << (INET_DIAG_CONG - 1))))
if (nla_put_string(skb, INET_DIAG_CONG, "reno") < 0)
goto errout;
if (asoc && inet_diag_msg_sctpaddrs_fill(skb, asoc))
goto errout;
nlmsg_end(skb, nlh);
return 0;
errout:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
/* callback and param */
struct sctp_comm_param {
struct sk_buff *skb;
struct netlink_callback *cb;
const struct inet_diag_req_v2 *r;
const struct nlmsghdr *nlh;
};
static size_t inet_assoc_attr_size(struct sctp_association *asoc)
{
int addrlen = sizeof(struct sockaddr_storage);
int addrcnt = 0;
struct sctp_sockaddr_entry *laddr;
list_for_each_entry_rcu(laddr, &asoc->base.bind_addr.address_list,
list)
addrcnt++;
return nla_total_size(sizeof(struct sctp_info))
+ nla_total_size(1) /* INET_DIAG_SHUTDOWN */
+ nla_total_size(1) /* INET_DIAG_TOS */
+ nla_total_size(1) /* INET_DIAG_TCLASS */
+ nla_total_size(addrlen * asoc->peer.transport_count)
+ nla_total_size(addrlen * addrcnt)
+ nla_total_size(sizeof(struct inet_diag_meminfo))
+ nla_total_size(sizeof(struct inet_diag_msg))
+ 64;
}
static int sctp_tsp_dump_one(struct sctp_transport *tsp, void *p)
{
struct sctp_association *assoc = tsp->asoc;
struct sock *sk = tsp->asoc->base.sk;
struct sctp_comm_param *commp = p;
struct sk_buff *in_skb = commp->skb;
const struct inet_diag_req_v2 *req = commp->r;
const struct nlmsghdr *nlh = commp->nlh;
struct net *net = sock_net(in_skb->sk);
struct sk_buff *rep;
int err;
err = sock_diag_check_cookie(sk, req->id.idiag_cookie);
if (err)
goto out;
err = -ENOMEM;
rep = nlmsg_new(inet_assoc_attr_size(assoc), GFP_KERNEL);
if (!rep)
goto out;
lock_sock(sk);
if (sk != assoc->base.sk) {
release_sock(sk);
sk = assoc->base.sk;
lock_sock(sk);
}
err = inet_sctp_diag_fill(sk, assoc, rep, req,
sk_user_ns(NETLINK_CB(in_skb).sk),
NETLINK_CB(in_skb).portid,
nlh->nlmsg_seq, 0, nlh);
release_sock(sk);
if (err < 0) {
WARN_ON(err == -EMSGSIZE);
kfree_skb(rep);
goto out;
}
err = netlink_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid,
MSG_DONTWAIT);
if (err > 0)
err = 0;
out:
return err;
}
static int sctp_tsp_dump(struct sctp_transport *tsp, void *p)
{
struct sctp_endpoint *ep = tsp->asoc->ep;
struct sctp_comm_param *commp = p;
struct sock *sk = ep->base.sk;
struct sk_buff *skb = commp->skb;
struct netlink_callback *cb = commp->cb;
const struct inet_diag_req_v2 *r = commp->r;
struct sctp_association *assoc =
list_entry(ep->asocs.next, struct sctp_association, asocs);
int err = 0;
/* find the ep only once through the transports by this condition */
if (tsp->asoc != assoc)
goto out;
if (r->sdiag_family != AF_UNSPEC && sk->sk_family != r->sdiag_family)
goto out;
lock_sock(sk);
if (sk != assoc->base.sk)
goto release;
list_for_each_entry(assoc, &ep->asocs, asocs) {
if (cb->args[4] < cb->args[1])
goto next;
if (r->id.idiag_sport != htons(assoc->base.bind_addr.port) &&
r->id.idiag_sport)
goto next;
if (r->id.idiag_dport != htons(assoc->peer.port) &&
r->id.idiag_dport)
goto next;
if (!cb->args[3] &&
inet_sctp_diag_fill(sk, NULL, skb, r,
sk_user_ns(NETLINK_CB(cb->skb).sk),
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
NLM_F_MULTI, cb->nlh) < 0) {
cb->args[3] = 1;
err = 2;
goto release;
}
cb->args[3] = 1;
if (inet_sctp_diag_fill(sk, assoc, skb, r,
sk_user_ns(NETLINK_CB(cb->skb).sk),
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, 0, cb->nlh) < 0) {
err = 2;
goto release;
}
next:
cb->args[4]++;
}
cb->args[1] = 0;
cb->args[2]++;
cb->args[3] = 0;
cb->args[4] = 0;
release:
release_sock(sk);
return err;
out:
cb->args[2]++;
return err;
}
static int sctp_ep_dump(struct sctp_endpoint *ep, void *p)
{
struct sctp_comm_param *commp = p;
struct sock *sk = ep->base.sk;
struct sk_buff *skb = commp->skb;
struct netlink_callback *cb = commp->cb;
const struct inet_diag_req_v2 *r = commp->r;
struct net *net = sock_net(skb->sk);
struct inet_sock *inet = inet_sk(sk);
int err = 0;
if (!net_eq(sock_net(sk), net))
goto out;
if (cb->args[4] < cb->args[1])
goto next;
if (r->sdiag_family != AF_UNSPEC &&
sk->sk_family != r->sdiag_family)
goto next;
if (r->id.idiag_sport != inet->inet_sport &&
r->id.idiag_sport)
goto next;
if (r->id.idiag_dport != inet->inet_dport &&
r->id.idiag_dport)
goto next;
if (inet_sctp_diag_fill(sk, NULL, skb, r,
sk_user_ns(NETLINK_CB(cb->skb).sk),
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, NLM_F_MULTI,
cb->nlh) < 0) {
err = 2;
goto out;
}
next:
cb->args[4]++;
out:
return err;
}
/* define the functions for sctp_diag_handler*/
static void sctp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
void *info)
{
struct sctp_infox *infox = (struct sctp_infox *)info;
if (infox->asoc) {
r->idiag_rqueue = atomic_read(&infox->asoc->rmem_alloc);
r->idiag_wqueue = infox->asoc->sndbuf_used;
} else {
r->idiag_rqueue = sk->sk_ack_backlog;
r->idiag_wqueue = sk->sk_max_ack_backlog;
}
if (infox->sctpinfo)
sctp_get_sctp_info(sk, infox->asoc, infox->sctpinfo);
}
static int sctp_diag_dump_one(struct sk_buff *in_skb,
const struct nlmsghdr *nlh,
const struct inet_diag_req_v2 *req)
{
struct net *net = sock_net(in_skb->sk);
union sctp_addr laddr, paddr;
struct sctp_comm_param commp = {
.skb = in_skb,
.r = req,
.nlh = nlh,
};
if (req->sdiag_family == AF_INET) {
laddr.v4.sin_port = req->id.idiag_sport;
laddr.v4.sin_addr.s_addr = req->id.idiag_src[0];
laddr.v4.sin_family = AF_INET;
paddr.v4.sin_port = req->id.idiag_dport;
paddr.v4.sin_addr.s_addr = req->id.idiag_dst[0];
paddr.v4.sin_family = AF_INET;
} else {
laddr.v6.sin6_port = req->id.idiag_sport;
memcpy(&laddr.v6.sin6_addr, req->id.idiag_src, 64);
laddr.v6.sin6_family = AF_INET6;
paddr.v6.sin6_port = req->id.idiag_dport;
memcpy(&paddr.v6.sin6_addr, req->id.idiag_dst, 64);
paddr.v6.sin6_family = AF_INET6;
}
return sctp_transport_lookup_process(sctp_tsp_dump_one,
net, &laddr, &paddr, &commp);
}
static void sctp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
const struct inet_diag_req_v2 *r, struct nlattr *bc)
{
u32 idiag_states = r->idiag_states;
struct net *net = sock_net(skb->sk);
struct sctp_comm_param commp = {
.skb = skb,
.cb = cb,
.r = r,
};
/* eps hashtable dumps
* args:
* 0 : if it will traversal listen sock
* 1 : to record the sock pos of this time's traversal
* 4 : to work as a temporary variable to traversal list
*/
if (cb->args[0] == 0) {
if (!(idiag_states & TCPF_LISTEN))
goto skip;
if (sctp_for_each_endpoint(sctp_ep_dump, &commp))
goto done;
skip:
cb->args[0] = 1;
cb->args[1] = 0;
cb->args[4] = 0;
}
/* asocs by transport hashtable dump
* args:
* 1 : to record the assoc pos of this time's traversal
* 2 : to record the transport pos of this time's traversal
* 3 : to mark if we have dumped the ep info of the current asoc
* 4 : to work as a temporary variable to traversal list
*/
if (!(idiag_states & ~TCPF_LISTEN))
goto done;
sctp_for_each_transport(sctp_tsp_dump, net, cb->args[2], &commp);
done:
cb->args[1] = cb->args[4];
cb->args[4] = 0;
}
static const struct inet_diag_handler sctp_diag_handler = {
.dump = sctp_diag_dump,
.dump_one = sctp_diag_dump_one,
.idiag_get_info = sctp_diag_get_info,
.idiag_type = IPPROTO_SCTP,
.idiag_info_size = sizeof(struct sctp_info),
};
static int __init sctp_diag_init(void)
{
return inet_diag_register(&sctp_diag_handler);
}
static void __exit sctp_diag_exit(void)
{
inet_diag_unregister(&sctp_diag_handler);
}
module_init(sctp_diag_init);
module_exit(sctp_diag_exit);
MODULE_LICENSE("GPL");
MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-132);

View File

@ -4202,6 +4202,222 @@ static void sctp_shutdown(struct sock *sk, int how)
}
}
int sctp_get_sctp_info(struct sock *sk, struct sctp_association *asoc,
struct sctp_info *info)
{
struct sctp_transport *prim;
struct list_head *pos;
int mask;
memset(info, 0, sizeof(*info));
if (!asoc) {
struct sctp_sock *sp = sctp_sk(sk);
info->sctpi_s_autoclose = sp->autoclose;
info->sctpi_s_adaptation_ind = sp->adaptation_ind;
info->sctpi_s_pd_point = sp->pd_point;
info->sctpi_s_nodelay = sp->nodelay;
info->sctpi_s_disable_fragments = sp->disable_fragments;
info->sctpi_s_v4mapped = sp->v4mapped;
info->sctpi_s_frag_interleave = sp->frag_interleave;
return 0;
}
info->sctpi_tag = asoc->c.my_vtag;
info->sctpi_state = asoc->state;
info->sctpi_rwnd = asoc->a_rwnd;
info->sctpi_unackdata = asoc->unack_data;
info->sctpi_penddata = sctp_tsnmap_pending(&asoc->peer.tsn_map);
info->sctpi_instrms = asoc->c.sinit_max_instreams;
info->sctpi_outstrms = asoc->c.sinit_num_ostreams;
list_for_each(pos, &asoc->base.inqueue.in_chunk_list)
info->sctpi_inqueue++;
list_for_each(pos, &asoc->outqueue.out_chunk_list)
info->sctpi_outqueue++;
info->sctpi_overall_error = asoc->overall_error_count;
info->sctpi_max_burst = asoc->max_burst;
info->sctpi_maxseg = asoc->frag_point;
info->sctpi_peer_rwnd = asoc->peer.rwnd;
info->sctpi_peer_tag = asoc->c.peer_vtag;
mask = asoc->peer.ecn_capable << 1;
mask = (mask | asoc->peer.ipv4_address) << 1;
mask = (mask | asoc->peer.ipv6_address) << 1;
mask = (mask | asoc->peer.hostname_address) << 1;
mask = (mask | asoc->peer.asconf_capable) << 1;
mask = (mask | asoc->peer.prsctp_capable) << 1;
mask = (mask | asoc->peer.auth_capable);
info->sctpi_peer_capable = mask;
mask = asoc->peer.sack_needed << 1;
mask = (mask | asoc->peer.sack_generation) << 1;
mask = (mask | asoc->peer.zero_window_announced);
info->sctpi_peer_sack = mask;
info->sctpi_isacks = asoc->stats.isacks;
info->sctpi_osacks = asoc->stats.osacks;
info->sctpi_opackets = asoc->stats.opackets;
info->sctpi_ipackets = asoc->stats.ipackets;
info->sctpi_rtxchunks = asoc->stats.rtxchunks;
info->sctpi_outofseqtsns = asoc->stats.outofseqtsns;
info->sctpi_idupchunks = asoc->stats.idupchunks;
info->sctpi_gapcnt = asoc->stats.gapcnt;
info->sctpi_ouodchunks = asoc->stats.ouodchunks;
info->sctpi_iuodchunks = asoc->stats.iuodchunks;
info->sctpi_oodchunks = asoc->stats.oodchunks;
info->sctpi_iodchunks = asoc->stats.iodchunks;
info->sctpi_octrlchunks = asoc->stats.octrlchunks;
info->sctpi_ictrlchunks = asoc->stats.ictrlchunks;
prim = asoc->peer.primary_path;
memcpy(&info->sctpi_p_address, &prim->ipaddr,
sizeof(struct sockaddr_storage));
info->sctpi_p_state = prim->state;
info->sctpi_p_cwnd = prim->cwnd;
info->sctpi_p_srtt = prim->srtt;
info->sctpi_p_rto = jiffies_to_msecs(prim->rto);
info->sctpi_p_hbinterval = prim->hbinterval;
info->sctpi_p_pathmaxrxt = prim->pathmaxrxt;
info->sctpi_p_sackdelay = jiffies_to_msecs(prim->sackdelay);
info->sctpi_p_ssthresh = prim->ssthresh;
info->sctpi_p_partial_bytes_acked = prim->partial_bytes_acked;
info->sctpi_p_flight_size = prim->flight_size;
info->sctpi_p_error = prim->error_count;
return 0;
}
EXPORT_SYMBOL_GPL(sctp_get_sctp_info);
/* use callback to avoid exporting the core structure */
int sctp_transport_walk_start(struct rhashtable_iter *iter)
{
int err;
err = rhashtable_walk_init(&sctp_transport_hashtable, iter,
GFP_KERNEL);
if (err)
return err;
err = rhashtable_walk_start(iter);
if (err && err != -EAGAIN) {
rhashtable_walk_exit(iter);
return err;
}
return 0;
}
void sctp_transport_walk_stop(struct rhashtable_iter *iter)
{
rhashtable_walk_stop(iter);
rhashtable_walk_exit(iter);
}
struct sctp_transport *sctp_transport_get_next(struct net *net,
struct rhashtable_iter *iter)
{
struct sctp_transport *t;
t = rhashtable_walk_next(iter);
for (; t; t = rhashtable_walk_next(iter)) {
if (IS_ERR(t)) {
if (PTR_ERR(t) == -EAGAIN)
continue;
break;
}
if (net_eq(sock_net(t->asoc->base.sk), net) &&
t->asoc->peer.primary_path == t)
break;
}
return t;
}
struct sctp_transport *sctp_transport_get_idx(struct net *net,
struct rhashtable_iter *iter,
int pos)
{
void *obj = SEQ_START_TOKEN;
while (pos && (obj = sctp_transport_get_next(net, iter)) &&
!IS_ERR(obj))
pos--;
return obj;
}
int sctp_for_each_endpoint(int (*cb)(struct sctp_endpoint *, void *),
void *p) {
int err = 0;
int hash = 0;
struct sctp_ep_common *epb;
struct sctp_hashbucket *head;
for (head = sctp_ep_hashtable; hash < sctp_ep_hashsize;
hash++, head++) {
read_lock(&head->lock);
sctp_for_each_hentry(epb, &head->chain) {
err = cb(sctp_ep(epb), p);
if (err)
break;
}
read_unlock(&head->lock);
}
return err;
}
EXPORT_SYMBOL_GPL(sctp_for_each_endpoint);
int sctp_transport_lookup_process(int (*cb)(struct sctp_transport *, void *),
struct net *net,
const union sctp_addr *laddr,
const union sctp_addr *paddr, void *p)
{
struct sctp_transport *transport;
int err = 0;
rcu_read_lock();
transport = sctp_addrs_lookup_transport(net, laddr, paddr);
if (!transport || !sctp_transport_hold(transport))
goto out;
err = cb(transport, p);
sctp_transport_put(transport);
out:
rcu_read_unlock();
return err;
}
EXPORT_SYMBOL_GPL(sctp_transport_lookup_process);
int sctp_for_each_transport(int (*cb)(struct sctp_transport *, void *),
struct net *net, int pos, void *p) {
struct rhashtable_iter hti;
void *obj;
int err;
err = sctp_transport_walk_start(&hti);
if (err)
return err;
sctp_transport_get_idx(net, &hti, pos);
obj = sctp_transport_get_next(net, &hti);
for (; obj && !IS_ERR(obj); obj = sctp_transport_get_next(net, &hti)) {
struct sctp_transport *transport = obj;
if (!sctp_transport_hold(transport))
continue;
err = cb(transport, p);
sctp_transport_put(transport);
if (err)
break;
}
sctp_transport_walk_stop(&hti);
return err;
}
EXPORT_SYMBOL_GPL(sctp_for_each_transport);
/* 7.2.1 Association Status (SCTP_STATUS)
* Applications can retrieve current status information about an