/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.network;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* Performs DNS lookup over Socks5 proxy that implements the RESOLVE extension.
* At this time, Tor is only known Socks5 proxy that supports it.
* <p>
* Adapted from https://github.com/btcsuite/btcd/blob/master/connmgr/tor.go
*/
public class DnsLookupTor {
private static final Logger log = LoggerFactory.getLogger(DnsLookupTor.class);
private static final Map<Byte, String> torStatusErrors = DnsLookupTor.createMap();
private static Map<Byte, String> createMap() {
HashMap<Byte, String> map = new HashMap<Byte, String>();
map.put(b('\u0000'), "tor succeeded");
map.put(b('\u0001'), "tor general error");
map.put(b('\u0002'), "tor not allowed");
map.put(b('\u0003'), "tor network is unreachable");
map.put(b('\u0004'), "tor host is unreachable");
map.put(b('\u0005'), "tor connection refused");
map.put(b('\u0006'), "tor TTL expired");
map.put(b('\u0007'), "tor command not supported");
map.put(b('\u0008'), "tor address type not supported");
return map;
}
/**
* Performs DNS lookup and returns a single InetAddress
*/
public static InetAddress lookup(Socks5Proxy proxy, String host) throws DnsLookupException {
try {
// note: This is creating a new connection to our proxy, without any authentication.
// This works fine when connecting to bitsquare's internal Tor proxy, but
// would fail if user has configured an external proxy that requires auth.
// It would be much better to use the already connected proxy socket, but when I
// tried that I get weird errors and the lookup fails.
//
// So this is an area for future improvement.
Socket proxySocket = new Socket(proxy.getInetAddress(), proxy.getPort());
proxySocket.getOutputStream().write(new byte[]{b('\u0005'), b('\u0001'), b('\u0000')});
byte[] buf = new byte[2];
proxySocket.getInputStream().read(buf);
if (buf[0] != b('\u0005')) {
throw new DnsLookupException("Invalid Proxy Response");
}
if (buf[1] != b('\u0000')) {
throw new DnsLookupException("Unrecognized Tor Auth Method");
}
byte[] hostBytes = host.getBytes();
buf = new byte[7 + hostBytes.length];
buf[0] = b('\u0005');
buf[1] = b('\u00f0');
buf[2] = b('\u0000');
buf[3] = b('\u0003');
buf[4] = (byte) hostBytes.length;
System.arraycopy(hostBytes, 0, buf, 5, hostBytes.length);
buf[5 + hostBytes.length] = 0;
proxySocket.getOutputStream().write(buf);
buf = new byte[4];
int bytesRead = proxySocket.getInputStream().read(buf);
// TODO: Should not be a length check here as well?
/* if (bytesRead != 4)
throw new DnsLookupException("Invalid Tor Address Response");*/
if (buf[0] != b('\u0005'))
throw new DnsLookupException("Invalid Tor Proxy Response");
if (buf[1] != b('\u0000')) {
if (!torStatusErrors.containsKey(buf[1])) {
throw new DnsLookupException("Invalid Tor Proxy Response");
}
throw new DnsLookupException(torStatusErrors.get(buf[1]));
}
if (buf[3] != b('\u0001'))
throw new DnsLookupException(torStatusErrors.get(b('\u0001')));
buf = new byte[4];
bytesRead = proxySocket.getInputStream().read(buf);
if (bytesRead != 4)
throw new DnsLookupException("Invalid Tor Address Response");
return InetAddress.getByAddress(buf);
} catch (IOException | DnsLookupException e) {
log.warn("Error resolving " + host + ". Exception:\n" + e.toString());
throw new DnsLookupException(e);
}
}
/**
* so we can have prettier code without a bunch of casts.
*/
private static byte b(char c) {
return (byte) c;
}
}