Commit 4d449bdc authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'ping6-SOL_IPV6'



Jakub Kicinski says:

====================
net: ping6: support setting basic SOL_IPV6 options via cmsg

Support for IPV6_HOPLIMIT, IPV6_TCLASS, IPV6_DONTFRAG on ICMPv6
sockets and associated tests. I have no immediate plans to
implement IPV6_FLOWINFO and all the extension header stuff.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents d54f16c7 a22982c3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1487,6 +1487,7 @@ static int __ip6_append_data(struct sock *sk,

	if (cork->length + length > mtu - headersize && ipc6->dontfrag &&
	    (sk->sk_protocol == IPPROTO_UDP ||
	     sk->sk_protocol == IPPROTO_ICMPV6 ||
	     sk->sk_protocol == IPPROTO_RAW)) {
		ipv6_local_rxpmtu(sk, fl6, mtu - headersize +
				sizeof(struct ipv6hdr));
+16 −5
Original line number Diff line number Diff line
@@ -101,11 +101,21 @@ static int ping_v6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
	ipc6.sockc.tsflags = sk->sk_tsflags;
	ipc6.sockc.mark = sk->sk_mark;

	err = sock_cmsg_send(sk, msg, &ipc6.sockc);
	if (err)
	if (msg->msg_controllen) {
		struct ipv6_txoptions opt = {};

		opt.tot_len = sizeof(opt);
		ipc6.opt = &opt;

		err = ip6_datagram_send_ctl(sock_net(sk), sk, msg, &fl6, &ipc6);
		if (err < 0)
			return err;

	/* TODO: use ip6_datagram_send_ctl to get options from cmsg */
		/* Changes to txoptions and flow info are not implemented, yet.
		 * Drop the options, fl6 is wiped below.
		 */
		ipc6.opt = NULL;
	}

	memset(&fl6, 0, sizeof(fl6));

@@ -140,6 +150,7 @@ static int ping_v6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
	pfh.wcheck = 0;
	pfh.family = AF_INET6;

	if (ipc6.hlimit < 0)
		ipc6.hlimit = ip6_sk_dst_hoplimit(np, &fl6, dst);

	lock_sock(sk);
+156 −0
Original line number Diff line number Diff line
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0

ksft_skip=4

NS=ns
IP6=2001:db8:1::1/64
TGT6=2001:db8:1::2
TMPF=`mktemp`

cleanup()
{
    rm -f $TMPF
    ip netns del $NS
}

trap cleanup EXIT

NSEXE="ip netns exec $NS"

tcpdump -h | grep immediate-mode >> /dev/null
if [ $? -ne 0 ]; then
    echo "SKIP - tcpdump with --immediate-mode option required"
    exit $ksft_skip
fi

# Namespaces
ip netns add $NS

$NSEXE sysctl -w net.ipv4.ping_group_range='0 2147483647' > /dev/null

# Connectivity
ip -netns $NS link add type dummy
ip -netns $NS link set dev dummy0 up
ip -netns $NS addr add $IP6 dev dummy0

# Test
BAD=0
TOTAL=0

check_result() {
    ((TOTAL++))
    if [ $1 -ne $2 ]; then
	echo "  Case $3 returned $1, expected $2"
	((BAD++))
    fi
}

# IPV6_DONTFRAG
for ovr in setsock cmsg both diff; do
    for df in 0 1; do
	for p in u i r; do
	    [ $p == "u" ] && prot=UDP
	    [ $p == "i" ] && prot=ICMP
	    [ $p == "r" ] && prot=RAW

	    [ $ovr == "setsock" ] && m="-F $df"
	    [ $ovr == "cmsg" ]    && m="-f $df"
	    [ $ovr == "both" ]    && m="-F $df -f $df"
	    [ $ovr == "diff" ]    && m="-F $((1 - df)) -f $df"

	    $NSEXE ./cmsg_sender -s -S 2000 -6 -p $p $m $TGT6 1234
	    check_result $? $df "DONTFRAG $prot $ovr"
	done
    done
done

# IPV6_TCLASS
TOS=0x10
TOS2=0x20

ip -6 -netns $NS rule add tos $TOS lookup 300
ip -6 -netns $NS route add table 300 prohibit any

for ovr in setsock cmsg both diff; do
    for p in u i r; do
	[ $p == "u" ] && prot=UDP
	[ $p == "i" ] && prot=ICMP
	[ $p == "r" ] && prot=RAW

	[ $ovr == "setsock" ] && m="-C"
	[ $ovr == "cmsg" ]    && m="-c"
	[ $ovr == "both" ]    && m="-C $((TOS2)) -c"
	[ $ovr == "diff" ]    && m="-C $((TOS )) -c"

	$NSEXE nohup tcpdump --immediate-mode -p -ni dummy0 -w $TMPF -c 4 2> /dev/null &
	BG=$!
	sleep 0.05

	$NSEXE ./cmsg_sender -6 -p $p $m $((TOS2)) $TGT6 1234
	check_result $? 0 "TCLASS $prot $ovr - pass"

	while [ -d /proc/$BG ]; do
	    $NSEXE ./cmsg_sender -6 -p u $TGT6 1234
	done

	tcpdump -r $TMPF -v 2>&1 | grep "class $TOS2" >> /dev/null
	check_result $? 0 "TCLASS $prot $ovr - packet data"
	rm $TMPF

	[ $ovr == "both" ]    && m="-C $((TOS )) -c"
	[ $ovr == "diff" ]    && m="-C $((TOS2)) -c"

	$NSEXE ./cmsg_sender -6 -p $p $m $((TOS)) -s $TGT6 1234
	check_result $? 1 "TCLASS $prot $ovr - rejection"
    done
done

# IPV6_HOPLIMIT
LIM=4

for ovr in setsock cmsg both diff; do
    for p in u i r; do
	[ $p == "u" ] && prot=UDP
	[ $p == "i" ] && prot=ICMP
	[ $p == "r" ] && prot=RAW

	[ $ovr == "setsock" ] && m="-L"
	[ $ovr == "cmsg" ]    && m="-l"
	[ $ovr == "both" ]    && m="-L $LIM -l"
	[ $ovr == "diff" ]    && m="-L $((LIM + 1)) -l"

	$NSEXE nohup tcpdump --immediate-mode -p -ni dummy0 -w $TMPF -c 4 2> /dev/null &
	BG=$!
	sleep 0.05

	$NSEXE ./cmsg_sender -6 -p $p $m $LIM $TGT6 1234
	check_result $? 0 "HOPLIMIT $prot $ovr - pass"

	while [ -d /proc/$BG ]; do
	    $NSEXE ./cmsg_sender -6 -p u $TGT6 1234
	done

	tcpdump -r $TMPF -v 2>&1 | grep "hlim $LIM[^0-9]" >> /dev/null
	check_result $? 0 "HOPLIMIT $prot $ovr - packet data"
	rm $TMPF
    done
done

# IPV6 exthdr
for p in u i r; do
    # Very basic "does it crash" test
    for h in h d r; do
	$NSEXE ./cmsg_sender -p $p -6 -H $h $TGT6 1234
	check_result $? 0 "ExtHdr $prot $ovr - pass"
    done
done

# Summary
if [ $BAD -ne 0 ]; then
    echo "FAIL - $BAD/$TOTAL cases failed"
    exit 1
else
    echo "OK"
    exit 0
fi
+147 −23
Original line number Diff line number Diff line
@@ -33,22 +33,28 @@ enum {
	ERN_CMSG_RCV,
};

struct option_cmsg_u32 {
	bool ena;
	unsigned int val;
};

struct options {
	bool silent_send;
	const char *host;
	const char *service;
	unsigned int size;
	struct {
		unsigned int mark;
		unsigned int dontfrag;
		unsigned int tclass;
		unsigned int hlimit;
	} sockopt;
	struct {
		unsigned int family;
		unsigned int type;
		unsigned int proto;
	} sock;
	struct {
		bool ena;
		unsigned int val;
	} mark;
	struct option_cmsg_u32 mark;
	struct {
		bool ena;
		unsigned int delay;
@@ -56,7 +62,14 @@ struct options {
	struct {
		bool ena;
	} ts;
	struct {
		struct option_cmsg_u32 dontfrag;
		struct option_cmsg_u32 tclass;
		struct option_cmsg_u32 hlimit;
		struct option_cmsg_u32 exthdr;
	} v6;
} opt = {
	.size = 13,
	.sock = {
		.family	= AF_UNSPEC,
		.type	= SOCK_DGRAM,
@@ -72,6 +85,7 @@ static void __attribute__((noreturn)) cs_usage(const char *bin)
	printf("Usage: %s [opts] <dst host> <dst port / service>\n", bin);
	printf("Options:\n"
	       "\t\t-s      Silent send() failures\n"
	       "\t\t-S      send() size\n"
	       "\t\t-4/-6   Force IPv4 / IPv6 only\n"
	       "\t\t-p prot Socket protocol\n"
	       "\t\t        (u = UDP (default); i = ICMP; r = RAW)\n"
@@ -80,6 +94,14 @@ static void __attribute__((noreturn)) cs_usage(const char *bin)
	       "\t\t-M val  Set SO_MARK via setsockopt\n"
	       "\t\t-d val  Set SO_TXTIME with given delay (usec)\n"
	       "\t\t-t      Enable time stamp reporting\n"
	       "\t\t-f val  Set don't fragment via cmsg\n"
	       "\t\t-F val  Set don't fragment via setsockopt\n"
	       "\t\t-c val  Set TCLASS via cmsg\n"
	       "\t\t-C val  Set TCLASS via setsockopt\n"
	       "\t\t-l val  Set HOPLIMIT via cmsg\n"
	       "\t\t-L val  Set HOPLIMIT via setsockopt\n"
	       "\t\t-H type Add an IPv6 header option\n"
	       "\t\t        (h = HOP; d = DST; r = RTDST)"
	       "");
	exit(ERN_HELP);
}
@@ -88,11 +110,14 @@ static void cs_parse_args(int argc, char *argv[])
{
	char o;

	while ((o = getopt(argc, argv, "46sp:m:M:d:t")) != -1) {
	while ((o = getopt(argc, argv, "46sS:p:m:M:d:tf:F:c:C:l:L:H:")) != -1) {
		switch (o) {
		case 's':
			opt.silent_send = true;
			break;
		case 'S':
			opt.size = atoi(optarg);
			break;
		case '4':
			opt.sock.family = AF_INET;
			break;
@@ -126,6 +151,44 @@ static void cs_parse_args(int argc, char *argv[])
		case 't':
			opt.ts.ena = true;
			break;
		case 'f':
			opt.v6.dontfrag.ena = true;
			opt.v6.dontfrag.val = atoi(optarg);
			break;
		case 'F':
			opt.sockopt.dontfrag = atoi(optarg);
			break;
		case 'c':
			opt.v6.tclass.ena = true;
			opt.v6.tclass.val = atoi(optarg);
			break;
		case 'C':
			opt.sockopt.tclass = atoi(optarg);
			break;
		case 'l':
			opt.v6.hlimit.ena = true;
			opt.v6.hlimit.val = atoi(optarg);
			break;
		case 'L':
			opt.sockopt.hlimit = atoi(optarg);
			break;
		case 'H':
			opt.v6.exthdr.ena = true;
			switch (optarg[0]) {
			case 'h':
				opt.v6.exthdr.val = IPV6_HOPOPTS;
				break;
			case 'd':
				opt.v6.exthdr.val = IPV6_DSTOPTS;
				break;
			case 'r':
				opt.v6.exthdr.val = IPV6_RTHDRDSTOPTS;
				break;
			default:
				printf("Error: hdr type: %s\n", optarg);
				break;
			}
			break;
		}
	}

@@ -136,6 +199,38 @@ static void cs_parse_args(int argc, char *argv[])
	opt.service = argv[optind + 1];
}

static void memrnd(void *s, size_t n)
{
	int *dword = s;
	char *byte;

	for (; n >= 4; n -= 4)
		*dword++ = rand();
	byte = (void *)dword;
	while (n--)
		*byte++ = rand();
}

static void
ca_write_cmsg_u32(char *cbuf, size_t cbuf_sz, size_t *cmsg_len,
		  int level, int optname, struct option_cmsg_u32 *uopt)
{
	struct cmsghdr *cmsg;

	if (!uopt->ena)
		return;

	cmsg = (struct cmsghdr *)(cbuf + *cmsg_len);
	*cmsg_len += CMSG_SPACE(sizeof(__u32));
	if (cbuf_sz < *cmsg_len)
		error(ERN_CMSG_WR, EFAULT, "cmsg buffer too small");

	cmsg->cmsg_level = level;
	cmsg->cmsg_type = optname;
	cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));
	*(__u32 *)CMSG_DATA(cmsg) = uopt->val;
}

static void
cs_write_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz)
{
@@ -145,17 +240,15 @@ cs_write_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz)
	msg->msg_control = cbuf;
	cmsg_len = 0;

	if (opt.mark.ena) {
		cmsg = (struct cmsghdr *)(cbuf + cmsg_len);
		cmsg_len += CMSG_SPACE(sizeof(__u32));
		if (cbuf_sz < cmsg_len)
			error(ERN_CMSG_WR, EFAULT, "cmsg buffer too small");
	ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
			  SOL_SOCKET, SO_MARK, &opt.mark);
	ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
			  SOL_IPV6, IPV6_DONTFRAG, &opt.v6.dontfrag);
	ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
			  SOL_IPV6, IPV6_TCLASS, &opt.v6.tclass);
	ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
			  SOL_IPV6, IPV6_HOPLIMIT, &opt.v6.hlimit);

		cmsg->cmsg_level = SOL_SOCKET;
		cmsg->cmsg_type = SO_MARK;
		cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));
		*(__u32 *)CMSG_DATA(cmsg) = opt.mark.val;
	}
	if (opt.txtime.ena) {
		struct sock_txtime so_txtime = {
			.clockid = CLOCK_MONOTONIC,
@@ -199,6 +292,17 @@ cs_write_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz)
		*(__u32 *)CMSG_DATA(cmsg) = SOF_TIMESTAMPING_TX_SCHED |
					    SOF_TIMESTAMPING_TX_SOFTWARE;
	}
	if (opt.v6.exthdr.ena) {
		cmsg = (struct cmsghdr *)(cbuf + cmsg_len);
		cmsg_len += CMSG_SPACE(8);
		if (cbuf_sz < cmsg_len)
			error(ERN_CMSG_WR, EFAULT, "cmsg buffer too small");

		cmsg->cmsg_level = SOL_IPV6;
		cmsg->cmsg_type = opt.v6.exthdr.val;
		cmsg->cmsg_len = CMSG_LEN(8);
		*(__u64 *)CMSG_DATA(cmsg) = 0;
	}

	if (cmsg_len)
		msg->msg_controllen = cmsg_len;
