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}")