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

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
...@@ -1487,6 +1487,7 @@ static int __ip6_append_data(struct sock *sk, ...@@ -1487,6 +1487,7 @@ static int __ip6_append_data(struct sock *sk,
if (cork->length + length > mtu - headersize && ipc6->dontfrag && if (cork->length + length > mtu - headersize && ipc6->dontfrag &&
(sk->sk_protocol == IPPROTO_UDP || (sk->sk_protocol == IPPROTO_UDP ||
sk->sk_protocol == IPPROTO_ICMPV6 ||
sk->sk_protocol == IPPROTO_RAW)) { sk->sk_protocol == IPPROTO_RAW)) {
ipv6_local_rxpmtu(sk, fl6, mtu - headersize + ipv6_local_rxpmtu(sk, fl6, mtu - headersize +
sizeof(struct ipv6hdr)); sizeof(struct ipv6hdr));
......
...@@ -101,11 +101,21 @@ static int ping_v6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) ...@@ -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.tsflags = sk->sk_tsflags;
ipc6.sockc.mark = sk->sk_mark; ipc6.sockc.mark = sk->sk_mark;
err = sock_cmsg_send(sk, msg, &ipc6.sockc); if (msg->msg_controllen) {
if (err) struct ipv6_txoptions opt = {};
return err;
opt.tot_len = sizeof(opt);
ipc6.opt = &opt;
/* TODO: use ip6_datagram_send_ctl to get options from cmsg */ err = ip6_datagram_send_ctl(sock_net(sk), sk, msg, &fl6, &ipc6);
if (err < 0)
return err;
/* 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)); memset(&fl6, 0, sizeof(fl6));
...@@ -140,7 +150,8 @@ static int ping_v6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) ...@@ -140,7 +150,8 @@ static int ping_v6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
pfh.wcheck = 0; pfh.wcheck = 0;
pfh.family = AF_INET6; pfh.family = AF_INET6;
ipc6.hlimit = ip6_sk_dst_hoplimit(np, &fl6, dst); if (ipc6.hlimit < 0)
ipc6.hlimit = ip6_sk_dst_hoplimit(np, &fl6, dst);
lock_sock(sk); lock_sock(sk);
err = ip6_append_data(sk, ping_getfrag, &pfh, len, err = ip6_append_data(sk, ping_getfrag, &pfh, len,
......
#!/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
...@@ -33,22 +33,28 @@ enum { ...@@ -33,22 +33,28 @@ enum {
ERN_CMSG_RCV, ERN_CMSG_RCV,
}; };
struct option_cmsg_u32 {
bool ena;
unsigned int val;
};
struct options { struct options {
bool silent_send; bool silent_send;
const char *host; const char *host;
const char *service; const char *service;
unsigned int size;
struct { struct {
unsigned int mark; unsigned int mark;
unsigned int dontfrag;
unsigned int tclass;
unsigned int hlimit;
} sockopt; } sockopt;
struct { struct {
unsigned int family; unsigned int family;
unsigned int type; unsigned int type;
unsigned int proto; unsigned int proto;
} sock; } sock;
struct { struct option_cmsg_u32 mark;
bool ena;
unsigned int val;
} mark;
struct { struct {
bool ena; bool ena;
unsigned int delay; unsigned int delay;
...@@ -56,7 +62,14 @@ struct options { ...@@ -56,7 +62,14 @@ struct options {
struct { struct {
bool ena; bool ena;
} ts; } ts;
struct {
struct option_cmsg_u32 dontfrag;
struct option_cmsg_u32 tclass;
struct option_cmsg_u32 hlimit;
struct option_cmsg_u32 exthdr;
} v6;
} opt = { } opt = {
.size = 13,
.sock = { .sock = {
.family = AF_UNSPEC, .family = AF_UNSPEC,
.type = SOCK_DGRAM, .type = SOCK_DGRAM,
...@@ -72,6 +85,7 @@ static void __attribute__((noreturn)) cs_usage(const char *bin) ...@@ -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("Usage: %s [opts] <dst host> <dst port / service>\n", bin);
printf("Options:\n" printf("Options:\n"
"\t\t-s Silent send() failures\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-4/-6 Force IPv4 / IPv6 only\n"
"\t\t-p prot Socket protocol\n" "\t\t-p prot Socket protocol\n"
"\t\t (u = UDP (default); i = ICMP; r = RAW)\n" "\t\t (u = UDP (default); i = ICMP; r = RAW)\n"
...@@ -80,6 +94,14 @@ static void __attribute__((noreturn)) cs_usage(const char *bin) ...@@ -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-M val Set SO_MARK via setsockopt\n"
"\t\t-d val Set SO_TXTIME with given delay (usec)\n" "\t\t-d val Set SO_TXTIME with given delay (usec)\n"
"\t\t-t Enable time stamp reporting\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); exit(ERN_HELP);
} }
...@@ -88,11 +110,14 @@ static void cs_parse_args(int argc, char *argv[]) ...@@ -88,11 +110,14 @@ static void cs_parse_args(int argc, char *argv[])
{ {
char o; 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) { switch (o) {
case 's': case 's':
opt.silent_send = true; opt.silent_send = true;
break; break;
case 'S':
opt.size = atoi(optarg);
break;
case '4': case '4':
opt.sock.family = AF_INET; opt.sock.family = AF_INET;
break; break;
...@@ -126,6 +151,44 @@ static void cs_parse_args(int argc, char *argv[]) ...@@ -126,6 +151,44 @@ static void cs_parse_args(int argc, char *argv[])
case 't': case 't':
opt.ts.ena = true; opt.ts.ena = true;
break; 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[]) ...@@ -136,6 +199,38 @@ static void cs_parse_args(int argc, char *argv[])
opt.service = argv[optind + 1]; 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 static void
cs_write_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz) 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) ...@@ -145,17 +240,15 @@ cs_write_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz)
msg->msg_control = cbuf; msg->msg_control = cbuf;
cmsg_len = 0; cmsg_len = 0;
if (opt.mark.ena) { ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
cmsg = (struct cmsghdr *)(cbuf + cmsg_len); SOL_SOCKET, SO_MARK, &opt.mark);
cmsg_len += CMSG_SPACE(sizeof(__u32)); ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
if (cbuf_sz < cmsg_len) SOL_IPV6, IPV6_DONTFRAG, &opt.v6.dontfrag);
error(ERN_CMSG_WR, EFAULT, "cmsg buffer too small"); 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) { if (opt.txtime.ena) {
struct sock_txtime so_txtime = { struct sock_txtime so_txtime = {
.clockid = CLOCK_MONOTONIC, .clockid = CLOCK_MONOTONIC,
...@@ -199,6 +292,17 @@ cs_write_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz) ...@@ -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 | *(__u32 *)CMSG_DATA(cmsg) = SOF_TIMESTAMPING_TX_SCHED |
SOF_TIMESTAMPING_TX_SOFTWARE; 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) if (cmsg_len)
msg->msg_controllen = 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) ...@@ -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[]) int main(int argc, char *argv[])
{ {
char buf[] = "blablablabla";
struct addrinfo hints, *ai; struct addrinfo hints, *ai;
struct iovec iov[1]; struct iovec iov[1];
struct msghdr msg; struct msghdr msg;
char cbuf[1024]; char cbuf[1024];
char *buf;
int err; int err;
int fd; int fd;
cs_parse_args(argc, argv); cs_parse_args(argc, argv);
buf = malloc(opt.size);
memrnd(buf, opt.size);
memset(&hints, 0, sizeof(hints)); memset(&hints, 0, sizeof(hints));
hints.ai_family = opt.sock.family; hints.ai_family = opt.sock.family;
...@@ -326,17 +453,14 @@ int main(int argc, char *argv[]) ...@@ -326,17 +453,14 @@ int main(int argc, char *argv[])
buf[0] = ICMPV6_ECHO_REQUEST; buf[0] = ICMPV6_ECHO_REQUEST;
buf[1] = 0; buf[1] = 0;
} else if (opt.sock.type == SOCK_RAW) { } 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;; struct sockaddr_in6 *sin6 = (void *)ai->ai_addr;;
memcpy(buf, &hdr, sizeof(hdr)); memcpy(buf, &hdr, sizeof(hdr));
sin6->sin6_port = htons(opt.sock.proto); sin6->sin6_port = htons(opt.sock.proto);
} }
if (opt.sockopt.mark && ca_set_sockopts(fd);
setsockopt(fd, SOL_SOCKET, SO_MARK,
&opt.sockopt.mark, sizeof(opt.sockopt.mark)))
error(ERN_SOCKOPT, errno, "setsockopt SO_MARK");
if (clock_gettime(CLOCK_REALTIME, &time_start_real)) if (clock_gettime(CLOCK_REALTIME, &time_start_real))
error(ERN_GETTIME, errno, "gettime REALTIME"); error(ERN_GETTIME, errno, "gettime REALTIME");
...@@ -344,7 +468,7 @@ int main(int argc, char *argv[]) ...@@ -344,7 +468,7 @@ int main(int argc, char *argv[])
error(ERN_GETTIME, errno, "gettime MONOTONIC"); error(ERN_GETTIME, errno, "gettime MONOTONIC");
iov[0].iov_base = buf; iov[0].iov_base = buf;
iov[0].iov_len = sizeof(buf); iov[0].iov_len = opt.size;
memset(&msg, 0, sizeof(msg)); memset(&msg, 0, sizeof(msg));
msg.msg_name = ai->ai_addr; msg.msg_name = ai->ai_addr;
...@@ -360,7 +484,7 @@ int main(int argc, char *argv[]) ...@@ -360,7 +484,7 @@ int main(int argc, char *argv[])
fprintf(stderr, "send failed: %s\n", strerror(errno)); fprintf(stderr, "send failed: %s\n", strerror(errno));
err = ERN_SEND; err = ERN_SEND;
goto err_out; goto err_out;
} else if (err != sizeof(buf)) { } else if (err != (int)opt.size) {
fprintf(stderr, "short send\n"); fprintf(stderr, "short send\n");
err = ERN_SEND_SHORT; err = ERN_SEND_SHORT;
goto err_out; goto err_out;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment