Verified Commit 893efe53 authored by Martin Prudek's avatar Martin Prudek
Browse files

Distinguish between IPv4 and IPv6

parent 04c6059f
......@@ -6,6 +6,7 @@ from .argparser import get_arg_parser
from .exceptions import NetmetrError
from .logging import logger
from .netmetr import Netmetr
from .protocols import Mode, get_proto_mode
from .config import make_default_config
......@@ -64,7 +65,8 @@ def main():
config["uuid"] = netmetr.get_uuid()
if not args.no_run:
netmetr.measure()
protocol_mode = get_proto_mode(config["protocol_mode"])
netmetr.measure(protocol_mode)
if args.dwlhist:
netmetr.download_history(config["max_history_logs"])
......
......@@ -8,7 +8,7 @@ CONFIG = "netmetr"
SETTINGS_SECTION = "settings"
UCI_MANDATORY_OPTIONS = {
"autostart_enabled", "hours_to_run", "autostart_delay",
"max_history_logs", "control_server"
"max_history_logs", "control_server", "protocol_mode"
}
......
......@@ -12,6 +12,7 @@ import urllib.parse
from . import __version__
from .exceptions import ControlServerError
from .logging import logger
from .protocols import Protocol
CLIENT_SW_VERSION = "Python netmetr client v{}".format(__version__)
DEFAULT_LANG = "en_US"
......@@ -19,7 +20,10 @@ DEFAULT_LANG = "en_US"
class ControlServer:
def __init__(self, url, uuid=None, use_tls=True):
self.address = url
self.url_dual = url
self.url_ipv4 = None
self.url_ipv6 = None
self.address = self.url_dual
self.uuid = uuid
self.use_tls = use_tls
......@@ -55,7 +59,19 @@ class ControlServer:
self.uuid = new_uuid
if not self.uuid:
raise ControlServerError("UUID not contained in control server response")
self.url_ipv4 = rep["settings"][0]["urls"].get("control_ipv4_only")
self.url_ipv6 = rep["settings"][0]["urls"].get("control_ipv6_only")
@contextlib.contextmanager
def use_proto(self, proto):
try:
if proto == Protocol.IPv4:
self.address = self.url_ipv4
else:
self.address = self.url_ipv6
yield
finally:
self.address = self.url_dual
def request_settings(self):
logger.progress("Requesting test config from the control server...")
......
......@@ -9,6 +9,7 @@ import typing
from .exceptions import MeasurementError
from .logging import logger
from .network import get_network_type
from .protocols import Protocol
RMBT_BIN = "rmbt"
SPEED_RESULT_REQUIRED_FIELDS = {
......@@ -18,10 +19,12 @@ SPEED_RESULT_REQUIRED_FIELDS = {
class Measurement:
def __init__(self, settings):
def __init__(self, proto: Protocol, settings):
self.results = None
self.proto = proto
self.test_token = settings["test_token"]
self.pings = PingMeasurement(
proto,
settings["test_numpings"],
settings["test_server_address"],
)
......@@ -63,8 +66,9 @@ class Measurement:
}
logger.info(
"Test result: download: {:.2f}Mbps, upload: "
"{} test result: download: {:.2f}Mbps, upload: "
"{:.2f}Mbps, ping: {:.2f}ms".format(
self.proto,
speed_results["res_dl_throughput_kbps"] / 1000,
speed_results["res_ul_throughput_kbps"] / 1000,
ping_shortest / 1000000
......@@ -111,8 +115,9 @@ class Measurement:
class PingMeasurement:
def __init__(self, count, test_server_address):
def __init__(self, proto: Protocol, count, test_server_address):
self.shortest = None
self.proto = proto
self.count = count
self.test_server_address = test_server_address
......@@ -120,7 +125,7 @@ class PingMeasurement:
"""Run serie of pings to the test server and computes & saves
the lowest one
"""
ping_cmd = "ping"
ping_cmd = "ping" if self.proto == Protocol.IPv4 else "ping6"
logger.progress("Starting ping test...")
ping_values = list()
for i in range(1, int(self.count)+1):
......
......@@ -7,6 +7,7 @@ from .control import ControlServer
from .exceptions import ControlServerError, MeasurementError
from .logging import logger
from .measurement import Measurement
from .protocols import Protocol, Mode
HIST_FILE = "/tmp/netmetr-history.json"
......@@ -43,22 +44,101 @@ class Netmetr:
def download_sync_code(self):
return self.control_server.download_sync_code()
def measure(self):
def measure(self, protocol_mode: Mode = Mode.prefer_6):
"""Measure speed and other parameters of internet connection
Do not raise any exception on basic connection problems. Raise exceptions
defined in netmetr.exceptions for more severe cases.
Keyword arguments:
protocol_mode -- the protocols we want to benchmark (IPv4/IPv6) defined
in netmetr.protocols
Returns a dictionary with test results. An example of such a dictionary
might be:
{
'IPv6': {
'error': 'Not available'
},
'IPv4': {
'download_mbps': 90.69,
'upload_mbps': 58.08,
'ping_ms': 4.2
}
}
"""
try:
measured = False
result = {}
proto_map = {
Protocol.IPv4: [Mode.only_4, Mode.both, Mode.prefer_4],
Protocol.IPv6: [Mode.only_6, Mode.both, Mode.prefer_6]
}
for proto, protocol_modes in proto_map.items():
if protocol_mode in protocol_modes:
try:
result[proto] = self._measure_protocol(proto)
measured = True
except ControlServerError as exc:
logger.error(f"{proto.value} not available for measurement ({exc})")
result[proto] = {"error": "Not available"}
except MeasurementError as exc:
logger.error(f"{proto.value} measurement failed: {exc}")
result[proto] = {"error": "Failed"}
if not measured:
proto = None
if protocol_mode == Mode.prefer_4:
proto = Protocol.IPv6
if protocol_mode == Mode.prefer_6:
proto = Protocol.IPv4
if proto is not None:
try:
result[proto] = self._measure_protocol(proto)
except ControlServerError as exc:
logger.error(f"{proto.value} not available for measurement ({exc})")
result[proto] = {"error": "Not available"}
except MeasurementError as exc:
logger.error(f"{proto.value} measurement failed: {exc}")
result[proto] = {"error": "Failed"}
return result
def measure_4(self):
"""Measure speed and other parameters of internet connection using IPv4
Raise an exception whenever it is not possible to successfully finish the
measurement
On success, return a dictionary like this:
{
'download_mbps': 90.69,
'upload_mbps': 58.08,
'ping_ms': 4.2
}
"""
return self._measure_protocol(Protocol.IPv4)
def measure_6(self):
"""Measure speed and other parameters of internet connection using IPv6
Raise an exception whenever it is not possible to successfully finish the
measurement
On success, return a dictionary like this:
{
'download_mbps': 90.69,
'upload_mbps': 58.08,
'ping_ms': 4.2
}
"""
return self._measure_protocol(Protocol.IPv6)
def _measure_protocol(self, proto: Protocol):
logger.progress(f"Preparing for {proto.value} measurement...")
with self.control_server.use_proto(proto):
test_settings = self.control_server.request_settings()
measurement = Measurement(test_settings)
measurement = Measurement(proto, test_settings)
simple_result, full_results = measurement.measure()
self.control_server.upload_result(*full_results)
return simple_result
except ControlServerError as exc:
logger.error(f"Measurement failed ({exc})")
return {"error": "Not available"}
except MeasurementError as exc:
logger.error(f"Measurement failed: {exc}")
return {"error": "Failed"}
import enum
from .exceptions import ConfigError
class Protocol(enum.Enum):
IPv4 = "IPv4"
IPv6 = "IPv6"
class Mode(enum.Enum):
only_4 = enum.auto()
only_6 = enum.auto()
prefer_4 = enum.auto()
prefer_6 = enum.auto()
both = enum.auto()
def get_proto_mode(string):
if string == "only_4":
return Mode.only_4
if string == "only_6":
return Mode.only_6
if string == "prefer_4":
return Mode.prefer_4
if string == "prefer_6":
return Mode.prefer_6
if string == "both":
return Mode.both
raise ConfigError("Not a valid protocol mode: {}".format(string))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment