summaryrefslogtreecommitdiff
path: root/src/or/connection_edge.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/or/connection_edge.c')
-rw-r--r--src/or/connection_edge.c134
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.
*/