diff --git a/CHANGELOG.md b/CHANGELOG.md index 8af4f8f7c..8476be520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ ([#5590](https://github.com/mitmproxy/mitmproxy/pull/5590), @mhils) * Add MQTT content view. ([#5588](https://github.com/mitmproxy/mitmproxy/pull/5588), @nikitastupin, @abbbe) +* Add WireGuard mode to enable userspace transparent proxying via WireGuard. + ([#5562](https://github.com/mitmproxy/mitmproxy/pull/5562), @decathorpe, @mhils) ## 28 June 2022: mitmproxy 8.1.1 diff --git a/mitmproxy/net/udp.py b/mitmproxy/net/udp.py index 152a68b5e..328fcba75 100644 --- a/mitmproxy/net/udp.py +++ b/mitmproxy/net/udp.py @@ -4,7 +4,9 @@ import asyncio import logging import socket from typing import Any, Callable, Optional, Union, cast + from mitmproxy.connection import Address +from mitmproxy.net import udp_wireguard from mitmproxy.utils import human logger = logging.getLogger(__name__) @@ -27,7 +29,6 @@ SockAddress = Union[tuple[str, int], tuple[str, int, int, int]] class DrainableDatagramProtocol(asyncio.DatagramProtocol): - _loop: asyncio.AbstractEventLoop _closed: asyncio.Event _paused: int @@ -98,6 +99,7 @@ class UdpServer(DrainableDatagramProtocol): def connection_made(self, transport: asyncio.BaseTransport) -> None: if self._transport is None: self._transport = cast(asyncio.DatagramTransport, transport) + self._transport.set_protocol(self) self._local_addr = transport.get_extra_info("sockname") super().connection_made(transport) @@ -112,7 +114,6 @@ class UdpServer(DrainableDatagramProtocol): class DatagramReader: - _packets: asyncio.Queue _eof: bool @@ -157,7 +158,6 @@ class DatagramReader: class DatagramWriter: - _transport: asyncio.DatagramTransport _remote_addr: Address _reader: DatagramReader | None @@ -175,14 +175,12 @@ class DatagramWriter: """ self._transport = transport self._remote_addr = remote_addr - proto = transport.get_protocol() - assert isinstance(proto, DrainableDatagramProtocol) self._reader = reader self._closed = asyncio.Event() if reader is not None else None @property - def _protocol(self) -> DrainableDatagramProtocol: - return cast(DrainableDatagramProtocol, self._transport.get_protocol()) + def _protocol(self) -> DrainableDatagramProtocol | udp_wireguard.WireGuardDatagramTransport: + return self._transport.get_protocol() # type: ignore def write(self, data: bytes) -> None: self._transport.sendto(data, self._remote_addr) diff --git a/mitmproxy/net/udp_wireguard.py b/mitmproxy/net/udp_wireguard.py new file mode 100644 index 000000000..b857ec76c --- /dev/null +++ b/mitmproxy/net/udp_wireguard.py @@ -0,0 +1,35 @@ +""" +This module contains a mock DatagramTransport for use with mitmproxy-wireguard. +""" +import asyncio +from typing import Any + +import mitmproxy_wireguard as wg + +from mitmproxy.connection import Address + + +class WireGuardDatagramTransport(asyncio.DatagramTransport): + def __init__(self, server: wg.Server, local_addr: Address, remote_addr: Address): + self._server: wg.Server = server + self._local_addr: Address = local_addr + self._remote_addr: Address = remote_addr + super().__init__() + + def sendto(self, data, addr=None): + self._server.send_datagram(data, self._local_addr, addr or self._remote_addr) + + def get_extra_info(self, name: str, default: Any = None) -> Any: + if name == "sockname": + return self._server.getsockname() + else: + raise NotImplementedError + + def get_protocol(self): + return self + + async def drain(self) -> None: + pass + + async def wait_closed(self) -> None: + pass diff --git a/mitmproxy/proxy/mode_servers.py b/mitmproxy/proxy/mode_servers.py index a7b8c17b1..ae70eb981 100644 --- a/mitmproxy/proxy/mode_servers.py +++ b/mitmproxy/proxy/mode_servers.py @@ -12,25 +12,29 @@ Example: from __future__ import annotations import asyncio +import json import logging - -import errno import socket +import textwrap import typing from abc import ABCMeta, abstractmethod from contextlib import contextmanager +from pathlib import Path from typing import ClassVar, Generic, TypeVar, cast, get_args +import errno +import mitmproxy_wireguard as wg + from mitmproxy import ctx, flow, platform from mitmproxy.connection import Address from mitmproxy.master import Master -from mitmproxy.net import udp +from mitmproxy.net import local_ip, udp +from mitmproxy.net.udp_wireguard import WireGuardDatagramTransport from mitmproxy.proxy import commands, layers, mode_specs, server from mitmproxy.proxy.context import Context from mitmproxy.proxy.layer import Layer from mitmproxy.utils import human - logger = logging.getLogger(__name__) @@ -62,8 +66,11 @@ class ServerManager(typing.Protocol): ... # pragma: no cover -class ServerInstance(Generic[M], metaclass=ABCMeta): +# Python 3.11: Use typing.Self +Self = TypeVar("Self", bound="ServerInstance") + +class ServerInstance(Generic[M], metaclass=ABCMeta): __modes: ClassVar[dict[str, type[ServerInstance]]] = {} def __init__(self, mode: M, manager: ServerManager): @@ -80,14 +87,20 @@ class ServerInstance(Generic[M], metaclass=ABCMeta): assert mode.type not in ServerInstance.__modes ServerInstance.__modes[mode.type] = cls - @staticmethod + @classmethod def make( + cls: typing.Type[Self], mode: mode_specs.ProxyMode | str, manager: ServerManager, - ) -> ServerInstance: + ) -> Self: if isinstance(mode, str): mode = mode_specs.ProxyMode.parse(mode) - return ServerInstance.__modes[mode.type](mode, manager) + inst = ServerInstance.__modes[mode.type](mode, manager) + + if not isinstance(inst, cls): + raise ValueError(f"{mode!r} is not a spec for a {cls.__name__} server.") + + return inst @property @abstractmethod @@ -107,6 +120,10 @@ class ServerInstance(Generic[M], metaclass=ABCMeta): def listen_addrs(self) -> tuple[Address, ...]: pass + @abstractmethod + def make_top_layer(self, context: Context) -> Layer: + pass + def to_json(self) -> dict: return { "type": self.mode.type, @@ -117,15 +134,67 @@ class ServerInstance(Generic[M], metaclass=ABCMeta): "listen_addrs": self.listen_addrs, } + async def handle_tcp_connection( + self, + reader: asyncio.StreamReader | wg.TcpStream, + writer: asyncio.StreamWriter | wg.TcpStream, + ) -> None: + connection_id = ( + "tcp", + writer.get_extra_info("peername"), + writer.get_extra_info("sockname"), + ) + handler = ProxyConnectionHandler( + ctx.master, reader, writer, ctx.options, self.mode + ) + handler.layer = self.make_top_layer(handler.layer.context) + if isinstance(self.mode, mode_specs.TransparentMode): + socket = writer.get_extra_info("socket") + try: + assert platform.original_addr + handler.layer.context.server.address = platform.original_addr(socket) + except Exception as e: + logger.error(f"Transparent mode failure: {e!r}") + return + with self.manager.register_connection(connection_id, handler): + await handler.handle_client() + + def handle_udp_datagram( + self, + transport: asyncio.DatagramTransport, + data: bytes, + remote_addr: Address, + local_addr: Address, + ) -> None: + connection_id = ("udp", remote_addr, local_addr) + if connection_id not in self.manager.connections: + reader = udp.DatagramReader() + writer = udp.DatagramWriter(transport, remote_addr, reader) + handler = ProxyConnectionHandler( + ctx.master, reader, writer, ctx.options, self.mode + ) + handler.timeout_watchdog.CONNECTION_TIMEOUT = 20 + handler.layer = self.make_top_layer(handler.layer.context) + handler.layer.context.client.transport_protocol = "udp" + handler.layer.context.server.transport_protocol = "udp" + + # pre-register here - we may get datagrams before the task is executed. + self.manager.connections[connection_id] = handler + asyncio.create_task(self.handle_udp_connection(connection_id, handler)) + else: + handler = self.manager.connections[connection_id] + reader = cast(udp.DatagramReader, handler.transports[handler.client].reader) + reader.feed_data(data, remote_addr) + + async def handle_udp_connection(self, connection_id: tuple, handler: ProxyConnectionHandler) -> None: + with self.manager.register_connection(connection_id, handler): + await handler.handle_client() + class AsyncioServerInstance(ServerInstance[M], metaclass=ABCMeta): _server: asyncio.Server | udp.UdpServer | None = None _listen_addrs: tuple[Address, ...] = tuple() - @abstractmethod - def make_top_layer(self, context: Context) -> Layer: - pass - @property def is_running(self) -> bool: return self._server is not None @@ -202,61 +271,117 @@ class AsyncioServerInstance(ServerInstance[M], metaclass=ABCMeta): def listen_addrs(self) -> tuple[Address, ...]: return self._listen_addrs - async def handle_tcp_connection( - self, - reader: asyncio.StreamReader, - writer: asyncio.StreamWriter, - ) -> None: - connection_id = ( - "tcp", - writer.get_extra_info("peername"), - writer.get_extra_info("sockname"), - ) - handler = ProxyConnectionHandler( - ctx.master, reader, writer, ctx.options, self.mode - ) - handler.layer = self.make_top_layer(handler.layer.context) - if isinstance(self.mode, mode_specs.TransparentMode): - socket = writer.get_extra_info("socket") - try: - assert platform.original_addr - handler.layer.context.server.address = platform.original_addr(socket) - except Exception as e: - logger.error(f"Transparent mode failure: {e!r}") - return - with self.manager.register_connection(connection_id, handler): - await handler.handle_client() - def handle_udp_datagram( - self, - transport: asyncio.DatagramTransport, - data: bytes, - remote_addr: Address, - local_addr: Address, - ) -> None: - connection_id = ("udp", remote_addr, local_addr) - if connection_id not in self.manager.connections: - reader = udp.DatagramReader() - writer = udp.DatagramWriter(transport, remote_addr, reader) - handler = ProxyConnectionHandler( - ctx.master, reader, writer, ctx.options, self.mode - ) - handler.timeout_watchdog.CONNECTION_TIMEOUT = 20 - handler.layer = self.make_top_layer(handler.layer.context) - handler.layer.context.client.transport_protocol = "udp" - handler.layer.context.server.transport_protocol = "udp" +class WireGuardServerInstance(ServerInstance[mode_specs.WireGuardMode]): + _server: wg.Server | None = None + _listen_addrs: tuple[Address, ...] = tuple() - # pre-register here - we may get datagrams before the task is executed. - self.manager.connections[connection_id] = handler - asyncio.create_task(self.handle_udp_connection(connection_id, handler)) + server_key: str + client_key: str + + def make_top_layer(self, context: Context) -> Layer: + return layers.modes.TransparentProxy(context) + + @property + def is_running(self) -> bool: + return self._server is not None + + async def start(self) -> None: + assert self._server is None + host = self.mode.listen_host(ctx.options.listen_host) + port = self.mode.listen_port(ctx.options.listen_port) + + if self.mode.data: + conf_path = Path(self.mode.data).expanduser() else: - handler = self.manager.connections[connection_id] - reader = cast(udp.DatagramReader, handler.transports[handler.client].reader) - reader.feed_data(data, remote_addr) + conf_path = Path(ctx.options.confdir).expanduser() / "wireguard.conf" - async def handle_udp_connection(self, connection_id: tuple, handler: ProxyConnectionHandler) -> None: - with self.manager.register_connection(connection_id, handler): - await handler.handle_client() + try: + if not conf_path.exists(): + conf_path.write_text(json.dumps({ + "server_key": wg.genkey(), + "client_key": wg.genkey(), + }, indent=4)) + + try: + c = json.loads(conf_path.read_text()) + self.server_key = c["server_key"] + self.client_key = c["client_key"] + except Exception as e: + raise ValueError(f"Invalid configuration file ({conf_path}): {e}") from e + # error early on invalid keys + p = wg.pubkey(self.client_key) + _ = wg.pubkey(self.server_key) + + self._server = await wg.start_server( + host, + port, + self.server_key, + [p], + self.wg_handle_tcp_connection, + self.wg_handle_udp_datagram, + ) + self._listen_addrs = (self._server.getsockname(),) + except Exception as e: + self.last_exception = e + message = f"{self.mode.description} failed to listen on {host or '*'}:{port} with {e}" + raise OSError(message) from e + else: + self.last_exception = None + + addrs = " and ".join({human.format_address(a) for a in self.listen_addrs}) + logger.info(f"{self.mode.description} listening at {addrs}.") + logger.info(self.client_conf()) + + def client_conf(self) -> str | None: + if not self._server: + return None + host = local_ip.get_local_ip() or local_ip.get_local_ip6() + port = self.mode.listen_port(ctx.options.listen_port) + return textwrap.dedent(f""" + ------------------------------------------------------------ + [Interface] + PrivateKey = {self.client_key} + Address = 10.0.0.1/32 + + [Peer] + PublicKey = {wg.pubkey(self.server_key)} + AllowedIPs = 0.0.0.0/0 + Endpoint = {host}:{port} + ------------------------------------------------------------ + """).strip() + + def to_json(self) -> dict: + return { + "wireguard_conf": self.client_conf(), + **super().to_json() + } + + async def stop(self) -> None: + assert self._server is not None + self._server.close() + await self._server.wait_closed() + self._server = None + self.last_exception = None + + addrs = " and ".join({human.format_address(a) for a in self.listen_addrs}) + logger.info(f"Stopped {self.mode.description} at {addrs}.") + + @property + def listen_addrs(self) -> tuple[Address, ...]: + return self._listen_addrs + + async def wg_handle_tcp_connection(self, stream: wg.TcpStream) -> None: + await self.handle_tcp_connection(stream, stream) + + def wg_handle_udp_datagram(self, data: bytes, remote_addr: Address, local_addr: Address) -> None: + transport = WireGuardDatagramTransport(self._server, local_addr, remote_addr) + self.handle_udp_datagram( + transport, + data, + remote_addr, + local_addr + ) class RegularInstance(AsyncioServerInstance[mode_specs.RegularMode]): diff --git a/mitmproxy/proxy/mode_specs.py b/mitmproxy/proxy/mode_specs.py index 1367ce4d4..872f8d3ff 100644 --- a/mitmproxy/proxy/mode_specs.py +++ b/mitmproxy/proxy/mode_specs.py @@ -245,3 +245,13 @@ class DnsMode(ProxyMode): def __post_init__(self) -> None: _check_empty(self.data) + + +class WireGuardMode(ProxyMode): + """Proxy Server based on WireGuard""" + description = "WireGuard server" + default_port = 51820 + transport_protocol = UDP + + def __post_init__(self) -> None: + pass diff --git a/mitmproxy/proxy/server.py b/mitmproxy/proxy/server.py index 5b532f16c..06033e427 100644 --- a/mitmproxy/proxy/server.py +++ b/mitmproxy/proxy/server.py @@ -18,7 +18,9 @@ from contextlib import contextmanager from dataclasses import dataclass from typing import Optional, Union +import mitmproxy_wireguard as wg from OpenSSL import SSL + from mitmproxy import http, options as moptions, tls from mitmproxy.proxy.context import Context from mitmproxy.proxy.layers.http import HTTPMode @@ -78,8 +80,8 @@ class TimeoutWatchdog: @dataclass class ConnectionIO: handler: Optional[asyncio.Task] = None - reader: Optional[Union[asyncio.StreamReader, udp.DatagramReader]] = None - writer: Optional[Union[asyncio.StreamWriter, udp.DatagramWriter]] = None + reader: Optional[Union[asyncio.StreamReader, udp.DatagramReader, wg.TcpStream]] = None + writer: Optional[Union[asyncio.StreamWriter, udp.DatagramWriter, wg.TcpStream]] = None class ConnectionHandler(metaclass=abc.ABCMeta): @@ -132,6 +134,8 @@ class ConnectionHandler(metaclass=abc.ABCMeta): self.transports[self.client].handler = handler self.server_event(events.Start()) await asyncio.wait([handler]) + if not handler.cancelled() and (e := handler.exception()): + self.log(f"mitmproxy has crashed!\n{traceback.format_exception(e)}", logging.ERROR) watch.cancel() while self.wakeup_timer: @@ -407,8 +411,8 @@ class ConnectionHandler(metaclass=abc.ABCMeta): class LiveConnectionHandler(ConnectionHandler, metaclass=abc.ABCMeta): def __init__( self, - reader: asyncio.StreamReader, - writer: asyncio.StreamWriter, + reader: Union[asyncio.StreamReader, wg.TcpStream], + writer: Union[asyncio.StreamWriter, wg.TcpStream], options: moptions.Options, mode: mode_specs.ProxyMode, ) -> None: diff --git a/setup.cfg b/setup.cfg index 0a3cb9560..a43c808d5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,6 +69,7 @@ exclude = mitmproxy/net/http/message.py mitmproxy/net/http/multipart.py mitmproxy/net/tls.py + mitmproxy/net/udp_wireguard.py mitmproxy/options.py mitmproxy/proxy/config.py mitmproxy/proxy/server.py diff --git a/setup.py b/setup.py index ffc5067a0..b7968f3da 100644 --- a/setup.py +++ b/setup.py @@ -82,6 +82,7 @@ setup( "hyperframe>=6.0,<7", "kaitaistruct>=0.10,<0.11", "ldap3>=2.8,<2.10", + "mitmproxy_wireguard>=0.1.6,<0.2", "msgpack>=1.0.0, <1.1.0", "passlib>=1.6.5, <1.8", "protobuf>=3.14,<5", diff --git a/test/mitmproxy/net/test_udp_wireguard.py b/test/mitmproxy/net/test_udp_wireguard.py new file mode 100644 index 000000000..d8659064c --- /dev/null +++ b/test/mitmproxy/net/test_udp_wireguard.py @@ -0,0 +1 @@ +# testing this in isolation makes no sense. See proxy/test_mode_servers.py. diff --git a/test/mitmproxy/proxy/test_mode_servers.py b/test/mitmproxy/proxy/test_mode_servers.py index 38608595d..c1228b3ca 100644 --- a/test/mitmproxy/proxy/test_mode_servers.py +++ b/test/mitmproxy/proxy/test_mode_servers.py @@ -1,13 +1,15 @@ import asyncio +import platform from typing import cast from unittest.mock import AsyncMock, MagicMock, Mock import pytest -from mitmproxy import platform +import mitmproxy.platform from mitmproxy.addons.proxyserver import Proxyserver from mitmproxy.net import udp -from mitmproxy.proxy.mode_servers import DnsInstance, ServerInstance +from mitmproxy.proxy.mode_servers import DnsInstance, ServerInstance, WireGuardServerInstance +from mitmproxy.proxy.server import ConnectionHandler from mitmproxy.test import taddons @@ -23,6 +25,9 @@ def test_make(): assert inst.mode.description assert inst.to_json() + with pytest.raises(ValueError, match="is not a spec for a WireGuardServerInstance server."): + WireGuardServerInstance.make("regular", manager) + async def test_last_exception_and_running(monkeypatch): manager = MagicMock() @@ -33,7 +38,6 @@ async def test_last_exception_and_running(monkeypatch): raise err with taddons.context(): - inst1 = ServerInstance.make("regular@127.0.0.1:0", manager) await inst1.start() assert inst1.last_exception is None @@ -80,9 +84,9 @@ async def test_transparent(failure, monkeypatch, caplog_async): manager = MagicMock() if failure: - monkeypatch.setattr(platform, "original_addr", None) + monkeypatch.setattr(mitmproxy.platform, "original_addr", None) else: - monkeypatch.setattr(platform, "original_addr", lambda s: ("address", 42)) + monkeypatch.setattr(mitmproxy.platform, "original_addr", lambda s: ("address", 42)) with taddons.context(Proxyserver()) as tctx: tctx.options.connection_strategy = "lazy" @@ -107,6 +111,91 @@ async def test_transparent(failure, monkeypatch, caplog_async): assert await caplog_async.await_log("Stopped transparent proxy") +async def test_wireguard(tdata, monkeypatch, caplog): + caplog.set_level("DEBUG") + + async def handle_client(self: ConnectionHandler): + t = self.transports[self.client] + data = await t.reader.read(65535) + t.writer.write(data.upper()) + await t.writer.drain() + t.writer.close() + + monkeypatch.setattr(ConnectionHandler, "handle_client", handle_client) + + system = platform.system() + if system == "Linux": + test_client_name = "linux-x86_64" + elif system == "Darwin": + test_client_name = "macos-x86_64" + elif system == "Windows": + test_client_name = "windows-x86_64.exe" + else: + return pytest.skip("Unsupported platform for wg-test-client.") + + test_client_path = tdata.path(f"wg-test-client/{test_client_name}") + test_conf = tdata.path(f"wg-test-client/test.conf") + + with taddons.context(Proxyserver()): + inst = WireGuardServerInstance.make(f"wireguard:{test_conf}@0", MagicMock()) + + await inst.start() + assert "WireGuard server listening" in caplog.text + + _, port = inst.listen_addrs[0] + + assert inst.is_running + proc = await asyncio.create_subprocess_exec( + test_client_path, + str(port), + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await proc.communicate() + + try: + assert proc.returncode == 0 + except AssertionError: + print(stdout) + print(stderr) + raise + + await inst.stop() + assert "Stopped WireGuard server" in caplog.text + + +async def test_wireguard_generate_conf(tmp_path): + with taddons.context(Proxyserver()) as tctx: + tctx.options.confdir = str(tmp_path) + inst = WireGuardServerInstance.make(f"wireguard@0", MagicMock()) + assert not inst.client_conf() # should not error. + + await inst.start() + + assert (tmp_path / "wireguard.conf").exists() + assert inst.client_conf() + assert inst.to_json()["wireguard_conf"] + k = inst.server_key + + inst2 = WireGuardServerInstance.make(f"wireguard@0", MagicMock()) + await inst2.start() + assert k == inst2.server_key + + await inst.stop() + await inst2.stop() + + +async def test_wireguard_invalid_conf(tmp_path): + with taddons.context(Proxyserver()): + # directory instead of filename + inst = WireGuardServerInstance.make(f"wireguard:{tmp_path}", MagicMock()) + + with pytest.raises(OSError): + await inst.start() + + assert "Invalid configuration file" in repr(inst.last_exception) + + async def test_tcp_start_error(): manager = MagicMock() diff --git a/test/mitmproxy/proxy/test_mode_specs.py b/test/mitmproxy/proxy/test_mode_specs.py index fef84654a..3f68740c5 100644 --- a/test/mitmproxy/proxy/test_mode_specs.py +++ b/test/mitmproxy/proxy/test_mode_specs.py @@ -58,6 +58,9 @@ def test_parse_specific_modes(): assert ProxyMode.parse("dns") assert ProxyMode.parse("reverse:dns://8.8.8.8") assert ProxyMode.parse("reverse:dtls://127.0.0.1:8004") + assert ProxyMode.parse("wireguard") + assert ProxyMode.parse("wireguard:foo.conf").data == "foo.conf" + assert ProxyMode.parse("wireguard@51821").listen_port() == 51821 with pytest.raises(ValueError, match="invalid port"): ProxyMode.parse("regular@invalid-port") diff --git a/test/wg-test-client/LICENSE b/test/wg-test-client/LICENSE new file mode 100644 index 000000000..11f277c48 --- /dev/null +++ b/test/wg-test-client/LICENSE @@ -0,0 +1,89 @@ +The mitmproxy_wireguard test client is available under the same license (MIT) +as the mitmproxy_wireguard Python package and mitmproxy itself: + +-------------------------------------------------------------------------------- + +Copyright (c) 2022, Fabio Valentini and Maximilian Hils + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + +The test client also contains code from third-party Rust crates, which are +available under the following licenses: + +aead v0.5.1: MIT OR Apache-2.0 +anyhow v1.0.65: MIT OR Apache-2.0 +base64 v0.13.0: MIT/Apache-2.0 +bitflags v1.3.2: MIT/Apache-2.0 +blake2 v0.10.4: MIT OR Apache-2.0 +block-buffer v0.10.3: MIT OR Apache-2.0 +boringtun v0.5.2: BSD-3-Clause +byteorder v1.4.3: Unlicense OR MIT +cfg-if v1.0.0: MIT/Apache-2.0 +chacha20poly1305 v0.10.1: Apache-2.0 OR MIT +chacha20 v0.9.0: Apache-2.0 OR MIT +cipher v0.4.3: MIT OR Apache-2.0 +cpufeatures v0.2.5: MIT OR Apache-2.0 +crypto-common v0.1.6: MIT OR Apache-2.0 +curve25519-dalek v3.2.0: BSD-3-Clause +digest v0.10.5: MIT OR Apache-2.0 +digest v0.9.0: MIT OR Apache-2.0 +generic-array v0.14.6: MIT +getrandom v0.1.16: MIT OR Apache-2.0 +getrandom v0.2.7: MIT OR Apache-2.0 +hex v0.4.3: MIT OR Apache-2.0 +hmac v0.12.1: MIT OR Apache-2.0 +inout v0.1.3: MIT OR Apache-2.0 +ip_network_table-deps-treebitmap v0.5.0: MIT +ip_network_table v0.2.0: BSD-2-Clause +ip_network v0.4.1: BSD-2-Clause +libc v0.2.132: MIT OR Apache-2.0 +lock_api v0.4.8: MIT OR Apache-2.0 +log v0.4.17: MIT OR Apache-2.0 +managed v0.8.0: 0BSD +once_cell v1.14.0: MIT OR Apache-2.0 +opaque-debug v0.3.0: MIT OR Apache-2.0 +parking_lot_core v0.9.3: MIT OR Apache-2.0 +parking_lot v0.12.1: MIT OR Apache-2.0 +pin-project-lite v0.2.9: Apache-2.0 OR MIT +poly1305 v0.8.0: Apache-2.0 OR MIT +rand_core v0.5.1: MIT OR Apache-2.0 +rand_core v0.6.4: MIT OR Apache-2.0 +ring v0.16.20: +scopeguard v1.1.0: MIT/Apache-2.0 +smallvec v1.9.0: MIT OR Apache-2.0 +smoltcp v0.8.1: 0BSD +spin v0.5.2: MIT +subtle v2.4.1: BSD-3-Clause +tracing-core v0.1.29: MIT +tracing v0.1.36: MIT +typenum v1.15.0: MIT OR Apache-2.0 +universal-hash v0.5.0: MIT OR Apache-2.0 +untrusted v0.7.1: ISC +untrusted v0.9.0: ISC +x25519-dalek v2.0.0-pre.1: BSD-3-Clause +zeroize v1.5.7: Apache-2.0 OR MIT + +-------------------------------------------------------------------------------- + +This list of third-party crates and their licenses was collected for v0.1.6 of +the test client by running this command: + +$ cargo tree --prefix none --edges no-build,no-dev,no-proc-macro --format "{p}: {l}" --no-dedupe | sort -u diff --git a/test/wg-test-client/README.md b/test/wg-test-client/README.md new file mode 100644 index 000000000..cffe47bb9 --- /dev/null +++ b/test/wg-test-client/README.md @@ -0,0 +1,9 @@ +# mitm-wg-test-client + +This directory contains simple test client binaries built from + version v0.1.6. New versions +of the test client binaries are published as release assets on GitHub. + +The test binaries are used for sending WireGuard traffic from userspace in +`tests/mitmproxy/proxy/test_mode_servers.py:test_wireguard`. + diff --git a/test/wg-test-client/linux-x86_64 b/test/wg-test-client/linux-x86_64 new file mode 100755 index 000000000..e514e644a Binary files /dev/null and b/test/wg-test-client/linux-x86_64 differ diff --git a/test/wg-test-client/macos-aarch64 b/test/wg-test-client/macos-aarch64 new file mode 100755 index 000000000..160cbb9f8 Binary files /dev/null and b/test/wg-test-client/macos-aarch64 differ diff --git a/test/wg-test-client/macos-x86_64 b/test/wg-test-client/macos-x86_64 new file mode 100755 index 000000000..85b16e972 Binary files /dev/null and b/test/wg-test-client/macos-x86_64 differ diff --git a/test/wg-test-client/test.conf b/test/wg-test-client/test.conf new file mode 100644 index 000000000..949b1f426 --- /dev/null +++ b/test/wg-test-client/test.conf @@ -0,0 +1,4 @@ +{ + "server_key": "EG47ZWjYjr+Y97TQ1A7sVl7Xn3mMWDnvjU/VxU769ls=", + "client_key": "qG8b7LI/s+ezngWpXqj5A7Nj988hbGL+eQ8ePki0iHk=" +} diff --git a/test/wg-test-client/windows-x86_64.exe b/test/wg-test-client/windows-x86_64.exe new file mode 100755 index 000000000..ed7287cad Binary files /dev/null and b/test/wg-test-client/windows-x86_64.exe differ