blob: eda3279659d417636d9eb6305743f4dc689b8a43 [file] [log] [blame]
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -08001/*
2 *
Craig Tiller6169d5f2016-03-31 07:46:18 -07003 * Copyright 2015, Google Inc.
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -08004 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
murgatroid9954070892016-08-08 17:01:18 -070034#include "src/core/lib/iomgr/port.h"
Craig Tiller0c0b60c2015-01-21 15:49:28 -080035
murgatroid99623dd4f2016-08-08 17:31:27 -070036#ifdef GRPC_POSIX_SOCKET
Craig Tiller0c0b60c2015-01-21 15:49:28 -080037
Craig Tiller9533d042016-03-25 17:11:06 -070038#include "src/core/lib/iomgr/tcp_client.h"
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080039
40#include <errno.h>
nnoble0c475f02014-12-05 15:37:39 -080041#include <netinet/in.h>
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080042#include <string.h>
43#include <unistd.h>
44
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080045#include <grpc/support/alloc.h>
46#include <grpc/support/log.h>
Masood Malekghassemi701af602015-06-03 15:01:17 -070047#include <grpc/support/string_util.h>
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080048#include <grpc/support/time.h>
49
Craig Tiller8a034482016-03-28 16:09:04 -070050#include "src/core/lib/iomgr/ev_posix.h"
Craig Tiller9533d042016-03-25 17:11:06 -070051#include "src/core/lib/iomgr/iomgr_posix.h"
Craig Tiller9533d042016-03-25 17:11:06 -070052#include "src/core/lib/iomgr/sockaddr_utils.h"
53#include "src/core/lib/iomgr/socket_utils_posix.h"
54#include "src/core/lib/iomgr/tcp_posix.h"
55#include "src/core/lib/iomgr/timer.h"
56#include "src/core/lib/iomgr/unix_sockets_posix.h"
57#include "src/core/lib/support/string.h"
Craig Tillerc46beaa2016-02-24 09:17:19 -080058
Craig Tiller5e53ddb2015-09-16 10:53:15 -070059extern int grpc_tcp_trace;
60
Craig Tillera82950e2015-09-22 12:33:20 -070061typedef struct {
ctiller58393c22015-01-07 14:03:30 -080062 gpr_mu mu;
ctiller18b49ab2014-12-09 14:39:16 -080063 grpc_fd *fd;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080064 gpr_timespec deadline;
David Garcia Quintasf747bbc2015-10-04 23:09:47 -070065 grpc_timer alarm;
ctiller58393c22015-01-07 14:03:30 -080066 int refs;
Craig Tiller33825112015-09-18 07:44:19 -070067 grpc_closure write_closure;
Craig Tillerb49736822015-06-30 08:15:08 -070068 grpc_pollset_set *interested_parties;
Craig Tiller1b22b9d2015-07-20 13:42:22 -070069 char *addr_str;
Craig Tillerd1bec032015-09-18 17:29:00 -070070 grpc_endpoint **ep;
71 grpc_closure *closure;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080072} async_connect;
73
murgatroid997871f732016-09-23 13:49:05 -070074static grpc_error *prepare_socket(const grpc_resolved_address *addr, int fd) {
Craig Tiller27f59af2016-04-28 14:19:48 -070075 grpc_error *err = GRPC_ERROR_NONE;
76
Craig Tiller80384bd2016-05-06 16:12:31 -070077 GPR_ASSERT(fd >= 0);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080078
Craig Tiller4f1d0f32016-05-06 17:12:37 -070079 err = grpc_set_socket_nonblocking(fd, 1);
80 if (err != GRPC_ERROR_NONE) goto error;
81 err = grpc_set_socket_cloexec(fd, 1);
82 if (err != GRPC_ERROR_NONE) goto error;
83 if (!grpc_is_unix_socket(addr)) {
84 err = grpc_set_socket_low_latency(fd, 1);
85 if (err != GRPC_ERROR_NONE) goto error;
Craig Tillera82950e2015-09-22 12:33:20 -070086 }
Craig Tiller4f1d0f32016-05-06 17:12:37 -070087 err = grpc_set_socket_no_sigpipe_if_possible(fd);
88 if (err != GRPC_ERROR_NONE) goto error;
Craig Tiller27f59af2016-04-28 14:19:48 -070089 goto done;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080090
91error:
Craig Tillera82950e2015-09-22 12:33:20 -070092 if (fd >= 0) {
93 close(fd);
94 }
Craig Tiller27f59af2016-04-28 14:19:48 -070095done:
96 return err;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080097}
98
Craig Tillerc027e772016-05-03 16:27:00 -070099static void tc_on_alarm(grpc_exec_ctx *exec_ctx, void *acp, grpc_error *error) {
ctiller58393c22015-01-07 14:03:30 -0800100 int done;
101 async_connect *ac = acp;
Craig Tillera82950e2015-09-22 12:33:20 -0700102 if (grpc_tcp_trace) {
Craig Tillerc027e772016-05-03 16:27:00 -0700103 const char *str = grpc_error_string(error);
104 gpr_log(GPR_DEBUG, "CLIENT_CONNECT: %s: on_alarm: error=%s", ac->addr_str,
105 str);
106 grpc_error_free_string(str);
Craig Tillera82950e2015-09-22 12:33:20 -0700107 }
108 gpr_mu_lock(&ac->mu);
109 if (ac->fd != NULL) {
110 grpc_fd_shutdown(exec_ctx, ac->fd);
111 }
ctiller58393c22015-01-07 14:03:30 -0800112 done = (--ac->refs == 0);
Craig Tillera82950e2015-09-22 12:33:20 -0700113 gpr_mu_unlock(&ac->mu);
114 if (done) {
115 gpr_mu_destroy(&ac->mu);
116 gpr_free(ac->addr_str);
117 gpr_free(ac);
118 }
ctiller58393c22015-01-07 14:03:30 -0800119}
120
Craig Tillerc027e772016-05-03 16:27:00 -0700121static void on_writable(grpc_exec_ctx *exec_ctx, void *acp, grpc_error *error) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800122 async_connect *ac = acp;
123 int so_error = 0;
124 socklen_t so_error_size;
125 int err;
ctiller58393c22015-01-07 14:03:30 -0800126 int done;
Craig Tillerd1bec032015-09-18 17:29:00 -0700127 grpc_endpoint **ep = ac->ep;
128 grpc_closure *closure = ac->closure;
Craig Tiller5553eb32015-07-21 12:28:56 -0700129 grpc_fd *fd;
130
Craig Tiller82c63eb2016-05-10 15:28:01 -0700131 GRPC_ERROR_REF(error);
132
Craig Tillera82950e2015-09-22 12:33:20 -0700133 if (grpc_tcp_trace) {
Craig Tillerc027e772016-05-03 16:27:00 -0700134 const char *str = grpc_error_string(error);
135 gpr_log(GPR_DEBUG, "CLIENT_CONNECT: %s: on_writable: error=%s",
136 ac->addr_str, str);
137 grpc_error_free_string(str);
Craig Tillera82950e2015-09-22 12:33:20 -0700138 }
Craig Tiller5e53ddb2015-09-16 10:53:15 -0700139
Craig Tillera82950e2015-09-22 12:33:20 -0700140 gpr_mu_lock(&ac->mu);
141 GPR_ASSERT(ac->fd);
Craig Tiller5553eb32015-07-21 12:28:56 -0700142 fd = ac->fd;
143 ac->fd = NULL;
Craig Tillera82950e2015-09-22 12:33:20 -0700144 gpr_mu_unlock(&ac->mu);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800145
David Garcia Quintasf747bbc2015-10-04 23:09:47 -0700146 grpc_timer_cancel(exec_ctx, &ac->alarm);
Craig Tiller26205362015-07-21 08:21:57 -0700147
Craig Tillera82950e2015-09-22 12:33:20 -0700148 gpr_mu_lock(&ac->mu);
Craig Tillerc027e772016-05-03 16:27:00 -0700149 if (error == GRPC_ERROR_NONE) {
Craig Tillera82950e2015-09-22 12:33:20 -0700150 do {
151 so_error_size = sizeof(so_error);
Craig Tiller0a8a0172016-02-25 07:36:27 -0800152 err = getsockopt(grpc_fd_wrapped_fd(fd), SOL_SOCKET, SO_ERROR, &so_error,
153 &so_error_size);
Craig Tillera82950e2015-09-22 12:33:20 -0700154 } while (err < 0 && errno == EINTR);
155 if (err < 0) {
Craig Tillerc027e772016-05-03 16:27:00 -0700156 error = GRPC_OS_ERROR(errno, "getsockopt");
Craig Tillera82950e2015-09-22 12:33:20 -0700157 goto finish;
158 } else if (so_error != 0) {
159 if (so_error == ENOBUFS) {
160 /* We will get one of these errors if we have run out of
161 memory in the kernel for the data structures allocated
162 when you connect a socket. If this happens it is very
163 likely that if we wait a little bit then try again the
164 connection will work (since other programs or this
165 program will close their network connections and free up
166 memory). This does _not_ indicate that there is anything
167 wrong with the server we are connecting to, this is a
168 local problem.
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800169
Craig Tillera82950e2015-09-22 12:33:20 -0700170 If you are looking at this code, then chances are that
171 your program or another program on the same computer
172 opened too many network connections. The "easy" fix:
173 don't do that! */
174 gpr_log(GPR_ERROR, "kernel out of buffers");
175 gpr_mu_unlock(&ac->mu);
176 grpc_fd_notify_on_write(exec_ctx, fd, &ac->write_closure);
177 return;
178 } else {
179 switch (so_error) {
180 case ECONNREFUSED:
Craig Tiller27f59af2016-04-28 14:19:48 -0700181 error = grpc_error_set_int(error, GRPC_ERROR_INT_ERRNO, errno);
182 error = grpc_error_set_str(error, GRPC_ERROR_STR_OS_ERROR,
183 "Connection refused");
Craig Tillera82950e2015-09-22 12:33:20 -0700184 break;
185 default:
Craig Tillerc027e772016-05-03 16:27:00 -0700186 error = GRPC_OS_ERROR(errno, "getsockopt(SO_ERROR)");
Craig Tillera82950e2015-09-22 12:33:20 -0700187 break;
188 }
189 goto finish;
190 }
191 } else {
192 grpc_pollset_set_del_fd(exec_ctx, ac->interested_parties, fd);
193 *ep = grpc_tcp_create(fd, GRPC_TCP_DEFAULT_READ_SLICE_SIZE, ac->addr_str);
194 fd = NULL;
ctiller58393c22015-01-07 14:03:30 -0800195 goto finish;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800196 }
Craig Tillera82950e2015-09-22 12:33:20 -0700197 } else {
Craig Tiller27f59af2016-04-28 14:19:48 -0700198 error =
199 grpc_error_set_str(error, GRPC_ERROR_STR_OS_ERROR, "Timeout occurred");
Craig Tillera82950e2015-09-22 12:33:20 -0700200 goto finish;
201 }
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800202
yang-gb063c872015-10-07 11:40:13 -0700203 GPR_UNREACHABLE_CODE(return );
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800204
ctiller58393c22015-01-07 14:03:30 -0800205finish:
Craig Tillera82950e2015-09-22 12:33:20 -0700206 if (fd != NULL) {
207 grpc_pollset_set_del_fd(exec_ctx, ac->interested_parties, fd);
yang-g5d850372015-12-01 10:32:28 -0800208 grpc_fd_orphan(exec_ctx, fd, NULL, NULL, "tcp_client_orphan");
Craig Tillera82950e2015-09-22 12:33:20 -0700209 fd = NULL;
210 }
ctiller58393c22015-01-07 14:03:30 -0800211 done = (--ac->refs == 0);
Craig Tillera82950e2015-09-22 12:33:20 -0700212 gpr_mu_unlock(&ac->mu);
Craig Tiller27f59af2016-04-28 14:19:48 -0700213 if (error != GRPC_ERROR_NONE) {
214 error = grpc_error_set_str(error, GRPC_ERROR_STR_DESCRIPTION,
215 "Failed to connect to remote host");
216 error =
217 grpc_error_set_str(error, GRPC_ERROR_STR_TARGET_ADDRESS, ac->addr_str);
218 }
Craig Tillera82950e2015-09-22 12:33:20 -0700219 if (done) {
220 gpr_mu_destroy(&ac->mu);
221 gpr_free(ac->addr_str);
222 gpr_free(ac);
223 }
Craig Tiller332f1b32016-05-24 13:21:21 -0700224 grpc_exec_ctx_sched(exec_ctx, closure, error, NULL);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800225}
226
Craig Tillere6282612016-04-13 14:14:34 -0700227static void tcp_client_connect_impl(grpc_exec_ctx *exec_ctx,
228 grpc_closure *closure, grpc_endpoint **ep,
229 grpc_pollset_set *interested_parties,
murgatroid997871f732016-09-23 13:49:05 -0700230 const grpc_resolved_address *addr,
231 gpr_timespec deadline) {
nnoble0c475f02014-12-05 15:37:39 -0800232 int fd;
233 grpc_dualstack_mode dsmode;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800234 int err;
235 async_connect *ac;
murgatroid997871f732016-09-23 13:49:05 -0700236 grpc_resolved_address addr6_v4mapped;
237 grpc_resolved_address addr4_copy;
Craig Tiller9bcc7512015-05-11 14:59:48 -0700238 grpc_fd *fdobj;
Craig Tillerfa275a92015-06-01 13:55:54 -0700239 char *name;
240 char *addr_str;
Craig Tiller27f59af2016-04-28 14:19:48 -0700241 grpc_error *error;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800242
Craig Tillerd1bec032015-09-18 17:29:00 -0700243 *ep = NULL;
244
nnoble0c475f02014-12-05 15:37:39 -0800245 /* Use dualstack sockets where available. */
Craig Tillera82950e2015-09-22 12:33:20 -0700246 if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) {
murgatroid997871f732016-09-23 13:49:05 -0700247 addr = &addr6_v4mapped;
Craig Tillera82950e2015-09-22 12:33:20 -0700248 }
nnoble0c475f02014-12-05 15:37:39 -0800249
Craig Tiller27f59af2016-04-28 14:19:48 -0700250 error = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode, &fd);
251 if (error != GRPC_ERROR_NONE) {
Craig Tiller332f1b32016-05-24 13:21:21 -0700252 grpc_exec_ctx_sched(exec_ctx, closure, error, NULL);
Craig Tiller27f59af2016-04-28 14:19:48 -0700253 return;
Craig Tillera82950e2015-09-22 12:33:20 -0700254 }
255 if (dsmode == GRPC_DSMODE_IPV4) {
256 /* If we got an AF_INET socket, map the address back to IPv4. */
257 GPR_ASSERT(grpc_sockaddr_is_v4mapped(addr, &addr4_copy));
murgatroid997871f732016-09-23 13:49:05 -0700258 addr = &addr4_copy;
Craig Tillera82950e2015-09-22 12:33:20 -0700259 }
Craig Tiller27f59af2016-04-28 14:19:48 -0700260 if ((error = prepare_socket(addr, fd)) != GRPC_ERROR_NONE) {
Craig Tiller332f1b32016-05-24 13:21:21 -0700261 grpc_exec_ctx_sched(exec_ctx, closure, error, NULL);
Craig Tillera82950e2015-09-22 12:33:20 -0700262 return;
263 }
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800264
Craig Tillera82950e2015-09-22 12:33:20 -0700265 do {
murgatroid997871f732016-09-23 13:49:05 -0700266 GPR_ASSERT(addr->len < ~(socklen_t)0);
267 err = connect(fd, (const struct sockaddr *)addr->addr, (socklen_t)addr->len);
Craig Tillera82950e2015-09-22 12:33:20 -0700268 } while (err < 0 && errno == EINTR);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800269
Craig Tillera82950e2015-09-22 12:33:20 -0700270 addr_str = grpc_sockaddr_to_uri(addr);
271 gpr_asprintf(&name, "tcp-client:%s", addr_str);
Craig Tillerfa275a92015-06-01 13:55:54 -0700272
Craig Tillera82950e2015-09-22 12:33:20 -0700273 fdobj = grpc_fd_create(fd, name);
Craig Tiller9bcc7512015-05-11 14:59:48 -0700274
Craig Tillera82950e2015-09-22 12:33:20 -0700275 if (err >= 0) {
276 *ep = grpc_tcp_create(fdobj, GRPC_TCP_DEFAULT_READ_SLICE_SIZE, addr_str);
Craig Tiller332f1b32016-05-24 13:21:21 -0700277 grpc_exec_ctx_sched(exec_ctx, closure, GRPC_ERROR_NONE, NULL);
Craig Tillera82950e2015-09-22 12:33:20 -0700278 goto done;
279 }
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800280
Craig Tillera82950e2015-09-22 12:33:20 -0700281 if (errno != EWOULDBLOCK && errno != EINPROGRESS) {
yang-g5d850372015-12-01 10:32:28 -0800282 grpc_fd_orphan(exec_ctx, fdobj, NULL, NULL, "tcp_client_connect_error");
Craig Tiller332f1b32016-05-24 13:21:21 -0700283 grpc_exec_ctx_sched(exec_ctx, closure, GRPC_OS_ERROR(errno, "connect"),
Craig Tiller77c983d2016-05-24 13:23:14 -0700284 NULL);
Craig Tillera82950e2015-09-22 12:33:20 -0700285 goto done;
286 }
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800287
Craig Tillera82950e2015-09-22 12:33:20 -0700288 grpc_pollset_set_add_fd(exec_ctx, interested_parties, fdobj);
Craig Tiller4b678bd2015-06-02 16:12:24 -0700289
Craig Tillera82950e2015-09-22 12:33:20 -0700290 ac = gpr_malloc(sizeof(async_connect));
Craig Tillerd1bec032015-09-18 17:29:00 -0700291 ac->closure = closure;
292 ac->ep = ep;
Craig Tiller9bcc7512015-05-11 14:59:48 -0700293 ac->fd = fdobj;
Craig Tillerb49736822015-06-30 08:15:08 -0700294 ac->interested_parties = interested_parties;
Craig Tiller1b22b9d2015-07-20 13:42:22 -0700295 ac->addr_str = addr_str;
296 addr_str = NULL;
Craig Tillera82950e2015-09-22 12:33:20 -0700297 gpr_mu_init(&ac->mu);
ctiller58393c22015-01-07 14:03:30 -0800298 ac->refs = 2;
Craig Tiller0fcd53c2015-02-18 15:10:53 -0800299 ac->write_closure.cb = on_writable;
300 ac->write_closure.cb_arg = ac;
ctiller58393c22015-01-07 14:03:30 -0800301
Craig Tillera82950e2015-09-22 12:33:20 -0700302 if (grpc_tcp_trace) {
303 gpr_log(GPR_DEBUG, "CLIENT_CONNECT: %s: asynchronously connecting",
304 ac->addr_str);
305 }
Craig Tiller5e53ddb2015-09-16 10:53:15 -0700306
Craig Tillera82950e2015-09-22 12:33:20 -0700307 gpr_mu_lock(&ac->mu);
David Garcia Quintasf747bbc2015-10-04 23:09:47 -0700308 grpc_timer_init(exec_ctx, &ac->alarm,
Craig Tillera82950e2015-09-22 12:33:20 -0700309 gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC),
310 tc_on_alarm, ac, gpr_now(GPR_CLOCK_MONOTONIC));
311 grpc_fd_notify_on_write(exec_ctx, ac->fd, &ac->write_closure);
312 gpr_mu_unlock(&ac->mu);
Craig Tillerfa275a92015-06-01 13:55:54 -0700313
314done:
Craig Tillera82950e2015-09-22 12:33:20 -0700315 gpr_free(name);
316 gpr_free(addr_str);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800317}
Craig Tiller0c0b60c2015-01-21 15:49:28 -0800318
Craig Tillere6282612016-04-13 14:14:34 -0700319// overridden by api_fuzzer.c
320void (*grpc_tcp_client_connect_impl)(
321 grpc_exec_ctx *exec_ctx, grpc_closure *closure, grpc_endpoint **ep,
murgatroid997871f732016-09-23 13:49:05 -0700322 grpc_pollset_set *interested_parties, const grpc_resolved_address *addr,
323 gpr_timespec deadline) = tcp_client_connect_impl;
Craig Tillere6282612016-04-13 14:14:34 -0700324
325void grpc_tcp_client_connect(grpc_exec_ctx *exec_ctx, grpc_closure *closure,
326 grpc_endpoint **ep,
327 grpc_pollset_set *interested_parties,
murgatroid997871f732016-09-23 13:49:05 -0700328 const grpc_resolved_address *addr,
Craig Tillere6282612016-04-13 14:14:34 -0700329 gpr_timespec deadline) {
330 grpc_tcp_client_connect_impl(exec_ctx, closure, ep, interested_parties, addr,
murgatroid997871f732016-09-23 13:49:05 -0700331 deadline);
Craig Tillere6282612016-04-13 14:14:34 -0700332}
333
Craig Tiller190d3602015-02-18 09:23:38 -0800334#endif