discovery.c revision 267606
1/*-
2 * Copyright (c) 2012 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Edward Tomasz Napierala under sponsorship
6 * from the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * $FreeBSD: head/usr.sbin/ctld/discovery.c 267606 2014-06-18 12:26:02Z mav $
30 */
31
32#include <assert.h>
33#include <stdint.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <netinet/in.h>
38#include <netdb.h>
39#include <sys/socket.h>
40
41#include "ctld.h"
42#include "iscsi_proto.h"
43
44static struct pdu *
45text_receive(struct connection *conn)
46{
47	struct pdu *request;
48	struct iscsi_bhs_text_request *bhstr;
49
50	request = pdu_new(conn);
51	pdu_receive(request);
52	if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
53	    ISCSI_BHS_OPCODE_TEXT_REQUEST)
54		log_errx(1, "protocol error: received invalid opcode 0x%x",
55		    request->pdu_bhs->bhs_opcode);
56	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
57#if 0
58	if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) == 0)
59		log_errx(1, "received Text PDU without the \"F\" flag");
60#endif
61	/*
62	 * XXX: Implement the C flag some day.
63	 */
64	if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0)
65		log_errx(1, "received Text PDU with unsupported \"C\" flag");
66	if (ntohl(bhstr->bhstr_cmdsn) < conn->conn_cmdsn) {
67		log_errx(1, "received Text PDU with decreasing CmdSN: "
68		    "was %d, is %d", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn));
69	}
70	if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) {
71		log_errx(1, "received Text PDU with wrong StatSN: "
72		    "is %d, should be %d", ntohl(bhstr->bhstr_expstatsn),
73		    conn->conn_statsn);
74	}
75	conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
76
77	return (request);
78}
79
80static struct pdu *
81text_new_response(struct pdu *request)
82{
83	struct pdu *response;
84	struct connection *conn;
85	struct iscsi_bhs_text_request *bhstr;
86	struct iscsi_bhs_text_response *bhstr2;
87
88	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
89	conn = request->pdu_connection;
90
91	response = pdu_new_response(request);
92	bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs;
93	bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE;
94	bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL;
95	bhstr2->bhstr_lun = bhstr->bhstr_lun;
96	bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag;
97	bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag;
98	bhstr2->bhstr_statsn = htonl(conn->conn_statsn++);
99	bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn);
100	bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn);
101
102	return (response);
103}
104
105static struct pdu *
106logout_receive(struct connection *conn)
107{
108	struct pdu *request;
109	struct iscsi_bhs_logout_request *bhslr;
110
111	request = pdu_new(conn);
112	pdu_receive(request);
113	if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
114	    ISCSI_BHS_OPCODE_LOGOUT_REQUEST)
115		log_errx(1, "protocol error: received invalid opcode 0x%x",
116		    request->pdu_bhs->bhs_opcode);
117	bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
118	if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION)
119		log_debugx("received Logout PDU with invalid reason 0x%x; "
120		    "continuing anyway", bhslr->bhslr_reason & 0x7f);
121	if (ntohl(bhslr->bhslr_cmdsn) < conn->conn_cmdsn) {
122		log_errx(1, "received Logout PDU with decreasing CmdSN: "
123		    "was %d, is %d", conn->conn_cmdsn,
124		    ntohl(bhslr->bhslr_cmdsn));
125	}
126	if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) {
127		log_errx(1, "received Logout PDU with wrong StatSN: "
128		    "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn),
129		    conn->conn_statsn);
130	}
131	conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
132
133	return (request);
134}
135
136static struct pdu *
137logout_new_response(struct pdu *request)
138{
139	struct pdu *response;
140	struct connection *conn;
141	struct iscsi_bhs_logout_request *bhslr;
142	struct iscsi_bhs_logout_response *bhslr2;
143
144	bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
145	conn = request->pdu_connection;
146
147	response = pdu_new_response(request);
148	bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs;
149	bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE;
150	bhslr2->bhslr_flags = 0x80;
151	bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY;
152	bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag;
153	bhslr2->bhslr_statsn = htonl(conn->conn_statsn++);
154	bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn);
155	bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn);
156
157	return (response);
158}
159
160static void
161discovery_add_target(struct keys *response_keys, struct target *targ)
162{
163	struct portal *portal;
164	char *buf;
165	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
166	struct addrinfo *ai;
167	int ret;
168
169	keys_add(response_keys, "TargetName", targ->t_name);
170	TAILQ_FOREACH(portal, &targ->t_portal_group->pg_portals, p_next) {
171		ai = portal->p_ai;
172		ret = getnameinfo(ai->ai_addr, ai->ai_addrlen,
173		    hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
174		    NI_NUMERICHOST | NI_NUMERICSERV);
175		if (ret != 0) {
176			log_warnx("getnameinfo: %s", gai_strerror(ret));
177			continue;
178		}
179		switch (ai->ai_addr->sa_family) {
180		case AF_INET:
181			if (strcmp(hbuf, "0.0.0.0") == 0)
182				continue;
183			ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf,
184			    targ->t_portal_group->pg_tag);
185			break;
186		case AF_INET6:
187			if (strcmp(hbuf, "::") == 0)
188				continue;
189			ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf,
190			    targ->t_portal_group->pg_tag);
191			break;
192		default:
193			continue;
194		}
195		if (ret <= 0)
196		    log_err(1, "asprintf");
197		keys_add(response_keys, "TargetAddress", buf);
198		free(buf);
199	}
200}
201
202void
203discovery(struct connection *conn)
204{
205	struct pdu *request, *response;
206	struct keys *request_keys, *response_keys;
207	struct target *targ;
208	const char *send_targets;
209
210	log_debugx("beginning discovery session; waiting for Text PDU");
211	request = text_receive(conn);
212	request_keys = keys_new();
213	keys_load(request_keys, request);
214
215	send_targets = keys_find(request_keys, "SendTargets");
216	if (send_targets == NULL)
217		log_errx(1, "received Text PDU without SendTargets");
218
219	response = text_new_response(request);
220	response_keys = keys_new();
221
222	if (strcmp(send_targets, "All") == 0) {
223		TAILQ_FOREACH(targ,
224		    &conn->conn_portal->p_portal_group->pg_conf->conf_targets,
225		    t_next) {
226			if (targ->t_portal_group !=
227			    conn->conn_portal->p_portal_group) {
228				log_debugx("not returning target \"%s\"; "
229				    "belongs to a different portal group",
230				    targ->t_name);
231				continue;
232			}
233			discovery_add_target(response_keys, targ);
234		}
235	} else {
236		targ = target_find(conn->conn_portal->p_portal_group->pg_conf,
237		    send_targets);
238		if (targ == NULL) {
239			log_debugx("initiator requested information on unknown "
240			    "target \"%s\"; returning nothing", send_targets);
241		} else
242			discovery_add_target(response_keys, targ);
243	}
244	keys_save(response_keys, response);
245
246	pdu_send(response);
247	pdu_delete(response);
248	keys_delete(response_keys);
249	pdu_delete(request);
250	keys_delete(request_keys);
251
252	log_debugx("done sending targets; waiting for Logout PDU");
253	request = logout_receive(conn);
254	response = logout_new_response(request);
255
256	pdu_send(response);
257	pdu_delete(response);
258	pdu_delete(request);
259
260	log_debugx("discovery session done");
261}
262