/*
 * Copyright (c) 2016 Fabien Siron <fabien.siron@epita.fr>
 * Copyright (c) 2017 JingPiao Chen <chenjingpiao@gmail.com>
 * Copyright (c) 2017 The strace developers.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "tests.h"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include "test_netlink.h"
#include <linux/if_ether.h>
#include <linux/inet_diag.h>
#include <linux/netlink_diag.h>
#include <linux/packet_diag.h>
#ifdef AF_SMC
# include <linux/smc_diag.h>
#endif
#include <linux/sock_diag.h>
#include <linux/unix_diag.h>

#define SMC_ACTIVE 1

#define TEST_SOCK_DIAG(fd_, nlh0_,					\
		       family_, type_, flags_,				\
		       obj_, print_family_, ...)			\
									\
	do {								\
		/* family only */					\
		uint8_t family = (family_);				\
		TEST_NETLINK_((fd_), (nlh0_),				\
			      type_, #type_,				\
			      flags_, #flags_,				\
			      sizeof(family), &family, sizeof(family),	\
			      printf("{family=%s}", #family_));		\
									\
		/* family and string */					\
		char buf[sizeof(family) + 4];				\
		memcpy(buf, &family, sizeof(family));			\
		memcpy(buf + sizeof(family), "1234", 4);		\
		TEST_NETLINK_((fd_), (nlh0_),				\
			      type_, #type_,				\
			      flags_, #flags_,				\
			      sizeof(buf), buf, sizeof(buf),		\
			      (print_family_);				\
			      printf(", ...}"));			\
									\
		/* sizeof(obj_) */					\
		TEST_NETLINK_((fd_), (nlh0_),				\
			      type_, #type_,				\
			      flags_, #flags_,				\
			      sizeof(obj_), &(obj_), sizeof(obj_),	\
			      (print_family_);				\
			      __VA_ARGS__);				\
									\
		/* short read of sizeof(obj_) */			\
		TEST_NETLINK_((fd_), (nlh0_),				\
			      type_, #type_,				\
			      flags_, #flags_,				\
			      sizeof(obj_), &(obj_), sizeof(obj_) - 1,	\
			      (print_family_);				\
			      printf(", %p}",				\
				     NLMSG_DATA(TEST_NETLINK_nlh) + 1));\
	} while (0)

static void
test_nlmsg_type(const int fd)
{
	long rc;
	struct nlmsghdr nlh = {
		.nlmsg_len = sizeof(nlh),
		.nlmsg_type = SOCK_DIAG_BY_FAMILY,
		.nlmsg_flags = NLM_F_REQUEST,
	};

	rc = sendto(fd, &nlh, sizeof(nlh), MSG_DONTWAIT, NULL, 0);
	printf("sendto(%d, {len=%u, type=SOCK_DIAG_BY_FAMILY"
	       ", flags=NLM_F_REQUEST, seq=0, pid=0}"
	       ", %u, MSG_DONTWAIT, NULL, 0) = %s\n",
	       fd, nlh.nlmsg_len, (unsigned) sizeof(nlh), sprintrc(rc));
}

static void
test_nlmsg_flags(const int fd)
{
	long rc;
	struct nlmsghdr nlh = {
		.nlmsg_len = sizeof(nlh),
		.nlmsg_type = SOCK_DIAG_BY_FAMILY,
		.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
	};

	rc = sendto(fd, &nlh, sizeof(nlh), MSG_DONTWAIT, NULL, 0);
	printf("sendto(%d, {len=%u, type=SOCK_DIAG_BY_FAMILY"
	       ", flags=NLM_F_REQUEST|NLM_F_DUMP, seq=0, pid=0}"
	       ", %u, MSG_DONTWAIT, NULL, 0) = %s\n",
	       fd, nlh.nlmsg_len, (unsigned) sizeof(nlh), sprintrc(rc));
}

static void
test_odd_family_req(const int fd)
{
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);

	/* unspecified family only */
	uint8_t family = 0;
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY,
		     NLM_F_REQUEST,
		     sizeof(family), &family, sizeof(family),
		     printf("{family=AF_UNSPEC}"));

	/* unknown family only */
	family = 0xff;
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY,
		     NLM_F_REQUEST,
		     sizeof(family), &family, sizeof(family),
		     printf("{family=%#x /* AF_??? */}", family));

	/* short read of family */
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY,
		     NLM_F_REQUEST,
		     sizeof(family), &family, sizeof(family) - 1,
		     printf("%p", NLMSG_DATA(TEST_NETLINK_nlh)));

	/* unspecified family and string */
	char buf[sizeof(family) + 4];
	family = 0;
	memcpy(buf, &family, sizeof(family));
	memcpy(buf + sizeof(family), "1234", 4);
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY,
		     NLM_F_REQUEST,
		     sizeof(buf), buf, sizeof(buf),
		     printf("{family=AF_UNSPEC, \"\\x31\\x32\\x33\\x34\"}"));

	/* unknown family and string */
	family = 0xfd;
	memcpy(buf, &family, sizeof(family));
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY,
		     NLM_F_REQUEST,
		     sizeof(buf), buf, sizeof(buf),
		     printf("{family=%#x /* AF_??? */"
			    ", \"\\x31\\x32\\x33\\x34\"}", family));
}

