Merge branch 'upnp-status'

This commit is contained in:
Jack Robison 2018-10-18 18:21:53 -04:00
commit cf1157f02a
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2
6 changed files with 114 additions and 27 deletions

View file

@ -54,6 +54,7 @@ most commands.
* added `address_unused` command to get existing or generate a new unused address.
* added pagination support for `address_list`, `channel_list`, `claim_list_mine`,
`transaction_list` and `utxo_list`.
* added `upnp` field to `status` response
* removed `send_amount_to_address` command previously marked as deprecated
* removed `channel_list_mine` command previously marked as deprecated
* removed `get_availability` command previously marked as deprecated

View file

@ -19,6 +19,7 @@ CLAIM_ACTION = 'Claim Action' # publish/create/update/abandon
NEW_CHANNEL = 'New Channel'
CREDITS_SENT = 'Credits Sent'
NEW_DOWNLOAD_STAT = 'Download'
UPNP_SETUP = "UPnP Setup"
BLOB_BYTES_UPLOADED = 'Blob Bytes Uploaded'
@ -69,6 +70,14 @@ class Manager:
'timestamp': utils.isonow(),
})
def send_upnp_setup_success_fail(self, success, status):
self.analytics_api.track(
self._event(UPNP_SETUP, {
'success': success,
'status': status,
})
)
def send_server_startup(self):
self.analytics_api.track(self._event(SERVER_STARTUP))

View file

@ -94,7 +94,7 @@ def rot13(some_str):
def deobfuscate(obfustacated):
return base64.b64decode(rot13(obfustacated))
return base64.b64decode(rot13(obfustacated)).decode()
def obfuscate(plain):

View file

