1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2017 Nexenta Systems, Inc.
25  */
26 
27 #include <sys/cpuvar.h>
28 #include <sys/types.h>
29 #include <sys/conf.h>
30 #include <sys/file.h>
31 #include <sys/ddi.h>
32 #include <sys/sunddi.h>
33 #include <sys/modctl.h>
34 #include <sys/sysmacros.h>
35 #include <sys/socket.h>
36 #include <sys/strsubr.h>
37 #include <inet/tcp.h>
38 #include <sys/nvpair.h>
39 
40 #include <sys/stmf.h>
41 #include <sys/stmf_ioctl.h>
42 #include <sys/portif.h>
43 #include <sys/idm/idm.h>
44 #include <sys/idm/idm_conn_sm.h>
45 #include <sys/idm/idm_text.h>
46 #include <sys/idm/idm_so.h>
47 
48 #include "iscsit_isns.h"
49 #include "iscsit.h"
50 
51 #define	IPADDRSTRLEN	INET6_ADDRSTRLEN	/* space for ipaddr string */
52 #define	PORTALSTRLEN	(IPADDRSTRLEN+16)	/* add space for :port,tag */
53 
54 void
55 iscsit_text_cmd_fini(iscsit_conn_t *ict);
56 
57 static void
iscsit_bump_ttt(iscsit_conn_t * ict)58 iscsit_bump_ttt(iscsit_conn_t *ict)
59 {
60 	/*
61 	 * Set the target task tag. The value will be zero when
62 	 * the connection is created. Increment it and wrap it
63 	 * back to one if we hit the reserved value.
64 	 *
65 	 * The TTT is fabricated since there is no real task associated
66 	 * with a text request. The idm task range is reused here since
67 	 * no real tasks can be started from a discovery session and
68 	 * thus no conflicts are possible.
69 	 */
70 	if (++ict->ict_text_rsp_ttt == IDM_TASKIDS_MAX)
71 		ict->ict_text_rsp_ttt = 1;
72 }
73 
74 static void
iscsit_text_resp_complete_cb(idm_pdu_t * pdu,idm_status_t status)75 iscsit_text_resp_complete_cb(idm_pdu_t *pdu, idm_status_t status)
76 {
77 	iscsit_conn_t *ict = pdu->isp_private;
78 
79 	idm_pdu_free(pdu);
80 	if (status != IDM_STATUS_SUCCESS) {
81 		/*
82 		 * Could not send the last text response.
83 		 * Clear any state and bump the TTT so subsequent
84 		 * requests will not match.
85 		 */
86 		iscsit_text_cmd_fini(ict);
87 		iscsit_bump_ttt(ict);
88 	}
89 	iscsit_conn_rele(ict);
90 }
91 
92 static void
iscsit_text_reject(idm_pdu_t * req_pdu,uint8_t reason_code)93 iscsit_text_reject(idm_pdu_t *req_pdu, uint8_t reason_code)
94 {
95 	iscsit_conn_t		*ict = req_pdu->isp_ic->ic_handle;
96 
97 	/*
98 	 * A reject means abandoning this text request.
99 	 * Cleanup any state from the request and increment the TTT
100 	 * in case the initiator does not get the reject response
101 	 * and attempts to resume this request.
102 	 */
103 	iscsit_text_cmd_fini(ict);
104 	iscsit_bump_ttt(ict);
105 	iscsit_send_reject(ict, req_pdu, reason_code);
106 	idm_pdu_complete(req_pdu, IDM_STATUS_SUCCESS);
107 
108 }
109 
110 
111 /*
112  * Add individual <TargetAddress=ipaddr> tuple to the nvlist
113  */
114 static void
iscsit_add_portal(struct sockaddr_storage * ss,int tag,nvlist_t * nv_resp)115 iscsit_add_portal(struct sockaddr_storage *ss, int tag, nvlist_t *nv_resp)
116 {
117 	char ipaddr[IPADDRSTRLEN];	/* ip address string */
118 	char ta_value[PORTALSTRLEN];	/* target address value */
119 	struct sockaddr_in *sin;
120 	struct sockaddr_in6 *sin6;
121 
122 	switch (ss->ss_family) {
123 	case AF_INET:
124 		sin = (struct sockaddr_in *)ss;
125 		(void) inet_ntop(AF_INET, &sin->sin_addr, ipaddr,
126 		    sizeof (ipaddr));
127 		(void) snprintf(ta_value, sizeof (ta_value), "%s:%d,%d",
128 		    ipaddr, ntohs(sin->sin_port), tag);
129 		break;
130 	case AF_INET6:
131 		sin6 = (struct sockaddr_in6 *)ss;
132 		(void) inet_ntop(AF_INET6, &sin6->sin6_addr, ipaddr,
133 		    sizeof (ipaddr));
134 		(void) snprintf(ta_value, sizeof (ta_value), "[%s]:%d,%d",
135 		    ipaddr, ntohs(sin6->sin6_port), tag);
136 		break;
137 	default:
138 		ASSERT(0);
139 		return;
140 	}
141 	(void) nvlist_add_string(nv_resp, "TargetAddress", ta_value);
142 }
143 
144 /*
145  * Process the special case of the default portal group.
146  * Network addresses are obtained from the network stack and
147  * require some reformatting.
148  */
149 static void
iscsit_add_default_portals(iscsit_conn_t * ict,idm_addr_list_t * ipaddr_p,nvlist_t * nv_resp)150 iscsit_add_default_portals(iscsit_conn_t *ict, idm_addr_list_t *ipaddr_p,
151     nvlist_t *nv_resp)
152 {
153 	int pass, i;
154 	idm_addr_t *tip;
155 	struct sockaddr_storage ss;
156 	struct sockaddr_in *sin;
157 	struct sockaddr_in6 *sin6;
158 
159 	/*
160 	 * If this request was received on one of the portals,
161 	 * output that portal first. Most initiators will try to
162 	 * connect on the first portal in the SendTargets response.
163 	 * For example, this will avoid the confusing situation of a
164 	 * discovery coming in on an IB interface and the initiator
165 	 * then doing the normal login on an ethernet interface.
166 	 */
167 	sin = (struct sockaddr_in *)&ss;
168 	sin6 = (struct sockaddr_in6 *)&ss;
169 	for (pass = 1; pass <= 2; pass++) {
170 		tip = &ipaddr_p->al_addrs[0];
171 		for (i = 0; i < ipaddr_p->al_out_cnt; i++, tip++) {
172 			/* Convert the address into sockaddr_storage format */
173 			switch (tip->a_addr.i_insize) {
174 			case sizeof (struct in_addr):
175 				sin->sin_family = AF_INET;
176 				sin->sin_port = htons(ISCSI_LISTEN_PORT);
177 				sin->sin_addr = tip->a_addr.i_addr.in4;
178 				break;
179 			case sizeof (struct in6_addr):
180 				sin6->sin6_family = AF_INET6;
181 				sin6->sin6_port = htons(ISCSI_LISTEN_PORT);
182 				sin6->sin6_addr = tip->a_addr.i_addr.in6;
183 				break;
184 			default:
185 				ASSERT(0);
186 				continue;
187 			}
188 			switch (pass) {
189 			case 1:
190 				/*
191 				 * On the first pass, skip portals that
192 				 * do not match the incoming connection.
193 				 */
194 				if (idm_ss_compare(&ss, &ict->ict_ic->ic_laddr,
195 				    B_TRUE, B_TRUE) != 0)
196 					continue;
197 				break;
198 			case 2:
199 				/*
200 				 * On the second pass, process the
201 				 * remaining portals.
202 				 */
203 				if (idm_ss_compare(&ss, &ict->ict_ic->ic_laddr,
204 				    B_TRUE, B_TRUE) == 0)
205 					continue;
206 				break;
207 			}
208 			/*
209 			 * Add portal to the response list.
210 			 * By convention, the default portal group tag == 1
211 			 */
212 			iscsit_add_portal(&ss, 1, nv_resp);
213 		}
214 	}
215 }
216 
217 /*
218  * Process a portal group from the configuration database.
219  */
220 static void
iscsit_add_portals(iscsit_conn_t * ict,iscsit_tpgt_t * tpg_list,nvlist_t * nv_resp)221 iscsit_add_portals(iscsit_conn_t *ict, iscsit_tpgt_t *tpg_list,
222     nvlist_t *nv_resp)
223 {
224 	int pass;
225 	iscsit_portal_t *portal, *next_portal;
226 	iscsit_tpg_t *tpg;
227 	struct sockaddr_storage *ss;
228 
229 	/*
230 	 * As with the default portal group, output the portal used by
231 	 * the incoming request first.
232 	 */
233 	tpg = tpg_list->tpgt_tpg;
234 	for (pass = 1; pass <= 2; pass++) {
235 		for (portal = avl_first(&tpg->tpg_portal_list);
236 		    portal != NULL;
237 		    portal = next_portal) {
238 
239 			next_portal = AVL_NEXT(&tpg->tpg_portal_list, portal);
240 			ss = &portal->portal_addr;
241 			switch (pass) {
242 			case 1:
243 				/*
244 				 * On the first pass, skip portals that
245 				 * do not match the incoming connection.
246 				 */
247 				if (idm_ss_compare(ss, &ict->ict_ic->ic_laddr,
248 				    B_TRUE, B_TRUE) != 0)
249 					continue;
250 				break;
251 			case 2:
252 				/*
253 				 * On the second pass, process the
254 				 * remaining portals.
255 				 */
256 				if (idm_ss_compare(ss, &ict->ict_ic->ic_laddr,
257 				    B_TRUE, B_TRUE) == 0)
258 					continue;
259 				break;
260 			}
261 			/* Add portal to the response list */
262 			iscsit_add_portal(ss, tpg_list->tpgt_tag, nv_resp);
263 		}
264 	}
265 }
266 
267 /*
268  * Process all the portal groups bound to a particular target.
269  */
270 static void
iscsit_add_tpgs(iscsit_conn_t * ict,iscsit_tgt_t * target,idm_addr_list_t * ipaddr_p,nvlist_t * nv_resp)271 iscsit_add_tpgs(iscsit_conn_t *ict, iscsit_tgt_t *target,
272     idm_addr_list_t *ipaddr_p,  nvlist_t *nv_resp)
273 {
274 	iscsit_tpgt_t *tpg_list;
275 
276 	/*
277 	 * Look through the portal groups associated with this target.
278 	 */
279 	mutex_enter(&target->target_mutex);
280 	tpg_list = avl_first(&target->target_tpgt_list);
281 
282 	/* check for the default portal group */
283 	if (tpg_list->tpgt_tpg == iscsit_global.global_default_tpg) {
284 		/*
285 		 * The default portal group is a special case and will
286 		 * return all reasonable interfaces on this node.
287 		 *
288 		 * A target cannot be bound to other portal groups
289 		 * if it is bound to the default portal group.
290 		 */
291 		ASSERT(AVL_NEXT(&target->target_tpgt_list, tpg_list) == NULL);
292 
293 		if (ipaddr_p != NULL) {
294 			/* convert the ip address list to nvlist format */
295 			iscsit_add_default_portals(ict, ipaddr_p, nv_resp);
296 		}
297 		mutex_exit(&target->target_mutex);
298 		return;
299 	}
300 
301 	/*
302 	 * Not the default portal group - process the user defined tpgs
303 	 */
304 	ASSERT(tpg_list != NULL);
305 	while (tpg_list != NULL) {
306 
307 		ASSERT(tpg_list->tpgt_tpg != iscsit_global.global_default_tpg);
308 
309 		/*
310 		 * Found a defined portal group - add each portal address.
311 		 * As with the default portal group, make 2 passes over
312 		 * the addresses in order to output the connection
313 		 * address first.
314 		 */
315 		iscsit_add_portals(ict, tpg_list, nv_resp);
316 
317 		tpg_list = AVL_NEXT(&target->target_tpgt_list, tpg_list);
318 	}
319 	mutex_exit(&target->target_mutex);
320 }
321 
322 #ifdef DEBUG
323 /*
324  * To test with smaller PDUs in order to force multi-PDU responses,
325  * set this value such that: 0 < test_max_len < 8192
326  */
327 uint32_t iscsit_text_max_len = ISCSI_DEFAULT_MAX_RECV_SEG_LEN;
328 #endif
329 
330 /*
331  * Format a text response PDU from the text buffer and send it.
332  */
333 static void
iscsit_send_next_text_response(iscsit_conn_t * ict,idm_pdu_t * rx_pdu)334 iscsit_send_next_text_response(iscsit_conn_t *ict, idm_pdu_t *rx_pdu)
335 {
336 	iscsi_text_hdr_t *th_req = (iscsi_text_hdr_t *)rx_pdu->isp_hdr;
337 	iscsi_text_rsp_hdr_t *th_resp;
338 	idm_pdu_t	*resp;
339 	uint32_t	len, remainder, max_len;
340 	char		*base;
341 	boolean_t	final;
342 
343 	max_len = ISCSI_DEFAULT_MAX_RECV_SEG_LEN;
344 #ifdef DEBUG
345 	if (iscsit_text_max_len > 0 && iscsit_text_max_len < max_len)
346 		max_len = iscsit_text_max_len;
347 #endif
348 	do {
349 		remainder = ict->ict_text_rsp_valid_len - ict->ict_text_rsp_off;
350 		if (remainder <= max_len) {
351 			len = remainder;
352 			final = B_TRUE;
353 		} else {
354 			len = max_len;
355 			final = B_FALSE;
356 		}
357 		/*
358 		 * Allocate a PDU and copy in text response buffer
359 		 */
360 		resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), len);
361 		idm_pdu_init(resp, ict->ict_ic, ict,
362 		    iscsit_text_resp_complete_cb);
363 		/* Advance the StatSN for each Text Response sent */
364 		resp->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
365 		base = ict->ict_text_rsp_buf + ict->ict_text_rsp_off;
366 		bcopy(base, resp->isp_data, len);
367 		/*
368 		 * Fill in the response header
369 		 */
370 		th_resp = (iscsi_text_rsp_hdr_t *)resp->isp_hdr;
371 		bzero(th_resp, sizeof (*th_resp));
372 		th_resp->opcode = ISCSI_OP_TEXT_RSP;
373 		th_resp->itt = th_req->itt;
374 		hton24(th_resp->dlength, len);
375 		if (final) {
376 			th_resp->flags = ISCSI_FLAG_FINAL;
377 			th_resp->ttt = ISCSI_RSVD_TASK_TAG;
378 			kmem_free(ict->ict_text_rsp_buf, ict->ict_text_rsp_len);
379 			ict->ict_text_rsp_buf = NULL;
380 			ict->ict_text_rsp_len = 0;
381 			ict->ict_text_rsp_valid_len = 0;
382 			ict->ict_text_rsp_off = 0;
383 		} else {
384 			th_resp->flags = ISCSI_FLAG_TEXT_CONTINUE;
385 			th_resp->ttt = ict->ict_text_rsp_ttt;
386 			ict->ict_text_rsp_off += len;
387 		}
388 		/* Send the response on its way */
389 		iscsit_conn_hold(ict);
390 		iscsit_pdu_tx(resp);
391 	} while (!final);
392 	/* Free the request pdu */
393 	idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
394 }
395 
396 /*
397  * Clean-up the text buffer if it exists.
398  */
399 void
iscsit_text_cmd_fini(iscsit_conn_t * ict)400 iscsit_text_cmd_fini(iscsit_conn_t *ict)
401 {
402 	if (ict->ict_text_rsp_buf != NULL) {
403 		ASSERT(ict->ict_text_rsp_len != 0);
404 		kmem_free(ict->ict_text_rsp_buf, ict->ict_text_rsp_len);
405 	}
406 	ict->ict_text_rsp_buf = NULL;
407 	ict->ict_text_rsp_len = 0;
408 	ict->ict_text_rsp_valid_len = 0;
409 	ict->ict_text_rsp_off = 0;
410 }
411 
412 /*
413  * Process an iSCSI text command.
414  *
415  * This code only handles the common case of a text command
416  * containing the single tuple SendTargets=All issued during
417  * a discovery session. The request will always arrive in a
418  * single PDU, but the response may span multiple PDUs if the
419  * configuration is large. I.e. many targets and portals.
420  *
421  * The request is checked for correctness and then the response
422  * is generated from the global target into nvlist format. Then
423  * the nvlist is reformatted into idm textbuf format which reflects
424  * the iSCSI defined <name=value> specification. Finally, the
425  * textbuf is sent to the initiator in one or more text response PDUs
426  */
427 void
iscsit_pdu_op_text_cmd(iscsit_conn_t * ict,idm_pdu_t * rx_pdu)428 iscsit_pdu_op_text_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu)
429 {
430 	iscsi_text_hdr_t *th_req = (iscsi_text_hdr_t *)rx_pdu->isp_hdr;
431 	nvlist_t *nv_resp;
432 	char *kv_pair;
433 	int flags;
434 	char *textbuf;
435 	int textbuflen;
436 	int validlen;
437 	int rc;
438 
439 	flags =  th_req->flags;
440 	if ((flags & ISCSI_FLAG_FINAL) != ISCSI_FLAG_FINAL) {
441 		/* Cannot handle multi-PDU requests now */
442 		iscsit_text_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED);
443 		return;
444 	}
445 	if (th_req->ttt != ISCSI_RSVD_TASK_TAG) {
446 		/*
447 		 * This is the initiator acknowledging our last PDU and
448 		 * indicating it is ready for the next PDU in the sequence.
449 		 */
450 		/*
451 		 * There can only be one outstanding text request on a
452 		 * connection. Make sure this one PDU has the current TTT.
453 		 */
454 		/* XXX combine the following 3 checks after testing */
455 		if (th_req->ttt != ict->ict_text_rsp_ttt) {
456 			/* Not part of this sequence */
457 			iscsit_text_reject(rx_pdu,
458 			    ISCSI_REJECT_CMD_NOT_SUPPORTED);
459 			return;
460 		}
461 		/*
462 		 * ITT should match what was saved from first PDU.
463 		 */
464 		if (th_req->itt != ict->ict_text_req_itt) {
465 			/* Not part of this sequence */
466 			iscsit_text_reject(rx_pdu,
467 			    ISCSI_REJECT_CMD_NOT_SUPPORTED);
468 			return;
469 		}
470 		/*
471 		 * Cannot deal with more key/value pairs now.
472 		 */
473 		if (rx_pdu->isp_datalen != 0) {
474 			iscsit_text_reject(rx_pdu,
475 			    ISCSI_REJECT_CMD_NOT_SUPPORTED);
476 			return;
477 		}
478 		iscsit_send_next_text_response(ict, rx_pdu);
479 		return;
480 	}
481 
482 	/*
483 	 * Initiator has started a new text request. Only
484 	 * one can be active at a time, so abandon any previous
485 	 * text request on this connection.
486 	 */
487 	iscsit_text_cmd_fini(ict);
488 
489 	/* Set the target task tag. */
490 	iscsit_bump_ttt(ict);
491 
492 	/* Save the initiator task tag */
493 	ict->ict_text_req_itt = th_req->itt;
494 
495 	/*
496 	 * Make sure this is a proper SendTargets request
497 	 */
498 	textbuf = (char *)rx_pdu->isp_data;
499 	textbuflen = rx_pdu->isp_datalen;
500 	kv_pair = "SendTargets=All";
501 	if (textbuflen >= strlen(kv_pair) &&
502 	    strcmp(kv_pair, textbuf) == 0 &&
503 	    ict->ict_op.op_discovery_session == B_TRUE) {
504 		/*
505 		 * Most common case of SendTargets=All during discovery.
506 		 */
507 		idm_addr_list_t *ipaddr_p;
508 		iscsit_tgt_t *tgt, *ntgt;
509 		int ipsize;
510 
511 
512 		/* Create an nvlist for response */
513 		if (nvlist_alloc(&nv_resp, 0, KM_SLEEP) != 0) {
514 			iscsit_text_reject(rx_pdu,
515 			    ISCSI_REJECT_CMD_NOT_SUPPORTED);
516 			return;
517 		}
518 
519 		/* Get the list of local interface addresses */
520 		ipsize = idm_get_ipaddr(&ipaddr_p);
521 
522 		/* Add targets to the response list */
523 		ISCSIT_GLOBAL_LOCK(RW_READER);
524 		for (tgt = avl_first(&iscsit_global.global_target_list);
525 		    tgt != NULL; tgt = ntgt) {
526 			struct sockaddr_storage v4sa, *sa;
527 			iscsit_tgt_state_t state;
528 			iscsit_portal_t *portal;
529 			iscsit_tpgt_t *tpgt;
530 
531 			ntgt = AVL_NEXT(&iscsit_global.global_target_list, tgt);
532 
533 			/* Only report online and onlining targets */
534 			state = tgt->target_state;
535 			if (state != TS_ONLINING && state != TS_ONLINE &&
536 			    state != TS_STMF_ONLINE)
537 				continue;
538 
539 			/*
540 			 * Report target if:
541 			 * - it is bound to default TPG
542 			 * - one of the addresses of TPGs the target is bound
543 			 *   to matches incoming connection dst address
544 			 */
545 			sa = &ict->ict_ic->ic_laddr;
546 			mutex_enter(&tgt->target_mutex);
547 			tpgt = avl_first(&tgt->target_tpgt_list);
548 			if (!(IS_DEFAULT_TPGT(tpgt))) {
549 				portal = iscsit_tgt_lookup_portal(tgt, sa,
550 				    &tpgt);
551 				if (portal == NULL &&
552 				    iscsit_is_v4_mapped(sa, &v4sa)) {
553 					portal = iscsit_tgt_lookup_portal(tgt,
554 					    &v4sa, &tpgt);
555 				}
556 				if (portal == NULL) {
557 					mutex_exit(&tgt->target_mutex);
558 					continue;
559 				}
560 				iscsit_portal_rele(portal);
561 				iscsit_tpgt_rele(tpgt);
562 			}
563 			mutex_exit(&tgt->target_mutex);
564 
565 			if (nvlist_add_string(nv_resp, "TargetName",
566 			    tgt->target_name) == 0) {
567 				/* Add the portal groups bound to this target */
568 				iscsit_add_tpgs(ict, tgt, ipaddr_p, nv_resp);
569 			}
570 		}
571 		ISCSIT_GLOBAL_UNLOCK();
572 		if (ipsize > 0)
573 			kmem_free(ipaddr_p, ipsize);
574 
575 		/* Convert the response nvlist into an idm text buffer */
576 		textbuf = 0;
577 		textbuflen = 0;
578 		validlen = 0;
579 		rc = idm_nvlist_to_textbuf(nv_resp, &textbuf,
580 		    &textbuflen, &validlen);
581 		nvlist_free(nv_resp);
582 		if (rc != 0) {
583 			if (textbuf && textbuflen)
584 				kmem_free(textbuf, textbuflen);
585 			iscsit_text_reject(rx_pdu,
586 			    ISCSI_REJECT_CMD_NOT_SUPPORTED);
587 			return;
588 		}
589 		ict->ict_text_rsp_buf = textbuf;
590 		ict->ict_text_rsp_len = textbuflen;
591 		ict->ict_text_rsp_valid_len = validlen;
592 		ict->ict_text_rsp_off = 0;
593 		iscsit_send_next_text_response(ict, rx_pdu);
594 	} else {
595 		/*
596 		 * Other cases to handle
597 		 *    Discovery session:
598 		 *	SendTargets=<target_name>
599 		 *    Normal session
600 		 *	SendTargets=<NULL> - assume target name of session
601 		 *    All others
602 		 *	Error
603 		 */
604 		iscsit_text_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED);
605 		return;
606 	}
607 }
608