@@ -286,18 +390,41 @@ cs_read_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz)
	}
}

static void ca_set_sockopts(int fd)
{
	if (opt.sockopt.mark &&
	    setsockopt(fd, SOL_SOCKET, SO_MARK,
		       &opt.sockopt.mark, sizeof(opt.sockopt.mark)))
		error(ERN_SOCKOPT, errno, "setsockopt SO_MARK");
	if (opt.sockopt.dontfrag &&
	    setsockopt(fd, SOL_IPV6, IPV6_DONTFRAG,
		       &opt.sockopt.dontfrag, sizeof(opt.sockopt.dontfrag)))
		error(ERN_SOCKOPT, errno, "setsockopt IPV6_DONTFRAG");
	if (opt.sockopt.tclass &&
	    setsockopt(fd, SOL_IPV6, IPV6_TCLASS,
		       &opt.sockopt.tclass, sizeof(opt.sockopt.tclass)))
		error(ERN_SOCKOPT, errno, "setsockopt IPV6_TCLASS");
	if (opt.sockopt.hlimit &&
	    setsockopt(fd, SOL_IPV6, IPV6_UNICAST_HOPS,
		       &opt.sockopt.hlimit, sizeof(opt.sockopt.hlimit)))
		error(ERN_SOCKOPT, errno, "setsockopt IPV6_HOPLIMIT");
}

