|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* MPTCP socket monitoring support | 
|  | * | 
|  | * Copyright (c) 2020 Red Hat | 
|  | * | 
|  | * Author: Paolo Abeni <pabeni@redhat.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/net.h> | 
|  | #include <linux/inet_diag.h> | 
|  | #include <net/netlink.h> | 
|  | #include <uapi/linux/mptcp.h> | 
|  | #include "protocol.h" | 
|  |  | 
|  | static int sk_diag_dump(struct sock *sk, struct sk_buff *skb, | 
|  | struct netlink_callback *cb, | 
|  | const struct inet_diag_req_v2 *req, | 
|  | struct nlattr *bc, bool net_admin) | 
|  | { | 
|  | if (!inet_diag_bc_sk(bc, sk)) | 
|  | return 0; | 
|  |  | 
|  | return inet_sk_diag_fill(sk, inet_csk(sk), skb, cb, req, NLM_F_MULTI, | 
|  | net_admin); | 
|  | } | 
|  |  | 
|  | static int mptcp_diag_dump_one(struct netlink_callback *cb, | 
|  | const struct inet_diag_req_v2 *req) | 
|  | { | 
|  | struct sk_buff *in_skb = cb->skb; | 
|  | struct mptcp_sock *msk = NULL; | 
|  | struct sk_buff *rep; | 
|  | int err = -ENOENT; | 
|  | struct net *net; | 
|  | struct sock *sk; | 
|  |  | 
|  | net = sock_net(in_skb->sk); | 
|  | msk = mptcp_token_get_sock(net, req->id.idiag_cookie[0]); | 
|  | if (!msk) | 
|  | goto out_nosk; | 
|  |  | 
|  | err = -ENOMEM; | 
|  | sk = (struct sock *)msk; | 
|  | rep = nlmsg_new(nla_total_size(sizeof(struct inet_diag_msg)) + | 
|  | inet_diag_msg_attrs_size() + | 
|  | nla_total_size(sizeof(struct mptcp_info)) + | 
|  | nla_total_size(sizeof(struct inet_diag_meminfo)) + 64, | 
|  | GFP_KERNEL); | 
|  | if (!rep) | 
|  | goto out; | 
|  |  | 
|  | err = inet_sk_diag_fill(sk, inet_csk(sk), rep, cb, req, 0, | 
|  | netlink_net_capable(in_skb, CAP_NET_ADMIN)); | 
|  | if (err < 0) { | 
|  | WARN_ON(err == -EMSGSIZE); | 
|  | kfree_skb(rep); | 
|  | goto out; | 
|  | } | 
|  | err = nlmsg_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid); | 
|  |  | 
|  | out: | 
|  | sock_put(sk); | 
|  |  | 
|  | out_nosk: | 
|  | return err; | 
|  | } | 
|  |  | 
|  | struct mptcp_diag_ctx { | 
|  | long s_slot; | 
|  | long s_num; | 
|  | unsigned int l_slot; | 
|  | unsigned int l_num; | 
|  | }; | 
|  |  | 
|  | static void mptcp_diag_dump_listeners(struct sk_buff *skb, struct netlink_callback *cb, | 
|  | const struct inet_diag_req_v2 *r, | 
|  | bool net_admin) | 
|  | { | 
|  | struct inet_diag_dump_data *cb_data = cb->data; | 
|  | struct mptcp_diag_ctx *diag_ctx = (void *)cb->ctx; | 
|  | struct nlattr *bc = cb_data->inet_diag_nla_bc; | 
|  | struct net *net = sock_net(skb->sk); | 
|  | int i; | 
|  |  | 
|  | for (i = diag_ctx->l_slot; i <= tcp_hashinfo.lhash2_mask; i++) { | 
|  | struct inet_listen_hashbucket *ilb; | 
|  | struct hlist_nulls_node *node; | 
|  | struct sock *sk; | 
|  | int num = 0; | 
|  |  | 
|  | ilb = &tcp_hashinfo.lhash2[i]; | 
|  |  | 
|  | rcu_read_lock(); | 
|  | spin_lock(&ilb->lock); | 
|  | sk_nulls_for_each(sk, node, &ilb->nulls_head) { | 
|  | const struct mptcp_subflow_context *ctx = mptcp_subflow_ctx(sk); | 
|  | struct inet_sock *inet = inet_sk(sk); | 
|  | int ret; | 
|  |  | 
|  | if (num < diag_ctx->l_num) | 
|  | goto next_listen; | 
|  |  | 
|  | if (!ctx || strcmp(inet_csk(sk)->icsk_ulp_ops->name, "mptcp")) | 
|  | goto next_listen; | 
|  |  | 
|  | sk = ctx->conn; | 
|  | if (!sk || !net_eq(sock_net(sk), net)) | 
|  | goto next_listen; | 
|  |  | 
|  | if (r->sdiag_family != AF_UNSPEC && | 
|  | sk->sk_family != r->sdiag_family) | 
|  | goto next_listen; | 
|  |  | 
|  | if (r->id.idiag_sport != inet->inet_sport && | 
|  | r->id.idiag_sport) | 
|  | goto next_listen; | 
|  |  | 
|  | if (!refcount_inc_not_zero(&sk->sk_refcnt)) | 
|  | goto next_listen; | 
|  |  | 
|  | ret = sk_diag_dump(sk, skb, cb, r, bc, net_admin); | 
|  |  | 
|  | sock_put(sk); | 
|  |  | 
|  | if (ret < 0) { | 
|  | spin_unlock(&ilb->lock); | 
|  | rcu_read_unlock(); | 
|  | diag_ctx->l_slot = i; | 
|  | diag_ctx->l_num = num; | 
|  | return; | 
|  | } | 
|  | diag_ctx->l_num = num + 1; | 
|  | num = 0; | 
|  | next_listen: | 
|  | ++num; | 
|  | } | 
|  | spin_unlock(&ilb->lock); | 
|  | rcu_read_unlock(); | 
|  |  | 
|  | cond_resched(); | 
|  | diag_ctx->l_num = 0; | 
|  | } | 
|  |  | 
|  | diag_ctx->l_num = 0; | 
|  | diag_ctx->l_slot = i; | 
|  | } | 
|  |  | 
|  | static void mptcp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, | 
|  | const struct inet_diag_req_v2 *r) | 
|  | { | 
|  | bool net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN); | 
|  | struct mptcp_diag_ctx *diag_ctx = (void *)cb->ctx; | 
|  | struct net *net = sock_net(skb->sk); | 
|  | struct inet_diag_dump_data *cb_data; | 
|  | struct mptcp_sock *msk; | 
|  | struct nlattr *bc; | 
|  |  | 
|  | BUILD_BUG_ON(sizeof(cb->ctx) < sizeof(*diag_ctx)); | 
|  |  | 
|  | cb_data = cb->data; | 
|  | bc = cb_data->inet_diag_nla_bc; | 
|  |  | 
|  | while ((msk = mptcp_token_iter_next(net, &diag_ctx->s_slot, | 
|  | &diag_ctx->s_num)) != NULL) { | 
|  | struct inet_sock *inet = (struct inet_sock *)msk; | 
|  | struct sock *sk = (struct sock *)msk; | 
|  | int ret = 0; | 
|  |  | 
|  | if (!(r->idiag_states & (1 << sk->sk_state))) | 
|  | 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; | 
|  |  | 
|  | ret = sk_diag_dump(sk, skb, cb, r, bc, net_admin); | 
|  | next: | 
|  | sock_put(sk); | 
|  | if (ret < 0) { | 
|  | /* will retry on the same position */ | 
|  | diag_ctx->s_num--; | 
|  | break; | 
|  | } | 
|  | cond_resched(); | 
|  | } | 
|  |  | 
|  | if ((r->idiag_states & TCPF_LISTEN) && r->id.idiag_dport == 0) | 
|  | mptcp_diag_dump_listeners(skb, cb, r, net_admin); | 
|  | } | 
|  |  | 
|  | static void mptcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, | 
|  | void *_info) | 
|  | { | 
|  | struct mptcp_sock *msk = mptcp_sk(sk); | 
|  | struct mptcp_info *info = _info; | 
|  | u32 flags = 0; | 
|  | bool slow; | 
|  | u8 val; | 
|  |  | 
|  | r->idiag_rqueue = sk_rmem_alloc_get(sk); | 
|  | r->idiag_wqueue = sk_wmem_alloc_get(sk); | 
|  |  | 
|  | if (inet_sk_state_load(sk) == TCP_LISTEN) { | 
|  | struct sock *lsk = READ_ONCE(msk->first); | 
|  |  | 
|  | if (lsk) { | 
|  | /* override with settings from tcp listener, | 
|  | * so Send-Q will show accept queue. | 
|  | */ | 
|  | r->idiag_rqueue = READ_ONCE(lsk->sk_ack_backlog); | 
|  | r->idiag_wqueue = READ_ONCE(lsk->sk_max_ack_backlog); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!info) | 
|  | return; | 
|  |  | 
|  | slow = lock_sock_fast(sk); | 
|  | info->mptcpi_subflows = READ_ONCE(msk->pm.subflows); | 
|  | info->mptcpi_add_addr_signal = READ_ONCE(msk->pm.add_addr_signaled); | 
|  | info->mptcpi_add_addr_accepted = READ_ONCE(msk->pm.add_addr_accepted); | 
|  | info->mptcpi_local_addr_used = READ_ONCE(msk->pm.local_addr_used); | 
|  | info->mptcpi_subflows_max = mptcp_pm_get_subflows_max(msk); | 
|  | val = mptcp_pm_get_add_addr_signal_max(msk); | 
|  | info->mptcpi_add_addr_signal_max = val; | 
|  | val = mptcp_pm_get_add_addr_accept_max(msk); | 
|  | info->mptcpi_add_addr_accepted_max = val; | 
|  | info->mptcpi_local_addr_max = mptcp_pm_get_local_addr_max(msk); | 
|  | if (test_bit(MPTCP_FALLBACK_DONE, &msk->flags)) | 
|  | flags |= MPTCP_INFO_FLAG_FALLBACK; | 
|  | if (READ_ONCE(msk->can_ack)) | 
|  | flags |= MPTCP_INFO_FLAG_REMOTE_KEY_RECEIVED; | 
|  | info->mptcpi_flags = flags; | 
|  | info->mptcpi_token = READ_ONCE(msk->token); | 
|  | info->mptcpi_write_seq = READ_ONCE(msk->write_seq); | 
|  | info->mptcpi_snd_una = READ_ONCE(msk->snd_una); | 
|  | info->mptcpi_rcv_nxt = READ_ONCE(msk->ack_seq); | 
|  | info->mptcpi_csum_enabled = READ_ONCE(msk->csum_enabled); | 
|  | unlock_sock_fast(sk, slow); | 
|  | } | 
|  |  | 
|  | static const struct inet_diag_handler mptcp_diag_handler = { | 
|  | .dump		 = mptcp_diag_dump, | 
|  | .dump_one	 = mptcp_diag_dump_one, | 
|  | .idiag_get_info  = mptcp_diag_get_info, | 
|  | .idiag_type	 = IPPROTO_MPTCP, | 
|  | .idiag_info_size = sizeof(struct mptcp_info), | 
|  | }; | 
|  |  | 
|  | static int __init mptcp_diag_init(void) | 
|  | { | 
|  | return inet_diag_register(&mptcp_diag_handler); | 
|  | } | 
|  |  | 
|  | static void __exit mptcp_diag_exit(void) | 
|  | { | 
|  | inet_diag_unregister(&mptcp_diag_handler); | 
|  | } | 
|  |  | 
|  | module_init(mptcp_diag_init); | 
|  | module_exit(mptcp_diag_exit); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-262 /* AF_INET - IPPROTO_MPTCP */); |