static void
test_odd_family_msg(const int fd)
{
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);

	/* unspecified family only */
	uint8_t family = 0;
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY, NLM_F_DUMP,
		     sizeof(family), &family, sizeof(family),
		     printf("{family=AF_UNSPEC}"));

	/* unknown family only */
	family = 0xff;
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY, NLM_F_DUMP,
		     sizeof(family), &family, sizeof(family),
		     printf("{family=%#x /* AF_??? */}", family));

	/* short read of family */
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY, NLM_F_DUMP,
		     sizeof(family), &family, sizeof(family) - 1,
		     printf("%p", NLMSG_DATA(TEST_NETLINK_nlh)));

	/* unspecified family and string */
	char buf[sizeof(family) + 4];
	family = 0;
	memcpy(buf, &family, sizeof(family));
	memcpy(buf + sizeof(family), "1234", 4);
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY, NLM_F_DUMP,
		     sizeof(buf), buf, sizeof(buf),
		     printf("{family=AF_UNSPEC, \"\\x31\\x32\\x33\\x34\"}"));

	/* unknown family and string */
	family = 0xfd;
	memcpy(buf, &family, sizeof(family));
	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY, NLM_F_DUMP,
		     sizeof(buf), buf, sizeof(buf),
		     printf("{family=%#x /* AF_??? */"
			    ", \"\\x31\\x32\\x33\\x34\"}", family));
}

static void
test_unix_diag_req(const int fd)
{
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	static const struct unix_diag_req req = {
		.sdiag_family = AF_UNIX,
		.sdiag_protocol = 253,
		.udiag_states = 1 << TCP_ESTABLISHED | 1 << TCP_LISTEN,
		.udiag_ino = 0xfacefeed,
		.udiag_show = UDIAG_SHOW_NAME,
		.udiag_cookie = { 0xdeadbeef, 0xbadc0ded }
	};
	TEST_SOCK_DIAG(fd, nlh0, AF_UNIX,
		       SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST, req,
		       printf("{sdiag_family=AF_UNIX"),
		       PRINT_FIELD_U(", ", req, sdiag_protocol);
		       printf(", udiag_states=1<<TCP_ESTABLISHED|1<<TCP_LISTEN");
		       PRINT_FIELD_U(", ", req, udiag_ino);
		       printf(", udiag_show=UDIAG_SHOW_NAME");
		       PRINT_FIELD_COOKIE(", ", req, udiag_cookie);
		       printf("}"));
}

static void
test_unix_diag_msg(const int fd)
{
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	static const struct unix_diag_msg msg = {
		.udiag_family = AF_UNIX,
		.udiag_type = SOCK_STREAM,
		.udiag_state = TCP_FIN_WAIT1,
		.udiag_ino = 0xfacefeed,
		.udiag_cookie = { 0xdeadbeef, 0xbadc0ded }
	};
	TEST_SOCK_DIAG(fd, nlh0, AF_UNIX,
		       SOCK_DIAG_BY_FAMILY, NLM_F_DUMP, msg,
		       printf("{udiag_family=AF_UNIX"),
		       printf(", udiag_type=SOCK_STREAM"
			      ", udiag_state=TCP_FIN_WAIT1");
		       PRINT_FIELD_U(", ", msg, udiag_ino);
		       PRINT_FIELD_COOKIE(", ", msg, udiag_cookie);
		       printf("}"));
}

