1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
|
// SPDX-License-Identifier: GPL-2.0
/*
* Exercise the RDS getsockopt() paths that were converted to the
* getsockopt_iter() / sockopt_t callback.
*
* Three distinct paths are covered:
*
* - RDS_RECVERR and SO_RDS_TRANSPORT, which now return their int value
* through copy_to_iter() and report the written length in opt->optlen.
*
* - RDS_INFO_*, which pins the userspace buffer with
* iov_iter_extract_pages() (including a non-zero starting page offset)
* and lets the info producers memcpy the snapshot in under a spinlock.
*
* The kvec (in-kernel buffer) -> -EOPNOTSUPP path of rds_info_getsockopt()
* is not reachable from a userspace getsockopt() and so is not tested here.
*/
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/rds.h>
#include "../../kselftest_harness.h"
#ifndef AF_RDS
#define AF_RDS 21
#endif
FIXTURE(rds) {
int fd;
};
FIXTURE_SETUP(rds)
{
self->fd = socket(AF_RDS, SOCK_SEQPACKET, 0);
if (self->fd < 0)
SKIP(return, "AF_RDS unavailable (errno %d) - load the rds module",
errno);
}
FIXTURE_TEARDOWN(rds)
{
if (self->fd >= 0)
close(self->fd);
}
/* RDS_RECVERR defaults to 0 and is reported back as a 4-byte int. */
TEST_F(rds, recverr_default)
{
socklen_t len = sizeof(int);
int val = 0xdeadbeef;
ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, &len));
EXPECT_EQ(sizeof(int), len);
EXPECT_EQ(0, val);
}
/* A value set via setsockopt() must be readable back unchanged. */
TEST_F(rds, recverr_set_get)
{
socklen_t len = sizeof(int);
int val = 1;
ASSERT_EQ(0, setsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, len));
val = 0;
ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, &val, &len));
EXPECT_EQ(sizeof(int), len);
EXPECT_EQ(1, val);
}
/* A buffer smaller than an int is rejected with EINVAL, not silently. */
TEST_F(rds, recverr_short_buffer)
{
socklen_t len = sizeof(int) - 1;
char buf[sizeof(int)];
EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_RECVERR, buf, &len));
EXPECT_EQ(EINVAL, errno);
}
/* An unbound socket reports RDS_TRANS_NONE for SO_RDS_TRANSPORT. */
TEST_F(rds, transport_unbound)
{
socklen_t len = sizeof(int);
int val = 0;
ASSERT_EQ(0, getsockopt(self->fd, SOL_RDS, SO_RDS_TRANSPORT, &val,
&len));
EXPECT_EQ(sizeof(int), len);
EXPECT_EQ(RDS_TRANS_NONE, (unsigned int)val);
}
TEST_F(rds, transport_short_buffer)
{
socklen_t len = sizeof(int) - 1;
char buf[sizeof(int)];
EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, SO_RDS_TRANSPORT, buf,
&len));
EXPECT_EQ(EINVAL, errno);
}
/*
* RDS_INFO_COUNTERS with a zero-length buffer is the "probe" call: it must
* fail with ENOSPC and report the required snapshot size in optlen.
*/
TEST_F(rds, info_counters_probe)
{
socklen_t len = 0;
EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL,
&len));
EXPECT_EQ(ENOSPC, errno);
EXPECT_GT(len, 0);
/* The snapshot is an array of fixed-size counter records. */
EXPECT_EQ(0, len % (socklen_t)sizeof(struct rds_info_counter));
}
/*
* A real snapshot into an unaligned userspace buffer exercises the
* iov_iter_extract_pages() path, including the non-zero offset0 handling
* that the patch reworked. Place the buffer at a non-page-aligned address
* spanning into the next page to make sure multi-page pinning works too.
*/
TEST_F(rds, info_counters_snapshot)
{
struct rds_info_counter *ctr;
socklen_t need = 0, len;
long pagesz = sysconf(_SC_PAGESIZE);
size_t offset, map_len;
unsigned int i, n;
char *region, *buf;
int ret;
/* Probe for the required size. */
getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &need);
ASSERT_GT(need, 0);
/*
* Place the buffer at a non-page-aligned offset that runs past the
* first page boundary, and size the mapping from the probed length so
* the test keeps working if the counter set grows.
*/
offset = pagesz - 64;
map_len = ((offset + need + pagesz - 1) / pagesz) * pagesz;
region = mmap(NULL, map_len, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(MAP_FAILED, region);
buf = region + offset;
/*
* On success the RDS_INFO path returns the positive per-element size
* (lens.each) rather than 0, and writes the full snapshot length back
* into optlen.
*/
len = need;
ret = getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, buf, &len);
ASSERT_GE(ret, 0) {
TH_LOG("getsockopt snapshot failed: errno %d", errno);
}
EXPECT_EQ(sizeof(struct rds_info_counter), ret);
EXPECT_EQ(need, len);
/* The counter names must be NUL-terminated, non-empty strings. */
ctr = (struct rds_info_counter *)buf;
n = len / sizeof(*ctr);
ASSERT_GT(n, 0);
for (i = 0; i < n; i++) {
size_t namelen = strnlen((char *)ctr[i].name,
sizeof(ctr[i].name));
EXPECT_GT(namelen, 0);
EXPECT_LT(namelen, sizeof(ctr[i].name));
}
munmap(region, map_len);
}
/*
* A non-zero but too-small buffer must report ENOSPC and the full required
* length, without corrupting memory past the buffer.
*/
TEST_F(rds, info_counters_short_buffer)
{
socklen_t need = 0, len;
char small[sizeof(struct rds_info_counter)];
getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, NULL, &need);
ASSERT_GT(need, 0);
/* Ask with a buffer guaranteed smaller than the full snapshot. */
if (need <= (socklen_t)sizeof(small))
SKIP(return, "snapshot fits in one record; nothing to test");
len = 1; /* < sizeof(struct rds_info_counter) */
EXPECT_EQ(-1, getsockopt(self->fd, SOL_RDS, RDS_INFO_COUNTERS, small,
&len));
EXPECT_EQ(ENOSPC, errno);
EXPECT_EQ(need, len);
}
TEST_HARNESS_MAIN
|