diff options
Diffstat (limited to 'src/or/connection_edge.c')
-rw-r--r-- | src/or/connection_edge.c | 134 |
1 files changed, 134 insertions, 0 deletions
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. */ |