inet_diag.c 31.5 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
/*
2
 * inet_diag.c	Module for monitoring INET transport protocols sockets.
Linus Torvalds's avatar
Linus Torvalds committed
3 4 5 6 7 8 9 10 11
 *
 * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
 *
 *	This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 */

12
#include <linux/kernel.h>
Linus Torvalds's avatar
Linus Torvalds committed
13 14 15 16
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/random.h>
17
#include <linux/slab.h>
Linus Torvalds's avatar
Linus Torvalds committed
18 19 20 21 22 23 24 25
#include <linux/cache.h>
#include <linux/init.h>
#include <linux/time.h>

#include <net/icmp.h>
#include <net/tcp.h>
#include <net/ipv6.h>
#include <net/inet_common.h>
26 27 28 29
#include <net/inet_connection_sock.h>
#include <net/inet_hashtables.h>
#include <net/inet_timewait_sock.h>
#include <net/inet6_hashtables.h>
30
#include <net/netlink.h>
Linus Torvalds's avatar
Linus Torvalds committed
31 32 33 34

#include <linux/inet.h>
#include <linux/stddef.h>

35
#include <linux/inet_diag.h>
36
#include <linux/sock_diag.h>
Linus Torvalds's avatar
Linus Torvalds committed
37

38 39
static const struct inet_diag_handler **inet_diag_table;

40
struct inet_diag_entry {
Eric Dumazet's avatar
Eric Dumazet committed
41 42
	const __be32 *saddr;
	const __be32 *daddr;
Linus Torvalds's avatar
Linus Torvalds committed
43 44 45 46
	u16 sport;
	u16 dport;
	u16 family;
	u16 userlocks;
47
	u32 ifindex;
48
	u32 mark;
Linus Torvalds's avatar
Linus Torvalds committed
49 50
};

51 52
static DEFINE_MUTEX(inet_diag_table_mutex);

53
static const struct inet_diag_handler *inet_diag_lock_handler(int proto)
54
{
55
	if (!inet_diag_table[proto])
56
		sock_load_diag_module(AF_INET, proto);
57 58

	mutex_lock(&inet_diag_table_mutex);
59
	if (!inet_diag_table[proto])
60 61
		return ERR_PTR(-ENOENT);

62
	return inet_diag_table[proto];
63 64
}

Eric Dumazet's avatar
Eric Dumazet committed
65
static void inet_diag_unlock_handler(const struct inet_diag_handler *handler)
66 67 68 69
{
	mutex_unlock(&inet_diag_table_mutex);
}

70
void inet_diag_msg_common_fill(struct inet_diag_msg *r, struct sock *sk)
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
{
	r->idiag_family = sk->sk_family;

	r->id.idiag_sport = htons(sk->sk_num);
	r->id.idiag_dport = sk->sk_dport;
	r->id.idiag_if = sk->sk_bound_dev_if;
	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 = sk->sk_v6_rcv_saddr;
		*(struct in6_addr *)r->id.idiag_dst = sk->sk_v6_daddr;
	} 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] = sk->sk_rcv_saddr;
	r->id.idiag_dst[0] = sk->sk_daddr;
	}
}
93
EXPORT_SYMBOL_GPL(inet_diag_msg_common_fill);
94

95 96 97
static size_t inet_sk_attr_size(struct sock *sk,
				const struct inet_diag_req_v2 *req,
				bool net_admin)
98
{
99 100 101 102 103 104 105
	const struct inet_diag_handler *handler;
	size_t aux = 0;

	handler = inet_diag_table[req->sdiag_protocol];
	if (handler && handler->idiag_get_aux_size)
		aux = handler->idiag_get_aux_size(sk, net_admin);

106 107 108 109
	return	  nla_total_size(sizeof(struct tcp_info))
		+ nla_total_size(1) /* INET_DIAG_SHUTDOWN */
		+ nla_total_size(1) /* INET_DIAG_TOS */
		+ nla_total_size(1) /* INET_DIAG_TCLASS */
110
		+ nla_total_size(4) /* INET_DIAG_MARK */
111
		+ nla_total_size(4) /* INET_DIAG_CLASS_ID */
112 113 114 115 116
		+ nla_total_size(sizeof(struct inet_diag_meminfo))
		+ nla_total_size(sizeof(struct inet_diag_msg))
		+ nla_total_size(SK_MEMINFO_VARS * sizeof(u32))
		+ nla_total_size(TCP_CA_NAME_MAX)
		+ nla_total_size(sizeof(struct tcpvegas_info))
117
		+ aux
118 119 120
		+ 64;
}

121 122
int inet_diag_msg_attrs_fill(struct sock *sk, struct sk_buff *skb,
			     struct inet_diag_msg *r, int ext,
123 124
			     struct user_namespace *user_ns,
			     bool net_admin)
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
{
	const struct inet_sock *inet = inet_sk(sk);

	if (nla_put_u8(skb, INET_DIAG_SHUTDOWN, sk->sk_shutdown))
		goto errout;

	/* IPv6 dual-stack sockets use inet->tos for IPv4 connections,
	 * hence this needs to be included regardless of socket family.
	 */
	if (ext & (1 << (INET_DIAG_TOS - 1)))
		if (nla_put_u8(skb, INET_DIAG_TOS, inet->tos) < 0)
			goto errout;

#if IS_ENABLED(CONFIG_IPV6)
	if (r->idiag_family == AF_INET6) {
		if (ext & (1 << (INET_DIAG_TCLASS - 1)))
			if (nla_put_u8(skb, INET_DIAG_TCLASS,
				       inet6_sk(sk)->tclass) < 0)
				goto errout;

		if (((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE)) &&
		    nla_put_u8(skb, INET_DIAG_SKV6ONLY, ipv6_only_sock(sk)))
			goto errout;
	}
#endif

151 152 153
	if (net_admin && nla_put_u32(skb, INET_DIAG_MARK, sk->sk_mark))
		goto errout;

154 155 156 157 158 159 160 161 162
	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);

163
int inet_sk_diag_fill(struct sock *sk, struct inet_connection_sock *icsk,
164
		      struct sk_buff *skb, const struct inet_diag_req_v2 *req,
Eric Dumazet's avatar
Eric Dumazet committed
165 166
		      struct user_namespace *user_ns,
		      u32 portid, u32 seq, u16 nlmsg_flags,
167 168
		      const struct nlmsghdr *unlh,
		      bool net_admin)