static void
test_netlink_diag_req(const int fd)
{
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	struct netlink_diag_req req = {
		.sdiag_family = AF_NETLINK,
		.sdiag_protocol = NDIAG_PROTO_ALL,
		.ndiag_ino = 0xfacefeed,
		.ndiag_show = NDIAG_SHOW_MEMINFO,
		.ndiag_cookie = { 0xdeadbeef, 0xbadc0ded }
	};
	TEST_SOCK_DIAG(fd, nlh0, AF_NETLINK,
		       SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST, req,
		       printf("{sdiag_family=AF_NETLINK"),
		       printf(", sdiag_protocol=NDIAG_PROTO_ALL");
		       PRINT_FIELD_U(", ", req, ndiag_ino);
		       printf(", ndiag_show=NDIAG_SHOW_MEMINFO");
		       PRINT_FIELD_COOKIE(", ", req, ndiag_cookie);
		       printf("}"));

	req.sdiag_protocol = NETLINK_ROUTE;
	req.ndiag_show = NDIAG_SHOW_GROUPS;
	TEST_SOCK_DIAG(fd, nlh0, AF_NETLINK,
		       SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST, req,
		       printf("{sdiag_family=AF_NETLINK"),
		       printf(", sdiag_protocol=NETLINK_ROUTE");
		       PRINT_FIELD_U(", ", req, ndiag_ino);
		       printf(", ndiag_show=NDIAG_SHOW_GROUPS");
		       PRINT_FIELD_COOKIE(", ", req, ndiag_cookie);
		       printf("}"));
}

static void
test_netlink_diag_msg(const int fd)
{
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	static const struct netlink_diag_msg msg = {
		.ndiag_family = AF_NETLINK,
		.ndiag_type = SOCK_RAW,
		.ndiag_protocol = NETLINK_ROUTE,
		.ndiag_state = NETLINK_CONNECTED,
		.ndiag_portid = 0xbadc0ded,
		.ndiag_dst_portid = 0xdeadbeef,
		.ndiag_dst_group = 0xfacefeed,
		.ndiag_ino = 0xdaeefacd,
		.ndiag_cookie = { 0xbadc0ded, 0xdeadbeef }
	};
	TEST_SOCK_DIAG(fd, nlh0, AF_NETLINK,
		       SOCK_DIAG_BY_FAMILY, NLM_F_DUMP, msg,
		       printf("{ndiag_family=AF_NETLINK"),
		       printf(", ndiag_type=SOCK_RAW"
			      ", ndiag_protocol=NETLINK_ROUTE"
			      ", ndiag_state=NETLINK_CONNECTED");
		       PRINT_FIELD_U(", ", msg, ndiag_portid);
		       PRINT_FIELD_U(", ", msg, ndiag_dst_portid);
		       PRINT_FIELD_U(", ", msg, ndiag_dst_group);
		       PRINT_FIELD_U(", ", msg, ndiag_ino);
		       PRINT_FIELD_COOKIE(", ", msg, ndiag_cookie);
		       printf("}"));
}

static void
test_packet_diag_req(const int fd)
{
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	static const struct packet_diag_req req = {
		.sdiag_family = AF_PACKET,
		.sdiag_protocol = ETH_P_LOOP,
		.pdiag_ino = 0xfacefeed,
		.pdiag_show = PACKET_SHOW_INFO,
		.pdiag_cookie = { 0xdeadbeef, 0xbadc0ded }
	};
	TEST_SOCK_DIAG(fd, nlh0, AF_PACKET,
		       SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST, req,
		       printf("{sdiag_family=AF_PACKET"),
		       printf(", sdiag_protocol=ETH_P_LOOP");
		       PRINT_FIELD_U(", ", req, pdiag_ino);
		       printf(", pdiag_show=PACKET_SHOW_INFO");
		       PRINT_FIELD_COOKIE(", ", req, pdiag_cookie);
		       printf("}"));
}

static void
test_packet_diag_msg(const int fd)
{
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	static const struct packet_diag_msg msg = {
		.pdiag_family = AF_PACKET,
		.pdiag_type = SOCK_STREAM,
		.pdiag_num = 0xbadc,
		.pdiag_ino = 0xfacefeed,
		.pdiag_cookie = { 0xdeadbeef, 0xbadc0ded }
	};
	TEST_SOCK_DIAG(fd, nlh0, AF_PACKET,
		       SOCK_DIAG_BY_FAMILY, NLM_F_DUMP, msg,
		       printf("{pdiag_family=AF_PACKET"),
		       printf(", pdiag_type=SOCK_STREAM");
		       PRINT_FIELD_U(", ", msg, pdiag_num);
		       PRINT_FIELD_U(", ", msg, pdiag_ino);
		       PRINT_FIELD_COOKIE(", ", msg, pdiag_cookie);
		       printf("}"));
}

