diff options
author | Nick Mathewson <nickm@torproject.org> | 2007-01-15 21:13:37 +0000 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2007-01-15 21:13:37 +0000 |
commit | ead35ef9440a4d20a559441b2c6779954d3c02d5 (patch) | |
tree | 6d991739d5cda23ccd4c3d36bb4c8945a0bb3fb7 | |
parent | 299730e0b684f7a910a45d94528701a84dc591a6 (diff) | |
download | tor-ead35ef9440a4d20a559441b2c6779954d3c02d5.tar tor-ead35ef9440a4d20a559441b2c6779954d3c02d5.tar.gz |
r11957@Kushana: nickm | 2007-01-15 15:25:57 -0500
Patch from Mike Perry: Track reasons for OR connection failure; display them in control events. Needs review and revision.
svn:r9354
-rw-r--r-- | doc/control-spec.txt | 14 | ||||
-rw-r--r-- | src/common/tortls.c | 54 | ||||
-rw-r--r-- | src/common/tortls.h | 15 | ||||
-rw-r--r-- | src/or/buffers.c | 2 | ||||
-rw-r--r-- | src/or/circuitbuild.c | 3 | ||||
-rw-r--r-- | src/or/connection.c | 32 | ||||
-rw-r--r-- | src/or/connection_or.c | 45 | ||||
-rw-r--r-- | src/or/control.c | 76 | ||||
-rw-r--r-- | src/or/or.h | 17 |
9 files changed, 218 insertions, 40 deletions
diff --git a/doc/control-spec.txt b/doc/control-spec.txt index 1904a0fa3..f4b8bcd9e 100644 --- a/doc/control-spec.txt +++ b/doc/control-spec.txt @@ -885,7 +885,8 @@ $Id$ 4.1.3. OR Connection status changed The syntax is: - "650" SP "ORCONN" SP (ServerID / Target) SP ORStatus + "650" SP "ORCONN" SP (ServerID / Target) SP ORStatus [ SP "REASON=" + Reason ] [ SP "NCIRCS=" NumCircuits ] ORStatus = "NEW" / "LAUNCHED" / "CONNECTED" / "FAILED" / "CLOSED" @@ -898,6 +899,17 @@ $Id$ A ServerID is specified unless it's a NEW connection, in which case we don't know what server it is yet, so we use Address:Port. + If extended events are enabled (see 3.19), optional reason and + circuit counting information is provided for CLOSED and FAILED + events. + + Reason = "MISC" / "DONE" / "CONNECTREFUSED" / + "IDENTITY" / "CONNECTRESET" / "TIMEOUT" / "NOROUTE" / + "IOERROR" + + NumCircuits counts both established and pending circuits. + + 4.1.4. Bandwidth used in the last second The syntax is: diff --git a/src/common/tortls.c b/src/common/tortls.c index 308c98155..f7e48b22f 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -73,8 +73,8 @@ static tor_tls_context_t *global_tls_context = NULL; static int tls_library_is_initialized = 0; /* Module-internal error codes. */ -#define _TOR_TLS_SYSCALL -6 -#define _TOR_TLS_ZERORETURN -5 +#define _TOR_TLS_SYSCALL -10 +#define _TOR_TLS_ZERORETURN -9 /* These functions are declared in crypto.c but not exported. */ EVP_PKEY *_crypto_pk_env_get_evp_pkey(crypto_pk_env_t *env, int private); @@ -103,6 +103,39 @@ tls_log_errors(int severity, const char *doing) } } +static int +tor_errno_to_tls_error(int e) { +#if defined(MS_WINDOWS) && !defined(USE_BSOCKETS) + switch(e) { + case WSAECONNRESET: // most common + return TOR_TLS_ERROR_CONNRESET; + case WSAETIMEDOUT: + return TOR_TLS_ERROR_TIMEOUT; + case WSAENETUNREACH: + case WSAEHOSTUNREACH: + return TOR_TLS_ERROR_NO_ROUTE; + case WSAECONNREFUSED: + return TOR_TLS_ERROR_CONNREFUSED; // least common + default: + return TOR_TLS_ERROR_MISC; + } +#else + switch(e) { + case ECONNRESET: // most common + return TOR_TLS_ERROR_CONNRESET; + case ETIMEDOUT: + return TOR_TLS_ERROR_TIMEOUT; + case EHOSTUNREACH: + case ENETUNREACH: + return TOR_TLS_ERROR_NO_ROUTE; + case ECONNREFUSED: + return TOR_TLS_ERROR_CONNREFUSED; // least common + default: + return TOR_TLS_ERROR_MISC; + } +#endif +} + #define CATCH_SYSCALL 1 #define CATCH_ZERO 2 @@ -121,6 +154,7 @@ tor_tls_get_error(tor_tls_t *tls, int r, int extra, const char *doing, int severity) { int err = SSL_get_error(tls->ssl, r); + int tor_error = TOR_TLS_ERROR_MISC; switch (err) { case SSL_ERROR_NONE: return TOR_TLS_DONE; @@ -131,25 +165,27 @@ tor_tls_get_error(tor_tls_t *tls, int r, int extra, case SSL_ERROR_SYSCALL: if (extra&CATCH_SYSCALL) return _TOR_TLS_SYSCALL; - if (r == 0) + if (r == 0) { log(severity, LD_NET, "TLS error: unexpected close while %s", doing); - else { + tor_error = TOR_TLS_ERROR_IO; + } else { int e = tor_socket_errno(tls->socket); log(severity, LD_NET, "TLS error: <syscall error while %s> (errno=%d: %s)", doing, e, tor_socket_strerror(e)); + tor_error = tor_errno_to_tls_error(e); } tls_log_errors(severity, doing); - return TOR_TLS_ERROR; + return tor_error; case SSL_ERROR_ZERO_RETURN: if (extra&CATCH_ZERO) return _TOR_TLS_ZERORETURN; log(severity, LD_NET, "TLS error: Zero return"); tls_log_errors(severity, doing); - return TOR_TLS_ERROR; + return TOR_TLS_ERROR_MISC; default: tls_log_errors(severity, doing); - return TOR_TLS_ERROR; + return TOR_TLS_ERROR_MISC; } } @@ -547,7 +583,7 @@ tor_tls_handshake(tor_tls_t *tls) if (ERR_peek_error() != 0) { tls_log_errors(tls->isServer ? LOG_INFO : LOG_WARN, "handshaking"); - return TOR_TLS_ERROR; + return TOR_TLS_ERROR_MISC; } if (r == TOR_TLS_DONE) { tls->state = TOR_TLS_ST_OPEN; @@ -607,7 +643,7 @@ tor_tls_shutdown(tor_tls_t *tls) tls->state == TOR_TLS_ST_SENTCLOSE) { log(LOG_WARN, LD_NET, "TLS returned \"half-closed\" value while already half-closed"); - return TOR_TLS_ERROR; + return TOR_TLS_ERROR_MISC; } tls->state = TOR_TLS_ST_SENTCLOSE; /* fall through ... */ diff --git a/src/common/tortls.h b/src/common/tortls.h index 8667ddf9b..5a6631dd5 100644 --- a/src/common/tortls.h +++ b/src/common/tortls.h @@ -19,11 +19,16 @@ typedef struct tor_tls_t tor_tls_t; /* Possible return values for most tor_tls_* functions. */ -#define TOR_TLS_ERROR -4 -#define TOR_TLS_CLOSE -3 -#define TOR_TLS_WANTREAD -2 -#define TOR_TLS_WANTWRITE -1 -#define TOR_TLS_DONE 0 +#define TOR_TLS_ERROR_MISC -9 +#define TOR_TLS_ERROR_IO -8 +#define TOR_TLS_ERROR_CONNREFUSED -7 +#define TOR_TLS_ERROR_CONNRESET -6 +#define TOR_TLS_ERROR_NO_ROUTE -5 +#define TOR_TLS_ERROR_TIMEOUT -4 +#define TOR_TLS_CLOSE -3 +#define TOR_TLS_WANTREAD -2 +#define TOR_TLS_WANTWRITE -1 +#define TOR_TLS_DONE 0 void tor_tls_free_all(void); int tor_tls_context_new(crypto_pk_env_t *rsa, diff --git a/src/or/buffers.c b/src/or/buffers.c index 9da86e8cd..050141a03 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -543,7 +543,7 @@ read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf) (int)at_most); if (buf_ensure_capacity(buf, at_most+buf->datalen)) - return TOR_TLS_ERROR; + return TOR_TLS_ERROR_MISC; if (at_most + buf->datalen > buf->len) at_most = buf->len - buf->datalen; diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 38135fb47..ab7bab787 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -897,7 +897,8 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer) * means that a connection broke or an extend failed. For now, * just give up. */ - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_OR_CONN_CLOSED); + circuit_mark_for_close(TO_CIRCUIT(circ), + END_CIRC_REASON_FLAG_REMOTE|END_CIRC_REASON_OR_CONN_CLOSED); return 0; #if 0 diff --git a/src/or/connection.c b/src/or/connection.c index e4a03ac61..3ca56017d 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -435,7 +435,8 @@ connection_about_to_close_connection(connection_t *conn) rep_hist_note_connect_failed(or_conn->identity_digest, now); entry_guard_register_connect_status(or_conn->identity_digest,0,now); router_set_status(or_conn->identity_digest, 0); - control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED); + control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED, + control_tls_error_to_reason(or_conn->tls_error)); } /* Inform any pending (not attached) circs that they should * give up. */ @@ -444,10 +445,12 @@ connection_about_to_close_connection(connection_t *conn) /* We only set hold_open_until_flushed when we're intentionally * closing a connection. */ rep_hist_note_disconnect(or_conn->identity_digest, now); - control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED); + control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED, + control_tls_error_to_reason(or_conn->tls_error)); } else if (or_conn->identity_digest) { rep_hist_note_connection_died(or_conn->identity_digest, now); - control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED); + control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED, + control_tls_error_to_reason(or_conn->tls_error)); } /* Now close all the attached circuits on it. */ circuit_unlink_all_from_or_conn(TO_OR_CONN(conn), @@ -824,7 +827,7 @@ connection_init_accepted_conn(connection_t *conn, uint8_t listener_type) switch (conn->type) { case CONN_TYPE_OR: - control_event_or_conn_status(TO_OR_CONN(conn), OR_CONN_EVENT_NEW); + control_event_or_conn_status(TO_OR_CONN(conn), OR_CONN_EVENT_NEW, 0); return connection_tls_start_handshake(TO_OR_CONN(conn), 1); case CONN_TYPE_AP: switch (listener_type) { @@ -1457,6 +1460,7 @@ connection_read_to_buf(connection_t *conn, int *max_to_read) /* else open, or closing */ result = read_to_buf_tls(or_conn->tls, at_most, conn->inbuf); + or_conn->tls_error = result; switch (result) { case TOR_TLS_CLOSE: @@ -1464,12 +1468,17 @@ connection_read_to_buf(connection_t *conn, int *max_to_read) "(Nickname %s, address %s", or_conn->nickname ? or_conn->nickname : "not set", conn->address); - return -1; - case TOR_TLS_ERROR: + return result; + case TOR_TLS_ERROR_IO: + case TOR_TLS_ERROR_CONNREFUSED: + case TOR_TLS_ERROR_CONNRESET: + case TOR_TLS_ERROR_NO_ROUTE: + case TOR_TLS_ERROR_TIMEOUT: + case TOR_TLS_ERROR_MISC: log_info(LD_NET,"tls error. breaking (nickname %s, address %s).", or_conn->nickname ? or_conn->nickname : "not set", conn->address); - return -1; + return result; case TOR_TLS_WANTWRITE: connection_start_writing(conn); return 0; @@ -1662,9 +1671,14 @@ connection_handle_write(connection_t *conn, int force) result = flush_buf_tls(or_conn->tls, conn->outbuf, max_to_write, &conn->outbuf_flushlen); switch (result) { - case TOR_TLS_ERROR: + case TOR_TLS_ERROR_IO: + case TOR_TLS_ERROR_CONNREFUSED: + case TOR_TLS_ERROR_CONNRESET: + case TOR_TLS_ERROR_NO_ROUTE: + case TOR_TLS_ERROR_TIMEOUT: + case TOR_TLS_ERROR_MISC: case TOR_TLS_CLOSE: - log_info(LD_NET,result==TOR_TLS_ERROR? + log_info(LD_NET,result!=TOR_TLS_CLOSE? "tls error. breaking.":"TLS connection closed on flush"); /* Don't flush; connection is dead. */ connection_close_immediate(conn); diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 804848771..b7f944955 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -435,7 +435,7 @@ connection_or_connect(uint32_t addr, uint16_t port, const char *id_digest) /* set up conn so it's got all the data we need to remember */ connection_or_init_conn_from_address(conn, addr, port, id_digest, 1); conn->_base.state = OR_CONN_STATE_CONNECTING; - control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED); + control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0); if (options->HttpsProxy) { /* we shouldn't connect directly. use the https proxy instead. */ @@ -453,7 +453,8 @@ connection_or_connect(uint32_t addr, uint16_t port, const char *id_digest) time(NULL)); router_set_status(conn->identity_digest, 0); } - control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED); + control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, + END_OR_CONN_REASON_TCP_REFUSED); connection_free(TO_CONN(conn)); return NULL; case 0: @@ -508,7 +509,12 @@ connection_tls_continue_handshake(or_connection_t *conn) { check_no_tls_errors(); switch (tor_tls_handshake(conn->tls)) { - case TOR_TLS_ERROR: + case TOR_TLS_ERROR_IO: + case TOR_TLS_ERROR_CONNREFUSED: + case TOR_TLS_ERROR_CONNRESET: + case TOR_TLS_ERROR_NO_ROUTE: + case TOR_TLS_ERROR_TIMEOUT: + case TOR_TLS_ERROR_MISC: case TOR_TLS_CLOSE: log_info(LD_OR,"tls error. breaking connection."); return -1; @@ -628,7 +634,8 @@ connection_or_check_valid_handshake(or_connection_t *conn, char *digest_rcvd) conn->_base.address, conn->_base.port, expected, seen); entry_guard_register_connect_status(conn->identity_digest,0,time(NULL)); router_set_status(conn->identity_digest, 0); - control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED); + control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, + END_OR_CONN_REASON_OR_IDENTITY); as_advertised = 0; } if (authdir_mode(options)) { @@ -672,7 +679,7 @@ connection_tls_finish_handshake(or_connection_t *conn) directory_set_dirty(); conn->_base.state = OR_CONN_STATE_OPEN; - control_event_or_conn_status(conn, OR_CONN_EVENT_CONNECTED); + control_event_or_conn_status(conn, OR_CONN_EVENT_CONNECTED, 0); if (started_here) { rep_hist_note_connect_succeeded(conn->identity_digest, time(NULL)); if (entry_guard_register_connect_status(conn->identity_digest, 1, @@ -791,3 +798,31 @@ connection_or_send_destroy(uint16_t circ_id, or_connection_t *conn, int reason) return 0; } +/** Count number of pending circs on an or_conn */ +int +connection_or_count_pending_circs(or_connection_t *or_conn) +{ + extern smartlist_t *circuits_pending_or_conns; + int cnt = 0; + + if (!circuits_pending_or_conns) + return 0; + + SMARTLIST_FOREACH(circuits_pending_or_conns, circuit_t *, circ, + { + if (circ->marked_for_close) + continue; + tor_assert(circ->state == CIRCUIT_STATE_OR_WAIT); + if (!circ->n_conn && + !memcmp(or_conn->identity_digest, circ->n_conn_id_digest, + DIGEST_LEN)) { + cnt++; + } + }); + + log_debug(LD_CIRC,"or_conn to %s, %d pending circs", + or_conn->nickname ? or_conn->nickname : "NULL", cnt); + return cnt; +} + + diff --git a/src/or/control.c b/src/or/control.c index 1eca654ba..acefdae9a 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -3247,13 +3247,65 @@ orconn_target_get_name(int long_names, } } +int +control_tls_error_to_reason(int e) { + switch(e) { + case TOR_TLS_ERROR_IO: + return END_OR_CONN_REASON_TLS_IO_ERROR; + case TOR_TLS_ERROR_CONNREFUSED: + return END_OR_CONN_REASON_TCP_REFUSED; + case TOR_TLS_ERROR_CONNRESET: + return END_OR_CONN_REASON_TLS_CONNRESET; + case TOR_TLS_ERROR_NO_ROUTE: + return END_OR_CONN_REASON_TLS_NO_ROUTE; + case TOR_TLS_ERROR_TIMEOUT: + return END_OR_CONN_REASON_TLS_TIMEOUT; + case TOR_TLS_WANTREAD: + case TOR_TLS_WANTWRITE: + case TOR_TLS_CLOSE: + case TOR_TLS_DONE: + return END_OR_CONN_REASON_DONE; + default: + return END_OR_CONN_REASON_TLS_MISC; + } +} + +const char * +or_conn_end_reason_to_string(int r) { + switch(r) { + case END_OR_CONN_REASON_DONE: + return "REASON=DONE"; + case END_OR_CONN_REASON_TCP_REFUSED: + return "REASON=CONNECTREFUSED"; + case END_OR_CONN_REASON_OR_IDENTITY: + return "REASON=IDENTITY"; + case END_OR_CONN_REASON_TLS_CONNRESET: + return "REASON=CONNECTRESET"; + case END_OR_CONN_REASON_TLS_TIMEOUT: + return "REASON=TIMEOUT"; + case END_OR_CONN_REASON_TLS_NO_ROUTE: + return "REASON=NOROUTE"; + case END_OR_CONN_REASON_TLS_IO_ERROR: + return "REASON=IOERROR"; + case END_OR_CONN_REASON_TLS_MISC: + return "REASON=MISC"; + case 0: + return ""; + default: + log_warn(LD_BUG, "Unrecognized or_conn reason code %d", r); + return "REASON=BOGUS"; + } +} + /** Something has happened to the OR connection <b>conn</b>: tell any * interested control connections. */ int -control_event_or_conn_status(or_connection_t *conn,or_conn_status_event_t tp) +control_event_or_conn_status(or_connection_t *conn,or_conn_status_event_t tp, + int reason) { char buf[HEX_DIGEST_LEN+3]; /* status, dollar, identity, NUL */ size_t len; + int ncircs = 0; if (!EVENT_IS_INTERESTING(EVENT_OR_CONN_STATUS)) return 0; @@ -3267,6 +3319,7 @@ control_event_or_conn_status(or_connection_t *conn,or_conn_status_event_t tp) if (EVENT_IS_INTERESTING1(EVENT_OR_CONN_STATUS)) { const char *status; char name[128]; + char ncircs_buf[32] = {0}; /* > 8 + log10(2^32)=10 + 2 */ switch (tp) { case OR_CONN_EVENT_LAUNCHED: status = "LAUNCHED"; break; @@ -3278,17 +3331,26 @@ control_event_or_conn_status(or_connection_t *conn,or_conn_status_event_t tp) log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); return 0; } + ncircs = connection_or_count_pending_circs(conn); + ncircs += conn->n_circuits; + if(ncircs && (tp == OR_CONN_EVENT_FAILED || tp == OR_CONN_EVENT_CLOSED)) { + tor_snprintf(ncircs_buf, sizeof(ncircs_buf), "%sNCIRCS=%d", + reason ? " " : "", ncircs); + } + if (EVENT_IS_INTERESTING1S(EVENT_OR_CONN_STATUS)) { orconn_target_get_name(0, name, sizeof(name), conn); - send_control1_event(EVENT_OR_CONN_STATUS, SHORT_NAMES, - "650 ORCONN %s %s\r\n", - name, status); + send_control1_event_extended(EVENT_OR_CONN_STATUS, SHORT_NAMES, + "650 ORCONN %s %s@%s%s\r\n", + name, status, + or_conn_end_reason_to_string(reason), ncircs_buf); } if (EVENT_IS_INTERESTING1L(EVENT_OR_CONN_STATUS)) { orconn_target_get_name(1, name, sizeof(name), conn); - send_control1_event(EVENT_OR_CONN_STATUS, LONG_NAMES, - "650 ORCONN %s %s\r\n", - name, status); + send_control1_event_extended(EVENT_OR_CONN_STATUS, LONG_NAMES, + "650 ORCONN %s %s@%s%s\r\n", + name, status, + or_conn_end_reason_to_string(reason), ncircs_buf); } } return 0; diff --git a/src/or/or.h b/src/or/or.h index d63f4603a..bbbe72068 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -476,6 +476,16 @@ typedef enum { #define RELAY_COMMAND_RENDEZVOUS_ESTABLISHED 39 #define RELAY_COMMAND_INTRODUCE_ACK 40 +/* Reasons why an OR connection is closed */ +#define END_OR_CONN_REASON_DONE 1 +#define END_OR_CONN_REASON_TCP_REFUSED 2 +#define END_OR_CONN_REASON_OR_IDENTITY 3 +#define END_OR_CONN_REASON_TLS_CONNRESET 4 /* tls connection reset by peer */ +#define END_OR_CONN_REASON_TLS_TIMEOUT 5 +#define END_OR_CONN_REASON_TLS_NO_ROUTE 6 /* no route to host/net */ +#define END_OR_CONN_REASON_TLS_IO_ERROR 7 /* tls read/write error */ +#define END_OR_CONN_REASON_TLS_MISC 8 + /* Reasons why we (or a remote OR) might close a stream. See tor-spec.txt for * documentation of these. */ #define END_STREAM_REASON_MISC 1 @@ -723,6 +733,7 @@ typedef struct or_connection_t { char *nickname; /**< Nickname of OR on other side (if any). */ tor_tls_t *tls; /**< TLS connection state */ + int tls_error; /**< Last tor_tls error code */ time_t timestamp_lastempty; /**< When was the outbuf last completely empty?*/ @@ -837,7 +848,7 @@ typedef struct control_connection_t { } control_connection_t; /** Cast a connection_t subtype pointer to a connection_t **/ -#define TO_CONN(c) &(((c)->_base)) +#define TO_CONN(c) (&(((c)->_base))) /** Helper macro: Given a pointer to to._base, of type from*, return &to. */ #define DOWNCAST(to, ptr) \ (to*) (((char*)(ptr)) - STRUCT_OFFSET(to, _base)) @@ -2150,6 +2161,7 @@ void connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn); int connection_or_send_destroy(uint16_t circ_id, or_connection_t *conn, int reason); +int connection_or_count_pending_circs(or_connection_t *or_conn); /********************************* control.c ***************************/ @@ -2216,8 +2228,9 @@ int control_event_circuit_status(origin_circuit_t *circ, int control_event_stream_status(edge_connection_t *conn, stream_status_event_t e, int reason); +int control_tls_error_to_reason(int e); int control_event_or_conn_status(or_connection_t *conn, - or_conn_status_event_t e); + or_conn_status_event_t e, int reason); int control_event_bandwidth_used(uint32_t n_read, uint32_t n_written); void control_event_logmsg(int severity, unsigned int domain, const char *msg); int control_event_descriptors_changed(smartlist_t *routers); |