Linus Torvalds's avatar
Linus Torvalds committed
169
{
170
	const struct tcp_congestion_ops *ca_ops;
Eric Dumazet's avatar
Eric Dumazet committed
171 172
	const struct inet_diag_handler *handler;
	int ext = req->idiag_ext;
173
	struct inet_diag_msg *r;
Linus Torvalds's avatar
Linus Torvalds committed
174
	struct nlmsghdr  *nlh;
175
	struct nlattr *attr;
176 177
	void *info = NULL;

178
	handler = inet_diag_table[req->sdiag_protocol];
Eric Dumazet's avatar
Eric Dumazet committed
179
	BUG_ON(!handler);
Linus Torvalds's avatar
Linus Torvalds committed
180

181
	nlh = nlmsg_put(skb, portid, seq, unlh->nlmsg_type, sizeof(*r),
182 183
			nlmsg_flags);
	if (!nlh)
184
		return -EMSGSIZE;
185

186
	r = nlmsg_data(nlh);
187
	BUG_ON(!sk_fullsock(sk));
188

189
	inet_diag_msg_common_fill(r, sk);
190 191 192
	r->idiag_state = sk->sk_state;
	r->idiag_timer = 0;
	r->idiag_retrans = 0;
Linus Torvalds's avatar
Linus Torvalds committed
193

194
	if (inet_diag_msg_attrs_fill(sk, skb, r, ext, user_ns, net_admin))
195 196
		goto errout;

197 198 199 200 201 202 203 204 205 206
	if (ext & (1 << (INET_DIAG_MEMINFO - 1))) {
		struct inet_diag_meminfo minfo = {
			.idiag_rmem = sk_rmem_alloc_get(sk),
			.idiag_wmem = sk->sk_wmem_queued,
			.idiag_fmem = sk->sk_forward_alloc,
			.idiag_tmem = sk_wmem_alloc_get(sk),
		};

		if (nla_put(skb, INET_DIAG_MEMINFO, sizeof(minfo), &minfo) < 0)
			goto errout;
207 208
	}

209 210
	if (ext & (1 << (INET_DIAG_SKMEMINFO - 1)))
		if (sock_diag_put_meminfo(sk, skb, INET_DIAG_SKMEMINFO))
211
			goto errout;
212

213 214 215 216 217 218 219 220 221
	/*
	 * RAW sockets might have user-defined protocols assigned,
	 * so report the one supplied on socket creation.
	 */
	if (sk->sk_type == SOCK_RAW) {
		if (nla_put_u8(skb, INET_DIAG_PROTOCOL, sk->sk_protocol))
			goto errout;
	}

Eric Dumazet's avatar
Eric Dumazet committed
222
	if (!icsk) {
223
		handler->idiag_get_info(sk, r, NULL);
224 225 226
		goto out;
	}

227
	if (icsk->icsk_pending == ICSK_TIME_RETRANS ||
228
	    icsk->icsk_pending == ICSK_TIME_REO_TIMEOUT ||
229
	    icsk->icsk_pending == ICSK_TIME_LOSS_PROBE) {
230 231
		r->idiag_timer = 1;
		r->idiag_retrans = icsk->icsk_retransmits;
232 233
		r->idiag_expires =
			jiffies_to_msecs(icsk->icsk_timeout - jiffies);
234
	} else if (icsk->icsk_pending == ICSK_TIME_PROBE0) {
235 236
		r->idiag_timer = 4;
		r->idiag_retrans = icsk->icsk_probes_out;
237 238
		r->idiag_expires =
			jiffies_to_msecs(icsk->icsk_timeout - jiffies);
Linus Torvalds's avatar
Linus Torvalds committed
239
	} else if (timer_pending(&sk->sk_timer)) {
240 241
		r->idiag_timer = 2;
		r->idiag_retrans = icsk->icsk_probes_out;
242 243
		r->idiag_expires =
			jiffies_to_msecs(sk->sk_timer.expires - jiffies);
Linus Torvalds's avatar
Linus Torvalds committed
244
	} else {
245 246
		r->idiag_timer = 0;
		r->idiag_expires = 0;
Linus Torvalds's avatar
Linus Torvalds committed
247
	}
248

249
	if ((ext & (1 << (INET_DIAG_INFO - 1))) && handler->idiag_info_size) {
250 251 252
		attr = nla_reserve_64bit(skb, INET_DIAG_INFO,
					 handler->idiag_info_size,
					 INET_DIAG_PAD);
253 254
		if (!attr)
			goto errout;
255

256
		info = nla_data(attr);
Linus Torvalds's avatar
Linus Torvalds committed
257 258
	}

259 260 261 262 263 264 265 266 267
	if (ext & (1 << (INET_DIAG_CONG - 1))) {
		int err = 0;

		rcu_read_lock();
		ca_ops = READ_ONCE(icsk->icsk_ca_ops);
		if (ca_ops)
			err = nla_put_string(skb, INET_DIAG_CONG, ca_ops->name);
		rcu_read_unlock();
		if (err < 0)
268
			goto errout;
269
	}
270

271
	handler->idiag_get_info(sk, r, info);
Linus Torvalds's avatar
Linus Torvalds committed
272

273 274 275 276
	if (ext & (1 << (INET_DIAG_INFO - 1)) && handler->idiag_get_aux)
		if (handler->idiag_get_aux(sk, net_admin, skb) < 0)
			goto errout;

277
	if (sk->sk_state < TCP_TIME_WAIT) {
278 279 280
		union tcp_cc_info info;
		size_t sz = 0;
		int attr;
281 282 283 284

		rcu_read_lock();
		ca_ops = READ_ONCE(icsk->icsk_ca_ops);
		if (ca_ops && ca_ops->get_info)
285
			sz = ca_ops->get_info(sk, ext, &attr, &info);
286
		rcu_read_unlock();
287
		if (sz && nla_put(skb, attr, sz, &info) < 0)
288 289
			goto errout;
	}
Linus Torvalds's avatar
Linus Torvalds committed
290

291 292
	if (ext & (1 << (INET_DIAG_CLASS_ID - 1)) ||
	    ext & (1 << (INET_DIAG_TCLASS - 1))) {
293 294 295 296 297
		u32 classid = 0;

#ifdef CONFIG_SOCK_CGROUP_DATA
		classid = sock_cgroup_classid(&sk->sk_cgrp_data);
#endif
298 299 300 301 302 303
		/* Fallback to socket priority if class id isn't set.
		 * Classful qdiscs use it as direct reference to class.
		 * For cgroup2 classid is always zero.
		 */
		if (!classid)
			classid = sk->sk_priority;
304 305 306 307 308

		if (nla_put_u32(skb, INET_DIAG_CLASS_ID, classid))
			goto errout;
	}

309
out:
310 311
	nlmsg_end(skb, nlh);
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
312

313 314
errout:
	nlmsg_cancel(skb, nlh);
315
	return -EMSGSIZE;
Linus Torvalds's avatar
Linus Torvalds committed
316
}
317 318 319
EXPORT_SYMBOL_GPL(inet_sk_diag_fill);