@ -6,9 +6,10 @@ import math
import binascii
from hashlib import sha256
from types import SimpleNamespace
from twisted.internet import defer, threads, reactor, error
from twisted.internet import defer, threads, reactor, error, task
import lbryschema
from aioupnp.upnp import UPnP
from aioupnp.fault import UPnPError
from lbrynet import conf
from lbrynet.core.utils import DeferredDict
from lbrynet.core.PaymentRateManager import OnlyFreePaymentsManager
@ -684,6 +685,8 @@ class UPnPComponent(Component):
self.upnp = None
self.upnp_redirects = {}
self.external_ip = None
self._maintain_redirects_lc = task.LoopingCall(self._maintain_redirects)
self._maintain_redirects_lc.clock = self.component_manager.reactor
@property
def component(self):
@ -697,40 +700,105 @@ class UPnPComponent(Component):
})
self.upnp_redirects.update(upnp_redirects)
@defer.inlineCallbacks
def _maintain_redirects(self):
# setup the gateway if necessary
if not self.upnp:
try:
self.upnp = yield from_future(UPnP.discover())
log.info("found upnp gateway: %s", self.upnp.gateway.manufacturer_string)
except Exception as err:
log.warning("upnp discovery failed: %s", err)
return
# update the external ip
try:
external_ip = yield from_future(self.upnp.get_external_ip())
if external_ip == "0.0.0.0":
log.warning("upnp doesn't know the external ip address (returned 0.0.0.0), using fallback")
external_ip = CS.get_external_ip()
if self.external_ip and self.external_ip != external_ip:
log.info("external ip changed from %s to %s", self.external_ip, external_ip)
elif not self.external_ip:
log.info("got external ip: %s", external_ip)
self.external_ip = external_ip
except (asyncio.TimeoutError, UPnPError):
pass
if not self.upnp_redirects: # setup missing redirects
try:
upnp_redirects = yield DeferredDict({
"UDP": from_future(self.upnp.get_next_mapping(self._int_dht_node_port, "UDP", "LBRY DHT port")),
"TCP": from_future(self.upnp.get_next_mapping(self._int_peer_port, "TCP", "LBRY peer port"))
})
self.upnp_redirects.update(upnp_redirects)
except (asyncio.TimeoutError, UPnPError):
self.upnp = None
return self._maintain_redirects()
else: # check existing redirects are still active
found = set()
mappings = yield from_future(self.upnp.get_redirects())
for mapping in mappings:
proto = mapping['NewProtocol']
if proto in self.upnp_redirects and mapping['NewExternalPort'] == self.upnp_redirects[proto]:
if mapping['NewInternalClient'] == self.upnp.lan_address:
found.add(proto)
if 'UDP' not in found:
try:
udp_port = yield from_future(
self.upnp.get_next_mapping(self._int_dht_node_port, "UDP", "LBRY DHT port")
)
self.upnp_redirects['UDP'] = udp_port
log.info("refreshed upnp redirect for dht port: %i", udp_port)
except (asyncio.TimeoutError, UPnPError):
del self.upnp_redirects['UDP']
if 'TCP' not in found:
try:
tcp_port = yield from_future(
self.upnp.get_next_mapping(self._int_peer_port, "TCP", "LBRY peer port")
)
self.upnp_redirects['TCP'] = tcp_port
log.info("refreshed upnp redirect for peer port: %i", tcp_port)
except (asyncio.TimeoutError, UPnPError):
del self.upnp_redirects['TCP']
if 'TCP' in self.upnp_redirects and 'UDP' in self.upnp_redirects:
log.debug("upnp redirects are still active")
@defer.inlineCallbacks
def start(self):
if not self.use_upnp:
self.external_ip = CS.get_external_ip()
return
try:
self.upnp = yield from_future(UPnP.discover())
log.info("found upnp gateway")
found = True
except Exception as err:
log.warning("upnp discovery failed: %s", err)
found = False
if found:
try:
self.external_ip = yield from_future(self.upnp.get_external_ip())
if self.external_ip == "0.0.0.0":
log.warning("upnp doesn't know the external ip address (returned 0.0.0.0), using fallback")
self.external_ip = CS.get_external_ip()
else:
log.info("got external ip from upnp: %s", self.external_ip)
yield self._setup_redirects()
log.info("set up upnp port redirects")
except Exception as err:
log.warning("error trying to set up upnp: %s", err)
self.external_ip = CS.get_external_ip()
success = False
yield self._maintain_redirects()
if self.upnp:
if not self.upnp_redirects:
log.error("failed to setup upnp, debugging infomation: %s", self.upnp.zipped_debugging_info)
else:
success = True
log.debug("set up upnp port redirects for gateway: %s", self.upnp.gateway.manufacturer_string)
else:
self.external_ip = CS.get_external_ip()
log.error("failed to setup upnp")
self.component_manager.analytics_manager.send_upnp_setup_success_fail(success, self.get_status())
self._maintain_redirects_lc.start(360, now=False)
def stop(self):
if self._maintain_redirects_lc.running:
self._maintain_redirects_lc.stop()
return defer.DeferredList(
[from_future(self.upnp.delete_port_mapping(port, protocol))
for protocol, port in self.upnp_redirects.items()]
)
def get_status(self):
return {
'redirects': self.upnp_redirects,
'gateway': '' if not self.upnp else self.upnp.gateway.manufacturer_string,
'dht_redirect_set': 'UDP' in self.upnp_redirects,
'peer_redirect_set': 'TCP' in self.upnp_redirects,
'external_ip': self.external_ip
}
class ExchangeRateManagerComponent(Component):
component_name = EXCHANGE_RATE_MANAGER_COMPONENT

View file

@ -771,6 +771,15 @@ class Daemon(AuthJSONRPCServer):
},
'file_manager': {
'managed_files': (int) count of files in the file manager,
},
'upnp': {
'redirects': {
<TCP | UDP>: (int) external_port,
},
'gateway': (str) manufacturer and model,
'dht_redirect_set': (bool),
'peer_redirect_set': (bool),
'external_ip': (str) external ip address,
}
}
"""

View file

@ -23,13 +23,13 @@ class CompareVersionTest(unittest.TestCase):
class ObfuscationTest(unittest.TestCase):
def test_deobfuscation_reverses_obfuscation(self):
plain = "my_test_string".encode()
obf = utils.obfuscate(plain)
plain = "my_test_string"
obf = utils.obfuscate(plain.encode())
self.assertEqual(plain, utils.deobfuscate(obf))
def test_can_use_unicode(self):
plain = ''.encode()
obf = utils.obfuscate(plain)
plain = ''
obf = utils.obfuscate(plain.encode())
self.assertEqual(plain, utils.deobfuscate(obf))