r8894@Kushana: nickm | 2006-09-21 18:30:42 -0400

Specify and implement SOCKS5 interface for reverse hostname lookup.


svn:r8451
This commit is contained in:
Nick Mathewson 2006-09-22 00:43:55 +00:00
parent 9bc8d69dfc
commit 213658f117
7 changed files with 120 additions and 38 deletions

View File

@ -6,6 +6,8 @@ Changes in version 0.1.2.2-alpha - 2006-??-??
previously never implemented. This is only supported by eventdns; previously never implemented. This is only supported by eventdns;
servers now announce in their descriptors whether they support servers now announce in their descriptors whether they support
eventdns. eventdns.
- Specify and implement client-side SOCKS5 interface for reverse DNS
lookups; see doc/socks-extensions.txt for full information.
o Minor features: o Minor features:
- Check for name servers (like Earthlink's) that hijack failing DNS - Check for name servers (like Earthlink's) that hijack failing DNS

View File

@ -32,15 +32,14 @@ def socks5Hello():
def socks5ParseHello(response): def socks5ParseHello(response):
if response != "\x05\x00": if response != "\x05\x00":
raise ValueError("Bizarre socks5 response") raise ValueError("Bizarre socks5 response")
def socks5ResolveRequest(hostname): def socks5ResolveRequest(hostname, atype=0x03, command=0xF0):
version = 5 version = 5
command = 0xF0
rsv = 0 rsv = 0
port = 0 port = 0
atype = 0x03
reqheader = struct.pack("!BBBBB",version, command, rsv, atype, len(hostname)) reqheader = struct.pack("!BBBBB",version, command, rsv, atype, len(hostname))
portstr = struct.pack("!H",port) portstr = struct.pack("!H",port)
return "%s%s%s"%(reqheader,hostname,portstr) return "%s%s%s"%(reqheader,hostname,portstr)
def socks5ParseResponse(r): def socks5ParseResponse(r):
if len(r)<8: if len(r)<8:
return None return None
@ -49,18 +48,27 @@ def socks5ParseResponse(r):
assert rsv==0 assert rsv==0
if reply != 0x00: if reply != 0x00:
return "ERROR",reply return "ERROR",reply
assert atype in (0x01,0x04) assert atype in (0x01,0x03,0x04)
expected_len = 4 + ({1:4,4:16}[atype]) + 2 if atype != 0x03:
if len(r) < expected_len: expected_len = 4 + ({1:4,4:16}[atype]) + 2
return None if len(r) < expected_len:
elif len(r) > expected_len: return None
raise ValueError("Overlong socks5 reply!") elif len(r) > expected_len:
addr = r[4:-2] raise ValueError("Overlong socks5 reply!")
if atype == 0x01: addr = r[4:-2]
return "%d.%d.%d.%d"%tuple(map(ord,addr)) if atype == 0x01:
return "%d.%d.%d.%d"%tuple(map(ord,addr))
else:
# not really the right way to format IPv6
return "IPv6: %s"%(":".join([hex(ord(c)) for c in addr]))
else: else:
# not really the right way to format IPv6 nul = r.index('\0',4)
return "IPv6: %s"%(":".join([hex(ord(c)) for c in addr])) return r[4:nul]
def socks5ResolvePTRRequest(hostname):
return socks5ResolveRequest(socket.inet_aton(hostname),
atype=1, command = 0xF1)
def parseHostAndPort(h): def parseHostAndPort(h):
host, port = "localhost", 9050 host, port = "localhost", 9050
@ -80,14 +88,18 @@ def parseHostAndPort(h):
return host, port return host, port
def resolve(hostname, sockshost, socksport, socksver=4): def resolve(hostname, sockshost, socksport, socksver=4, reverse=0):
assert socksver in (4,5) assert socksver in (4,5)
if socksver == 4: if socksver == 4:
fmt = socks4AResolveRequest fmt = socks4AResolveRequest
parse = socks4AParseResponse parse = socks4AParseResponse
else: elif not reverse:
fmt = socks5ResolveRequest fmt = socks5ResolveRequest
parse = socks5ParseResponse parse = socks5ParseResponse
else:
fmt = socks5ResolvePTRRequest
parse = socks5ParseResponse
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sockshost,socksport)) s.connect((sockshost,socksport))
if socksver == 5: if socksver == 5:
@ -114,14 +126,25 @@ if __name__ == '__main__':
print "Syntax: resolve.py [-4|-5] hostname [sockshost:socksport]" print "Syntax: resolve.py [-4|-5] hostname [sockshost:socksport]"
sys.exit(0) sys.exit(0)
socksver = 4 socksver = 4
if sys.argv[1] in ("-4", "-5"): reverse = 0
socksver = int(sys.argv[1][1]) while sys.argv[1] == '-':
del sys.argv[1] if sys.argv[1] in ("-4", "-5"):
if len(sys.argv) == 4: socksver = int(sys.argv[1][1])
del sys.argv[1]
elif sys.argv[1] == '-x':
reverse = 1
del sys.argv[1]
elif sys.argv[1] == '--':
break
if len(sys.argv) >= 4:
print "Syntax: resolve.py [-4|-5] hostname [sockshost:socksport]" print "Syntax: resolve.py [-4|-5] hostname [sockshost:socksport]"
sys.exit(0) sys.exit(0)
if len(sys.argv) == 3: if len(sys.argv) == 3:
sh,sp = parseHostAndPort(sys.argv[2]) sh,sp = parseHostAndPort(sys.argv[2])
else: else:
sh,sp = parseHostAndPort("") sh,sp = parseHostAndPort("")
resolve(sys.argv[1], sh, sp, socksver)
if reverse and socksver == 4:
socksver = 5
resolve(sys.argv[1], sh, sp, socksver, reverse)