static int inet_csk_diag_fill(struct sock *sk,
Eric Dumazet's avatar
Eric Dumazet committed
320
			      struct sk_buff *skb,
321
			      const struct inet_diag_req_v2 *req,
322
			      struct user_namespace *user_ns,
323
			      u32 portid, u32 seq, u16 nlmsg_flags,
324 325
			      const struct nlmsghdr *unlh,
			      bool net_admin)
326
{
327 328
	return inet_sk_diag_fill(sk, inet_csk(sk), skb, req, user_ns,
				 portid, seq, nlmsg_flags, unlh, net_admin);
329
}
Linus Torvalds's avatar
Linus Torvalds committed
330

331
static int inet_twsk_diag_fill(struct sock *sk,
Eric Dumazet's avatar
Eric Dumazet committed
332
			       struct sk_buff *skb,
333
			       u32 portid, u32 seq, u16 nlmsg_flags,
334 335
			       const struct nlmsghdr *unlh)
{
336
	struct inet_timewait_sock *tw = inet_twsk(sk);
337
	struct inet_diag_msg *r;
338
	struct nlmsghdr *nlh;
339
	long tmo;
340

341
	nlh = nlmsg_put(skb, portid, seq, unlh->nlmsg_type, sizeof(*r),
342 343
			nlmsg_flags);
	if (!nlh)
344
		return -EMSGSIZE;
345

346
	r = nlmsg_data(nlh);
347 348
	BUG_ON(tw->tw_state != TCP_TIME_WAIT);

349
	tmo = tw->tw_timer.expires - jiffies;
350 351 352
	if (tmo < 0)
		tmo = 0;

353
	inet_diag_msg_common_fill(r, sk);
354
	r->idiag_retrans      = 0;
355

356 357
	r->idiag_state	      = tw->tw_substate;
	r->idiag_timer	      = 3;
358
	r->idiag_expires      = jiffies_to_msecs(tmo);
359 360 361 362
	r->idiag_rqueue	      = 0;
	r->idiag_wqueue	      = 0;
	r->idiag_uid	      = 0;
	r->idiag_inode	      = 0;
363

364 365
	nlmsg_end(skb, nlh);
	return 0;
366 367
}

368 369
static int inet_req_diag_fill(struct sock *sk, struct sk_buff *skb,
			      u32 portid, u32 seq, u16 nlmsg_flags,
370
			      const struct nlmsghdr *unlh, bool net_admin)
371
{
372
	struct request_sock *reqsk = inet_reqsk(sk);
373 374 375 376 377 378 379 380 381 382 383 384 385
	struct inet_diag_msg *r;
	struct nlmsghdr *nlh;
	long tmo;

	nlh = nlmsg_put(skb, portid, seq, unlh->nlmsg_type, sizeof(*r),
			nlmsg_flags);
	if (!nlh)
		return -EMSGSIZE;

	r = nlmsg_data(nlh);
	inet_diag_msg_common_fill(r, sk);
	r->idiag_state = TCP_SYN_RECV;
	r->idiag_timer = 1;
386
	r->idiag_retrans = reqsk->num_retrans;
387 388 389 390

	BUILD_BUG_ON(offsetof(struct inet_request_sock, ir_cookie) !=
		     offsetof(struct sock, sk_cookie));

391
	tmo = inet_reqsk(sk)->rsk_timer.expires - jiffies;
392 393 394 395 396 397
	r->idiag_expires = (tmo >= 0) ? jiffies_to_msecs(tmo) : 0;
	r->idiag_rqueue	= 0;
	r->idiag_wqueue	= 0;
	r->idiag_uid	= 0;
	r->idiag_inode	= 0;

398 399 400 401
	if (net_admin && nla_put_u32(skb, INET_DIAG_MARK,
				     inet_rsk(reqsk)->ir_mark))
		return -EMSGSIZE;

402 403 404 405
	nlmsg_end(skb, nlh);
	return 0;
}

406
static int sk_diag_fill(struct sock *sk, struct sk_buff *skb,
407
			const struct inet_diag_req_v2 *r,
408
			struct user_namespace *user_ns,
409
			u32 portid, u32 seq, u16 nlmsg_flags,
410
			const struct nlmsghdr *unlh, bool net_admin)
411 412
{
	if (sk->sk_state == TCP_TIME_WAIT)
413
		return inet_twsk_diag_fill(sk, skb, portid, seq,
414 415
					   nlmsg_flags, unlh);

416 417
	if (sk->sk_state == TCP_NEW_SYN_RECV)
		return inet_req_diag_fill(sk, skb, portid, seq,
418
					  nlmsg_flags, unlh, net_admin);
419

420
	return inet_csk_diag_fill(sk, skb, r, user_ns, portid, seq,
421
				  nlmsg_flags, unlh, net_admin);
422 423
}

424 425 426
struct sock *inet_diag_find_one_icsk(struct net *net,
				     struct inet_hashinfo *hashinfo,
				     const struct inet_diag_req_v2 *req)
Linus Torvalds's avatar
Linus Torvalds committed
427
{
Eric Dumazet's avatar
Eric Dumazet committed
428
	struct sock *sk;
429

430
	rcu_read_lock();
Eric Dumazet's avatar
Eric Dumazet committed
431
	if (req->sdiag_family == AF_INET)
432
		sk = inet_lookup(net, hashinfo, NULL, 0, req->id.idiag_dst[0],
433 434
				 req->id.idiag_dport, req->id.idiag_src[0],
				 req->id.idiag_sport, req->id.idiag_if);
435
#if IS_ENABLED(CONFIG_IPV6)
436 437 438
	else if (req->sdiag_family == AF_INET6) {
		if (ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_dst) &&
		    ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_src))
439
			sk = inet_lookup(net, hashinfo, NULL, 0, req->id.idiag_dst[3],
440 441 442
					 req->id.idiag_dport, req->id.idiag_src[3],
					 req->id.idiag_sport, req->id.idiag_if);
		else
443
			sk = inet6_lookup(net, hashinfo, NULL, 0,
444 445 446 447 448 449
					  (struct in6_addr *)req->id.idiag_dst,
					  req->id.idiag_dport,
					  (struct in6_addr *)req->id.idiag_src,
					  req->id.idiag_sport,
					  req->id.idiag_if);
	}
