diff options
author | Nick Mathewson <nickm@torproject.org> | 2006-08-10 09:01:37 +0000 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2006-08-10 09:01:37 +0000 |
commit | 5cff4164a08af2a869e5e85aa6d359b865eee9aa (patch) | |
tree | e529cd925b0c3634b512bf638e9edb31dbde22f2 /src | |
parent | 8cbd03fdaf3c138a2ca41b41c995643146fdac94 (diff) | |
download | tor-5cff4164a08af2a869e5e85aa6d359b865eee9aa.tar.gz tor-5cff4164a08af2a869e5e85aa6d359b865eee9aa.zip |
r7299@Kushana: nickm | 2006-08-10 01:08:58 -0700
Patch from Tup to add support for transparent AP connections: this basically bundles the functionality of trans-proxy-tor into the tor mainline. Now hosts with compliant pf/netfilter implementations can redirect TCP connections straight to Tor without diverting through SOCKS.
svn:r7007
Diffstat (limited to 'src')
-rw-r--r-- | src/or/config.c | 16 | ||||
-rw-r--r-- | src/or/connection.c | 26 | ||||
-rw-r--r-- | src/or/connection_edge.c | 134 | ||||
-rw-r--r-- | src/or/control.c | 3 | ||||
-rw-r--r-- | src/or/hibernate.c | 1 | ||||
-rw-r--r-- | src/or/or.h | 21 | ||||
-rw-r--r-- | src/or/relay.c | 4 |
7 files changed, 195 insertions, 10 deletions
diff --git a/src/or/config.c b/src/or/config.c index 0d9fd4c150..a234707e43 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -235,6 +235,8 @@ static config_var_t _option_vars[] = { VAR("TrackHostExits", CSV, TrackHostExits, NULL), VAR("TrackHostExitsExpire",INTERVAL, TrackHostExitsExpire, "30 minutes"), OBSOLETE("TrafficShaping"), + VAR("TransListenAddress", LINELIST, TransListenAddress, NULL), + VAR("TransPort", UINT, TransPort, "0"), VAR("UseEntryGuards", BOOL, UseEntryGuards, "1"), VAR("User", STRING, User, NULL), VAR("V1AuthoritativeDirectory",BOOL, V1AuthoritativeDir, "0"), @@ -2067,6 +2069,7 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("SocksPort must be defined if SocksListenAddress is defined."); #endif + /* XXX TransListenAddress should be checked here as well */ if (options->SocksListenAddress) { config_line_t *line = NULL; char *address = NULL; @@ -2144,8 +2147,12 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->SocksPort < 0 || options->SocksPort > 65535) REJECT("SocksPort option out of bounds."); - if (options->SocksPort == 0 && options->ORPort == 0) - REJECT("SocksPort and ORPort are both undefined? Quitting."); + if (options->TransPort < 0 || options->TransPort > 65535) + REJECT("TransPort option out of bounds."); + + if (options->SocksPort == 0 && options->TransPort == 0 && + options->ORPort == 0) + REJECT("SocksPort, TransPort, and ORPort are all undefined? Quitting."); if (options->ControlPort < 0 || options->ControlPort > 65535) REJECT("ControlPort option out of bounds."); @@ -2153,6 +2160,11 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->DirPort < 0 || options->DirPort > 65535) REJECT("DirPort option out of bounds."); +#ifndef USE_TRANSPARENT + if (options->TransPort || options->TransListenAddress) + REJECT("TransPort and TransListenAddress are disabled in this build."); +#endif + if (options->StrictExitNodes && (!options->ExitNodes || !strlen(options->ExitNodes)) && (!old_options || diff --git a/src/or/connection.c b/src/or/connection.c index f5149a4047..e760e257f5 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -16,7 +16,8 @@ const char connection_c_id[] = static connection_t *connection_create_listener(const char *listenaddress, uint16_t listenport, int type); -static int connection_init_accepted_conn(connection_t *conn); +static int connection_init_accepted_conn(connection_t *conn, + uint8_t listener_type); static int connection_handle_listener_read(connection_t *conn, int new_type); static int connection_receiver_bucket_should_increase(or_connection_t *conn); static int connection_finished_flushing(connection_t *conn); @@ -44,6 +45,7 @@ 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: return "Socks"; case CONN_TYPE_DIR_LISTENER: return "Directory listener"; case CONN_TYPE_DIR: return "Directory"; @@ -69,6 +71,7 @@ conn_state_to_string(int type, int state) switch (type) { case CONN_TYPE_OR_LISTENER: case CONN_TYPE_AP_LISTENER: + case CONN_TYPE_AP_TRANS_LISTENER: case CONN_TYPE_DIR_LISTENER: case CONN_TYPE_CONTROL_LISTENER: if (state == LISTENER_STATE_READY) @@ -93,6 +96,7 @@ conn_state_to_string(int type, int state) break; case CONN_TYPE_AP: switch (state) { + case AP_CONN_STATE_ORIGDST_WAIT: case AP_CONN_STATE_SOCKS_WAIT: return "waiting for dest info"; case AP_CONN_STATE_RENDDESC_WAIT: return "waiting for rendezvous desc"; case AP_CONN_STATE_CONTROLLER_WAIT: return "waiting for controller"; @@ -786,7 +790,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) return 0; /* no need to tear down the parent */ } - if (connection_init_accepted_conn(newconn) < 0) { + if (connection_init_accepted_conn(newconn, conn->type) < 0) { connection_mark_for_close(newconn); return 0; } @@ -797,7 +801,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) * If conn is an OR, start the tls handshake. */ static int -connection_init_accepted_conn(connection_t *conn) +connection_init_accepted_conn(connection_t *conn, uint8_t listener_type) { connection_start_reading(conn); @@ -806,7 +810,14 @@ connection_init_accepted_conn(connection_t *conn) control_event_or_conn_status(TO_OR_CONN(conn), OR_CONN_EVENT_NEW); return connection_tls_start_handshake(TO_OR_CONN(conn), 1); case CONN_TYPE_AP: - conn->state = AP_CONN_STATE_SOCKS_WAIT; + switch (listener_type) { + case CONN_TYPE_AP_LISTENER: + conn->state = AP_CONN_STATE_SOCKS_WAIT; + break; + case CONN_TYPE_AP_TRANS_LISTENER: + conn->state = AP_CONN_STATE_ORIGDST_WAIT; + break; + } break; case CONN_TYPE_DIR: conn->purpose = DIR_PURPOSE_SERVER; @@ -1046,6 +1057,10 @@ retry_all_listeners(int force, smartlist_t *replaced_conns, options->SocksPort, "127.0.0.1", force, replaced_conns, new_conns, 0)<0) return -1; + if (retry_listeners(CONN_TYPE_AP_TRANS_LISTENER, options->TransListenAddress, + options->TransPort, "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, @@ -1260,6 +1275,7 @@ connection_handle_read(connection_t *conn) case CONN_TYPE_OR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_OR); case CONN_TYPE_AP_LISTENER: + case CONN_TYPE_AP_TRANS_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_AP); case CONN_TYPE_DIR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_DIR); @@ -1896,6 +1912,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_DIR_LISTENER || conn->type == CONN_TYPE_CONTROL_LISTENER) return 1; @@ -2248,6 +2265,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_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 746d65afc2..289538e6f6 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -17,6 +17,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_transparent(edge_connection_t *conn); /** An AP stream has failed/finished. If it hasn't already sent back * a socks reply, send one now (based on endreason). Also set @@ -80,6 +81,7 @@ connection_edge_reached_eof(edge_connection_t *conn) /** Handle new bytes on conn->inbuf based on state: * - If it's waiting for socks info, try to read another step of the * socks handshake out of conn->inbuf. + * - If it's waiting for the original destination, fetch it. * - If it's open, then package more relay cells from the stream. * - Else, leave the bytes on inbuf alone for now. * @@ -98,6 +100,12 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) return -1; } return 0; + case AP_CONN_STATE_ORIGDST_WAIT: + if (connection_ap_process_transparent(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) { @@ -234,6 +242,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_ORIGDST_WAIT: case AP_CONN_STATE_RENDDESC_WAIT: case AP_CONN_STATE_CIRCUIT_WAIT: case AP_CONN_STATE_CONNECT_WAIT: @@ -1216,6 +1225,89 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, return 0; /* unreached but keeps the compiler happy */ } +/** Fetch the original destination address and port from a + * system-specific interface and put them into a + * socks_request_t as if they came from a socks request. + * + * Return -1 if an error prevents fetching the destination, + * else return 0. + */ +static int +connection_ap_get_original_destination(edge_connection_t *conn, + socks_request_t *req) +{ +#ifdef TRANS_NETFILTER + /* Linux 2.4+ */ + struct sockaddr_in orig_dst; + socklen_t orig_dst_len = sizeof(orig_dst); + char tmpbuf[INET_NTOA_BUF_LEN]; + + if (getsockopt(conn->_base.s, SOL_IP, SO_ORIGINAL_DST, + (struct sockaddr*)&orig_dst, &orig_dst_len) < 0) { + int e = tor_socket_errno(conn->_base.s); + log_warn(LD_NET, "getsockopt() failed: %s", tor_socket_strerror(e)); + return -1; + } + + tor_inet_ntoa(&orig_dst.sin_addr, tmpbuf, sizeof(tmpbuf)); + strlcpy(req->address, tmpbuf, sizeof(req->address)); + req->port = ntohs(orig_dst.sin_port); +#endif + +#ifdef TRANS_PF + struct sockaddr_in proxy_addr; + socklen_t proxy_addr_len = sizeof(proxy_addr); + char tmpbuf[INET_NTOA_BUF_LEN]; + struct pfioc_natlook pnl; + int pf = -1; + + if (getsockname(conn->_base.s, (struct sockaddr*)&proxy_addr, + &proxy_addr_len) < 0) { + int e = tor_socket_errno(conn->_base.s); + log_warn(LD_NET, "getsockname() failed: %s", tor_socket_strerror(e)); + return -1; + } + + memset(&pnl, 0, sizeof(pnl)); + pnl.af = AF_INET; + pnl.proto = IPPROTO_TCP; + pnl.direction = PF_OUT; + pnl.saddr.v4.s_addr = htonl(conn->_base.addr); + pnl.sport = htons(conn->_base.port); + pnl.daddr.v4.s_addr = proxy_addr.sin_addr.s_addr; + pnl.dport = proxy_addr.sin_port; + + /* XXX We should open the /dev/pf device once and close it at cleanup time + * instead of reopening it for every connection. Ideally, it should be + * opened before dropping privs. */ +#ifdef OPENBSD + /* only works on OpenBSD */ + pf = open("/dev/pf", O_RDONLY); +#else + /* works on NetBSD and FreeBSD */ + pf = open("/dev/pf", O_RDWR); +#endif + + if (pf < 0) { + log_warn(LD_NET, "open(\"/dev/pf\") failed: %s", strerror(errno)); + return -1; + } + + if (ioctl(pf, DIOCNATLOOK, &pnl) < 0) { + log_warn(LD_NET, "ioctl(DIOCNATLOOK) failed: %s", strerror(errno)); + close(pf); + return -1; + } + close(pf); + + tor_inet_ntoa(&pnl.rdaddr.v4, tmpbuf, sizeof(tmpbuf)); + strlcpy(req->address, tmpbuf, sizeof(req->address)); + req->port = ntohs(pnl.rdport); +#endif + + return 0; +} + /** connection_edge_process_inbuf() found a conn in state * socks_wait. See if conn->inbuf has the right bytes to proceed with * the socks handshake. @@ -1278,6 +1370,48 @@ connection_ap_handshake_process_socks(edge_connection_t *conn) return connection_ap_handshake_rewrite_and_attach(conn, NULL); } +/** connection_edge_process_inbuf() found a conn in state + * origdst_wait. Get the original destination and + * 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_transparent(edge_connection_t *conn) +{ + 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_ORIGDST_WAIT); + tor_assert(conn->socks_request); + socks = conn->socks_request; + + /* pretend that a socks handshake completed so we don't try to + * send a socks reply down a transparent conn */ + socks->command = SOCKS_COMMAND_CONNECT; + socks->has_finished = 1; + + log_debug(LD_APP,"entered."); + + if (connection_ap_get_original_destination(conn, socks) < 0) { + log_warn(LD_APP,"Fetching original destination failed. Closing."); + connection_mark_unattached_ap(conn, 0); + return -1; + } + /* we have the original destination */ + + control_event_stream_status(conn, STREAM_EVENT_NEW); + + if (options->LeaveStreamsUnattached) { + conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT; + return 0; + } + 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 757a078e5f..28b87c21af 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -1423,7 +1423,8 @@ handle_getinfo_helper(const char *question, char **answer) 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_ORIGDST_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 a1aca0327a..93c4cbe553 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -712,6 +712,7 @@ hibernate_begin(int new_state, time_t now) /* close listeners. leave control listener(s). */ 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_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 fe4651dfeb..24701301fb 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -113,6 +113,15 @@ #error "Tor requires libevent to build." #endif +#ifdef TRANS_NETFILTER +#include <linux/netfilter_ipv4.h> +#endif + +#ifdef TRANS_PF +#include <net/if.h> +#include <net/pfvar.h> +#endif + #include "../common/crypto.h" #include "../common/tortls.h" #include "../common/log.h" @@ -215,7 +224,9 @@ typedef enum { #define CONN_TYPE_CONTROL_LISTENER 12 /** Type for connections from user interface process. */ #define CONN_TYPE_CONTROL 13 -#define _CONN_TYPE_MAX 13 +/** Type for sockets listening for transparent proxy connections. */ +#define CONN_TYPE_AP_TRANS_LISTENER 14 +#define _CONN_TYPE_MAX 14 #define CONN_IS_EDGE(x) \ ((x)->type == CONN_TYPE_EXIT || (x)->type == CONN_TYPE_AP) @@ -283,7 +294,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 proxy connection: waiting for original + * destination. */ +#define AP_CONN_STATE_ORIGDST_WAIT 12 +#define _AP_CONN_STATE_MAX 12 #define _DIR_CONN_STATE_MIN 1 /** State for connection to directory server: waiting for connect(). */ @@ -1386,6 +1400,8 @@ 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. */ + config_line_t *TransListenAddress; /** Addresses to bind for listening for OR connections. */ config_line_t *ORListenAddress; /** Addresses to bind for listening for directory connections. */ @@ -1407,6 +1423,7 @@ 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. */ 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. */ diff --git a/src/or/relay.c b/src/or/relay.c index f7eee2917b..666cd891a1 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -845,7 +845,9 @@ connection_edge_process_relay_cell_not_open( conn->chosen_exit_name, ttl); } circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ)); - connection_ap_handshake_socks_reply(conn, NULL, 0, SOCKS5_SUCCEEDED); + /* don't send a socks reply to transparent conns */ + if (!conn->socks_request->has_finished) + connection_ap_handshake_socks_reply(conn, NULL, 0, SOCKS5_SUCCEEDED); /* handle anything that might have queued */ if (connection_edge_package_raw_inbuf(conn, 1) < 0) { /* (We already sent an end cell if possible) */ |