int main(int argc, char *argv[])
{
	char buf[] = "blablablabla";
	struct addrinfo hints, *ai;
	struct iovec iov[1];
	struct msghdr msg;
	char cbuf[1024];
	char *buf;
	int err;
	int fd;

	cs_parse_args(argc, argv);

	buf = malloc(opt.size);
	memrnd(buf, opt.size);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = opt.sock.family;

@@ -326,17 +453,14 @@ int main(int argc, char *argv[])
		buf[0] = ICMPV6_ECHO_REQUEST;
		buf[1] = 0;
	} else if (opt.sock.type == SOCK_RAW) {
		struct udphdr hdr = { 1, 2, htons(sizeof(buf)), 0 };
		struct udphdr hdr = { 1, 2, htons(opt.size), 0 };
		struct sockaddr_in6 *sin6 = (void *)ai->ai_addr;;

		memcpy(buf, &hdr, sizeof(hdr));
		sin6->sin6_port = htons(opt.sock.proto);
	}

	if (opt.sockopt.mark &&
	    setsockopt(fd, SOL_SOCKET, SO_MARK,
		       &opt.sockopt.mark, sizeof(opt.sockopt.mark)))
		error(ERN_SOCKOPT, errno, "setsockopt SO_MARK");
	ca_set_sockopts(fd);

	if (clock_gettime(CLOCK_REALTIME, &time_start_real))
		error(ERN_GETTIME, errno, "gettime REALTIME");
@@ -344,7 +468,7 @@ int main(int argc, char *argv[])
		error(ERN_GETTIME, errno, "gettime MONOTONIC");

	iov[0].iov_base = buf;
	iov[0].iov_len = sizeof(buf);
	iov[0].iov_len = opt.size;

	memset(&msg, 0, sizeof(msg));
	msg.msg_name = ai->ai_addr;
@@ -360,7 +484,7 @@ int main(int argc, char *argv[])
			fprintf(stderr, "send failed: %s\n", strerror(errno));
		err = ERN_SEND;
		goto err_out;
	} else if (err != sizeof(buf)) {
	} else if (err != (int)opt.size) {
		fprintf(stderr, "short send\n");
		err = ERN_SEND_SHORT;
		goto err_out;