Ticket #17975: patch_2016_12_20.txt

File patch_2016_12_20.txt, 14.5 KB (added by michaelsonntag, 11 months ago)

Patch suggestion (v3)

Line 
1diff --git a/change_separate_exit_and_relay.txt b/change_separate_exit_and_relay.txt
2new file mode 100644
3index 0000000..28db1d2
4--- /dev/null
5+++ b/change_separate_exit_and_relay.txt
6@@ -0,0 +1,2 @@
7+- Minor features:
8+    - Allow separation of exit and relay traffic to different source IP addresses (Ticket #17975). Written by Michael Sonntag.
9diff --git a/doc/tor.1.txt b/doc/tor.1.txt
10index 36a67ff..a0aefbd 100644
11--- a/doc/tor.1.txt
12+++ b/doc/tor.1.txt
13@@ -625,6 +625,20 @@ GENERAL OPTIONS
14     This setting will be ignored for connections to the loopback addresses
15     (127.0.0.0/8 and ::1).
16 
17+[[OutboundBindAddressOR]] **OutboundBindAddressOR** __IP__::
18+    Make all outbound non-exit (=relay and other) connections originate from the IP
19+    address specified. This option overrides **OutboundBindAddress** for the same
20+    IP version. This option may be used twice, once with an IPv4 address and once
21+    with an IPv6 address. This setting will be ignored for connections to the
22+    loopback addresses (127.0.0.0/8 and ::1).
23+
24+[[OutboundBindAddressExit]] **OutboundBindAddressExit** __IP__::
25+    Make all outbound exit connections originate from the IP address specified. This
26+    option overrides **OutboundBindAddress** for the same IP version. This option
27+    may be used twice, once with an IPv4 address and once with an IPv6 address. This
28+    setting will be ignored for connections to the loopback addresses (127.0.0.0/8
29+    and ::1).
30+
31 [[PidFile]] **PidFile** __FILE__::
32     On startup, write our PID to FILE. On clean shutdown, remove
33     FILE.
34diff --git a/src/config/torrc.sample.in b/src/config/torrc.sample.in
35index 5328206..3777744 100644
36--- a/src/config/torrc.sample.in
37+++ b/src/config/torrc.sample.in
38@@ -95,7 +95,12 @@
39 
40 ## If you have multiple network interfaces, you can specify one for
41 ## outgoing traffic to use.
42-# OutboundBindAddress 10.0.0.5
43+## OutboundBindAddressExit will be used for all exit traffic, while
44+## OutboundBindAddressOR will be used for all other connections.
45+## If you do not wish to differentiate, use OutboundBindAddress to
46+## specify the same address for both in a single line.
47+#OutboundBindAddressExit 10.0.0.4
48+#OutboundBindAddressOR 10.0.0.5
49 
50 ## A handle for your relay, so people don't have to refer to it by key.
51 ## Nicknames must be between 1 and 19 characters inclusive, and must
52diff --git a/src/or/config.c b/src/or/config.c
53index 7f789c4..61f51dd 100644
54--- a/src/or/config.c
55+++ b/src/or/config.c
56@@ -405,6 +405,8 @@ static config_var_t option_vars_[] = {
57   V(ORListenAddress,             LINELIST, NULL),
58   VPORT(ORPort,                      LINELIST, NULL),
59   V(OutboundBindAddress,         LINELIST,   NULL),
60+  V(OutboundBindAddressOR,       LINELIST,   NULL),
61+  V(OutboundBindAddressExit,     LINELIST,   NULL),
62 
63   OBSOLETE("PathBiasDisableRate"),
64   V(PathBiasCircThreshold,       INT,      "-1"),
65@@ -7941,60 +7943,79 @@ getinfo_helper_config(control_connection_t *conn,
66   return 0;
67 }
68 
69-/** Parse outbound bind address option lines. If <b>validate_only</b>
70- * is not 0 update OutboundBindAddressIPv4_ and
71- * OutboundBindAddressIPv6_ in <b>options</b>. On failure, set
72- * <b>msg</b> (if provided) to a newly allocated string containing a
73- * description of the problem and return -1. */
74+/* Check whether an address has already been set against the options
75+ * depending on address family and destination type. Any exsting
76+ * value will lead to a fail, even if it is the same value. If not
77+ * set and not only validating, copy it into this location too.
78+ * Returns 0 on success or -1 if this address is already set.
79+ */
80 static int
81-parse_outbound_addresses(or_options_t *options, int validate_only, char **msg)
82+verify_and_store_address(sa_family_t family, tor_addr_t *addr,
83+       outbound_addr_t type, or_options_t *options, int validate_only)
84 {
85-  const config_line_t *lines = options->OutboundBindAddress;
86-  int found_v4 = 0, found_v6 = 0;
87-
88+  if (type<0 || type>=OUTBOUND_MAX || (family!=AF_INET && family!=AF_INET6)) {
89+    return -1;
90+  }
91+  tor_addr_t *dest=&options->OutboundBindAddresses[type][family];
92+  if (!tor_addr_is_null(dest)) {
93+    return -1;
94+  }
95   if (!validate_only) {
96-    memset(&options->OutboundBindAddressIPv4_, 0,
97-           sizeof(options->OutboundBindAddressIPv4_));
98-    memset(&options->OutboundBindAddressIPv6_, 0,
99-           sizeof(options->OutboundBindAddressIPv6_));
100+    tor_addr_copy(dest, addr);
101   }
102+  return 0;
103+}
104+
105+/* Parse a list of address lines for a specific destination type.
106+ * Will store them into the options if not validate_only. If a
107+ * problem occurs, a suitable error message is store in msg.
108+ * Returns 0 on success or -1 if any address is already set.
109+ */
110+static int
111+parse_address_lines(const config_line_t *lines, outbound_addr_t type,
112+           or_options_t *options, int validate_only, char **msg)
113+{
114+  tor_addr_t addr;
115+  sa_family_t family;
116   while (lines) {
117-    tor_addr_t addr, *dst_addr = NULL;
118-    int af = tor_addr_parse(&addr, lines->value);
119-    switch (af) {
120-    case AF_INET:
121-      if (found_v4) {
122-        if (msg)
123-          tor_asprintf(msg, "Multiple IPv4 outbound bind addresses "
124-                       "configured: %s", lines->value);
125-        return -1;
126-      }
127-      found_v4 = 1;
128-      dst_addr = &options->OutboundBindAddressIPv4_;
129-      break;
130-    case AF_INET6:
131-      if (found_v6) {
132-        if (msg)
133-          tor_asprintf(msg, "Multiple IPv6 outbound bind addresses "
134-                       "configured: %s", lines->value);
135-        return -1;
136-      }
137-      found_v6 = 1;
138-      dst_addr = &options->OutboundBindAddressIPv6_;
139-      break;
140-    default:
141+    family = tor_addr_parse(&addr, lines->value);
142+    if (verify_and_store_address(family, &addr, type,
143+                                 options, validate_only)) {
144       if (msg)
145-        tor_asprintf(msg, "Outbound bind address '%s' didn't parse.",
146+        tor_asprintf(msg, "Multiple%s%s outbound bind addresses "
147+                     "configured: %s",
148+                     family==AF_INET?" IPv4":(family==AF_INET6?" IPv6":""),
149+                     type==OR?" OR":(type==EXIT?" exit":""),
150                      lines->value);
151       return -1;
152     }
153-    if (!validate_only)
154-      tor_addr_copy(dst_addr, &addr);
155     lines = lines->next;
156   }
157   return 0;
158 }
159 
160+/** Parse outbound bind address option lines. If <b>validate_only</b>
161+ * is not 0 update OutboundBindAddresses in <b>options</b>.
162+ * Only one address can be set for any of these values.
163+ * On failure, set <b>msg</b> (if provided) to a newly allocated string
164+ * containing a description of the problem and return -1.
165+ */
166+static int
167+parse_outbound_addresses(or_options_t *options, int validate_only, char **msg)
168+{
169+  if (!validate_only) {
170+    memset(&options->OutboundBindAddresses, 0,
171+           sizeof(tor_addr_t)*OUTBOUND_MAX*AF_MAX);
172+  }
173+  parse_address_lines(options->OutboundBindAddress, EXIT_AND_OR,
174+                      options, validate_only, msg);
175+  parse_address_lines(options->OutboundBindAddressOR, OR,
176+                      options, validate_only, msg);
177+  parse_address_lines(options->OutboundBindAddressExit, EXIT,
178+                      options, validate_only, msg);
179+  return 0;
180+}
181+
182 /** Load one of the geoip files, <a>family</a> determining which
183  * one. <a>default_fname</a> is used if on Windows and
184  * <a>fname</a> equals "<default>". */
185diff --git a/src/or/connection.c b/src/or/connection.c
186index 5b22594..19f0866 100644
187--- a/src/or/connection.c
188+++ b/src/or/connection.c
189@@ -134,6 +134,8 @@ static int connection_read_https_proxy_response(connection_t *conn);
190 static void connection_send_socks5_connect(connection_t *conn);
191 static const char *proxy_type_to_string(int proxy_type);
192 static int get_proxy_type(void);
193+const tor_addr_t *conn_get_outbound_address(sa_family_t family,
194+                  const or_options_t *options, unsigned int conn_type);
195 
196 /** The last addresses that our network interface seemed to have been
197  * binding to.  We use this as one way to detect when our IP changes.
198@@ -1771,7 +1773,7 @@ connection_connect_sockaddr,(connection_t *conn,
199 
200   /*
201    * We've got the socket open; give the OOS handler a chance to check
202-   * against configuured maximum socket number, but tell it no exhaustion
203+   * against configured maximum socket number, but tell it no exhaustion
204    * failure.
205    */
206   connection_check_oos(get_n_open_sockets(), 0);
207@@ -1890,6 +1892,35 @@ connection_connect_log_client_use_ip_version(const connection_t *conn)
208   }
209 }
210 
211+/** Retrieve the outbound address depending on the protocol (IPv4 or IPv6)
212+ * and the connection type (relay, exit, ...)
213+ * Return a socket address or NULL in case nothing is configured.
214+ **/
215+const tor_addr_t *
216+conn_get_outbound_address(sa_family_t family,
217+             const or_options_t *options, unsigned int conn_type)
218+{
219+  const tor_addr_t *ext_addr = NULL;
220+
221+  // If an exit connection, use the exit address (if present)
222+  if (conn_type == CONN_TYPE_EXIT) {
223+    if (!tor_addr_is_null(&options->OutboundBindAddresses[EXIT][family])) {
224+      ext_addr = &options->OutboundBindAddresses[EXIT][family];
225+    } else if (!tor_addr_is_null(
226+                 &options->OutboundBindAddresses[EXIT_AND_OR][family])) {
227+      ext_addr = &options->OutboundBindAddresses[EXIT_AND_OR][family];
228+    }
229+  } else { // All non-exit connections
230+    if (!tor_addr_is_null(&options->OutboundBindAddresses[OR][family])) {
231+      ext_addr = &options->OutboundBindAddresses[OR][family];
232+    } else if (!tor_addr_is_null(
233+                 &options->OutboundBindAddresses[EXIT_AND_OR][family])) {
234+      ext_addr = &options->OutboundBindAddresses[EXIT_AND_OR][family];
235+    }
236+  }
237+  return ext_addr;
238+}
239+
240 /** Take conn, make a nonblocking socket; try to connect to
241  * addr:port (port arrives in *host order*). If fail, return -1 and if
242  * applicable put your best guess about errno into *<b>socket_error</b>.
243@@ -1911,26 +1942,15 @@ connection_connect(connection_t *conn, const char *address,
244   struct sockaddr *bind_addr = NULL;
245   struct sockaddr *dest_addr;
246   int dest_addr_len, bind_addr_len = 0;
247-  const or_options_t *options = get_options();
248-  int protocol_family;
249 
250   /* Log if we didn't stick to ClientUseIPv4/6 or ClientPreferIPv6OR/DirPort
251    */
252   connection_connect_log_client_use_ip_version(conn);
253 
254-  if (tor_addr_family(addr) == AF_INET6)
255-    protocol_family = PF_INET6;
256-  else
257-    protocol_family = PF_INET;
258-
259   if (!tor_addr_is_loopback(addr)) {
260     const tor_addr_t *ext_addr = NULL;
261-    if (protocol_family == AF_INET &&
262-        !tor_addr_is_null(&options->OutboundBindAddressIPv4_))
263-      ext_addr = &options->OutboundBindAddressIPv4_;
264-    else if (protocol_family == AF_INET6 &&
265-             !tor_addr_is_null(&options->OutboundBindAddressIPv6_))
266-      ext_addr = &options->OutboundBindAddressIPv6_;
267+    ext_addr = conn_get_outbound_address(tor_addr_family(addr), get_options(),
268+                                         conn->type);
269     if (ext_addr) {
270       memset(&bind_addr_ss, 0, sizeof(bind_addr_ss));
271       bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0,
272diff --git a/src/or/or.h b/src/or/or.h
273index 00031a8..31e8666 100644
274--- a/src/or/or.h
275+++ b/src/or/or.h
276@@ -3557,6 +3557,10 @@ typedef struct routerset_t routerset_t;
277  * to pick its own port. */
278 #define CFG_AUTO_PORT 0xc4005e
279 
280+/** Enumeration of outbound address configuration types:
281+ * Exit-only, OR-only, or both */
282+typedef enum {EXIT, OR, EXIT_AND_OR, OUTBOUND_MAX} outbound_addr_t;
283+
284 /** Configuration options for a Tor process. */
285 typedef struct {
286   uint32_t magic_;
287@@ -3640,10 +3644,12 @@ typedef struct {
288   config_line_t *ControlListenAddress;
289   /** Local address to bind outbound sockets */
290   config_line_t *OutboundBindAddress;
291-  /** IPv4 address derived from OutboundBindAddress. */
292-  tor_addr_t OutboundBindAddressIPv4_;
293-  /** IPv6 address derived from OutboundBindAddress. */
294-  tor_addr_t OutboundBindAddressIPv6_;
295+  /** Local address to bind outbound relay sockets */
296+  config_line_t *OutboundBindAddressOR;
297+  /** Local address to bind outbound exit sockets */
298+  config_line_t *OutboundBindAddressExit;
299+  /** Addresses derived from the various OutboundBindAddress lines. */
300+  tor_addr_t OutboundBindAddresses[OUTBOUND_MAX][AF_MAX];
301   /** Directory server only: which versions of
302    * Tor should we tell users to run? */
303   config_line_t *RecommendedVersions;
304diff --git a/src/or/policies.c b/src/or/policies.c
305index f4c0cdd..e6e0b5e 100644
306--- a/src/or/policies.c
307+++ b/src/or/policies.c
308@@ -1976,10 +1976,10 @@ policies_copy_ipv4h_to_smartlist(smartlist_t *addr_list, uint32_t ipv4h_addr)
309   }
310 }
311 
312-/** Helper function that adds copies of
313- * or_options->OutboundBindAddressIPv[4|6]_ to a smartlist as tor_addr_t *, as
314- * long as or_options is non-NULL, and the addresses are not
315- * tor_addr_is_null(), by passing them to policies_add_addr_to_smartlist.
316+/** Helper function that adds copies of or_options->OutboundBindAddresses
317+ * to a smartlist as tor_addr_t *, as long as or_options is non-NULL, and
318+ * the addresses are not tor_addr_is_null(), by passing them to
319+ * policies_add_addr_to_smartlist.
320  *
321  * The caller is responsible for freeing all the tor_addr_t* in the smartlist.
322  */
323@@ -1988,10 +1988,14 @@ policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list,
324                                               const or_options_t *or_options)
325 {
326   if (or_options) {
327-    policies_copy_addr_to_smartlist(addr_list,
328-                                    &or_options->OutboundBindAddressIPv4_);
329-    policies_copy_addr_to_smartlist(addr_list,
330-                                    &or_options->OutboundBindAddressIPv6_);
331+    for (int i=0;i<OUTBOUND_MAX;i++) {
332+      for (int j=0;i<AF_MAX;j++) {
333+        if (!tor_addr_is_null(&or_options->OutboundBindAddresses[i][j])) {
334+          policies_copy_addr_to_smartlist(addr_list,
335+                          &or_options->OutboundBindAddresses[i][j]);
336+        }
337+      }
338+    }
339   }
340 }
341 
342diff --git a/src/test/test_policy.c b/src/test/test_policy.c
343index 4df40f6..905a8f6 100644
344--- a/src/test/test_policy.c
345+++ b/src/test/test_policy.c
346@@ -1083,8 +1083,10 @@ test_policies_getinfo_helper_policies(void *arg)
347   append_exit_policy_string(&mock_my_routerinfo.exit_policy, "reject *6:*");
348 
349   mock_options.IPv6Exit = 1;
350-  tor_addr_from_ipv4h(&mock_options.OutboundBindAddressIPv4_, TEST_IPV4_ADDR);
351-  tor_addr_parse(&mock_options.OutboundBindAddressIPv6_, TEST_IPV6_ADDR);
352+  tor_addr_from_ipv4h(&mock_options.OutboundBindAddresses[EXIT][AF_INET],
353+                      TEST_IPV4_ADDR);
354+  tor_addr_parse(&mock_options.OutboundBindAddresses[EXIT][AF_INET6],
355+                 TEST_IPV6_ADDR);
356 
357   mock_options.ExitPolicyRejectPrivate = 1;
358   mock_options.ExitPolicyRejectLocalInterfaces = 1;