1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2012 The FreeBSD Foundation
5 * All rights reserved.
6 *
7 * This software was developed by Edward Tomasz Napierala under sponsorship
8 * from the FreeBSD Foundation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 *
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/usr.sbin/iscsid/pdu.c 330449 2018-03-05 07:26:05Z eadler $");
35
36#include <sys/types.h>
37#include <sys/uio.h>
38#include <assert.h>
39#include <errno.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43
44#include "iscsid.h"
45#include "iscsi_proto.h"
46
47#ifdef ICL_KERNEL_PROXY
48#include <sys/ioctl.h>
49#endif
50
51static int
52pdu_ahs_length(const struct pdu *pdu)
53{
54
55	return (pdu->pdu_bhs->bhs_total_ahs_len * 4);
56}
57
58static int
59pdu_data_segment_length(const struct pdu *pdu)
60{
61	uint32_t len = 0;
62
63	len += pdu->pdu_bhs->bhs_data_segment_len[0];
64	len <<= 8;
65	len += pdu->pdu_bhs->bhs_data_segment_len[1];
66	len <<= 8;
67	len += pdu->pdu_bhs->bhs_data_segment_len[2];
68
69	return (len);
70}
71
72static void
73pdu_set_data_segment_length(struct pdu *pdu, uint32_t len)
74{
75
76	pdu->pdu_bhs->bhs_data_segment_len[2] = len;
77	pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8;
78	pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16;
79}
80
81struct pdu *
82pdu_new(struct connection *conn)
83{
84	struct pdu *pdu;
85
86	pdu = calloc(1, sizeof(*pdu));
87	if (pdu == NULL)
88		log_err(1, "calloc");
89
90	pdu->pdu_bhs = calloc(1, sizeof(*pdu->pdu_bhs));
91	if (pdu->pdu_bhs == NULL)
92		log_err(1, "calloc");
93
94	pdu->pdu_connection = conn;
95
96	return (pdu);
97}
98
99struct pdu *
100pdu_new_response(struct pdu *request)
101{
102
103	return (pdu_new(request->pdu_connection));
104}
105
106#ifdef ICL_KERNEL_PROXY
107
108static void
109pdu_receive_proxy(struct pdu *pdu)
110{
111	struct iscsi_daemon_receive *idr;
112	size_t len;
113	int error;
114
115	assert(pdu->pdu_connection->conn_conf.isc_iser != 0);
116
117	pdu->pdu_data = malloc(ISCSI_MAX_DATA_SEGMENT_LENGTH);
118	if (pdu->pdu_data == NULL)
119		log_err(1, "malloc");
120
121	idr = calloc(1, sizeof(*idr));
122	if (idr == NULL)
123		log_err(1, "calloc");
124
125	idr->idr_session_id = pdu->pdu_connection->conn_session_id;
126	idr->idr_bhs = pdu->pdu_bhs;
127	idr->idr_data_segment_len = ISCSI_MAX_DATA_SEGMENT_LENGTH;
128	idr->idr_data_segment = pdu->pdu_data;
129
130	error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDRECEIVE, idr);
131	if (error != 0)
132		log_err(1, "ISCSIDRECEIVE");
133
134	len = pdu_ahs_length(pdu);
135	if (len > 0)
136		log_errx(1, "protocol error: non-empty AHS");
137
138	len = pdu_data_segment_length(pdu);
139	assert(len <= ISCSI_MAX_DATA_SEGMENT_LENGTH);
140	pdu->pdu_data_len = len;
141
142	free(idr);
143}
144
145static void
146pdu_send_proxy(struct pdu *pdu)
147{
148	struct iscsi_daemon_send *ids;
149	int error;
150
151	assert(pdu->pdu_connection->conn_conf.isc_iser != 0);
152
153	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
154
155	ids = calloc(1, sizeof(*ids));
156	if (ids == NULL)
157		log_err(1, "calloc");
158
159	ids->ids_session_id = pdu->pdu_connection->conn_session_id;
160	ids->ids_bhs = pdu->pdu_bhs;
161	ids->ids_data_segment_len = pdu->pdu_data_len;
162	ids->ids_data_segment = pdu->pdu_data;
163
164	error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDSEND, ids);
165	if (error != 0)
166		log_err(1, "ISCSIDSEND");
167
168	free(ids);
169}
170
171#endif /* ICL_KERNEL_PROXY */
172
173static size_t
174pdu_padding(const struct pdu *pdu)
175{
176
177	if ((pdu->pdu_data_len % 4) != 0)
178		return (4 - (pdu->pdu_data_len % 4));
179
180	return (0);
181}
182
183static void
184pdu_read(const struct connection *conn, char *data, size_t len)
185{
186	ssize_t ret;
187
188	while (len > 0) {
189		ret = read(conn->conn_socket, data, len);
190		if (ret < 0) {
191			if (timed_out()) {
192				fail(conn, "Login Phase timeout");
193				log_errx(1, "exiting due to timeout");
194			}
195			fail(conn, strerror(errno));
196			log_err(1, "read");
197		} else if (ret == 0) {
198			fail(conn, "connection lost");
199			log_errx(1, "read: connection lost");
200		}
201		len -= ret;
202		data += ret;
203	}
204}
205
206void
207pdu_receive(struct pdu *pdu)
208{
209	size_t len, padding;
210	char dummy[4];
211
212#ifdef ICL_KERNEL_PROXY
213	if (pdu->pdu_connection->conn_conf.isc_iser != 0)
214		return (pdu_receive_proxy(pdu));
215#endif
216
217	assert(pdu->pdu_connection->conn_conf.isc_iser == 0);
218
219	pdu_read(pdu->pdu_connection,
220	    (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs));
221
222	len = pdu_ahs_length(pdu);
223	if (len > 0)
224		log_errx(1, "protocol error: non-empty AHS");
225
226	len = pdu_data_segment_length(pdu);
227	if (len > 0) {
228		if (len > ISCSI_MAX_DATA_SEGMENT_LENGTH) {
229			log_errx(1, "protocol error: received PDU "
230			    "with DataSegmentLength exceeding %d",
231			    ISCSI_MAX_DATA_SEGMENT_LENGTH);
232		}
233
234		pdu->pdu_data_len = len;
235		pdu->pdu_data = malloc(len);
236		if (pdu->pdu_data == NULL)
237			log_err(1, "malloc");
238
239		pdu_read(pdu->pdu_connection,
240		    (char *)pdu->pdu_data, pdu->pdu_data_len);
241
242		padding = pdu_padding(pdu);
243		if (padding != 0) {
244			assert(padding < sizeof(dummy));
245			pdu_read(pdu->pdu_connection,
246			    (char *)dummy, padding);
247		}
248	}
249}
250
251void
252pdu_send(struct pdu *pdu)
253{
254	ssize_t ret, total_len;
255	size_t padding;
256	uint32_t zero = 0;
257	struct iovec iov[3];
258	int iovcnt;
259
260#ifdef ICL_KERNEL_PROXY
261	if (pdu->pdu_connection->conn_conf.isc_iser != 0)
262		return (pdu_send_proxy(pdu));
263#endif
264
265	assert(pdu->pdu_connection->conn_conf.isc_iser == 0);
266
267	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
268	iov[0].iov_base = pdu->pdu_bhs;
269	iov[0].iov_len = sizeof(*pdu->pdu_bhs);
270	total_len = iov[0].iov_len;
271	iovcnt = 1;
272
273	if (pdu->pdu_data_len > 0) {
274		iov[1].iov_base = pdu->pdu_data;
275		iov[1].iov_len = pdu->pdu_data_len;
276		total_len += iov[1].iov_len;
277		iovcnt = 2;
278
279		padding = pdu_padding(pdu);
280		if (padding > 0) {
281			assert(padding < sizeof(zero));
282			iov[2].iov_base = &zero;
283			iov[2].iov_len = padding;
284			total_len += iov[2].iov_len;
285			iovcnt = 3;
286		}
287	}
288
289	ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt);
290	if (ret < 0) {
291		if (timed_out())
292			log_errx(1, "exiting due to timeout");
293		log_err(1, "writev");
294	}
295	if (ret != total_len)
296		log_errx(1, "short write");
297}
298
299void
300pdu_delete(struct pdu *pdu)
301{
302
303	free(pdu->pdu_data);
304	free(pdu->pdu_bhs);
305	free(pdu);
306}
307