ensure non loopback address for windivert method

This commit is contained in:
nom3ad 2022-11-06 17:16:15 +05:30 committed by Brian May
parent 9c5517fd25
commit 7b8f140870
3 changed files with 44 additions and 15 deletions

View File

@ -307,9 +307,9 @@ class FirewallClient:
def get_pfile(): def get_pfile():
if can_use_stdio: if can_use_stdio:
import io
self.p.stdin.write(b'STDIO:\n') self.p.stdin.write(b'STDIO:\n')
self.p.stdin.flush() self.p.stdin.flush()
class RWPair: class RWPair:
def __init__(self, r, w): def __init__(self, r, w):
self.r = r self.r = r
@ -318,13 +318,15 @@ class FirewallClient:
self.readline = r.readline self.readline = r.readline
self.write = w.write self.write = w.write
self.flush = w.flush self.flush = w.flush
def close(self): def close(self):
for f in self.r, self.w: for f in self.r, self.w:
try: try:
f.close() f.close()
except: except Exception:
pass pass
return RWPair(self.p.stdout, self.p.stdin) return RWPair(self.p.stdout, self.p.stdin)
# import io
# return io.BufferedRWPair(self.p.stdout, self.p.stdin, 1) # return io.BufferedRWPair(self.p.stdout, self.p.stdin, 1)
else: else:
import base64 import base64
@ -885,7 +887,11 @@ def main(listenip_v6, listenip_v4,
# listenip_v4 contains user specified value or it is set to "auto". # listenip_v4 contains user specified value or it is set to "auto".
if listenip_v4 == "auto": if listenip_v4 == "auto":
listenip_v4 = ('127.0.0.1', 0) if sys.platform == 'win32':
listenip_v4 = ('0.0.0.0', 0) # windivert method won't work with loopback interface
else:
listenip_v4 = ('127.0.0.1', 0)
debug1("Using default IPv4 listen address " + listenip_v4[0])
# listenip_v6 is... # listenip_v6 is...
# None when IPv6 is disabled. # None when IPv6 is disabled.
@ -895,8 +901,11 @@ def main(listenip_v6, listenip_v4,
debug1("IPv6 disabled by --disable-ipv6") debug1("IPv6 disabled by --disable-ipv6")
if listenip_v6 == "auto": if listenip_v6 == "auto":
if avail.ipv6: if avail.ipv6:
debug1("IPv6 enabled: Using default IPv6 listen address ::1") if sys.platform == 'win32':
listenip_v6 = ('::1', 0) listenip_v6 = ('::', 0) # windivert method won't work with loopback interface
else:
listenip_v6 = ('::1', 0)
debug1("IPv6 enabled: Using default IPv6 listen address " + listenip_v6[0])
else: else:
debug1("IPv6 disabled since it isn't supported by method " debug1("IPv6 disabled since it isn't supported by method "
"%s." % fw.method.name) "%s." % fw.method.name)

View File

@ -4,6 +4,8 @@ import ipaddress
import threading import threading
from collections import namedtuple from collections import namedtuple
import socket import socket
import subprocess
import re
from multiprocessing import shared_memory from multiprocessing import shared_memory
import struct import struct
from functools import wraps from functools import wraps
@ -270,6 +272,18 @@ class Method(BaseMethod):
def __init__(self, name): def __init__(self, name):
super().__init__(name) super().__init__(name)
def _get_local_proxy_listen_addr(self, port, family):
proto = "TCPv6" if family.version == 6 else "TCP"
for line in subprocess.check_output(["netstat", "-a", "-n", "-p", proto]).decode().splitlines():
try:
_, local_addr, _, state, *_ = re.split(r"\s+", line.strip())
except ValueError:
continue
port_suffix = ":" + str(port)
if state == "LISTENING" and local_addr.endswith(port_suffix):
return ipaddress.ip_address(local_addr[:-len(port_suffix)].strip("[]"))
raise Fatal("Could not find listening address for {}/{}".format(port, proto))
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, user, tmark): def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, user, tmark):
log(f"{port=}, {dnsport=}, {nslist=}, {family=}, {subnets=}, {udp=}, {user=}, {tmark=}") log(f"{port=}, {dnsport=}, {nslist=}, {family=}, {subnets=}, {udp=}, {user=}, {tmark=}")
@ -279,16 +293,20 @@ class Method(BaseMethod):
family = IPFamily(family) family = IPFamily(family)
# using loopback proxy address never worked. # using loopback proxy address never worked.
# >>> self.proxy_addr[family] = family.loopback_addr
# See: https://github.com/basil00/Divert/issues/17#issuecomment-341100167 ,https://github.com/basil00/Divert/issues/82) # See: https://github.com/basil00/Divert/issues/17#issuecomment-341100167 ,https://github.com/basil00/Divert/issues/82)
# As a workaround we use another interface ip instead. # As a workaround we use another interface ip instead.
# self.proxy_addr[family] = family.loopback_addr
local_addr = self._get_local_proxy_listen_addr(port, family)
for addr in (ipaddress.ip_address(info[4][0]) for info in socket.getaddrinfo(socket.gethostname(), None)): for addr in (ipaddress.ip_address(info[4][0]) for info in socket.getaddrinfo(socket.gethostname(), None)):
if addr.is_loopback or addr.version != family.version: if addr.is_loopback or addr.version != family.version:
continue continue
self.proxy_addr[family] = str(addr) if local_addr.is_unspecified or local_addr == addr:
break debug2("Found non loopback address to connect to proxy: " + str(addr))
self.proxy_addr[family] = str(addr)
break
else: else:
raise Fatal(f"Could not find a non loopback proxy address for {family.name}") raise Fatal("Windivert method requires proxy to listen on non loopback address")
self.proxy_port = port self.proxy_port = port
@ -423,6 +441,8 @@ class Method(BaseMethod):
else: else:
# ip_checks.append(f"ip.SrcAddr=={hex(int(addr))}") # only Windivert >=2 supports this # ip_checks.append(f"ip.SrcAddr=={hex(int(addr))}") # only Windivert >=2 supports this
ip_filters.append(f"ipv6.SrcAddr=={addr}") ip_filters.append(f"ipv6.SrcAddr=={addr}")
if not ip_filters:
raise Fatal("At least ipv4 or ipv6 address is expected")
filter = f"{direction} and {proto.filter} and ({' or '.join(ip_filters)}) and tcp.SrcPort=={self.proxy_port}" filter = f"{direction} and {proto.filter} and ({' or '.join(ip_filters)}) and tcp.SrcPort=={self.proxy_port}"
debug2(f"[INGRESS] {filter=}") debug2(f"[INGRESS] {filter=}")
with pydivert.WinDivert(filter) as w: with pydivert.WinDivert(filter) as w:

View File

@ -12,7 +12,7 @@ import ipaddress
from urllib.parse import urlparse from urllib.parse import urlparse
import sshuttle.helpers as helpers import sshuttle.helpers as helpers
from sshuttle.helpers import debug2, debug3, which, get_path, Fatal from sshuttle.helpers import debug2, which, get_path, Fatal
def get_module_source(name): def get_module_source(name):