Linus Torvalds's avatar
Linus Torvalds committed
450
#endif
451 452
	else {
		rcu_read_unlock();
453
		return ERR_PTR(-EINVAL);
454 455
	}
	rcu_read_unlock();
Eric Dumazet's avatar
Eric Dumazet committed
456
	if (!sk)
457
		return ERR_PTR(-ENOENT);
Linus Torvalds's avatar
Linus Torvalds committed
458

459 460 461 462 463 464 465 466 467 468 469 470 471 472
	if (sock_diag_check_cookie(sk, req->id.idiag_cookie)) {
		sock_gen_put(sk);
		return ERR_PTR(-ENOENT);
	}

	return sk;
}
EXPORT_SYMBOL_GPL(inet_diag_find_one_icsk);

int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
			    struct sk_buff *in_skb,
			    const struct nlmsghdr *nlh,
			    const struct inet_diag_req_v2 *req)
{
473
	bool net_admin = netlink_net_capable(in_skb, CAP_NET_ADMIN);
474 475 476 477 478 479 480 481
	struct net *net = sock_net(in_skb->sk);
	struct sk_buff *rep;
	struct sock *sk;
	int err;

	sk = inet_diag_find_one_icsk(net, hashinfo, req);
	if (IS_ERR(sk))
		return PTR_ERR(sk);
Linus Torvalds's avatar
Linus Torvalds committed
482

483
	rep = nlmsg_new(inet_sk_attr_size(sk, req, net_admin), GFP_KERNEL);
484 485
	if (!rep) {
		err = -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
486
		goto out;
487
	}
Linus Torvalds's avatar
Linus Torvalds committed
488

489
	err = sk_diag_fill(sk, rep, req,
490
			   sk_user_ns(NETLINK_CB(in_skb).sk),
491
			   NETLINK_CB(in_skb).portid,
492
			   nlh->nlmsg_seq, 0, nlh, net_admin);
493 494
	if (err < 0) {
		WARN_ON(err == -EMSGSIZE);
495
		nlmsg_free(rep);
496 497
		goto out;
	}
498
	err = netlink_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid,
499
			      MSG_DONTWAIT);
Linus Torvalds's avatar
Linus Torvalds committed
500 501 502 503
	if (err > 0)
		err = 0;

out:
504 505 506
	if (sk)
		sock_gen_put(sk);

507 508
	return err;
}
509
EXPORT_SYMBOL_GPL(inet_diag_dump_one_icsk);
510

511
static int inet_diag_cmd_exact(int cmd, struct sk_buff *in_skb,
512
			       const struct nlmsghdr *nlh,
513
			       const struct inet_diag_req_v2 *req)
514 515 516 517 518 519 520
{
	const struct inet_diag_handler *handler;
	int err;

	handler = inet_diag_lock_handler(req->sdiag_protocol);
	if (IS_ERR(handler))
		err = PTR_ERR(handler);
521
	else if (cmd == SOCK_DIAG_BY_FAMILY)
522
		err = handler->dump_one(in_skb, nlh, req);
523 524 525 526
	else if (cmd == SOCK_DESTROY && handler->destroy)
		err = handler->destroy(in_skb, req);
	else
		err = -EOPNOTSUPP;
527
	inet_diag_unlock_handler(handler);
528

Linus Torvalds's avatar
Linus Torvalds committed
529 530 531
	return err;
}

Al Viro's avatar
Al Viro committed
532
static int bitstring_match(const __be32 *a1, const __be32 *a2, int bits)
Linus Torvalds's avatar
Linus Torvalds committed
533 534 535 536 537 538 539 540 541 542
{
	int words = bits >> 5;

	bits &= 0x1f;

	if (words) {
		if (memcmp(a1, a2, words << 2))
			return 0;
	}
	if (bits) {
Al Viro's avatar
Al Viro committed
543 544
		__be32 w1, w2;
		__be32 mask;
Linus Torvalds's avatar
Linus Torvalds committed
545 546 547 548 549 550 551 552 553 554 555 556 557

		w1 = a1[words];
		w2 = a2[words];

		mask = htonl((0xffffffff) << (32 - bits));

		if ((w1 ^ w2) & mask)
			return 0;
	}

	return 1;
}

558
static int inet_diag_bc_run(const struct nlattr *_bc,
Eric Dumazet's avatar
Eric Dumazet committed
559
			    const struct inet_diag_entry *entry)
