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/ctld/pdu.c 281532 2015-04-14 18:13:55Z delphij $");
33
34#include <sys/types.h>
35#include <sys/uio.h>
36#include <assert.h>
37#include <stdlib.h>
38#include <unistd.h>
39
40#include "ctld.h"
41#include "iscsi_proto.h"
42
43#ifdef ICL_KERNEL_PROXY
44#include <sys/ioctl.h>
45#endif
46
47extern bool proxy_mode;
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	size_t len;
110
111	assert(proxy_mode);
112
113	kernel_receive(pdu);
114
115	len = pdu_ahs_length(pdu);
116	if (len > 0)
117		log_errx(1, "protocol error: non-empty AHS");
118
119	len = pdu_data_segment_length(pdu);
120	assert(len <= MAX_DATA_SEGMENT_LENGTH);
121	pdu->pdu_data_len = len;
122}
123
124static void
125pdu_send_proxy(struct pdu *pdu)
126{
127
128	assert(proxy_mode);
129
130	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
131	kernel_send(pdu);
132}
133
134#endif /* ICL_KERNEL_PROXY */
135
136static size_t
137pdu_padding(const struct pdu *pdu)
138{
139
140	if ((pdu->pdu_data_len % 4) != 0)
141		return (4 - (pdu->pdu_data_len % 4));
142
143	return (0);
144}
145
146static void
147pdu_read(int fd, char *data, size_t len)
148{
149	ssize_t ret;
150
151	while (len > 0) {
152		ret = read(fd, data, len);
153		if (ret < 0) {
154			if (timed_out())
155				log_errx(1, "exiting due to timeout");
156			log_err(1, "read");
157		} else if (ret == 0)
158			log_errx(1, "read: connection lost");
159		len -= ret;
160		data += ret;
161	}
162}
163
164void
165pdu_receive(struct pdu *pdu)
166{
167	size_t len, padding;
168	char dummy[4];
169
170#ifdef ICL_KERNEL_PROXY
171	if (proxy_mode)
172		return (pdu_receive_proxy(pdu));
173#endif
174
175	assert(proxy_mode == false);
176
177	pdu_read(pdu->pdu_connection->conn_socket,
178	    (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs));
179
180	len = pdu_ahs_length(pdu);
181	if (len > 0)
182		log_errx(1, "protocol error: non-empty AHS");
183
184	len = pdu_data_segment_length(pdu);
185	if (len > 0) {
186		if (len > MAX_DATA_SEGMENT_LENGTH) {
187			log_errx(1, "protocol error: received PDU "
188			    "with DataSegmentLength exceeding %d",
189			    MAX_DATA_SEGMENT_LENGTH);
190		}
191
192		pdu->pdu_data_len = len;
193		pdu->pdu_data = malloc(len);
194		if (pdu->pdu_data == NULL)
195			log_err(1, "malloc");
196
197		pdu_read(pdu->pdu_connection->conn_socket,
198		    (char *)pdu->pdu_data, pdu->pdu_data_len);
199
200		padding = pdu_padding(pdu);
201		if (padding != 0) {
202			assert(padding < sizeof(dummy));
203			pdu_read(pdu->pdu_connection->conn_socket,
204			    (char *)dummy, padding);
205		}
206	}
207}
208
209void
210pdu_send(struct pdu *pdu)
211{
212	ssize_t ret, total_len;
213	size_t padding;
214	uint32_t zero = 0;
215	struct iovec iov[3];
216	int iovcnt;
217
218#ifdef ICL_KERNEL_PROXY
219	if (proxy_mode)
220		return (pdu_send_proxy(pdu));
221#endif
222
223	assert(proxy_mode == false);
224
225	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
226	iov[0].iov_base = pdu->pdu_bhs;
227	iov[0].iov_len = sizeof(*pdu->pdu_bhs);
228	total_len = iov[0].iov_len;
229	iovcnt = 1;
230
231	if (pdu->pdu_data_len > 0) {
232		iov[1].iov_base = pdu->pdu_data;
233		iov[1].iov_len = pdu->pdu_data_len;
234		total_len += iov[1].iov_len;
235		iovcnt = 2;
236
237		padding = pdu_padding(pdu);
238		if (padding > 0) {
239			assert(padding < sizeof(zero));
240			iov[2].iov_base = &zero;
241			iov[2].iov_len = padding;
242			total_len += iov[2].iov_len;
243			iovcnt = 3;
244		}
245	}
246
247	ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt);
248	if (ret < 0) {
249		if (timed_out())
250			log_errx(1, "exiting due to timeout");
251		log_err(1, "writev");
252	}
253	if (ret != total_len)
254		log_errx(1, "short write");
255}
256
257void
258pdu_delete(struct pdu *pdu)
259{
260
261	free(pdu->pdu_data);
262	free(pdu->pdu_bhs);
263	free(pdu);
264}
265