static void
test_inet_diag_sockid(const int fd)
{
	const char address[] = "12.34.56.78";
	const char address6[] = "12:34:56:78:90:ab:cd:ef";
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	struct inet_diag_req_v2 req = {
		.sdiag_family = AF_INET,
		.idiag_ext = 1 << (INET_DIAG_CONG - 1),
		.sdiag_protocol = IPPROTO_TCP,
		.idiag_states = 1 << TCP_CLOSE,
		.id = {
			.idiag_sport = 0xfacd,
			.idiag_dport = 0xdead,
			.idiag_if = ifindex_lo(),
			.idiag_cookie = { 0xdeadbeef, 0xbadc0ded }
		},
	};

	if (!inet_pton(AF_INET, address, &req.id.idiag_src) ||
	    !inet_pton(AF_INET, address, &req.id.idiag_dst))
		perror_msg_and_skip("inet_pton");

	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST,
		     sizeof(req), &req, sizeof(req),
		     printf("{sdiag_family=AF_INET"),
		     printf(", sdiag_protocol=IPPROTO_TCP"
			    ", idiag_ext=1<<(INET_DIAG_CONG-1)"
			    ", idiag_states=1<<TCP_CLOSE"
			    ", id={idiag_sport=htons(%u)"
			    ", idiag_dport=htons(%u)"
			    ", idiag_src=inet_addr(\"%s\")"
			    ", idiag_dst=inet_addr(\"%s\")",
			    ntohs(req.id.idiag_sport),
			    ntohs(req.id.idiag_dport),
			    address, address);
		     printf(", idiag_if=" IFINDEX_LO_STR);
		     PRINT_FIELD_COOKIE(", ", req.id, idiag_cookie);
		     printf("}}"));

	req.sdiag_family = AF_INET6;
	if (!inet_pton(AF_INET6, address6, &req.id.idiag_src) ||
	    !inet_pton(AF_INET6, address6, &req.id.idiag_dst))
		perror_msg_and_skip("inet_pton");

	TEST_NETLINK(fd, nlh0,
		     SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST,
		     sizeof(req), &req, sizeof(req),
		     printf("{sdiag_family=AF_INET6"),
		     printf(", sdiag_protocol=IPPROTO_TCP"
			    ", idiag_ext=1<<(INET_DIAG_CONG-1)"
			    ", idiag_states=1<<TCP_CLOSE"
			    ", id={idiag_sport=htons(%u)"
			    ", idiag_dport=htons(%u)"
			    ", inet_pton(AF_INET6, \"%s\", &idiag_src)"
			    ", inet_pton(AF_INET6, \"%s\", &idiag_dst)",
			    ntohs(req.id.idiag_sport),
			    ntohs(req.id.idiag_dport),
			    address6, address6);
		     printf(", idiag_if=" IFINDEX_LO_STR);
		     PRINT_FIELD_COOKIE(", ", req.id, idiag_cookie);
		     printf("}}"));
}

static void
test_inet_diag_req(const int fd)
{
	const char address[] = "12.34.56.78";
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	struct inet_diag_req req = {
		.idiag_family = AF_INET,
		.idiag_src_len = 0xde,
		.idiag_dst_len = 0xba,
		.idiag_ext = 1 << (INET_DIAG_TOS - 1),
		.id = {
			.idiag_sport = 0xdead,
			.idiag_dport = 0xadcd,
			.idiag_if = ifindex_lo(),
			.idiag_cookie = { 0xdeadbeef, 0xbadc0ded }
		},
		.idiag_states = 1 << TCP_LAST_ACK,
		.idiag_dbs = 0xfacefeed,
	};

	if (!inet_pton(AF_INET, address, &req.id.idiag_src) ||
	    !inet_pton(AF_INET, address, &req.id.idiag_dst))
		perror_msg_and_skip("inet_pton");

	TEST_SOCK_DIAG(fd, nlh0, AF_INET,
		       TCPDIAG_GETSOCK, NLM_F_REQUEST, req,
		       printf("{idiag_family=AF_INET"),
		       PRINT_FIELD_U(", ", req, idiag_src_len);
		       PRINT_FIELD_U(", ", req, idiag_dst_len);
		       printf(", idiag_ext=1<<(INET_DIAG_TOS-1)");
		       printf(", id={idiag_sport=htons(%u)"
			      ", idiag_dport=htons(%u)"
			      ", idiag_src=inet_addr(\"%s\")"
			      ", idiag_dst=inet_addr(\"%s\")",
			      ntohs(req.id.idiag_sport),
			      ntohs(req.id.idiag_dport),
			      address, address);
		       printf(", idiag_if=" IFINDEX_LO_STR);
		       PRINT_FIELD_COOKIE(", ", req.id, idiag_cookie);
		       printf("}, idiag_states=1<<TCP_LAST_ACK");
		       PRINT_FIELD_U(", ", req, idiag_dbs);
		       printf("}"));
}

