avoid forwarding to a private (lokal) IP addr (#256)
* avoid forwarding to a private (lokal) IP addr * test DNS resolver issues * increase test coverage * update changelog
This commit is contained in:
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
|
||||||
|
- detect usage of a local DNS resolver [#37](https://github.com/s-allius/tsun-gen3-proxy/issues/37)
|
||||||
- path for logs is now configurable by cli args
|
- path for logs is now configurable by cli args
|
||||||
- configure the number of keeped logfiles by cli args
|
- configure the number of keeped logfiles by cli args
|
||||||
- add DOCS.md for add-ons
|
- add DOCS.md for add-ons
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import json
|
|||||||
import gc
|
import gc
|
||||||
from aiomqtt import MqttCodeError
|
from aiomqtt import MqttCodeError
|
||||||
from asyncio import StreamReader, StreamWriter
|
from asyncio import StreamReader, StreamWriter
|
||||||
|
from ipaddress import ip_address
|
||||||
|
|
||||||
from inverter_ifc import InverterIfc
|
from inverter_ifc import InverterIfc
|
||||||
from proxy import Proxy
|
from proxy import Proxy
|
||||||
@@ -101,6 +102,20 @@ class InverterBase(InverterIfc, Proxy):
|
|||||||
logging.info(f'[{stream.node_id}] Connect to {addr}')
|
logging.info(f'[{stream.node_id}] Connect to {addr}')
|
||||||
connect = asyncio.open_connection(host, port)
|
connect = asyncio.open_connection(host, port)
|
||||||
reader, writer = await connect
|
reader, writer = await connect
|
||||||
|
r_addr = writer.get_extra_info('peername')
|
||||||
|
if r_addr is not None:
|
||||||
|
(ip, _) = r_addr
|
||||||
|
if ip_address(ip).is_private:
|
||||||
|
logging.error(
|
||||||
|
f"""resolve {host} to {ip}, which is a private IP!
|
||||||
|
\u001B[31m Check your DNS settings and use a public DNS resolver!
|
||||||
|
|
||||||
|
To prevent a possible loop, forwarding to local IP addresses is
|
||||||
|
not supported and is deactivated for subsequent connections
|
||||||
|
\u001B[0m
|
||||||
|
""")
|
||||||
|
Config.act_config[self.config_id]['enabled'] = False
|
||||||
|
|
||||||
ifc = AsyncStreamClient(
|
ifc = AsyncStreamClient(
|
||||||
reader, writer, self.local, self.__del_remote)
|
reader, writer, self.local, self.__del_remote)
|
||||||
|
|
||||||
|
|||||||
@@ -54,11 +54,12 @@ class FakeReader():
|
|||||||
|
|
||||||
|
|
||||||
class FakeWriter():
|
class FakeWriter():
|
||||||
|
peer = ('47.1.2.3', 10000)
|
||||||
def write(self, buf: bytes):
|
def write(self, buf: bytes):
|
||||||
return
|
return
|
||||||
def get_extra_info(self, sel: str):
|
def get_extra_info(self, sel: str):
|
||||||
if sel == 'peername':
|
if sel == 'peername':
|
||||||
return ('47.1.2.3', 10000)
|
return self.peer
|
||||||
elif sel == 'sockname':
|
elif sel == 'sockname':
|
||||||
return 'sock:1234'
|
return 'sock:1234'
|
||||||
assert False
|
assert False
|
||||||
@@ -241,6 +242,118 @@ async def test_remote_conn(config_conn, patch_open_connection):
|
|||||||
cnt += 1
|
cnt += 1
|
||||||
assert cnt == 0
|
assert cnt == 0
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_remote_conn_to_private(config_conn, patch_open_connection):
|
||||||
|
'''check DNS resolving of the TSUN FQDN to a local address'''
|
||||||
|
_ = config_conn
|
||||||
|
_ = patch_open_connection
|
||||||
|
assert asyncio.get_running_loop()
|
||||||
|
InverterBase._registry.clear()
|
||||||
|
reader = FakeReader()
|
||||||
|
writer = FakeWriter()
|
||||||
|
FakeWriter.peer = ("192.168.0.1", 10000)
|
||||||
|
|
||||||
|
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||||
|
assert inverter.local.stream
|
||||||
|
assert inverter.local.ifc
|
||||||
|
await inverter.create_remote()
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert not Config.act_config['tsun']['enabled']
|
||||||
|
assert inverter.remote.stream
|
||||||
|
assert inverter.remote.ifc
|
||||||
|
assert inverter.local.ifc.healthy()
|
||||||
|
|
||||||
|
# outside context manager the unhealth AsyncStream is released
|
||||||
|
FakeWriter.peer = ("47.1.2.3", 10000)
|
||||||
|
cnt = 0
|
||||||
|
for inv in InverterBase:
|
||||||
|
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
|
||||||
|
cnt += 1
|
||||||
|
del inv
|
||||||
|
assert cnt == 1
|
||||||
|
|
||||||
|
del inverter
|
||||||
|
cnt = 0
|
||||||
|
for inv in InverterBase:
|
||||||
|
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||||
|
cnt += 1
|
||||||
|
assert cnt == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_remote_conn_to_loopback(config_conn, patch_open_connection):
|
||||||
|
'''check DNS resolving of the TSUN FQDN to the loopback address'''
|
||||||
|
_ = config_conn
|
||||||
|
_ = patch_open_connection
|
||||||
|
assert asyncio.get_running_loop()
|
||||||
|
InverterBase._registry.clear()
|
||||||
|
reader = FakeReader()
|
||||||
|
writer = FakeWriter()
|
||||||
|
FakeWriter.peer = ("127.0.0.1", 10000)
|
||||||
|
|
||||||
|
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||||
|
assert inverter.local.stream
|
||||||
|
assert inverter.local.ifc
|
||||||
|
await inverter.create_remote()
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert not Config.act_config['tsun']['enabled']
|
||||||
|
assert inverter.remote.stream
|
||||||
|
assert inverter.remote.ifc
|
||||||
|
assert inverter.local.ifc.healthy()
|
||||||
|
|
||||||
|
# outside context manager the unhealth AsyncStream is released
|
||||||
|
FakeWriter.peer = ("47.1.2.3", 10000)
|
||||||
|
cnt = 0
|
||||||
|
for inv in InverterBase:
|
||||||
|
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
|
||||||
|
cnt += 1
|
||||||
|
del inv
|
||||||
|
assert cnt == 1
|
||||||
|
|
||||||
|
del inverter
|
||||||
|
cnt = 0
|
||||||
|
for inv in InverterBase:
|
||||||
|
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||||
|
cnt += 1
|
||||||
|
assert cnt == 0
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_remote_conn_to_None(config_conn, patch_open_connection):
|
||||||
|
'''check if get_extra_info() return None in case of an error'''
|
||||||
|
_ = config_conn
|
||||||
|
_ = patch_open_connection
|
||||||
|
assert asyncio.get_running_loop()
|
||||||
|
InverterBase._registry.clear()
|
||||||
|
reader = FakeReader()
|
||||||
|
writer = FakeWriter()
|
||||||
|
FakeWriter.peer = None
|
||||||
|
|
||||||
|
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||||
|
assert inverter.local.stream
|
||||||
|
assert inverter.local.ifc
|
||||||
|
await inverter.create_remote()
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert Config.act_config['tsun']['enabled']
|
||||||
|
assert inverter.remote.stream
|
||||||
|
assert inverter.remote.ifc
|
||||||
|
assert inverter.local.ifc.healthy()
|
||||||
|
|
||||||
|
# outside context manager the unhealth AsyncStream is released
|
||||||
|
FakeWriter.peer = ("47.1.2.3", 10000)
|
||||||
|
cnt = 0
|
||||||
|
for inv in InverterBase:
|
||||||
|
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
|
||||||
|
cnt += 1
|
||||||
|
del inv
|
||||||
|
assert cnt == 1
|
||||||
|
|
||||||
|
del inverter
|
||||||
|
cnt = 0
|
||||||
|
for inv in InverterBase:
|
||||||
|
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||||
|
cnt += 1
|
||||||
|
assert cnt == 0
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_unhealthy_remote(config_conn, patch_open_connection, patch_unhealthy_remote):
|
async def test_unhealthy_remote(config_conn, patch_open_connection, patch_unhealthy_remote):
|
||||||
_ = config_conn
|
_ = config_conn
|
||||||
|
|||||||
Reference in New Issue
Block a user