xref: /openbsd-src/sbin/isakmpd/transport.c (revision 9ddd0770ccfc17714ea3221d02a367d6ba4de8e9)
1*9ddd0770Smortimer /* $OpenBSD: transport.c,v 1.39 2021/01/28 01:18:44 mortimer Exp $	 */
2bcfff259Sniklas /* $EOM: transport.c,v 1.43 2000/10/10 12:36:39 provos Exp $	 */
32040585eSniklas 
42040585eSniklas /*
5758ee5e5Sniklas  * Copyright (c) 1998, 1999 Niklas Hallqvist.  All rights reserved.
6cd6bf844Sho  * Copyright (c) 2001, 2004 H�kan Olsson.  All rights reserved.
72040585eSniklas  *
82040585eSniklas  * Redistribution and use in source and binary forms, with or without
92040585eSniklas  * modification, are permitted provided that the following conditions
102040585eSniklas  * are met:
112040585eSniklas  * 1. Redistributions of source code must retain the above copyright
122040585eSniklas  *    notice, this list of conditions and the following disclaimer.
132040585eSniklas  * 2. Redistributions in binary form must reproduce the above copyright
142040585eSniklas  *    notice, this list of conditions and the following disclaimer in the
152040585eSniklas  *    documentation and/or other materials provided with the distribution.
162040585eSniklas  *
172040585eSniklas  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
182040585eSniklas  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
192040585eSniklas  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
202040585eSniklas  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
212040585eSniklas  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
222040585eSniklas  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
232040585eSniklas  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
242040585eSniklas  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
252040585eSniklas  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
262040585eSniklas  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
272040585eSniklas  */
282040585eSniklas 
292040585eSniklas /*
302040585eSniklas  * This code was written under funding by Ericsson Radio Systems.
312040585eSniklas  */
322040585eSniklas 
332040585eSniklas #include <sys/queue.h>
34349b5519Shshoexer #include <netdb.h>
35349fdaddSho #include <string.h>
362040585eSniklas 
372040585eSniklas #include "conf.h"
382040585eSniklas #include "exchange.h"
392040585eSniklas #include "log.h"
402040585eSniklas #include "message.h"
412040585eSniklas #include "sa.h"
422040585eSniklas #include "timer.h"
432040585eSniklas #include "transport.h"
44cd6bf844Sho #include "virtual.h"
452040585eSniklas 
46893467d6Sniklas /* If no retransmit limit is given, use this as a default.  */
47893467d6Sniklas #define RETRANSMIT_DEFAULT 10
48893467d6Sniklas 
492040585eSniklas LIST_HEAD(transport_method_list, transport_vtbl) transport_method_list;
502040585eSniklas 
51*9ddd0770Smortimer struct transport_list transport_list;
52*9ddd0770Smortimer 
53862799fcSangelos /* Call the reinit function of the various transports.  */
54862799fcSangelos void
transport_reinit(void)55862799fcSangelos transport_reinit(void)
56862799fcSangelos {
57862799fcSangelos 	struct transport_vtbl *method;
58862799fcSangelos 
59862799fcSangelos 	for (method = LIST_FIRST(&transport_method_list); method;
60862799fcSangelos 	    method = LIST_NEXT(method, link))
61cd6bf844Sho 		if (method->reinit)
62862799fcSangelos 			method->reinit();
63862799fcSangelos }
64862799fcSangelos 
652040585eSniklas /* Initialize the transport maintenance module.  */
662040585eSniklas void
transport_init(void)672040585eSniklas transport_init(void)
682040585eSniklas {
692040585eSniklas 	LIST_INIT(&transport_list);
702040585eSniklas 	LIST_INIT(&transport_method_list);
712040585eSniklas }
722040585eSniklas 
732040585eSniklas /* Register another transport T.  */
742040585eSniklas void
transport_setup(struct transport * t,int toplevel)75cd6bf844Sho transport_setup(struct transport *t, int toplevel)
762040585eSniklas {
77dec6ea27Sho 	if (toplevel) {
78dec6ea27Sho 		/* Only the toplevel (virtual) transport has sendqueues.  */
79dec6ea27Sho 		LOG_DBG((LOG_TRANSPORT, 70,
80dec6ea27Sho 		    "transport_setup: virtual transport %p", t));
812040585eSniklas 		TAILQ_INIT(&t->sendq);
82867e2181Sho 		TAILQ_INIT(&t->prio_sendq);
83dec6ea27Sho 		t->refcnt = 0;
84dec6ea27Sho 	} else {
85dec6ea27Sho 		/* udp and udp_encap trp goes into the transport list.  */
86dec6ea27Sho 		LOG_DBG((LOG_TRANSPORT, 70,
87dec6ea27Sho 		    "transport_setup: added %p to transport list", t));
88dec6ea27Sho 		LIST_INSERT_HEAD(&transport_list, t, link);
89dec6ea27Sho 		t->refcnt = 1;
90cd6bf844Sho 	}
91893467d6Sniklas 	t->flags = 0;
92758ee5e5Sniklas }
93758ee5e5Sniklas 
94758ee5e5Sniklas /* Add a referer to transport T.  */
95758ee5e5Sniklas void
transport_reference(struct transport * t)96758ee5e5Sniklas transport_reference(struct transport *t)
97758ee5e5Sniklas {
98758ee5e5Sniklas 	t->refcnt++;
993bf15937Sho 	LOG_DBG((LOG_TRANSPORT, 95,
100758ee5e5Sniklas 	    "transport_reference: transport %p now has %d references", t,
10151ca15aeSniklas 	    t->refcnt));
102758ee5e5Sniklas }
103758ee5e5Sniklas 
104758ee5e5Sniklas /*
105758ee5e5Sniklas  * Remove a referer from transport T, removing all of T when no referers left.
106758ee5e5Sniklas  */
107758ee5e5Sniklas void
transport_release(struct transport * t)108758ee5e5Sniklas transport_release(struct transport *t)
109758ee5e5Sniklas {
1103bf15937Sho 	LOG_DBG((LOG_TRANSPORT, 95,
111758ee5e5Sniklas 	    "transport_release: transport %p had %d references", t,
11251ca15aeSniklas 	    t->refcnt));
113758ee5e5Sniklas 	if (--t->refcnt)
114758ee5e5Sniklas 		return;
115758ee5e5Sniklas 
11651ca15aeSniklas 	LOG_DBG((LOG_TRANSPORT, 70, "transport_release: freeing %p", t));
117758ee5e5Sniklas 	t->vtbl->remove(t);
118758ee5e5Sniklas }
119758ee5e5Sniklas 
120758ee5e5Sniklas void
transport_report(void)121758ee5e5Sniklas transport_report(void)
122758ee5e5Sniklas {
123cd6bf844Sho 	struct virtual_transport *v;
124758ee5e5Sniklas 	struct transport *t;
125758ee5e5Sniklas 	struct message *msg;
126758ee5e5Sniklas 
127fb9475d6Sderaadt 	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) {
12851ca15aeSniklas 		LOG_DBG((LOG_REPORT, 0,
129758ee5e5Sniklas 		    "transport_report: transport %p flags %x refcnt %d", t,
13051ca15aeSniklas 		    t->flags, t->refcnt));
131758ee5e5Sniklas 
132cd6bf844Sho 		/* XXX Report sth on the virtual transport?  */
133758ee5e5Sniklas 		t->vtbl->report(t);
134758ee5e5Sniklas 
135fb9475d6Sderaadt 		/*
136fb9475d6Sderaadt 		 * This is the reason message_dump_raw lives outside
137fb9475d6Sderaadt 		 * message.c.
138fb9475d6Sderaadt 		 */
139cd6bf844Sho 		v = (struct virtual_transport *)t->virtual;
140cd6bf844Sho 		if ((v->encap_is_active && v->encap == t) ||
141cd6bf844Sho 		    (!v->encap_is_active && v->main == t)) {
142cd6bf844Sho 			for (msg = TAILQ_FIRST(&t->virtual->prio_sendq); msg;
143867e2181Sho 			    msg = TAILQ_NEXT(msg, link))
144cd6bf844Sho 				message_dump_raw("udp_report(prio)", msg,
145cd6bf844Sho 				    LOG_REPORT);
146867e2181Sho 
147cd6bf844Sho 			for (msg = TAILQ_FIRST(&t->virtual->sendq); msg;
148cd6bf844Sho 			    msg = TAILQ_NEXT(msg, link))
149cd6bf844Sho 				message_dump_raw("udp_report", msg,
150cd6bf844Sho 				    LOG_REPORT);
151cd6bf844Sho 		}
152758ee5e5Sniklas 	}
1532040585eSniklas }
1542040585eSniklas 
155867e2181Sho int
transport_prio_sendqs_empty(void)156867e2181Sho transport_prio_sendqs_empty(void)
157867e2181Sho {
158867e2181Sho 	struct transport *t;
159867e2181Sho 
160867e2181Sho 	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link))
161cd6bf844Sho 		if (TAILQ_FIRST(&t->virtual->prio_sendq))
162867e2181Sho 			return 0;
163867e2181Sho 	return 1;
164867e2181Sho }
165867e2181Sho 
1662040585eSniklas /* Register another transport method T.  */
1672040585eSniklas void
transport_method_add(struct transport_vtbl * t)1682040585eSniklas transport_method_add(struct transport_vtbl *t)
1692040585eSniklas {
1702040585eSniklas 	LIST_INSERT_HEAD(&transport_method_list, t, link);
1712040585eSniklas }
1722040585eSniklas 
1732040585eSniklas /*
174bbbf0f95Sho  * Build up a file descriptor set FDS with all transport descriptors we want
1752040585eSniklas  * to read from.  Return the number of file descriptors select(2) needs to
1762040585eSniklas  * check in order to cover the ones we setup in here.
1772040585eSniklas  */
1782040585eSniklas int
transport_fd_set(fd_set * fds)1792040585eSniklas transport_fd_set(fd_set * fds)
1802040585eSniklas {
181cd6bf844Sho 	struct transport *t;
1822040585eSniklas 	int	n;
1832040585eSniklas 	int	max = -1;
1842040585eSniklas 
1852040585eSniklas 	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link))
186cd6bf844Sho 		if (t->virtual->flags & TRANSPORT_LISTEN) {
187aa584aacSho 			n = t->vtbl->fd_set(t, fds, 1);
1882040585eSniklas 			if (n > max)
1892040585eSniklas 				max = n;
190cd6bf844Sho 
191cd6bf844Sho 			LOG_DBG((LOG_TRANSPORT, 95, "transport_fd_set: "
192cd6bf844Sho 			    "transport %p (virtual %p) fd %d", t,
193cd6bf844Sho 			    t->virtual, n));
1942040585eSniklas 		}
1952040585eSniklas 	return max + 1;
1962040585eSniklas }
1972040585eSniklas 
1982040585eSniklas /*
199bbbf0f95Sho  * Build up a file descriptor set FDS with all the descriptors belonging to
2002040585eSniklas  * transport where messages are queued for transmittal.  Return the number
2012040585eSniklas  * of file descriptors select(2) needs to check in order to cover the ones
2022040585eSniklas  * we setup in here.
2032040585eSniklas  */
2042040585eSniklas int
transport_pending_wfd_set(fd_set * fds)2052040585eSniklas transport_pending_wfd_set(fd_set * fds)
2062040585eSniklas {
207cd6bf844Sho 	struct transport *t;
2082040585eSniklas 	int	n;
2092040585eSniklas 	int	max = -1;
2102040585eSniklas 
211fb9475d6Sderaadt 	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) {
212cd6bf844Sho 		if (TAILQ_FIRST(&t->virtual->sendq) ||
213cd6bf844Sho 		    TAILQ_FIRST(&t->virtual->prio_sendq)) {
214aa584aacSho 			n = t->vtbl->fd_set(t, fds, 1);
215dec6ea27Sho 			LOG_DBG((LOG_TRANSPORT, 95,
216dec6ea27Sho 			    "transport_pending_wfd_set: "
217cd6bf844Sho 			    "transport %p (virtual %p) fd %d pending", t,
218cd6bf844Sho 			    t->virtual, n));
2192040585eSniklas 			if (n > max)
2202040585eSniklas 				max = n;
2212040585eSniklas 		}
2222040585eSniklas 	}
2232040585eSniklas 	return max + 1;
2242040585eSniklas }
2252040585eSniklas 
2262040585eSniklas /*
2272040585eSniklas  * For each transport with a file descriptor in FDS, try to get an
2282040585eSniklas  * incoming message and start processing it.
2292040585eSniklas  */
2302040585eSniklas void
transport_handle_messages(fd_set * fds)2312040585eSniklas transport_handle_messages(fd_set *fds)
2322040585eSniklas {
2332040585eSniklas 	struct transport *t;
2342040585eSniklas 
235cd6bf844Sho 	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) {
23650eea14cSho 		if ((t->flags & TRANSPORT_LISTEN) &&
237aa584aacSho 		    (*t->vtbl->fd_isset)(t, fds)) {
238aa584aacSho 			(*t->virtual->vtbl->handle_message)(t);
239aa584aacSho 			(*t->vtbl->fd_set)(t, fds, 0);
240cd6bf844Sho 		}
241cd6bf844Sho 	}
2422040585eSniklas }
2432040585eSniklas 
2442040585eSniklas /*
245a6341f1eSniklas  * Send the first queued message on the transports found whose file
246a6341f1eSniklas  * descriptor is in FDS and has messages queued.  Remove the fd bit from
247a6341f1eSniklas  * FDS as soon as one message has been sent on it so other transports
248a6341f1eSniklas  * sharing the socket won't get service without an intervening select
249a6341f1eSniklas  * call.  Perhaps a fairness strategy should be implemented between
250a6341f1eSniklas  * such transports.  Now early transports in the list will potentially
251a6341f1eSniklas  * be favoured to later ones sharing the file descriptor.
2522040585eSniklas  */
2532040585eSniklas void
transport_send_messages(fd_set * fds)2542040585eSniklas transport_send_messages(fd_set * fds)
2552040585eSniklas {
256a6341f1eSniklas 	struct transport *t, *next;
2572040585eSniklas 	struct message *msg;
258a6341f1eSniklas 	struct exchange *exchange;
259349b5519Shshoexer 	struct sockaddr *dst;
2606ee513e5Sjca 	struct timespec expiration;
261a6341f1eSniklas 	int             expiry, ok_to_drop_message;
262349b5519Shshoexer 	char peer[NI_MAXHOST], peersv[NI_MAXSERV];
2632040585eSniklas 
264fb9475d6Sderaadt 	/*
265fb9475d6Sderaadt 	 * Reference all transports first so noone will disappear while in
266fb9475d6Sderaadt 	 * use.
267fb9475d6Sderaadt 	 */
268dec6ea27Sho 	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link))
269cd6bf844Sho 		transport_reference(t->virtual);
270a6341f1eSniklas 
271fb9475d6Sderaadt 	for (t = LIST_FIRST(&transport_list); t; t = LIST_NEXT(t, link)) {
272cd6bf844Sho 		if ((TAILQ_FIRST(&t->virtual->sendq) ||
273cd6bf844Sho 		    TAILQ_FIRST(&t->virtual->prio_sendq)) &&
274aa584aacSho 		    t->vtbl->fd_isset(t, fds)) {
275cd6bf844Sho 			/* Remove fd bit.  */
276aa584aacSho 			t->vtbl->fd_set(t, fds, 0);
277867e2181Sho 
278867e2181Sho 			/* Prefer a message from the prioritized sendq.  */
279cd6bf844Sho 			if (TAILQ_FIRST(&t->virtual->prio_sendq)) {
280cd6bf844Sho 				msg = TAILQ_FIRST(&t->virtual->prio_sendq);
281cd6bf844Sho 				TAILQ_REMOVE(&t->virtual->prio_sendq, msg,
282cd6bf844Sho 				    link);
283fb9475d6Sderaadt 			} else {
284cd6bf844Sho 				msg = TAILQ_FIRST(&t->virtual->sendq);
285cd6bf844Sho 				TAILQ_REMOVE(&t->virtual->sendq, msg, link);
286867e2181Sho 			}
287867e2181Sho 
288893467d6Sniklas 			msg->flags &= ~MSG_IN_TRANSIT;
289a6341f1eSniklas 			exchange = msg->exchange;
290a6341f1eSniklas 			exchange->in_transit = 0;
2912040585eSniklas 
2922040585eSniklas 			/*
29350eea14cSho 			 * We disregard the potential error message here,
29450eea14cSho 			 * hoping that the retransmit will go better.
2952040585eSniklas 			 * XXX Consider a retry/fatal error discriminator.
2962040585eSniklas 			 */
297cd6bf844Sho 			t->virtual->vtbl->send_message(msg, 0);
2982040585eSniklas 			msg->xmits++;
2992040585eSniklas 
300a6341f1eSniklas 			/*
30150eea14cSho 			 * This piece of code has been proven to be quite
302cd6bf844Sho 			 * delicate. Think twice for before altering.
303cd6bf844Sho 			 * Here's an outline:
304cd6bf844Sho 			 *
305cd6bf844Sho 			 * If this message is not the one which finishes an
306cd6bf844Sho 			 * exchange, check if we have reached the number of
307cd6bf844Sho 			 * retransmit before queuing it up for another.
308a6341f1eSniklas 			 *
30950eea14cSho 			 * If it is a finishing message we still may have to
31050eea14cSho 			 * keep it around for an on-demand retransmit when
31150eea14cSho 			 * seeing a duplicate of our peer's previous message.
312a6341f1eSniklas 			 */
313fb9475d6Sderaadt 			if ((msg->flags & MSG_LAST) == 0) {
31420c653ebSyasuoka 				if (msg->flags & MSG_DONTRETRANSMIT)
31520c653ebSyasuoka 					exchange->last_sent = 0;
31620c653ebSyasuoka 				else if (msg->xmits > conf_get_num("General",
31750eea14cSho 				    "retransmits", RETRANSMIT_DEFAULT)) {
318349b5519Shshoexer 					t->virtual->vtbl->get_dst(t->virtual, &dst);
319c506f982Shshoexer 					if (getnameinfo(dst, SA_LEN(dst), peer,
320c506f982Shshoexer 					    sizeof peer, peersv, sizeof peersv,
321349b5519Shshoexer 					    NI_NUMERICHOST | NI_NUMERICSERV)) {
322349b5519Shshoexer 						strlcpy(peer, "<unknown>", sizeof peer);
323349b5519Shshoexer 						strlcpy(peersv, "<?>", sizeof peersv);
324fdf8d522Sho 					}
325349b5519Shshoexer 					log_print("transport_send_messages: "
326349b5519Shshoexer 					    "giving up on exchange %s, no "
327349b5519Shshoexer 					    "response from peer %s:%s",
328349b5519Shshoexer 					    exchange->name ? exchange->name :
329349b5519Shshoexer 					    "<unnamed>", peer, peersv);
330349b5519Shshoexer 
331a6341f1eSniklas 					exchange->last_sent = 0;
332dec6ea27Sho #ifdef notyet
333dec6ea27Sho 					exchange_free(exchange);
334dec6ea27Sho 					exchange = 0;
335dec6ea27Sho #endif
336fb9475d6Sderaadt 				} else {
3376ee513e5Sjca 					clock_gettime(CLOCK_MONOTONIC,
3386ee513e5Sjca 					    &expiration);
3392040585eSniklas 
3402040585eSniklas 					/*
34150eea14cSho 					 * XXX Calculate from round trip
34250eea14cSho 					 * timings and a backoff func.
3432040585eSniklas 					 */
3442040585eSniklas 					expiry = msg->xmits * 2 + 5;
3452040585eSniklas 					expiration.tv_sec += expiry;
34651ca15aeSniklas 					LOG_DBG((LOG_TRANSPORT, 30,
34750eea14cSho 					    "transport_send_messages: "
34850eea14cSho 					    "message %p scheduled for "
34950eea14cSho 					    "retransmission %d in %d secs",
35051ca15aeSniklas 					    msg, msg->xmits, expiry));
3517638e87cSniklas 					if (msg->retrans)
3527638e87cSniklas 						timer_remove_event(msg->retrans);
353a6341f1eSniklas 					msg->retrans
354bcfff259Sniklas 					    = timer_add_event("message_send_expire",
355bcfff259Sniklas 						(void (*) (void *)) message_send_expire,
3562040585eSniklas 						msg, &expiration);
357fb9475d6Sderaadt 					/*
358fb9475d6Sderaadt 					 * If we cannot retransmit, we
359fb9475d6Sderaadt 					 * cannot...
360fb9475d6Sderaadt 					 */
36150eea14cSho 					exchange->last_sent =
36250eea14cSho 					    msg->retrans ? msg : 0;
363a6341f1eSniklas 				}
364fb9475d6Sderaadt 			} else
36550eea14cSho 				exchange->last_sent =
36650eea14cSho 				    exchange->last_received ? msg : 0;
367a6341f1eSniklas 
368a6341f1eSniklas 			/*
36950eea14cSho 			 * If this message is not referred to for later
37050eea14cSho 			 * retransmission it will be ok for us to drop it
37150eea14cSho 			 * after the post-send function. But as the post-send
37250eea14cSho 			 * function may remove the exchange, we need to
37350eea14cSho 			 * remember this fact here.
374a6341f1eSniklas 			 */
375a6341f1eSniklas 			ok_to_drop_message = exchange->last_sent == 0;
376a6341f1eSniklas 
377a6341f1eSniklas 			/*
37850eea14cSho 			 * If this is not a retransmit call post-send
37950eea14cSho 			 * functions that allows parallel work to be done
380cd6bf844Sho 			 * while the network and peer does their share of
381cd6bf844Sho 			 * the job.  Note that a post-send function may take
382cd6bf844Sho 			 * away the exchange we belong to, but only if no
38350eea14cSho 			 * retransmits are possible.
384a6341f1eSniklas 			 */
385a6341f1eSniklas 			if (msg->xmits == 1)
386a6341f1eSniklas 				message_post_send(msg);
387a6341f1eSniklas 
388a6341f1eSniklas 			if (ok_to_drop_message)
3892040585eSniklas 				message_free(msg);
3902040585eSniklas 		}
391567bb3dbSniklas 	}
392567bb3dbSniklas 
393fb9475d6Sderaadt 	for (t = LIST_FIRST(&transport_list); t; t = next) {
394a6341f1eSniklas 		next = LIST_NEXT(t, link);
395cd6bf844Sho 		transport_release(t->virtual);
3962040585eSniklas 	}
3972040585eSniklas }
3982040585eSniklas 
3992040585eSniklas /*
4002040585eSniklas  * Textual search after the transport method denoted by NAME, then create
4012040585eSniklas  * a transport connected to the peer with address ADDR, given in a transport-
4022040585eSniklas  * specific string format.
4032040585eSniklas  */
4042040585eSniklas struct transport *
transport_create(char * name,char * addr)4052040585eSniklas transport_create(char *name, char *addr)
4062040585eSniklas {
4072040585eSniklas 	struct transport_vtbl *method;
4082040585eSniklas 
4092040585eSniklas 	for (method = LIST_FIRST(&transport_method_list); method;
4102040585eSniklas 	    method = LIST_NEXT(method, link))
4112040585eSniklas 		if (strcmp(method->name, name) == 0)
4122040585eSniklas 			return (*method->create) (addr);
4132040585eSniklas 	return 0;
4142040585eSniklas }
415