static void
test_inet_diag_req_v2(const int fd)
{
	const char address[] = "87.65.43.21";
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	struct inet_diag_req_v2 req = {
		.sdiag_family = AF_INET,
		.idiag_ext = 1 << (INET_DIAG_CONG - 1),
		.sdiag_protocol = IPPROTO_TCP,
		.idiag_states = 1 << TCP_CLOSE,
		.id = {
			.idiag_sport = 0xfacd,
			.idiag_dport = 0xdead,
			.idiag_if = ifindex_lo(),
			.idiag_cookie = { 0xdeadbeef, 0xbadc0ded }
		},
	};

	if (!inet_pton(AF_INET, address, &req.id.idiag_src) ||
	    !inet_pton(AF_INET, address, &req.id.idiag_dst))
		perror_msg_and_skip("inet_pton");

	TEST_SOCK_DIAG(fd, nlh0, AF_INET,
		       SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST, req,
		       printf("{sdiag_family=AF_INET"),
		       printf(", sdiag_protocol=IPPROTO_TCP"
			      ", idiag_ext=1<<(INET_DIAG_CONG-1)"
			      ", idiag_states=1<<TCP_CLOSE"
			      ", id={idiag_sport=htons(%u)"
			      ", idiag_dport=htons(%u)"
			      ", idiag_src=inet_addr(\"%s\")"
			      ", idiag_dst=inet_addr(\"%s\")",
			      ntohs(req.id.idiag_sport),
			      ntohs(req.id.idiag_dport),
			      address, address);
		       printf(", idiag_if=" IFINDEX_LO_STR);
		       PRINT_FIELD_COOKIE(", ", req.id, idiag_cookie);
		       printf("}}"));
}

static void
test_inet_diag_msg(const int fd)
{
	const char address[] = "11.22.33.44";
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	struct inet_diag_msg msg = {
		.idiag_family = AF_INET,
		.idiag_state = TCP_LISTEN,
		.idiag_timer = 0xfa,
		.idiag_retrans = 0xde,
		.id = {
			.idiag_sport = 0xfacf,
			.idiag_dport = 0xdead,
			.idiag_if = ifindex_lo(),
			.idiag_cookie = { 0xdeadbeef, 0xbadc0ded }
		},
		.idiag_expires = 0xfacefeed,
		.idiag_rqueue = 0xdeadbeef,
		.idiag_wqueue = 0xadcdfafc,
		.idiag_uid = 0xdecefaeb,
		.idiag_inode = 0xbadc0ded,
	};

	if (!inet_pton(AF_INET, address, &msg.id.idiag_src) ||
	    !inet_pton(AF_INET, address, &msg.id.idiag_dst))
		perror_msg_and_skip("inet_pton");

	TEST_SOCK_DIAG(fd, nlh0, AF_INET,
		       SOCK_DIAG_BY_FAMILY, NLM_F_DUMP, msg,
		       printf("{idiag_family=AF_INET"),
		       printf(", idiag_state=TCP_LISTEN");
		       PRINT_FIELD_U(", ", msg, idiag_timer);
		       PRINT_FIELD_U(", ", msg, idiag_retrans);
		       printf(", id={idiag_sport=htons(%u)"
			      ", idiag_dport=htons(%u)"
			      ", idiag_src=inet_addr(\"%s\")"
			      ", idiag_dst=inet_addr(\"%s\")",
			      ntohs(msg.id.idiag_sport),
			      ntohs(msg.id.idiag_dport),
			      address, address);
		       printf(", idiag_if=" IFINDEX_LO_STR);
		       PRINT_FIELD_COOKIE(", ", msg.id, idiag_cookie);
		       PRINT_FIELD_U("}, ", msg, idiag_expires);
		       PRINT_FIELD_U(", ", msg, idiag_rqueue);
		       PRINT_FIELD_U(", ", msg, idiag_wqueue);
		       PRINT_FIELD_U(", ", msg, idiag_uid);
		       PRINT_FIELD_U(", ", msg, idiag_inode);
		       printf("}"));
}

