Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/ctld/iscsi.cc
103478 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2003, 2004 Silicon Graphics International Corp.
5
* Copyright (c) 1997-2007 Kenneth D. Merry
6
* Copyright (c) 2012 The FreeBSD Foundation
7
* Copyright (c) 2017 Jakub Wojciech Klama <[email protected]>
8
* All rights reserved.
9
* Copyright (c) 2025 Chelsio Communications, Inc.
10
*
11
* Portions of this software were developed by Edward Tomasz Napierala
12
* under sponsorship from the FreeBSD Foundation.
13
*
14
* Redistribution and use in source and binary forms, with or without
15
* modification, are permitted provided that the following conditions
16
* are met:
17
* 1. Redistributions of source code must retain the above copyright
18
* notice, this list of conditions, and the following disclaimer,
19
* without modification.
20
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
21
* substantially similar to the "NO WARRANTY" disclaimer below
22
* ("Disclaimer") and any redistribution must be conditioned upon
23
* including a substantially similar Disclaimer requirement for further
24
* binary redistribution.
25
*
26
* NO WARRANTY
27
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
30
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
36
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37
* POSSIBILITY OF SUCH DAMAGES.
38
*
39
*/
40
41
#include <sys/param.h>
42
#include <sys/linker.h>
43
#include <sys/module.h>
44
#include <sys/time.h>
45
#include <assert.h>
46
#include <libiscsiutil.h>
47
#include <stdio.h>
48
#include <stdlib.h>
49
#include <cam/ctl/ctl.h>
50
#include <cam/ctl/ctl_io.h>
51
#include <cam/ctl/ctl_ioctl.h>
52
53
#include "ctld.hh"
54
#include "iscsi.hh"
55
56
#define SOCKBUF_SIZE 1048576
57
58
struct iscsi_portal final : public portal {
59
iscsi_portal(struct portal_group *pg, const char *listen,
60
portal_protocol protocol, freebsd::addrinfo_up ai) :
61
portal(pg, listen, protocol, std::move(ai)) {}
62
63
bool init_socket_options(int s) override;
64
void handle_connection(freebsd::fd_up fd, const char *host,
65
const struct sockaddr *client_sa) override;
66
};
67
68
struct iscsi_portal_group final : public portal_group {
69
iscsi_portal_group(struct conf *conf, std::string_view name) :
70
portal_group(conf, name) {}
71
72
const char *keyword() const override
73
{ return "portal-group"; }
74
75
void allocate_tag() override;
76
bool add_portal(const char *value, portal_protocol protocol)
77
override;
78
void add_default_portals() override;
79
bool set_filter(const char *str) override;
80
81
virtual port_up create_port(struct target *target, auth_group_sp ag)
82
override;
83
virtual port_up create_port(struct target *target, uint32_t ctl_port)
84
override;
85
private:
86
static uint16_t last_portal_group_tag;
87
};
88
89
struct iscsi_port final : public portal_group_port {
90
iscsi_port(struct target *target, struct portal_group *pg,
91
auth_group_sp ag) :
92
portal_group_port(target, pg, ag) {}
93
iscsi_port(struct target *target, struct portal_group *pg,
94
uint32_t ctl_port) :
95
portal_group_port(target, pg, ctl_port) {}
96
97
bool kernel_create_port() override;
98
bool kernel_remove_port() override;
99
100
private:
101
static bool module_loaded;
102
static void load_kernel_module();
103
};
104
105
struct iscsi_target final : public target {
106
iscsi_target(struct conf *conf, std::string_view name) :
107
target(conf, "target", name) {}
108
109
bool add_initiator_name(std::string_view name) override;
110
bool add_initiator_portal(const char *addr) override;
111
bool add_lun(u_int id, const char *lun_name) override;
112
bool add_portal_group(const char *pg_name, const char *ag_name)
113
override;
114
struct lun *start_lun(u_int id) override;
115
116
protected:
117
struct portal_group *default_portal_group() override;
118
};
119
120
#ifdef ICL_KERNEL_PROXY
121
static void pdu_receive_proxy(struct pdu *pdu);
122
static void pdu_send_proxy(struct pdu *pdu);
123
#endif /* ICL_KERNEL_PROXY */
124
static void pdu_fail(const struct connection *conn, const char *reason);
125
126
uint16_t iscsi_portal_group::last_portal_group_tag = 0xff;
127
bool iscsi_port::module_loaded = false;
128
129
static struct connection_ops conn_ops = {
130
.timed_out = timed_out,
131
#ifdef ICL_KERNEL_PROXY
132
.pdu_receive_proxy = pdu_receive_proxy,
133
.pdu_send_proxy = pdu_send_proxy,
134
#else
135
.pdu_receive_proxy = nullptr,
136
.pdu_send_proxy = nullptr,
137
#endif
138
.fail = pdu_fail,
139
};
140
141
portal_group_up
142
iscsi_make_portal_group(struct conf *conf, std::string_view name)
143
{
144
return std::make_unique<iscsi_portal_group>(conf, name);
145
}
146
147
target_up
148
iscsi_make_target(struct conf *conf, std::string_view name)
149
{
150
return std::make_unique<iscsi_target>(conf, name);
151
}
152
153
void
154
iscsi_portal_group::allocate_tag()
155
{
156
set_tag(++last_portal_group_tag);
157
}
158
159
bool
160
iscsi_portal_group::add_portal(const char *value, portal_protocol protocol)
161
{
162
switch (protocol) {
163
case portal_protocol::ISCSI:
164
case portal_protocol::ISER:
165
break;
166
default:
167
log_warnx("unsupported portal protocol for %s", value);
168
return (false);
169
}
170
171
freebsd::addrinfo_up ai = parse_addr_port(value, "3260");
172
if (!ai) {
173
log_warnx("invalid listen address %s", value);
174
return (false);
175
}
176
177
/*
178
* XXX: getaddrinfo(3) may return multiple addresses; we should turn
179
* those into multiple portals.
180
*/
181
182
pg_portals.emplace_back(std::make_unique<iscsi_portal>(this, value,
183
protocol, std::move(ai)));
184
return (true);
185
}
186
187
void
188
iscsi_portal_group::add_default_portals()
189
{
190
add_portal("0.0.0.0", portal_protocol::ISCSI);
191
add_portal("[::]", portal_protocol::ISCSI);
192
}
193
194
bool
195
iscsi_portal_group::set_filter(const char *str)
196
{
197
enum discovery_filter filter;
198
199
if (strcmp(str, "none") == 0) {
200
filter = discovery_filter::NONE;
201
} else if (strcmp(str, "portal") == 0) {
202
filter = discovery_filter::PORTAL;
203
} else if (strcmp(str, "portal-name") == 0) {
204
filter = discovery_filter::PORTAL_NAME;
205
} else if (strcmp(str, "portal-name-auth") == 0) {
206
filter = discovery_filter::PORTAL_NAME_AUTH;
207
} else {
208
log_warnx("invalid discovery-filter \"%s\" for portal-group "
209
"\"%s\"; valid values are \"none\", \"portal\", "
210
"\"portal-name\", and \"portal-name-auth\"",
211
str, name());
212
return (false);
213
}
214
215
if (pg_discovery_filter != discovery_filter::UNKNOWN &&
216
pg_discovery_filter != filter) {
217
log_warnx("cannot set discovery-filter to \"%s\" for "
218
"portal-group \"%s\"; already has a different "
219
"value", str, name());
220
return (false);
221
}
222
223
pg_discovery_filter = filter;
224
return (true);
225
}
226
227
port_up
228
iscsi_portal_group::create_port(struct target *target, auth_group_sp ag)
229
{
230
return std::make_unique<iscsi_port>(target, this, ag);
231
}
232
233
port_up
234
iscsi_portal_group::create_port(struct target *target, uint32_t ctl_port)
235
{
236
return std::make_unique<iscsi_port>(target, this, ctl_port);
237
}
238
239
void
240
iscsi_port::load_kernel_module()
241
{
242
int saved_errno;
243
244
if (module_loaded)
245
return;
246
247
saved_errno = errno;
248
if (modfind("cfiscsi") == -1 && kldload("cfiscsi") == -1)
249
log_warn("couldn't load cfiscsi");
250
errno = saved_errno;
251
module_loaded = true;
252
}
253
254
bool
255
iscsi_port::kernel_create_port()
256
{
257
struct portal_group *pg = p_portal_group;
258
struct target *targ = p_target;
259
260
load_kernel_module();
261
262
freebsd::nvlist_up nvl = pg->options();
263
nvlist_add_string(nvl.get(), "cfiscsi_target", targ->name());
264
nvlist_add_string(nvl.get(), "ctld_portal_group_name", pg->name());
265
nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u",
266
pg->tag());
267
268
if (targ->has_alias()) {
269
nvlist_add_string(nvl.get(), "cfiscsi_target_alias",
270
targ->alias());
271
}
272
273
return (ctl_create_port("iscsi", nvl.get(), &p_ctl_port));
274
}
275
276
bool
277
iscsi_port::kernel_remove_port()
278
{
279
freebsd::nvlist_up nvl(nvlist_create(0));
280
nvlist_add_string(nvl.get(), "cfiscsi_target", p_target->name());
281
nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u",
282
p_portal_group->tag());
283
284
return (ctl_remove_port("iscsi", nvl.get()));
285
}
286
287
bool
288
iscsi_portal::init_socket_options(int s)
289
{
290
int sockbuf;
291
292
sockbuf = SOCKBUF_SIZE;
293
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbuf,
294
sizeof(sockbuf)) == -1) {
295
log_warn("setsockopt(SO_RCVBUF) failed for %s", listen());
296
return (false);
297
}
298
sockbuf = SOCKBUF_SIZE;
299
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbuf,
300
sizeof(sockbuf)) == -1) {
301
log_warn("setsockopt(SO_SNDBUF) failed for %s", listen());
302
return (false);
303
}
304
return (true);
305
}
306
307
bool
308
iscsi_target::add_initiator_name(std::string_view name)
309
{
310
if (!use_private_auth("initiator-name"))
311
return (false);
312
return (t_auth_group->add_initiator_name(name));
313
}
314
315
bool
316
iscsi_target::add_initiator_portal(const char *addr)
317
{
318
if (!use_private_auth("initiator-portal"))
319
return (false);
320
return (t_auth_group->add_initiator_portal(addr));
321
}
322
323
bool
324
iscsi_target::add_lun(u_int id, const char *lun_name)
325
{
326
std::string lun_label = "LUN " + std::to_string(id);
327
return target::add_lun(id, lun_label.c_str(), lun_name);
328
}
329
330
bool
331
iscsi_target::add_portal_group(const char *pg_name, const char *ag_name)
332
{
333
struct portal_group *pg;
334
auth_group_sp ag;
335
336
pg = t_conf->find_portal_group(pg_name);
337
if (pg == NULL) {
338
log_warnx("unknown portal-group \"%s\" for %s", pg_name,
339
label());
340
return (false);
341
}
342
343
if (ag_name != NULL) {
344
ag = t_conf->find_auth_group(ag_name);
345
if (ag == NULL) {
346
log_warnx("unknown auth-group \"%s\" for %s", ag_name,
347
label());
348
return (false);
349
}
350
}
351
352
if (!t_conf->add_port(this, pg, std::move(ag))) {
353
log_warnx("can't link portal-group \"%s\" to %s", pg_name,
354
label());
355
return (false);
356
}
357
return (true);
358
}
359
360
struct lun *
361
iscsi_target::start_lun(u_int id)
362
{
363
std::string lun_label = "LUN " + std::to_string(id);
364
std::string lun_name = freebsd::stringf("%s,lun,%u", name(), id);
365
return target::start_lun(id, lun_label.c_str(), lun_name.c_str());
366
}
367
368
struct portal_group *
369
iscsi_target::default_portal_group()
370
{
371
return t_conf->find_portal_group("default");
372
}
373
374
#ifdef ICL_KERNEL_PROXY
375
376
static void
377
pdu_receive_proxy(struct pdu *pdu)
378
{
379
struct connection *conn;
380
size_t len;
381
382
assert(proxy_mode);
383
conn = pdu->pdu_connection;
384
385
kernel_receive(pdu);
386
387
len = pdu_ahs_length(pdu);
388
if (len > 0)
389
log_errx(1, "protocol error: non-empty AHS");
390
391
len = pdu_data_segment_length(pdu);
392
assert(len <= (size_t)conn->conn_max_recv_data_segment_length);
393
pdu->pdu_data_len = len;
394
}
395
396
static void
397
pdu_send_proxy(struct pdu *pdu)
398
{
399
400
assert(proxy_mode);
401
402
pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
403
kernel_send(pdu);
404
}
405
406
#endif /* ICL_KERNEL_PROXY */
407
408
static void
409
pdu_fail(const struct connection *conn __unused, const char *reason __unused)
410
{
411
}
412
413
iscsi_connection::iscsi_connection(struct portal *portal, freebsd::fd_up fd,
414
const char *host, const struct sockaddr *client_sa) :
415
conn_portal(portal), conn_fd(std::move(fd)), conn_initiator_addr(host),
416
conn_initiator_sa(client_sa)
417
{
418
connection_init(&conn, &conn_ops, proxy_mode);
419
conn.conn_socket = conn_fd;
420
}
421
422
iscsi_connection::~iscsi_connection()
423
{
424
chap_delete(conn_chap);
425
}
426
427
void
428
iscsi_connection::kernel_handoff()
429
{
430
struct portal_group *pg = conn_portal->portal_group();
431
struct ctl_iscsi req;
432
433
bzero(&req, sizeof(req));
434
435
req.type = CTL_ISCSI_HANDOFF;
436
strlcpy(req.data.handoff.initiator_name, conn_initiator_name.c_str(),
437
sizeof(req.data.handoff.initiator_name));
438
strlcpy(req.data.handoff.initiator_addr, conn_initiator_addr.c_str(),
439
sizeof(req.data.handoff.initiator_addr));
440
if (!conn_initiator_alias.empty()) {
441
strlcpy(req.data.handoff.initiator_alias,
442
conn_initiator_alias.c_str(),
443
sizeof(req.data.handoff.initiator_alias));
444
}
445
memcpy(req.data.handoff.initiator_isid, conn_initiator_isid,
446
sizeof(req.data.handoff.initiator_isid));
447
strlcpy(req.data.handoff.target_name, conn_target->name(),
448
sizeof(req.data.handoff.target_name));
449
strlcpy(req.data.handoff.offload, pg->offload(),
450
sizeof(req.data.handoff.offload));
451
#ifdef ICL_KERNEL_PROXY
452
if (proxy_mode)
453
req.data.handoff.connection_id = conn.conn_socket;
454
else
455
req.data.handoff.socket = conn.conn_socket;
456
#else
457
req.data.handoff.socket = conn.conn_socket;
458
#endif
459
req.data.handoff.portal_group_tag = pg->tag();
460
if (conn.conn_header_digest == CONN_DIGEST_CRC32C)
461
req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C;
462
if (conn.conn_data_digest == CONN_DIGEST_CRC32C)
463
req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C;
464
req.data.handoff.cmdsn = conn.conn_cmdsn;
465
req.data.handoff.statsn = conn.conn_statsn;
466
req.data.handoff.max_recv_data_segment_length =
467
conn.conn_max_recv_data_segment_length;
468
req.data.handoff.max_send_data_segment_length =
469
conn.conn_max_send_data_segment_length;
470
req.data.handoff.max_burst_length = conn.conn_max_burst_length;
471
req.data.handoff.first_burst_length = conn.conn_first_burst_length;
472
req.data.handoff.immediate_data = conn.conn_immediate_data;
473
474
if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
475
log_err(1, "error issuing CTL_ISCSI ioctl; "
476
"dropping connection");
477
}
478
479
if (req.status != CTL_ISCSI_OK) {
480
log_errx(1, "error returned from CTL iSCSI handoff request: "
481
"%s; dropping connection", req.error_str);
482
}
483
}
484
485
void
486
iscsi_connection::handle()
487
{
488
login();
489
if (conn_session_type == CONN_SESSION_TYPE_NORMAL) {
490
kernel_handoff();
491
log_debugx("connection handed off to the kernel");
492
} else {
493
assert(conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
494
discovery();
495
}
496
}
497
498
void
499
iscsi_portal::handle_connection(freebsd::fd_up fd, const char *host,
500
const struct sockaddr *client_sa)
501
{
502
struct conf *conf = portal_group()->conf();
503
504
iscsi_connection conn(this, std::move(fd), host, client_sa);
505
start_timer(conf->timeout(), true);
506
kernel_capsicate();
507
conn.handle();
508
}
509
510