tor: return the connection's actual remote address rather than the proxy's

In this commit, we fix an issue where connections made through Tor's
SOCKS proxy would result in the remote address being the address of the
proxy itself

We fix this by using an internal proxyConn struct that sets the correct
address at the time of the connection.
This commit is contained in:
Wilmer Paulino 2018-04-30 02:58:12 -04:00
parent 5f1d2524b8
commit 5d29dea21a
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F

@ -40,12 +40,46 @@ var (
} }
) )
// Dial establishes a connection to the address via Tor's SOCKS proxy. Only TCP // proxyConn is a wrapper around net.Conn that allows us to expose the actual
// remote address we're dialing, rather than the proxy's address.
type proxyConn struct {
net.Conn
remoteAddr net.Addr
}
func (c *proxyConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
// Dial is a wrapper over the non-exported dial function that returns a wrapper
// around net.Conn in order to expose the actual remote address we're dialing,
// rather than the proxy's address.
func Dial(address, socksAddr string, streamIsolation bool) (net.Conn, error) {
conn, err := dial(address, socksAddr, streamIsolation)
if err != nil {
return nil, err
}
// Now that the connection is established, we'll create our internal
// proxyConn that will serve in populating the correct remote address
// of the connection, rather than using the proxy's address.
remoteAddr, err := ParseAddr(address, socksAddr)
if err != nil {
return nil, err
}
return &proxyConn{
Conn: conn,
remoteAddr: remoteAddr,
}, nil
}
// dial establishes a connection to the address via Tor's SOCKS proxy. Only TCP
// is supported over Tor. The final argument determines if we should force // is supported over Tor. The final argument determines if we should force
// stream isolation for this new connection. If we do, then this means this new // stream isolation for this new connection. If we do, then this means this new
// connection will use a fresh circuit, rather than possibly re-using an // connection will use a fresh circuit, rather than possibly re-using an
// existing circuit. // existing circuit.
func Dial(address, socksAddr string, streamIsolation bool) (net.Conn, error) { func dial(address, socksAddr string, streamIsolation bool) (net.Conn, error) {
// If we were requested to force stream isolation for this connection, // If we were requested to force stream isolation for this connection,
// we'll populate the authentication credentials with random data as // we'll populate the authentication credentials with random data as
// Tor will create a new circuit for each set of credentials. // Tor will create a new circuit for each set of credentials.
@ -91,7 +125,7 @@ func LookupSRV(service, proto, name, socksAddr, dnsServer string,
streamIsolation bool) (string, []*net.SRV, error) { streamIsolation bool) (string, []*net.SRV, error) {
// Connect to the DNS server we'll be using to query SRV records. // Connect to the DNS server we'll be using to query SRV records.
conn, err := Dial(dnsServer, socksAddr, streamIsolation) conn, err := dial(dnsServer, socksAddr, streamIsolation)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -158,3 +192,52 @@ func ResolveTCPAddr(address, socksAddr string) (*net.TCPAddr, error) {
Port: p, Port: p,
}, nil }, nil
} }
// ParseAddr parses an address from its string format to a net.Addr.
func ParseAddr(address, socksAddr string) (net.Addr, error) {
host, portStr, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, err
}
if IsOnionHost(host) {
return &OnionAddr{OnionService: host, Port: port}, nil
}
return ResolveTCPAddr(address, socksAddr)
}
// IsOnionHost determines whether a host is part of an onion address.
func IsOnionHost(host string) bool {
// Note the starting index of the onion suffix in the host depending
// on its length.
var suffixIndex int
switch len(host) {
case V2Len:
suffixIndex = V2Len - OnionSuffixLen
case V3Len:
suffixIndex = V3Len - OnionSuffixLen
default:
return false
}
// Make sure the host ends with the ".onion" suffix.
if host[suffixIndex:] != OnionSuffix {
return false
}
// We'll now attempt to decode the host without its suffix, as the
// suffix includes invalid characters. This will tell us if the host is
// actually valid if succesful.
host = host[:suffixIndex]
if _, err := Base32Encoding.DecodeString(host); err != nil {
return false
}
return true
}