diff --git a/ChangeLog b/ChangeLog index 74265159a1..417b2a356f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,9 @@ Changes in version 0.1.2.4-alpha - 2006-11-?? + o Major features + - Add support for using natd; this allows FreeBSDs earlier than 5.1.2 to + have ipfw send connections through Tor without using SOCKS. (Patch from + Zajcev Evgeny with tweaks from tup.) + o Minor features - Add breakdown of public key operations to dumped statistics. diff --git a/src/or/buffers.c b/src/or/buffers.c index 421ab43337..b85b79cec8 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -1304,6 +1304,34 @@ fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) return 1; } +/** Try to read a single LF-terminated line from buf, and write it, + * NUL-terminated, into the *data_len byte buffer at data_out. + * Set *data_len to the number of bytes in the line, not counting the + * terminating NUL. Return 1 if we read a whole line, return 0 if we don't + * have a whole line yet, and return -1 if the line length exceeds + *data_len. + */ +int +fetch_from_buf_line_lf(buf_t *buf, char *data_out, size_t *data_len) +{ + char *cp; + size_t sz; + + size_t remaining = buf->datalen - _buf_offset(buf,buf->cur); + cp = find_char_on_buf(buf, buf->cur, remaining, '\n'); + if (!cp) + return 0; + sz = _buf_offset(buf, cp); + if (sz+2 > *data_len) { + *data_len = sz+2; + return -1; + } + fetch_from_buf(data_out, sz+1, buf); + data_out[sz+1] = '\0'; + *data_len = sz+1; + return 1; +} + /** Compress on uncompress the data_len bytes in data using the * zlib state state, appending the result to buf. If * done is true, flush the data in the state and finish the diff --git a/src/or/config.c b/src/or/config.c index 358be63bfd..6dfb40dafc 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -195,6 +195,8 @@ static config_var_t _option_vars[] = { VAR("MyFamily", STRING, MyFamily, NULL), VAR("NewCircuitPeriod", INTERVAL, NewCircuitPeriod, "30 seconds"), VAR("NamingAuthoritativeDirectory",BOOL, NamingAuthoritativeDir, "0"), + VAR("NatdListenAddress", LINELIST, NatdListenAddress, NULL), + VAR("NatdPort", UINT, NatdPort, "0"), VAR("Nickname", STRING, Nickname, NULL), VAR("NoPublish", BOOL, NoPublish, "0"), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), @@ -2086,21 +2088,30 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->TransPort == 0 && options->TransListenAddress != NULL) REJECT("TransPort must be defined if TransListenAddress is defined."); + if (options->NatdPort == 0 && options->NatdListenAddress != NULL) + REJECT("NatdPort must be defined if NatdListenAddress is defined."); + #if 0 /* don't complain, since a standard configuration does this! */ if (options->SocksPort == 0 && options->SocksListenAddress != NULL) REJECT("SocksPort must be defined if SocksListenAddress is defined."); #endif - for (i = 0; i < 2; ++i) { + for (i = 0; i < 3; ++i) { int is_socks = i==0; + int is_trans = i==1; config_line_t *line, *opt, *old; - const char *tp = is_socks ? "SOCKS proxy" : "transparent proxy"; + const char *tp = is_socks ? "SOCKS proxy" : + is_trans ? "transparent proxy" + : "natd proxy"; if (is_socks) { opt = options->SocksListenAddress; old = old_options ? old_options->SocksListenAddress : NULL; - } else { + } else if (is_trans) { opt = options->TransListenAddress; old = old_options ? old_options->TransListenAddress : NULL; + } else { + opt = options->NatdListenAddress; + old = old_options ? old_options->NatdListenAddress : NULL; } for (line = opt; line; line = line->next) { @@ -2184,9 +2195,13 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->TransPort < 0 || options->TransPort > 65535) REJECT("TransPort option out of bounds."); + if (options->NatdPort < 0 || options->NatdPort > 65535) + REJECT("NatdPort option out of bounds."); + if (options->SocksPort == 0 && options->TransPort == 0 && - options->ORPort == 0) - REJECT("SocksPort, TransPort, and ORPort are all undefined? Quitting."); + options->NatdPort == 0 && options->ORPort == 0) + REJECT("SocksPort, TransPort, NatdPort, and ORPort are all undefined? " + "Quitting."); if (options->ControlPort < 0 || options->ControlPort > 65535) REJECT("ControlPort option out of bounds."); diff --git a/src/or/connection.c b/src/or/connection.c index ce7046675e..428d71608f 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -45,7 +45,9 @@ conn_type_to_string(int type) case CONN_TYPE_OR: return "OR"; case CONN_TYPE_EXIT: return "Exit"; case CONN_TYPE_AP_LISTENER: return "Socks listener"; - case CONN_TYPE_AP_TRANS_LISTENER: return "Transparent listener"; + case CONN_TYPE_AP_TRANS_LISTENER: + return "Transparent pf/netfilter listener"; + case CONN_TYPE_AP_NATD_LISTENER: return "Transparent natd listener"; case CONN_TYPE_AP: return "Socks"; case CONN_TYPE_DIR_LISTENER: return "Directory listener"; case CONN_TYPE_DIR: return "Directory"; @@ -72,6 +74,7 @@ conn_state_to_string(int type, int state) case CONN_TYPE_OR_LISTENER: case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: + case CONN_TYPE_AP_NATD_LISTENER: case CONN_TYPE_DIR_LISTENER: case CONN_TYPE_CONTROL_LISTENER: if (state == LISTENER_STATE_READY) @@ -97,6 +100,7 @@ conn_state_to_string(int type, int state) case CONN_TYPE_AP: switch (state) { case AP_CONN_STATE_SOCKS_WAIT: return "waiting for dest info"; + case AP_CONN_STATE_NATD_WAIT: return "waiting for natd dest info"; case AP_CONN_STATE_RENDDESC_WAIT: return "waiting for rendezvous desc"; case AP_CONN_STATE_CONTROLLER_WAIT: return "waiting for controller"; case AP_CONN_STATE_CIRCUIT_WAIT: return "waiting for safe circuit"; @@ -827,6 +831,9 @@ connection_init_accepted_conn(connection_t *conn, uint8_t listener_type) case CONN_TYPE_AP_TRANS_LISTENER: conn->state = AP_CONN_STATE_CIRCUIT_WAIT; return connection_ap_process_transparent(TO_EDGE_CONN(conn)); + case CONN_TYPE_AP_NATD_LISTENER: + conn->state = AP_CONN_STATE_NATD_WAIT; + break; } break; case CONN_TYPE_DIR: @@ -1071,6 +1078,10 @@ retry_all_listeners(int force, smartlist_t *replaced_conns, options->TransPort, "127.0.0.1", force, replaced_conns, new_conns, 0)<0) return -1; + if (retry_listeners(CONN_TYPE_AP_NATD_LISTENER, options->NatdListenAddress, + options->NatdPort, "127.0.0.1", force, + replaced_conns, new_conns, 0)<0) + return -1; if (retry_listeners(CONN_TYPE_CONTROL_LISTENER, options->ControlListenAddress, options->ControlPort, "127.0.0.1", force, @@ -1286,6 +1297,7 @@ connection_handle_read(connection_t *conn) return connection_handle_listener_read(conn, CONN_TYPE_OR); case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: + case CONN_TYPE_AP_NATD_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_AP); case CONN_TYPE_DIR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_DIR); @@ -1922,6 +1934,7 @@ connection_is_listener(connection_t *conn) if (conn->type == CONN_TYPE_OR_LISTENER || conn->type == CONN_TYPE_AP_LISTENER || conn->type == CONN_TYPE_AP_TRANS_LISTENER || + conn->type == CONN_TYPE_AP_NATD_LISTENER || conn->type == CONN_TYPE_DIR_LISTENER || conn->type == CONN_TYPE_CONTROL_LISTENER) return 1; @@ -2280,6 +2293,7 @@ assert_connection_ok(connection_t *conn, time_t now) case CONN_TYPE_OR_LISTENER: case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: + case CONN_TYPE_AP_NATD_LISTENER: case CONN_TYPE_DIR_LISTENER: case CONN_TYPE_CONTROL_LISTENER: tor_assert(conn->state == LISTENER_STATE_READY); diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index cd04549a9b..bb86b1cf1c 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -28,6 +28,7 @@ const char connection_edge_c_id[] = static smartlist_t *redirect_exit_list = NULL; static int connection_ap_handshake_process_socks(edge_connection_t *conn); +static int connection_ap_process_natd(edge_connection_t *conn); static int connection_exit_connect_dir(edge_connection_t *exit_conn); /** An AP stream has failed/finished. If it hasn't already sent back @@ -109,6 +110,12 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) return -1; } return 0; + case AP_CONN_STATE_NATD_WAIT: + if (connection_ap_process_natd(conn) < 0) { + /* already marked */ + return -1; + } + return 0; case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: if (connection_edge_package_raw_inbuf(conn, package_partial) < 0) { @@ -247,6 +254,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) connection_edge_consider_sending_sendme(conn); return 0; case AP_CONN_STATE_SOCKS_WAIT: + case AP_CONN_STATE_NATD_WAIT: case AP_CONN_STATE_RENDDESC_WAIT: case AP_CONN_STATE_CIRCUIT_WAIT: case AP_CONN_STATE_CONNECT_WAIT: @@ -1471,7 +1479,7 @@ connection_ap_process_transparent(edge_connection_t *conn) if (connection_ap_get_original_destination(conn, socks) < 0) { log_warn(LD_APP,"Fetching original destination failed. Closing."); - connection_mark_unattached_ap(conn, 0); + connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_FETCH_ORIG_DEST); return -1; } /* we have the original destination */ @@ -1485,6 +1493,77 @@ connection_ap_process_transparent(edge_connection_t *conn) return connection_ap_handshake_rewrite_and_attach(conn, NULL); } +/** connection_edge_process_inbuf() found a conn in state + * natd_wait. See if conn->inbuf has the right bytes to proceed. + * See libalias(3) and ProxyEncodeTcpStream() in alias_proxy.c for + * the encoding form of the original destination. + * + * If the original destination is complete, send it to + * connection_ap_handshake_rewrite_and_attach(). + * + * Return -1 if an unexpected error with conn (and it should be marked + * for close), else return 0. + */ +static int +connection_ap_process_natd(edge_connection_t *conn) +{ + char tmp_buf[36], *tbuf, *daddr; + size_t tlen = 30; + int err; + socks_request_t *socks; + or_options_t *options = get_options(); + + tor_assert(conn); + tor_assert(conn->_base.type == CONN_TYPE_AP); + tor_assert(conn->_base.state == AP_CONN_STATE_NATD_WAIT); + tor_assert(conn->socks_request); + socks = conn->socks_request; + + log_debug(LD_APP,"entered."); + + /* look for LF-terminated "[DEST ip_addr port]" + * where ip_addr is a dotted-quad and port is in string form */ + err = fetch_from_buf_line_lf(conn->_base.inbuf, tmp_buf, &tlen); + if (err == 0) + return 0; + if (err < 0) { + log_warn(LD_APP,"Natd handshake failed (DEST too long). Closing"); + connection_mark_unattached_ap(conn, END_STREAM_REASON_INVALID_NATD_DEST); + return -1; + } + + if (strncmp(tmp_buf, "[DEST ", 6)) { + log_warn(LD_APP,"Natd handshake failed. Closing. tmp_buf = '%s'", tmp_buf); + connection_mark_unattached_ap(conn, END_STREAM_REASON_INVALID_NATD_DEST); + return -1; + } + + tbuf = &tmp_buf[0] + 6; + daddr = tbuf; + while (tbuf[0] != '\0' && tbuf[0] != ' ') + tbuf++; + tbuf[0] = '\0'; + tbuf++; + + /* pretend that a socks handshake completed so we don't try to + * send a socks reply down a natd conn */ + socks->command = SOCKS_COMMAND_CONNECT; + socks->has_finished = 1; + + strlcpy(socks->address, daddr, sizeof(socks->address)); + socks->port = atoi(tbuf); + + control_event_stream_status(conn, STREAM_EVENT_NEW, 0); + + if (options->LeaveStreamsUnattached) { + conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT; + return 0; + } + conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT; + + return connection_ap_handshake_rewrite_and_attach(conn, NULL); +} + /** Iterate over the two bytes of stream_id until we get one that is not * already in use; return it. Return 0 if can't get a unique stream_id. */ diff --git a/src/or/control.c b/src/or/control.c index 736cc3ee32..18d0e47d2b 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -1583,7 +1583,8 @@ handle_getinfo_helper(control_connection_t *control_conn, origin_circuit_t *origin_circ = NULL; if (conns[i]->type != CONN_TYPE_AP || conns[i]->marked_for_close || - conns[i]->state == AP_CONN_STATE_SOCKS_WAIT) + conns[i]->state == AP_CONN_STATE_SOCKS_WAIT || + conns[i]->state == AP_CONN_STATE_NATD_WAIT) continue; conn = TO_EDGE_CONN(conns[i]); switch (conn->_base.state) diff --git a/src/or/hibernate.c b/src/or/hibernate.c index be6761a309..2b4657d4a1 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -713,6 +713,7 @@ hibernate_begin(int new_state, time_t now) while ((conn = connection_get_by_type(CONN_TYPE_OR_LISTENER)) || (conn = connection_get_by_type(CONN_TYPE_AP_LISTENER)) || (conn = connection_get_by_type(CONN_TYPE_AP_TRANS_LISTENER)) || + (conn = connection_get_by_type(CONN_TYPE_AP_NATD_LISTENER)) || (conn = connection_get_by_type(CONN_TYPE_DIR_LISTENER))) { log_info(LD_NET,"Closing listener type %d", conn->type); connection_mark_for_close(conn); diff --git a/src/or/or.h b/src/or/or.h index 68b36d45dd..3a45f0c761 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -224,9 +224,13 @@ typedef enum { #define CONN_TYPE_CONTROL_LISTENER 12 /** Type for connections from user interface process. */ #define CONN_TYPE_CONTROL 13 -/** Type for sockets listening for transparent proxy connections. */ +/** Type for sockets listening for transparent connections redirected by pf or + * netfilter. */ #define CONN_TYPE_AP_TRANS_LISTENER 14 -#define _CONN_TYPE_MAX 14 +/** Type for sockets listening for transparent connections redirected by + * natd. */ +#define CONN_TYPE_AP_NATD_LISTENER 15 +#define _CONN_TYPE_MAX 15 #define CONN_IS_EDGE(x) \ ((x)->type == CONN_TYPE_EXIT || (x)->type == CONN_TYPE_AP) @@ -294,7 +298,10 @@ typedef enum { #define AP_CONN_STATE_RESOLVE_WAIT 10 /** State for a SOCKS connection: ready to send and receive. */ #define AP_CONN_STATE_OPEN 11 -#define _AP_CONN_STATE_MAX 11 +/** State for a transparent natd connection: waiting for original + * destination. */ +#define AP_CONN_STATE_NATD_WAIT 12 +#define _AP_CONN_STATE_MAX 12 #define _DIR_CONN_STATE_MIN 1 /** State for connection to directory server: waiting for connect(). */ @@ -489,6 +496,8 @@ typedef enum { #define END_STREAM_REASON_CANT_ATTACH 257 #define END_STREAM_REASON_NET_UNREACHABLE 258 #define END_STREAM_REASON_SOCKSPROTOCOL 259 +#define END_STREAM_REASON_CANT_FETCH_ORIG_DEST 260 +#define END_STREAM_REASON_INVALID_NATD_DEST 261 /* OR this with the argument to control_event_stream_status to indicate that * the reason came from an END cell. */ @@ -1450,8 +1459,11 @@ typedef struct { config_line_t *DirPolicy; /**< Lists of dir policy components */ /** Addresses to bind for listening for SOCKS connections. */ config_line_t *SocksListenAddress; - /** Addresses to bind for listening for transparent connections. */ + /** Addresses to bind for listening for transparent pf/nefilter + * connections. */ config_line_t *TransListenAddress; + /** Addresses to bind for listening for transparent natd connections */ + config_line_t *NatdListenAddress; /** Addresses to bind for listening for OR connections. */ config_line_t *ORListenAddress; /** Addresses to bind for listening for directory connections. */ @@ -1473,7 +1485,9 @@ typedef struct { * length (alpha in geometric distribution). */ int ORPort; /**< Port to listen on for OR connections. */ int SocksPort; /**< Port to listen on for SOCKS connections. */ - int TransPort; /**< Port to listen on for transparent connections. */ + /** Port to listen on for transparent pf/netfilter connections. */ + int TransPort; + int NatdPort; /**< Port to listen on for transparent natd connections. */ int ControlPort; /**< Port to listen on for control connections. */ int DirPort; /**< Port to listen on for directory connections. */ int AssumeReachable; /**< Whether to publish our descriptor regardless. */ @@ -1715,6 +1729,7 @@ int fetch_from_buf_socks(buf_t *buf, socks_request_t *req, int fetch_from_buf_control0(buf_t *buf, uint32_t *len_out, uint16_t *type_out, char **body_out, int check_for_v1); int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); +int fetch_from_buf_line_lf(buf_t *buf, char *data_out, size_t *data_len); void assert_buf_ok(buf_t *buf);