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