#ifdef AF_SMC
static void
test_smc_diag_req(const int fd)
{
	const char address[] = "43.21.56.78";
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	struct smc_diag_req req = {
		.diag_family = AF_SMC,
		.diag_ext = 1 << (SMC_DIAG_CONNINFO - 1),
		.id = {
			.idiag_sport = 0xdead,
			.idiag_dport = 0xadcd,
			.idiag_if = ifindex_lo(),
			.idiag_cookie = { 0xdeadbeef, 0xbadc0ded },
		},
	};

	if (!inet_pton(AF_INET, address, &req.id.idiag_src) ||
	    !inet_pton(AF_INET, address, &req.id.idiag_dst))
		perror_msg_and_skip("inet_pton");

	TEST_SOCK_DIAG(fd, nlh0, AF_SMC,
		       SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST, req,
		       printf("{diag_family=AF_SMC"),
		       printf(", diag_ext=1<<(SMC_DIAG_CONNINFO-1)");
		       printf(", id={idiag_sport=htons(%u)"
			      ", idiag_dport=htons(%u)"
			      ", idiag_src=inet_addr(\"%s\")"
			      ", idiag_dst=inet_addr(\"%s\")",
			      ntohs(req.id.idiag_sport),
			      ntohs(req.id.idiag_dport),
			      address, address);
		       printf(", idiag_if=" IFINDEX_LO_STR);
		       PRINT_FIELD_COOKIE(", ", req.id, idiag_cookie);
		       printf("}}"));
}

static void
test_smc_diag_msg(const int fd)
{
	const char address[] = "34.87.12.90";
	void *const nlh0 = tail_alloc(NLMSG_HDRLEN);
	struct smc_diag_msg msg = {
		.diag_family = AF_SMC,
		.diag_state = SMC_ACTIVE,
		.diag_fallback = 0xde,
		.diag_shutdown = 0xba,
		.id = {
			.idiag_sport = 0xdead,
			.idiag_dport = 0xadcd,
			.idiag_if = ifindex_lo(),
			.idiag_cookie = { 0xdeadbeef, 0xbadc0ded },
		},
		.diag_uid = 0xadcdfafc,
		.diag_inode = 0xbadc0ded,
	};

	if (!inet_pton(AF_INET, address, &msg.id.idiag_src) ||
	    !inet_pton(AF_INET, address, &msg.id.idiag_dst))
		perror_msg_and_skip("inet_pton");

	TEST_SOCK_DIAG(fd, nlh0, AF_SMC,
		       SOCK_DIAG_BY_FAMILY, NLM_F_DUMP, msg,
		       printf("{diag_family=AF_SMC"),
		       printf(", diag_state=SMC_ACTIVE");
		       PRINT_FIELD_U(", ", msg, diag_fallback);
		       PRINT_FIELD_U(", ", msg, diag_shutdown);
		       printf(", id={idiag_sport=htons(%u)"
			      ", idiag_dport=htons(%u)"
			      ", idiag_src=inet_addr(\"%s\")"
			      ", idiag_dst=inet_addr(\"%s\")",
			      ntohs(msg.id.idiag_sport),
			      ntohs(msg.id.idiag_dport),
			      address, address);
		       printf(", idiag_if=" IFINDEX_LO_STR);
		       PRINT_FIELD_COOKIE(", ", msg.id, idiag_cookie);
		       PRINT_FIELD_U("}, ", msg, diag_uid);
		       PRINT_FIELD_U(", ", msg, diag_inode);
		       printf("}"));
}
#endif

int
main(void)
{
	skip_if_unavailable("/proc/self/fd/");

	int fd = create_nl_socket(NETLINK_SOCK_DIAG);

	test_nlmsg_type(fd);
	test_nlmsg_flags(fd);
	test_odd_family_req(fd);
	test_odd_family_msg(fd);
	test_unix_diag_req(fd);
	test_unix_diag_msg(fd);
	test_netlink_diag_req(fd);
	test_netlink_diag_msg(fd);
	test_packet_diag_req(fd);
	test_packet_diag_msg(fd);
	test_inet_diag_sockid(fd);
	test_inet_diag_req(fd);
	test_inet_diag_req_v2(fd);
	test_inet_diag_msg(fd);
#ifdef AF_SMC
	test_smc_diag_req(fd);
	test_smc_diag_msg(fd);
#endif

	printf("+++ exited with 0 +++\n");

	return 0;
}