close remaining local dns resolution gaps when classifying hostnames and connecting via tor

This commit is contained in:
Craig Raw
2026-08-06 14:13:24 +02:00
parent b6ed4ba7c9
commit 03222f20c3
3 changed files with 80 additions and 2 deletions
@@ -16,12 +16,14 @@ package com.sparrowwallet.sparrow.net;
* limitations under the License.
*/
import com.google.common.net.InetAddresses;
import com.sparrowwallet.sparrow.AppServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Locale;
/**
* Matches a request based on IP Address or subnet mask matching against the remote
@@ -109,7 +111,18 @@ public final class IpAddressMatcher {
public static boolean isLocalNetworkAddress(String address) {
try {
return "localhost".equals(address) || "127.0.0.1".equals(address) || LOCAL_RANGE_1.matches(address) || LOCAL_RANGE_2.matches(address) || LOCAL_RANGE_3.matches(address) || LOCAL_RANGE_4.matches(address);
if("localhost".equals(address) || "127.0.0.1".equals(address)) {
return true;
}
//Matching a hostname against the local ranges requires resolving it, which leaks the name to (and trusts the answer of) the local DNS resolver even when a proxy is configured
//Only IP literals and mDNS names (which RFC 6762 requires to be resolved via link-local multicast, not upstream DNS) are considered potentially local when using a proxy
if(AppServices.isUsingProxy() && !InetAddresses.isInetAddress(address) && !address.toLowerCase(Locale.ROOT).endsWith(".local")) {
log.info("Avoiding local DNS resolution of " + address + ", assuming it is a non-local address to be resolved by the configured proxy");
return false;
}
return LOCAL_RANGE_1.matches(address) || LOCAL_RANGE_2.matches(address) || LOCAL_RANGE_3.matches(address) || LOCAL_RANGE_4.matches(address);
} catch(IllegalArgumentException e) {
if(AppServices.isUsingProxy()) {
log.info(e.getMessage() + ", assuming it is a non-local address to be resolved by the configured proxy");
@@ -23,6 +23,6 @@ public class TorTcpTransport extends TcpTransport {
}
socket = new Socket(Tor.getDefault().getProxy());
socket.connect(new InetSocketAddress(server.getHost(), server.getPortOrDefault(getDefaultPort())));
socket.connect(InetSocketAddress.createUnresolved(server.getHost(), server.getPortOrDefault(getDefaultPort())));
}
}
@@ -0,0 +1,65 @@
package com.sparrowwallet.sparrow.net;
import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.sparrow.SparrowWallet;
import com.sparrowwallet.sparrow.io.Config;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class IpAddressMatcherTest {
@TempDir
private static Path tempHome;
@BeforeAll
public static void setUp() {
//Isolate Config.get() from the developer's config so the proxy setting can be changed safely
System.setProperty(SparrowWallet.APP_HOME_PROPERTY, tempHome.toString());
Network.set(Network.MAINNET);
}
@AfterAll
public static void tearDown() {
Config.get().setUseProxy(false);
System.clearProperty(SparrowWallet.APP_HOME_PROPERTY);
}
@Test
public void classifiesAddressesWithoutProxy() {
Config.get().setUseProxy(false);
assertTrue(IpAddressMatcher.isLocalNetworkAddress("localhost"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("127.0.0.1"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("192.168.1.5"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("10.0.0.10"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("172.16.0.1"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("100.64.0.1"));
assertFalse(IpAddressMatcher.isLocalNetworkAddress("8.8.8.8"));
}
@Test
public void classifiesIpLiteralsWithProxy() {
Config.get().setUseProxy(true);
//IP literals classify without DNS resolution, so local network servers must still connect directly when a proxy is configured
assertTrue(IpAddressMatcher.isLocalNetworkAddress("localhost"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("127.0.0.1"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("192.168.1.5"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("10.0.0.10"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("172.16.0.1"));
assertTrue(IpAddressMatcher.isLocalNetworkAddress("100.64.0.1"));
assertFalse(IpAddressMatcher.isLocalNetworkAddress("8.8.8.8"));
}
@Test
public void assumesHostnamesAreRemoteWithProxy() {
Config.get().setUseProxy(true);
//Hostnames are not resolved by the local DNS resolver when a proxy is configured, and are assumed to be remote addresses resolved by the proxy
assertFalse(IpAddressMatcher.isLocalNetworkAddress("electrumx.example.com"));
assertFalse(IpAddressMatcher.isLocalNetworkAddress("notarealhost.invalid"));
}
}