1 /* Copyright (c) 2001 Matej Pfajfar.
2 * Copyright (c) 2001-2004, Roger Dingledine.
3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
4 * Copyright (c) 2007-2008, The Tor Project, Inc. */
5 /* See LICENSE for licensing information */
9 * \brief Functions for processing incoming cells.
12 /* In-points to command.c:
14 * - command_process_cell(), called from
15 * connection_or_process_cells_from_inbuf() in connection_or.c
20 /** How many CELL_PADDING cells have we received, ever? */
21 uint64_t stats_n_padding_cells_processed
= 0;
22 /** How many CELL_CREATE cells have we received, ever? */
23 uint64_t stats_n_create_cells_processed
= 0;
24 /** How many CELL_CREATED cells have we received, ever? */
25 uint64_t stats_n_created_cells_processed
= 0;
26 /** How many CELL_RELAY cells have we received, ever? */
27 uint64_t stats_n_relay_cells_processed
= 0;
28 /** How many CELL_DESTROY cells have we received, ever? */
29 uint64_t stats_n_destroy_cells_processed
= 0;
30 /** How many CELL_VERSIONS cells have we received, ever? */
31 uint64_t stats_n_versions_cells_processed
= 0;
32 /** How many CELL_NETINFO cells have we received, ever? */
33 uint64_t stats_n_netinfo_cells_processed
= 0;
35 /* These are the main functions for processing cells */
36 static void command_process_create_cell(cell_t
*cell
, or_connection_t
*conn
);
37 static void command_process_created_cell(cell_t
*cell
, or_connection_t
*conn
);
38 static void command_process_relay_cell(cell_t
*cell
, or_connection_t
*conn
);
39 static void command_process_destroy_cell(cell_t
*cell
, or_connection_t
*conn
);
40 static void command_process_versions_cell(var_cell_t
*cell
,
41 or_connection_t
*conn
);
42 static void command_process_netinfo_cell(cell_t
*cell
, or_connection_t
*conn
);
44 #ifdef KEEP_TIMING_STATS
45 /** This is a wrapper function around the actual function that processes the
46 * <b>cell</b> that just arrived on <b>conn</b>. Increment <b>*time</b>
47 * by the number of microseconds used by the call to <b>*func(cell, conn)</b>.
50 command_time_process_cell(cell_t
*cell
, or_connection_t
*conn
, int *time
,
51 void (*func
)(cell_t
*, or_connection_t
*))
53 struct timeval start
, end
;
56 tor_gettimeofday(&start
);
60 tor_gettimeofday(&end
);
61 time_passed
= tv_udiff(&start
, &end
) ;
63 if (time_passed
> 10000) { /* more than 10ms */
64 log_debug(LD_OR
,"That call just took %ld ms.",time_passed
/1000);
66 if (time_passed
< 0) {
67 log_info(LD_GENERAL
,"That call took us back in time!");
74 /** Process a <b>cell</b> that was just received on <b>conn</b>. Keep internal
75 * statistics about how many of each cell we've processed so far
76 * this second, and the total number of microseconds it took to
77 * process each type of cell.
80 command_process_cell(cell_t
*cell
, or_connection_t
*conn
)
82 int handshaking
= (conn
->_base
.state
== OR_CONN_STATE_OR_HANDSHAKING
);
83 #ifdef KEEP_TIMING_STATS
84 /* how many of each cell have we seen so far this second? needs better
86 static int num_create
=0, num_created
=0, num_relay
=0, num_destroy
=0;
87 /* how long has it taken to process each type of cell? */
88 static int create_time
=0, created_time
=0, relay_time
=0, destroy_time
=0;
89 static time_t current_second
= 0; /* from previous calls to time */
91 time_t now
= time(NULL
);
93 if (now
> current_second
) { /* the second has rolled over */
96 "At end of second: %d creates (%d ms), %d createds (%d ms), "
97 "%d relays (%d ms), %d destroys (%d ms)",
98 num_create
, create_time
/1000,
99 num_created
, created_time
/1000,
100 num_relay
, relay_time
/1000,
101 num_destroy
, destroy_time
/1000);
104 num_create
= num_created
= num_relay
= num_destroy
= 0;
105 create_time
= created_time
= relay_time
= destroy_time
= 0;
107 /* remember which second it is, for next time */
108 current_second
= now
;
112 #ifdef KEEP_TIMING_STATS
113 #define PROCESS_CELL(tp, cl, cn) STMT_BEGIN { \
115 command_time_process_cell(cl, cn, & tp ## time , \
116 command_process_ ## tp ## _cell); \
119 #define PROCESS_CELL(tp, cl, cn) command_process_ ## tp ## _cell(cl, cn)
122 /* Reject all but VERSIONS and NETINFO when handshaking. */
123 if (handshaking
&& cell
->command
!= CELL_VERSIONS
&&
124 cell
->command
!= CELL_NETINFO
)
127 switch (cell
->command
) {
129 ++stats_n_padding_cells_processed
;
133 case CELL_CREATE_FAST
:
134 ++stats_n_create_cells_processed
;
135 PROCESS_CELL(create
, cell
, conn
);
138 case CELL_CREATED_FAST
:
139 ++stats_n_created_cells_processed
;
140 PROCESS_CELL(created
, cell
, conn
);
143 case CELL_RELAY_EARLY
:
144 ++stats_n_relay_cells_processed
;
145 PROCESS_CELL(relay
, cell
, conn
);
148 ++stats_n_destroy_cells_processed
;
149 PROCESS_CELL(destroy
, cell
, conn
);
152 tor_fragile_assert();
155 ++stats_n_netinfo_cells_processed
;
156 PROCESS_CELL(netinfo
, cell
, conn
);
159 log_fn(LOG_INFO
, LD_PROTOCOL
,
160 "Cell of unknown type (%d) received. Dropping.", cell
->command
);
165 /** Process a <b>cell</b> that was just received on <b>conn</b>. Keep internal
166 * statistics about how many of each cell we've processed so far
167 * this second, and the total number of microseconds it took to
168 * process each type of cell.
171 command_process_var_cell(var_cell_t
*cell
, or_connection_t
*conn
)
173 #ifdef KEEP_TIMING_STATS
174 /* how many of each cell have we seen so far this second? needs better
176 static int num_versions
=0, num_cert
=0;
178 time_t now
= time(NULL
);
180 if (now
> current_second
) { /* the second has rolled over */
183 "At end of second: %d versions (%d ms), %d cert (%d ms)",
184 num_versions
, versions_time
/1000,
185 cert
, cert_time
/1000);
187 num_versions
= num_cert
= 0;
188 versions_time
= cert_time
= 0;
190 /* remember which second it is, for next time */
191 current_second
= now
;
195 /* reject all when not handshaking. */
196 if (conn
->_base
.state
!= OR_CONN_STATE_OR_HANDSHAKING
)
199 switch (cell
->command
) {
201 ++stats_n_versions_cells_processed
;
202 PROCESS_CELL(versions
, cell
, conn
);
206 "Variable-length cell of unknown type (%d) received.",
208 tor_fragile_assert();
213 /** Process a 'create' <b>cell</b> that just arrived from <b>conn</b>. Make a
214 * new circuit with the p_circ_id specified in cell. Put the circuit in state
215 * onionskin_pending, and pass the onionskin to the cpuworker. Circ will get
216 * picked up again when the cpuworker finishes decrypting it.
219 command_process_create_cell(cell_t
*cell
, or_connection_t
*conn
)
224 if (we_are_hibernating()) {
226 "Received create cell but we're shutting down. Sending back "
228 connection_or_send_destroy(cell
->circ_id
, conn
,
229 END_CIRC_REASON_HIBERNATING
);
233 if (!server_mode(get_options())) {
234 log_fn(LOG_PROTOCOL_WARN
, LD_PROTOCOL
,
235 "Received create cell (type %d) from %s:%d, but we're a client. "
236 "Sending back a destroy.",
237 (int)cell
->command
, conn
->_base
.address
, conn
->_base
.port
);
238 connection_or_send_destroy(cell
->circ_id
, conn
,
239 END_CIRC_REASON_TORPROTOCOL
);
243 /* If the high bit of the circuit ID is not as expected, close the
245 id_is_high
= cell
->circ_id
& (1<<15);
246 if ((id_is_high
&& conn
->circ_id_type
== CIRC_ID_TYPE_HIGHER
) ||
247 (!id_is_high
&& conn
->circ_id_type
== CIRC_ID_TYPE_LOWER
)) {
248 log_fn(LOG_PROTOCOL_WARN
, LD_PROTOCOL
,
249 "Received create cell with unexpected circ_id %d. Closing.",
251 connection_or_send_destroy(cell
->circ_id
, conn
,
252 END_CIRC_REASON_TORPROTOCOL
);
256 if (circuit_id_in_use_on_orconn(cell
->circ_id
, conn
)) {
257 routerinfo_t
*router
= router_get_by_digest(conn
->identity_digest
);
258 log_fn(LOG_PROTOCOL_WARN
, LD_PROTOCOL
,
259 "Received CREATE cell (circID %d) for known circ. "
260 "Dropping (age %d).",
261 cell
->circ_id
, (int)(time(NULL
) - conn
->_base
.timestamp_created
));
263 log_fn(LOG_PROTOCOL_WARN
, LD_PROTOCOL
,
264 "Details: nickname \"%s\", platform %s.",
265 router
->nickname
, escaped(router
->platform
));
269 circ
= or_circuit_new(cell
->circ_id
, conn
);
270 circ
->_base
.purpose
= CIRCUIT_PURPOSE_OR
;
271 circuit_set_state(TO_CIRCUIT(circ
), CIRCUIT_STATE_ONIONSKIN_PENDING
);
272 if (cell
->command
== CELL_CREATE
) {
273 char *onionskin
= tor_malloc(ONIONSKIN_CHALLENGE_LEN
);
274 memcpy(onionskin
, cell
->payload
, ONIONSKIN_CHALLENGE_LEN
);
276 /* hand it off to the cpuworkers, and then return. */
277 if (assign_onionskin_to_cpuworker(NULL
, circ
, onionskin
) < 0) {
278 log_warn(LD_GENERAL
,"Failed to hand off onionskin. Closing.");
279 circuit_mark_for_close(TO_CIRCUIT(circ
), END_CIRC_REASON_INTERNAL
);
282 log_debug(LD_OR
,"success: handed off onionskin.");
284 /* This is a CREATE_FAST cell; we can handle it immediately without using
286 char keys
[CPATH_KEY_MATERIAL_LEN
];
287 char reply
[DIGEST_LEN
*2];
288 tor_assert(cell
->command
== CELL_CREATE_FAST
);
289 if (fast_server_handshake(cell
->payload
, reply
, keys
, sizeof(keys
))<0) {
290 log_warn(LD_OR
,"Failed to generate key material. Closing.");
291 circuit_mark_for_close(TO_CIRCUIT(circ
), END_CIRC_REASON_INTERNAL
);
294 if (onionskin_answer(circ
, CELL_CREATED_FAST
, reply
, keys
)<0) {
295 log_warn(LD_OR
,"Failed to reply to CREATE_FAST cell. Closing.");
296 circuit_mark_for_close(TO_CIRCUIT(circ
), END_CIRC_REASON_INTERNAL
);
302 /** Process a 'created' <b>cell</b> that just arrived from <b>conn</b>.
304 * that it's intended for. If we're not the origin of the circuit, package
305 * the 'created' cell in an 'extended' relay cell and pass it back. If we
306 * are the origin of the circuit, send it to circuit_finish_handshake() to
307 * finish processing keys, and then call circuit_send_next_onion_skin() to
308 * extend to the next hop in the circuit if necessary.
311 command_process_created_cell(cell_t
*cell
, or_connection_t
*conn
)
315 circ
= circuit_get_by_circid_orconn(cell
->circ_id
, conn
);
319 "(circID %d) unknown circ (probably got a destroy earlier). "
320 "Dropping.", cell
->circ_id
);
324 if (circ
->n_circ_id
!= cell
->circ_id
) {
325 log_fn(LOG_PROTOCOL_WARN
,LD_PROTOCOL
,
326 "got created cell from Tor client? Closing.");
327 circuit_mark_for_close(circ
, END_CIRC_REASON_TORPROTOCOL
);
331 if (CIRCUIT_IS_ORIGIN(circ
)) { /* we're the OP. Handshake this. */
332 origin_circuit_t
*origin_circ
= TO_ORIGIN_CIRCUIT(circ
);
334 log_debug(LD_OR
,"at OP. Finishing handshake.");
335 if ((err_reason
= circuit_finish_handshake(origin_circ
, cell
->command
,
336 cell
->payload
)) < 0) {
337 log_warn(LD_OR
,"circuit_finish_handshake failed.");
338 circuit_mark_for_close(circ
, -err_reason
);
341 log_debug(LD_OR
,"Moving to next skin.");
342 if ((err_reason
= circuit_send_next_onion_skin(origin_circ
)) < 0) {
343 log_info(LD_OR
,"circuit_send_next_onion_skin failed.");
344 /* XXX push this circuit_close lower */
345 circuit_mark_for_close(circ
, -err_reason
);
348 } else { /* pack it into an extended relay cell, and send it. */
350 "Converting created cell to extended relay cell, sending.");
351 relay_send_command_from_edge(0, circ
, RELAY_COMMAND_EXTENDED
,
352 cell
->payload
, ONIONSKIN_REPLY_LEN
,
357 /** Process a 'relay' or 'relay_early' <b>cell</b> that just arrived from
358 * <b>conn</b>. Make sure it came in with a recognized circ_id. Pass it on to
359 * circuit_receive_relay_cell() for actual processing.
362 command_process_relay_cell(cell_t
*cell
, or_connection_t
*conn
)
365 int reason
, direction
;
367 circ
= circuit_get_by_circid_orconn(cell
->circ_id
, conn
);
371 "unknown circuit %d on connection from %s:%d. Dropping.",
372 cell
->circ_id
, conn
->_base
.address
, conn
->_base
.port
);
376 if (circ
->state
== CIRCUIT_STATE_ONIONSKIN_PENDING
) {
377 log_fn(LOG_PROTOCOL_WARN
,LD_PROTOCOL
,"circuit in create_wait. Closing.");
378 circuit_mark_for_close(circ
, END_CIRC_REASON_TORPROTOCOL
);
382 if (CIRCUIT_IS_ORIGIN(circ
)) {
383 /* if we're a relay and treating connections with recent local
384 * traffic better, then this is one of them. */
385 conn
->client_used
= time(NULL
);
388 if (!CIRCUIT_IS_ORIGIN(circ
) &&
389 cell
->circ_id
== TO_OR_CIRCUIT(circ
)->p_circ_id
)
390 direction
= CELL_DIRECTION_OUT
;
392 direction
= CELL_DIRECTION_IN
;
394 /* If we have a relay_early cell, make sure that it's outbound, and we've
395 * gotten no more than MAX_RELAY_EARLY_CELLS_PER_CIRCUIT of them. */
396 if (cell
->command
== CELL_RELAY_EARLY
) {
397 if (direction
== CELL_DIRECTION_IN
) {
398 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
399 "Received an inbound RELAY_EARLY cell on circuit %d from %s:%d."
401 cell
->circ_id
, conn
->_base
.address
, conn
->_base
.port
);
402 circuit_mark_for_close(circ
, END_CIRC_REASON_TORPROTOCOL
);
405 or_circuit_t
*or_circ
= TO_OR_CIRCUIT(circ
);
406 if (or_circ
->remaining_relay_early_cells
== 0) {
407 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
408 "Received too many RELAY_EARLY cells on circ %d from %s:%d."
410 cell
->circ_id
, safe_str(conn
->_base
.address
), conn
->_base
.port
);
411 circuit_mark_for_close(circ
, END_CIRC_REASON_TORPROTOCOL
);
414 --or_circ
->remaining_relay_early_cells
;
418 if ((reason
= circuit_receive_relay_cell(cell
, circ
, direction
)) < 0) {
419 log_fn(LOG_PROTOCOL_WARN
,LD_PROTOCOL
,"circuit_receive_relay_cell "
420 "(%s) failed. Closing.",
421 direction
==CELL_DIRECTION_OUT
?"forward":"backward");
422 circuit_mark_for_close(circ
, -reason
);
426 /** Process a 'destroy' <b>cell</b> that just arrived from
427 * <b>conn</b>. Find the circ that it refers to (if any).
429 * If the circ is in state
430 * onionskin_pending, then call onion_pending_remove() to remove it
431 * from the pending onion list (note that if it's already being
432 * processed by the cpuworker, it won't be in the list anymore; but
433 * when the cpuworker returns it, the circuit will be gone, and the
434 * cpuworker response will be dropped).
436 * Then mark the circuit for close (which marks all edges for close,
437 * and passes the destroy cell onward if necessary).
440 command_process_destroy_cell(cell_t
*cell
, or_connection_t
*conn
)
445 circ
= circuit_get_by_circid_orconn(cell
->circ_id
, conn
);
446 reason
= (uint8_t)cell
->payload
[0];
448 log_info(LD_OR
,"unknown circuit %d on connection from %s:%d. Dropping.",
449 cell
->circ_id
, conn
->_base
.address
, conn
->_base
.port
);
452 log_debug(LD_OR
,"Received for circID %d.",cell
->circ_id
);
454 if (!CIRCUIT_IS_ORIGIN(circ
) &&
455 cell
->circ_id
== TO_OR_CIRCUIT(circ
)->p_circ_id
) {
456 /* the destroy came from behind */
457 circuit_set_p_circid_orconn(TO_OR_CIRCUIT(circ
), 0, NULL
);
458 circuit_mark_for_close(circ
, reason
|END_CIRC_REASON_FLAG_REMOTE
);
459 } else { /* the destroy came from ahead */
460 circuit_set_n_circid_orconn(circ
, 0, NULL
);
461 if (CIRCUIT_IS_ORIGIN(circ
)) {
462 circuit_mark_for_close(circ
, reason
|END_CIRC_REASON_FLAG_REMOTE
);
465 log_debug(LD_OR
, "Delivering 'truncated' back.");
466 payload
[0] = (char)reason
;
467 relay_send_command_from_edge(0, circ
, RELAY_COMMAND_TRUNCATED
,
468 payload
, sizeof(payload
), NULL
);
473 /** Process a 'versions' cell. The current link protocol version must be 0
474 * to indicate that no version has yet been negotiated. We compare the
475 * versions in the cell to the list of versions we support, pick the
476 * highest version we have in common, and continue the negotiation from
480 command_process_versions_cell(var_cell_t
*cell
, or_connection_t
*conn
)
482 int highest_supported_version
= 0;
483 const char *cp
, *end
;
484 if (conn
->link_proto
!= 0 ||
485 conn
->_base
.state
!= OR_CONN_STATE_OR_HANDSHAKING
||
486 (conn
->handshake_state
&& conn
->handshake_state
->received_versions
)) {
487 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
488 "Received a VERSIONS cell on a connection with its version "
489 "already set to %d; dropping", (int) conn
->link_proto
);
492 tor_assert(conn
->handshake_state
);
493 end
= cell
->payload
+ cell
->payload_len
;
494 for (cp
= cell
->payload
; cp
+1 < end
; ++cp
) {
495 uint16_t v
= ntohs(get_uint16(cp
));
496 if (is_or_protocol_version_known(v
) && v
> highest_supported_version
)
497 highest_supported_version
= v
;
499 if (!highest_supported_version
) {
500 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
501 "Couldn't find a version in common between my version list and the "
502 "list in the VERSIONS cell; closing connection.");
503 connection_mark_for_close(TO_CONN(conn
));
505 } else if (highest_supported_version
== 1) {
506 /* Negotiating version 1 makes no sense, since version 1 has no VERSIONS
508 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
509 "Used version negotiation protocol to negotiate a v1 connection. "
510 "That's crazily non-compliant. Closing connection.");
511 connection_mark_for_close(TO_CONN(conn
));
514 conn
->link_proto
= highest_supported_version
;
515 conn
->handshake_state
->received_versions
= 1;
517 log_info(LD_OR
, "Negotiated version %d with %s:%d; sending NETINFO.",
518 highest_supported_version
, safe_str(conn
->_base
.address
),
520 tor_assert(conn
->link_proto
>= 2);
522 if (connection_or_send_netinfo(conn
) < 0) {
523 connection_mark_for_close(TO_CONN(conn
));
528 /** Process a 'netinfo' cell: read and act on its contents, and set the
529 * connection state to "open". */
531 command_process_netinfo_cell(cell_t
*cell
, or_connection_t
*conn
)
534 uint8_t my_addr_type
;
536 const char *my_addr_ptr
;
537 const char *cp
, *end
;
538 uint8_t n_other_addrs
;
539 time_t now
= time(NULL
);
541 long apparent_skew
= 0;
542 uint32_t my_apparent_addr
= 0;
544 if (conn
->link_proto
< 2) {
545 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
546 "Received a NETINFO cell on %s connection; dropping.",
547 conn
->link_proto
== 0 ? "non-versioned" : "a v1");
550 if (conn
->_base
.state
!= OR_CONN_STATE_OR_HANDSHAKING
) {
551 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
552 "Received a NETINFO cell on non-handshaking connection; dropping.");
555 tor_assert(conn
->handshake_state
&&
556 conn
->handshake_state
->received_versions
);
557 /* Decode the cell. */
558 timestamp
= ntohl(get_uint32(cell
->payload
));
559 if (labs(now
- conn
->handshake_state
->sent_versions_at
) < 180) {
560 apparent_skew
= now
- timestamp
;
563 my_addr_type
= (uint8_t) cell
->payload
[4];
564 my_addr_len
= (uint8_t) cell
->payload
[5];
565 my_addr_ptr
= cell
->payload
+ 6;
566 end
= cell
->payload
+ CELL_PAYLOAD_SIZE
;
567 cp
= cell
->payload
+ 6 + my_addr_len
;
569 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
570 "Addresses too long in netinfo cell; closing connection.");
571 connection_mark_for_close(TO_CONN(conn
));
573 } else if (my_addr_type
== RESOLVED_TYPE_IPV4
&& my_addr_len
== 4) {
574 my_apparent_addr
= ntohl(get_uint32(my_addr_ptr
));
577 n_other_addrs
= (uint8_t) *cp
++;
578 while (n_other_addrs
&& cp
< end
-2) {
579 /* Consider all the other addresses; if any matches, this connection is
582 const char *next
= decode_address_from_payload(&addr
, cp
, end
-cp
);
584 log_fn(LOG_PROTOCOL_WARN
, LD_OR
,
585 "Bad address in netinfo cell; closing connection.");
586 connection_mark_for_close(TO_CONN(conn
));
589 if (tor_addr_eq(&addr
, &conn
->real_addr
)) {
590 conn
->is_canonical
= 1;
597 /* Act on apparent skew. */
598 /** Warn when we get a netinfo skew with at least this value. */
599 #define NETINFO_NOTICE_SKEW 3600
600 if (labs(apparent_skew
) > NETINFO_NOTICE_SKEW
&&
601 router_get_by_digest(conn
->identity_digest
)) {
604 /*XXXX be smarter about when everybody says we are skewed. */
605 if (router_digest_is_trusted_dir(conn
->identity_digest
))
609 format_time_interval(dbuf
, sizeof(dbuf
), apparent_skew
);
610 log_fn(severity
, LD_GENERAL
, "Received NETINFO cell with skewed time from "
611 "server at %s:%d. It seems that our clock is %s by %s, or "
612 "that theirs is %s. Tor requires an accurate clock to work: "
613 "please check your time and date settings.",
614 conn
->_base
.address
, (int)conn
->_base
.port
,
615 apparent_skew
>0 ? "ahead" : "behind", dbuf
,
616 apparent_skew
>0 ? "behind" : "ahead");
617 control_event_general_status(LOG_WARN
,
618 "CLOCK_SKEW SKEW=%ld SOURCE=OR:%s:%d",
619 apparent_skew
, conn
->_base
.address
, conn
->_base
.port
);
622 /* XXX maybe act on my_apparent_addr, if the source is sufficiently
625 if (connection_or_set_state_open(conn
)<0)
626 connection_mark_for_close(TO_CONN(conn
));
628 log_info(LD_OR
, "Got good NETINFO cell from %s:%d; OR connection is now "
629 "open, using protocol version %d",
630 safe_str(conn
->_base
.address
), conn
->_base
.port
,
631 (int)conn
->link_proto
);
632 assert_connection_ok(TO_CONN(conn
),time(NULL
));