diff --git a/.github/workflows/python-publish-macos-and-linux.yml b/.github/workflows/python-publish-macos-and-linux.yml index eb44378..148f773 100644 --- a/.github/workflows/python-publish-macos-and-linux.yml +++ b/.github/workflows/python-publish-macos-and-linux.yml @@ -4,8 +4,8 @@ name: Upload python package for macOS & linux on: - release: - types: [ created ] + pull_request: + branches: [ "master" ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -45,4 +45,3 @@ jobs: CIBW_ARCHS: ${{ matrix.arch }} run: | cibuildwheel --output-dir dist - twine upload dist/* --skip-existing \ No newline at end of file diff --git a/.github/workflows/python-publish-windows.yml b/.github/workflows/python-publish-windows.yml new file mode 100644 index 0000000..6ce92f9 --- /dev/null +++ b/.github/workflows/python-publish-windows.yml @@ -0,0 +1,35 @@ +# This workflows will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload python package for windows + +on: + pull_request: + branches: [ "master" ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: '! github.event.pull_request.draft' + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -U build setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + CIBW_ARCHS: ${{ matrix.arch }} + run: | + python3 -m build diff --git a/pyproject.toml b/pyproject.toml index 602030c..fd0d034 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "pytun-pmd3" version = "1.0.0" -description = "python-pytun fork with Darwin support (IPv6-ONLY)" +description = "python-pytun fork with darwin and windows support (IPv6-ONLY)" readme = "README.md" requires-python = ">=3.8" license = { file = "LICENSE" } -keywords = ["tun", "tuntap", "darwin", "pymobiledevice3", "macos"] +keywords = ["tun", "tuntap", "darwin", "pymobiledevice3", "macos", "windows"] authors = [ { name = "doronz88", email = "doron88@gmail.com" } ] @@ -20,6 +20,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3 :: Only", ] @@ -30,6 +31,9 @@ test = ["pytest"] "Homepage" = "https://github.com/doronz88/pytun-pmd3" "Bug Reports" = "https://github.com/doronz88/pytun-pmd3/issues" +[tool.setuptools] +package-data = { "pytun_pmd3" = ["wintun/**/*"] } + [tool.setuptools.packages.find] exclude = ["docs*", "tests*"] diff --git a/pytun_pmd3/__init__.py b/pytun_pmd3/__init__.py new file mode 100644 index 0000000..003f66a --- /dev/null +++ b/pytun_pmd3/__init__.py @@ -0,0 +1 @@ +from pytun_pmd3.wintun import TunTapDevice diff --git a/pytun_pmd3/exceptions.py b/pytun_pmd3/exceptions.py new file mode 100644 index 0000000..f42f5d6 --- /dev/null +++ b/pytun_pmd3/exceptions.py @@ -0,0 +1,2 @@ +class PyWinTunException(Exception): + pass diff --git a/pytun_pmd3/wintun.py b/pytun_pmd3/wintun.py new file mode 100644 index 0000000..d4cb443 --- /dev/null +++ b/pytun_pmd3/wintun.py @@ -0,0 +1,303 @@ +import ctypes +import platform +import subprocess +from ctypes import POINTER, Structure, WinDLL, byref, c_ubyte, c_ulonglong, c_void_p, create_unicode_buffer, \ + get_last_error, string_at +from ctypes.wintypes import BOOL, BOOLEAN, BYTE, DWORD, HANDLE, LARGE_INTEGER, LPCWSTR, ULARGE_INTEGER, ULONG, USHORT +from pathlib import Path +from socket import AF_INET6 +from uuid import uuid4 + +from pytun_pmd3.exceptions import PyWinTunException + +DEFAULT_ADAPTER_NAME = 'pywintun' +DEFAULT_RING_CAPCITY = 0x400000 + +# Load the Wintun library +wintun = WinDLL(str(Path(__file__).parent / f'wintun/bin/{platform.uname().machine}/wintun.dll')) +iphlpapi = WinDLL('Iphlpapi.dll') +kernel32 = WinDLL('kernel32.dll') + +FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 +FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 + +# Define the return type and argument types of the methods + +UCHAR = c_ubyte + + +class ULARGE_INTEGER(Structure): + _fields_ = [("QuadPart", c_ulonglong)] + + +class MIB_IPINTERFACE_ROW(Structure): + _fields_ = [ + ("Family", ULONG), + ("InterfaceLuid", ULARGE_INTEGER), + ("InterfaceIndex", ULONG), + ("MaxReassemblySize", ULONG), + ("InterfaceIdentifier", c_ulonglong), + ("MinRouterAdvertisementInterval", ULONG), + ("MaxRouterAdvertisementInterval", ULONG), + ("AdvertisingEnabled", BOOLEAN), + ("ForwardingEnabled", BOOLEAN), + ("WeakHostSend", BOOLEAN), + ("WeakHostReceive", BOOLEAN), + ("UseAutomaticMetric", BOOLEAN), + ("UseNeighborUnreachabilityDetection", BOOLEAN), + ("ManagedAddressConfigurationSupported", BOOLEAN), + ("OtherStatefulConfigurationSupported", BOOLEAN), + ("AdvertiseDefaultRoute", BOOLEAN), + ("RouterDiscoveryBehavior", ULONG), + ("DadTransmits", ULONG), + ("BaseReachableTime", ULONG), + ("RetransmitTime", ULONG), + ("PathMtuDiscoveryTimeout", ULONG), + ("LinkLocalAddressBehavior", ULONG), + ("LinkLocalAddressTimeout", ULONG), + ("ZoneIndices", ULONG * 16), + ("SitePrefixLength", ULONG), + ("Metric", ULONG), + ("NlMtu", ULONG), + ("Connected", BOOLEAN), + ("SupportsWakeUpPatterns", BOOLEAN), + ("SupportsNeighborDiscovery", BOOLEAN), + ("SupportsRouterDiscovery", BOOLEAN), + ("ReachableTime", ULONG), + ("TransmitOffload", ULONG), + ("ReceiveOffload", ULONG), + ("DisableDefaultRoutes", BOOLEAN), + ] + + +class SOCKADDR_INET(Structure): + _fields_ = [ + ("Ipv4", ULONG), # Placeholder for union + ("Ipv6", BYTE * 16), + ("si_family", USHORT), + ] + + +class MIB_UNICASTIPADDRESS_ROW(Structure): + _fields_ = [ + ("Address", SOCKADDR_INET), + ("InterfaceLuid", ULARGE_INTEGER), + ("InterfaceIndex", DWORD), + ("PrefixOrigin", DWORD), + ("SuffixOrigin", DWORD), + ("ValidLifetime", DWORD), + ("PreferredLifetime", DWORD), + ("OnLinkPrefixLength", UCHAR), + ("SkipAsSource", BOOLEAN), + ("DadState", DWORD), + ("ScopeId", ULONG), + ("CreationTimeStamp", LARGE_INTEGER), + ] + + +# WintunCreateAdapter(const WCHAR *AdapterName, const GUID *TunnelType, const GUID *RequestedGUID, GUID *AllocatedGUID, +# DWORD *LastError) +wintun.WintunCreateAdapter.restype = HANDLE +wintun.WintunCreateAdapter.argtypes = [LPCWSTR, POINTER(c_ubyte * 16), + POINTER(c_ubyte * 16), POINTER(c_ubyte * 16), + POINTER(DWORD)] + +wintun.WintunCloseAdapter.restype = BOOL +wintun.WintunCloseAdapter.argtypes = [HANDLE] + +wintun.WintunGetAdapterLUID.restype = None +wintun.WintunGetAdapterLUID.argtypes = [HANDLE, POINTER(ULARGE_INTEGER)] + +wintun.WintunStartSession.restype = HANDLE +wintun.WintunStartSession.argtypes = [HANDLE, ULONG] + +wintun.WintunEndSession.restype = None +wintun.WintunEndSession.argtypes = [HANDLE] + +wintun.WintunAllocateSendPacket.restype = POINTER(c_ubyte) +wintun.WintunAllocateSendPacket.argtypes = [HANDLE, DWORD] + +wintun.WintunReceivePacket.restype = POINTER(c_ubyte) +wintun.WintunReceivePacket.argtypes = [HANDLE, POINTER(DWORD)] + +wintun.WintunReleaseReceivePacket.restype = None +wintun.WintunReleaseReceivePacket.argtypes = [HANDLE, POINTER(c_ubyte)] + +wintun.WintunSendPacket.restype = None +wintun.WintunSendPacket.argtypes = [HANDLE, c_void_p] + +iphlpapi.InitializeIpInterfaceEntry.restype = None +iphlpapi.InitializeIpInterfaceEntry.argtypes = [c_void_p] + +iphlpapi.SetIpInterfaceEntry.restype = DWORD +iphlpapi.SetIpInterfaceEntry.argtypes = [c_void_p] + +iphlpapi.GetIpInterfaceEntry.restype = DWORD +iphlpapi.GetIpInterfaceEntry.argtypes = [POINTER(MIB_IPINTERFACE_ROW)] + +iphlpapi.CreateUnicastIpAddressEntry.argtypes = [POINTER(MIB_UNICASTIPADDRESS_ROW)] +iphlpapi.CreateUnicastIpAddressEntry.restype = DWORD + + +def get_error_message(error_code: int) -> str: + """ + Uses FormatMessage to retrieve the system error message for the given error_code. + + :param error_code: The error code for which to get the error message. + :return: The formatted error message string. + """ + buffer = create_unicode_buffer(256) # Adjust the size as necessary + kernel32.FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + None, + error_code, + 0, + buffer, + len(buffer), + None + ) + return buffer.value.strip() + + +def raise_last_error() -> None: + raise Exception(get_error_message(get_last_error())) + + +def set_adapter_mtu(adapter_handle: HANDLE, mtu: int) -> None: + luid = ULARGE_INTEGER() + wintun.WintunGetAdapterLUID(adapter_handle, byref(luid)) + + print('luid', luid, hex(adapter_handle)) + row = MIB_IPINTERFACE_ROW() + iphlpapi.InitializeIpInterfaceEntry(byref(row)) + + row.InterfaceLuid.QuadPart = luid.QuadPart # Ensure correct assignment + row.Family = 2 # Assuming IPv4; for IPv6, use AF_INET6 or 23 + + row.NlMtu = mtu # Set the new MTU value + + # Attempt to set the modified interface entry + result = iphlpapi.SetIpInterfaceEntry(byref(row)) + if result != 0: + raise PyWinTunException(f"Failed to set adapter MTU, error code: {result} ({get_error_message(result)})") + + # Attempt to get the current interface entry to ensure all other fields are correctly populated + result = iphlpapi.GetIpInterfaceEntry(byref(row)) + if result != 0: + raise PyWinTunException(f"Failed to get IP interface entry, error code: {result}") + + +class TunTapDevice: + def __init__(self, name: str = DEFAULT_ADAPTER_NAME) -> None: + tunnel_type_guid = (c_ubyte * 16)(*uuid4().bytes) + requested_guid = (c_ubyte * 16)(*uuid4().bytes) + allocated_guid = (c_ubyte * 16)() # Empty GUID, to be filled by the function + last_error = DWORD() + self._name = name + + self.session = None + + # Create an adapter + self.handle = wintun.WintunCreateAdapter(name, byref(tunnel_type_guid), + byref(requested_guid), + byref(allocated_guid), byref(last_error)) + + if not self.handle: + raise PyWinTunException(f"Failed to create adapter. Last error: {last_error.value}") + + @property + def luid(self) -> c_ulonglong: + luid = ULARGE_INTEGER() + wintun.WintunGetAdapterLUID(self.handle, byref(luid)) + row = MIB_IPINTERFACE_ROW() + iphlpapi.InitializeIpInterfaceEntry(byref(row)) + return luid.QuadPart + + @property + def ip_interface_entry(self) -> MIB_IPINTERFACE_ROW: + row = MIB_IPINTERFACE_ROW() + iphlpapi.InitializeIpInterfaceEntry(byref(row)) + + row.InterfaceLuid.QuadPart = self.luid + row.Family = AF_INET6 + + result = iphlpapi.GetIpInterfaceEntry(byref(row)) + if result != 0: + raise PyWinTunException(f"Failed to get IP interface entry, error code: {result}") + return row + + @ip_interface_entry.setter + def ip_interface_entry(self, value: MIB_IPINTERFACE_ROW) -> None: + result = iphlpapi.SetIpInterfaceEntry(byref(value)) + if result != 0: + raise PyWinTunException(f"Failed to set adapter MTU, error code: {result} ({get_error_message(result)})") + + @property + def mtu(self) -> int: + return self.ip_interface_entry.NlMtu + + @mtu.setter + def mtu(self, value: int) -> None: + row = self.ip_interface_entry + row.NlMtu = value + self.ip_interface_entry = row + + @property + def interface_index(self) -> int: + return self.ip_interface_entry.InterfaceIndex + + def close(self) -> None: + wintun.WintunCloseAdapter(self.handle) + + @property + def addr(self) -> str: + return '' + + @addr.setter + def addr(self, value: str) -> None: + result = subprocess.run(f"netsh interface ipv6 set address interface={self.interface_index} address={value}/64", + shell=True, capture_output=True, text=True) + + # Check result + if result.returncode != 0: + raise PyWinTunException(f"Failed to set IPv6 address. Error: {result.stderr}") + + @property + def name(self) -> str: + return self._name + + def up(self, capacity: int = DEFAULT_RING_CAPCITY) -> None: + self.session = wintun.WintunStartSession(self.handle, capacity) + + def down(self) -> None: + wintun.WintunEndSession(self.session) + self.session = None + + def read(self) -> bytes: + size = DWORD() + packet_ptr = wintun.WintunReceivePacket(self.session, byref(size)) + if not packet_ptr: + # No packet was received + return b'' + + # Create a bytes object from the packet data + packet_data = string_at(packet_ptr, size.value) + + wintun.WintunReleaseReceivePacket(self.session, packet_ptr) + + if (packet_data[0] >> 4) != 6: + # make sure to output only IPv6 packets + return b'' + + return packet_data + + def write(self, payload: bytes) -> None: + if payload.startswith(b'\x00\x00\x86\xdd'): + payload = payload[4:] + + packet_ptr = wintun.WintunAllocateSendPacket(self.session, len(payload)) + if packet_ptr == 0: + raise PyWinTunException('failed to allocate packet') + + ctypes.memmove(packet_ptr, payload, len(payload)) + wintun.WintunSendPacket(self.session, packet_ptr) diff --git a/pytun_pmd3/wintun/LICENSE.txt b/pytun_pmd3/wintun/LICENSE.txt new file mode 100644 index 0000000..bc8d3f0 --- /dev/null +++ b/pytun_pmd3/wintun/LICENSE.txt @@ -0,0 +1,84 @@ +Prebuilt Binaries License +------------------------- + +1. DEFINITIONS. "Software" means the precise contents of the "wintun.dll" + files that are included in the .zip file that contains this document as + downloaded from wintun.net/builds. + +2. LICENSE GRANT. WireGuard LLC grants to you a non-exclusive and + non-transferable right to use Software for lawful purposes under certain + obligations and limited rights as set forth in this agreement. + +3. RESTRICTIONS. Software is owned and copyrighted by WireGuard LLC. It is + licensed, not sold. Title to Software and all associated intellectual + property rights are retained by WireGuard. You must not: + a. reverse engineer, decompile, disassemble, extract from, or otherwise + modify the Software; + b. modify or create derivative work based upon Software in whole or in + parts, except insofar as only the API interfaces of the "wintun.h" file + distributed alongside the Software (the "Permitted API") are used; + c. remove any proprietary notices, labels, or copyrights from the Software; + d. resell, redistribute, lease, rent, transfer, sublicense, or otherwise + transfer rights of the Software without the prior written consent of + WireGuard LLC, except insofar as the Software is distributed alongside + other software that uses the Software only via the Permitted API; + e. use the name of WireGuard LLC, the WireGuard project, the Wintun + project, or the names of its contributors to endorse or promote products + derived from the Software without specific prior written consent. + +4. LIMITED WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF + ANY KIND. WIREGUARD LLC HEREBY EXCLUDES AND DISCLAIMS ALL IMPLIED OR + STATUTORY WARRANTIES, INCLUDING ANY WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE, QUALITY, NON-INFRINGEMENT, TITLE, RESULTS, + EFFORTS, OR QUIET ENJOYMENT. THERE IS NO WARRANTY THAT THE PRODUCT WILL BE + ERROR-FREE OR WILL FUNCTION WITHOUT INTERRUPTION. YOU ASSUME THE ENTIRE + RISK FOR THE RESULTS OBTAINED USING THE PRODUCT. TO THE EXTENT THAT + WIREGUARD LLC MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, + THE SCOPE AND DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER + SUCH LAW. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR + A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE + EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. + +5. LIMITATION OF LIABILITY. To the extent not prohibited by law, in no event + WireGuard LLC or any third-party-developer will be liable for any lost + revenue, profit or data or for special, indirect, consequential, incidental + or punitive damages, however caused regardless of the theory of liability, + arising out of or related to the use of or inability to use Software, even + if WireGuard LLC has been advised of the possibility of such damages. + Solely you are responsible for determining the appropriateness of using + Software and accept full responsibility for all risks associated with its + exercise of rights under this agreement, including but not limited to the + risks and costs of program errors, compliance with applicable laws, damage + to or loss of data, programs or equipment, and unavailability or + interruption of operations. The foregoing limitations will apply even if + the above stated warranty fails of its essential purpose. You acknowledge, + that it is in the nature of software that software is complex and not + completely free of errors. In no event shall WireGuard LLC or any + third-party-developer be liable to you under any theory for any damages + suffered by you or any user of Software or for any special, incidental, + indirect, consequential or similar damages (including without limitation + damages for loss of business profits, business interruption, loss of + business information or any other pecuniary loss) arising out of the use or + inability to use Software, even if WireGuard LLC has been advised of the + possibility of such damages and regardless of the legal or quitable theory + (contract, tort, or otherwise) upon which the claim is based. + +6. TERMINATION. This agreement is affected until terminated. You may + terminate this agreement at any time. This agreement will terminate + immediately without notice from WireGuard LLC if you fail to comply with + the terms and conditions of this agreement. Upon termination, you must + delete Software and all copies of Software and cease all forms of + distribution of Software. + +7. SEVERABILITY. If any provision of this agreement is held to be + unenforceable, this agreement will remain in effect with the provision + omitted, unless omission would frustrate the intent of the parties, in + which case this agreement will immediately terminate. + +8. RESERVATION OF RIGHTS. All rights not expressly granted in this agreement + are reserved by WireGuard LLC. For example, WireGuard LLC reserves the + right at any time to cease development of Software, to alter distribution + details, features, specifications, capabilities, functions, licensing + terms, release dates, APIs, ABIs, general availability, or other + characteristics of the Software. diff --git a/pytun_pmd3/wintun/README.md b/pytun_pmd3/wintun/README.md new file mode 100644 index 0000000..16e6a68 --- /dev/null +++ b/pytun_pmd3/wintun/README.md @@ -0,0 +1,339 @@ +# [Wintun Network Adapter](https://www.wintun.net/) +### TUN Device Driver for Windows + +This is a layer 3 TUN driver for Windows 7, 8, 8.1, and 10. Originally created for [WireGuard](https://www.wireguard.com/), it is intended to be useful to a wide variety of projects that require layer 3 tunneling devices with implementations primarily in userspace. + +## Installation + +Wintun is deployed as a platform-specific `wintun.dll` file. Install the `wintun.dll` file side-by-side with your application. Download the dll from [wintun.net](https://www.wintun.net/), alongside the header file for your application described below. + +## Usage + +Include the [`wintun.h` file](https://git.zx2c4.com/wintun/tree/api/wintun.h) in your project simply by copying it there and dynamically load the `wintun.dll` using [`LoadLibraryEx()`](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa) and [`GetProcAddress()`](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress) to resolve each function, using the typedefs provided in the header file. The [`InitializeWintun` function in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c) provides this in a function that you can simply copy and paste. + +With the library setup, Wintun can then be used by first creating an adapter, configuring it, and then setting its status to "up". Adapters have names (e.g. "OfficeNet") and types (e.g. "Wintun"). + +```C +WINTUN_ADAPTER_HANDLE Adapter1 = WintunCreateAdapter(L"OfficeNet", L"Wintun", &SomeFixedGUID1); +WINTUN_ADAPTER_HANDLE Adapter2 = WintunCreateAdapter(L"HomeNet", L"Wintun", &SomeFixedGUID2); +WINTUN_ADAPTER_HANDLE Adapter3 = WintunCreateAdapter(L"Data Center", L"Wintun", &SomeFixedGUID3); +``` + +After creating an adapter, we can use it by starting a session: + +```C +WINTUN_SESSION_HANDLE Session = WintunStartSession(Adapter2, 0x400000); +``` + +Then, the `WintunAllocateSendPacket` and `WintunSendPacket` functions can be used for sending packets ([used by `SendPackets` in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c)): + +```C +BYTE *OutgoingPacket = WintunAllocateSendPacket(Session, PacketDataSize); +if (OutgoingPacket) +{ + memcpy(OutgoingPacket, PacketData, PacketDataSize); + WintunSendPacket(Session, OutgoingPacket); +} +else if (GetLastError() != ERROR_BUFFER_OVERFLOW) // Silently drop packets if the ring is full + Log(L"Packet write failed"); +``` + +And the `WintunReceivePacket` and `WintunReleaseReceivePacket` functions can be used for receiving packets ([used by `ReceivePackets` in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c)): + +```C +for (;;) +{ + DWORD IncomingPacketSize; + BYTE *IncomingPacket = WintunReceivePacket(Session, &IncomingPacketSize); + if (IncomingPacket) + { + DoSomethingWithPacket(IncomingPacket, IncomingPacketSize); + WintunReleaseReceivePacket(Session, IncomingPacket); + } + else if (GetLastError() == ERROR_NO_MORE_ITEMS) + WaitForSingleObject(WintunGetReadWaitEvent(Session), INFINITE); + else + { + Log(L"Packet read failed"); + break; + } +} +``` + +Some high performance use cases may want to spin on `WintunReceivePackets` for a number of cycles before falling back to waiting on the read-wait event. + +You are **highly encouraged** to read the [**example.c short example**](https://git.zx2c4.com/wintun/tree/example/example.c) to see how to put together a simple userspace network tunnel. + +The various functions and definitions are [documented in the reference below](#Reference). + +## Reference + +### Macro Definitions + +#### WINTUN\_MAX\_POOL + +`#define WINTUN_MAX_POOL 256` + +Maximum pool name length including zero terminator + +#### WINTUN\_MIN\_RING\_CAPACITY + +`#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */` + +Minimum ring capacity. + +#### WINTUN\_MAX\_RING\_CAPACITY + +`#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */` + +Maximum ring capacity. + +#### WINTUN\_MAX\_IP\_PACKET\_SIZE + +`#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF` + +Maximum IP packet size + +### Typedefs + +#### WINTUN\_ADAPTER\_HANDLE + +`typedef void* WINTUN_ADAPTER_HANDLE` + +A handle representing Wintun adapter + +#### WINTUN\_ENUM\_CALLBACK + +`typedef BOOL(* WINTUN_ENUM_CALLBACK) (WINTUN_ADAPTER_HANDLE Adapter, LPARAM Param)` + +Called by WintunEnumAdapters for each adapter in the pool. + +**Parameters** + +- *Adapter*: Adapter handle, which will be freed when this function returns. +- *Param*: An application-defined value passed to the WintunEnumAdapters. + +**Returns** + +Non-zero to continue iterating adapters; zero to stop. + +#### WINTUN\_LOGGER\_CALLBACK + +`typedef void(* WINTUN_LOGGER_CALLBACK) (WINTUN_LOGGER_LEVEL Level, DWORD64 Timestamp, const WCHAR *Message)` + +Called by internal logger to report diagnostic messages + +**Parameters** + +- *Level*: Message level. +- *Timestamp*: Message timestamp in in 100ns intervals since 1601-01-01 UTC. +- *Message*: Message text. + +#### WINTUN\_SESSION\_HANDLE + +`typedef void* WINTUN_SESSION_HANDLE` + +A handle representing Wintun session + +### Enumeration Types + +#### WINTUN\_LOGGER\_LEVEL + +`enum WINTUN_LOGGER_LEVEL` + +Determines the level of logging, passed to WINTUN\_LOGGER\_CALLBACK. + +- *WINTUN\_LOG\_INFO*: Informational +- *WINTUN\_LOG\_WARN*: Warning +- *WINTUN\_LOG\_ERR*: Error + +Enumerator + +### Functions + +#### WintunCreateAdapter() + +`WINTUN_ADAPTER_HANDLE WintunCreateAdapter (const WCHAR * Name, const WCHAR * TunnelType, const GUID * RequestedGUID)` + +Creates a new Wintun adapter. + +**Parameters** + +- *Name*: The requested name of the adapter. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters. +- *Name*: Name of the adapter tunnel type. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters. +- *RequestedGUID*: The GUID of the created network adapter, which then influences NLA generation deterministically. If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is created for each new adapter. It is called "requested" GUID because the API it uses is completely undocumented, and so there could be minor interesting complications with its usage. + +**Returns** + +If the function succeeds, the return value is the adapter handle. Must be released with WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call GetLastError. + +#### WintunOpenAdapter() + +`WINTUN_ADAPTER_HANDLE WintunOpenAdapter (const WCHAR * Name)` + +Opens an existing Wintun adapter. + +**Parameters** + +- *Name*: The requested name of the adapter. Zero-terminated string of up to MAX\_ADAPTER\_NAME-1 characters. + +**Returns** + +If the function succeeds, the return value is adapter handle. Must be released with WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call GetLastError. + +#### WintunCloseAdapter() + +`void WintunCloseAdapter (WINTUN_ADAPTER_HANDLE Adapter)` + +Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter. + +**Parameters** + +- *Adapter*: Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter. + +#### WintunDeleteDriver() + +`BOOL WintunDeleteDriver ()` + +Deletes the Wintun driver if there are no more adapters in use. + +**Returns** + +If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError. + +#### WintunGetAdapterLuid() + +`void WintunGetAdapterLuid (WINTUN_ADAPTER_HANDLE Adapter, NET_LUID * Luid)` + +Returns the LUID of the adapter. + +**Parameters** + +- *Adapter*: Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter +- *Luid*: Pointer to LUID to receive adapter LUID. + +#### WintunGetRunningDriverVersion() + +`DWORD WintunGetRunningDriverVersion (void )` + +Determines the version of the Wintun driver currently loaded. + +**Returns** + +If the function succeeds, the return value is the version number. If the function fails, the return value is zero. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_FILE\_NOT\_FOUND Wintun not loaded + +#### WintunSetLogger() + +`void WintunSetLogger (WINTUN_LOGGER_CALLBACK NewLogger)` + +Sets logger callback function. + +**Parameters** + +- *NewLogger*: Pointer to callback function to use as a new global logger. NewLogger may be called from various threads concurrently. Should the logging require serialization, you must handle serialization in NewLogger. Set to NULL to disable. + +#### WintunStartSession() + +`WINTUN_SESSION_HANDLE WintunStartSession (WINTUN_ADAPTER_HANDLE Adapter, DWORD Capacity)` + +Starts Wintun session. + +**Parameters** + +- *Adapter*: Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter +- *Capacity*: Rings capacity. Must be between WINTUN\_MIN\_RING\_CAPACITY and WINTUN\_MAX\_RING\_CAPACITY (incl.) Must be a power of two. + +**Returns** + +Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is NULL. To get extended error information, call GetLastError. + +#### WintunEndSession() + +`void WintunEndSession (WINTUN_SESSION_HANDLE Session)` + +Ends Wintun session. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession + +#### WintunGetReadWaitEvent() + +`HANDLE WintunGetReadWaitEvent (WINTUN_SESSION_HANDLE Session)` + +Gets Wintun session's read-wait event handle. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession + +**Returns** + +Pointer to receive event handle to wait for available data when reading. Should WintunReceivePackets return ERROR\_NO\_MORE\_ITEMS (after spinning on it for a while under heavy load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call CloseHandle on this event - it is managed by the session. + +#### WintunReceivePacket() + +`BYTE* WintunReceivePacket (WINTUN_SESSION_HANDLE Session, DWORD * PacketSize)` + +Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned from this function to release internal buffer. This function is thread-safe. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession +- *PacketSize*: Pointer to receive packet size. + +**Returns** + +Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_HANDLE\_EOF Wintun adapter is terminating; ERROR\_NO\_MORE\_ITEMS Wintun buffer is exhausted; ERROR\_INVALID\_DATA Wintun buffer is corrupt + +#### WintunReleaseReceivePacket() + +`void WintunReleaseReceivePacket (WINTUN_SESSION_HANDLE Session, const BYTE * Packet)` + +Releases internal buffer after the received packet has been processed by the client. This function is thread-safe. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession +- *Packet*: Packet obtained with WintunReceivePacket + +#### WintunAllocateSendPacket() + +`BYTE* WintunAllocateSendPacket (WINTUN_SESSION_HANDLE Session, DWORD PacketSize)` + +Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of calls define the packet sending order. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession +- *PacketSize*: Exact packet size. Must be less or equal to WINTUN\_MAX\_IP\_PACKET\_SIZE. + +**Returns** + +Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Possible errors include the following: ERROR\_HANDLE\_EOF Wintun adapter is terminating; ERROR\_BUFFER\_OVERFLOW Wintun buffer is full; + +#### WintunSendPacket() + +`void WintunSendPacket (WINTUN_SESSION_HANDLE Session, const BYTE * Packet)` + +Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the WintunSendPacket yet. + +**Parameters** + +- *Session*: Wintun session handle obtained with WintunStartSession +- *Packet*: Packet obtained with WintunAllocateSendPacket + +## Building + +**Do not distribute drivers or files named "Wintun", as they will most certainly clash with official deployments. Instead distribute [`wintun.dll` as downloaded from wintun.net](https://www.wintun.net).** + +General requirements: + +- [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) with Windows SDK +- [Windows Driver Kit](https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk) + +`wintun.sln` may be opened in Visual Studio for development and building. Be sure to run `bcdedit /set testsigning on` and then reboot before to enable unsigned driver loading. The default run sequence (F5) in Visual Studio will build the example project and its dependencies. + +## License + +The entire contents of [the repository](https://git.zx2c4.com/wintun/), including all documentation and example code, is "Copyright © 2018-2021 WireGuard LLC. All Rights Reserved." Source code is licensed under the [GPLv2](COPYING). Prebuilt binaries from [wintun.net](https://www.wintun.net/) are released under a more permissive license suitable for more forms of software contained inside of the .zip files distributed there. diff --git a/pytun_pmd3/wintun/bin/amd64/wintun.dll b/pytun_pmd3/wintun/bin/amd64/wintun.dll new file mode 100644 index 0000000..aee04e7 Binary files /dev/null and b/pytun_pmd3/wintun/bin/amd64/wintun.dll differ diff --git a/pytun_pmd3/wintun/bin/arm/wintun.dll b/pytun_pmd3/wintun/bin/arm/wintun.dll new file mode 100644 index 0000000..0017794 Binary files /dev/null and b/pytun_pmd3/wintun/bin/arm/wintun.dll differ diff --git a/pytun_pmd3/wintun/bin/arm64/wintun.dll b/pytun_pmd3/wintun/bin/arm64/wintun.dll new file mode 100644 index 0000000..dc4e4ae Binary files /dev/null and b/pytun_pmd3/wintun/bin/arm64/wintun.dll differ diff --git a/pytun_pmd3/wintun/bin/x86/wintun.dll b/pytun_pmd3/wintun/bin/x86/wintun.dll new file mode 100644 index 0000000..2ab97db Binary files /dev/null and b/pytun_pmd3/wintun/bin/x86/wintun.dll differ diff --git a/pytun_pmd3/wintun/include/wintun.h b/pytun_pmd3/wintun/include/wintun.h new file mode 100644 index 0000000..55d441f --- /dev/null +++ b/pytun_pmd3/wintun/include/wintun.h @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT + * + * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ALIGNED +# if defined(_MSC_VER) +# define ALIGNED(n) __declspec(align(n)) +# elif defined(__GNUC__) +# define ALIGNED(n) __attribute__((aligned(n))) +# else +# error "Unable to define ALIGNED" +# endif +#endif + +/* MinGW is missing this one, unfortunately. */ +#ifndef _Post_maybenull_ +# define _Post_maybenull_ +#endif + +#pragma warning(push) +#pragma warning(disable : 4324) /* structure was padded due to alignment specifier */ + +/** + * A handle representing Wintun adapter + */ +typedef struct _WINTUN_ADAPTER *WINTUN_ADAPTER_HANDLE; + +/** + * Creates a new Wintun adapter. + * + * @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @param TunnelType Name of the adapter tunnel type. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @param RequestedGUID The GUID of the created network adapter, which then influences NLA generation deterministically. + * If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is + * created for each new adapter. It is called "requested" GUID because the API it uses is + * completely undocumented, and so there could be minor interesting complications with its usage. + * + * @return If the function succeeds, the return value is the adapter handle. Must be released with + * WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call + * GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_CREATE_ADAPTER_FUNC) +(_In_z_ LPCWSTR Name, _In_z_ LPCWSTR TunnelType, _In_opt_ const GUID *RequestedGUID); + +/** + * Opens an existing Wintun adapter. + * + * @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1 + * characters. + * + * @return If the function succeeds, the return value is the adapter handle. Must be released with + * WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call + * GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_OPEN_ADAPTER_FUNC)(_In_z_ LPCWSTR Name); + +/** + * Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter. + * + * @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter. + */ +typedef VOID(WINAPI WINTUN_CLOSE_ADAPTER_FUNC)(_In_opt_ WINTUN_ADAPTER_HANDLE Adapter); + +/** + * Deletes the Wintun driver if there are no more adapters in use. + * + * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To + * get extended error information, call GetLastError. + */ +typedef _Return_type_success_(return != FALSE) +BOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID); + +/** + * Returns the LUID of the adapter. + * + * @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter + * + * @param Luid Pointer to LUID to receive adapter LUID. + */ +typedef VOID(WINAPI WINTUN_GET_ADAPTER_LUID_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _Out_ NET_LUID *Luid); + +/** + * Determines the version of the Wintun driver currently loaded. + * + * @return If the function succeeds, the return value is the version number. If the function fails, the return value is + * zero. To get extended error information, call GetLastError. Possible errors include the following: + * ERROR_FILE_NOT_FOUND Wintun not loaded + */ +typedef _Return_type_success_(return != 0) +DWORD(WINAPI WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC)(VOID); + +/** + * Determines the level of logging, passed to WINTUN_LOGGER_CALLBACK. + */ +typedef enum +{ + WINTUN_LOG_INFO, /**< Informational */ + WINTUN_LOG_WARN, /**< Warning */ + WINTUN_LOG_ERR /**< Error */ +} WINTUN_LOGGER_LEVEL; + +/** + * Called by internal logger to report diagnostic messages + * + * @param Level Message level. + * + * @param Timestamp Message timestamp in in 100ns intervals since 1601-01-01 UTC. + * + * @param Message Message text. + */ +typedef VOID(CALLBACK *WINTUN_LOGGER_CALLBACK)( + _In_ WINTUN_LOGGER_LEVEL Level, + _In_ DWORD64 Timestamp, + _In_z_ LPCWSTR Message); + +/** + * Sets logger callback function. + * + * @param NewLogger Pointer to callback function to use as a new global logger. NewLogger may be called from various + * threads concurrently. Should the logging require serialization, you must handle serialization in + * NewLogger. Set to NULL to disable. + */ +typedef VOID(WINAPI WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_CALLBACK NewLogger); + +/** + * Minimum ring capacity. + */ +#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */ + +/** + * Maximum ring capacity. + */ +#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */ + +/** + * A handle representing Wintun session + */ +typedef struct _TUN_SESSION *WINTUN_SESSION_HANDLE; + +/** + * Starts Wintun session. + * + * @param Adapter Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter + * + * @param Capacity Rings capacity. Must be between WINTUN_MIN_RING_CAPACITY and WINTUN_MAX_RING_CAPACITY (incl.) + * Must be a power of two. + * + * @return Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is + * NULL. To get extended error information, call GetLastError. + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +WINTUN_SESSION_HANDLE(WINAPI WINTUN_START_SESSION_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _In_ DWORD Capacity); + +/** + * Ends Wintun session. + * + * @param Session Wintun session handle obtained with WintunStartSession + */ +typedef VOID(WINAPI WINTUN_END_SESSION_FUNC)(_In_ WINTUN_SESSION_HANDLE Session); + +/** + * Gets Wintun session's read-wait event handle. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @return Pointer to receive event handle to wait for available data when reading. Should + * WintunReceivePackets return ERROR_NO_MORE_ITEMS (after spinning on it for a while under heavy + * load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call + * CloseHandle on this event - it is managed by the session. + */ +typedef HANDLE(WINAPI WINTUN_GET_READ_WAIT_EVENT_FUNC)(_In_ WINTUN_SESSION_HANDLE Session); + +/** + * Maximum IP packet size + */ +#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF + +/** + * Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned + * from this function to release internal buffer. This function is thread-safe. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param PacketSize Pointer to receive packet size. + * + * @return Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the + * return value is NULL. To get extended error information, call GetLastError. Possible errors include the + * following: + * ERROR_HANDLE_EOF Wintun adapter is terminating; + * ERROR_NO_MORE_ITEMS Wintun buffer is exhausted; + * ERROR_INVALID_DATA Wintun buffer is corrupt + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(*PacketSize) +BYTE *(WINAPI WINTUN_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _Out_ DWORD *PacketSize); + +/** + * Releases internal buffer after the received packet has been processed by the client. This function is thread-safe. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Packet Packet obtained with WintunReceivePacket + */ +typedef VOID( + WINAPI WINTUN_RELEASE_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet); + +/** + * Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send + * and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of + * calls define the packet sending order. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param PacketSize Exact packet size. Must be less or equal to WINTUN_MAX_IP_PACKET_SIZE. + * + * @return Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails, + * the return value is NULL. To get extended error information, call GetLastError. Possible errors include the + * following: + * ERROR_HANDLE_EOF Wintun adapter is terminating; + * ERROR_BUFFER_OVERFLOW Wintun buffer is full; + */ +typedef _Must_inspect_result_ +_Return_type_success_(return != NULL) +_Post_maybenull_ +_Post_writable_byte_size_(PacketSize) +BYTE *(WINAPI WINTUN_ALLOCATE_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ DWORD PacketSize); + +/** + * Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket + * order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the + * WintunSendPacket yet. + * + * @param Session Wintun session handle obtained with WintunStartSession + * + * @param Packet Packet obtained with WintunAllocateSendPacket + */ +typedef VOID(WINAPI WINTUN_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet); + +#pragma warning(pop) + +#ifdef __cplusplus +} +#endif diff --git a/setup.py b/setup.py index d65be74..c0ba173 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,17 @@ import sys - from pathlib import Path -from setuptools import setup, Extension -if sys.platform == 'darwin': - sources = ['darwin_pytun.c'] -else: - sources = ['linux_pytun.c'] +from setuptools import Extension, find_packages, setup -setup(name='pytun-pmd3', ext_modules=[ - Extension('pytun_pmd3', sources, include_dirs=[Path(__file__).parent])], extra_compile_args = ["-Wall", "-Wextra", "-pedantic"]) +if sys.platform in ['darwin', 'linux']: + if sys.platform == 'darwin': + sources = ['darwin_pytun.c'] + else: + sources = ['linux_pytun.c'] + + setup(name='pytun-pmd3', ext_modules=[ + Extension('pytun_pmd3', sources, include_dirs=[Path(__file__).parent])], + extra_compile_args=["-Wall", "-Wextra", "-pedantic"]) +else: + # windows + setup(name='pytun-pmd3', packages=find_packages()) diff --git a/test/test_tap.py b/test/test_tap.py index cbee910..8de62c3 100644 --- a/test/test_tap.py +++ b/test/test_tap.py @@ -1,10 +1,12 @@ -import sys +import errno import optparse -import socket import select -import errno +import socket +import sys + import pytun + class TunnelServer(object): def __init__(self, taddr, tmask, tmtu, laddr, lport, raddr, rport): diff --git a/test/test_tun.py b/test/test_tun.py index 11c5696..a9de0cc 100644 --- a/test/test_tun.py +++ b/test/test_tun.py @@ -1,10 +1,12 @@ -import sys +import errno import optparse -import socket import select -import errno +import socket +import sys + import pytun + class TunnelServer(object): def __init__(self, taddr, tdstaddr, tmask, tmtu, laddr, lport, raddr, rport):