Linus Torvalds's avatar
Linus Torvalds committed
560
{
561 562 563
	const void *bc = nla_data(_bc);
	int len = nla_len(_bc);

Linus Torvalds's avatar
Linus Torvalds committed
564 565
	while (len > 0) {
		int yes = 1;
566
		const struct inet_diag_bc_op *op = bc;
Linus Torvalds's avatar
Linus Torvalds committed
567 568

		switch (op->code) {
569
		case INET_DIAG_BC_NOP:
Linus Torvalds's avatar
Linus Torvalds committed
570
			break;
571
		case INET_DIAG_BC_JMP:
Linus Torvalds's avatar
Linus Torvalds committed
572 573
			yes = 0;
			break;
574 575 576
		case INET_DIAG_BC_S_EQ:
			yes = entry->sport == op[1].no;
			break;
577
		case INET_DIAG_BC_S_GE:
Linus Torvalds's avatar
Linus Torvalds committed
578 579
			yes = entry->sport >= op[1].no;
			break;
580
		case INET_DIAG_BC_S_LE:
581
			yes = entry->sport <= op[1].no;
Linus Torvalds's avatar
Linus Torvalds committed
582
			break;
583 584 585
		case INET_DIAG_BC_D_EQ:
			yes = entry->dport == op[1].no;
			break;
586
		case INET_DIAG_BC_D_GE:
Linus Torvalds's avatar
Linus Torvalds committed
587 588
			yes = entry->dport >= op[1].no;
			break;
589
		case INET_DIAG_BC_D_LE:
Linus Torvalds's avatar
Linus Torvalds committed
590 591
			yes = entry->dport <= op[1].no;
			break;
592
		case INET_DIAG_BC_AUTO:
Linus Torvalds's avatar
Linus Torvalds committed
593 594
			yes = !(entry->userlocks & SOCK_BINDPORT_LOCK);
			break;
595
		case INET_DIAG_BC_S_COND:
596
		case INET_DIAG_BC_D_COND: {
Eric Dumazet's avatar
Eric Dumazet committed
597 598
			const struct inet_diag_hostcond *cond;
			const __be32 *addr;
Linus Torvalds's avatar
Linus Torvalds committed
599

Eric Dumazet's avatar
Eric Dumazet committed
600
			cond = (const struct inet_diag_hostcond *)(op + 1);
Linus Torvalds's avatar
Linus Torvalds committed
601
			if (cond->port != -1 &&
602
			    cond->port != (op->code == INET_DIAG_BC_S_COND ?
Linus Torvalds's avatar
Linus Torvalds committed
603 604 605 606
					     entry->sport : entry->dport)) {
				yes = 0;
				break;
			}
607

608
			if (op->code == INET_DIAG_BC_S_COND)
Linus Torvalds's avatar
Linus Torvalds committed
609 610 611 612
				addr = entry->saddr;
			else
				addr = entry->daddr;

613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
			if (cond->family != AF_UNSPEC &&
			    cond->family != entry->family) {
				if (entry->family == AF_INET6 &&
				    cond->family == AF_INET) {
					if (addr[0] == 0 && addr[1] == 0 &&
					    addr[2] == htonl(0xffff) &&
					    bitstring_match(addr + 3,
							    cond->addr,
							    cond->prefix_len))
						break;
				}
				yes = 0;
				break;
			}

			if (cond->prefix_len == 0)
				break;
630 631
			if (bitstring_match(addr, cond->addr,
					    cond->prefix_len))
Linus Torvalds's avatar
Linus Torvalds committed
632 633 634 635
				break;
			yes = 0;
			break;
		}
636 637 638 639 640 641 642 643
		case INET_DIAG_BC_DEV_COND: {
			u32 ifindex;

			ifindex = *((const u32 *)(op + 1));
			if (ifindex != entry->ifindex)
				yes = 0;
			break;
		}
644 645 646 647 648 649 650 651
		case INET_DIAG_BC_MARK_COND: {
			struct inet_diag_markcond *cond;

			cond = (struct inet_diag_markcond *)(op + 1);
			if ((entry->mark & cond->mask) != cond->mark)
				yes = 0;
			break;
		}
Linus Torvalds's avatar
Linus Torvalds committed
652 653
		}

654
		if (yes) {
Linus Torvalds's avatar
Linus Torvalds committed
655 656 657 658 659 660 661
			len -= op->yes;
			bc += op->yes;
		} else {
			len -= op->no;
			bc += op->no;
		}
	}
662
	return len == 0;
Linus Torvalds's avatar
Linus Torvalds committed
663 664
}

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
/* This helper is available for all sockets (ESTABLISH, TIMEWAIT, SYN_RECV)
 */
static void entry_fill_addrs(struct inet_diag_entry *entry,
			     const struct sock *sk)
{
#if IS_ENABLED(CONFIG_IPV6)
	if (sk->sk_family == AF_INET6) {
		entry->saddr = sk->sk_v6_rcv_saddr.s6_addr32;
		entry->daddr = sk->sk_v6_daddr.s6_addr32;
	} else
#endif
	{
		entry->saddr = &sk->sk_rcv_saddr;
		entry->daddr = &sk->sk_daddr;
	}
}

682 683 684
int inet_diag_bc_sk(const struct nlattr *bc, struct sock *sk)
{
	struct inet_sock *inet = inet_sk(sk);
Eric Dumazet's avatar
Eric Dumazet committed
685
	struct inet_diag_entry entry;
686

Eric Dumazet's avatar
Eric Dumazet committed
687
	if (!bc)
688 689 690
		return 1;

	entry.family = sk->sk_family;
691
	entry_fill_addrs(&entry, sk);
692 693
	entry.sport = inet->inet_num;
	entry.dport = ntohs(inet->inet_dport);
694
	entry.ifindex = sk->sk_bound_dev_if;
695
	entry.userlocks = sk_fullsock(sk) ? sk->sk_userlocks : 0;
696 697 698 699 700 701
	if (sk_fullsock(sk))
		entry.mark = sk->sk_mark;
	else if (sk->sk_state == TCP_NEW_SYN_RECV)
		entry.mark = inet_rsk(inet_reqsk(sk))->ir_mark;
	else
		entry.mark = 0;
702 703 704 705 706

	return inet_diag_bc_run(bc, &entry);
}
EXPORT_SYMBOL_GPL(inet_diag_bc_sk);

Linus Torvalds's avatar
Linus Torvalds committed
707 708 709
static int valid_cc(const void *bc, int len, int cc)
{
	while (len >= 0) {
710
		const struct inet_diag_bc_op *op = bc;
Linus Torvalds's avatar
Linus Torvalds committed
711 712 713 714 715

		if (cc > len)
			return 0;
		if (cc == len)
			return 1;
716
		if (op->yes < 4 || op->yes & 3)
Linus Torvalds's avatar
Linus Torvalds committed
717 718 719 720 721 722 723
			return 0;
		len -= op->yes;
		bc  += op->yes;
	}
	return 0;
}

724 725 726 727 728 729 730 731 732 733 734
/* data is u32 ifindex */
static bool valid_devcond(const struct inet_diag_bc_op *op, int len,
			  int *min_len)
{
	/* Check ifindex space. */
	*min_len += sizeof(u32);
	if (len < *min_len)
		return false;

	return true;
}
735 736 737 738 739
/* Validate an inet_diag_hostcond. */
static bool valid_hostcond(const struct inet_diag_bc_op *op, int len,
			   int *min_len)
{
	struct inet_diag_hostcond *cond;
Eric Dumazet's avatar
Eric Dumazet committed
740
	int addr_len;
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772

	/* Check hostcond space. */
	*min_len += sizeof(struct inet_diag_hostcond);
	if (len < *min_len)
		return false;
	cond = (struct inet_diag_hostcond *)(op + 1);

	/* Check address family and address length. */
	switch (cond->family) {
	case AF_UNSPEC:
		addr_len = 0;
		break;
	case AF_INET:
		addr_len = sizeof(struct in_addr);
		break;
	case AF_INET6:
		addr_len = sizeof(struct in6_addr);
		break;
	default:
		return false;
	}
	*min_len += addr_len;
	if (len < *min_len)
		return false;

	/* Check prefix length (in bits) vs address length (in bytes). */
	if (cond->prefix_len > 8 * addr_len)
		return false;

	return true;
}

773
/* Validate a port comparison operator. */
Eric Dumazet's avatar
Eric Dumazet committed
774 775
static bool valid_port_comparison(const struct inet_diag_bc_op *op,
				  int len, int *min_len)
776 777 778 779 780 781 782 783
{
	/* Port comparisons put the port in a follow-on inet_diag_bc_op. */
	*min_len += sizeof(struct inet_diag_bc_op);
	if (len < *min_len)
		return false;
	return true;
}

