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);