#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <err.h>
#include <libnvmf.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "internal.h"
struct io_controller_data {
struct nvme_discovery_log_entry entry;
bool wildcard;
};
struct discovery_controller {
struct nvme_discovery_log *discovery_log;
size_t discovery_log_len;
int s;
};
struct discovery_thread_arg {
struct controller *c;
struct nvmf_qpair *qp;
int s;
};
static struct io_controller_data *io_controllers;
static struct nvmf_association *discovery_na;
static u_int num_io_controllers;
static bool
init_discovery_log_entry(struct nvme_discovery_log_entry *entry, int s,
const char *subnqn)
{
struct sockaddr_storage ss;
socklen_t len;
bool wildcard;
len = sizeof(ss);
if (getsockname(s, (struct sockaddr *)&ss, &len) == -1)
err(1, "getsockname");
memset(entry, 0, sizeof(*entry));
entry->trtype = NVMF_TRTYPE_TCP;
switch (ss.ss_family) {
case AF_INET:
{
struct sockaddr_in *sin;
sin = (struct sockaddr_in *)&ss;
entry->adrfam = NVMF_ADRFAM_IPV4;
snprintf(entry->trsvcid, sizeof(entry->trsvcid), "%u",
htons(sin->sin_port));
if (inet_ntop(AF_INET, &sin->sin_addr, entry->traddr,
sizeof(entry->traddr)) == NULL)
err(1, "inet_ntop");
wildcard = (sin->sin_addr.s_addr == htonl(INADDR_ANY));
break;
}
case AF_INET6:
{
struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)&ss;
entry->adrfam = NVMF_ADRFAM_IPV6;
snprintf(entry->trsvcid, sizeof(entry->trsvcid), "%u",
htons(sin6->sin6_port));
if (inet_ntop(AF_INET6, &sin6->sin6_addr, entry->traddr,
sizeof(entry->traddr)) == NULL)
err(1, "inet_ntop");
wildcard = (memcmp(&sin6->sin6_addr, &in6addr_any,
sizeof(in6addr_any)) == 0);
break;
}
default:
errx(1, "Unsupported address family %u", ss.ss_family);
}
entry->subtype = NVMF_SUBTYPE_NVME;
if (flow_control_disable)
entry->treq |= (1 << 2);
entry->portid = htole16(1);
entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC);
entry->aqsz = NVME_MAX_ADMIN_ENTRIES;
strlcpy(entry->subnqn, subnqn, sizeof(entry->subnqn));
return (wildcard);
}
void
init_discovery(void)
{
struct nvmf_association_params aparams;
memset(&aparams, 0, sizeof(aparams));
aparams.sq_flow_control = false;
aparams.dynamic_controller_model = true;
aparams.max_admin_qsize = NVME_MAX_ADMIN_ENTRIES;
aparams.tcp.pda = 0;
aparams.tcp.header_digests = header_digests;
aparams.tcp.data_digests = data_digests;
aparams.tcp.maxh2cdata = maxh2cdata;
discovery_na = nvmf_allocate_association(NVMF_TRTYPE_TCP, true,
&aparams);
if (discovery_na == NULL)
err(1, "Failed to create discovery association");
}
void
discovery_add_io_controller(int s, const char *subnqn)
{
struct io_controller_data *icd;
io_controllers = reallocf(io_controllers, (num_io_controllers + 1) *
sizeof(*io_controllers));
icd = &io_controllers[num_io_controllers];
num_io_controllers++;
icd->wildcard = init_discovery_log_entry(&icd->entry, s, subnqn);
}
static void
build_discovery_log_page(struct discovery_controller *dc)
{
struct sockaddr_storage ss;
socklen_t len;
char traddr[256];
u_int i, nentries;
uint8_t adrfam;
if (dc->discovery_log != NULL)
return;
len = sizeof(ss);
if (getsockname(dc->s, (struct sockaddr *)&ss, &len) == -1) {
warn("build_discovery_log_page: getsockname");
return;
}
memset(traddr, 0, sizeof(traddr));
switch (ss.ss_family) {
case AF_INET:
{
struct sockaddr_in *sin;
sin = (struct sockaddr_in *)&ss;
adrfam = NVMF_ADRFAM_IPV4;
if (inet_ntop(AF_INET, &sin->sin_addr, traddr,
sizeof(traddr)) == NULL) {
warn("build_discovery_log_page: inet_ntop");
return;
}
break;
}
case AF_INET6:
{
struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)&ss;
adrfam = NVMF_ADRFAM_IPV6;
if (inet_ntop(AF_INET6, &sin6->sin6_addr, traddr,
sizeof(traddr)) == NULL) {
warn("build_discovery_log_page: inet_ntop");
return;
}
break;
}
default:
assert(false);
}
nentries = 0;
for (i = 0; i < num_io_controllers; i++) {
if (io_controllers[i].wildcard &&
io_controllers[i].entry.adrfam != adrfam)
continue;
nentries++;
}
dc->discovery_log_len = sizeof(*dc->discovery_log) +
nentries * sizeof(struct nvme_discovery_log_entry);
dc->discovery_log = calloc(dc->discovery_log_len, 1);
dc->discovery_log->numrec = nentries;
dc->discovery_log->recfmt = 0;
nentries = 0;
for (i = 0; i < num_io_controllers; i++) {
if (io_controllers[i].wildcard &&
io_controllers[i].entry.adrfam != adrfam)
continue;
dc->discovery_log->entries[nentries] = io_controllers[i].entry;
if (io_controllers[i].wildcard)
memcpy(dc->discovery_log->entries[nentries].traddr,
traddr, sizeof(traddr));
}
}
static void
handle_get_log_page_command(const struct nvmf_capsule *nc,
const struct nvme_command *cmd, struct discovery_controller *dc)
{
uint64_t offset;
uint32_t length;
switch (nvmf_get_log_page_id(cmd)) {
case NVME_LOG_DISCOVERY:
break;
default:
warnx("Unsupported log page %u for discovery controller",
nvmf_get_log_page_id(cmd));
goto error;
}
build_discovery_log_page(dc);
offset = nvmf_get_log_page_offset(cmd);
if (offset >= dc->discovery_log_len)
goto error;
length = nvmf_get_log_page_length(cmd);
if (length > dc->discovery_log_len - offset)
length = dc->discovery_log_len - offset;
nvmf_send_controller_data(nc, (char *)dc->discovery_log + offset,
length);
return;
error:
nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
}
static bool
discovery_command(const struct nvmf_capsule *nc, const struct nvme_command *cmd,
void *arg)
{
struct discovery_controller *dc = arg;
switch (cmd->opc) {
case NVME_OPC_GET_LOG_PAGE:
handle_get_log_page_command(nc, cmd, dc);
return (true);
default:
return (false);
}
}
static void *
discovery_thread(void *arg)
{
struct discovery_thread_arg *dta = arg;
struct discovery_controller dc;
pthread_detach(pthread_self());
memset(&dc, 0, sizeof(dc));
dc.s = dta->s;
controller_handle_admin_commands(dta->c, discovery_command, &dc);
free(dc.discovery_log);
free_controller(dta->c);
nvmf_free_qpair(dta->qp);
close(dta->s);
free(dta);
return (NULL);
}
void
handle_discovery_socket(int s)
{
struct nvmf_fabric_connect_data data;
struct nvme_controller_data cdata;
struct nvmf_qpair_params qparams;
struct discovery_thread_arg *dta;
struct nvmf_capsule *nc;
struct nvmf_qpair *qp;
pthread_t thr;
int error;
memset(&qparams, 0, sizeof(qparams));
qparams.tcp.fd = s;
nc = NULL;
qp = nvmf_accept(discovery_na, &qparams, &nc, &data);
if (qp == NULL) {
warnx("Failed to create discovery qpair: %s",
nvmf_association_error(discovery_na));
goto error;
}
if (strcmp(data.subnqn, NVMF_DISCOVERY_NQN) != 0) {
warn("Discovery qpair with invalid SubNQN: %.*s",
(int)sizeof(data.subnqn), data.subnqn);
nvmf_connect_invalid_parameters(nc, true,
offsetof(struct nvmf_fabric_connect_data, subnqn));
goto error;
}
error = nvmf_finish_accept(nc, 1);
if (error != 0) {
warnc(error, "Failed to send CONNECT reponse");
goto error;
}
nvmf_init_discovery_controller_data(qp, &cdata);
dta = malloc(sizeof(*dta));
dta->qp = qp;
dta->s = s;
dta->c = init_controller(qp, &cdata);
error = pthread_create(&thr, NULL, discovery_thread, dta);
if (error != 0) {
warnc(error, "Failed to create discovery thread");
free_controller(dta->c);
free(dta);
goto error;
}
nvmf_free_capsule(nc);
return;
error:
if (nc != NULL)
nvmf_free_capsule(nc);
if (qp != NULL)
nvmf_free_qpair(qp);
close(s);
}