check upnp redirects and external ip in a looping call

-add upnp setup success/fail analytics event
This commit is contained in:
Jack Robison 2018-10-18 17:41:49 -04:00
parent 6d8ab97ca9
commit 213bdac55f
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2
3 changed files with 91 additions and 23 deletions

View file

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

View file

@ -6,9 +6,10 @@ import math
import binascii import binascii
from hashlib import sha256 from hashlib import sha256
from types import SimpleNamespace from types import SimpleNamespace
from twisted.internet import defer, threads, reactor, error from twisted.internet import defer, threads, reactor, error, task
import lbryschema import lbryschema
from aioupnp.upnp import UPnP from aioupnp.upnp import UPnP
from aioupnp.fault import UPnPError
from lbrynet import conf from lbrynet import conf
from lbrynet.core.utils import DeferredDict from lbrynet.core.utils import DeferredDict
from lbrynet.core.PaymentRateManager import OnlyFreePaymentsManager from lbrynet.core.PaymentRateManager import OnlyFreePaymentsManager
@ -684,6 +685,8 @@ class UPnPComponent(Component):
self.upnp = None self.upnp = None
self.upnp_redirects = {} self.upnp_redirects = {}
self.external_ip = None self.external_ip = None
self._maintain_redirects_lc = task.LoopingCall(self._maintain_redirects)
self._maintain_redirects_lc.clock = self.component_manager.reactor
@property @property
def component(self): def component(self):
@ -697,41 +700,91 @@ class UPnPComponent(Component):
}) })
self.upnp_redirects.update(upnp_redirects) 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 @defer.inlineCallbacks
def start(self): def start(self):
if not self.use_upnp: if not self.use_upnp:
self.external_ip = CS.get_external_ip() self.external_ip = CS.get_external_ip()
return return
try: success = False
self.upnp = yield from_future(UPnP.discover()) yield self._maintain_redirects()
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()
except Exception as err:
log.warning("error trying to set up upnp: %s", err)
self.external_ip = CS.get_external_ip()
else:
self.external_ip = CS.get_external_ip()
if self.upnp: if self.upnp:
if not self.upnp_redirects: if not self.upnp_redirects:
log.error("failed to setup upnp, debugging infomation: %s", self.upnp.zipped_debugging_info) log.error("failed to setup upnp, debugging infomation: %s", self.upnp.zipped_debugging_info)
else: else:
log.info("set up upnp port redirects for gateway: %s", self.upnp.gateway.manufacturer_string) success = True
log.debug("set up upnp port redirects for gateway: %s", self.upnp.gateway.manufacturer_string)
else: else:
log.error("failed to setup upnp") 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): def stop(self):
if self._maintain_redirects_lc.running:
self._maintain_redirects_lc.stop()
return defer.DeferredList( return defer.DeferredList(
[from_future(self.upnp.delete_port_mapping(port, protocol)) [from_future(self.upnp.delete_port_mapping(port, protocol))
for protocol, port in self.upnp_redirects.items()] for protocol, port in self.upnp_redirects.items()]
@ -741,6 +794,9 @@ class UPnPComponent(Component):
return { return {
'redirects': self.upnp_redirects, 'redirects': self.upnp_redirects,
'gateway': '' if not self.upnp else self.upnp.gateway.manufacturer_string, '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
} }

View file

@ -777,6 +777,9 @@ class Daemon(AuthJSONRPCServer):
<TCP | UDP>: (int) external_port, <TCP | UDP>: (int) external_port,
}, },
'gateway': (str) manufacturer and model, 'gateway': (str) manufacturer and model,
'dht_redirect_set': (bool),
'peer_redirect_set': (bool),
'external_ip': (str) external ip address,
} }
} }
""" """