1188628Simp/*- 2204977Simp * SPDX-License-Identifier: BSD-2-Clause 3188628Simp * 4188628Simp * Copyright (c) 2023-2024 Chelsio Communications, Inc. 5188628Simp * Written by: John Baldwin <jhb@FreeBSD.org> 6188628Simp */ 7188628Simp 8188628Simp#include <sys/socket.h> 9188628Simp#include <err.h> 10188628Simp#include <libnvmf.h> 11188628Simp#include <stdlib.h> 12188628Simp#include <string.h> 13188628Simp#include <sysexits.h> 14188628Simp#include <unistd.h> 15188628Simp 16188628Simp#include "comnd.h" 17188628Simp#include "fabrics.h" 18188628Simp 19188628Simp/* 20188628Simp * Settings that are currently hardcoded but could be exposed to the 21188628Simp * user via additional command line options: 22188628Simp * 23188628Simp * - ADMIN queue entries 24188628Simp * - MaxR2T 25188628Simp */ 26188628Simp 27188628Simpstatic struct options { 28188628Simp const char *transport; 29188628Simp const char *address; 30188628Simp const char *cntlid; 31188628Simp const char *subnqn; 32288424Sjhb const char *hostnqn; 33288424Sjhb uint32_t kato; 34188628Simp uint16_t num_io_queues; 35288424Sjhb uint16_t queue_size; 36188628Simp bool data_digests; 37188628Simp bool flow_control; 38188628Simp bool header_digests; 39188628Simp} opt = { 40188628Simp .transport = "tcp", 41188628Simp .address = NULL, 42188628Simp .cntlid = "dynamic", 43188628Simp .subnqn = NULL, 44294849Sjhb .hostnqn = NULL, 45188628Simp .kato = NVMF_KATO_DEFAULT / 1000, 46188628Simp .num_io_queues = 1, 47188628Simp .queue_size = 0, 48288424Sjhb .data_digests = false, 49288424Sjhb .flow_control = false, 50240562Szont .header_digests = false, 51240005Szont}; 52240005Szont 53288424Sjhbstatic void 54240562Szonttcp_association_params(struct nvmf_association_params *params) 55288424Sjhb{ 56188628Simp params->tcp.pda = 0; 57240562Szont params->tcp.header_digests = opt.header_digests; 58288424Sjhb params->tcp.data_digests = opt.data_digests; 59240562Szont /* XXX */ 60240005Szont params->tcp.maxr2t = 1; 61288424Sjhb} 62240005Szont 63188628Simpstatic int 64288424Sjhbconnect_nvm_controller(enum nvmf_trtype trtype, int adrfam, const char *address, 65288424Sjhb const char *port, uint16_t cntlid, const char *subnqn) 66288424Sjhb{ 67288424Sjhb struct nvme_controller_data cdata; 68288424Sjhb struct nvmf_association_params aparams; 69288424Sjhb struct nvmf_qpair *admin, **io; 70288424Sjhb int error; 71288424Sjhb 72288424Sjhb memset(&aparams, 0, sizeof(aparams)); 73288424Sjhb aparams.sq_flow_control = opt.flow_control; 74288424Sjhb switch (trtype) { 75288424Sjhb case NVMF_TRTYPE_TCP: 76288424Sjhb tcp_association_params(&aparams); 77288424Sjhb break; 78288424Sjhb default: 79288424Sjhb warnx("Unsupported transport %s", nvmf_transport_type(trtype)); 80288424Sjhb return (EX_UNAVAILABLE); 81288424Sjhb } 82288424Sjhb 83288424Sjhb io = calloc(opt.num_io_queues, sizeof(*io)); 84240005Szont error = connect_nvm_queues(&aparams, trtype, adrfam, address, port, 85188628Simp cntlid, subnqn, opt.hostnqn, opt.kato, &admin, io, 86288424Sjhb opt.num_io_queues, opt.queue_size, &cdata); 87288424Sjhb if (error != 0) { 88188628Simp free(io); 89288424Sjhb return (error); 90188628Simp } 91188628Simp 92288424Sjhb error = nvmf_handoff_host(admin, opt.num_io_queues, io, &cdata); 93288424Sjhb if (error != 0) { 94288424Sjhb warnc(error, "Failed to handoff queues to kernel"); 95240005Szont free(io); 96288424Sjhb return (EX_IOERR); 97288424Sjhb } 98288424Sjhb free(io); 99288424Sjhb return (0); 100240562Szont} 101240005Szont 102288424Sjhbstatic void 103240005Szontconnect_discovery_entry(struct nvme_discovery_log_entry *entry) 104188628Simp{ 105288424Sjhb int adrfam; 106188628Simp 107188628Simp switch (entry->trtype) { 108288424Sjhb case NVMF_TRTYPE_TCP: 109288424Sjhb switch (entry->adrfam) { 110240005Szont case NVMF_ADRFAM_IPV4: 111240005Szont adrfam = AF_INET; 112240562Szont break; 113188628Simp case NVMF_ADRFAM_IPV6: 114240562Szont adrfam = AF_INET6; 115240562Szont break; 116288424Sjhb default: 117240005Szont warnx("Skipping unsupported address family for %s", 118240005Szont entry->subnqn); 119188628Simp return; 120288424Sjhb } 121288424Sjhb switch (entry->tsas.tcp.sectype) { 122288424Sjhb case NVME_TCP_SECURITY_NONE: 123288424Sjhb break; 124288424Sjhb default: 125288424Sjhb warnx("Skipping unsupported TCP security type for %s", 126240005Szont entry->subnqn); 127188628Simp return; 128289239Sbdrewery } 129288424Sjhb break; 130288424Sjhb default: 131288424Sjhb warnx("Skipping unsupported transport %s for %s", 132288424Sjhb nvmf_transport_type(entry->trtype), entry->subnqn); 133288424Sjhb return; 134295056Sjhb } 135288424Sjhb 136288424Sjhb /* 137288424Sjhb * XXX: Track portids and avoid duplicate connections for a 138286938Sjhb * given (subnqn,portid)? 139289239Sbdrewery */ 140 141 /* XXX: Should this make use of entry->aqsz in some way? */ 142 connect_nvm_controller(entry->trtype, adrfam, entry->traddr, 143 entry->trsvcid, entry->cntlid, entry->subnqn); 144} 145 146static void 147connect_discovery_log_page(struct nvmf_qpair *qp) 148{ 149 struct nvme_discovery_log *log; 150 int error; 151 152 error = nvmf_host_fetch_discovery_log_page(qp, &log); 153 if (error != 0) 154 errc(EX_IOERR, error, "Failed to fetch discovery log page"); 155 156 for (u_int i = 0; i < log->numrec; i++) 157 connect_discovery_entry(&log->entries[i]); 158 free(log); 159} 160 161static void 162discover_controllers(enum nvmf_trtype trtype, const char *address, 163 const char *port) 164{ 165 struct nvmf_qpair *qp; 166 167 qp = connect_discovery_adminq(trtype, address, port, opt.hostnqn); 168 169 connect_discovery_log_page(qp); 170 171 nvmf_free_qpair(qp); 172} 173 174static void 175connect_fn(const struct cmd *f, int argc, char *argv[]) 176{ 177 enum nvmf_trtype trtype; 178 const char *address, *port; 179 char *tofree; 180 u_long cntlid; 181 int error; 182 183 if (arg_parse(argc, argv, f)) 184 return; 185 186 if (opt.num_io_queues <= 0) 187 errx(EX_USAGE, "Invalid number of I/O queues"); 188 189 if (strcasecmp(opt.transport, "tcp") == 0) { 190 trtype = NVMF_TRTYPE_TCP; 191 } else 192 errx(EX_USAGE, "Unsupported or invalid transport"); 193 194 nvmf_parse_address(opt.address, &address, &port, &tofree); 195 if (port == NULL) 196 errx(EX_USAGE, "Explicit port required"); 197 198 cntlid = nvmf_parse_cntlid(opt.cntlid); 199 200 error = connect_nvm_controller(trtype, AF_UNSPEC, address, port, cntlid, 201 opt.subnqn); 202 if (error != 0) 203 exit(error); 204 205 free(tofree); 206} 207 208static void 209connect_all_fn(const struct cmd *f, int argc, char *argv[]) 210{ 211 enum nvmf_trtype trtype; 212 const char *address, *port; 213 char *tofree; 214 215 if (arg_parse(argc, argv, f)) 216 return; 217 218 if (opt.num_io_queues <= 0) 219 errx(EX_USAGE, "Invalid number of I/O queues"); 220 221 if (strcasecmp(opt.transport, "tcp") == 0) { 222 trtype = NVMF_TRTYPE_TCP; 223 } else 224 errx(EX_USAGE, "Unsupported or invalid transport"); 225 226 nvmf_parse_address(opt.address, &address, &port, &tofree); 227 discover_controllers(trtype, address, port); 228 229 free(tofree); 230} 231 232static const struct opts connect_opts[] = { 233#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc } 234 OPT("transport", 't', arg_string, opt, transport, 235 "Transport type"), 236 OPT("cntlid", 'c', arg_string, opt, cntlid, 237 "Controller ID"), 238 OPT("nr-io-queues", 'i', arg_uint16, opt, num_io_queues, 239 "Number of I/O queues"), 240 OPT("queue-size", 'Q', arg_uint16, opt, queue_size, 241 "Number of entries in each I/O queue"), 242 OPT("keep-alive-tmo", 'k', arg_uint32, opt, kato, 243 "Keep Alive timeout (in seconds)"), 244 OPT("hostnqn", 'q', arg_string, opt, hostnqn, 245 "Host NQN"), 246 OPT("flow_control", 'F', arg_none, opt, flow_control, 247 "Request SQ flow control"), 248 OPT("hdr_digests", 'g', arg_none, opt, header_digests, 249 "Enable TCP PDU header digests"), 250 OPT("data_digests", 'G', arg_none, opt, data_digests, 251 "Enable TCP PDU data digests"), 252 { NULL, 0, arg_none, NULL, NULL } 253}; 254#undef OPT 255 256static const struct args connect_args[] = { 257 { arg_string, &opt.address, "address" }, 258 { arg_string, &opt.subnqn, "SubNQN" }, 259 { arg_none, NULL, NULL }, 260}; 261 262static const struct args connect_all_args[] = { 263 { arg_string, &opt.address, "address" }, 264 { arg_none, NULL, NULL }, 265}; 266 267static struct cmd connect_cmd = { 268 .name = "connect", 269 .fn = connect_fn, 270 .descr = "Connect to a fabrics controller", 271 .ctx_size = sizeof(opt), 272 .opts = connect_opts, 273 .args = connect_args, 274}; 275 276static struct cmd connect_all_cmd = { 277 .name = "connect-all", 278 .fn = connect_all_fn, 279 .descr = "Discover and connect to fabrics controllers", 280 .ctx_size = sizeof(opt), 281 .opts = connect_opts, 282 .args = connect_all_args, 283}; 284 285CMD_COMMAND(connect_cmd); 286CMD_COMMAND(connect_all_cmd); 287