From 6dff21165b1522a69a75c8306214794d692f9dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Sanchez?= Date: Wed, 21 Oct 2020 20:57:01 +0200 Subject: [PATCH] Initial version --- .editorconfig | 12 +++ .gitignore | 8 ++ .isort.cfg | 6 ++ Makefile | 12 +++ README.md | 37 ++++++++ poetry.lock | 188 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 24 +++++ src/mtuprobe/__init__.py | 0 src/mtuprobe/__main__.py | 133 +++++++++++++++++++++++++++ src/mtuprobe/ping.py | 60 +++++++++++++ src/mtuprobe/probe.py | 77 ++++++++++++++++ 11 files changed, 557 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .isort.cfg create mode 100644 Makefile create mode 100644 README.md create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 src/mtuprobe/__init__.py create mode 100644 src/mtuprobe/__main__.py create mode 100644 src/mtuprobe/ping.py create mode 100644 src/mtuprobe/probe.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5c2153d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..041b0d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.pyc +*.swp +*.un~ +.pytest_cache +.idea +dist +*.egg-info +/docs/build/ diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..ba2778d --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,6 @@ +[settings] +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +line_length=88 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c82e00c --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +PYTHON_BIN ?= poetry run python + +format: isort black + +black: + $(PYTHON_BIN) -m black --target-version py38 --exclude '/(\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist|node_modules|webpack_bundles)/' . + +isort: + $(PYTHON_BIN) -m isort src + +publish: + poetry publish diff --git a/README.md b/README.md new file mode 100644 index 0000000..de1c38f --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +mtuprobe +======== + +A tool to probe the current +[MTU](https://en.wikipedia.org/wiki/Maximum_transmission_unit) on an IPv4 path. + +So far it's compatible with Linux only however it can very easily be adapted +to any other operating system. + +## Installation + +``` +pip install mtuprobe +``` + +## Usage + +The default options are sane and should give you good results. Suppose that +you want to know the current effective MTU towards `wikipedia.org`, in a shell +you can try: + +``` +% mtuprobe wikipedia.org +Max packet size: 1464 +Expected ethernet MTU: 1492 +``` + +You can check out `mtuprobe -h` to get the complete list of options. + +Values are: + +- **Max packet size** — Max packet size specified to `ping`, meaning the + size of the data segment of the ICMP packet +- **Expected ethernet MTU** — That's what the MTU should be if you're + transmitting over Ethernet and the header sizes are what is expected from + such a setup. This should apply most of the time but surely some weird + network setups could violate this. diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..a835176 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,188 @@ +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.4" + +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +python-versions = ">=3.6" +version = "20.8b1" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +category = "dev" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" + +[[package]] +category = "dev" +description = "A Python utility / library to sort Python imports." +name = "isort" +optional = false +python-versions = ">=3.6,<4.0" +version = "5.6.4" + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] + +[[package]] +category = "dev" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +name = "mypy-extensions" +optional = false +python-versions = "*" +version = "0.4.3" + +[[package]] +category = "dev" +description = "Utility library for gitignore style pattern matching of file paths." +name = "pathspec" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.8.0" + +[[package]] +category = "dev" +description = "Alternative regular expression module, to replace re." +name = "regex" +optional = false +python-versions = "*" +version = "2020.10.15" + +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.1" + +[[package]] +category = "dev" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" +optional = false +python-versions = "*" +version = "1.4.1" + +[[package]] +category = "dev" +description = "Backported and Experimental Type Hints for Python 3.5+" +name = "typing-extensions" +optional = false +python-versions = "*" +version = "3.7.4.3" + +[metadata] +content-hash = "22cc595eb90611f27b6989913f6c237fc193f5baf74851759659194b8391acf3" +python-versions = "^3.8" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +isort = [ + {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, + {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +pathspec = [ + {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, + {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, +] +regex = [ + {file = "regex-2020.10.15-cp27-cp27m-win32.whl", hash = "sha256:e935a166a5f4c02afe3f7e4ce92ce5a786f75c6caa0c4ce09c922541d74b77e8"}, + {file = "regex-2020.10.15-cp27-cp27m-win_amd64.whl", hash = "sha256:d81be22d5d462b96a2aa5c512f741255ba182995efb0114e5a946fe254148df1"}, + {file = "regex-2020.10.15-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6d4cdb6c20e752426b2e569128488c5046fb1b16b1beadaceea9815c36da0847"}, + {file = "regex-2020.10.15-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:25991861c6fef1e5fd0a01283cf5658c5e7f7aa644128e85243bc75304e91530"}, + {file = "regex-2020.10.15-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:6e9f72e0ee49f7d7be395bfa29e9533f0507a882e1e6bf302c0a204c65b742bf"}, + {file = "regex-2020.10.15-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:578ac6379e65eb8e6a85299b306c966c852712c834dc7eef0ba78d07a828f67b"}, + {file = "regex-2020.10.15-cp36-cp36m-win32.whl", hash = "sha256:65b6b018b07e9b3b6a05c2c3bb7710ed66132b4df41926c243887c4f1ff303d5"}, + {file = "regex-2020.10.15-cp36-cp36m-win_amd64.whl", hash = "sha256:2f60ba5c33f00ce9be29a140e6f812e39880df8ba9cb92ad333f0016dbc30306"}, + {file = "regex-2020.10.15-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5d4a3221f37520bb337b64a0632716e61b26c8ae6aaffceeeb7ad69c009c404b"}, + {file = "regex-2020.10.15-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:26b85672275d8c7a9d4ff93dbc4954f5146efdb2ecec89ad1de49439984dea14"}, + {file = "regex-2020.10.15-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:828618f3c3439c5e6ef8621e7c885ca561bbaaba90ddbb6a7dfd9e1ec8341103"}, + {file = "regex-2020.10.15-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:aef23aed9d4017cc74d37f703d57ce254efb4c8a6a01905f40f539220348abf9"}, + {file = "regex-2020.10.15-cp37-cp37m-win32.whl", hash = "sha256:6c72adb85adecd4522a488a751e465842cdd2a5606b65464b9168bf029a54272"}, + {file = "regex-2020.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:ef3a55b16c6450574734db92e0a3aca283290889934a23f7498eaf417e3af9f0"}, + {file = "regex-2020.10.15-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8958befc139ac4e3f16d44ec386c490ea2121ed8322f4956f83dd9cad8e9b922"}, + {file = "regex-2020.10.15-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3dd952f3f8dc01b72c0cf05b3631e05c50ac65ddd2afdf26551638e97502107b"}, + {file = "regex-2020.10.15-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:608d6c05452c0e6cc49d4d7407b4767963f19c4d2230fa70b7201732eedc84f2"}, + {file = "regex-2020.10.15-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:02686a2f0b1a4be0facdd0d3ad4dc6c23acaa0f38fb5470d892ae88584ba705c"}, + {file = "regex-2020.10.15-cp38-cp38-win32.whl", hash = "sha256:137da580d1e6302484be3ef41d72cf5c3ad22a076070051b7449c0e13ab2c482"}, + {file = "regex-2020.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:20cdd7e1736f4f61a5161aa30d05ac108ab8efc3133df5eb70fe1e6a23ea1ca6"}, + {file = "regex-2020.10.15-cp39-cp39-manylinux1_i686.whl", hash = "sha256:85b733a1ef2b2e7001aff0e204a842f50ad699c061856a214e48cfb16ace7d0c"}, + {file = "regex-2020.10.15-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:af1f5e997dd1ee71fb6eb4a0fb6921bf7a778f4b62f1f7ef0d7445ecce9155d6"}, + {file = "regex-2020.10.15-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:b5eeaf4b5ef38fab225429478caf71f44d4a0b44d39a1aa4d4422cda23a9821b"}, + {file = "regex-2020.10.15-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:aeac7c9397480450016bc4a840eefbfa8ca68afc1e90648aa6efbfe699e5d3bb"}, + {file = "regex-2020.10.15-cp39-cp39-win32.whl", hash = "sha256:698f8a5a2815e1663d9895830a063098ae2f8f2655ae4fdc5dfa2b1f52b90087"}, + {file = "regex-2020.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:a51e51eecdac39a50ede4aeed86dbef4776e3b73347d31d6ad0bc9648ba36049"}, + {file = "regex-2020.10.15.tar.gz", hash = "sha256:d25f5cca0f3af6d425c9496953445bf5b288bb5b71afc2b8308ad194b714c159"}, +] +toml = [ + {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, + {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, +] +typed-ast = [ + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, +] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b1b92a2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "mtuprobe" +version = "0.1.0" +description = "A tool to find out maximum MTU size on an IPv4 path" +authors = ["Rémy Sanchez "] +license = "WTFPL" +readme = "README.md" +homepage = "https://github.com/Xowap/mtuprobe" +repository = "https://github.com/Xowap/mtuprobe" +documentation = "https://github.com/Xowap/mtuprobe" + +[tool.poetry.scripts] +mtuprobe = 'mtuprobe.__main__:__main__' + +[tool.poetry.dependencies] +python = "^3.8" + +[tool.poetry.dev-dependencies] +isort = "^5.6.4" +black = "^20.8b1" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/src/mtuprobe/__init__.py b/src/mtuprobe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mtuprobe/__main__.py b/src/mtuprobe/__main__.py new file mode 100644 index 0000000..1c81920 --- /dev/null +++ b/src/mtuprobe/__main__.py @@ -0,0 +1,133 @@ +from argparse import ArgumentParser +from enum import Enum +from typing import Optional, Sequence + +from . import ping +from .probe import probe_mtu + + +class Mode(Enum): + """ + Ping compatibility mode + + - auto = auto-detect + - gnu = GNU ping implementation + """ + + auto = "auto" + gnu = "gnu" + + +def positive_int(i) -> int: + """ + Checks that provided integers are positive and non-null (from CLI args) + + Parameters + ---------- + i + String to parse + """ + + x = int(i) + + if x <= 0: + raise ValueError("Integer must be postitive and non-null") + + return x + + +def parse_args(argv: Optional[Sequence[str]] = None): + """ + Setups and executes the CLI arguments parser + + Parameters + ---------- + argv + If not specified, argparse will default to the "real" argv + """ + + parser = ArgumentParser( + description=( + "mtuprobe uses the system ping utility to determine the IPv4 MTU " + "between current machine and a network address" + ) + ) + + parser.add_argument( + "-b", "--ping-bin", default="ping", help="Path to ping binary (default: ping)" + ) + parser.add_argument( + "-m", + "--mode", + type=Mode, + default=Mode.auto, + help=( + "Compatibility mode of the binary. Options: " + + ", ".join([x.name for x in Mode]) + + ". (default: auto)" + ), + ) + parser.add_argument( + "-s", + "--max-size", + type=positive_int, + help="Max payload size (default: 3000)", + default=3000, + ) + parser.add_argument( + "-c", + "--count", + default=4, + type=positive_int, + help="Number of pings sent (default: 4)", + ) + parser.add_argument( + "-q", + "--quiet", + action="store_true", + default=False, + help="If set, only the MTU will be printed", + ) + + parser.add_argument("address", help="Address you want to ping") + + return parser.parse_args(argv) + + +def main(argv: Optional[Sequence[str]] = None): + """ + Executes the main program + + Parameters + ---------- + argv + Provide custom CLI args if you call this from Python, otherwise the + "real ones" will be used + """ + + args = parse_args(argv) + probe_mtu( + bin_path=args.ping_bin, + addr=args.address, + count=args.count, + max_size=args.max_size, + quiet=args.quiet, + runner=getattr(ping, args.mode.name), + ) + + +def __main__(): + """ + Wrapper around the main function to catch interrupts + """ + + try: + main() + except KeyboardInterrupt: + pass + except SystemExit: + pass + + +if __name__ == "__main__": + __main__() diff --git a/src/mtuprobe/ping.py b/src/mtuprobe/ping.py new file mode 100644 index 0000000..727e3b5 --- /dev/null +++ b/src/mtuprobe/ping.py @@ -0,0 +1,60 @@ +from subprocess import DEVNULL, Popen +from sys import stderr + + +def auto(bin_path: str, addr: str, count: int, size: int) -> bool: + """ + In the future, auto-detect which ping implementation to call, however for + now just forwards the call to gnu. + + Returns True if at least one ping went through (and came back) + + Parameters + ---------- + bin_path + Path to ping binary + addr + Address to ping + count + Number of pings to send + size + Size of the payload + """ + + return gnu(bin_path, addr, count, size) + + +def gnu(bin_path: str, addr: str, count: int, size: int) -> bool: + """ + Using the GNU ping (so Linux machines most likely) + """ + + try: + with Popen( + args=[ + bin_path, + "-i", + "0.2", + "-c", + f"{count}", + "-M", + "do", + "-s", + f"{size}", + addr, + ], + stdout=DEVNULL, + stdin=DEVNULL, + stderr=DEVNULL, + ) as p: + p.wait() + except FileNotFoundError: + stderr.write(f'\rping binary "{bin_path}" could not be found\n') + stderr.flush() + exit(1) + except PermissionError: + stderr.write(f'\rping binary "{bin_path}" cannot be executed\n') + stderr.flush() + exit(1) + + return not p.returncode diff --git a/src/mtuprobe/probe.py b/src/mtuprobe/probe.py new file mode 100644 index 0000000..938057d --- /dev/null +++ b/src/mtuprobe/probe.py @@ -0,0 +1,77 @@ +from collections import Callable + + +def bisect(n, mapper, tester): + """ + Runs a bisection. + + - `n` is the number of elements to be bisected + - `mapper` is a callable that will transform an integer from "0" to "n" + into a value that can be tested + - `tester` returns true if the value is within the "right" range + """ + + if n < 1: + raise ValueError("Cannot bisect an empty array") + + left = 0 + right = n - 1 + + while left + 1 < right: + mid = int((left + right) / 2) + + val = mapper(mid) + + if tester(val): + right = mid + else: + left = mid + + return mapper(right) + + +def probe_mtu( + bin_path: str, addr: str, count: int, max_size: int, quiet: bool, runner: Callable +) -> None: + """ + Uses a bisection algorithm to pinpoint the exact MTU of the path + + Parameters + ---------- + bin_path + Path to the ping binary + addr + Address to ping + count + Number of pings to do for each try + max_size + Max MTU size to test + quiet + Stay quiet in the output (just print the found MTU) + runner + Ping runner function + """ + + def mapper(n): + return n + 1 + + def tester(n): + if not quiet: + print(f"\rTesting packet size: {n: >6}", end="", flush=True) + + return not runner( + bin_path=bin_path, + addr=addr, + count=count, + size=n, + ) + + size = bisect(max_size, mapper, tester) - 1 + + if not quiet: + extra = 8 + 20 # ICMP + IPv4 + + print(f"\rMax packet size: {size: >6}") + print(f"Expected ethernet MTU: {size + extra: >6}") + else: + print(f"{size}")