784 785
static bool valid_markcond(const struct inet_diag_bc_op *op, int len,
			   int *min_len)
Linus Torvalds's avatar
Linus Torvalds committed
786
{
787 788 789 790 791 792 793 794
	*min_len += sizeof(struct inet_diag_markcond);
	return len >= *min_len;
}

static int inet_diag_bc_audit(const struct nlattr *attr,
			      const struct sk_buff *skb)
{
	bool net_admin = netlink_net_capable(skb, CAP_NET_ADMIN);
795 796 797 798 799 800 801 802
	const void *bytecode, *bc;
	int bytecode_len, len;

	if (!attr || nla_len(attr) < sizeof(struct inet_diag_bc_op))
		return -EINVAL;

	bytecode = bc = nla_data(attr);
	len = bytecode_len = nla_len(attr);
Linus Torvalds's avatar
Linus Torvalds committed
803 804

	while (len > 0) {
805
		int min_len = sizeof(struct inet_diag_bc_op);
Eric Dumazet's avatar
Eric Dumazet committed
806
		const struct inet_diag_bc_op *op = bc;
Linus Torvalds's avatar
Linus Torvalds committed
807 808

		switch (op->code) {
809 810
		case INET_DIAG_BC_S_COND:
		case INET_DIAG_BC_D_COND:
811 812
			if (!valid_hostcond(bc, len, &min_len))
				return -EINVAL;
813
			break;
814 815 816 817
		case INET_DIAG_BC_DEV_COND:
			if (!valid_devcond(bc, len, &min_len))
				return -EINVAL;
			break;
818
		case INET_DIAG_BC_S_EQ:
819 820
		case INET_DIAG_BC_S_GE:
		case INET_DIAG_BC_S_LE:
821
		case INET_DIAG_BC_D_EQ:
822 823
		case INET_DIAG_BC_D_GE:
		case INET_DIAG_BC_D_LE:
824
			if (!valid_port_comparison(bc, len, &min_len))
Linus Torvalds's avatar
Linus Torvalds committed
825 826
				return -EINVAL;
			break;
827 828 829 830 831 832
		case INET_DIAG_BC_MARK_COND:
			if (!net_admin)
				return -EPERM;
			if (!valid_markcond(bc, len, &min_len))
				return -EINVAL;
			break;
833 834
		case INET_DIAG_BC_AUTO:
		case INET_DIAG_BC_JMP:
835
		case INET_DIAG_BC_NOP:
Linus Torvalds's avatar
Linus Torvalds committed
836 837 838 839
			break;
		default:
			return -EINVAL;
		}
840 841 842 843 844 845 846 847 848

		if (op->code != INET_DIAG_BC_NOP) {
			if (op->no < min_len || op->no > len + 4 || op->no & 3)
				return -EINVAL;
			if (op->no < len &&
			    !valid_cc(bytecode, bytecode_len, len - op->no))
				return -EINVAL;
		}

849
		if (op->yes < min_len || op->yes > len + 4 || op->yes & 3)
850
			return -EINVAL;
851
		bc  += op->yes;
Linus Torvalds's avatar
Linus Torvalds committed
852 853 854 855 856
		len -= op->yes;
	}
	return len == 0 ? 0 : -EINVAL;
}

857 858
static int inet_csk_diag_dump(struct sock *sk,
			      struct sk_buff *skb,
859
			      struct netlink_callback *cb,
860
			      const struct inet_diag_req_v2 *r,
861 862
			      const struct nlattr *bc,
			      bool net_admin)
Linus Torvalds's avatar
Linus Torvalds committed
863
{
864 865
	if (!inet_diag_bc_sk(bc, sk))
		return 0;
Linus Torvalds's avatar
Linus Torvalds committed
866

867
	return inet_csk_diag_fill(sk, skb, r,
868
				  sk_user_ns(NETLINK_CB(cb->skb).sk),
869
				  NETLINK_CB(cb->skb).portid,
870 871
				  cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh,
				  net_admin);
Linus Torvalds's avatar
Linus Torvalds committed
872 873
}

874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
static void twsk_build_assert(void)
{
	BUILD_BUG_ON(offsetof(struct inet_timewait_sock, tw_family) !=
		     offsetof(struct sock, sk_family));

	BUILD_BUG_ON(offsetof(struct inet_timewait_sock, tw_num) !=
		     offsetof(struct inet_sock, inet_num));

	BUILD_BUG_ON(offsetof(struct inet_timewait_sock, tw_dport) !=
		     offsetof(struct inet_sock, inet_dport));

	BUILD_BUG_ON(offsetof(struct inet_timewait_sock, tw_rcv_saddr) !=
		     offsetof(struct inet_sock, inet_rcv_saddr));

	BUILD_BUG_ON(offsetof(struct inet_timewait_sock, tw_daddr) !=
		     offsetof(struct inet_sock, inet_daddr));

#if IS_ENABLED(CONFIG_IPV6)
	BUILD_BUG_ON(offsetof(struct inet_timewait_sock, tw_v6_rcv_saddr) !=
		     offsetof(struct sock, sk_v6_rcv_saddr));

	BUILD_BUG_ON(offsetof(struct inet_timewait_sock, tw_v6_daddr) !=
		     offsetof(struct sock, sk_v6_daddr));
#endif
}

900
void inet_diag_dump_icsk(struct inet_hashinfo *hashinfo, struct sk_buff *skb,
Eric Dumazet's avatar
Eric Dumazet committed
901
			 struct netlink_callback *cb,
902
			 const struct inet_diag_req_v2 *r, struct nlattr *bc)
Linus Torvalds's avatar
Linus Torvalds committed
903
{
904
	bool net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN);
905
	struct net *net = sock_net(skb->sk);
906
	u32 idiag_states = r->idiag_states;
907 908
	int i, num, s_i, s_num;
	struct sock *sk;
909

910 911
	if (idiag_states & TCPF_SYN_RECV)
		idiag_states |= TCPF_NEW_SYN_RECV;
Linus Torvalds's avatar
Linus Torvalds committed
912 913
	s_i = cb->args[1];
	s_num = num = cb->args[2];
914

Linus Torvalds's avatar
Linus Torvalds committed
915
	if (cb->args[0] == 0) {
916
		if (!(idiag_states & TCPF_LISTEN) || r->id.idiag_dport)
Linus Torvalds's avatar
Linus Torvalds committed
917
			goto skip_listen_ht;
918

919
		for (i = s_i; i < INET_LHTABLE_SIZE; i++) {
920
			struct inet_listen_hashbucket *ilb;
Linus Torvalds's avatar
Linus Torvalds committed
921 922

			num = 0;
923
			ilb = &hashinfo->listening_hash[i];
924
			spin_lock(&ilb->lock);
925
			sk_for_each(sk, &ilb->head) {
Linus Torvalds's avatar
Linus Torvalds committed
926 927
				struct inet_sock *inet = inet_sk(sk);

928 929 930
				if (!net_eq(sock_net(sk), net))
					continue;

Linus Torvalds's avatar
Linus Torvalds committed
931 932 933 934 935
				if (num < s_num) {
					num++;
					continue;
				}

936
				if (r->sdiag_family != AF_UNSPEC &&
Eric Dumazet's avatar
Eric Dumazet committed
937
				    sk->sk_family != r->sdiag_family)
938 939
					goto next_listen;

940
				if (r->id.idiag_sport != inet->inet_sport &&
941
				    r->id.idiag_sport)
Linus Torvalds's avatar
Linus Torvalds committed
942 943
					goto next_listen;

944 945
				if (inet_csk_diag_dump(sk, skb, cb, r,
						       bc, net_admin) < 0) {
946
					spin_unlock(&ilb->lock);
Linus Torvalds's avatar
Linus Torvalds committed
947 948 949 950 951 952
					goto done;
				}

next_listen:
				++num;
			}
953
			spin_unlock(&ilb->lock);
Linus Torvalds's avatar
Linus Torvalds committed
954 955 956 957 958 959 960 961

			s_num = 0;
		}
skip_listen_ht:
		cb->args[0] = 1;
		s_i = num = s_num = 0;
	}

962
	if (!(idiag_states & ~TCPF_LISTEN))
963
		goto out;
Linus Torvalds's avatar
Linus Torvalds committed
964

965
#define SKARR_SZ 16
966
	for (i = s_i; i <= hashinfo->ehash_mask; i++) {
967
		struct inet_ehash_bucket *head = &hashinfo->ehash[i];
968
		spinlock_t *lock = inet_ehash_lockp(hashinfo, i);
969
		struct hlist_nulls_node *node;
970 971 972
		struct sock *sk_arr[SKARR_SZ];
		int num_arr[SKARR_SZ];
		int idx, accum, res;
973

Eric Dumazet's avatar
Eric Dumazet committed
974
		if (hlist_nulls_empty(&head->chain))
975 976
			continue;

Linus Torvalds's avatar
Linus Torvalds committed
977 978 979
		if (i > s_i)
			s_num = 0;

980 981 982
next_chunk:
		num = 0;
		accum = 0;
983
		spin_lock_bh(lock);
984
		sk_nulls_for_each(sk, node, &head->chain) {
985
			int state;
Linus Torvalds's avatar
Linus Torvalds committed
986

987 988
			if (!net_eq(sock_net(sk), net))
				continue;
Linus Torvalds's avatar
Linus Torvalds committed
989 990
			if (num < s_num)
				goto next_normal;
991 992
			state = (sk->sk_state == TCP_TIME_WAIT) ?
				inet_twsk(sk)->tw_substate : sk->sk_state;
993
			if (!(idiag_states & (1 << state)))
Linus Torvalds's avatar
Linus Torvalds committed
994
				goto next_normal;
995
			if (r->sdiag_family != AF_UNSPEC &&
Eric Dumazet's avatar
Eric Dumazet committed
996
			    sk->sk_family != r->sdiag_family)
997
				goto next_normal;
Eric Dumazet's avatar
Eric Dumazet committed
998
			if (r->id.idiag_sport != htons(sk->sk_num) &&
999
			    r->id.idiag_sport)
Linus Torvalds's avatar
Linus Torvalds committed
1000
				goto next_normal;
Eric Dumazet's avatar
Eric Dumazet committed
1001
			if (r->id.idiag_dport != sk->sk_dport &&
1002
			    r->id.idiag_dport)
Linus Torvalds's avatar
Linus Torvalds committed
1003
				goto next_normal;
1004 1005 1006 1007 1008
			twsk_build_assert();

			if (!inet_diag_bc_sk(bc, sk))
				goto next_normal;

1009 1010 1011
			if (!refcount_inc_not_zero(&sk->sk_refcnt))
				goto next_normal;

1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
			num_arr[accum] = num;
			sk_arr[accum] = sk;
			if (++accum == SKARR_SZ)
				break;
next_normal:
			++num;
		}
		spin_unlock_bh(lock);
		res = 0;
		for (idx = 0; idx < accum; idx++) {
			if (res >= 0) {
				res = sk_diag_fill(sk_arr[idx], skb, r,
1024 1025 1026
					   sk_user_ns(NETLINK_CB(cb->skb).sk),
					   NETLINK_CB(cb->skb).portid,
					   cb->nlh->nlmsg_seq, NLM_F_MULTI,
1027
					   cb->nlh, net_admin);
1028 1029
				if (res < 0)
					num = num_arr[idx];
Linus Torvalds's avatar
Linus Torvalds committed
1030
			}
1031
			sock_gen_put(sk_arr[idx]);
Linus Torvalds's avatar
Linus Torvalds committed
1032
		}
1033 1034
		if (res < 0)
			break;
1035
		cond_resched();
1036 1037 1038 1039
		if (accum == SKARR_SZ) {
			s_num = num + 1;
			goto next_chunk;
		}
Linus Torvalds's avatar
Linus Torvalds committed
1040 1041 1042 1043 1044
	}

done:
	cb->args[1] = i;
	cb->args[2] = num;
1045 1046 1047
out:
	;
}
1048
EXPORT_SYMBOL_GPL(inet_diag_dump_icsk);
1049 1050

static int __inet_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
1051
			    const struct inet_diag_req_v2 *r,
Eric Dumazet's avatar
Eric Dumazet committed
1052
			    struct nlattr *bc)
1053 1054
{
	const struct inet_diag_handler *handler;
1055
	int err = 0;
1056 1057 1058

	handler = inet_diag_lock_handler(r->sdiag_protocol);
	if (!IS_ERR(handler))
1059
		handler->dump(skb, cb, r, bc);
1060 1061
	else
		err = PTR_ERR(handler);
1062
	inet_diag_unlock_handler(handler);
1063

1064
	return err ? : skb->len;
Linus Torvalds's avatar
Linus Torvalds committed
1065 1066
}

1067 1068
static int inet_diag_dump(struct sk_buff *skb, struct netlink_callback *cb)
{
1069
	int hdrlen = sizeof(struct inet_diag_req_v2);
Eric Dumazet's avatar
Eric Dumazet committed
1070
	struct nlattr *bc = NULL;
1071 1072 1073 1074

	if (nlmsg_attrlen(cb->nlh, hdrlen))
		bc = nlmsg_find_attr(cb->nlh, hdrlen, INET_DIAG_REQ_BYTECODE);

1075
	return __inet_diag_dump(skb, cb, nlmsg_data(cb->nlh), bc);
1076 1077
}

Eric Dumazet's avatar
Eric Dumazet committed
1078
static int inet_diag_type2proto(int type)
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
{
	switch (type) {
	case TCPDIAG_GETSOCK:
		return IPPROTO_TCP;
	case DCCPDIAG_GETSOCK:
		return IPPROTO_DCCP;
	default:
		return 0;
	}
}

Eric Dumazet's avatar
Eric Dumazet committed
1090 1091
static int inet_diag_dump_compat(struct sk_buff *skb,
				 struct netlink_callback *cb)
1092
{
1093
	struct inet_diag_req *rc = nlmsg_data(cb->nlh);
Eric Dumazet's avatar
Eric Dumazet committed
1094
	int hdrlen = sizeof(struct inet_diag_req);
1095
	struct inet_diag_req_v2 req;
1096 1097
	struct nlattr *bc = NULL;

1098
	req.sdiag_family = AF_UNSPEC; /* compatibility */
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
	req.sdiag_protocol = inet_diag_type2proto(cb->nlh->nlmsg_type);
	req.idiag_ext = rc->idiag_ext;
	req.idiag_states = rc->idiag_states;
	req.id = rc->id;

	if (nlmsg_attrlen(cb->nlh, hdrlen))
		bc = nlmsg_find_attr(cb->nlh, hdrlen, INET_DIAG_REQ_BYTECODE);

	return __inet_diag_dump(skb, cb, &req, bc);
}

1110
static int inet_diag_get_exact_compat(struct sk_buff *in_skb,
Eric Dumazet's avatar
Eric Dumazet committed
1111
				      const struct nlmsghdr *nlh)
1112
{
1113
	struct inet_diag_req *rc = nlmsg_data(nlh);
1114
	struct inet_diag_req_v2 req;
1115 1116 1117 1118 1119 1120 1121

	req.sdiag_family = rc->idiag_family;
	req.sdiag_protocol = inet_diag_type2proto(nlh->nlmsg_type);
	req.idiag_ext = rc->idiag_ext;
	req.idiag_states = rc->idiag_states;
	req.id = rc->id;

1122
	return inet_diag_cmd_exact(SOCK_DIAG_BY_FAMILY, in_skb, nlh, &req);
1123 1124
}

1125
static int inet_diag_rcv_msg_compat(struct sk_buff *skb, struct nlmsghdr *nlh)
Linus Torvalds's avatar
Linus Torvalds committed
1126
{
1127
	int hdrlen = sizeof(struct inet_diag_req);
1128
	struct net *net = sock_net(skb->sk);
Linus Torvalds's avatar
Linus Torvalds committed
1129

1130 1131 1132
	if (nlh->nlmsg_type >= INET_DIAG_GETSOCK_MAX ||
	    nlmsg_len(nlh) < hdrlen)
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
1133

1134
	if (nlh->nlmsg_flags & NLM_F_DUMP) {
1135 1136
		if (nlmsg_attrlen(nlh, hdrlen)) {
			struct nlattr *attr;
1137
			int err;
Linus Torvalds's avatar
Linus Torvalds committed
1138

1139 1140
			attr = nlmsg_find_attr(nlh, hdrlen,
					       INET_DIAG_REQ_BYTECODE);
1141
			err = inet_diag_bc_audit(attr, skb);
1142 1143
			if (err)
				return err;
1144
		}
1145 1146 1147 1148
		{
			struct netlink_dump_control c = {
				.dump = inet_diag_dump_compat,
			};
1149
			return netlink_dump_start(net->diag_nlsk, skb, nlh, &c);
1150
		}
Linus Torvalds's avatar
Linus Torvalds committed
1151
	}
1152

1153
	return inet_diag_get_exact_compat(skb, nlh);
Linus Torvalds's avatar
Linus Torvalds committed
1154 1155
}

1156
static int inet_diag_handler_cmd(struct sk_buff *skb, struct nlmsghdr *h)
1157
{
1158
	int hdrlen = sizeof(struct inet_diag_req_v2);
1159
	struct net *net = sock_net(skb->sk);
1160 1161 1162 1163

	if (nlmsg_len(h) < hdrlen)
		return -EINVAL;

1164 1165
	if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY &&
	    h->nlmsg_flags & NLM_F_DUMP) {
1166 1167
		if (nlmsg_attrlen(h, hdrlen)) {
			struct nlattr *attr;
1168
			int err;
Eric Dumazet's avatar
Eric Dumazet committed
1169

1170 1171
			attr = nlmsg_find_attr(h, hdrlen,
					       INET_DIAG_REQ_BYTECODE);
1172
			err = inet_diag_bc_audit(attr, skb);
1173 1174
			if (err)
				return err;
1175
		}
1176 1177 1178 1179
		{
			struct netlink_dump_control c = {
				.dump = inet_diag_dump,
			};
1180
			return netlink_dump_start(net->diag_nlsk, skb, h, &c);
1181
		}
1182 1183
	}

1184
	return inet_diag_cmd_exact(h->nlmsg_type, skb, h, nlmsg_data(h));
1185 1186
}

1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
static
int inet_diag_handler_get_info(struct sk_buff *skb, struct sock *sk)
{
	const struct inet_diag_handler *handler;
	struct nlmsghdr *nlh;
	struct nlattr *attr;
	struct inet_diag_msg *r;
	void *info = NULL;
	int err = 0;

	nlh = nlmsg_put(skb, 0, 0, SOCK_DIAG_BY_FAMILY, sizeof(*r), 0);
	if (!nlh)
		return -ENOMEM;

	r = nlmsg_data(nlh);
	memset(r, 0, sizeof(*r));
	inet_diag_msg_common_fill(r, sk);
1204 1205
	if (sk->sk_type == SOCK_DGRAM || sk->sk_type == SOCK_STREAM)
		r->id.idiag_sport = inet_sk(sk)->inet_sport;
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
	r->idiag_state = sk->sk_state;

	if ((err = nla_put_u8(skb, INET_DIAG_PROTOCOL, sk->sk_protocol))) {
		nlmsg_cancel(skb, nlh);
		return err;
	}

	handler = inet_diag_lock_handler(sk->sk_protocol);
	if (IS_ERR(handler)) {
		inet_diag_unlock_handler(handler);
		nlmsg_cancel(skb, nlh);</