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$");
33
34#include <sys/types.h>
35#include <sys/uio.h>
36#include <assert.h>
37#include <stdint.h>
38#include <stdio.h>
39#include <stdlib.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(int fd, char *data, size_t len)
183{
184	ssize_t ret;
185
186	while (len > 0) {
187		ret = read(fd, data, len);
188		if (ret < 0) {
189			if (timed_out())
190				log_errx(1, "exiting due to timeout");
191			log_err(1, "read");
192		} else if (ret == 0)
193			log_errx(1, "read: connection lost");
194		len -= ret;
195		data += ret;
196	}
197}
198
199void
200pdu_receive(struct pdu *pdu)
201{
202	size_t len, padding;
203	char dummy[4];
204
205#ifdef ICL_KERNEL_PROXY
206	if (pdu->pdu_connection->conn_conf.isc_iser != 0)
207		return (pdu_receive_proxy(pdu));
208#endif
209
210	assert(pdu->pdu_connection->conn_conf.isc_iser == 0);
211
212	pdu_read(pdu->pdu_connection->conn_socket,
213	    (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs));
214
215	len = pdu_ahs_length(pdu);
216	if (len > 0)
217		log_errx(1, "protocol error: non-empty AHS");
218
219	len = pdu_data_segment_length(pdu);
220	if (len > 0) {
221		if (len > ISCSI_MAX_DATA_SEGMENT_LENGTH) {
222			log_errx(1, "protocol error: received PDU "
223			    "with DataSegmentLength exceeding %d",
224			    ISCSI_MAX_DATA_SEGMENT_LENGTH);
225		}
226
227		pdu->pdu_data_len = len;
228		pdu->pdu_data = malloc(len);
229		if (pdu->pdu_data == NULL)
230			log_err(1, "malloc");
231
232		pdu_read(pdu->pdu_connection->conn_socket,
233		    (char *)pdu->pdu_data, pdu->pdu_data_len);
234
235		padding = pdu_padding(pdu);
236		if (padding != 0) {
237			assert(padding < sizeof(dummy));
238			pdu_read(pdu->pdu_connection->conn_socket,
239			    (char *)dummy, padding);
240		}
241	}
242}
243
244void
245pdu_send(struct pdu *pdu)
246{
247	ssize_t ret, total_len;
248	size_t padding;
249	uint32_t zero = 0;
250	struct iovec iov[3];
251	int iovcnt;
252
253#ifdef ICL_KERNEL_PROXY
254	if (pdu->pdu_connection->conn_conf.isc_iser != 0)
255		return (pdu_send_proxy(pdu));
256#endif
257
258	assert(pdu->pdu_connection->conn_conf.isc_iser == 0);
259
260	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
261	iov[0].iov_base = pdu->pdu_bhs;
262	iov[0].iov_len = sizeof(*pdu->pdu_bhs);
263	total_len = iov[0].iov_len;
264	iovcnt = 1;
265
266	if (pdu->pdu_data_len > 0) {
267		iov[1].iov_base = pdu->pdu_data;
268		iov[1].iov_len = pdu->pdu_data_len;
269		total_len += iov[1].iov_len;
270		iovcnt = 2;
271
272		padding = pdu_padding(pdu);
273		if (padding > 0) {
274			assert(padding < sizeof(zero));
275			iov[2].iov_base = &zero;
276			iov[2].iov_len = padding;
277			total_len += iov[2].iov_len;
278			iovcnt = 3;
279		}
280	}
281
282	ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt);
283	if (ret < 0) {
284		if (timed_out())
285			log_errx(1, "exiting due to timeout");
286		log_err(1, "writev");
287	}
288	if (ret != total_len)
289		log_errx(1, "short write");
290}
291
292void
293pdu_delete(struct pdu *pdu)
294{
295
296	free(pdu->pdu_data);
297	free(pdu->pdu_bhs);
298	free(pdu);
299}
300