From d7678e14b52736200b7f02256f94adce2757ed82 Mon Sep 17 00:00:00 2001
From: SomberNight <somber.night@protonmail.com>
Date: Mon, 16 May 2022 22:24:07 +0200
Subject: [PATCH] interface: bypass proxy for servers on localhost

closes https://github.com/spesmilo/electrum/issues/3126
closes https://github.com/spesmilo/electrum/issues/7256

I am unsure if this would be safe to do for the more general "server running on private ip" case,
so this is restricted to localhost only atm.
---
 electrum/interface.py | 10 +++++++++-
 electrum/util.py      | 15 ++++++++++++++-
 2 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/electrum/interface.py b/electrum/interface.py
index 50949a908..298ec0809 100644
--- a/electrum/interface.py
+++ b/electrum/interface.py
@@ -372,9 +372,17 @@ class Interface(Logger):
         self.blockchain = None  # type: Optional[Blockchain]
         self._requested_chunks = set()  # type: Set[int]
         self.network = network
-        self.proxy = MySocksProxy.from_proxy_dict(proxy)
         self.session = None  # type: Optional[NotificationSession]
         self._ipaddr_bucket = None
+        # Set up proxy.
+        # - for servers running on localhost, the proxy is not used. If user runs their own server
+        #   on same machine, this lets them enable the proxy (which is used for e.g. FX rates).
+        #   note: we could maybe relax this further and bypass the proxy for all private
+        #         addresses...? e.g. 192.168.x.x
+        if util.is_localhost(server.host):
+            self.logger.info(f"looks like localhost: not using proxy for this server")
+            proxy = None
+        self.proxy = MySocksProxy.from_proxy_dict(proxy)
 
         # Latest block header and corresponding height, as claimed by the server.
         # Note that these values are updated before they are verified.
diff --git a/electrum/util.py b/electrum/util.py
index eaf584792..4c30d681c 100644
--- a/electrum/util.py
+++ b/electrum/util.py
@@ -1529,11 +1529,24 @@ def is_ip_address(x: Union[str, bytes]) -> bool:
         return False
 
 
-def is_private_netaddress(host: str) -> bool:
+def is_localhost(host: str) -> bool:
     if str(host) in ('localhost', 'localhost.',):
         return True
     if host[0] == '[' and host[-1] == ']':  # IPv6
         host = host[1:-1]
+    try:
+        ip_addr = ipaddress.ip_address(host)  # type: Union[IPv4Address, IPv6Address]
+        return ip_addr.is_loopback
+    except ValueError:
+        pass  # not an IP
+    return False
+
+
+def is_private_netaddress(host: str) -> bool:
+    if is_localhost(host):
+        return True
+    if host[0] == '[' and host[-1] == ']':  # IPv6
+        host = host[1:-1]
     try:
         ip_addr = ipaddress.ip_address(host)  # type: Union[IPv4Address, IPv6Address]
         return ip_addr.is_private