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 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: releng/11.0/usr.sbin/iscsid/pdu.c 294932 2016-01-27 18:12:42Z trasz $");
33
34#include <sys/types.h>
35#include <sys/uio.h>
36#include <assert.h>
37#include <errno.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41
42#include "iscsid.h"
43#include "iscsi_proto.h"
44
45#ifdef ICL_KERNEL_PROXY
46#include <sys/ioctl.h>
47#endif
48
49static int
50pdu_ahs_length(const struct pdu *pdu)
51{
52
53	return (pdu->pdu_bhs->bhs_total_ahs_len * 4);
54}
55
56static int
57pdu_data_segment_length(const struct pdu *pdu)
58{
59	uint32_t len = 0;
60
61	len += pdu->pdu_bhs->bhs_data_segment_len[0];
62	len <<= 8;
63	len += pdu->pdu_bhs->bhs_data_segment_len[1];
64	len <<= 8;
65	len += pdu->pdu_bhs->bhs_data_segment_len[2];
66
67	return (len);
68}
69
70static void
71pdu_set_data_segment_length(struct pdu *pdu, uint32_t len)
72{
73
74	pdu->pdu_bhs->bhs_data_segment_len[2] = len;
75	pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8;
76	pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16;
77}
78
79struct pdu *
80pdu_new(struct connection *conn)
81{
82	struct pdu *pdu;
83
84	pdu = calloc(sizeof(*pdu), 1);
85	if (pdu == NULL)
86		log_err(1, "calloc");
87
88	pdu->pdu_bhs = calloc(sizeof(*pdu->pdu_bhs), 1);
89	if (pdu->pdu_bhs == NULL)
90		log_err(1, "calloc");
91
92	pdu->pdu_connection = conn;
93
94	return (pdu);
95}
96
97struct pdu *
98pdu_new_response(struct pdu *request)
99{
100
101	return (pdu_new(request->pdu_connection));
102}
103
104#ifdef ICL_KERNEL_PROXY
105
106static void
107pdu_receive_proxy(struct pdu *pdu)
108{
109	struct iscsi_daemon_receive *idr;
110	size_t len;
111	int error;
112
113	assert(pdu->pdu_connection->conn_conf.isc_iser != 0);
114
115	pdu->pdu_data = malloc(ISCSI_MAX_DATA_SEGMENT_LENGTH);
116	if (pdu->pdu_data == NULL)
117		log_err(1, "malloc");
118
119	idr = calloc(1, sizeof(*idr));
120	if (idr == NULL)
121		log_err(1, "calloc");
122
123	idr->idr_session_id = pdu->pdu_connection->conn_session_id;
124	idr->idr_bhs = pdu->pdu_bhs;
125	idr->idr_data_segment_len = ISCSI_MAX_DATA_SEGMENT_LENGTH;
126	idr->idr_data_segment = pdu->pdu_data;
127
128	error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDRECEIVE, idr);
129	if (error != 0)
130		log_err(1, "ISCSIDRECEIVE");
131
132	len = pdu_ahs_length(pdu);
133	if (len > 0)
134		log_errx(1, "protocol error: non-empty AHS");
135
136	len = pdu_data_segment_length(pdu);
137	assert(len <= ISCSI_MAX_DATA_SEGMENT_LENGTH);
138	pdu->pdu_data_len = len;
139
140	free(idr);
141}
142
143static void
144pdu_send_proxy(struct pdu *pdu)
145{
146	struct iscsi_daemon_send *ids;
147	int error;
148
149	assert(pdu->pdu_connection->conn_conf.isc_iser != 0);
150
151	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
152
153	ids = calloc(1, sizeof(*ids));
154	if (ids == NULL)
155		log_err(1, "calloc");
156
157	ids->ids_session_id = pdu->pdu_connection->conn_session_id;
158	ids->ids_bhs = pdu->pdu_bhs;
159	ids->ids_data_segment_len = pdu->pdu_data_len;
160	ids->ids_data_segment = pdu->pdu_data;
161
162	error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDSEND, ids);
163	if (error != 0)
164		log_err(1, "ISCSIDSEND");
165
166	free(ids);
167}
168
169#endif /* ICL_KERNEL_PROXY */
170
171static size_t
172pdu_padding(const struct pdu *pdu)
173{
174
175	if ((pdu->pdu_data_len % 4) != 0)
176		return (4 - (pdu->pdu_data_len % 4));
177
178	return (0);
179}
180
181static void
182pdu_read(const struct connection *conn, char *data, size_t len)
183{
184	ssize_t ret;
185
186	while (len > 0) {
187		ret = read(conn->conn_socket, data, len);
188		if (ret < 0) {
189			if (timed_out()) {
190				fail(conn, "Login Phase timeout");
191				log_errx(1, "exiting due to timeout");
192			}
193			fail(conn, strerror(errno));
194			log_err(1, "read");
195		} else if (ret == 0) {
196			fail(conn, "connection lost");
197			log_errx(1, "read: connection lost");
198		}
199		len -= ret;
200		data += ret;
201	}
202}
203
204void
205pdu_receive(struct pdu *pdu)
206{
207	size_t len, padding;
208	char dummy[4];
209
210#ifdef ICL_KERNEL_PROXY
211	if (pdu->pdu_connection->conn_conf.isc_iser != 0)
212		return (pdu_receive_proxy(pdu));
213#endif
214
215	assert(pdu->pdu_connection->conn_conf.isc_iser == 0);
216
217	pdu_read(pdu->pdu_connection,
218	    (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs));
219
220	len = pdu_ahs_length(pdu);
221	if (len > 0)
222		log_errx(1, "protocol error: non-empty AHS");
223
224	len = pdu_data_segment_length(pdu);
225	if (len > 0) {
226		if (len > ISCSI_MAX_DATA_SEGMENT_LENGTH) {
227			log_errx(1, "protocol error: received PDU "
228			    "with DataSegmentLength exceeding %d",
229			    ISCSI_MAX_DATA_SEGMENT_LENGTH);
230		}
231
232		pdu->pdu_data_len = len;
233		pdu->pdu_data = malloc(len);
234		if (pdu->pdu_data == NULL)
235			log_err(1, "malloc");
236
237		pdu_read(pdu->pdu_connection,
238		    (char *)pdu->pdu_data, pdu->pdu_data_len);
239
240		padding = pdu_padding(pdu);
241		if (padding != 0) {
242			assert(padding < sizeof(dummy));
243			pdu_read(pdu->pdu_connection,
244			    (char *)dummy, padding);
245		}
246	}
247}
248
249void
250pdu_send(struct pdu *pdu)
251{
252	ssize_t ret, total_len;
253	size_t padding;
254	uint32_t zero = 0;
255	struct iovec iov[3];
256	int iovcnt;
257
258#ifdef ICL_KERNEL_PROXY
259	if (pdu->pdu_connection->conn_conf.isc_iser != 0)
260		return (pdu_send_proxy(pdu));
261#endif
262
263	assert(pdu->pdu_connection->conn_conf.isc_iser == 0);
264
265	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
266	iov[0].iov_base = pdu->pdu_bhs;
267	iov[0].iov_len = sizeof(*pdu->pdu_bhs);
268	total_len = iov[0].iov_len;
269	iovcnt = 1;
270
271	if (pdu->pdu_data_len > 0) {
272		iov[1].iov_base = pdu->pdu_data;
273		iov[1].iov_len = pdu->pdu_data_len;
274		total_len += iov[1].iov_len;
275		iovcnt = 2;
276
277		padding = pdu_padding(pdu);
278		if (padding > 0) {
279			assert(padding < sizeof(zero));
280			iov[2].iov_base = &zero;
281			iov[2].iov_len = padding;
282			total_len += iov[2].iov_len;
283			iovcnt = 3;
284		}
285	}
286
287	ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt);
288	if (ret < 0) {
289		if (timed_out())
290			log_errx(1, "exiting due to timeout");
291		log_err(1, "writev");
292	}
293	if (ret != total_len)
294		log_errx(1, "short write");
295}
296
297void
298pdu_delete(struct pdu *pdu)
299{
300
301	free(pdu->pdu_data);
302	free(pdu->pdu_bhs);
303	free(pdu);
304}
305