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