1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2023-2024 Chelsio Communications, Inc. 5 * Written by: John Baldwin <jhb@FreeBSD.org> 6 */ 7 8 #include <sys/socket.h> 9 #include <err.h> 10 #include <libnvmf.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <sysexits.h> 14 #include <unistd.h> 15 16 #include "comnd.h" 17 #include "fabrics.h" 18 19 /* 20 * Settings that are currently hardcoded but could be exposed to the 21 * user via additional command line options: 22 * 23 * - ADMIN queue entries 24 * - MaxR2T 25 */ 26 27 static struct options { 28 const char *transport; 29 const char *address; 30 const char *cntlid; 31 const char *subnqn; 32 const char *hostnqn; 33 uint32_t kato; 34 uint32_t reconnect_delay; 35 uint32_t controller_loss_timeout; 36 uint16_t num_io_queues; 37 uint16_t queue_size; 38 bool data_digests; 39 bool flow_control; 40 bool header_digests; 41 } opt = { 42 .transport = "tcp", 43 .address = NULL, 44 .cntlid = "dynamic", 45 .subnqn = NULL, 46 .hostnqn = NULL, 47 .kato = NVMF_KATO_DEFAULT / 1000, 48 .reconnect_delay = NVMF_DEFAULT_RECONNECT_DELAY, 49 .controller_loss_timeout = NVMF_DEFAULT_CONTROLLER_LOSS, 50 .num_io_queues = 1, 51 .queue_size = 0, 52 .data_digests = false, 53 .flow_control = false, 54 .header_digests = false, 55 }; 56 57 static void 58 tcp_association_params(struct nvmf_association_params *params) 59 { 60 params->tcp.pda = 0; 61 params->tcp.header_digests = opt.header_digests; 62 params->tcp.data_digests = opt.data_digests; 63 /* XXX */ 64 params->tcp.maxr2t = 1; 65 } 66 67 static int 68 connect_nvm_controller(enum nvmf_trtype trtype, int adrfam, const char *address, 69 const char *port, uint16_t cntlid, const char *subnqn, 70 const struct nvme_discovery_log_entry *dle) 71 { 72 struct nvme_controller_data cdata; 73 struct nvme_discovery_log_entry dle_thunk; 74 struct nvmf_association_params aparams; 75 struct nvmf_qpair *admin, **io; 76 const char *hostnqn; 77 int error; 78 79 memset(&aparams, 0, sizeof(aparams)); 80 aparams.sq_flow_control = opt.flow_control; 81 switch (trtype) { 82 case NVMF_TRTYPE_TCP: 83 tcp_association_params(&aparams); 84 break; 85 default: 86 warnx("Unsupported transport %s", nvmf_transport_type(trtype)); 87 return (EX_UNAVAILABLE); 88 } 89 90 hostnqn = opt.hostnqn; 91 if (hostnqn == NULL) 92 hostnqn = nvmf_default_hostnqn(); 93 io = calloc(opt.num_io_queues, sizeof(*io)); 94 error = connect_nvm_queues(&aparams, trtype, adrfam, address, port, 95 cntlid, subnqn, hostnqn, opt.kato * 1000, &admin, io, 96 opt.num_io_queues, opt.queue_size, &cdata); 97 if (error != 0) { 98 free(io); 99 return (error); 100 } 101 102 if (dle == NULL) { 103 error = nvmf_init_dle_from_admin_qp(admin, &cdata, &dle_thunk); 104 if (error != 0) { 105 warnc(error, "Failed to generate handoff parameters"); 106 disconnect_nvm_queues(admin, io, opt.num_io_queues); 107 free(io); 108 return (EX_IOERR); 109 } 110 dle = &dle_thunk; 111 } 112 113 error = nvmf_handoff_host(dle, hostnqn, admin, opt.num_io_queues, io, 114 &cdata, opt.reconnect_delay, opt.controller_loss_timeout); 115 if (error != 0) { 116 warnc(error, "Failed to handoff queues to kernel"); 117 free(io); 118 return (EX_IOERR); 119 } 120 free(io); 121 return (0); 122 } 123 124 static void 125 connect_discovery_entry(struct nvme_discovery_log_entry *entry) 126 { 127 int adrfam; 128 129 switch (entry->trtype) { 130 case NVMF_TRTYPE_TCP: 131 switch (entry->adrfam) { 132 case NVMF_ADRFAM_IPV4: 133 adrfam = AF_INET; 134 break; 135 case NVMF_ADRFAM_IPV6: 136 adrfam = AF_INET6; 137 break; 138 default: 139 warnx("Skipping unsupported address family for %s", 140 entry->subnqn); 141 return; 142 } 143 switch (entry->tsas.tcp.sectype) { 144 case NVME_TCP_SECURITY_NONE: 145 break; 146 default: 147 warnx("Skipping unsupported TCP security type for %s", 148 entry->subnqn); 149 return; 150 } 151 break; 152 default: 153 warnx("Skipping unsupported transport %s for %s", 154 nvmf_transport_type(entry->trtype), entry->subnqn); 155 return; 156 } 157 158 /* 159 * XXX: Track portids and avoid duplicate connections for a 160 * given (subnqn,portid)? 161 */ 162 163 /* XXX: Should this make use of entry->aqsz in some way? */ 164 connect_nvm_controller(entry->trtype, adrfam, entry->traddr, 165 entry->trsvcid, entry->cntlid, entry->subnqn, entry); 166 } 167 168 static void 169 connect_discovery_log_page(struct nvmf_qpair *qp) 170 { 171 struct nvme_discovery_log *log; 172 int error; 173 174 error = nvmf_host_fetch_discovery_log_page(qp, &log); 175 if (error != 0) 176 errc(EX_IOERR, error, "Failed to fetch discovery log page"); 177 178 for (u_int i = 0; i < log->numrec; i++) 179 connect_discovery_entry(&log->entries[i]); 180 free(log); 181 } 182 183 static void 184 discover_controllers(enum nvmf_trtype trtype, const char *address, 185 const char *port) 186 { 187 struct nvmf_qpair *qp; 188 189 qp = connect_discovery_adminq(trtype, address, port, opt.hostnqn); 190 191 connect_discovery_log_page(qp); 192 193 nvmf_free_qpair(qp); 194 } 195 196 static void 197 connect_fn(const struct cmd *f, int argc, char *argv[]) 198 { 199 enum nvmf_trtype trtype; 200 const char *address, *port; 201 char *tofree; 202 u_long cntlid; 203 int error; 204 205 if (arg_parse(argc, argv, f)) 206 return; 207 208 if (opt.num_io_queues <= 0) 209 errx(EX_USAGE, "Invalid number of I/O queues"); 210 211 if (strcasecmp(opt.transport, "tcp") == 0) { 212 trtype = NVMF_TRTYPE_TCP; 213 } else 214 errx(EX_USAGE, "Unsupported or invalid transport"); 215 216 nvmf_parse_address(opt.address, &address, &port, &tofree); 217 if (port == NULL) 218 errx(EX_USAGE, "Explicit port required"); 219 220 cntlid = nvmf_parse_cntlid(opt.cntlid); 221 222 error = connect_nvm_controller(trtype, AF_UNSPEC, address, port, cntlid, 223 opt.subnqn, NULL); 224 if (error != 0) 225 exit(error); 226 227 free(tofree); 228 } 229 230 static void 231 connect_all_fn(const struct cmd *f, int argc, char *argv[]) 232 { 233 enum nvmf_trtype trtype; 234 const char *address, *port; 235 char *tofree; 236 237 if (arg_parse(argc, argv, f)) 238 return; 239 240 if (opt.num_io_queues <= 0) 241 errx(EX_USAGE, "Invalid number of I/O queues"); 242 243 if (strcasecmp(opt.transport, "tcp") == 0) { 244 trtype = NVMF_TRTYPE_TCP; 245 } else 246 errx(EX_USAGE, "Unsupported or invalid transport"); 247 248 nvmf_parse_address(opt.address, &address, &port, &tofree); 249 discover_controllers(trtype, address, port); 250 251 free(tofree); 252 } 253 254 static const struct opts connect_opts[] = { 255 #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } 256 OPT("transport", 't', arg_string, opt, transport, 257 "Transport type"), 258 OPT("cntlid", 'c', arg_string, opt, cntlid, 259 "Controller ID"), 260 OPT("nr-io-queues", 'i', arg_uint16, opt, num_io_queues, 261 "Number of I/O queues"), 262 OPT("queue-size", 'Q', arg_uint16, opt, queue_size, 263 "Number of entries in each I/O queue"), 264 OPT("keep-alive-tmo", 'k', arg_uint32, opt, kato, 265 "Keep Alive timeout (in seconds)"), 266 OPT("reconnect-delay", 'r', arg_uint32, opt, reconnect_delay, 267 "Delay between reconnect attempts after connection loss " 268 "(in seconds)"), 269 OPT("ctrl-loss-tmo", 'l', arg_uint32, opt, controller_loss_timeout, 270 "Controller loss timeout after connection loss (in seconds)"), 271 OPT("hostnqn", 'q', arg_string, opt, hostnqn, 272 "Host NQN"), 273 OPT("flow_control", 'F', arg_none, opt, flow_control, 274 "Request SQ flow control"), 275 OPT("hdr_digests", 'g', arg_none, opt, header_digests, 276 "Enable TCP PDU header digests"), 277 OPT("data_digests", 'G', arg_none, opt, data_digests, 278 "Enable TCP PDU data digests"), 279 { NULL, 0, arg_none, NULL, NULL } 280 }; 281 #undef OPT 282 283 static const struct args connect_args[] = { 284 { arg_string, &opt.address, "address" }, 285 { arg_string, &opt.subnqn, "SubNQN" }, 286 { arg_none, NULL, NULL }, 287 }; 288 289 static const struct args connect_all_args[] = { 290 { arg_string, &opt.address, "address" }, 291 { arg_none, NULL, NULL }, 292 }; 293 294 static struct cmd connect_cmd = { 295 .name = "connect", 296 .fn = connect_fn, 297 .descr = "Connect to a fabrics controller", 298 .ctx_size = sizeof(opt), 299 .opts = connect_opts, 300 .args = connect_args, 301 }; 302 303 static struct cmd connect_all_cmd = { 304 .name = "connect-all", 305 .fn = connect_all_fn, 306 .descr = "Discover and connect to fabrics controllers", 307 .ctx_size = sizeof(opt), 308 .opts = connect_opts, 309 .args = connect_all_args, 310 }; 311 312 CMD_COMMAND(connect_cmd); 313 CMD_COMMAND(connect_all_cmd); 314