View File

@ -109,7 +109,12 @@ d - Special-case localhost?
o Connect to resolve cells, server-side. o Connect to resolve cells, server-side.
o Add element to routerinfo to note routers that aren't using eventdns, o Add element to routerinfo to note routers that aren't using eventdns,
so we can avoid sending them reverse DNS etc. so we can avoid sending them reverse DNS etc.
- Add client-side interface . Add client-side interface
o SOCKS interface: specify
o SOCKS interface: implement
- Cache answers client-side
o Add to Tor-resolve.py
- Add to tor-resolve
- Performance improvements - Performance improvements

View File

@ -46,6 +46,12 @@ Tor's extensions to the SOCKS protocol
(We support RESOLVE in SOCKS4 too, even though it is unnecessary.) (We support RESOLVE in SOCKS4 too, even though it is unnecessary.)
For SOCKS5 only, we support reverse resolution with a new command value,
"RESOLVE_PTR". In response to a "RESOLVE_PTR" SOCKS5 command with an IPv4
address as its target, Tor attempts to find the canonical hostname for that
IPv4 record, and returns it in the "server bound address" portion of the
reply. (This was not supported before Tor 0.1.2.2-alpha)
3. HTTP-resistance 3. HTTP-resistance
Tor checks the first byte of each SOCKS request to see whether it looks Tor checks the first byte of each SOCKS request to see whether it looks

View File

@ -974,8 +974,9 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
return 0; /* not yet */ return 0; /* not yet */
req->command = (unsigned char) *(buf->cur+1); req->command = (unsigned char) *(buf->cur+1);
if (req->command != SOCKS_COMMAND_CONNECT && if (req->command != SOCKS_COMMAND_CONNECT &&
req->command != SOCKS_COMMAND_RESOLVE) { req->command != SOCKS_COMMAND_RESOLVE &&
/* not a connect or resolve? we don't support it. */ req->command != SOCKS_COMMAND_RESOLVE_PTR) {
/* not a connect or resolve or a resolve_ptr? we don't support it. */
log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.",
req->command); req->command);
return -1; return -1;
@ -999,7 +1000,8 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
strlcpy(req->address,tmpbuf,sizeof(req->address)); strlcpy(req->address,tmpbuf,sizeof(req->address));
req->port = ntohs(*(uint16_t*)(buf->cur+8)); req->port = ntohs(*(uint16_t*)(buf->cur+8));
buf_remove_from_front(buf, 10); buf_remove_from_front(buf, 10);
if (!addressmap_have_mapping(req->address) && if (req->command != SOCKS_COMMAND_RESOLVE_PTR &&
!addressmap_have_mapping(req->address) &&
!have_warned_about_unsafe_socks) { !have_warned_about_unsafe_socks) {
log_warn(LD_APP, log_warn(LD_APP,
"Your application (using socks5 on port %d) is giving " "Your application (using socks5 on port %d) is giving "
@ -1025,6 +1027,11 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
"%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN);
return -1; return -1;
} }
if (req->command == SOCKS_COMMAND_RESOLVE_PTR) {
log_warn(LD_APP, "socks5 received RESOLVE_PTR command with "
"hostname type. Rejecting.");
return -1;
}
memcpy(req->address,buf->cur+5,len); memcpy(req->address,buf->cur+5,len);
req->address[len] = 0; req->address[len] = 0;
req->port = ntohs(get_uint16(buf->cur+5+len)); req->port = ntohs(get_uint16(buf->cur+5+len));
@ -1059,7 +1066,8 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
req->command = (unsigned char) *(buf->cur+1); req->command = (unsigned char) *(buf->cur+1);
if (req->command != SOCKS_COMMAND_CONNECT && if (req->command != SOCKS_COMMAND_CONNECT &&
req->command != SOCKS_COMMAND_RESOLVE) { req->command != SOCKS_COMMAND_RESOLVE) {
/* not a connect or resolve? we don't support it. */ /* not a connect or resolve? we don't support it. (No resolve_ptr with
* socks4.) */
log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.",
req->command); req->command);
return -1; return -1;

View File

@ -1120,7 +1120,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
return -1; return -1;
} }
if (socks->command == SOCKS_COMMAND_RESOLVE) { if (socks->command == SOCKS_COMMAND_RESOLVE) { // resolve_ptr XXXX NM
uint32_t answer; uint32_t answer;
struct in_addr in; struct in_addr in;
/* Reply to resolves immediately if we can. */ /* Reply to resolves immediately if we can. */
@ -1181,7 +1181,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
rend_cache_entry_t *entry; rend_cache_entry_t *entry;
int r; int r;
if (socks->command == SOCKS_COMMAND_RESOLVE) { if (socks->command != SOCKS_COMMAND_CONNECT) {
/* if it's a resolve request, fail it right now, rather than /* if it's a resolve request, fail it right now, rather than
* building all the circuits and then realizing it won't work. */ * building all the circuits and then realizing it won't work. */
log_warn(LD_APP, log_warn(LD_APP,
@ -1524,13 +1524,17 @@ int
connection_ap_handshake_send_resolve(edge_connection_t *ap_conn, connection_ap_handshake_send_resolve(edge_connection_t *ap_conn,
origin_circuit_t *circ) origin_circuit_t *circ)
{ {
int payload_len; int payload_len, command;
const char *string_addr; const char *string_addr;
char inaddr_buf[32];
command = ap_conn->socks_request->command;
tor_assert(ap_conn->_base.type == CONN_TYPE_AP); tor_assert(ap_conn->_base.type == CONN_TYPE_AP);
tor_assert(ap_conn->_base.state == AP_CONN_STATE_CIRCUIT_WAIT); tor_assert(ap_conn->_base.state == AP_CONN_STATE_CIRCUIT_WAIT);
tor_assert(ap_conn->socks_request); tor_assert(ap_conn->socks_request);
tor_assert(ap_conn->socks_request->command == SOCKS_COMMAND_RESOLVE); tor_assert(command == SOCKS_COMMAND_RESOLVE ||
command == SOCKS_COMMAND_RESOLVE_PTR);
tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_GENERAL); tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_GENERAL);
ap_conn->stream_id = get_unique_stream_id_by_circ(circ); ap_conn->stream_id = get_unique_stream_id_by_circ(circ);
@ -1540,9 +1544,27 @@ connection_ap_handshake_send_resolve(edge_connection_t *ap_conn,
return -1; return -1;
} }
string_addr = ap_conn->socks_request->address; if (command == SOCKS_COMMAND_RESOLVE) {
payload_len = strlen(string_addr)+1; string_addr = ap_conn->socks_request->address;
tor_assert(payload_len <= RELAY_PAYLOAD_SIZE); payload_len = strlen(string_addr)+1;
tor_assert(payload_len <= RELAY_PAYLOAD_SIZE);
} else {
struct in_addr in;
uint32_t a;
if (tor_inet_aton(ap_conn->socks_request->address, &in) == 0) {
connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL);
return -1;
}
a = ntohl(in.s_addr);
tor_snprintf(inaddr_buf, sizeof(inaddr_buf), "%d.%d.%d.%d.in-addr.arpa",
(int)(uint8_t)((a )&0xff),
(int)(uint8_t)((a>>8 )&0xff),
(int)(uint8_t)((a>>16)&0xff),
(int)(uint8_t)((a>>24)&0xff));
string_addr = inaddr_buf;
payload_len = strlen(inaddr_buf)+1;
tor_assert(payload_len <= RELAY_PAYLOAD_SIZE);
}
log_debug(LD_APP, log_debug(LD_APP,
"Sending relay cell to begin stream %d.", ap_conn->stream_id); "Sending relay cell to begin stream %d.", ap_conn->stream_id);
@ -1625,7 +1647,7 @@ connection_ap_make_bridge(char *address, uint16_t port)
} }
/** Send an answer to an AP connection that has requested a DNS lookup /** Send an answer to an AP connection that has requested a DNS lookup
* via SOCKS. The type should be one of RESOLVED_TYPE_(IPV4|IPV6) or * via SOCKS. The type should be one of RESOLVED_TYPE_(IPV4|IPV6|HOSTNAME) or
* -1 for unreachable; the answer should be in the format specified * -1 for unreachable; the answer should be in the format specified
* in the socks extensions document. * in the socks extensions document.
**/ **/
@ -1636,7 +1658,7 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn,
const char *answer, const char *answer,
int ttl) int ttl)
{ {
char buf[256]; char buf[384];
size_t replylen; size_t replylen;
if (answer_type == RESOLVED_TYPE_IPV4) { if (answer_type == RESOLVED_TYPE_IPV4) {
@ -1675,6 +1697,14 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn,
memcpy(buf+4, answer, 16); /* address */ memcpy(buf+4, answer, 16); /* address */
set_uint16(buf+20, 0); /* port == 0. */ set_uint16(buf+20, 0); /* port == 0. */
replylen = 22; replylen = 22;
} else if (answer_type == RESOLVED_TYPE_HOSTNAME && answer_len < 256) {
buf[1] = SOCKS5_SUCCEEDED;
buf[2] = 0; /* reserved */
buf[3] = 0x03; /* Domainname address type */
memcpy(buf+4, answer, answer_len); /* address */
buf[4+answer_len] = '\0';
set_uint16(buf+4+answer_len+1, 0); /* port == 0. */
replylen = 4+answer_len+1+2;
} else { } else {
buf[1] = SOCKS5_HOST_UNREACHABLE; buf[1] = SOCKS5_HOST_UNREACHABLE;
memset(buf+2, 0, 8); memset(buf+2, 0, 8);
@ -1699,7 +1729,8 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn,
void void
connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply, connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply,
size_t replylen, size_t replylen,
socks5_reply_status_t status) { socks5_reply_status_t status)
{
char buf[256]; char buf[256];
tor_assert(conn->socks_request); /* make sure it's an AP stream */ tor_assert(conn->socks_request); /* make sure it's an AP stream */
@ -2072,7 +2103,7 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit)
} }
} }
if (conn->socks_request->command != SOCKS_COMMAND_RESOLVE) { if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) {
struct in_addr in; struct in_addr in;
uint32_t addr = 0; uint32_t addr = 0;
addr_policy_result_t r; addr_policy_result_t r;
@ -2082,7 +2113,13 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit)
exit->exit_policy); exit->exit_policy);
if (r == ADDR_POLICY_REJECTED || r == ADDR_POLICY_PROBABLY_REJECTED) if (r == ADDR_POLICY_REJECTED || r == ADDR_POLICY_PROBABLY_REJECTED)
return 0; return 0;
} else { } else { /* Some kind of a resolve. */
/* Can't support reverse lookups without eventdns. */
if (conn->socks_request->command == SOCKS_COMMAND_RESOLVE_PTR &&
exit->has_old_dnsworkers)
return 0;
/* Don't send DNS requests to non-exit servers by default. */ /* Don't send DNS requests to non-exit servers by default. */
if (policy_is_reject_star(exit->exit_policy)) if (policy_is_reject_star(exit->exit_policy))
return 0; return 0;

View File

@ -1612,6 +1612,7 @@ typedef struct {
#define MAX_SOCKS_ADDR_LEN 256 #define MAX_SOCKS_ADDR_LEN 256
#define SOCKS_COMMAND_CONNECT 0x01 #define SOCKS_COMMAND_CONNECT 0x01
#define SOCKS_COMMAND_RESOLVE 0xF0 #define SOCKS_COMMAND_RESOLVE 0xF0
#define SOCKS_COMMAND_RESOLVE_PTR 0xF1
/** State of a SOCKS request from a user to an OP */ /** State of a SOCKS request from a user to an OP */
struct socks_request_t { struct socks_request_t {
char socks_version; /**< Which version of SOCKS did the client use? */ char socks_version; /**< Which version of SOCKS did the client use? */